Recherche…


Introduction

Ce tutoriel vous guidera à travers les étapes pour créer un simple calque personnalisé pour Caffe en utilisant python. À la fin, il existe des exemples de couches personnalisées. Généralement, vous créez une couche personnalisée pour implémenter une fonctionnalité qui n'est pas disponible dans Caffe, en l'adaptant à vos besoins.

La création d'une couche personnalisée python ajoute un peu de temps à votre réseau et n'est probablement pas aussi efficace qu'une couche personnalisée C ++. Cependant, de cette façon, vous n'aurez pas à compiler tout le caffe avec votre nouveau calque.

Paramètres

Paramètre Détails
Haut Un tableau avec les blobs supérieurs de votre couche. Accéder aux données qui lui sont transmises en utilisant top [i] .data , où i est l'index d'un blob spécifique
bas Un tableau avec les taches de fond de votre couche. Accédez aux données qui lui sont transmises en utilisant les données de base [i] , où i est l'index d'un objet blob spécifique

Remarques

- Caffe construit avec la couche Python

Caffe doit être compilé avec l'option WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- Où dois-je enregistrer le fichier de classe?

Vous avez deux options (du moins que je connais). Soit vous pouvez enregistrer le fichier de couche personnalisé dans le même dossier que vous allez exécuter la commande caffe (probablement là où se trouveraient vos fichiers prototxt). Une autre méthode, également préférée, consiste à enregistrer toutes vos couches personnalisées dans un dossier et à ajouter ce dossier à votre PYTHONPATH.

Les références

  1. Le blog de Christopher Bourez
  2. Caffe Github
  3. StackOverflow

Modèle de calque

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

Des choses si importantes à retenir:

  • Votre couche personnalisée doit hériter de caffe.Layer (n'oubliez donc pas d' importer caffe );
  • Vous devez définir les quatre méthodes suivantes: setup , forward , reshape et backward ;
  • Toutes les méthodes ont un haut et un bas paramètres, qui sont les blobs qui stockent l'entrée et la sortie passée à votre couche. Vous pouvez y accéder en utilisant les données supérieures [i] .data ou en bas [i]. , Où i est l'index du blob au cas où vous auriez plus d'un blob supérieur ou inférieur.

- Méthode de configuration

La méthode Setup est appelée une fois au cours de l'exécution, lorsque Caffe instancie toutes les couches. C'est là que vous allez lire les paramètres, instancier des tampons de taille fixe.

- Méthode de remodelage

Utilisez la méthode de remise en forme pour l'initialisation / la configuration qui dépend de la taille du blob inférieur (entrée de couche). Il est appelé une fois lorsque le réseau est instancié.

- méthode de transfert

La méthode Forward est appelée pour chaque lot d'entrée et correspond à la plupart de vos logiques.

- méthode arrière

La méthode Backward est appelée lors du retour en arrière du réseau. Par exemple, dans une couche de type convolution, ce serait l'endroit où calculer les dégradés. Ceci est facultatif (un calque ne peut être que vers l'avant).

Modèle Prototxt

Ok, maintenant vous avez votre couche conçue! Voici comment vous le définissez dans votre fichier .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
  }
}

Remarques importantes:

  • le type doit être Python ;
  • Vous devez avoir un dictionnaire python_param avec au moins les paramètres de module et de couche ;
  • module fait référence au fichier où vous avez implémenté votre couche (sans le .py );
  • le calque fait référence au nom de votre classe;
  • Vous pouvez passer des paramètres à la couche en utilisant param_str (plus sur leur accès ci-dessous);
  • Tout comme n'importe quel autre calque, vous pouvez définir dans quelle phase vous souhaitez qu'il soit actif (voir les exemples pour voir comment vérifier la phase en cours);

Passer des paramètres à la couche

Vous pouvez définir les paramètres de couche dans le prototxt en utilisant param_str . Une fois que vous l'avez fait, voici un exemple de la manière dont vous accédez à ces paramètres dans la classe de couche:

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

Mesurer la couche

Dans cet exemple, nous allons concevoir un calque «mesure», qui affiche la précision et une matrice de confusion pour un problème binaire pendant l’entraînement et la précision, le taux de faux positifs et le taux de faux négatifs pendant le test / validation. Bien que Caffe ait déjà une couche de précision, vous voulez parfois quelque chose de plus, comme une mesure F.

Ceci est mon measureLayer.py avec ma définition de 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

Et voici un exemple de prototxt avec:

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

Couche de données

Cet exemple est une couche de données personnalisée qui reçoit un fichier texte avec des chemins d’image, charge un lot d’images et les pré-traite. Juste un petit conseil, Caffe a déjà une grande gamme de couches de données et probablement un calque personnalisé n'est pas le moyen le plus efficace si vous voulez juste quelque chose de simple.

Mon dataLayer.py pourrait être quelque chose comme:

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

Et le prototxt serait comme:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow