Sök…


Introduktion

Den här handledningen kommer att guida igenom stegen för att skapa ett enkelt anpassat lager för Caffe med python. I slutet av det finns det några exempel på anpassade lager. Vanligtvis skapar du ett anpassat lager för att implementera en funktionalitet som inte är tillgänglig i Caffe och stämma in det efter dina behov.

Att skapa ett anpassat pythonlager lägger till lite overhead till ditt nätverk och är förmodligen inte lika effektivt som ett C ++ anpassat lager. Men på detta sätt behöver du inte sammanställa hela caffe med ditt nya lager.

parametrar

Parameter detaljer
topp En matris med de översta klumparna i ditt lager. Få åtkomst till data som skickas till den genom att använda topp [i] .data , där i är indexet för en specifik klump
botten En matris med bottenflaskorna i ditt lager. Få åtkomst till data som skickas till det genom att använda botten [i] .data , där i är indexet för en specifik klump

Anmärkningar

- Caffe build med Python-lager

Caffe måste sammanställas med alternativet WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- Var ska jag spara klassfilen?

Du har två alternativ (åtminstone det jag känner till). Antingen kan du spara den anpassade lagringsfilen i samma mapp som du ska köra caffe-kommandot (förmodligen där dina prototxtfiler skulle vara). Ett annat sätt, också min favorit, är att spara alla dina anpassade lager i en mapp och lägga till den här mappen till din PYTHONPATH.

referenser

  1. Christopher Bourezs blogg
  2. Caffe Github
  3. Stackoverflow

Skiktmall

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

Så viktiga saker att komma ihåg:

  • Ditt anpassade lager måste ärva från caffe.Layer (så glöm inte att importera caffe );
  • Du måste definiera de fyra följande metoderna: installation , framåt , omformning och bakåt ;
  • Alla metoder har topp- och bottenparametrar , som är klumparna som lagrar ingången och den utgång som skickas till ditt lager. Du får åtkomst till den med hjälp av övre [i] .data eller botten [i] .data , där i är indexet för kloden om du har mer än en övre eller nedre klump.

- Inställningsmetod

Setup-metoden kallas en gång under körningens livstid, när Caffe instanterar alla lager. Det är här du kommer att läsa parametrar, instansera buffertar i fast storlek.

- Omforma metoden

Använd omformningsmetoden för initiering / inställning som beror på storleken på bottenklumpen (lagerinmatning). Det kallas en gång när nätverket är inställt.

- Framåtmetod

Framåt-metoden kallas för varje inmatningsbatch och är där det mesta av din logik kommer att vara.

- Bakåtmetod

Metoden Bakåt kallas under nätverkets bakåtpassning. Till exempel i ett upplösningsliknande lager skulle det vara här du skulle beräkna lutningarna. Detta är valfritt (ett lager kan endast vara framåt).

Prototxtmall

Ok, så nu har du ditt lager utformat! Så här definierar du det i din .prototxt- fil:

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

Viktiga kommentarer:

  • typen måste vara Python ;
  • Du måste ha en python_param ordbok med åtminstone modulen och lagerparametrar;
  • modul hänvisar till filen där du implementerade ditt lager (utan .py );
  • lager hänvisar till namnet på din klass;
  • Du kan skicka parametrar till lagret med param_str (mer om hur du får åtkomst till dem).
  • Precis som alla andra lager kan du definiera i vilken fas du vill att den ska vara aktiv (se exemplen för att se hur du kan kontrollera den aktuella fasen);

Att överföra parametrar till lagret

Du kan definiera lagerparametrarna i prototekst med param_str . När du har gjort det här är ett exempel på hur du får åtkomst till dessa parametrar i lagerklassen:

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

Mät lager

I det här exemplet kommer vi att utforma ett "mått" -lager, som matar ut noggrannheten och en förvirringsmatris för ett binärt problem under träning och noggrannhet, falsk positiv hastighet och falsk negativ hastighet under test / validering. Även om Caffe redan har ett noggrannhetsskikt, vill du ibland ha något mer, som en F-åtgärd.

Detta är min measureLayer.py med min klassdefinition:

#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

Och detta är ett exempel på en prototxt med det:

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

Datalager

Det här exemplet är ett anpassat dataskikt som tar emot en textfil med bildvägar, laddar en grupp bilder och förbehandlar dem. Bara ett snabbt tips, Caffe har redan ett stort utbud av dataskikt och förmodligen är ett anpassat lager inte det mest effektiva sättet om du bara vill ha något enkelt.

Min dataLayer.py kan vara något som:

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

Och prototypen skulle vara som:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow