使用Numpy手动实现全连接神经网络

什么是神经网络

接收刺激 ——> 产生信号 ——> 经验学习,这样的一组过程就是一次学习过程,而这样的一组过程,如果被放到一个容器中,那么,这个容器就是一个神经元

神经元

而神经网络,其实就是由多个神经元的组合而成的,多个神经元共同组成一个神经网络;整个学习过程可以这样文字描述:接收刺激——>产生信号——>接收刺激——>产生信号——>…——>经验学习

神经网络

什么是前向传播

假设我们有这样一组数据

x=[(1.0, 2.3), (3.1, 0.5), (1.2, 1.3), (0.9, 0.6)], y=[1, 1, 0, 0]

我们需要找到从x->y的关系,其实也可以认为这是一个分类问题,因为当我们粗略的查看xy两个数据时发现,当x1 + x2 > 3时,y对应的是1,因此我们就希望找到这样一个函数y = W*x + b能够描述这个关系,那么就要去解出Wb这两个参数,而在解的过程,我们需要代入已知数据x到我们假设的函数y = W * x + b当中,可以得出这样的一个式子:y_pre = W * xi + b,xi属于x,这个过程,就是前向传播的过程,在这前向传播过程当中,我们需要去解出参数Wb,而这个解的过程,我们需要利用一个叫代价函数的东西

什么是代价函数

通过刚才的前向传播过程,我们得到了每一个xi对应的y_pre值,这个y_pre值,是根据我们假设的函数所求得的,那么怎么判断这个函数能否很好的描述出我们想要的关系呢?这就需要我们去计算y_truey_pre的误差总和:cost_function = abs(y_true - y_pre),而我们的代价函数是与Wb有关的,我们期望找到代价函数的最小值,使得函数描述能够更加准确;因此,当代价函数取到最小值时对应的Wb就是我们所需要的此时的参数Wb的解,为了解出Wb,我们采用梯度下降法,梯度下降法在这篇博文说了差不多了,所以就不细说了;但是,在进行梯度下降时,有一个很繁琐的问题,就是求代价函数与每一个节点的梯度,根据梯度对权重W进行更新操作,如何快速的求解这些梯度问题,这个时候,就需要反向传播算法了

什么是反向传播

对于反向传播的具体数学公式推导,这本书详细的说明了(主要是有一些中间步骤的推导我还没完全弄明白);反向传播算法,其实就是根据最后输出层的输出数值与实际结果之间的误差,将这个误差反向从输出层开始从右向左反向传播回输入层。假定我们的误差函数为g(x)=(1/2)*(y_pre - y_true)^2y=Σ(Wx),进行求偏导数,然后进行一系列数学公式推导整合之后,我们得到了更新权重W的公式

△w=-n*(∂[g(x)]/∂[w]) {权值校正=(-1)*学习率*局部梯度*神经元的输入信号}

这里之所以取反的原因是我们利用代价函数求出每个节点的梯度,梯度的方向代表了误差扩大方向,因此为了实现误差缩小,采用取反操作。因此,根据反向传播算法,我们可以确定每一个权重参数wi的更新公式w -= △w

具体函数实现

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
import numpy as np
import random


class Network(object):
"""[summary]

Arguments:
object {[type]} -- [description]

Returns:
[type] -- [description]
"""

def __init__(self, sizes):
"""[summary]

Arguments:
sizes {[type]} -- [description]
"""
self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1: ]]
self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]

def feedfoward(self, a):
"""[summary]

Arguments:
a {[type]} -- [description]

Returns:
[type] -- [description]
"""
for b, w in zip(self.biases, self.weights):
# zip()函数的使用
# a = [1, 2, 3]; b = [4, 5, 6]
# zip(a, b) =>> [(1, 4), (2, 5), (3, 6)]
a = sigmoid(np.dot(w, a) + b)
return a

def SGD(self, training_data, epochs, mini_batch_size, learn_rate, test_data=None):
"""[summary]

Arguments:
training_data {list} -- [训练数据]
epochs {int} -- 训练轮书
mini_batch_size {int} -- [每批计算数据大小]
learn_rate {float} -- [学习率]

Keyword Arguments:
test_data {list{int}} -- [测试数据集] (default: {None})
"""
if test_data:
n_test = len(test_data)
n = len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
# 根据mini_batch_size将training_data分割成小批数据用于随机梯度下降
mini_batches = [training_data[k: k + mini_batch_size] for k in xrange(0, n, mini_batch_size)]
# 循环拿取每批训练数据进行训练
for mini_batch in mini_batches:
#
self.update_mini_batch(mini_batch, learn_rate)
if test_data:
print "Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)
else:
print "Epoch {0} complete".format(j)

def update_mini_batch(self, mini_batch, eta):
"""[summary]

Arguments:
mini_batch {list} -- [description]
eta {float} -- [学习率]
"""
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
# 更新对应批量的权重参数 w 与偏置参数 b
nabla_b = [nb + dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw + dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w - (eta / len(mini_batch)) * nw for w, nw in zip(self.weights, nabla_w)]
self.biases = [b - (eta / len(mini_batch)) * nb for b, nb in zip(self.biases, nabla_b)]

def backprop(self, x, y):
"""[summary]

Arguments:
x {int} -- [输入信号]
y {int} -- [正确输出信号]

Returns:
[tuple(list, list)] -- [返回]
"""
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
activation = x
activations = [x]
zs = []
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation) + b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# 计算最后一层的误差值大小
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# 利用反向传播算法求出对应权重参数 w 与偏置参数 b 的更新值
for l in xrange(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l + 1].transopse(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l - 1].trainspose())
return (nabla_b, nabla_w)

def evaluate(self, test_data):
"""[summary]

Arguments:
test_data {list} -- [测试数据集]

Returns:
[int] -- [返回预测正确的个数]
"""
test_result = [(np.argmax(self.feedfoward(x)), y) for x, y in test_data]
return sum(int(x == y) for (x, y) in test_result)

def cost_derivative(self, output_activations, y):
"""[代价函数]

Arguments:
output_activations {list} -- [预测输出值]
y {list} -- [实际值]

Returns:
[list] -- [返回对应项实际值与预测输出值的误差]
"""
return (output_activations - y)

def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))

def sigmoid_prime(z):
"""[sigmoid函数的导数公式]

Arguments:
z {list} -- [输入信号]

Returns:
[list] -- [sigmoid激活函数的导数值]
"""
return sigmoid(z) * (1 - sigmoid(z))