caffe
Livelli personalizzati di Python
Ricerca…
introduzione
Questo tutorial guiderà attraverso i passaggi per creare un semplice livello personalizzato per Caffe usando python. Alla fine di esso, ci sono alcuni esempi di livelli personalizzati. Di solito dovresti creare un livello personalizzato per implementare una funzionalità che non è disponibile in Caffe, sintonizzandola per le tue esigenze.
La creazione di un livello personalizzato python aggiunge un sovraccarico alla rete e probabilmente non è efficiente come un livello personalizzato C ++. Tuttavia, in questo modo, non dovrai compilare l'intero caffe con il tuo nuovo livello.
Parametri
| Parametro | Dettagli |
|---|---|
| superiore | Una matrice con i blob top del tuo livello. Accedere ai dati trasmessi utilizzando top [i] .data , dove i è l'indice di un blob specifico |
| parte inferiore | Una matrice con i blob in basso del tuo livello. Accedere ai dati passati ad esso usando bottom [i] .data , dove i è l'indice di un blob specifico |
Osservazioni
- Caffe build con strato Python
Caffe deve essere compilato con WITH_PYTHON_LAYER opzione WITH_PYTHON_LAYER :
WITH_PYTHON_LAYER=1 make && make pycaffe
- Dove devo salvare il file di classe?
Hai due opzioni (almeno che io sappia). O è possibile salvare il file di livello personalizzato nella stessa cartella in cui si eseguirà il comando caffe (probabilmente dove sarebbero i file di prototipo). Un altro modo, anche il mio preferito, è salvare tutti i livelli personalizzati in una cartella e aggiungere questa cartella al tuo PYTHONPATH.
Riferimenti
Modello di livello
import caffe
class My_Custom_Layer(caffe.Layer):
def setup(self, bottom, top):
pass
def forward(self, bottom, top):
pass
def reshape(self, bottom, top):
pass
def backward(self, bottom, top):
pass
Cose così importanti da ricordare:
- Il tuo livello personalizzato deve ereditare da caffe.Layer (quindi non dimenticare di importare caffe );
- È necessario definire i seguenti quattro metodi: setup , forward , reshape e backward ;
- Tutti i metodi hanno i parametri top e bottom , che sono i BLOB che memorizzano l'input e l'output passato al layer. Puoi accedervi usando top [i] .data o bottom [i] .data , dove i è l'indice del blob nel caso in cui tu abbia più di un blob superiore o inferiore.
- Metodo di installazione
Il metodo Setup viene chiamato una volta durante la vita dell'esecuzione, quando Caffe crea un'istanza di tutti i livelli. Qui è dove potrete leggere i parametri, creare istanze di buffer a dimensione fissa.
- Metodo di rimodellamento
Utilizzare il metodo di risagoma per l'inizializzazione / impostazione che dipende dalla dimensione del blob in basso (livello di input). Viene chiamato una volta quando viene creata un'istanza della rete.
- Metodo di inoltro
Il metodo Forward viene chiamato per ogni batch di input ed è dove la maggior parte della tua logica sarà.
- Metodo a ritroso
Il metodo Indietro viene chiamato durante il passaggio all'indietro della rete. Ad esempio, in un livello simile a una convoluzione, questo sarebbe il punto in cui dovresti calcolare i gradienti. Questo è opzionale (un livello può essere solo inoltrato).
Modello di prototesto
Ok, ora hai progettato il tuo livello! Questo è il modo in cui lo definisci nel tuo file .prototxt :
layer {
name: "LayerName"
type: "Python"
top: "TopBlobName"
bottom: "BottomBlobName"
python_param {
module: "My_Custom_Layer_File"
layer: "My_Custom_Layer_Class"
param_str: '{"param1": 1,"param2":True, "param3":"some string"}'
}
include{
phase: TRAIN
}
}
Osservazioni importanti:
- il tipo deve essere Python ;
- Devi avere un dizionario python_param con almeno i parametri module e layer ;
- modulo si riferisce al file in cui hai implementato il tuo layer (senza il .py );
- livello si riferisce al nome della tua classe;
- Puoi passare i parametri al layer usando param_str (altro per accedervi più sotto);
- Proprio come qualsiasi altro livello, puoi definire in quale fase vuoi che sia attivo (vedi gli esempi per vedere come puoi controllare la fase corrente);
Passare i parametri al livello
È possibile definire i parametri del livello nel prototesto usando param_str . Una volta che lo hai fatto, ecco un esempio su come accedi a questi parametri all'interno della classe del livello:
def setup(self, bottom, top):
params = eval(self.param_str)
param1 = params["param1"]
param2 = params.get('param2', False) #I usually use this when fetching a bool
param3 = params["param3"]
#Continue with the setup
# ...
Misura livello
In questo esempio progetteremo uno strato "di misura", che emette la precisione e una matrice di confusione per un problema binario durante l'allenamento e l'accuratezza, il tasso di falsi positivi e il tasso di falsi negativi durante il test / la convalida. Anche se Caffe ha già un livello Accuracy, a volte vuoi qualcosa di più, come un F-measure.
Questo è il mio measureLayer.py con la mia definizione di classe:
#Remark: This class is designed for a binary problem, where the first class would be the 'negative'
# and the second class would be 'positive'
import caffe
TRAIN = 0
TEST = 1
class Measure_Layer(caffe.Layer):
#Setup method
def setup(self, bottom, top):
#We want two bottom blobs, the labels and the predictions
if len(bottom) != 2:
raise Exception("Wrong number of bottom blobs (prediction and label)")
#And some top blobs, depending on the phase
if self.phase = TEST and len(top) != 3:
raise Exception("Wrong number of top blobs (acc, FPR, FNR)")
if self.phase = TRAIN and len(top) != 5:
raise Exception("Wrong number of top blobs (acc, tp, tn, fp and fn)")
#Initialize some attributes
self.TPs = 0.0
self.TNs = 0.0
self.FPs = 0.0
self.FNs = 0.0
self.totalImgs = 0
#Forward method
def forward(self, bottom, top):
#The order of these depends on the prototxt definition
predictions = bottom[0].data
labels = bottom[1].data
self.totalImgs += len(labels)
for i in range(len(labels)): #len(labels) is equal to the batch size
pred = predictions[i] #pred is a tuple with the normalized probability
#of a sample i.r.t. two classes
lab = labels[i]
if pred[0] > pred[1]:
if lab == 1.0:
self.FNs += 1.0
else:
self.TNs += 1.0
else:
if lab == 1.0:
self.TPs += 1.0
else:
self.FPs += 1.0
acc = (self.TPs + self.TNs) / self.totalImgs
try: #just assuring we don't divide by 0
fpr = self.FPs / (self.FPs + self.TNs)
except:
fpr = -1.0
try: #just assuring we don't divide by 0
fnr = self.FNs / (self.FNs + self.TPs)
except:
fnr = -1.0
#output data to top blob
top[0].data = acc
if self.phase == TRAIN:
top[1].data = self.TPs
top[2].data = self.TNs
top[3].data = self.FPs
top[4].data = self.FNs
elif self.phase == TEST:
top[1].data = fpr
top[2].data = fnr
def reshape(self, bottom, top):
"""
We don't need to reshape or instantiate anything that is input-size sensitive
"""
pass
def backward(self, bottom, top):
"""
This layer does not back propagate
"""
pass
E questo è un esempio di un prototipo con esso:
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "TPs"
top: "TNs"
top: "FPs"
top: "FNs"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TRAIN
}
}
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "FPR"
top: "FNR"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TEST
}
}
Livello dati
Questo esempio è un livello dati personalizzato, che riceve un file di testo con percorsi di immagine, carica un lotto di immagini e le pre-processa. Solo un suggerimento, Caffe ha già una vasta gamma di livelli di dati e probabilmente un livello personalizzato non è il modo più efficiente se vuoi semplicemente qualcosa di semplice.
Il mio dataLayer.py potrebbe essere qualcosa del tipo:
import caffe
class Custom_Data_Layer(caffe.Layer):
def setup(self, bottom, top):
# Check top shape
if len(top) != 2:
raise Exception("Need to define tops (data and label)")
#Check bottom shape
if len(bottom) != 0:
raise Exception("Do not define a bottom.")
#Read parameters
params = eval(self.param_str)
src_file = params["src_file"]
self.batch_size = params["batch_size"]
self.im_shape = params["im_shape"]
self.crop_size = params.get("crop_size", False)
#Reshape top
if self.crop_size:
top[0].reshape(self.batch_size, 3, self.crop_size, self.crop_size)
else:
top[0].reshape(self.batch_size, 3, self.im_shape, self.im_shape)
top[1].reshape(self.batch_size)
#Read source file
#I'm just assuming we have this method that reads the source file
#and returns a list of tuples in the form of (img, label)
self.imgTuples = readSrcFile(src_file)
self._cur = 0 #use this to check if we need to restart the list of imgs
def forward(self, bottom, top):
for itt in range(self.batch_size):
# Use the batch loader to load the next image.
im, label = self.load_next_image()
#Here we could preprocess the image
# ...
# Add directly to the top blob
top[0].data[itt, ...] = im
top[1].data[itt, ...] = label
def load_next_img(self):
#If we have finished forwarding all images, then an epoch has finished
#and it is time to start a new one
if self._cur == len(self.imgTuples):
self._cur = 0
shuffle(self.imgTuples)
im, label = self.imgTuples[self._cur]
self._cur += 1
return im, label
def reshape(self, bottom, top):
"""
There is no need to reshape the data, since the input is of fixed size
(img shape and batch size)
"""
pass
def backward(self, bottom, top):
"""
This layer does not back propagate
"""
pass
E il prototipo sarebbe come:
layer {
name: "Data"
type: "Python"
top: "data"
top: "label"
python_param {
module: "dataLayer"
layer: "Custom_Data_Layer"
param_str: '{"batch_size": 126,"im_shape":256, "crop_size":224, "src_file": "path_to_TRAIN_file.txt"}'
}
}