前言
在深度学习的神经网络算法中涉及到了很多的数学公式的推导以及数学原理的运用和实现,我们在学习和了解该算法的时候,大量的数学推导会轻易地将我们绕晕,造成算法逻辑上的混乱以及整体理解的偏差,尤其是反向传播这一过程。为了让大家更好地理解深度学习神经网络算法而不是苦苦地专注于各项数学公式的推导,在本文中将大量省去数学公式推导的过程转而将注意力放在反向传播算法结构和过程的整体把握 , 并且会给出生动简单的例子帮助大家更好地理解过程,让大家尝试着理解后分析而不是一上来就酷酷一顿分析后发现根本无法理解。
一、定义与简介
在深度学习中,神经网络反向传播算法(Backpropagation)是一种用于训练多层前馈神经网络的监督学习技术。它基于梯度下降优化方法,通过计算损失函数相对于网络参数的梯度来更新网络权重,目的是最小化损失函数,从而提高模型的预测准确性。说人话 : 反向传播在做的事情,其实就是在神经网络上往回传导误差的变化率,而这个过程是将输出层和预测值之间的误差量化后以损失函数的形式表示并通过链式法则往回对每一层神经网络求导实现的,每一层每一个神经元导出来的结果就是误差变化率(又称梯度),然后再用这个梯度来更新网络中的权重和偏置项。---根本目的 : 更新参数,优化模型
二、神经网络算法反向传播步骤
反向传播基本流程原理图
1. 前向传播(Forward Pass) :
输入数据被送入神经网络。
数据经过每一层的神经元,每层的输出成为下一层的输入。
每层的神经元通常会应用一个激活函数,如R e L U 、 s i g m o i d 或 t a n h ReLU、sigmoid或tanh R e LU 、 s i g m o i d 或 t anh 。
这个过程一直持续到数据通过最后一层(输出层)。
2. 计算损失(Loss Calculation) :
网络的输出与真实标签之间的差异被计算出来,这个差异通过一个损失函数(如均方误差、交叉熵损失)来量化。(因为损失函数为预测误差提供了一个数值上的量化度量,这允许我们明确地看到模型的预测与实际情况之间的差距有多大以及后续对模型调优。)
损失函数的值表示了模型的预测与实际结果之间的误差。(我们之所以要将这个最终误差通过损失函数表示出来是因为我们需要根据这个误差来更新拟合我们每一层神经网络的参数,这个误差归根到底只是个测量指标,因为当我们的神经网络模型越精确越稳定的时候,相应的损失函数计算出来的误差值也就越小)
没看懂 ? 没关系,直接上例子!
让我们通过一个简单的示例来讲解损失计算的过程。假设我们正在处理一个二分类问题,我们的目标是根据特征来预测一个实例是否属于类别1(是/否)。我们使用一个简单的神经网络,它只有一个隐藏层,并且我们使用交叉熵损失函数来计算损失。
步骤1: 准备数据
假设我们有以下训练样本:
特征:x 1 = [ 1 , 2 ] x_1 = [1, 2] x 1 = [ 1 , 2 ] (一个包含两个特征的向量)
真实标签:y = 1 y = 1 y = 1 (表示这个实例属于类别1)
步骤2: 神经网络结构
我们的神经网络结构如下:
输入层:2个神经元(对应于特征x 1 x_1 x 1 )
隐藏层:3个神经元(使用ReLU激活函数)
输出层:1个神经元(使用sigmoid激活函数)
步骤3: 参数初始化
为了简单起见,我们初始化网络参数如下:
权重:W 1 = [ 0.1 , 0.2 ; 0.3 , 0.4 ; 0.5 , 0.6 ] W_1 = [0.1, 0.2; 0.3, 0.4; 0.5, 0.6] W 1 = [ 0.1 , 0.2 ; 0.3 , 0.4 ; 0.5 , 0.6 ] (输入层到隐藏层)
偏置:b 1 = [ 0.1 , 0.2 , 0.3 ] b_1 = [0.1, 0.2, 0.3] b 1 = [ 0.1 , 0.2 , 0.3 ]
权重:W 2 = [ 0.7 , 0.8 , 0.9 ] W_2 = [0.7, 0.8, 0.9] W 2 = [ 0.7 , 0.8 , 0.9 ] (隐藏层到输出层)
偏置:b 2 = 0.5 b_2 = 0.5 b 2 = 0.5
步骤4: 前向传播
计算隐藏层的输入和输出:
输入:z 1 = x 1 ⋅ W 1 + b 1 = [ 1 , 2 ] ⋅ [ 0.1 , 0.2 ; 0.3 , 0.4 ; 0.5 , 0.6 ] + [ 0.1 , 0.2 , 0.3 ] z_1 = x_1 \cdot W_1 + b_1 = [1, 2] \cdot [0.1, 0.2; 0.3, 0.4; 0.5, 0.6] + [0.1, 0.2, 0.3] z 1 = x 1 ⋅ W 1 + b 1 = [ 1 , 2 ] ⋅ [ 0.1 , 0.2 ; 0.3 , 0.4 ; 0.5 , 0.6 ] + [ 0.1 , 0.2 , 0.3 ]
输出:a 1 = ReLU ( z 1 ) a_1 = \text{ReLU}(z_1) a 1 = ReLU ( z 1 ) (应用ReLU激活函数)
计算输出层的输入和输出(预测值):
输入:z 2 = a 1 ⋅ W 2 + b 2 z_2 = a_1 \cdot W_2 + b_2 z 2 = a 1 ⋅ W 2 + b 2
输出(预测值):y ^ = σ ( z 2 ) \hat{y} = \sigma(z_2) y ^ = σ ( z 2 ) (应用sigmoid激活函数)
步骤5: 计算损失
我们使用二元交叉熵损失函数,定义为:
L ( y , y ^ ) = − [ y log ( y ^ ) + ( 1 − y ) log ( 1 − y ^ ) ] L(y, \hat{y}) = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})] L ( y , y ^ ) = − [ y log ( y ^ ) + ( 1 − y ) log ( 1 − y ^ )]
其中,y y y 是真实标签,y ^ \hat{y} y ^ 是预测值。
将真实标签和预测值代入损失函数:
L ( 1 , y ^ ) = − [ 1 log ( y ^ ) + ( 1 − 1 ) log ( 1 − y ^ ) ] L(1, \hat{y}) = -[1 \log(\hat{y}) + (1 - 1) \log(1 - \hat{y})] L ( 1 , y ^ ) = − [ 1 log ( y ^ ) + ( 1 − 1 ) log ( 1 − y ^ )]
L ( 1 , y ^ ) = − log ( y ^ ) L(1, \hat{y}) = -\log(\hat{y}) L ( 1 , y ^ ) = − log ( y ^ )
假设sigmoid函数输出的预测值y ^ \hat{y} y ^ 为0.7,那么损失为:
L ( 1 , 0.7 ) = − log ( 0.7 ) L(1, 0.7) = -\log(0.7) L ( 1 , 0.7 ) = − log ( 0.7 )
步骤6: 损失函数值
计算得到的损失值可以告诉我们模型当前的表现。在训练过程中,我们的目标是通过调整权重和偏置来最小化这个损失值。
这个示例展示了一个非常简单的损失计算过程。在实际应用中,损失计算可能会更复杂,涉及更多的数据样本、更复杂的网络结构和不同类型的损失函数。此外,损失计算通常在每个批次(batch)或整个数据集上进行,以评估模型的整体性能。不过,当你了解了这个简单的损失函数计算过程,我相信对于整个的神经网络你也会得心应手,无非就是计算过程的叠加和维度的相应变化罢了。
3. 反向传播(Backward Pass) :
损失函数关于网络参数的梯度需要被计算出来。这通常通过链式法则(链式求导)来完成。
从输出层开始,计算损失函数关于输出层权重的梯度,然后是隐藏层,一直到输入层。
在每一层,梯度是通过对激活函数和损失函数进行求导来计算的。
过程详解:
反向传播的核心 : 计算损失函数相对于每个权重的梯度 。这些梯度指示了如何调整权重以减少损失。梯度计算通常使用链式法则,从输出层向输入层反向进行。
对于第 L L L 层的权重 W ( L ) W^{(L)} W ( L ) ,梯度计算公式为:
∂ L ∂ W ( L ) = ∂ L ∂ a ( L ) ⋅ ∂ a ( L ) ∂ z ( L ) ⋅ ∂ z ( L ) ∂ W ( L ) \frac{\partial L}{\partial W^{(L)}} = \frac{\partial L}{\partial a^{(L)}} \cdot \frac{\partial a^{(L)}}{\partial z^{(L)}} \cdot \frac{\partial z^{(L)}}{\partial W^{(L)}} ∂ W ( L ) ∂ L = ∂ a ( L ) ∂ L ⋅ ∂ z ( L ) ∂ a ( L ) ⋅ ∂ W ( L ) ∂ z ( L )
其中,z ( L ) z^{(L)} z ( L ) 是第 L L L 层的加权输入,∂ L ∂ a ( L ) \frac{\partial L}{\partial a^{(L)}} ∂ a ( L ) ∂ L 是损失函数关于第 L L L 层输出的梯度,∂ a ( L ) ∂ z ( L ) \frac{\partial a^{(L)}}{\partial z^{(L)}} ∂ z ( L ) ∂ a ( L ) 是激活函数关于其输入的导数。
我知道大家这个过程会非常疑惑,包括我本人在早期学习这个过程的时候看了大量的文章也感觉到还是有点云里雾里,但其实我们都将这个过程想的过于复杂了点,损失函数往回求导,求出来的导也就是损失函数相对于每个参数的导数。就比如在 y = k x + b y=kx+b y = k x + b 图像中,这个函数的导数也就是斜率 K K K ,斜率可以很好地反映该图像的凹凸和增减性,我们需要求出这个斜率,因为我们要找损失函数的最小值,也就是图像的最低点,那么对于凹函数的图像而言,越靠近最低值的地方它的斜率也就越小,所以我们利用这个特性来很好地把握每次更新参数值的力度大小(也就是每次调整参数值的大小都会根据目前靠近最低点的距离来测算),不止于一下子下降过头或者下降太少,这也是梯度下降的本质。而反向传播说白了就是个计算公式用来计算每一个神经元参数的斜率。
老样子,听不懂 ? OK,上例子。
实例讲解反向传播:
让我们通过一个简单的神经网络示例来讲解反向传播的过程。假设我们有一个简单的两层神经网络,用于执行二分类任务。网络结构如下:
输入层:包含2个输入单元(特征)。
隐藏层:包含3个隐藏单元,使用ReLU激活函数。
输出层:包含1个输出单元,使用sigmoid激活函数进行二分类。
我们使用均方误差(MSE)作为损失函数,损失函数定义为:L = 1 2 ( y pred − y true ) 2 L = \frac{1}{2}(y_{\text{pred}} - y_{\text{true}})^2 L = 2 1 ( y pred − y true ) 2 ,其中 y pred y_{\text{pred}} y pred 是预测值,y true y_{\text{true}} y true 是真实值。
步骤1: 参数初始化
假设我们有以下初始化参数:
权重矩阵 W 1 W_1 W 1 (输入层到隐藏层): [ 0.15 0.20 0.25 0.30 0.35 0.40 ] \begin{bmatrix} 0.15 & 0.20 \\ 0.25 & 0.30 \\ 0.35 & 0.40 \end{bmatrix} 0.15 0.25 0.35 0.20 0.30 0.40
偏置向量 b 1 b_1 b 1 (隐藏层): [ 0.35 , 0.35 , 0.35 ] [0.35, 0.35, 0.35] [ 0.35 , 0.35 , 0.35 ]
权重矩阵 W 2 W_2 W 2 (隐藏层到输出层): [ 0.4 , 0.4 , 0.4 ] [0.4, 0.4, 0.4] [ 0.4 , 0.4 , 0.4 ]
偏置 b 2 b_2 b 2 (输出层): 0.3 0.3 0.3
步骤2: 前向传播
假设输入特征 x x x 为 [ 1 , 2 ] [1, 2] [ 1 , 2 ] ,真实标签 y y y 为 1。
计算隐藏层的输出:
z 1 = x W 1 + b 1 = [ 1 , 2 ] [ 0.15 0.20 0.25 0.30 0.35 0.40 ] + [ 0.35 , 0.35 , 0.35 ] = [ 1.55 , 1.95 , 2.35 ] z_1 = xW_1 + b_1 = [1, 2] \begin{bmatrix} 0.15 & 0.20 \\ 0.25 & 0.30 \\ 0.35 & 0.40 \end{bmatrix} + [0.35, 0.35, 0.35] = [1.55, 1.95, 2.35] z 1 = x W 1 + b 1 = [ 1 , 2 ] 0.15 0.25 0.35 0.20 0.30 0.40 + [ 0.35 , 0.35 , 0.35 ] = [ 1.55 , 1.95 , 2.35 ]
a 1 = ReLU ( z 1 ) = [ 1.55 , 1.95 , 2.35 ] a_1 = \text{ReLU}(z_1) = [1.55, 1.95, 2.35] a 1 = ReLU ( z 1 ) = [ 1.55 , 1.95 , 2.35 ] (因为ReLU函数将所有负值变为0)
计算输出层的输出(预测值):
z 2 = a 1 W 2 + b 2 = [ 1.55 , 1.95 , 2.35 ] [ 0.4 , 0.4 , 0.4 ] + 0.3 = 3.5 z_2 = a_1W_2 + b_2 = [1.55, 1.95, 2.35] [0.4, 0.4, 0.4] + 0.3 = 3.5 z 2 = a 1 W 2 + b 2 = [ 1.55 , 1.95 , 2.35 ] [ 0.4 , 0.4 , 0.4 ] + 0.3 = 3.5
y pred = σ ( z 2 ) = 1 1 + e − 3.5 ≈ 0.9365 y_{\text{pred}} = \sigma(z_2) = \frac{1}{1 + e^{-3.5}} \approx 0.9365 y pred = σ ( z 2 ) = 1 + e − 3.5 1 ≈ 0.9365
步骤3: 计算损失
L = 1 2 ( y pred − y true ) 2 = 1 2 ( 0.9365 − 1 ) 2 ≈ 0.0307 L = \frac{1}{2}(y_{\text{pred}} - y_{\text{true}})^2 = \frac{1}{2}(0.9365 - 1)^2 \approx 0.0307 L = 2 1 ( y pred − y true ) 2 = 2 1 ( 0.9365 − 1 ) 2 ≈ 0.0307
步骤4: 反向传播
计算输出层的梯度 :
∂ L ∂ z 2 = y pred − y true = 0.9365 − 1 = − 0.0635 \frac{\partial L}{\partial z_2} = y_{\text{pred}} - y_{\text{true}} = 0.9365 - 1 = -0.0635 ∂ z 2 ∂ L = y pred − y true = 0.9365 − 1 = − 0.0635
∂ L ∂ W 2 = ∂ L ∂ z 2 ⋅ a 1 = − 0.0635 ⋅ [ 1.55 , 1.95 , 2.35 ] = [ − 0.0988 , − 0.1233 , − 0.1487 ] \frac{\partial L}{\partial W_2} = \frac{\partial L}{\partial z_2} \cdot a_1 = -0.0635 \cdot [1.55, 1.95, 2.35] = [-0.0988, -0.1233, -0.1487] ∂ W 2 ∂ L = ∂ z 2 ∂ L ⋅ a 1 = − 0.0635 ⋅ [ 1.55 , 1.95 , 2.35 ] = [ − 0.0988 , − 0.1233 , − 0.1487 ]
∂ L ∂ b 2 = ∂ L ∂ z 2 = − 0.0635 \frac{\partial L}{\partial b_2} = \frac{\partial L}{\partial z_2} = -0.0635 ∂ b 2 ∂ L = ∂ z 2 ∂ L = − 0.0635
计算隐藏层的梯度 :
∂ L ∂ z 1 = ( ∂ L ∂ z 2 ⋅ W 2 ) ⋅ ReLU ′ ( z 1 ) \frac{\partial L}{\partial z_1} = (\frac{\partial L}{\partial z_2} \cdot W_2) \cdot \text{ReLU}'(z_1) ∂ z 1 ∂ L = ( ∂ z 2 ∂ L ⋅ W 2 ) ⋅ ReLU ′ ( z 1 )
ReLU ′ ( z 1 ) = [ 1 , 1 , 1 ] \text{ReLU}'(z_1) = [1, 1, 1] ReLU ′ ( z 1 ) = [ 1 , 1 , 1 ] (因为所有z1的值都是正的,所以ReLU的导数为1)
∂ L ∂ z 1 = ( − 0.0635 ⋅ [ 0.4 , 0.4 , 0.4 ] ) ⋅ [ 1 , 1 , 1 ] = [ − 0.0254 , − 0.0254 , − 0.0254 ] \frac{\partial L}{\partial z_1} = (-0.0635 \cdot [0.4, 0.4, 0.4]) \cdot [1, 1, 1] = [-0.0254, -0.0254, -0.0254] ∂ z 1 ∂ L = ( − 0.0635 ⋅ [ 0.4 , 0.4 , 0.4 ]) ⋅ [ 1 , 1 , 1 ] = [ − 0.0254 , − 0.0254 , − 0.0254 ]
∂ L ∂ W 1 = ∂ L ∂ z 1 ⋅ x T = [ − 0.0254 , − 0.0254 , − 0.0254 ] ⋅ [ 1 , 2 ] = [ − 0.0254 , − 0.0508 , − 0.0762 ] \frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial z_1} \cdot x^T = [-0.0254, -0.0254, -0.0254] \cdot [1, 2] = [-0.0254, -0.0508, -0.0762] ∂ W 1 ∂ L = ∂ z 1 ∂ L ⋅ x T = [ − 0.0254 , − 0.0254 , − 0.0254 ] ⋅ [ 1 , 2 ] = [ − 0.0254 , − 0.0508 , − 0.0762 ]
∂ L ∂ b 1 = ∂ L ∂ z 1 = [ − 0.0254 , − 0.0254 , − 0.0254 ] \frac{\partial L}{\partial b_1} = \frac{\partial L}{\partial z_1} = [-0.0254, -0.0254, -0.0254] ∂ b 1 ∂ L = ∂ z 1 ∂ L = [ − 0.0254 , − 0.0254 , − 0.0254 ]
步骤5: 更新参数
使用梯度下降法更新参数:
W 2 = W 2 − η ∂ L ∂ W 2 W_2 = W_2 - \eta \frac{\partial L}{\partial W_2} W 2 = W 2 − η ∂ W 2 ∂ L
b 2 = b 2 − η ∂ L ∂ b 2 b_2 = b_2 - \eta \frac{\partial L}{\partial b_2} b 2 = b 2 − η ∂ b 2 ∂ L
W 1 = W 1 − η ∂ L ∂ W 1 W_1 = W_1 - \eta \frac{\partial L}{\partial W_1} W 1 = W 1 − η ∂ W 1 ∂ L
b 1 = b 1 − η ∂ L ∂ b 1 b_1 = b_1 - \eta \frac{\partial L}{\partial b_1} b 1 = b 1 − η ∂ b 1 ∂ L
其中 η \eta η 是学习率,例如 η = 0.01 \eta = 0.01 η = 0.01 。
这个过程就是一次反向传播的迭代。在实际应用中,我们会对整个训练集进行多次迭代,每次迭代都包括前向传播计算预测值和损失,然后反向传播计算梯度并更新参数。通过这种方式,模型逐渐学习到如何最小化损失函数,从而提高预测的准确性。
4. 权重更新(Weight Update) :
一旦计算出梯度,就可以使用梯度下降或其变体(如随机梯度下降、Adam优化器等)来更新网络的权重。
更新规则通常是:权重 = 权重 − 学习率 ∗ 梯度 权重 = 权重 - 学习率 * 梯度 权重 = 权重 − 学习率 ∗ 梯度 。也就是说,一旦计算出梯度,就可以使用梯度下降法来更新权重:
W ( L ) = W ( L ) − η ∂ L ∂ W ( L ) W^{(L)} = W^{(L)} - \eta \frac{\partial L}{\partial W^{(L)}} W ( L ) = W ( L ) − η ∂ W ( L ) ∂ L
其中,η \eta η 是学习率,控制了权重更新的步长。
学习率是一个超参数,他决定了权重更新的幅度。
5. 迭代过程(Iteration) :
重复上述过程,每次使用新的训练数据批次进行前向传播和反向传播。
这个过程会持续多个迭代周期(epochs),直到模型的性能不再显著提高,或者达到预定的迭代次数。
6. 参数初始化和正则化 :
在训练开始之前,网络的权重通常被初始化为小的随机值。
为了防止过拟合,可能会在训练过程中使用正则化技术,如dropout、L1/L2正则化等。此外,还可以使用各种优化算法(如Adam、RMSprop等)来加速训练过程并提高模型性能。
反向传播算法的关键在于有效地计算梯度。在实际应用中,这通常是通过自动微分工具(如TensorFlow或PyTorch)来完成的,这些工具可以自动地为程序员计算梯度,而不需要手动推导和编程。
三、加深理解
1.梯度与参数之间的关系
在神经网络中,通过反向传播算法求出来的“导数”或“梯度”并不是参数本身,而是损失函数相对于每个参数的导数。这些梯度表示了损失函数在参数空间中的斜率,它们告诉我们损失函数在每个参数上的微小变化如何影响整体的损失值。
梯度与参数的关系
梯度下降更新参数
在计算出梯度后,我们一般会使用以下梯度下降公式来更新参数:θ new = θ old − η ⋅ ∇ θ J ( θ ) \theta_{\text{new}} = \theta_{\text{old}} - \eta \cdot \nabla_\theta J(\theta) θ new = θ old − η ⋅ ∇ θ J ( θ )
其中:
θ \theta θ 表示网络的参数(权重或偏置)。
∇ θ J ( θ ) \nabla_\theta J(\theta) ∇ θ J ( θ ) 是损失函数 J J J 相对于参数 θ \theta θ 的梯度。
η \eta η 是学习率,它是一个超参数,用于控制更新步长的大小。
示例
假设一个简单的线性模型 y = w x + b y = wx + b y = w x + b ,其中 w w w 是权重,b b b 是偏置,损失函数是均方误差。对于一个训练样本,损失函数可以表示为:
J ( w , b ) = 1 2 ( w x + b − y true ) 2 J(w, b) = \frac{1}{2} (wx + b - y_{\text{true}})^2 J ( w , b ) = 2 1 ( w x + b − y true ) 2
通过求导,我们可以得到:
∂ J ∂ w = ( w x + b − y true ) ⋅ x \frac{\partial J}{\partial w} = (wx + b - y_{\text{true}}) \cdot x ∂ w ∂ J = ( w x + b − y true ) ⋅ x
∂ J ∂ b = ( w x + b − y true ) \frac{\partial J}{\partial b} = (wx + b - y_{\text{true}}) ∂ b ∂ J = ( w x + b − y true )
这些导数(梯度)告诉我们损失函数在当前参数下的局部变化率。然后,我们可以使用这些梯度来更新 w w w 和 b b b ,以减少损失函数的值。
总结来说,梯度是损失函数相对于参数的导数,它们不是参数本身,但为我们提供了如何调整参数以优化模型的指导。
四、总结
深度学习中的神经网络反向传播算法是一种用于训练多层前馈神经网络的监督学习技术,是深度学习成功的基石之一,它使得神经网络能够通过大量数据学习复杂的模式和特征。深度学习已经被应用在了很多地方,在不同领域内,散发着数学和计算机科学的光芒。
五、Reference
反向传播(back propagation)
以上就是笔者关于深度学习反向传播算法的讲解和总结,欢迎大家点赞,收藏,交流和关注,O(∩_∩)O谢谢!