ND Convolution Backprogation

8

Für meine Ausbildung versuche ich, eine N-dimensionale Faltungsschicht in einem Faltungsnetzwerk zu implementieren.

Ich möchte eine Backpropagation-Funktion implementieren. Ich bin mir jedoch nicht sicher, wie ich dies am effizientesten tun kann.

Zur Zeit benutze ich signal.fftconvolve:

  • Falten Sie im Vorwärtsschritt den Filter und den Kernel vorwärts über alle Filter.

  • Falten Sie im Backpropagation-Schritt die Ableitungen (in allen Dimensionen mit der FlipAllAxes-Funktion umgekehrt) mit dem Array ( https://jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/ ) alle Filter und summiere sie. Die Ausgabe, die ich nehme, ist die Summe jedes Bildes, das mit jeder Ableitung für jeden Filter gefaltet wird.

Ich bin besonders verwirrt darüber, wie man die Derivate faltet . Die Verwendung der folgenden Klasse zur Rückausbreitung führt zu einer Explosion der Größe der Gewichte.

Was ist der richtige Weg, um die Faltung der Ableitung mit dem Ausgang und den Filtern zu programmieren?

BEARBEITEN:

Laut diesem Artikel ( Fast Training of Convolutional Networks durch FFTs ), der genau das tun möchte, was ich tun möchte:

  • Die Ableitungen für die vorherige Schicht ergeben sich aus der Faltung der Ableitungen der aktuellen Schicht mit den Gewichten:

    dL / dy_f = dL / dx * w_f ^ T.

  • Die Ableitung für die Gewichte ist die stückweise Summe der Faltung der Ableitungen mit der ursprünglichen Eingabe:

    dL / dy = dL / dx * x

Ich habe dies nach bestem Wissen und Gewissen umgesetzt. Dies scheint jedoch nicht das beabsichtigte Ergebnis zu liefern, da das Netzwerk, das ich mit dieser Ebene geschrieben habe, während des Trainings wilde Schwankungen aufweist.

    import numpy as np
    from scipy import signal

    class ConvNDLayer:
        def __init__(self,channels, kernel_size, dim):

            self.channels = channels
            self.kernel_size = kernel_size;
            self.dim = dim

            self.last_input = None

            self.filt_dims = np.ones(dim+1).astype(int)
            self.filt_dims[1:] =  self.filt_dims[1:]*kernel_size
            self.filt_dims[0]= self.filt_dims[0]*channels 
            self.filters = np.random.randn(*self.filt_dims)/(kernel_size)**dim


        def FlipAllAxes(self, array):

            sl = slice(None,None,-1)
            return array[tuple([sl]*array.ndim)] 

        def ViewAsWindows(self, array, window_shape, step=1):
             # -- basic checks on arguments
             if not isinstance(array, cp.ndarray):
                 raise TypeError("`array` must be a Cupy ndarray")
             ndim = array.ndim
             if isinstance(window_shape, numbers.Number):
                  window_shape = (window_shape,) * ndim
             if not (len(window_shape) == ndim):
                   raise ValueError("`window_shape` is incompatible with `arr_in.shape`")

             if isinstance(step, numbers.Number):
                  if step < 1:
                  raise ValueError("`step` must be >= 1")
                  step = (step,) * ndim
             if len(step) != ndim:
                   raise ValueError("`step` is incompatible with `arr_in.shape`")

              arr_shape = array.shape
              window_shape = np.asarray(window_shape, dtype=arr_shape.dtype))

              if ((arr_shape - window_shape) < 0).any():
                   raise ValueError("`window_shape` is too large")

              if ((window_shape - 1) < 0).any():
                    raise ValueError("`window_shape` is too small")

               # -- build rolling window view
                    slices = tuple(slice(None, None, st) for st in step)
                    window_strides = array.strides
                    indexing_strides = array[slices].strides
                    win_indices_shape = (((array.shape -window_shape)
                    // step) + 1)

                 new_shape = tuple(list(win_indices_shape) + list(window_shape))
                 strides = tuple(list(indexing_strides) + list(window_strides))

                  arr_out = as_strided(array, shape=new_shape, strides=strides)

                  return arr_out

        def UnrollAxis(self, array, axis):
             # This so it works with a single dimension or a sequence of them
             axis = cp.asnumpy(cp.atleast_1d(axis))
             axis2 = cp.asnumpy(range(len(axis)))

             # Put unrolled axes at the beginning
             array = cp.moveaxis(array, axis,axis2)
             # Unroll
             return array.reshape((-1,) + array.shape[len(axis):])

        def Forward(self, array):

             output_shape =cp.zeros(array.ndim + 1)    
             output_shape[1:] =  cp.asarray(array.shape)
             output_shape[0]= self.channels 
             output_shape = output_shape.astype(int)
             output = cp.zeros(cp.asnumpy(output_shape))

             self.last_input = array

             for i, kernel in enumerate(self.filters):
                    conv = self.Convolve(array, kernel)
                    output[i] = conv

             return output


        def Backprop(self, d_L_d_out, learn_rate):

            d_A= cp.zeros_like(self.last_input)
            d_W = cp.zeros_like(self.filters)


           for i, (kernel, d_L_d_out_f) in enumerate(zip(self.filters, d_L_d_out)):

                d_A += signal.fftconvolve(d_L_d_out_f, kernel.T, "same")
                conv = signal.fftconvolve(d_L_d_out_f, self.last_input, "same")
                conv = self.ViewAsWindows(conv, kernel.shape)
                axes = np.arange(kernel.ndim)
                conv = self.UnrollAxis(conv, axes)  
                d_W[i] = np.sum(conv, axis=0)


           output = d_A*learn_rate
           self.filters =  self.filters - d_W*learn_rate
           return output
Jack Rolph
quelle

Antworten:

0

Das Multiplizieren von Verläufen mit learn_rate reicht normalerweise nicht aus.

Um eine bessere Leistung zu erzielen und starke Schwankungen zu reduzieren, werden die Gradienten mithilfe von Optimierern mithilfe von Methoden wie der Division durch die letzten Gradienten (RMSprop) skaliert.

Die Aktualisierungen hängen auch vom Fehler ab, wenn Sie Fehler für jede Probe einzeln übergeben, was normalerweise zu Rauschen führt. Daher wird es als besser angesehen, über mehrere Proben (Mini-Batches) zu mitteln.

SajanGohil
quelle