笔记整理:前向传递与反向传播

前向传递与反向传播

引言

神经网络其实就是一个输入 X X X到输出 Y Y Y的映射函数: f ( X ) = Y f(X)=Y f(X)=Y,函数的系数(权重)就是我们所要训练的网络参数 W W W,只要能够确定网络参数 W W W,对于任何的输出 x i x_i xi就能得到与之对应的 y i y_i yi

问题

当我们手中只有训练集的输入 X X X和输出 Y Y Y,我们应该如何调整网络参数 W W W,使得输出的 f ( X ) = Y ^ f(X)=\hat{Y} f(X)=Y^与训练集中的真实值 Y Y Y尽可能接近?

先对反向传播过程有一个直观上的印象。首先,反向传播的核心是cost fuction J J J对网络中参数(各层的权重 W W W以及偏置项 b b b)的偏导表达式,如下:
∂ J ∂ w \frac{\partial{J}}{\partial{w}} wJ
∂ J ∂ b \frac{\partial{J}}{\partial{b}} bJ
上述的表达式描述了代价函数 J J J随权重 W W W或偏置 b b b变化而变化的程度。

BP算法的思路: 如果当前代价函数所得的数值与真实值相差较远,通过调整 w w w b b b的值,使得新的代价函数所得的预测值更接近真实值。一直重复该过程,直到最终的代价函数所得的预测值与真实值相差在误差范围之内,则停止迭代。

前向传递过程

前向传播过程:已知输入 X X X、权重 W W W以及偏置 b b b,求得输出 Y Y Y
最普通的DNN可以看作一个多层感知机MLP, w 24 ( 3 ) w_{24}^{(3)} w24(3)代表神经网络第2层的第4个神经元连接神经网络第3层的第2个神经元的权重。

提问:为什么权重不写成 w 42 ( 3 ) w_{42}^{(3)} w42(3)
回答:如果写成 w 42 ( 3 ) w_{42}^{(3)} w42(3),则第3层神经元的输入就要通过 w T x + b w^Tx+b wTx+b来计算,而当权重为 w 24 ( 3 ) w_{24}^{(3)} w24(3)时,只需要计算 w x + b wx+b wx+b,省略了矩阵转置的额外工作量。

DNN
偏置项 b b b的定义也类似, b 3 ( 2 ) b_3^{(2)} b3(2)表示神经网络第2层的第3个神经元的偏置。
在这里插入图片描述
再定义 a j i a_j^i aji z j i z_j^i zji,分别表示第 i i i层神经网络的第 j j j个神经元的激活前的输入和激活后的输出,并假设 σ ( z ) \sigma(z) σ(z)是激活函数。

Sigmoid函数:
σ ( x ) = 1 1 + e − x \sigma(x)=\frac{1}{1+e^{-x}} σ(x)=1+ex1
σ ′ ( x ) = e − x ( 1 + e − x ) 2 = 1 1 + e − x ⋅ ( 1 − 1 1 + e − x ) = σ ( x ) ⋅ ( 1 − σ ( x ) ) {\sigma}'(x)=\frac{e^{-x}}{(1+e^{-x})^2} = \frac{1}{1+e^{-x}}\cdot{(1-\frac{1}{1+e^{-x}})}=\sigma(x)\cdot(1-\sigma(x)) σ(x)=(1+ex)2ex=1+ex1(11+ex1)=σ(x)(1σ(x))sigmoid函数

三层DDN,输入层-隐藏层-输出层
举例:
对于第2层第1个节点的输出 a 1 2 a_1^2 a12有: a 1 2 = σ ( z 1 2 ) = σ ( w 11 2 x 1 + w 12 2 x 2 + w 13 2 x 3 + b 1 2 ) a_1^2 = \sigma(z_1^2)=\sigma(w_{11}^2x_1+w_{12}^2x_2+w_{13}^2x_3+b^2_1) a12=σ(z12)=σ(w112x1+w122x2+w132x3+b12)
所以一般化,假设 l − 1 l-1 l1层有 m m m个神经元,对于 a j i a_j^i aji有:
a j l = σ ( z j l ) = σ ( ∑ k = 1 m w j k l a k l − 1 + b j l ) a_j^l=\sigma(z_j^l)=\sigma(\sum_{k=1}^m{w_{jk}^la_k^{l-1}+b_j^l}) ajl=σ(zjl)=σ(k=1mwjklakl1+bjl)

矩阵形式:
a L = σ ( w L a L − 1 + b L ) a^L = \sigma(w^La^{L-1}+b^L) aL=σ(wLaL1+bL)

反向传播过程

当通过前向传递得到由任意一组随机参数 W W W b b b计算出的预测结果后,我们可以利用损失函数相对于每个参数的梯度对他们进行修正,从而得到误差范围内的模型,即目标函数 Y Y Y。事实上,神经网络的训练就是这样一个不停的前向-反向传播的过程,直到网络的预测能力达到我们的预期。

反向传播的四个基本方程

根据上述过程描述,权重 w w w和偏置 b b b的改变如何影响代价函数 J J J是理解反向传播的关键,意味着计算出每个 ∂ J ∂ w j k l \frac{\partial{J}}{\partial{w^l_{jk}}} wjklJ ∂ J ∂ b j l \frac{\partial{J}}{\partial{b_j^l}} bjlJ
在讨论之前,引入误差 δ j l \delta_j^l δjl,表示第 l l l层第 j j j个神经元的误差。对于这个误差的理解,《Neural Network and Deep Learning》书中给了一个比较形象的例子。
解释误差的例子
如上图所示,有一个小恶魔在网络的第 l l l层第 j j j个神经元捣乱,使得这个神经元发生了变化 Δ z j l \Delta z_j^l Δzjl,那么这个神经元在激活后即为 σ ( z j l + Δ z j l ) \sigma(z_j^l+\Delta z_j^l) σ(zjl+Δzjl),然后这个误差向后逐层传播下去,导致最终的代价函数变化了 ∂ J ∂ z j l Δ j l \frac{\partial{J}}{\partial{z_j^l}}\Delta_j^l zjlJΔjl。然而,小恶魔良心发现,给予我们帮助,使得目标函数 Y Y Y输出的值接近groud truth。选择一个和 ∂ J ∂ z j l \frac{\partial{J}}{\partial{z_j^l}} zjlJ方向相反的 Δ z j l \Delta z_j^l Δzjl,使得损失函数的值更小(梯度下降法)。随着迭代的进行, ∂ J ∂ z j l \frac{\partial{J}}{\partial{z_j^l}} zjlJ会逐渐趋向于0,此时有局部最优解了。这就启发我们可以用 ∂ J ∂ z j l \frac{\partial{J}}{\partial{z_j^l}} zjlJ来衡量神经元的误差:
δ j l = ∂ J ∂ z j l \delta_j^l = \frac{\partial{J}}{\partial{z_j^l}} δjl=zjlJ

1. 输出层的误差方程(计算最后一层神经网络产生的误差)

δ j L = ∂ J ∂ z j L = ∂ J ∂ a j L ∂ a j L ∂ z j L = ∂ J ∂ a j L σ ′ ( z j L ) \delta_j^L = \frac{\partial{J}}{\partial{z_j^L}}=\frac{\partial{J}}{\partial{a_j^L}}\frac{\partial{a_j^L}}{\partial{z_j^L}}=\frac{\partial{J}}{\partial{a_j^L}}{\sigma}'(z_j^L) δjL=zjLJ=ajLJzjLajL=ajLJσ(zjL)

注释:等式的最右边的第一项, ∂ J ∂ a j L \frac{\partial{J}}{\partial{a_j^L}} ajLJ衡量了代价函数随着网络最终输出的变化快慢,而第二项 σ ′ ( z j L ) {\sigma}'(z_j^L) σ(zjL)则衡量了激活函数输出随 z j L z_j^L zjL的变化快慢。 L L L指最后一层输出层。

矩阵形式:
假设代价函数为 J ( w , b , x , y ) = 1 2 ∑ j = 1 m ( y j − a j L ) 2 J(w, b, x, y) = \frac{1}{2}\sum_{j=1}^m(y_j-a_j^L)^2 J(w,b,x,y)=21j=1m(yjajL)2,则 ∂ J ∂ a j L = a j L − y i \frac{\partial{J}}{\partial{a_j^L}}=a_j^L-y_i ajLJ=ajLyi
则得到以下公式:
δ L = ▽ a J ⊙ σ ′ ( z L ) \delta^L=\bigtriangledown_aJ\odot{\sigma}'(z^L) δL=aJσ(zL)
说明: ⊙ \odot 为Hadamard积,即矩阵的点积

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)
由以上公式可得,可以通过第 l + 1 l+1 l+1层得误差,求得第 l l l层的误差,通过结合输出层误差方程和误差传递方程,就可以计算神经网络中任意一层的误差,先计算 δ L \delta^L δL,再计算 δ L − 1 \delta^{L-1} δL1 δ L − 2 \delta^{L-2} δL2 δ L − 3 \delta^{L-3} δL3…,直到输入层。

推导过程:
δ j l = ∂ J ∂ z j l = ∑ k = 1 m ∂ J ∂ z k l + 1 ∂ z k l + 1 ∂ a j l ∂ a j l ∂ z j l = ∑ k = 1 m δ j l + 1 ⋅ ∂ w k j l + 1 a j l + b k l + 1 ∂ a j l ⋅ σ ( z j l ) ′ = ∑ k = 1 m δ j l + 1 ⋅ w k j l + 1 ⋅ σ ( z j l ) ′ \begin{aligned} \delta_j^l & = \frac{\partial{J}}{\partial{z_j^l}} \\ & = \sum_{k=1}^m\frac{\partial{J}}{\partial{z_k^{l+1}}}\frac{\partial{z_k^{l+1}}}{\partial{a_j^l}}\frac{\partial{a_j^l}}{\partial{z_j^l}} \\ & =\sum_{k=1}^m\delta_j^{l+1}\cdot\frac{\partial{w_{kj}^{l+1}a_j^l+b_k^{l+1}}}{\partial{a_j^l}}\cdot {\sigma(z_j^l)}' \\ & =\sum_{k=1}^{m}\delta_j^{l+1}\cdot w_{kj}^{l+1}\cdot {\sigma(z_j^l)}' \end{aligned} δjl=zjlJ=k=1mzkl+1Jajlzkl+1zjlajl=k=1mδjl+1ajlwkjl+1ajl+bkl+1σ(zjl)=k=1mδjl+1wkjl+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)

3.代价函数对权重的改变率(计算权重的梯度)

∂ J ∂ w j k l = a k l − 1 δ j l \frac{\partial{J}}{\partial{w_{jk}^l}}=a_k^{l-1}\delta_j^l wjklJ=akl1δjl
推导过程:
∂ J ∂ w j k l = ∂ J ∂ z j k l ⋅ ∂ z j k l ∂ w j k l = δ j l ⋅ ∂ ( w j k l a k l − 1 + b j l ) ∂ w j k l = a k l − 1 δ j l \begin{aligned} \frac{\partial{J}}{\partial{w_{jk}^l}} & = \frac{\partial{J}}{\partial{z_{jk}^l}} \cdot \frac{\partial{z_{jk}^l}}{\partial{w_{jk}^l}}\\ & = \delta_j^l \cdot \frac{\partial{(w_{jk}^l a_k^{l-1}+b_j^l)}}{\partial{w_{jk}^l}} \\ & = a_k^{l-1}\delta_j^l \end{aligned} wjklJ=zjklJwjklzjkl=δjlwjkl(wjklakl1+bjl)=akl1δjl
可以化简得到下面式子:
∂ J ∂ w = a i n δ o u t \frac{\partial J}{\partial w}=a_{in}\delta_{out} wJ=ainδout
由上述的公式可得,当上一层激活后的输出接近0的时候,无论返回的误差为多大, ∂ J ∂ w \frac{\partial J}{\partial w} wJ的改变都很小,这就是解释了为什么神经元饱和不利于训练。当激活前的神经元没有被激活,或者激活后输出神经元处于饱和状态,权重和偏置会学习的非常慢,这不是我们想要的效果。这也说明了为什么我们平时总是说激活函数的选择非常重要。

4. 代价函数对偏置的改变率(计算偏置的梯度)

公式如下:
∂ J ∂ b j l = δ j l \frac{\partial J}{\partial{b_j^l}} = \delta_j^l bjlJ=δjl

推导过程:
∂ J ∂ b j l = ∂ J ∂ z j l ⋅ ∂ z j l ∂ b j l = δ j l ⋅ ∂ ( w j k l a k l − 1 + b j l ) ∂ b j l = δ j l \begin{aligned} \frac{\partial J}{\partial b_j^l} &=\frac{\partial J}{\partial z_j^l} \cdot \frac{\partial z_j^l}{\partial b_j^l} \\ &=\delta_j^l \cdot \frac{\partial(w_{jk}^l a_k^{l-1}+b_j^l)}{\partial b_j^l} \\ &=\delta_j^l \end{aligned} bjlJ=zjlJbjlzjl=δjlbjl(wjklakl1+bjl)=δjl

反向传播算法伪代码

  • 输入训练集,共有 m m m个样本
  • 对于训练集中的每个样本 x x x,设置输入层(input layer)对应的激活函数 a l a^l al
  • 前向传播: a l = σ ( z l ) = σ ( w l a l − 1 + b l ) a^l = \sigma(z^l)=\sigma(w^l a^{l-1}+b^l) al=σ(zl)=σ(wlal1+bl)
  • 计算输出层误差: δ L = ▽ a J ⊙ σ ′ ( z L ) \delta^L=\bigtriangledown_aJ\odot{\sigma}'(z^L) δL=aJσ(zL)
  • 反向传播: δ 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)
  • 使用梯度下降(gradient descent),优化权重和偏置:
    w l → w l − η m ∑ x = 1 m δ x , l ( a x , l − 1 ) T w^l \rightarrow w^l-\frac{\eta}{m}\sum_{x=1}^{m}\delta^{x,l}(a^{x,l-1})^T wlwlmηx=1mδx,l(ax,l1)T
    b l → b l − η m ∑ x = 1 k δ x , l b^l \rightarrow b^l - \frac{\eta}{m}\sum_{x=1}^k\delta^{x,l} blblmηx=1kδx,l

链接:含有实例的BP算法推导

附上代码:

import numpy as np 
import random

def sigmoid_func(x):
    """
    激活函数sigmoid
    """
    return 1/(1 + np.exp(-x))

def sigmoid_derivation(x):
    """
    激活函数的导数
    """
    y = 1/(1 + np.exp(-x))
    
    return y * (1 - y)

def loss_function(output_arry, groud_truth):
    """
    求出与groud truth的损失
    """
    diff = (np.sum((output_arry - groud_truth) ** 2)) / 2
    # print(diff)
    return diff

def backpropagation_func(input_arry, groud_truth, theta):
    """
    反向传播优化权重以及偏置
    input: 
    input_arry --> 输入的矩阵
    groud_truth --> 真实值
    theta ---> 学习率
    
    return: 
    output ---> 输出的值
    """
    # 假定是一个3层的神经网络
    # 假设输入的单个样本的特征向量是1个纬度,共有2个样本,即输入的矩阵为2*1
    # 第2层的有2个神经元,即w的矩阵为2*2
    
    # 前向传递
    w_2 = [[0.15, 0.20], [0.25, 0.30]]
    bias_2 = 0.35
    w_3 = [[0.40, 0.45], [0.50, 0.55]]
    bias_3 = 0.6
    # print(bias_2)
    # print(w_2)

    z_2 = np.dot(w_2, input_arry) + bias_2
    a_2 = sigmoid_func(z_2)
    # print('z2=', z_2)
    # print('a2=', a_2)
    
    z_3 = np.dot(w_3, a_2) + bias_3
    a_3 = sigmoid_func(z_3)
    # print('z3=', z_3)
    # print('a3=', a_3)

    loss = loss_function(a_3, groud_truth)
    print("前向传播的损失为: {}".format(loss))

    for count in range(1000):
        if loss > 0.01:
            # 反向传播
           
            # 输出层的误差(对z的梯度)
            sigmoid_de = sigmoid_derivation(z_3)
            # print('梯度:{}'.format(sigmoid_de))
            #numpy中 * 表示Hadmard乘积
            # print(a_3 - groud_truth)
            
            delta_L = (a_3 - groud_truth) * sigmoid_de
            # print("输出层的误差:{}".format(delta_L))
            

            # 计算第二层的神经元的误差
            # 切记,这里需要将w_3转置,因为上一层的神经元影响下一层的两个神经元
            w_3_transpose = list(np.array(w_3).T)
            delta_l = np.dot(w_3_transpose, delta_L) * sigmoid_derivation(z_2)
            # print("第二层神经元的误差:{}".format(delta_l))
            # print('w_3 = ', w_3[0])
            # print('result = \n', theta * delta_L[0] * a_2)
            # 权重更新
            for i in range(len(w_3)):
                w_3[i] -= theta * delta_L[i] * a_2
            # print("最后一层权重的更新为: {}".format(w_3))
            for i in range(len(w_2)):
                w_2[i] -= theta * delta_l[i] * input_arry
            # print("非最后一层权重更新为:{}".format(w_2))

            # 偏置更新
            bias_3 -= theta * delta_L
            bias_2 -= theta * delta_l
            
            # 再一次进行前向传播
            z_2 = np.dot(w_2, input_arry) + bias_2
            a_2 = sigmoid_func(z_2)
            # print('z2=', z_2)
            # print('a2=', a_2)
            
            z_3 = np.dot(w_3, a_2) + bias_3
            a_3 = sigmoid_func(z_3)
            # print('z3=', z_3)
            # print('a3=', a_3)

            # 反向传播
            loss = loss_function(a_3, groud_truth)
            print("经过反向传播{}次,损失为: {}".format((count+1), loss))
        else:
            break
    
    return w_2, w_3, bias_3, bias_2, loss 

if __name__ == '__main__':
    print("=====反向传播start======")
    theta = 0.5
    input_arry = np.array([0.05, 0.10])
    # print(input_arry)    
    groud_truth = np.array([0.01, 0.99])
    # print(groud_truth.shape)
    # print(input_arry.shape)
    param = backpropagation_func(input_arry, groud_truth, theta)
    print('loss = {}\n'.format(param[4]))
    print('权重w_2 = {}, \n w_3 = {}\n'.format(param[0], param[1]))
    print('偏置bias_2 = {},\n bias_3 = {} \n'.format(param[2], param[3]))

结果:

=====反向传播start======
前向传播的损失为: 0.2983711087600027
经过反向传播1次,损失为: 0.28047144679143016
经过反向传播2次,损失为: 0.2619076230693008
经过反向传播3次,损失为: 0.24293543335380882
经过反向传播4次,损失为: 0.22387321290700182
经过反向传播5次,损失为: 0.20507531776516638
经过反向传播6次,损失为: 0.1868957476561393
......
经过反向传播66次,损失为: 0.010433675583027257
经过反向传播67次,损失为: 0.010230717010390656
经过反向传播68次,损失为: 0.010034748131211645
经过反向传播69次,损失为: 0.009845424426560941
loss = 0.009845424426560941

权重w_2 = [array([0.15686046, 0.21372093]), array([0.25617323, 0.31234645])], 
 w_3 = [array([-0.6641919 , -0.61741066]), array([0.83781677, 0.88865522])]

偏置bias_2 = [-1.18080352  1.16167516],
 bias_3 = [0.48720928 0.47346454] 
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值