caffe
Niestandardowe warstwy Python
Szukaj…
Wprowadzenie
Ten samouczek poprowadzi Cię przez kolejne etapy tworzenia prostej niestandardowej warstwy dla Caffe przy użyciu Pythona. Na końcu jest kilka przykładów niestandardowych warstw. Zwykle tworzysz niestandardową warstwę, aby zaimplementować funkcję niedostępną w Caffe, dostosowując ją do swoich wymagań.
Utworzenie niestandardowej warstwy python powoduje dodatkowe obciążenie sieci i prawdopodobnie nie jest tak wydajne jak niestandardowa warstwa C ++. Jednak w ten sposób nie będziesz musiał kompilować całej kawy z nową warstwą.
Parametry
| Parametr | Detale |
|---|---|
| Top | Tablica z górnymi kroplami twojej warstwy. Dostęp do danych przekazywanych do niego przy użyciu top [i] .data , gdzie i jest indeksem określonego obiektu blob |
| Dolny | Tablica z dolnymi kroplami twojej warstwy. Uzyskaj dostęp do danych przekazywanych do niego przy użyciu bottom [i] .data , gdzie i jest indeksem określonego obiektu blob |
Uwagi
- Kompilacja Caffe z warstwą Python
Caffe należy skompilować z opcją WITH_PYTHON_LAYER :
WITH_PYTHON_LAYER=1 make && make pycaffe
- Gdzie mam zapisać plik zajęć?
Masz dwie opcje (przynajmniej o których wiem). Możesz zapisać niestandardowy plik warstwy w tym samym folderze, w którym zamierzasz uruchomić polecenie caffe (prawdopodobnie tam, gdzie byłyby twoje pliki prototxt). Innym sposobem, również moim ulubionym, jest zapisanie wszystkich niestandardowych warstw w folderze i dodanie tego folderu do PYTHONPATH.
Bibliografia
Szablon warstwy
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
Tak ważne rzeczy do zapamiętania:
- Twoja niestandardowa warstwa musi dziedziczyć po caffe.Layer (więc nie zapomnij zaimportować caffe );
- Musisz zdefiniować cztery następujące metody: setup , forward , reshape i backward ;
- Wszystkie metody mają górne i dolne parametry, które są kroplami przechowującymi dane wejściowe i wyjściowe przekazywane do warstwy. Możesz uzyskać do niego dostęp za pomocą górnych [i] .data lub dolnych [i] .data , gdzie i to indeks obiektu blob w przypadku, gdy masz więcej niż jeden górny lub dolny obiekt blob.
- Metoda instalacji
Metoda instalacji jest wywoływana jeden raz w trakcie wykonywania programu, gdy Caffe tworzy instancję wszystkich warstw. Tutaj odczytujesz parametry, tworzysz bufory o stałym rozmiarze.
- Metoda przekształcenia
Użyj metody zmiany kształtu do inicjalizacji / konfiguracji, która zależy od rozmiaru dolnego obiektu blob (wejście warstwy). Jest on wywoływany raz, gdy tworzona jest sieć.
- Metoda przekazania
Metoda Forward jest wywoływana dla każdej partii wejściowej i jest tam, gdzie będzie większość logiki.
- Metoda wsteczna
Metoda wsteczna jest wywoływana podczas wstecznego przejścia sieci. Na przykład w warstwie podobnej do splotu w tym miejscu można obliczyć gradienty. Jest to opcjonalne (warstwa może być tylko do przodu).
Szablon Prototxt
Ok, więc teraz masz zaprojektowaną warstwę! Oto jak definiujesz to w pliku .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
}
}
Ważne uwagi:
- typ musi być Python ;
- Musisz mieć słownik python_param z co najmniej parametrami modułu i warstw ;
- moduł odnosi się do pliku, w którym zaimplementowałeś swoją warstwę (bez .py );
- warstwa odnosi się do nazwy twojej klasy;
- Możesz przekazać parametry do warstwy używając param_str (więcej o dostępie poniżej);
- Podobnie jak każda inna warstwa, możesz zdefiniować, w której fazie ma być aktywna (zobacz przykłady, aby sprawdzić, jak można sprawdzić bieżącą fazę);
Przekazywanie parametrów do warstwy
Możesz zdefiniować parametry warstwy w prototxt za pomocą param_str . Gdy to zrobisz, oto przykład, w jaki sposób uzyskujesz dostęp do tych parametrów w klasie warstw:
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
# ...
Zmierz warstwę
W tym przykładzie zaprojektujemy warstwę „miary”, która generuje dokładność i matrycę pomyłek dla problemu binarnego podczas treningu oraz dokładność, częstość fałszywie dodatnich i fałszywie ujemnych wyników podczas testu / walidacji. Chociaż Caffe ma już warstwę dokładności, czasami potrzebujesz czegoś więcej, na przykład miary F.
Oto moja miaraLayer.py z definicją mojej klasy:
#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
A to jest przykład prototekstu z nim:
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
}
}
Warstwa danych
Ten przykład to niestandardowa warstwa danych, która odbiera plik tekstowy ze ścieżkami obrazu, ładuje partię obrazów i przetwarza je wstępnie. Krótka wskazówka, Caffe ma już duży zakres warstw danych i prawdopodobnie warstwa niestandardowa nie jest najbardziej wydajnym sposobem, jeśli chcesz czegoś prostego.
Mój dataLayer.py może wyglądać następująco:
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
Prototxt wyglądałby tak:
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"}'
}
}