Skip to content

Commit 8bd91e0

Browse files
committed
Merge pull request aimacode#129 from Schrodinger1926/learner
Revised Implementation of backpropagation, util func
2 parents 171782d + 1c1075a commit 8bd91e0

3 files changed

Lines changed: 170 additions & 11 deletions

File tree

learning.py

Lines changed: 136 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -416,24 +416,152 @@ def predict(example):
416416
# ______________________________________________________________________________
417417

418418

419-
def NeuralNetLearner(dataset, sizes):
420-
"""Layered feed-forward network."""
419+
def NeuralNetLearner(dataset, hidden_layer_sizes=[3],
420+
learning_rate=0.01, epoches=100):
421+
"""
422+
Layered feed-forward network.
423+
hidden_layer_sizes: List of number of hidden units per hidden layer
424+
learning_rate: Learning rate of gradient decent
425+
epoches: Number of passes over the dataset
426+
"""
421427

422-
activations = [[0.0 for i in range(n)] for n in sizes] # noqa
423-
weights = [] # noqa
428+
examples = dataset.examples
429+
i_units = len(dataset.inputs)
430+
o_units = 1 # As of now, dataset.target gives only one index.
431+
432+
# construct a network
433+
raw_net = network(i_units, hidden_layer_sizes, o_units)
434+
learned_net = BackPropagationLearner(dataset, raw_net,
435+
learning_rate, epoches)
424436

425437
def predict(example):
426-
unimplemented()
438+
439+
# Input nodes
440+
i_nodes = learned_net[0]
441+
442+
# Activate input layer
443+
for v, n in zip(example, i_nodes):
444+
n.value = v
445+
446+
# Forward pass
447+
for layer in learned_net[1:]:
448+
for node in layer:
449+
inc = [n.value for n in node.inputs]
450+
in_val = dotproduct(inc, node.weights)
451+
node.value = node.activation(in_val)
452+
453+
# Hypothesis
454+
o_nodes = learned_net[-1]
455+
pred = [o_nodes[i].value for i in range(o_units)]
456+
return pred[0]
427457

428458
return predict
429459

430460

431461
class NNUnit:
462+
"""
463+
Single Unit of Multiple Layer Neural Network
464+
inputs: Incoming connections
465+
weights: weights to incoming connections
466+
"""
432467

433-
"""Unit of a neural net."""
468+
def __init__(self, weights=None, inputs=None):
469+
self.weights = []
470+
self.inputs = []
471+
self.value = None
472+
self.activation = sigmoid
434473

435-
def __init__(self):
436-
unimplemented()
474+
475+
def network(input_units, hidden_layer_sizes, output_units):
476+
"""
477+
Create of Directed Acyclic Network of given number layers
478+
hidden_layers_sizes : list number of neuron units in each hidden layer
479+
excluding input and output layers.
480+
"""
481+
layers_sizes = [input_units] + hidden_layer_sizes + [output_units]
482+
net = [[NNUnit() for n in range(size)]
483+
for size in layers_sizes]
484+
n_layers = len(net)
485+
486+
# Make Connection
487+
for i in range(1, n_layers):
488+
for n in net[i]:
489+
for k in net[i-1]:
490+
n.inputs.append(k)
491+
n.weights.append(0)
492+
return net
493+
494+
495+
def BackPropagationLearner(dataset, network, learning_rate, epoches):
496+
"[Fig. 18.23] The back-propagation algorithm for multilayer network"
497+
# Initialise weights
498+
for layer in network:
499+
for node in layer:
500+
node.weights = [random.uniform(-0.5, 0.5)
501+
for i in range(len(node.weights))]
502+
503+
examples = dataset.examples
504+
'''
505+
As of now dataset.target gives an int instead of list,
506+
Changing dataset class will have effect on all the learners.
507+
Will be taken care of later
508+
'''
509+
idx_t = [dataset.target]
510+
idx_i = dataset.inputs
511+
n_layers = len(network)
512+
o_nodes = network[-1]
513+
i_nodes = network[0]
514+
515+
for epoch in range(epoches):
516+
# Iterate over each example
517+
for e in examples:
518+
i_val = [e[i] for i in idx_i]
519+
t_val = [e[i] for i in idx_t]
520+
# Activate input layer
521+
for v, n in zip(i_val, i_nodes):
522+
n.value = v
523+
524+
# Forward pass
525+
for layer in network[1:]:
526+
for node in layer:
527+
inc = [n.value for n in node.inputs]
528+
in_val = dotproduct(inc, node.weights)
529+
node.value = node.activation(in_val)
530+
531+
# Initialize delta
532+
delta = [[] for i in range(n_layers)]
533+
534+
# Compute outer layer delta
535+
o_units = len(o_nodes)
536+
err = [t_val[i] - o_nodes[i].value
537+
for i in range(o_units)]
538+
delta[-1] = [(o_nodes[i].value)*(1 - o_nodes[i].value) *
539+
(err[i]) for i in range(o_units)]
540+
541+
# Backward pass
542+
h_layers = n_layers - 2
543+
for i in range(h_layers, 0, -1):
544+
layer = network[i]
545+
h_units = len(layer)
546+
nx_layer = network[i+1]
547+
# weights from each ith layer node to each i + 1th layer node
548+
w = [[node.weights[k] for node in nx_layer]
549+
for k in range(h_units)]
550+
551+
delta[i] = [(layer[j].value) * (1 - layer[j].value) *
552+
dotproduct(w[j], delta[i+1])
553+
for j in range(h_units)]
554+
555+
# Update weights
556+
for i in range(1, n_layers):
557+
layer = network[i]
558+
inc = [node.value for node in network[i-1]]
559+
units = len(layer)
560+
for j in range(units):
561+
layer[j].weights = vector_add(layer[j].weights,
562+
scalar_vector_product(learning_rate * delta[i][j], inc))
563+
564+
return network
437565

438566

439567
def PerceptronLearner(dataset, sizes):

tests/test_utils.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ def test_product():
3131

3232
def test_first():
3333
assert first('word') == 'w'
34-
assert first('') == None
34+
assert first('') is None
3535
assert first('', 'empty') == 'empty'
3636
assert first(range(10)) == 0
37-
assert first(x for x in range(10) if x > 3) == 4
38-
assert first(x for x in range(10) if x > 100) == None
37+
assert first(x for x in range(10) if x > 3) == 4
38+
assert first(x for x in range(10) if x > 100) is None
39+
3940

4041
def test_is_in():
4142
e = []
@@ -90,6 +91,10 @@ def test_vector_add():
9091
assert vector_add((0, 1), (8, 9)) == (8, 10)
9192

9293

94+
def test_scalar_vector_product():
95+
assert scalar_vector_product(2, [1, 2, 3]) == [2, 4, 6]
96+
97+
9398
def test_num_or_str():
9499
assert num_or_str('42') == 42
95100
assert num_or_str(' 42x ') == '42x'
@@ -111,5 +116,17 @@ def f():
111116
assert f() == 'f'
112117

113118

119+
def test_sigmoid():
120+
assert math.isclose(0.5, sigmoid(0)) is True
121+
assert math.isclose(0.7310585786300049, sigmoid(1)) is True
122+
assert math.isclose(0.2689414213699951, sigmoid(-1)) is True
123+
124+
125+
def test_step():
126+
assert step(1) == 1
127+
assert step(0) == 1
128+
assert step(-1) == 0
129+
130+
114131
if __name__ == '__main__':
115132
pytest.main()

utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ def vector_add(a, b):
169169
return tuple(map(operator.add, a, b))
170170

171171

172+
def scalar_vector_product(X, Y):
173+
"""Return vector as a product of a scalar and a vector"""
174+
return [X*y for y in Y]
175+
176+
172177
def probability(p):
173178
"Return true with probability p."
174179
return p > random.uniform(0.0, 1.0)
@@ -216,6 +221,15 @@ def clip(x, lowest, highest):
216221
return max(lowest, min(x, highest))
217222

218223

224+
def sigmoid(x):
225+
"""Return activation value of x with sigmoid function"""
226+
return 1/(1 + math.exp(-x))
227+
228+
229+
def step(x):
230+
"""Return activation value of x with sign function"""
231+
return 1 if x >= 0 else 0
232+
219233
# ______________________________________________________________________________
220234
# Misc Functions
221235

0 commit comments

Comments
 (0)