Ricerca…


introduzione

Questo tutorial guiderà attraverso i passaggi per creare un semplice livello personalizzato per Caffe usando python. Alla fine di esso, ci sono alcuni esempi di livelli personalizzati. Di solito dovresti creare un livello personalizzato per implementare una funzionalità che non è disponibile in Caffe, sintonizzandola per le tue esigenze.

La creazione di un livello personalizzato python aggiunge un sovraccarico alla rete e probabilmente non è efficiente come un livello personalizzato C ++. Tuttavia, in questo modo, non dovrai compilare l'intero caffe con il tuo nuovo livello.

Parametri

Parametro Dettagli
superiore Una matrice con i blob top del tuo livello. Accedere ai dati trasmessi utilizzando top [i] .data , dove i è l'indice di un blob specifico
parte inferiore Una matrice con i blob in basso del tuo livello. Accedere ai dati passati ad esso usando bottom [i] .data , dove i è l'indice di un blob specifico

Osservazioni

- Caffe build con strato Python

Caffe deve essere compilato con WITH_PYTHON_LAYER opzione WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- Dove devo salvare il file di classe?

Hai due opzioni (almeno che io sappia). O è possibile salvare il file di livello personalizzato nella stessa cartella in cui si eseguirà il comando caffe (probabilmente dove sarebbero i file di prototipo). Un altro modo, anche il mio preferito, è salvare tutti i livelli personalizzati in una cartella e aggiungere questa cartella al tuo PYTHONPATH.

Riferimenti

  1. Il blog di Christopher Bourez
  2. Caffe Github
  3. StackOverflow

Modello di livello

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

Cose così importanti da ricordare:

  • Il tuo livello personalizzato deve ereditare da caffe.Layer (quindi non dimenticare di importare caffe );
  • È necessario definire i seguenti quattro metodi: setup , forward , reshape e backward ;
  • Tutti i metodi hanno i parametri top e bottom , che sono i BLOB che memorizzano l'input e l'output passato al layer. Puoi accedervi usando top [i] .data o bottom [i] .data , dove i è l'indice del blob nel caso in cui tu abbia più di un blob superiore o inferiore.

- Metodo di installazione

Il metodo Setup viene chiamato una volta durante la vita dell'esecuzione, quando Caffe crea un'istanza di tutti i livelli. Qui è dove potrete leggere i parametri, creare istanze di buffer a dimensione fissa.

- Metodo di rimodellamento

Utilizzare il metodo di risagoma per l'inizializzazione / impostazione che dipende dalla dimensione del blob in basso (livello di input). Viene chiamato una volta quando viene creata un'istanza della rete.

- Metodo di inoltro

Il metodo Forward viene chiamato per ogni batch di input ed è dove la maggior parte della tua logica sarà.

- Metodo a ritroso

Il metodo Indietro viene chiamato durante il passaggio all'indietro della rete. Ad esempio, in un livello simile a una convoluzione, questo sarebbe il punto in cui dovresti calcolare i gradienti. Questo è opzionale (un livello può essere solo inoltrato).

Modello di prototesto

Ok, ora hai progettato il tuo livello! Questo è il modo in cui lo definisci nel tuo file .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
  }
}

Osservazioni importanti:

  • il tipo deve essere Python ;
  • Devi avere un dizionario python_param con almeno i parametri module e layer ;
  • modulo si riferisce al file in cui hai implementato il tuo layer (senza il .py );
  • livello si riferisce al nome della tua classe;
  • Puoi passare i parametri al layer usando param_str (altro per accedervi più sotto);
  • Proprio come qualsiasi altro livello, puoi definire in quale fase vuoi che sia attivo (vedi gli esempi per vedere come puoi controllare la fase corrente);

Passare i parametri al livello

È possibile definire i parametri del livello nel prototesto usando param_str . Una volta che lo hai fatto, ecco un esempio su come accedi a questi parametri all'interno della classe del livello:

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

Misura livello

In questo esempio progetteremo uno strato "di misura", che emette la precisione e una matrice di confusione per un problema binario durante l'allenamento e l'accuratezza, il tasso di falsi positivi e il tasso di falsi negativi durante il test / la convalida. Anche se Caffe ha già un livello Accuracy, a volte vuoi qualcosa di più, come un F-measure.

Questo è il mio measureLayer.py con la mia definizione di classe:

#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

E questo è un esempio di un prototipo con esso:

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

Livello dati

Questo esempio è un livello dati personalizzato, che riceve un file di testo con percorsi di immagine, carica un lotto di immagini e le pre-processa. Solo un suggerimento, Caffe ha già una vasta gamma di livelli di dati e probabilmente un livello personalizzato non è il modo più efficiente se vuoi semplicemente qualcosa di semplice.

Il mio dataLayer.py potrebbe essere qualcosa del tipo:

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

E il prototipo sarebbe come:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow