Para mi educación, estoy tratando de implementar una capa convolucional N-dimensional en una red neuronal convolucional.
Me gustaría implementar una función de retropropagación. Sin embargo, no estoy seguro de la forma más eficiente de hacerlo.
Actualmente, estoy usando signal.fftconvolve
para:
En el paso hacia adelante, convolucione el filtro y el kernel hacia adelante sobre todos los filtros;
En el paso Backpropagation, convolucione los derivados (invertidos en todas las dimensiones con la función FlipAllAxes) con la matriz ( https://jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/ ) sobre todos los filtros y sumarlos. La salida que considero es la suma de cada imagen convolucionada con cada derivada para cada filtro.
Estoy particularmente confundido acerca de cómo convolucionar los derivados . El uso de la clase a continuación para propagar hacia atrás da como resultado una explosión en el tamaño de los pesos.
¿Cuál es la forma correcta de programar la convolución de la derivada con la salida y los filtros?
EDITAR:
De acuerdo con este documento ( Capacitación rápida de redes convolucionales a través de FFT ), que busca hacer exactamente lo que deseo hacer:
Las derivadas para la capa anterior están dadas por la convolución de las derivadas de la capa actual con los pesos:
dL / dy_f = dL / dx * w_f ^ T
La derivada para los pesos es la suma por partes de la convolución de las derivadas con la entrada original:
dL / dy = dL / dx * x
He implementado, lo mejor que sé cómo, esto a continuación. Sin embargo, esto no parece dar el resultado deseado, ya que la red que he escrito usando esta capa exhibe fluctuaciones salvajes durante el entrenamiento.
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