nndl是什么?
《神经网络与深度学习》(《Neural Network and Deep Learning》)是机器学习大神
Michael Nielsen介绍神经网络入门的一本教材,英文地址:http://neuralnetworksanddeeplearning.com/在此感谢译者Xiaohu Zhu和Freeman Zhang。有对机器学习和神经网络有关内容感兴趣的读者,点这里获取相关资源。
P.S.: 本文仅对该书第二章进行总结,其中的描述会不全面,想详细了解请点击链接下载学习。
反向传播算法简介
反向传播算法(Backpropagation Algorithm),最初在二十世纪70年代被提出,但真正得到重视是在1986年。反向传播被应用于计算损失函数(代价函数)的梯度,具有稳定、速度快等特点。
定义&公式
权重(Weight) w j k l : w_{jk}^{l}: wjkl: 代表从 ( l − 1 ) t h (l-1)^{th} (l−1)th层的第 k t h k^{th} kth 个神经元到 l t h l^{th} lth 层的第 j t h j^{th} jth 个神经元的连接上的权重(注意 j j j 和 k k k 代表的意义从位置上看是反过来的,这样做是为了方便矩阵乘积直接计算而不用进行转置操作,一开始不太好理解;还有上面的层数 l l l 指的是到达层的位置,故前一层 ( l − 1 ) t h (l-1)^{th} (l−1)th 需要减去 1 1 1)。
偏置(Bias) b j l : b_{j}^{l} : bjl: 表示在第 l t h l^{th} lth 层第 j t h j^{th} jth 个神经元的偏置(有种把坐标系的x,y竖起来的感觉)。
带权输入
z
j
l
z_j^l
zjl:中间变量,表示
l
l
l层第
j
j
j个神经元的带权输入,即:
z
j
l
=
∑
k
w
j
k
l
a
k
l
−
1
+
b
j
l
.
z_j^l=\sum_kw_{jk}^la_k^{l-1}+b_j^l.
zjl=k∑wjklakl−1+bjl.
误差(Error) δ j l : \delta_j^l: δjl:中间量,表示 l t h l^{th} lth层第 j t h j^{th} jth个神经元上的误差。
Hadamard 乘积 s ⊙ t : s \odot t : s⊙t: 矩阵或向量的对应元素相乘,即 ( s ⊙ t ) j = s j t j (s \odot t)_j=s_jt_j (s⊙t)j=sjtj 。
激活函数(Activation Function) Sigmoid函数 : : : σ ( z ) = 1 1 + exp ( − z ) , \sigma(z)=\frac{1}{1+\exp(-z)}, σ(z)=1+exp(−z)1,其导数可表示为: σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z)=\sigma(z)(1-\sigma(z)) σ′(z)=σ(z)(1−σ(z))
激活值(Activation) a j l : a_{j}^{l} : ajl: 表示第 l t h l^{th} lth 层第 j t h j^{th} jth 个神经元的激活值,由激活函数定义可记为: a j l = σ ( z j l ) a_j^l=\sigma(z_j^l) ajl=σ(zjl)。
l t h l^{th} lth 层的第 j t h j^{th} jth 个神经元的激活值 a j l a_j^l ajl 和 l t h l^{th} lth 层的激活值之间的联系:
a j l = σ ( z j l ) = σ ( ∑ k w j k l a k l − 1 + b j l ) . a^l_j = \sigma(z_j^l)= \sigma\left(\sum_kw^l_{jk}a^{l−1}_k+b^l_j\right). ajl=σ(zjl)=σ(k∑wjklakl−1+bjl).
a l = σ ( w l a l − 1 + b l ) . a^l = \sigma(w^la^{l-1}+b^ l). al=σ(wlal−1+bl).
二次代价函数(Cost Function) :
C = 1 2 n ∑ x ∣ ∣ y ( x ) − a L ( x ) ∣ ∣ 2 . C=\frac{1}{2n}\sum_x{||y(x)-a^L(x)||^2}. C=2n1x∑∣∣y(x)−aL(x)∣∣2.
基本思想
反向传播的目标是计算代价函数 C C C分别关于权重 w w w和偏置 b b b的偏导 ∂ C / ∂ w \partial C/\partial w ∂C/∂w和 ∂ C / ∂ b \partial C/\partial b ∂C/∂b 。为了使反向传播算法可以正常工作,我们需要做两个主要假设如下。
- 第一个假设:代价函数可以被写成一个在每个训练样本 x x x上的代价函数 C x C_x Cx的均值 C = 1 n ∑ x C x = 1 n ∑ x 1 2 ∣ ∣ y − a L ∣ ∣ 2 C=\frac1n\sum_x{C_x}=\frac1n\sum_x{\frac12||y-a^L||^2} C=n1∑xCx=n1∑x21∣∣y−aL∣∣2。其原因是反向传播实际上是对一个独立的训练样本计算 ∂ C x / ∂ w \partial C_x/\partial w ∂Cx/∂w和 ∂ C x / ∂ b \partial C_x/\partial b ∂Cx/∂b ,然后我们通过在所有训练样本上进行平均化获得 ∂ C / ∂ w \partial C/\partial w ∂C/∂w和 ∂ C / ∂ b \partial C/\partial b ∂C/∂b 。
- 第二个假设:代价函数可以写成神经网络输出的函数,例如:
C = 1 2 n ∑ x ∣ ∣ y ( x ) − a L ( x ) ∣ ∣ 2 = 1 2 ∑ j ( y j − a j L ) 2 C=\frac{1}{2n}\sum_x{||y(x)-a^L(x)||^2}=\frac12\sum_j{(y_j-a^L_j)^2} C=2n1x∑∣∣y(x)−aL(x)∣∣2=21j∑(yj−ajL)2
Back Propagation四个基本方程
-
输出层的误差(注意输出层使用大写L记号) δ j L = ∂ C ∂ a j L σ ′ ( z j L ) \delta_j^L=\frac{\partial C}{\partial a_j^L}\sigma'(z_j^L) δjL=∂ajL∂Cσ′(zjL)矩阵表示:
δ L = ∇ a C ⊙ σ ′ ( z L ) \delta^L=\nabla_aC\odot\sigma'(z^L) δL=∇aC⊙σ′(zL) -
使用下一层的误差 δ l + 1 \delta^{l+1} δl+1来表示当前层的误差 δ l \delta^l δl
δ j l = ∑ k w j k l + 1 δ k l + 1 σ ′ ( z j l ) \delta_j^l=\sum_k{w_{jk}^{l+1}\delta_{k}^{l+1}\sigma'(z_j^l)} δjl=k∑wjkl+1δkl+1σ′(zjl)
δ l = ( ( w l + 1 ) T δ l + 1 ) ⊙ σ ′ ( z l ) \delta^l=((w^{l+1})^T\delta^{l+1})\odot \sigma'(z^l) δl=((wl+1)Tδl+1)⊙σ′(zl)
-
代价函数关于网络中任意偏置的改变率 ∂ C ∂ b j l = δ j l \frac{\partial C}{\partial b_j^l}=\delta_j^l ∂bjl∂C=δjl
-
代价函数关于任何一个权重的改变率 ∂ C ∂ w j k l = a k l − 1 δ j l \frac{\partial C}{\partial w_{jk}^l}=a_k^{l-1}\delta_j^l ∂wjkl∂C=akl−1δjl矩阵形式:
∂ C ∂ w = a i n δ o u t \frac{\partial C}{\partial w}=a_{in}\delta_{out} ∂w∂C=ainδout
算法表示
- 输入 x x x:为输入层设置激活值 a 1 a^1 a1;
- 前向传播:对每个 l = 2 , 3 , ⋯ , L l=2,3,\cdots,L l=2,3,⋯,L计算相应的 z l = w l a l − 1 + b l z^l=w^la^{l-1}+b^l zl=wlal−1+bl和 a l = σ ( z l ) a^l=\sigma(z^l) al=σ(zl);
- 输出层误差 δ l \delta^l δl:计算向量 δ l = ∇ a C ⊙ σ ′ ( z l ) \delta^l=\nabla_aC\odot\sigma'(z^l) δl=∇aC⊙σ′(zl);
- 反向传播误差:对每一个 l = L − 1 , L − 2 , ⋯ , 2 , l=L-1,L-2,\cdots,2, l=L−1,L−2,⋯,2, 计算 δ l = ( ( w l + 1 ) T δ l + 1 ) ⊙ σ ′ ( z l ) \delta_l=((w^{l+1})^T\delta^{l+1})\odot\sigma'(z^l) δl=((wl+1)Tδl+1)⊙σ′(zl)
- 输出:代价函数的梯度 ∂ C ∂ w j k l = a k l − 1 δ j l \frac{\partial C}{\partial w_{jk}^l}=a_k^{l-1}\delta_j^l ∂wjkl∂C=akl−1δjl ∂ C ∂ b j l = δ j l \frac{\partial C}{\partial b_j^l}=\delta_j^l ∂bjl∂C=δjl
Python实现
仅反向传播部分的代码,完整代码请见该书第二章。
import numpy as np
def sigmoid(z):
return 1. / (1. + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
class Network(object):
"""
省略其余方法的实现
"""
def backprop(self, x, y):
# 初始化权重向量和偏置向量,一般为零矩阵
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# 初始化激活值以及z向量(神经元的值z组成的向量)
activation = x
activations = [x]
z_vectors = []
# 开始循环计算z向量及激活向量
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation) + b
z_vectors.append(z)
activation = sigmoid(z)
activations.append(activation)
# 开始计算输出层的误差,应用第一个方程
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(z_vectors[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].T)
# 开始向前计算误差并存入梯度b及梯度w,应用第二个方程
for l in range(2, self.num_layers):
z = z_vectors[-l]
delta = np.dot(self.weights[-l + 1].T, delta) * sigmoid_prime(z)
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].T)
return nabla_b, nabla_w
def cost_derivative(self, output_activations, y):
# 定义代价函数的导数(梯度),本例中使用二次代价函数,故其导数为(输出层激活向量-y)
return output_activations - y