caffe
Anpassade Python-lager
Sök…
Introduktion
Den här handledningen kommer att guida igenom stegen för att skapa ett enkelt anpassat lager för Caffe med python. I slutet av det finns det några exempel på anpassade lager. Vanligtvis skapar du ett anpassat lager för att implementera en funktionalitet som inte är tillgänglig i Caffe och stämma in det efter dina behov.
Att skapa ett anpassat pythonlager lägger till lite overhead till ditt nätverk och är förmodligen inte lika effektivt som ett C ++ anpassat lager. Men på detta sätt behöver du inte sammanställa hela caffe med ditt nya lager.
parametrar
| Parameter | detaljer |
|---|---|
| topp | En matris med de översta klumparna i ditt lager. Få åtkomst till data som skickas till den genom att använda topp [i] .data , där i är indexet för en specifik klump |
| botten | En matris med bottenflaskorna i ditt lager. Få åtkomst till data som skickas till det genom att använda botten [i] .data , där i är indexet för en specifik klump |
Anmärkningar
- Caffe build med Python-lager
Caffe måste sammanställas med alternativet WITH_PYTHON_LAYER :
WITH_PYTHON_LAYER=1 make && make pycaffe
- Var ska jag spara klassfilen?
Du har två alternativ (åtminstone det jag känner till). Antingen kan du spara den anpassade lagringsfilen i samma mapp som du ska köra caffe-kommandot (förmodligen där dina prototxtfiler skulle vara). Ett annat sätt, också min favorit, är att spara alla dina anpassade lager i en mapp och lägga till den här mappen till din PYTHONPATH.
referenser
Skiktmall
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
Så viktiga saker att komma ihåg:
- Ditt anpassade lager måste ärva från caffe.Layer (så glöm inte att importera caffe );
- Du måste definiera de fyra följande metoderna: installation , framåt , omformning och bakåt ;
- Alla metoder har topp- och bottenparametrar , som är klumparna som lagrar ingången och den utgång som skickas till ditt lager. Du får åtkomst till den med hjälp av övre [i] .data eller botten [i] .data , där i är indexet för kloden om du har mer än en övre eller nedre klump.
- Inställningsmetod
Setup-metoden kallas en gång under körningens livstid, när Caffe instanterar alla lager. Det är här du kommer att läsa parametrar, instansera buffertar i fast storlek.
- Omforma metoden
Använd omformningsmetoden för initiering / inställning som beror på storleken på bottenklumpen (lagerinmatning). Det kallas en gång när nätverket är inställt.
- Framåtmetod
Framåt-metoden kallas för varje inmatningsbatch och är där det mesta av din logik kommer att vara.
- Bakåtmetod
Metoden Bakåt kallas under nätverkets bakåtpassning. Till exempel i ett upplösningsliknande lager skulle det vara här du skulle beräkna lutningarna. Detta är valfritt (ett lager kan endast vara framåt).
Prototxtmall
Ok, så nu har du ditt lager utformat! Så här definierar du det i din .prototxt- fil:
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
}
}
Viktiga kommentarer:
- typen måste vara Python ;
- Du måste ha en python_param ordbok med åtminstone modulen och lagerparametrar;
- modul hänvisar till filen där du implementerade ditt lager (utan .py );
- lager hänvisar till namnet på din klass;
- Du kan skicka parametrar till lagret med param_str (mer om hur du får åtkomst till dem).
- Precis som alla andra lager kan du definiera i vilken fas du vill att den ska vara aktiv (se exemplen för att se hur du kan kontrollera den aktuella fasen);
Att överföra parametrar till lagret
Du kan definiera lagerparametrarna i prototekst med param_str . När du har gjort det här är ett exempel på hur du får åtkomst till dessa parametrar i lagerklassen:
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
# ...
Mät lager
I det här exemplet kommer vi att utforma ett "mått" -lager, som matar ut noggrannheten och en förvirringsmatris för ett binärt problem under träning och noggrannhet, falsk positiv hastighet och falsk negativ hastighet under test / validering. Även om Caffe redan har ett noggrannhetsskikt, vill du ibland ha något mer, som en F-åtgärd.
Detta är min measureLayer.py med min klassdefinition:
#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
Och detta är ett exempel på en prototxt med det:
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
}
}
Datalager
Det här exemplet är ett anpassat dataskikt som tar emot en textfil med bildvägar, laddar en grupp bilder och förbehandlar dem. Bara ett snabbt tips, Caffe har redan ett stort utbud av dataskikt och förmodligen är ett anpassat lager inte det mest effektiva sättet om du bara vill ha något enkelt.
Min dataLayer.py kan vara något som:
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
Och prototypen skulle vara som:
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"}'
}
}