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