Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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