Zoeken…


Invoering

Deze zelfstudie leidt u door de stappen om een eenvoudige aangepaste laag voor Caffe te maken met python. Tegen het einde zijn er enkele voorbeelden van aangepaste lagen. Gewoonlijk zou u een aangepaste laag maken om een functionaliteit te implementeren die niet beschikbaar is in Caffe, en deze afstemmen op uw vereisten.

Het maken van een aangepaste python-laag voegt wat overhead toe aan uw netwerk en is waarschijnlijk niet zo efficiënt als een aangepaste C ++ -laag. Op deze manier hoef je echter niet de hele caffe te compileren met je nieuwe laag.

parameters

Parameter Details
top Een array met de bovenste blobs van je laag. Toegang tot gegevens die worden doorgegeven met behulp van top [i] .data , waarbij i de index is van een specifieke blob
bodem Een array met de onderste klodders van je laag. Toegang tot gegevens die worden doorgegeven met behulp van bottom [i] .data , waarbij i de index is van een specifieke blob

Opmerkingen

- Caffe gebouwd met Python-laag

Caffe moet worden gecompileerd met de optie WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- Waar moet ik het klassenbestand opslaan?

Je hebt twee opties (tenminste dat weet ik). U kunt het bestand met de aangepaste laag opslaan in dezelfde map als waarin u het caffe-commando gaat uitvoeren (waarschijnlijk waar uw prototxt-bestanden zouden zijn). Een andere manier, ook mijn favoriete, is om al je aangepaste lagen in een map op te slaan en deze map toe te voegen aan je PYTHONPATH.

Referenties

  1. Blog van Christopher Bourez
  2. Caffe Github
  3. StackOverflow

Laagsjabloon

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

Zo belangrijke dingen om te onthouden:

  • Je aangepaste laag moet erven van caffe.Layer (vergeet dus niet om caffe te importeren );
  • U moet de volgende vier methoden definiëren: instellen , doorsturen , opnieuw vormen en achteruit ;
  • Alle methoden hebben een bovenste en onderste parameter, dit zijn de blobs die de invoer opslaan en de uitvoer die aan uw laag wordt doorgegeven. Je kunt er toegang toe krijgen met bovenste [i] .data of onderste [i] .data , waarbij i de index van de blob is voor het geval je meer dan één bovenste of onderste blob hebt.

- Instellingsmethode

De installatiemethode wordt eenmaal tijdens de levensduur van de uitvoering aangeroepen, wanneer Caffe alle lagen instantieert. Hier leest u parameters en maakt u buffers met een vaste grootte.

- Verander de methode

Gebruik de hervormingsmethode voor initialisatie / instelling die afhankelijk is van de grootte van de onderste blob (laaginvoer). Het wordt één keer opgeroepen wanneer het netwerk wordt geïnstantieerd.

- Doorstuurmethode

De Forward-methode wordt voor elke invoerbatch aangeroepen en is waar het grootste deel van uw logica zal zijn.

- Achteruitmethode

De Achteruitmethode wordt aangeroepen tijdens de achterwaartse doorgang van het netwerk. In een convolutie-achtige laag zou dit bijvoorbeeld zijn waar u de verlopen zou berekenen. Dit is optioneel (een laag kan alleen doorsturen).

Prototxt-sjabloon

Ok, dus nu heb je je laag ontworpen! Zo definieert u het in uw .prototxt- bestand:

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

Belangrijke opmerkingen:

  • type moet Python zijn ;
  • U moet een python_param woordenboek met ten minste de module en laagparameters hebben;
  • module verwijst naar het bestand waarin u uw laag heeft geïmplementeerd (zonder de .py );
  • laag verwijst naar de naam van uw klas;
  • U kunt parameters doorgeven aan de laag met behulp van param_str (meer informatie over toegang hieronder);
  • Net als elke andere laag, kunt u bepalen in welke fase u wilt dat deze actief is (zie de voorbeelden om te zien hoe u de huidige fase kunt controleren);

Parameters doorgeven aan de laag

U kunt de laagparameters in het prototekst definiëren met behulp van param_str . Nadat u dit hebt gedaan, volgt hier een voorbeeld van hoe u toegang krijgt tot deze paremeters in de laagklasse:

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

Laag meten

In dit voorbeeld zullen we een "meet" -laag ontwerpen, die de nauwkeurigheid en een verwarringmatrix voor een binair probleem tijdens de training en de nauwkeurigheid, fout-positieve snelheid en fout-negatieve snelheid tijdens test / validatie uitvoert. Hoewel Caffe al een Nauwkeurigheidslaag heeft, wil je soms iets meer, zoals een F-maat.

Dit is mijn maatregelLayer.py met mijn klassendefinitie:

#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

En dit is een voorbeeld van een prototekst ermee:

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

Gegevenslaag

Dit voorbeeld is een aangepaste gegevenslaag die een tekstbestand met afbeeldingspaden ontvangt, een reeks afbeeldingen laadt en deze voorbewerkt. Gewoon een snelle tip, Caffe heeft al een groot aantal gegevenslagen en waarschijnlijk is een aangepaste laag niet de meest efficiënte manier als u gewoon iets eenvoudigs wilt.

Mijn dataLayer.py kan zoiets zijn als:

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

En de prototekst zou zijn als:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow