-
Notifications
You must be signed in to change notification settings - Fork 0
/
layers.go
126 lines (106 loc) · 3.48 KB
/
layers.go
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package feedforward
// Represents a layer of a feedforward neural network.
type layer interface {
initialize(Initializer)
processInput([]float64) []float64
getOutputCache() []float64
processError([]float64) []float64
getWeights() [][]float64
getBiases() []float64
}
// Base layer implementation
type baseLayer struct {
weights [][]float64
biases []float64
activation ActivationFunction
prevLayerNeurons int
neurons int
outputCache []float64
}
// Initializes weights and biases of the entire layer using the provided initializer
func (l *baseLayer) initialize(initializer Initializer) {
initializer.Initialize(l.weights)
for i := 0; i < l.neurons; i++ {
l.biases[i] = 0
}
}
// Computes output of the entire layer for the given input and caches the output before returning to caller.
// The output is computed concurrently for each neuron through goroutines, synchronized through a WaitGroup.
func (l *baseLayer) processInput(input []float64) []float64 {
output := make([]float64, l.neurons)
for i := 0; i < l.neurons; i++ {
output[i] = l.activation.Value(l.net(i, input))
}
l.outputCache = output
return output
}
// Computes net of i-th neuron
func (l *baseLayer) net(i int, input []float64) float64 {
net := l.biases[i]
for j := 0; j < l.prevLayerNeurons; j++ {
net += input[j] * l.weights[j][i]
}
return net
}
// Gets cached output
func (l *baseLayer) getOutputCache() []float64 {
return l.outputCache
}
// Gets underlying weight slice
func (l *baseLayer) getWeights() [][]float64 {
return l.weights
}
// Gets underlying bias slice
func (l *baseLayer) getBiases() []float64 {
return l.biases
}
// Type representing a hidden layer.
// Extends all properties from the baseLayer.
// Additionally holds outgoing weights used in calculating the weighted layer error.
type hiddenLayer struct {
baseLayer
nextLayerNeurons int
nextLayerWeights [][]float64
}
// Constructor of a hidden layer.
func newHiddenLayer(weights [][]float64, biases []float64, nextLayerWeights [][]float64, activation ActivationFunction) layer {
return &hiddenLayer{
baseLayer: baseLayer{weights: weights, biases: biases, activation: activation, prevLayerNeurons: len(weights), neurons: len(biases)},
nextLayerWeights: nextLayerWeights,
nextLayerNeurons: len(nextLayerWeights[0]),
}
}
// Computes the weighted error of this layer.
// Computations are performed concurrently for every neuron of this layer.
func (h *hiddenLayer) processError(delta []float64) []float64 {
layerError := make([]float64, h.neurons)
output := h.outputCache
for i := 0; i < h.neurons; i++ {
sum := 0.
for j := 0; j < h.nextLayerNeurons; j++ {
sum += delta[j] * h.nextLayerWeights[i][j]
}
layerError[i] = h.activation.Gradient(output[i]) * sum
}
return layerError
}
// Type representing a hidden layer.
// Extends all properties from the baseLayer and provides implementation of processError.
type outputLayer struct {
baseLayer
}
// Constructor of an output layer.
func newOutputLayer(weights [][]float64, biases []float64, activation ActivationFunction) layer {
return &outputLayer{
baseLayer: baseLayer{weights: weights, biases: biases, activation: activation, prevLayerNeurons: len(weights), neurons: len(biases)},
}
}
// Computes the error of the output layer.
func (o *outputLayer) processError(delta []float64) []float64 {
layerError := make([]float64, o.neurons)
output := o.outputCache
for i := 0; i < o.neurons; i++ {
layerError[i] = o.activation.Gradient(output[i]) * delta[i]
}
return layerError
}