-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathnn.py
154 lines (119 loc) · 4.59 KB
/
nn.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
import numpy as np
from abc import ABC, abstractmethod
def create_weight_matrix(nrows, ncols):
"""Create a weight matrix with normally distributed random elements."""
return np.random.default_rng().normal(loc=0, scale=1/(nrows*ncols), size=(nrows, ncols))
def create_bias_vector(length):
"""Create a bias vector with normally distributed random elements."""
return create_weight_matrix(length, 1)
class ActivationFunction:
"""Class to be inherited by activation functions."""
@abstractmethod
def f(self, x):
"""The method that implements the function."""
pass
@abstractmethod
def df(self, x):
"""Derivative of the function with respect to its input."""
pass
class LeakyReLU(ActivationFunction):
"""Leaky Rectified Linear Unit."""
def __init__(self, leaky_param=0.1):
self.alpha = leaky_param
def f(self, x):
return np.maximum(x, x*self.alpha)
def df(self, x):
return np.maximum(x > 0, self.alpha)
class Sigmoid(ActivationFunction):
def f(self, x):
return 1/(1 + np.exp(-x))
def df(self, x):
return self.f(x) * (1 - self.f(x))
class LossFunction:
"""Class to be inherited by loss functions."""
@abstractmethod
def loss(self, values, expected):
"""Compute the loss of the computed values with respect to the expected ones."""
pass
@abstractmethod
def dloss(self, values, expected):
"""Derivative of the loss with respect to the computed values."""
pass
class MSELoss(LossFunction):
"""Mean Squared Error Loss function."""
def loss(self, values, expected):
return np.mean((values - expected)**2)
def dloss(self, values, expected):
return 2*(values - expected)/values.size
class CrossEntropyLoss(LossFunction):
"""Cross entropy loss function following the pytorch docs."""
def loss(self, values, target_class):
return -values[target_class, 0] + np.log(np.sum(np.exp(values)))
def dloss(self, values, target_class):
d = np.exp(values)/np.sum(np.exp(values))
d[target_class, 0] -= 1
return d
class Layer:
"""Model the connections between two sets of neurons in a network."""
def __init__(self, ins, outs, act_function):
self.ins = ins
self.outs = outs
self.act_function = act_function
self._W = create_weight_matrix(self.outs, self.ins)
self._b = create_bias_vector(self.outs)
def forward_pass(self, x):
"""Compute the next set of neuron states with the given set of states."""
return self.act_function.f(np.dot(self._W, x) + self._b)
class NeuralNetwork:
"""A series of connected, compatible layers."""
def __init__(self, layers, loss_function, learning_rate):
self._layers = layers
self._loss_function = loss_function
self.lr = learning_rate
# Check layer compatibility
for (from_, to_) in zip(self._layers[:-1], self._layers[1:]):
if from_.outs != to_.ins:
raise ValueError("Layers should have compatible shapes.")
def forward_pass(self, x):
out = x
for layer in self._layers:
out = layer.forward_pass(out)
return out
def loss(self, values, expected):
return self._loss_function.loss(values, expected)
def train(self, x, t):
"""Train the network on input x and expected output t."""
# Accumulate intermediate results during forward pass.
xs = [x]
for layer in self._layers:
xs.append(layer.forward_pass(xs[-1]))
dx = self._loss_function.dloss(xs.pop(), t)
for layer, x in zip(self._layers[::-1], xs[::-1]):
# Compute the derivatives
y = np.dot(layer._W, x) + layer._b
db = layer.act_function.df(y) * dx
dx = np.dot(layer._W.T, db)
dW = np.dot(db, x.T)
# Update parameters.
layer._W -= self.lr * dW
layer._b -= self.lr * db
if __name__ == "__main__":
"""Demo of a network as a series of layers."""
net = NeuralNetwork([
Layer(2, 4, LeakyReLU()),
Layer(4, 4, LeakyReLU()),
Layer(4, 3, LeakyReLU()),
], MSELoss(), 0.001)
t = np.zeros(shape=(3, 1))
loss = 0
for _ in range(100):
x = np.random.normal(size=(2, 1))
loss += net.loss(net.forward_pass(x), t)
print(loss)
for _ in range(10000):
net.train(np.random.normal(size=(2, 1)), t)
loss = 0
for _ in range(100):
x = np.random.normal(size=(2, 1))
loss += net.loss(net.forward_pass(x), t)
print(loss)