Skip to content
Snippets Groups Projects
convolution1D.py 3.03 KiB
Newer Older
  • Learn to ignore specific revisions
  • johannes bilk's avatar
    johannes bilk committed
    import numpy as np
    from .layer import Layer
    from .weights import Weights
    
    
    def getWindows1D(input: np.ndarray, kernelSize: int, stride: int = 1, padding: int = 0, dilation: int = 0) -> np.ndarray:
        batch_size, channels, length = input.shape
    
        if dilation > 0:
            input = np.insert(input, [i + 1 for i in range(dilation, length - 1, dilation)], 0, axis=2)
    
        if padding > 0:
            input = np.pad(input, pad_width=((0, 0), (0, 0), (padding, padding)), mode='constant', constant_values=0)
    
        output_length = (length + 2 * padding - kernelSize) // stride + 1
        batch_strides, channel_strides, length_strides = input.strides
        striding = (batch_strides, channel_strides, stride * length_strides, length_strides)
    
        return np.lib.stride_tricks.as_strided(input, (batch_size, channels, output_length, kernelSize), striding)
    
    
    class Convolution1D(Layer):
        def __init__(self, inChannels: int, outChannels: int, kernelSize: int, stride: int = 1, padding: int= 0, dilation: int = 0, weights: Weights = None, bias: Weights = None) -> None:
            self.inChannels = inChannels
            self.outChannels = outChannels
            self.kernelSize = kernelSize
            self.stride = stride
            self.padding = padding
            self.dilation = dilation
    
            # learnable parameters
            self.weights = Weights((outChannels, inChannels, kernelSize), values=weights)
            self.bias = Weights(outChannels, init='zeros', values=bias)
            self.input = None
            self.windows = None
    
        def params(self) -> tuple[Weights, Weights]:
            """
            returns weights and bias in a python list, called by optimizers
            """
            return [self.weights, self.bias]
    
        def forward(self, input: np.ndarray) -> np.ndarray:
            """
            The forward pass of convolution
            """
            self.input = input.copy()
    
            self.windows = getWindows1D(input, self.kernelSize, stride=self.stride, padding=self.padding, dilation=self.dilation)
            output = np.einsum('bclw,ocw->bol', self.windows, self.weights.values) + self.bias.values[None, :, None]
    
            return output
    
        def backward(self, gradient: np.ndarray) -> np.ndarray:
            """
            The backward pass of convolution
            """
            padding = (self.kernelSize - 1) * (self.dilation + 1)
    
            gradientWindows = getWindows1D(gradient, self.kernelSize, padding=padding, stride=1, dilation=self.dilation)
            rotatedKernel = np.flip(self.weights.values, axis=2)
    
            self.weights.deltas = np.einsum('bclw,bol->ocw', self.windows, gradient)
            self.bias.deltas = np.sum(gradient, axis=(0, 2))
    
            return np.einsum('bclw,ocw->bol', gradientWindows, rotatedKernel)
    
        def __str__(self) -> str:
            printString = 'Conv1DLayer'
            printString += '    input channels: ' + str(self.inChannels)
            printString += '    output channels: ' + str(self.outChannels)
            printString += '    kernel size: ' + str(self.kernelSize)
            printString += '    padding: ' + str(self.padding)
            printString += '    stride: ' + str(self.stride)
            return printString