caffe
Aangepaste Python-lagen
Zoeken…
Invoering
Deze zelfstudie leidt u door de stappen om een eenvoudige aangepaste laag voor Caffe te maken met python. Tegen het einde zijn er enkele voorbeelden van aangepaste lagen. Gewoonlijk zou u een aangepaste laag maken om een functionaliteit te implementeren die niet beschikbaar is in Caffe, en deze afstemmen op uw vereisten.
Het maken van een aangepaste python-laag voegt wat overhead toe aan uw netwerk en is waarschijnlijk niet zo efficiënt als een aangepaste C ++ -laag. Op deze manier hoef je echter niet de hele caffe te compileren met je nieuwe laag.
parameters
| Parameter | Details |
|---|---|
| top | Een array met de bovenste blobs van je laag. Toegang tot gegevens die worden doorgegeven met behulp van top [i] .data , waarbij i de index is van een specifieke blob |
| bodem | Een array met de onderste klodders van je laag. Toegang tot gegevens die worden doorgegeven met behulp van bottom [i] .data , waarbij i de index is van een specifieke blob |
Opmerkingen
- Caffe gebouwd met Python-laag
Caffe moet worden gecompileerd met de optie WITH_PYTHON_LAYER :
WITH_PYTHON_LAYER=1 make && make pycaffe
- Waar moet ik het klassenbestand opslaan?
Je hebt twee opties (tenminste dat weet ik). U kunt het bestand met de aangepaste laag opslaan in dezelfde map als waarin u het caffe-commando gaat uitvoeren (waarschijnlijk waar uw prototxt-bestanden zouden zijn). Een andere manier, ook mijn favoriete, is om al je aangepaste lagen in een map op te slaan en deze map toe te voegen aan je PYTHONPATH.
Referenties
Laagsjabloon
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
Zo belangrijke dingen om te onthouden:
- Je aangepaste laag moet erven van caffe.Layer (vergeet dus niet om caffe te importeren );
- U moet de volgende vier methoden definiëren: instellen , doorsturen , opnieuw vormen en achteruit ;
- Alle methoden hebben een bovenste en onderste parameter, dit zijn de blobs die de invoer opslaan en de uitvoer die aan uw laag wordt doorgegeven. Je kunt er toegang toe krijgen met bovenste [i] .data of onderste [i] .data , waarbij i de index van de blob is voor het geval je meer dan één bovenste of onderste blob hebt.
- Instellingsmethode
De installatiemethode wordt eenmaal tijdens de levensduur van de uitvoering aangeroepen, wanneer Caffe alle lagen instantieert. Hier leest u parameters en maakt u buffers met een vaste grootte.
- Verander de methode
Gebruik de hervormingsmethode voor initialisatie / instelling die afhankelijk is van de grootte van de onderste blob (laaginvoer). Het wordt één keer opgeroepen wanneer het netwerk wordt geïnstantieerd.
- Doorstuurmethode
De Forward-methode wordt voor elke invoerbatch aangeroepen en is waar het grootste deel van uw logica zal zijn.
- Achteruitmethode
De Achteruitmethode wordt aangeroepen tijdens de achterwaartse doorgang van het netwerk. In een convolutie-achtige laag zou dit bijvoorbeeld zijn waar u de verlopen zou berekenen. Dit is optioneel (een laag kan alleen doorsturen).
Prototxt-sjabloon
Ok, dus nu heb je je laag ontworpen! Zo definieert u het in uw .prototxt- bestand:
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
}
}
Belangrijke opmerkingen:
- type moet Python zijn ;
- U moet een python_param woordenboek met ten minste de module en laagparameters hebben;
- module verwijst naar het bestand waarin u uw laag heeft geïmplementeerd (zonder de .py );
- laag verwijst naar de naam van uw klas;
- U kunt parameters doorgeven aan de laag met behulp van param_str (meer informatie over toegang hieronder);
- Net als elke andere laag, kunt u bepalen in welke fase u wilt dat deze actief is (zie de voorbeelden om te zien hoe u de huidige fase kunt controleren);
Parameters doorgeven aan de laag
U kunt de laagparameters in het prototekst definiëren met behulp van param_str . Nadat u dit hebt gedaan, volgt hier een voorbeeld van hoe u toegang krijgt tot deze paremeters in de laagklasse:
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
# ...
Laag meten
In dit voorbeeld zullen we een "meet" -laag ontwerpen, die de nauwkeurigheid en een verwarringmatrix voor een binair probleem tijdens de training en de nauwkeurigheid, fout-positieve snelheid en fout-negatieve snelheid tijdens test / validatie uitvoert. Hoewel Caffe al een Nauwkeurigheidslaag heeft, wil je soms iets meer, zoals een F-maat.
Dit is mijn maatregelLayer.py met mijn klassendefinitie:
#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
En dit is een voorbeeld van een prototekst ermee:
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
}
}
Gegevenslaag
Dit voorbeeld is een aangepaste gegevenslaag die een tekstbestand met afbeeldingspaden ontvangt, een reeks afbeeldingen laadt en deze voorbewerkt. Gewoon een snelle tip, Caffe heeft al een groot aantal gegevenslagen en waarschijnlijk is een aangepaste laag niet de meest efficiënte manier als u gewoon iets eenvoudigs wilt.
Mijn dataLayer.py kan zoiets zijn als:
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
En de prototekst zou zijn als:
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"}'
}
}