caffe
Benutzerdefinierte Python-Ebenen
Suche…
Einführung
Dieses Tutorial führt Sie durch die Schritte zum Erstellen einer einfachen benutzerdefinierten Ebene für Caffe mit Python. Am Ende gibt es einige Beispiele für benutzerdefinierte Ebenen. Normalerweise erstellen Sie eine benutzerdefinierte Ebene, um eine Funktionalität zu implementieren, die in Caffe nicht verfügbar ist, und sie an Ihre Anforderungen anpassen.
Durch das Erstellen einer benutzerdefinierten Python-Schicht wird Ihr Netzwerk etwas belastet und ist wahrscheinlich nicht so effizient wie eine benutzerdefinierte C ++ - Schicht. Auf diese Weise müssen Sie jedoch nicht die gesamte Caffe mit Ihrer neuen Ebene zusammenstellen.
Parameter
| Parameter | Einzelheiten |
|---|---|
| oben | Ein Array mit den oberen Flecken Ihrer Ebene. Zugriff auf Daten, die mit top [i] .data übergeben werden , wobei i der Index eines bestimmten Blobs ist |
| Unterseite | Ein Array mit den unteren Flecken Ihrer Ebene. Zugreifen auf Daten, die mithilfe von bottom [i] .data übergeben werden , wobei i der Index eines bestimmten Blobs ist |
Bemerkungen
- Caffe Build mit Python-Layer
Caffe muss mit der WITH_PYTHON_LAYER Option kompiliert werden:
WITH_PYTHON_LAYER=1 make && make pycaffe
- Wo soll ich die Klassendatei speichern?
Sie haben zwei Möglichkeiten (zumindest die ich kenne). Sie können die benutzerdefinierte Ebenendatei auch in demselben Ordner speichern, in dem Sie den Befehl caffe ausführen (wahrscheinlich dort, wo sich Ihre Prototxtdateien befinden würden). Eine andere Möglichkeit, auch meine bevorzugte, besteht darin, alle benutzerdefinierten Ebenen in einem Ordner zu speichern und diesen Ordner zu Ihrem PYTHONPATH hinzuzufügen.
Verweise
Ebenenvorlage
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
So wichtige Dinge zu beachten:
- Ihre benutzerdefinierte Ebene muss von caffe erben. Layer (vergessen Sie nicht, caffe zu importieren ).
- Sie müssen die vier folgenden Methoden definieren: setup , forward , reshape und backward ;
- Alle Methoden verfügen über einen oberen und einen unteren Parameter. Hierbei handelt es sich um die Blobs, die die Eingabe und die Ausgabe speichern, die an Ihre Ebene übergeben werden. Sie können darauf mit top [i] .data oder bottom [i] .data zugreifen , wobei i der Index des Blobs ist, falls Sie mehr als einen oberen oder unteren Blob haben.
- Einrichtungsmethode
Die Setup-Methode wird einmal während der Laufzeit der Ausführung aufgerufen, wenn Caffe alle Layer instanziiert. Hier werden Sie Parameter lesen und Puffer mit fester Größe instanziieren.
- Umformmethode
Verwenden Sie die Umformungsmethode für die Initialisierung / Einrichtung, die von der Größe des unteren Blobs (Ebeneneingabe) abhängt. Es wird einmal aufgerufen, wenn das Netzwerk instanziiert wird.
- Weiterleitungsmethode
Die Weiterleitungsmethode wird für jeden Eingangsstapel aufgerufen. Hier finden Sie den größten Teil Ihrer Logik.
- Rückwärtsmethode
Die Backward-Methode wird während des Rückwärtsdurchlaufs des Netzwerks aufgerufen. In einer Faltungs-ähnlichen Ebene würden Sie beispielsweise die Gradienten berechnen. Dies ist optional (eine Ebene kann nur vorwärts sein).
Prototxt-Vorlage
Ok, jetzt hast du deine Ebene entworfen! So definieren Sie es in Ihrer .prototxt- Datei:
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
}
}
Wichtige Anmerkungen:
- Typ muss Python sein .
- Sie müssen über ein Python_param- Wörterbuch mit mindestens den Modul- und Layer- Parametern verfügen.
- Modul bezieht sich auf die Datei, in der Sie Ihre Ebene implementiert haben (ohne .py );
- Layer bezieht sich auf den Namen Ihrer Klasse;
- Sie können Parameter mit param_str an den Layer übergeben (mehr über den Zugriff auf sie unten).
- Wie bei jeder anderen Ebene können Sie festlegen, in welcher Phase sie aktiviert werden soll.
Parameter an die Ebene übergeben
Sie können die Layer-Parameter im prototxt mit param_str definieren . Wenn Sie dies getan haben, finden Sie hier ein Beispiel, wie Sie auf diese Parameter innerhalb der Layer-Klasse zugreifen können:
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
# ...
Ebene messen
In diesem Beispiel entwerfen wir eine "Messschicht", die die Genauigkeit und eine Verwirrungsmatrix für ein binäres Problem während des Trainings und die Genauigkeit, False-Positive-Rate und False-Negative-Rate während des Tests / der Validierung ausgibt. Obwohl Caffe bereits über eine Genauigkeitsebene verfügt, möchten Sie manchmal mehr, z. B. ein F-Maß.
Dies ist mein measureLayer.py mit meiner Klassendefinition:
#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
Und dies ist ein Beispiel eines Prototxtes damit:
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
}
}
Datenschicht
In diesem Beispiel handelt es sich um eine benutzerdefinierte Datenschicht, die eine Textdatei mit Bildpfaden empfängt, einen Stapel von Bildern lädt und diese vorverarbeitet. Nur ein kleiner Tipp: Caffe verfügt bereits über eine große Auswahl an Datenebenen. Wahrscheinlich ist eine benutzerdefinierte Ebene nicht die effizienteste Methode, wenn Sie nur etwas einfaches wollen.
Mein dataLayer.py könnte ungefähr so aussehen:
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
Und der Prototxt wäre wie folgt :
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"}'
}
}