caffe
Пользовательские слои Python
Поиск…
Вступление
В этом руководстве будут описаны шаги по созданию простого пользовательского уровня для Caffe с использованием python. В конце этого есть несколько примеров пользовательских слоев. Обычно вы создаете настраиваемый уровень для реализации функциональности, недоступной в Caffe, настраивая ее для ваших требований.
Создание пользовательского уровня на python добавляет некоторые накладные расходы в вашу сеть и, вероятно, не так эффективен, как пользовательский уровень C ++. Однако, таким образом, вам не придется составлять весь кофе с вашим новым слоем.
параметры
| параметр | подробности |
|---|---|
| Топ | Массив с верхними блоками вашего слоя. Доступ к данным, переданным ему, с использованием верхних [i] .data , где i - индекс конкретного blob |
| низ | Массив с нижними каплями вашего слоя. Получите доступ к данным, переданным ему, используя нижние [i] .data , где i - индекс конкретного blob |
замечания
- Caffe build с слоем Python
Caffe необходимо скомпилировать с WITH_PYTHON_LAYER опции WITH_PYTHON_LAYER :
WITH_PYTHON_LAYER=1 make && make pycaffe
- Где я должен сохранить файл класса?
У вас есть два варианта (по крайней мере, я знаю). Либо вы можете сохранить файл пользовательского слоя в той же папке, в какой вы собираетесь запускать команду caffe (вероятно, где будут файлы ваших прототипов). Другим способом, также моим любимым, является сохранение всех ваших пользовательских слоев в папке и добавление этой папки в ваш PYTHONPATH.
Рекомендации
Шаблон слоя
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
Так важно помнить:
- Ваш пользовательский слой должен наследовать от caffe.Layer (так что не забудьте импортировать кофе );
- Вы должны определить четыре следующих метода: настройку , перемотку , изменение и назад ;
- Все методы имеют верхний и нижний параметры, которые являются блоками, которые хранят вход и выход, переданные вашему слою. Вы можете получить к нему доступ, используя верхние [i] .data или bottom [i] .data , где i - индекс blob, если у вас более одного верхнего или нижнего blob.
- Способ установки
Метод Setup вызывается один раз в течение всего времени выполнения, когда Caffe создает все слои. Здесь вы будете читать параметры, создавать буферы фиксированного размера.
- Изменить форму
Используйте метод reshape для инициализации / настройки, который зависит от размера нижней ячейки (слоя ввода). Он вызывается один раз, когда сеть создается.
- Передовой метод
Метод Forward вызывается для каждой входной партии и где большая часть вашей логики будет.
- Обратный метод
Обратный метод вызывается во время обратного прохода сети. Например, в свертоподобном слое это будет то, где вы будете вычислять градиенты. Это необязательно (слой может быть только для пересылки).
Шаблон Prototxt
Итак, теперь у вас есть свой слой! Так вы определяете его в файле .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
}
}
Важные замечания:
- тип должен быть Python ;
- У вас должен быть словарь python_param с параметрами модуля и уровня ;
- модуль относится к файлу, в котором вы реализовали свой слой (без .py );
- layer ссылается на имя вашего класса;
- Вы можете передать параметры на уровень с помощью param_str (подробнее о доступе к ним ниже);
- Как и любой другой уровень, вы можете определить, на каком этапе вы хотите, чтобы он был активным (см. Примеры, чтобы увидеть, как вы можете проверить текущую фазу);
Передача параметров на уровень
Вы можете определить параметры слоя в prototxt, используя param_str . После того, как вы это сделали, вот пример того, как вы обращаетесь к этим параметрам внутри класса слоев:
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
# ...
Уровень измерения
В этом примере мы создадим слой «меру», который выводит точность и матрицу путаницы для двоичной проблемы во время обучения, а также точность, ложную положительную скорость и ложную отрицательную скорость во время теста / проверки. Хотя у Caffe уже есть уровень Accuracy, иногда вам нужно что-то большее, например, F-меру.
Это мой параметр measureLayer.py с моим определением класса:
#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
И вот пример прототипа :
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
}
}
Уровень данных
Этот пример представляет собой пользовательский уровень данных, который получает текстовый файл с путями изображений, загружает пакет изображений и препроцессы их. Просто быстрый совет, у Caffe уже есть большой диапазон слоев данных, и, вероятно, пользовательский уровень не самый эффективный способ, если вы просто хотите что-то простое.
Мой dataLayer.py может выглядеть примерно так:
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
И прототип будет выглядеть так:
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"}'
}
}