-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLinearModel.cs
201 lines (176 loc) · 6.45 KB
/
LinearModel.cs
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
namespace Backend
{
public class LinearModel : Model
{
// List to hold layers -> Transfer to queue with Compile().
List<Layer> _layers = new List<Layer>();
// Nullable until Compile() called.
Queue<Layer>? _queue;
// Boolean that indicates whether the model is ready to use and train - Compile() is a prerequisite.
bool _readyToTrain = false;
int _batchSize;
CostFunction _costFunction;
float _learningRate;
int _epochs;
public void AddLayer(Layer layer)
{
_layers.Add(layer);
}
// Uses the errors generated in backpropagation to change the model's weights and biases.
public void TrainingStep(List<float[,]> errors)
{
for (int i = _layers.Count - 1; i > 0; i--)
{
ModifyWeightsAndBiases(errors[_layers.Count - 1 - i], (DenseLayer)_layers[i], _layers[i - 1], _learningRate);
}
float[,] newWeights = ((DenseLayer)_layers[1]).GetWeights();
// Pushes trained layer attributes onto queue for future forward passes.
Requeue();
}
// Code to modify the weights and biases based on the errors generated in backpropagation.
public static void ModifyWeightsAndBiases(float[,] error, DenseLayer layer, Layer previousLayer, float eta)
{
float[] averagedError = Function.AverageMatrix(error);
// For gradient descent, we are multiplying eta by -1 so that we go against the direction of steepest ascent.
// For gradient ascent, we would omit multiplying eta by -1.
eta = eta * -1;
Function.Vectorise(averagedError, x => eta * x);
// Adds error * eta (learning rate) to the biases to improve them. Eta means that not overly large steps are made.
layer.ModifyBias(averagedError);
float[] averagedActivation = Function.AverageMatrix(previousLayer.GetActivationOutput());
float[,] modification = Function.Transpose(Function.OuterProduct(averagedActivation, averagedError));
// Multiplies by eta (learning rate) then adds to weights to improve them.
Function.Vectorise(modification, x => eta * x);
layer.ModifyWeights(modification);
}
// float[] is the output of the model.
public float[] ForwardPropagate(float[] input)
{
if (_readyToTrain == false)
{
throw new Exception("Model must be compiled before training");
}
else
{
for (int i = 0; i < _queue.GetQueueSize(); i++)
{
Layer layer = _queue.Dequeue();
input = layer.ForwardPass(input);
}
Requeue();
return input;
}
}
// Polymorphism for batched inputs.
public override float[,] ForwardPropagate(float[,] input)
{
if (_readyToTrain == false)
{
throw new Exception("Model must be compiled before training");
}
else
{
for (int i = 0; i < _queue.GetQueueSize(); i++)
{
Layer layer = _queue.Dequeue();
input = layer.ForwardPass(input);
}
Requeue();
return input;
}
}
// Pushes layers back onto Queue for next forward propagation.
public void Requeue()
{
_queue.ResetPointers();
for (int i = 0; i < _layers.Count; i++)
{
_queue.Enqueue(_layers[i]);
}
}
// Sets parameters for a given layer in the linear model from a text file.
public void SetParameters(int index, string filename)
{
try
{
DenseLayer layer = (DenseLayer)_layers[index];
layer.LoadWeightsAndBias(filename);
}
catch
{
throw new ArgumentException("Selected file is incompatible.");
}
}
public override void Compile(CostFunction costFunction)
{
_costFunction = costFunction;
if (_layers[0] is not InputLayer)
{
throw new ArgumentException("First layer must be Input layer");
}
if (_layers.FindAll(s => s is InputLayer).Count > 1)
{
throw new ArgumentException("Only one input layer is allowed");
}
_queue = new Queue<Layer>(_layers.Count());
foreach (Layer l in _layers)
{
_queue.Enqueue(l);
}
_readyToTrain = true;
}
public void Train(string filename, int epochs, float learningRate, int batchSize)
{
if (_readyToTrain == false)
{
throw new Exception("Model must be compiled before training");
}
if (batchSize < 1)
{
throw new ArgumentException("Batch size cannot be less than 1");
}
_epochs = epochs;
_learningRate = learningRate;
_batchSize = batchSize;
// Filename refers to file name of or path to the dataset file.
Optimiser.Fit(this, filename);
}
public int GetBatchSize()
{
return _batchSize;
}
public CostFunction GetCostFunction()
{
return _costFunction;
}
public float GetLearningRate()
{
return _learningRate;
}
public int GetEpochs()
{
return _epochs;
}
// Next two methods are used by the optimiser for checking that the dataset is of the correct input shape.
public override int GetInputSize()
{
// GetOutputSize() works here because input size = output size for InputLayer objects.
return _queue.Peek().GetOutputSize();
}
public override int GetOutputSize()
{
return _queue.PeekEnd().GetOutputSize();
}
public List<Layer> GetLayers()
{
return _layers;
}
}
}