Para minha educação, estou tentando implementar uma camada convolucional N-dimensional em uma rede neural convolucional.
Eu gostaria de implementar uma função de retropropagação. No entanto, não tenho certeza da maneira mais eficiente de fazê-lo.
No momento, estou usando signal.fftconvolve
para:
Na etapa anterior, envolva o filtro e o kernel adiante em todos os filtros;
Na etapa Backpropagation, envolva as derivadas (revertidas em todas as dimensões com a função FlipAllAxes) com a matriz ( https://jefkine.com/general/2016/09/05/backpropagation-in-convolutional-neural-networks/ ) sobre todos os filtros e soma-os. A saída que considero ser a soma de cada imagem convoluída com cada derivada para cada filtro.
Estou particularmente confuso sobre como envolver os derivados . Usar a classe abaixo para retropropilar resulta em uma explosão no tamanho dos pesos.
Qual é a maneira correta de programar a convolução da derivada com a saída e os filtros?
EDITAR:
De acordo com este artigo ( Treinamento Rápido de Redes Convolucionais através de FFTs ), que procura fazer exatamente o que eu desejo:
As derivadas para a camada anterior são dadas pela convolução das derivadas da camada atual com os pesos:
dL / dy_f = dL / dx * w_f ^ T
A derivada para os pesos é a soma por partes da convolução das derivadas com a entrada original:
dL / dy = dL / dx * x
Eu implementei, da melhor maneira que sei, isso abaixo. No entanto, isso não parece dar o resultado pretendido, pois a rede que escrevi usando essa camada exibe flutuações violentas durante o treinamento.
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