-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathneuron.go
294 lines (267 loc) · 9.76 KB
/
neuron.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
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
package wann
import (
"errors"
"fmt"
"math/rand"
"github.com/dave/jennifer/jen"
)
// Neuron is a list of input-neurons, and an activation function.
type Neuron struct {
Net *Network
InputNodes []NeuronIndex // pointers to other neurons
ActivationFunction ActivationFunctionIndex
Value *float64
distanceFromOutputNode int // Used when traversing nodes and drawing diagrams
neuronIndex NeuronIndex
}
// NewBlankNeuron creates a new Neuron, with the Step activation function as the default
func (net *Network) NewBlankNeuron() (*Neuron, NeuronIndex) {
// Pre-allocate room for 16 connections and use Linear as the default activation function
neuron := Neuron{Net: net, InputNodes: make([]NeuronIndex, 0, 16), ActivationFunction: Swish}
neuron.neuronIndex = NeuronIndex(len(net.AllNodes))
net.AllNodes = append(net.AllNodes, neuron)
return &neuron, neuron.neuronIndex
}
// NewNeuron creates a new *Neuron, with a randomly chosen activation function
func (net *Network) NewNeuron() (*Neuron, NeuronIndex) {
chosenActivationFunctionIndex := ActivationFunctionIndex(rand.Intn(len(ActivationFunctions)))
inputNodes := make([]NeuronIndex, 0, 16)
neuron := Neuron{
Net: net,
InputNodes: inputNodes,
ActivationFunction: chosenActivationFunctionIndex,
}
// The length of net.AllNodes is what will be the last index
neuronIndex := NeuronIndex(len(net.AllNodes))
// Assign the neuron index in the net to the neuron
neuron.neuronIndex = neuronIndex
// Add this neuron to the net
net.AllNodes = append(net.AllNodes, neuron)
return &neuron, neuronIndex
}
// NewUnconnectedNeuron returns a new unconnected neuron with neuronIndex -1 and net pointer set to nil
func NewUnconnectedNeuron() *Neuron {
// Pre-allocate room for 16 connections and use Linear as the default activation function
neuron := Neuron{Net: nil, InputNodes: make([]NeuronIndex, 0, 16), ActivationFunction: Linear}
neuron.neuronIndex = -1
return &neuron
}
// Connect this neuron to a network, overwriting any existing connections.
// This will also clear any input nodes to this neuron, since the net is different.
// TODO: Find the input nodes from the neuron.Net, save those and re-assign if there are matches?
func (neuron *Neuron) Connect(net *Network) {
neuron.InputNodes = []NeuronIndex{}
neuron.Net = net
for ni := range net.AllNodes {
// Check if this network already has a pointer to this neuron
if &net.AllNodes[ni] == neuron {
// Yes, assign the index
neuron.neuronIndex = NeuronIndex(ni)
// All good, bail
return
}
}
// The neuron was not found in the network
// Find what will be the last index in net.AllNodes
neuronIndex := len(net.AllNodes)
// Add this neuron to the network
net.AllNodes = append(net.AllNodes, *neuron)
// Assign the index
net.AllNodes[neuronIndex].neuronIndex = NeuronIndex(neuronIndex)
}
// RandomizeActivationFunction will choose a random activation function for this neuron
func (neuron *Neuron) RandomizeActivationFunction() {
chosenActivationFunctionIndex := ActivationFunctionIndex(rand.Intn(len(ActivationFunctions)))
neuron.ActivationFunction = chosenActivationFunctionIndex
}
// SetValue can be used for setting a value for this neuron instead of using input neutrons.
// This changes how the Evaluation function behaves.
func (neuron *Neuron) SetValue(x float64) {
neuron.Value = &x
}
// HasInput checks if the given neuron is an input neuron to this one
func (neuron *Neuron) HasInput(e NeuronIndex) bool {
for _, ni := range neuron.InputNodes {
if ni == e {
return true
}
}
return false
}
// FindInput checks if the given neuron is an input neuron to this one,
// and also returns the index to InputNeurons, if found.
func (neuron *Neuron) FindInput(e NeuronIndex) (int, bool) {
for i, n := range neuron.InputNodes {
if n == e {
return i, true
}
}
return -1, false
}
// Is check if the given NeuronIndex points to this neuron
func (neuron *Neuron) Is(e NeuronIndex) bool {
return neuron.neuronIndex == e
}
// AddInput will add an input neuron
func (neuron *Neuron) AddInput(ni NeuronIndex) error {
if neuron.Is(ni) {
return errors.New("adding a neuron as input to itself")
}
if neuron.HasInput(ni) {
return errors.New("neuron already exists")
}
neuron.InputNodes = append(neuron.InputNodes, ni)
return nil
}
// AddInputNeuron both adds a neuron to this network (if needed) and also
// adds its neuron index to the neuron.InputNeurons
func (neuron *Neuron) AddInputNeuron(n *Neuron) error {
// If n.neuronIndex is known to this network, just add the NeuronIndex to neuron.InputNeurons
if neuron.Net.Exists(n.neuronIndex) {
return neuron.AddInput(n.neuronIndex)
}
// If not, add this neuron to the network first
node := *n
node.neuronIndex = NeuronIndex(len(neuron.Net.AllNodes))
neuron.Net.AllNodes = append(neuron.Net.AllNodes, node)
return neuron.AddInput(n.neuronIndex)
}
// RemoveInput will remove an input neuron
func (neuron *Neuron) RemoveInput(e NeuronIndex) error {
if i, found := neuron.FindInput(e); found {
// Found it, remove the neuron at index i
neuron.InputNodes = append(neuron.InputNodes[:i], neuron.InputNodes[i+1:]...)
return nil
}
return errors.New("neuron does not exist")
}
// Exists checks if the given NeuronIndex exists in this Network
func (net *Network) Exists(ni NeuronIndex) bool {
for i := range net.AllNodes {
neuronIndex := NeuronIndex(i)
if neuronIndex == ni {
return true
}
}
return false
}
// InputNeuronsAreGood checks if all input neurons of this neuron exists in neuron.Net
func (neuron *Neuron) InputNeuronsAreGood() bool {
for _, inputNeuronIndex := range neuron.InputNodes {
if !neuron.Net.Exists(inputNeuronIndex) {
return false
}
}
return true
}
// evaluate will return a weighted sum of the input nodes,
// using the .Value field if it is set and no input nodes are available.
// returns true if the maximum number of evaluation loops is reached
func (neuron *Neuron) evaluate(weight float64, maxEvaluationLoops *int) (float64, bool) {
if *maxEvaluationLoops <= 0 {
return 0.0, true
}
// Assume this is the Output neuron, recursively evaluating the result
// For each input neuron, evaluate them
summed := 0.0
counter := 0
for _, inputNeuronIndex := range neuron.InputNodes {
// Let each input neuron do its own evauluation, using the given weight
(*maxEvaluationLoops)--
// TODO: Figure out exactly why this one kicks in (and if it matters)
// It only seems to kick in during "go test" and not in evolve/main.go
if int(inputNeuronIndex) >= len(neuron.Net.AllNodes) {
continue
//panic("TOO HIGH INPUT NEURON INDEX")
}
result, stopNow := neuron.Net.AllNodes[inputNeuronIndex].evaluate(weight, maxEvaluationLoops)
summed += result * weight
counter++
if stopNow || (*maxEvaluationLoops < 0) {
break
}
}
// No input neurons. Use the .Value field if it's not nil and this is not the output node
if counter == 0 && neuron.Value != nil && !neuron.IsOutput() {
return *(neuron.Value), false
}
// Return the averaged sum, or 0
if counter == 0 {
// This should never happen
return 0.0, false
}
// This should run, also when this neuron is the output neuron
f := neuron.GetActivationFunction()
//fmt.Println(neuron.ActivationFunction.Name() + " = " + neuron.ActivationFunction.String())
// Run the input through the activation function
// TODO: Does "retval := f(summed)"" perform better?, or the one that averages the sum first?
//retval := f(summed / float64(counter))
retval := f(summed)
return retval, false
}
// GetActivationFunction returns the activation function for this neuron
func (neuron *Neuron) GetActivationFunction() func(float64) float64 {
return ActivationFunctions[neuron.ActivationFunction]
}
// In checks if this neuron is in the given collection
func (neuron *Neuron) In(collection []NeuronIndex) bool {
for _, existingNodeIndex := range collection {
if neuron.Is(existingNodeIndex) {
return true
}
}
return false
}
// IsInput returns true if this is an input node or not
// Returns false if nil
func (neuron *Neuron) IsInput() bool {
if neuron.Net == nil {
return false
}
return neuron.Net.IsInput(neuron.neuronIndex)
}
// IsOutput returns true if this is an output node or not
// Returns false if nil
func (neuron *Neuron) IsOutput() bool {
if neuron.Net == nil {
return false
}
return neuron.Net.OutputNode == neuron.neuronIndex
}
// Copy a Neuron to a new Neuron, and assign the pointer to the given network to .Net
func (neuron Neuron) Copy(net *Network) Neuron {
var newNeuron Neuron
newNeuron.Net = net
newNeuron.InputNodes = neuron.InputNodes
newNeuron.ActivationFunction = neuron.ActivationFunction
newNeuron.Value = neuron.Value
newNeuron.distanceFromOutputNode = neuron.distanceFromOutputNode
newNeuron.neuronIndex = neuron.neuronIndex
return newNeuron
}
// String will return a string containing both the pointer address and the number of input neurons
func (neuron *Neuron) String() string {
nodeType := " Node"
if neuron.IsInput() {
nodeType = " Input node"
} else if neuron.IsOutput() {
nodeType = "Output node"
}
return fmt.Sprintf("%s ID %d has these input connections: %v", nodeType, neuron.neuronIndex, neuron.InputNodes)
}
// InputStatement returns a statement like "inputData[0]", if this node is a network input node
func (neuron *Neuron) InputStatement() (*jen.Statement, error) {
// If this node is a network input node, return a statement representing this input,
// like "inputData[0]"
if !neuron.IsInput() {
return jen.Empty(), errors.New(" not an input node")
}
for i, ni := range neuron.Net.InputNodes {
if ni == neuron.neuronIndex {
// This index in the neuron.NetInputNodes is i
return jen.Id("inputData").Index(jen.Lit(i)), nil
}
}
// Not found!
return jen.Empty(), errors.New("not an input node for the associated network")
}