-
Notifications
You must be signed in to change notification settings - Fork 0
/
NeuralNet.py
363 lines (319 loc) · 14.6 KB
/
NeuralNet.py
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
import copy
import sys
from datetime import datetime
from math import exp
from random import random, randint, choice
import numpy as np
class Perceptron(object):
"""
Class to represent a single Perceptron in the net.
"""
def __init__(self, inSize=1, weights=None):
self.inSize = inSize+1#number of perceptrons feeding into this one; add one for bias
if weights is None:
#weights of previous layers into this one, random if passed in as None
self.weights = [1.0]*self.inSize
self.setRandomWeights()
else:
self.weights = weights
def getWeightedSum(self, inActs):
"""
Returns the sum of the input weighted by the weights.
Inputs:
inActs (list<float/int>): input values, same as length as inSize
Returns:
float
The weighted sum
"""
return sum([inAct*inWt for inAct,inWt in zip(inActs,self.weights)])
def sigmoid(self, value):
"""
Return the value of a sigmoid function.
Args:
value (float): the value to get sigmoid for
Returns:
float
The output of the sigmoid function parametrized by
the value.
"""
"""YOUR CODE"""
return 1./(1.+exp(-value))
def sigmoidActivation(self, inActs):
"""
Returns the activation value of this Perceptron with the given input.
Same as g(z) in book.
Remember to add 1 to the start of inActs for the bias input.
Inputs:
inActs (list<float/int>): input values, not including bias
Returns:
float
The value of the sigmoid of the weighted input
"""
"""YOUR CODE"""
return self.sigmoid(self.getWeightedSum([1]+inActs))
def sigmoidDeriv(self, value):
"""
Return the value of the derivative of a sigmoid function.
Args:
value (float): the value to get sigmoid for
Returns:
float
The output of the derivative of a sigmoid function
parametrized by the value.
"""
"""YOUR CODE"""
tmp = self.sigmoid(value)
return tmp*(1. - tmp)
def sigmoidActivationDeriv(self, inActs):
"""
Returns the derivative of the activation of this Perceptron with the
given input. Same as g'(z) in book (note that this is not rounded.
Remember to add 1 to the start of inActs for the bias input.
Inputs:
inActs (list<float/int>): input values, not including bias
Returns:
int
The derivative of the sigmoid of the weighted input
"""
"""YOUR CODE"""
return (self.sigmoidDeriv(self.getWeightedSum([1]+inActs)))
# if(rfloat < 0.5):
# return 0
# else:
# return 1
def updateWeights(self, inActs, alpha, delta):
"""
Updates the weights for this Perceptron given the input delta.
Remember to add 1 to the start of inActs for the bias input.
Inputs:
inActs (list<float/int>): input values, not including bias
alpha (float): The learning rate
delta (float): If this is an output, then g'(z)*error
If this is a hidden unit, then the as defined-
g'(z)*sum over weight*delta for the next layer
Returns:
float
Return the total modification of all the weights (sum of each abs(modification))
"""
totalModification = 0
"""YOUR CODE"""
hwx = self.sigmoidActivation(inActs)
gdz = self.sigmoidDeriv(hwx)
Actslist = [1] + inActs
for i in range(len(Actslist)):
modification = alpha * delta * Actslist[i]
totalModification += abs(modification)
self.weights[i] += modification
return totalModification
def setRandomWeights(self):
"""
Generates random input weights that vary from -1.0 to 1.0
"""
for i in range(self.inSize):
self.weights[i] = (random() + .0001) * (choice([-1,1]))
def __str__(self):
""" toString """
outStr = ''
outStr += 'Perceptron with %d inputs\n'%self.inSize
outStr += 'Node input weights %s\n'%str(self.weights)
return outStr
class NeuralNet(object):
"""
Class to hold the net of perceptrons and implement functions for it.
"""
def __init__(self, layerSize):#default 3 layer, 1 percep per layer
"""
Initiates the NN with the given sizes.
Args:
layerSize (list<int>): the number of perceptrons in each layer
"""
self.layerSize = layerSize #Holds number of inputs and percepetrons in each layer
self.outputLayer = []
self.numHiddenLayers = len(layerSize)-2
self.hiddenLayers = [[] for x in range(self.numHiddenLayers)]
self.numLayers = self.numHiddenLayers+1
#build hidden layer(s)
for h in range(self.numHiddenLayers):
for p in range(layerSize[h+1]):
percep = Perceptron(layerSize[h]) # num of perceps feeding into this one
self.hiddenLayers[h].append(percep)
#build output layer
for i in range(layerSize[-1]):
percep = Perceptron(layerSize[-2]) # num of perceps feeding into this one
self.outputLayer.append(percep)
#build layers list that holds all layers in order - use this structure
# to implement back propagation
self.layers = [self.hiddenLayers[h] for h in xrange(self.numHiddenLayers)] + [self.outputLayer]
def __str__(self):
"""toString"""
outStr = ''
outStr +='\n'
for hiddenIndex in range(self.numHiddenLayers):
outStr += '\nHidden Layer #%d'%hiddenIndex
for index in range(len(self.hiddenLayers[hiddenIndex])):
outStr += 'Percep #%d: %s'%(index,str(self.hiddenLayers[hiddenIndex][index]))
outStr +='\n'
for i in range(len(self.outputLayer)):
outStr += 'Output Percep #%d:%s'%(i,str(self.outputLayer[i]))
return outStr
def feedForward(self, inActs):
"""
Propagate input vector forward to calculate outputs.
Args:
inActs (list<float>): the input to the NN (an example)
Returns:
list<list<float/int>>
A list of lists. The first list is the input list, and the others are
lists of the output values of all perceptrons in each layer.
"""
"""YOUR CODE"""
finalout = [inActs]
prevLayer = inActs
for i in range(self.numHiddenLayers+1):
finalout += [[None]*(self.layerSize[i+1])]
inSize = len(prevLayer)
for j in range(self.layerSize[i+1]):
finalout[i+1][j] = self.layers[i][j].sigmoidActivation(prevLayer)
prevLayer = finalout[-1]
return finalout
def backPropLearning(self, examples, alpha):
"""
Run a single iteration of backward propagation learning algorithm.
See the text and slides for pseudo code.
Args:
examples (list<tuple<list<float>,list<float>>>):
for each tuple first element is input(feature)"vector" (list)
second element is output "vector" (list)
alpha (float): the alpha to training with
Returns
tuple<float,float>
A tuple of averageError and averageWeightChange, to be used as stopping conditions.
averageError is the summed error^2/2 of all examples, divided by numExamples*numOutputs.
averageWeightChange is the summed absolute weight change of all perceptrons,
divided by the sum of their input sizes (the average weight change for a single perceptron).
"""
#keep track of output
averageError = 0
averageWeightChange = 0
numWeights = 0
for example in examples:#for each example
#keep track of deltas to use in weight change
deltas = []
#Neural net output list
allLayerOutput = self.feedForward(example[0])
lastLayerOutput = allLayerOutput[-1]
#Empty output layer delta list
outDelta = []
#iterate through all output layer neurons
for outputNum in xrange(len(example[1])):
gPrime = self.outputLayer[outputNum].sigmoidActivationDeriv(allLayerOutput[-2])
error = example[1][outputNum] - lastLayerOutput[outputNum]
delta = gPrime * error
averageError+=error*error/2
outDelta.append(delta)
deltas.append(outDelta)
"""
Backpropagate through all hidden layers, calculating and storing
the deltas for each perceptron layer.
"""
for layerNum in xrange(self.numHiddenLayers-1,-1,-1):
layer = self.layers[layerNum]
nextLayer = self.layers[layerNum+1]
hiddenDelta = []
#Iterate through all neurons in this layer
for neuronNum in xrange(len(layer)):
gPrime = layer[neuronNum].sigmoidActivationDeriv(allLayerOutput[layerNum])
delta = gPrime*sum([self.layers[layerNum+1][i].weights[neuronNum+1] * deltas[0][i] for i in range(len(deltas[0]))])
hiddenDelta.append(delta)
deltas = [hiddenDelta]+deltas
"""Get output of all layers"""
"""
Having aggregated all deltas, update the weights of the
hidden and output layers accordingly.
"""
for numLayer in xrange(0,self.numLayers):
layer = self.layers[numLayer]
for numNeuron in xrange(len(layer)):
weightMod = layer[numNeuron].updateWeights(allLayerOutput[numLayer],alpha,deltas[numLayer][numNeuron])
averageWeightChange += weightMod
numWeights += layer[numNeuron].inSize
#end for each example
#calculate final output
averageError /= (len(examples)*len(examples[0][1])) #number of examples x length of output vector
averageWeightChange/=(numWeights)
return averageError, averageWeightChange
def buildNeuralNet(examples, alpha=0.1, weightChangeThreshold = 0.00008,hiddenLayerList = [1], maxItr = sys.maxint, startNNet = None):
"""
Train a neural net for the given input.
Args:
examples (tuple<list<tuple<list,list>>,
list<tuple<list,list>>>): A tuple of training and test examples
alpha (float): the alpha to train with
weightChangeThreshold (float): The threshold to stop training at
maxItr (int): Maximum number of iterations to run
hiddenLayerList (list<int>): The list of numbers of Perceptrons
for the hidden layer(s).
startNNet (NeuralNet): A NeuralNet to train, or none if a new NeuralNet
can be trained from random weights.
Returns
tuple<NeuralNet,float>
A tuple of the trained Neural Network and the accuracy that it achieved
once the weight modification reached the threshold, or the iteration
exceeds the maximum iteration.
"""
examplesTrain,examplesTest = examples
numIn = len(examplesTrain[0][0])
numOut = len(examplesTest[0][1])
time = datetime.now().time()
if startNNet is not None:
hiddenLayerList = [len(layer) for layer in startNNet.hiddenLayers]
print "Starting training at time %s with %d inputs, %d outputs, %s hidden layers, size of training set %d, and size of test set %d"\
%(str(time),numIn,numOut,str(hiddenLayerList),len(examplesTrain),len(examplesTest))
layerList = [numIn]+hiddenLayerList+[numOut]
nnet = NeuralNet(layerList)
if startNNet is not None:
nnet =startNNet
"""
YOUR CODE
"""
iteration=0
trainError=0
weightMod=1000
"""
Iterate for as long as it takes to reach weight modification threshold
"""
while(iteration < maxItr):
if(weightMod <= weightChangeThreshold):
break
trainError,weightMod = nnet.backPropLearning(examplesTrain,alpha)
iteration += 1
# print iteration,trainError,weightMod
if iteration%10==0:
print '! on iteration %d; training error %f and weight change %f'%(iteration,trainError,weightMod)
else :
print '.',
time = datetime.now().time()
print 'Finished after %d iterations at time %s with training error %f and weight change %f'%(iteration,str(time),trainError,weightMod)
"""
Get the accuracy of your Neural Network on the test examples.
For each text example, you should first feedforward to get the NN outputs. Then, round the list of outputs from the output layer of the neural net.
If the entire rounded list from the NN matches with the known list from the test example, then add to testCorrect, else add to testError.
"""
testError = 0
testCorrect = 0
for i in range(len(examplesTest)):
ypredict = nnet.feedForward(examplesTest[i][0])[-1]
ytest = examplesTest[i][1]
same = True
for j in range(len(ypredict)):
if(round(ypredict[j]) != ytest[j]):
same = False
break
if (same):
testCorrect += 1.
else:
testError += 1.
testAccuracy = testCorrect/len(examplesTest)
print 'Feed Forward Test correctly classified %d, incorrectly classified %d, test percent error %f\n'%(testCorrect,testError,testAccuracy)
"""return something"""
return tuple([nnet,testAccuracy])