Szukaj…


Wprowadzenie

Ten samouczek poprowadzi Cię przez kolejne etapy tworzenia prostej niestandardowej warstwy dla Caffe przy użyciu Pythona. Na końcu jest kilka przykładów niestandardowych warstw. Zwykle tworzysz niestandardową warstwę, aby zaimplementować funkcję niedostępną w Caffe, dostosowując ją do swoich wymagań.

Utworzenie niestandardowej warstwy python powoduje dodatkowe obciążenie sieci i prawdopodobnie nie jest tak wydajne jak niestandardowa warstwa C ++. Jednak w ten sposób nie będziesz musiał kompilować całej kawy z nową warstwą.

Parametry

Parametr Detale
Top Tablica z górnymi kroplami twojej warstwy. Dostęp do danych przekazywanych do niego przy użyciu top [i] .data , gdzie i jest indeksem określonego obiektu blob
Dolny Tablica z dolnymi kroplami twojej warstwy. Uzyskaj dostęp do danych przekazywanych do niego przy użyciu bottom [i] .data , gdzie i jest indeksem określonego obiektu blob

Uwagi

- Kompilacja Caffe z warstwą Python

Caffe należy skompilować z opcją WITH_PYTHON_LAYER :

WITH_PYTHON_LAYER=1 make && make pycaffe

- Gdzie mam zapisać plik zajęć?

Masz dwie opcje (przynajmniej o których wiem). Możesz zapisać niestandardowy plik warstwy w tym samym folderze, w którym zamierzasz uruchomić polecenie caffe (prawdopodobnie tam, gdzie byłyby twoje pliki prototxt). Innym sposobem, również moim ulubionym, jest zapisanie wszystkich niestandardowych warstw w folderze i dodanie tego folderu do PYTHONPATH.

Bibliografia

  1. Blog Christophera Boureza
  2. Caffe Github
  3. Przepełnienie stosu

Szablon warstwy

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

Tak ważne rzeczy do zapamiętania:

  • Twoja niestandardowa warstwa musi dziedziczyć po caffe.Layer (więc nie zapomnij zaimportować caffe );
  • Musisz zdefiniować cztery następujące metody: setup , forward , reshape i backward ;
  • Wszystkie metody mają górne i dolne parametry, które są kroplami przechowującymi dane wejściowe i wyjściowe przekazywane do warstwy. Możesz uzyskać do niego dostęp za pomocą górnych [i] .data lub dolnych [i] .data , gdzie i to indeks obiektu blob w przypadku, gdy masz więcej niż jeden górny lub dolny obiekt blob.

- Metoda instalacji

Metoda instalacji jest wywoływana jeden raz w trakcie wykonywania programu, gdy Caffe tworzy instancję wszystkich warstw. Tutaj odczytujesz parametry, tworzysz bufory o stałym rozmiarze.

- Metoda przekształcenia

Użyj metody zmiany kształtu do inicjalizacji / konfiguracji, która zależy od rozmiaru dolnego obiektu blob (wejście warstwy). Jest on wywoływany raz, gdy tworzona jest sieć.

- Metoda przekazania

Metoda Forward jest wywoływana dla każdej partii wejściowej i jest tam, gdzie będzie większość logiki.

- Metoda wsteczna

Metoda wsteczna jest wywoływana podczas wstecznego przejścia sieci. Na przykład w warstwie podobnej do splotu w tym miejscu można obliczyć gradienty. Jest to opcjonalne (warstwa może być tylko do przodu).

Szablon Prototxt

Ok, więc teraz masz zaprojektowaną warstwę! Oto jak definiujesz to w pliku .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
  }
}

Ważne uwagi:

  • typ musi być Python ;
  • Musisz mieć słownik python_param z co najmniej parametrami modułu i warstw ;
  • moduł odnosi się do pliku, w którym zaimplementowałeś swoją warstwę (bez .py );
  • warstwa odnosi się do nazwy twojej klasy;
  • Możesz przekazać parametry do warstwy używając param_str (więcej o dostępie poniżej);
  • Podobnie jak każda inna warstwa, możesz zdefiniować, w której fazie ma być aktywna (zobacz przykłady, aby sprawdzić, jak można sprawdzić bieżącą fazę);

Przekazywanie parametrów do warstwy

Możesz zdefiniować parametry warstwy w prototxt za pomocą param_str . Gdy to zrobisz, oto przykład, w jaki sposób uzyskujesz dostęp do tych parametrów w klasie warstw:

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

Zmierz warstwę

W tym przykładzie zaprojektujemy warstwę „miary”, która generuje dokładność i matrycę pomyłek dla problemu binarnego podczas treningu oraz dokładność, częstość fałszywie dodatnich i fałszywie ujemnych wyników podczas testu / walidacji. Chociaż Caffe ma już warstwę dokładności, czasami potrzebujesz czegoś więcej, na przykład miary F.

Oto moja miaraLayer.py z definicją mojej klasy:

#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

A to jest przykład prototekstu z nim:

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

Warstwa danych

Ten przykład to niestandardowa warstwa danych, która odbiera plik tekstowy ze ścieżkami obrazu, ładuje partię obrazów i przetwarza je wstępnie. Krótka wskazówka, Caffe ma już duży zakres warstw danych i prawdopodobnie warstwa niestandardowa nie jest najbardziej wydajnym sposobem, jeśli chcesz czegoś prostego.

Mój dataLayer.py może wyglądać następująco:

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

Prototxt wyglądałby tak:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow