ND Convolution Backprogation - PullRequest
       66

ND Convolution Backprogation

8 голосов
/ 12 февраля 2020

Для моего образования я пытаюсь реализовать N-мерный сверточный слой в сверточной нейронной сети.

Я хотел бы реализовать функцию обратного распространения. Однако я не уверен в наиболее эффективном способе сделать это.

В настоящее время я использую signal.fftconvolve для:

  • На шаге вперёд сверните фильтр и ядро ​​перенаправляют на все фильтры;

  • На этапе обратного распространения сворачиваем производные (обращенные во всех измерениях с помощью функции FlipAllAxes) с массивом (https://jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/) по всем фильтрам и суммировать их. Вывод, который я принимаю, является суммой каждого изображения, свернутого с каждой производной для каждого фильтра.

Меня особенно смущает, как свертывать производные . Использование приведенного ниже класса для обратного распространения приводит к взрыву размеров весов.

Как правильно запрограммировать свертку производной с выходом и фильтрами?

РЕДАКТИРОВАТЬ:

Согласно этому документу ( Быстрое обучение сверточных сетей с помощью БПФ ), который стремится сделать именно то, что я sh должен сделать:

  • Производные для предыдущего слоя задаются сверткой производных текущего слоя с весами:

    dL / dy_f = dL / dx * w_f ^ T

  • Производные для весов представляют собой кусочную сумму свертки производных с исходным вводом:

    дл / дд = dL / dx * x

I реализовали, насколько я знаю, это ниже. Однако это, похоже, не дает ожидаемого результата, поскольку сеть, которую я написал с использованием этого слоя, демонстрирует сильные колебания во время обучения.

    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

1 Ответ

0 голосов
/ 02 марта 2020

Умножение градиентов на Learn_rate обычно недостаточно.

Для повышения производительности и снижения значительных колебаний градиенты масштабируются с использованием оптимизаторов такими методами, как деление на несколько последних градиентов (RMSprop).

Обновления также зависят от ошибки, если вы передаете ошибку для каждого сэмпла индивидуально, которая обычно создает шум, поэтому считается, что лучше усреднять результаты по нескольким сэмплам (мини-пакетам).

...