Buscar..


Introducción

Este tutorial lo guiará a través de los pasos para crear una capa personalizada simple para Caffe usando python. Al final, hay algunos ejemplos de capas personalizadas. Por lo general, crearía una capa personalizada para implementar una funcionalidad que no está disponible en Caffe, ajustándola a sus requisitos.

La creación de una capa personalizada de Python agrega algo de sobrecarga a su red y probablemente no sea tan eficiente como una capa personalizada de C ++. Sin embargo, de esta manera, no tendrá que compilar todo el cafe con su nueva capa.

Parámetros

Parámetro Detalles
parte superior Una matriz con las mejores manchas de tu capa. Acceda a los datos que se le pasan usando top [i] .data , donde i es el índice de un blob específico
fondo Una matriz con las manchas inferiores de tu capa. Acceda a los datos que se le pasan usando la parte inferior [i] .data , donde i es el índice de un blob específico

Observaciones

- Caffe construir con la capa de Python

Caffe debe compilarse con la opción WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- ¿Dónde debo guardar el archivo de clase?

Tienes dos opciones (al menos que yo sepa). O bien, puede guardar el archivo de capa personalizado en la misma carpeta en la que va a ejecutar el comando caffe (probablemente donde se encuentren sus archivos de prototxt). Otra forma, también mi favorita, es guardar todas sus capas personalizadas en una carpeta y agregar esta carpeta a su PYTHONPATH.

Referencias

  1. El blog de Christopher Bourez.
  2. Caffe Github
  3. Desbordamiento de pila

Plantilla de capa

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

Cosas tan importantes para recordar:

  • Tu capa personalizada debe heredar de caffe.Layer (así que no olvides importar caffe );
  • Debe definir los cuatro métodos siguientes: configuración , reenvío , remodelación y retroceso ;
  • Todos los métodos tienen los parámetros superior e inferior , que son las burbujas que almacenan la entrada y la salida que se pasa a su capa. Puede acceder a él utilizando top [i] .data o bottom [i] .data , donde i es el índice del blob en caso de que tenga más de un blob superior o inferior.

- Método de configuración

El método de configuración se llama una vez durante el tiempo de vida de la ejecución, cuando Caffe crea una instancia de todas las capas. Aquí es donde leerá los parámetros, creará una instancia de los buffers de tamaño fijo.

- remodelar el método

Utilice el método de remodelación para la inicialización / configuración que depende del tamaño del blob inferior (entrada de capa). Se llama una vez cuando se crea una instancia de la red.

- Método hacia adelante

Se llama al método Forward para cada lote de entrada y es donde estará la mayor parte de su lógica.

- Método hacia atrás

El método Backward se llama durante el paso hacia atrás de la red. Por ejemplo, en una capa similar a una convolución, esto sería donde se calcularían los gradientes. Esto es opcional (una capa puede ser solo hacia adelante).

Plantilla Prototxt

Ok, ahora tienes tu capa diseñada! Así es como lo define en su archivo .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
  }
}

Observaciones importantes:

  • el tipo debe ser Python ;
  • Debe tener un diccionario python_param con al menos el módulo y los parámetros de capa ;
  • módulo se refiere al archivo donde implementó su capa (sin el .py );
  • capa se refiere al nombre de su clase;
  • Puede pasar parámetros a la capa usando param_str (más información sobre cómo acceder a ellos más adelante);
  • Al igual que cualquier otra capa, puede definir en qué fase desea que esté activa (vea los ejemplos para ver cómo puede verificar la fase actual);

Pasando parámetros a la capa.

Puede definir los parámetros de capa en el prototxt usando param_str . Una vez que lo haya hecho, este es un ejemplo de cómo acceder a estos parámetros dentro de la clase de capa:

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
    # ...

Capa de medida

En este ejemplo, diseñaremos una capa de "medida", que genera la matriz de precisión y confusión para un problema binario durante el entrenamiento y la precisión, la tasa de falsos positivos y la tasa de falsos negativos durante la prueba / validación. Aunque Caffe ya tiene una capa de precisión, a veces quieres algo más, como una F-medida.

Este es mi measureLayer.py con mi definición de clase:

#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

Y este es un ejemplo de un prototxt con él:

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

Capa de datos

Este ejemplo es una capa de datos personalizada, que recibe un archivo de texto con rutas de imagen, carga un lote de imágenes y las procesa previamente. Solo una sugerencia rápida, Caffe ya tiene una gran variedad de capas de datos y, probablemente, una capa personalizada no es la forma más eficiente si solo desea algo simple.

Mi dataLayer.py podría ser algo como:

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

Y el prototexto sería como:

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow