Поиск…


Вступление

В этом руководстве будут описаны шаги по созданию простого пользовательского уровня для 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.

Рекомендации

  1. Блог Кристофера Буреса
  2. Caffe Github
  3. Переполнение стека

Шаблон слоя

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"}'
  }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow