神经网络-bp反向传播

一、代价函数的形式

二次代价函数如下:

其中$x$为输入数据对应的正确输出,$a^L(x)$为输出节点对应的输出。将其简化为对单个$x$输出的误差公式如下:

可以看出$C$就是对众多$C_x$求平均。接下来对$C_x$做单独分析。

二、根据输出误差对b,w求导

数学符号定义
$a^L$:输出向量(第三节定义会改)
$w^L$:输出层的权重
$b^L$:输出层偏置

2.1 定义神经元


首先确定神经元形状,这里确定为s型神经元,公式如下:

对其求导得:(后面用得上)

2.2 误差函数说明


输出节点的误差为:

其中$a^L(x)$为:(对于$w,a,b$均是向量)

对$a^L(x)$求w偏导为:(后面2.3用的上)

对$a^L$求b偏导为:(后面2.3用的上)

2.3 使用输出误差函数对w,b偏导


$C_x^L$对w求偏导得:

$C_x^L$对b求偏导得:

这里仅仅求出了输出层的w,b偏导,那如何求出其他层的w,b偏导呢?使用反向传播公式推导。

三、反向传播公式推导

数学符定义
$a^L$:第L层的输出向量
$z^L$:第L层的输入向量
$w^L$:输出层的权重
$b^L$:输出层偏置
$C_x^L$:第L层误差向量,一般只有输出层用的上
$y$:正确的输出向量

3.1 正常的推导方法


假设当前L层误差定义为:

$C_x^L$对w求偏导得:

$C_x^L$对b求偏导得:

这样一层一层传递,就可以计算出每一个w,b的偏导数。但是公式会越来越长,计算量越来越大,接下来提出一种简化计算的方法。

3.2 优化的推导方法

3.2.1 当L前层对b,w求导

令$\delta ^ L$为第L层的误差,公式为:(这里还没有和w,b关联起来)

则此时根据2.3节可以得出如下结果:

由此可以,当前L层的b偏导向量等于当前层的误差向量,w偏导向量等于误差向量乘L-1层的输出向量,后面我们只需到得到每一层的误差向量就好。这里可能会有问题,这个$\delta ^ L$看不懂啊?可以理解为没有意义,为了简化计算。(实际上有其物理意义)

由于上式只适用于s型神经元,和二元误差,修改为更加通用的:

其中$\nabla_a C$对应于$(a^{L}-y)$,$\sigma’(z^L)$对应于$(a^L(1-a^L))$

3.2.2 L-1前层对b,w求导

接下来推导下一节点的误差$\delta ^ {L-1}$,仔细观察$\frac{\partial C_x^L}{\partial b^{L-1}}$,根据上面的结论它其实等于L-1层的误差值,表达式如下:

则此时

可以验证,和提出$\delta ^L$误差向量之前的证明结果一样,优点是计算量固定,由前一层的误差推倒后一层的误差,每次的计算量相差不大。

3.2.3 四大公式汇总

其中:$\nabla_a C$为输出层的求导,然后经过第二个公式层层推导,推导出每一层的$\delta ^ L$,最后两个公式根据每一层的误差对w,b求导。

四、python算法实现

直接上程序,注:是不能直接运行的,为了更好理解:

符号说明:
delta对应于符号$\delta$
nabla对应于符号$\nabla$

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
def backprop(self,x,y):
"""
:param x: 输入训练数据
:param y: 正确是数据
:return:NULL
"""

# 开辟新的b,w储存空间,类似于new一个空间
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]

activation = x #做一个变量存,先暂时保存了x的值
activations = [x] #这是一个list,先保存了x的,放在最前面
zs = [] #这是一个list,用来保存s型神经元计算后的值

# 正序输出
for b, w in zip(self.biases, self.weights):
# w是一个二维向量,每一维表示一个目标神经元,activation一维向量(动态变化),dot表示向量乘
z = np.dot(w, activation)+b
# z保存到zs之中,后面bp要用到
zs.append(z)
# s型神经元的输出
activation = sigmoid(z)
# a保存到activations中,后面bp要用到
activations.append(activation)

#activations[-1]位置储存的是输出向量,向量对应元素相乘,一定要是np.array创建的数组才可以这么乘
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])
#只要有\delta函数,根据4大公式后两个,直接得到b
nabla_b[-1] = delta
#只要有\delta函数,根据4大公式后两个,直接得到w
nabla_w[-1] = np.dot(delta, activations[-2].transpose())

#反向传播开始,主要使用到4大公式中的第二个
for L in range(2, self.num_layers):
#表示为当前层每个神经元的输入,是一个向量。在第一次循环里表示倒数第二层
z = zs[-L]
#查看该函数的注释
sp = sigmoid_prime(z)

# 使用反向传播规则,在第一次循环中推导倒数第二层的误差
delta = np.dot(self.weights[-L + 1].transpose(), delta) * sp
#只要有\delta函数,根据4大公式后两个,直接得到b
nabla_b[-L] = delta
#只要有\delta函数,根据4大公式后两个,直接得到w
nabla_w[-L] = np.dot(delta, activations[-L - 1].transpose())

# 求输出和输入的误差
def cost_derivative(self, output_activations, y):
return (output_activations - y)
反向传播主程序
1
2
3
4
5
6
7
8
# 定义S型函数
def sigmoid(z):
# 1.0而不是1,就很有讲究,表示计算方式为浮点型
return 1.0 / (1.0 + np.exp(-z))

# S型函数对z求导
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
s型神经元函数
-------------本文结束感谢您的阅读-------------
0%