深度学习|误差逆传播:梯度速解

news2024/11/14 7:18:32

文章目录

    • 引言
    • 链式法则
    • 误差逆传播
      • 加法的逆传播
      • 乘法的逆传播
      • 逆传播求梯度
    • SoftmaxWithLoss 层
      • 正向传播
      • 逆传播
      • 代码实现参考
    • 结语

引言

我们知道训练神经网络模型的核心是以损失函数为基准来调整优化网络参数,使得网络的输出尽可能接近真实标签。在神经网络中,优化网络参数需要计算每个权重参数的梯度,不同的网络结构,计算梯度的方式和复杂度往往大不相同,有没有一种算法,即可以有效囊括所有类型的网络结构的梯度计算,又足以保证梯度计算的高效性?答案就是我们今天要讲的误差逆传播算法。

在这里插入图片描述

链式法则

要理解误差逆传播算法,需要先了解微分中链式法则的概念。链式法则是微分中的基本法则,可用于求解复合函数的导数。

如果某个函数由复合函数表示,则该复合函数的导数可以用构成该复合函数的各个函数的导数的乘积表示。

以式 1 所示的复合函数为例:

z = t 2 t = x + y (1) z = t^2 \\ t = x + y \tag{1} z=t2t=x+y(1)

通过链式法则求解 ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz

∂ z ∂ x = ∂ z ∂ t ∂ t ∂ x = 2 t × 1 = 2 ( x + y ) \frac{\partial{z}}{\partial{x}} = \frac{\partial{z}}{\partial{t}} \frac{\partial{t}}{\partial{x}} = 2t \times 1 = 2(x + y) xz=tzxt=2t×1=2(x+y)

可见一个复杂函数的求导问题可以分解为组成该复杂函数的局部函数的求导问题,我们完全可以将复杂函数的导数等价的表示为其所有局部函数的导数的乘积。

在神经网络中,误差逆传播算法就是利用链式法则来计算网络中每个参数的梯度。

误差逆传播

在前文「深度学习|模型训练:手写 SimpleNet」中,我们演示了使用数值微分方式求梯度的过程,数值微分的方式求梯度简单、易于理解与实现,但它的问题是计算效率很低。在 SimpleNet 的示例中,我们使用数值微分法训练所需时间长达 27.7 小时,几乎是不可用的状态。

那么有没有更高效的替代方式呢?终于轮到神经网络的主角算法误差逆传播出场了!

参考上文求复合函数(式 1)关于 x 的导数 ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz 的求解过程,给定 x 与 y,按照函数式求解 z 的正向计算的过程,就好比神经网络的前向传播Forward Propagation),而沿着函数正向计算的链路,从最末端逆向计算每个局部函数的导数,最终相乘从而得到该复杂函数的导数,就好比神经网络的逆传播Backward Propagation)。

前向传播(Forward Propagation):将输入数据通过网络进行运算,得到网络的输出、输出与目标值之间的误差
逆传播(Backward Propagation):从输出层开始,将误差逆传播到隐藏层,直到输入层。逆传播过程可以计算每个权重的梯度,即误差相对于每个权重的偏导数。

误差逆传播error BackPropagation,简称 BP)就是基于数学推导的解析性(相对于数值微分的数值性)梯度计算方法(符号微分Symbolic Differentiation),按照数学中求导的链式法则,局部导数会按正向传播的反方向传递。

以求解 ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz 为例,我们可以用如下图 1 中红色箭头所指过程表示该求导过程:

在这里插入图片描述

图 1 所示从左到右是复合函数的正向传播过程,表示的是 t = x + y t = x + y t=x+y z = t 2 z = t^2 z=t2 的正向计算过程。

从右往左是复合函数的逆传播过程,通过逆传播求函数关于 x 的导数 ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz,只需要沿着正向计算的链路逆向计算每个局部函数的导数,例如从输出 zz 本身的导数是 ∂ z ∂ z \frac{\partial{z}}{\partial{z}} zz,从 zt 的导数是 ∂ z ∂ t \frac{\partial{z}}{\partial{t}} tz,从 tx 的导数是 ∂ t ∂ x \frac{\partial{t}}{\partial{x}} xt,最终将每个环节的导数相乘即是该复合函数的导数 ∂ z ∂ z ∂ z ∂ t ∂ t ∂ x \frac{\partial{z}}{\partial{z}} \frac{\partial{z}}{\partial{t}} \frac{\partial{t}}{\partial{x}} zztzxt(其中 ∂ z ∂ z \frac{\partial{z}}{\partial{z}} zz 可忽略)。这就是 BP 算法的基本思想。

不难发现,神经网络中的前向传播都是由一些简单的加法、乘法等常用的运算复合而成,而神经网络的逆传播就是求解网络整个“复合函数”关于网络各层中权重参数梯度

我们在了解了 BP 算法的基本思路后,不难得出这些梯度的求解方式:沿着网络的正向运算过程,反向从输出层开始,往前计算每层运算的局部梯度,然后将求解目标参数梯度的完整链路上的所有局部梯度相乘,得到的就是目标参数的梯度。

接下来我们可以找到在逆传播过程中,使用 BP 算法求解加法、乘法等常用运算的梯度的规律。应用这些规律,我们可以在神经网络的逆传播运算过程中高效地计算梯度。

加法的逆传播

z = x + y z = x + y z=x+y 为例,其梯度 ( ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz ∂ z ∂ y \frac{\partial{z}}{\partial{y}} yz) 永远为 (1, 1)。

因此加法运算在逆传播时,总是将下游梯度乘以 1,即原封不动传递给上游。

我们可以使用 AddLayer 类实现加法运算的前向传播与逆传播:

class AddLayer:
    """
    加法运算的前向传播与逆传播
    """

    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        """
        前向传播

        Args:
            x: 输入 x
            y: 输入 y

        Returns:
            out: 输出
        """

        out = x + y

        return out

    def backward(self, dout):
        """
        逆传播

        Args:
            dout: 上游梯度

        Returns:
            dx: x 的梯度
            dy: y 的梯度
        """

        dx = dout * 1
        dy = dout * 1

        return dx, dy

这里采用了标准层封装的方式来实现加法运算,将加法运算封装成了一个可以被任意结构的神经网络直接复用的小组件,其他如乘法运算激活函数损失函数等我们都将采用这样的实现方式。采用这样的封装方式,我们就可以在组装我们想要的网络时随意选择我们想要的组件(基本运算单元)。在实际的生产级机器学习框架(如 Scikit-learn、TensorFlow、PyTorch 等)中,这些底层运算封装也正是采用了这样的方式实现。

乘法的逆传播

z = x y z = xy z=xy 为例,其梯度 ( ∂ z ∂ x \frac{\partial{z}}{\partial{x}} xz ∂ z ∂ y \frac{\partial{z}}{\partial{y}} yz) = (y, x)。

因此乘法运算的逆传播时,总是将下游梯度乘以上游相乘参数的值(翻转值)。比如 x 与 y 相乘,求关于 x 的偏导数时,y 是 x 的翻转值;求关于 y 的偏导数时,x 是 y 的翻转值。

同上,我们用 MulLayer 类实现乘法运算的前向传播与逆传播:

class MulLayer:
    """
    乘法运算的前向传播与逆传播
    """

    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        """
        前向传播

        Args:
            x: 输入 x
            y: 输入 y

        Returns:
            out: 输出
        """

        self.x = x
        self.y = y
        out = x * y

        return out

    def backward(self, dout):
        """
        逆传播

        Args:
            dout: 上游梯度

        Returns:
            dx: x 的梯度
            dy: y 的梯度
        """

        dx = dout * self.y
        dy = dout * self.x

        return dx, dy

逆传播求梯度

y = x 1 x 2 + x 3 y = x_1x_2 + x_3 y=x1x2+x3 为例,求 ( x 1 , x 2 , x 3 ) (x_1, x_2, x_3) (x1,x2,x3)= (100, 2, 300) 处的梯度。

在这里插入图片描述

y = x 1 x 2 + x 3 y = x_1x_2 + x_3 y=x1x2+x3 的计算链路如图 2,我们可以直接通过上文的 MulLayer 和 AddLayer 进行前向传播求 y,以及逆传播求关于 ( x 1 , x 2 , x 3 ) (x_1, x_2, x_3) (x1,x2,x3) 的梯度。

x1, x2, x3 = 100, 2, 300
mul_layer = MulLayer()
add_layer = AddLayer()

# forward
a = mul_layer.forward(x1, x2)
y = add_layer.forward(a, x3)
print(y)                # 500

# backward
da, dx3 = add_layer.backward(1)
dx1, dx2 = mul_layer.backward(da)
print(dx1, dx2, dx3)    # (x2, x1, 1) = (2, 100, 1)

运行结果与图 2 中所示 ( x 2 , x 1 , 1 ) (x_2, x_1, 1) (x2,x1,1)(输入 x 1 , x 2 , x 3 x_1, x_2, x_3 x1,x2,x3 各自的反向红色箭头是它们各自的梯度)一致,可见逆传播求梯度的结果是符合预期的。

以上逆传播求梯度过程可以直接应用在神经网络中对数组和矩阵的运算上:

x1, x2, x3 = np.array([100, 101, 102]), np.array([2, 3, 4]), np.array([300, 301, 302])
mul_layer = MulLayer()
add_layer = AddLayer()

# forward
a = mul_layer.forward(x1, x2)
y = add_layer.forward(a, x3)
print(y)                # [500 604 710]

# backward
da, dx3 = add_layer.backward(1)
dx1, dx2 = mul_layer.backward(da)
print(dx1, dx2, dx3)    # (x2, x1, 1) = [2 3 4] [100 101 102] 1

SoftmaxWithLoss 层

我们知道神经网络模型的训练过程就是根据损失函数关于权重参数的梯度优化权重参数的过程,其中求解损失函数关于权重参数的梯度是运算的核心。而损失函数往往是神经网络正向传播中的最后一个环节(训练过程的最后一个过程是损失函数,推理过程则一般不需要计算损失),根据 BP 算法的思路,在逆传播过程中,求解损失函数的“局部梯度”就成了求解权重参数梯度的第一步。

由于在多分类任务中,神经网络模型经常使用 Softmax 函数来对最终输出做归一化处理,我们在封装损失函数时,通常会将 Softmax 函数与损失函数结合在一起,这样的结构我们称之为SoftmaxWithLoss层。

下面我们以交叉熵误差为例,通过实现一个 SoftmaxWithLoss 层来演示 BP 算法及其“局部梯度”的求解过程。

假定网络的输出层有 n 个神经元(n 个分类类别),则 SoftmaxWithLoss 层的计算过程如图 3 所示:

在这里插入图片描述

从前面的层输入的是 ( a 1 , a 2 , . . . , a n ) (a_1, a_2, ..., a_n) (a1,a2,...,an),Softmax 层输出的是 ( y 1 , y 2 , . . . , y n ) (y_1, y_2, ..., y_n) (y1,y2,...,yn),实际结果分别是 ( t 1 , t 2 , . . . , t n ) (t_1, t_2, ..., t_n) (t1,t2,...,tn),Cross Entropy Error 输出的损失是 L。

正向传播

Softmax 往往作为网络输出层的激活函数,对网络的输出做最后的归一化处理;而在模型训练时,Softmax 的输出与实际结果作为损失函数的输入,可以计算出模型训练所需的损失值。可见 SoftmaxWithLoss 层实际就是经过了 Softmax 计算和 Loss Function 计算两个过程。

其中 Softmax 计算过程如式 2:

y k = e a k ∑ i = 1 n e a i (2) y_k = \frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}} \tag{2} yk=i=1neaieak(2)

Loss Function(交叉熵误差)计算过程如式 3:

L = − ∑ k t k log ⁡ y k (3) L = -\sum_{k} t_k \log{y_k} \tag{3} L=ktklogyk(3)

合并式 2 和式 3,SoftmaxWithLoss 层的正向传播总计算式 4:

L = cross_entropy_error ( y , t ) = cross_entropy_error ( softmax ( a ) , t ) = − ∑ k t k log ⁡ e a k ∑ i = 1 n e a i (4) \begin{split} L = \text{cross\_entropy\_error}(y, t) \\ = \text{cross\_entropy\_error}(\text{softmax}(a), t) \\ = - \sum_{k} t_k \log{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} \end{split} \tag{4} L=cross_entropy_error(y,t)=cross_entropy_error(softmax(a),t)=ktklogi=1neaieak(4)

SoftmaxWithLoss 层的正向传播分布计算过程:

  1. 指数运算:计算每个输入 a k a_k ak 的自然指数 e a k e^{a_k} eak
  2. 加法运算:计算所有输入的自然指数之和 S = ∑ i = 1 n e a i S = \sum_{i=1}^{n} e^{a_i} S=i=1neai
  3. 除法运算:计算所有输入的自然指数之和的倒数 1 S = 1 ∑ i = 1 n e a i \frac{1}{S} = \frac{1}{\sum_{i=1}^{n} e^{a_i}} S1=i=1neai1
  4. 乘法运算:计算每个输入 a k a_k ak 的自然指数与所有输入的自然指数之和的倒数的乘积 y k = e a k ∑ i = 1 n e a i y_k = \frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}} yk=i=1neaieak
  5. 对数运算:计算每个输入 a k a_k ak 的自然指数与所有输入的自然指数之和的倒数的乘积的对数 log ⁡ y k = log ⁡ e a k ∑ i = 1 n e a i \log{y_k} = \log{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} logyk=logi=1neaieak
  6. 乘法运算:计算每个输入 a k a_k ak 的自然指数与所有输入的自然指数之和的倒数的乘积的对数与实际结果 t k t_k tk 的乘积 t k log ⁡ y k = t k log ⁡ e a k ∑ i = 1 n e a i t_k \log{y_k} = t_k \log{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} tklogyk=tklogi=1neaieak
  7. 加法运算:计算所有输入的自然指数与所有输入的自然指数之和的倒数的乘积的对数与实际结果的乘积之和 ∑ k t k log ⁡ y k = ∑ k t k log ⁡ e a k ∑ i = 1 n e a i \sum_{k} t_k \log{y_k} = \sum_{k} t_k \log{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} ktklogyk=ktklogi=1neaieak
  8. 乘法运算:计算所有输入的自然指数与所有输入的自然指数之和的倒数的乘积的对数与实际结果的乘积之和的相反数 L = − ∑ k t k log ⁡ y k = − ∑ k t k log ⁡ e a k ∑ i = 1 n e a i L = - \sum_{k} t_k \log{y_k} = - \sum_{k} t_k \log{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} L=ktklogyk=ktklogi=1neaieak

正向传播计算的是 SoftmaxWithLoss 根据输入 a 和真实结果 t 计算误差的过程;相对的,我们再看如何使用逆传播迅速计算 SoftmaxWithLoss 层的损失梯度(损失函数关于输入 a 的梯度)。

逆传播

逆传播计算过程是沿着正向传播过程的反方向进行的,首先误差梯度的初始值永远是 ∂ L ∂ L \frac{\partial{L}}{\partial{L}} LL = 1,逆传播的计算过程如下:

  1. 乘法求导(8)

− ∑ k t k log ⁡ y k = ∑ k t k log ⁡ y k × ( − 1 ) - \sum_{k} t_k \log{y_k} = \sum_{k} t_k \log{y_k} \times (-1) ktklogyk=ktklogyk×(1)

该步是 ∑ k t k log ⁡ y k \sum_{k} t_k \log{y_k} ktklogyk ( − 1 ) (-1) (1) 做乘法运算,逆传播计算 ∑ k t k log ⁡ y k \sum_{k} t_k \log{y_k} ktklogyk 的局部梯度取乘数 -1,此时梯度为 − 1 × 1 = − 1 -1 \times 1 = -1 1×1=1

  1. 加法求导(7)

∑ k t k log ⁡ y k = t 1 log ⁡ y 1 + t 2 log ⁡ y 2 + . . . + t n log ⁡ y n \sum_{k} t_k \log{y_k} = t_1 \log{y_1} + t_2 \log{y_2} + ... + t_n \log{y_n} ktklogyk=t1logy1+t2logy2+...+tnlogyn

该步是对各项 t k log ⁡ y k t_k \log{y_k} tklogyk 做加法运算,逆传播计算各项 t k log ⁡ y k t_k \log{y_k} tklogyk 的局部梯度取原值 -1,此时各项梯度为 -1;

  1. 乘法求导(6)

t k log ⁡ y k t_k \log{y_k} tklogyk

该步是 t k t_k tk log ⁡ y k \log{y_k} logyk 做乘法运算,逆传播计算 log ⁡ y k \log{y_k} logyk 的局部梯度取乘数 t k t_k tk,此时梯度为 t k × ( − 1 ) = − t k t_k \times (-1) = -t_k tk×(1)=tk

  1. 对数求导(5)

log ⁡ y k \log{y_k} logyk

该步是对 y k y_k yk 做对数运算,逆传播计算 y k y_k yk 的局部梯度取自变量的倒数 1 y k \frac{1}{y_k} yk1,此时梯度为 − t k × 1 y k = − t k y k - t_k \times \frac{1}{y_k} = - \frac{t_k}{y_k} tk×yk1=yktk

  1. 乘法求导(4)

y k = e a k ∑ i = 1 n e a i y_k = \frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}} yk=i=1neaieak

该步是 e a k e^{a_k} eak 1 ∑ i = 1 n e a i \frac{1}{\sum_{i=1}^{n} e^{a_i}} i=1neai1 做乘法运算,逆传播计算 1 ∑ i = 1 n e a i \frac{1}{\sum_{i=1}^{n} e^{a_i}} i=1neai1 的局部梯度取乘数 e a k e^{a_k} eak,此时梯度为:

− t k y k × e a k = − t k × e a k e a k ∑ i = 1 n e a i = − t k ∑ i = 1 n e a i - \frac{t_k}{y_k} \times e^{a_k} = - \frac{t_k \times e^{a_k}}{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} = - t_k \sum_{i=1}^{n} e^{a_i} yktk×eak=i=1neaieaktk×eak=tki=1neai

因为 1 ∑ i = 1 n e a i \frac{1}{\sum_{i=1}^{n} e^{a_i}} i=1neai1 分别与 n 项 e a i e^{a_i} eai 做乘法运算,逆传播计算局部梯度求所有分支的和 - t k ∑ i = 1 n e a i t_k \sum_{i=1}^{n} e^{a_i} tki=1neai,此时梯度为:

∑ k ( − t k ∑ i = 1 n e a i ) \sum_k{(- t_k \sum_{i=1}^{n} e^{a_i})} k(tki=1neai)

逆传播计算 e a k e^{a_k} eak 的局部梯度取乘数 1 ∑ i = 1 n e a i \frac{1}{\sum_{i=1}^{n} e^{a_i}} i=1neai1,此时梯度为:

− t k y k × 1 ∑ i = 1 n e a i = − t k e a k ∑ i = 1 n e a i 1 ∑ i = 1 n e a i = − t k e a k (temp-1) \begin{split} - \frac{t_k}{y_k} \times \frac{1}{\sum_{i=1}^{n} e^{a_i}} \\ = - \frac{t_k}{\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}}} \frac{1}{\sum_{i=1}^{n} e^{a_i}} \\ = - \frac{t_k}{e^{a_k}} \tag{temp-1} \end{split} yktk×i=1neai1=i=1neaieaktki=1neai1=eaktk(temp-1)

  1. 除法求导(3)

1 S = 1 ∑ i = 1 n e a i \frac{1}{S} = \frac{1}{\sum_{i=1}^{n} e^{a_i}} S1=i=1neai1

该步是用 ∑ i = 1 n e a i \sum_{i=1}^{n} e^{a_i} i=1neai 除 1,逆传播计算 ∑ i = 1 n e a i \sum_{i=1}^{n} e^{a_i} i=1neai 的局部梯度为: − 1 S 2 = − 1 ( ∑ i = 1 n e a i ) 2 - \frac{1}{S^{2}} = - \frac{1}{(\sum_{i=1}^{n} e^{a_i})^{2}} S21=(i=1neai)21,此时梯度为:

∑ k ( − t k ∑ i = 1 n e a i ) × ( − 1 ( ∑ i = 1 n e a i ) 2 ) = ∑ k ( t k ) 1 ( ∑ i = 1 n e a i ) 2 ∑ i = 1 n e a i = ∑ k ( t k ) 1 ∑ i = 1 n e a i \begin{split} \sum_k(- t_k \sum_{i=1}^{n} e^{a_i}) \times (- \frac{1}{(\sum_{i=1}^{n} e^{a_i})^{2}}) \\ = \sum_k(t_k) \frac{1}{(\sum_{i=1}^{n} e^{a_i})^{2}} \sum_{i=1}^{n} e^{a_i} \\ = \sum_k(t_k) \frac{1}{\sum_{i=1}^{n} e^{a_i}} \end{split} k(tki=1neai)×((i=1neai)21)=k(tk)(i=1neai)21i=1neai=k(tk)i=1neai1

此处因为 t k t_k tk 是 one-hot 编码,有且仅有一个 k 令 t k t_k tk 为 1,其他 t 全是 0,即 ∑ k ( t k ) = 1 \sum_k(t_k) = 1 k(tk)=1,因此此时梯度为:

∑ k ( t k ) 1 ∑ i = 1 n e a i = 1 ∑ i = 1 n e a i \sum_k(t_k) \frac{1}{\sum_{i=1}^{n} e^{a_i}} = \frac{1}{\sum_{i=1}^{n} e^{a_i}} k(tk)i=1neai1=i=1neai1

  1. 加法求导(2)

S = ∑ i = 1 n e a i S = \sum_{i=1}^{n} e^{a_i} S=i=1neai

该步是对各项 e a i e^{a_i} eai 做加法运算,逆传播计算各项 e a i e^{a_i} eai 的局部梯度取原值为:

1 ∑ i = 1 n e a i (temp-2) \frac{1}{\sum_{i=1}^{n} e^{a_i}} \tag{temp-2} i=1neai1(temp-2)

  1. 指数求导(1)

e a k e^{a_k} eak

该步是自然指数运算,逆传播求 e a k e^{a_k} eak 的局部梯度取自身为: e a k e^{a_k} eak

因为 e a i e^{a_i} eai 被分别使用于正向传播 2正向转播 4 的运算,逆传播求 e a i e^{a_i} eai 的梯度前,需要将正向传播 2正向转播 4处的梯度(即 temp-2 与 temp-1)求和作为 e a i e^{a_i} eai 的逆传播输入值,即:

1 ∑ i = 1 n e a i + ( − t k e a k ) = 1 ∑ i = 1 n e a i − t k e a k \frac{1}{\sum_{i=1}^{n} e^{a_i}} + (- \frac{t_k}{e^{a_k}}) = \frac{1}{\sum_{i=1}^{n} e^{a_i}} - \frac{t_k}{e^{a_k}} i=1neai1+(eaktk)=i=1neai1eaktk

因此此处的梯度为:

e a k × ( 1 ∑ i = 1 n e a i − t k e a k ) = e a k 1 ∑ i = 1 n e a i − t k = y k − t k \begin{split} e^{a_k} \times (\frac{1}{\sum_{i=1}^{n} e^{a_i}} - \frac{t_k}{e^{a_k}}) \\ = \frac{e^{a_k}}{\frac{1}{\sum_{i=1}^{n} e^{a_i}}} - t_k \\ = y_k - t_k \end{split} eak×(i=1neai1eaktk)=i=1neai1eaktk=yktk

即通过 SoftmaxWithLoss 层的逆传播过程推演计算,我们得到了损失函数 L 关于输入 a k a_k ak 的梯度为式 5:

∂ L ∂ a k = y k − t k (5) \frac{\partial{L}}{\partial{a_k}} = y_k - t_k \tag{5} akL=yktk(5)

相比数值微分法求梯度需要大量计算,我们可以直接利用公式 5 求损失函数 L 关于输入 a k a_k ak 的梯度,这将极大提高计算效率。

代码实现参考

根据上文 SoftmaxWithLoss 层的正向传播算式 4 与逆传播算式 5,我们可以轻易实现 SoftmaxWithLoss 层:

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None  # softmax的输出
        self.t = None  # 监督数据

    def forward(self, x, t):
        """
        前向传播

        Args:
            x: 输入数据
            t: 监督数据

        Returns:
            float: 损失
        """

        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)

        return self.loss

    def backward(self, dout=1):
        """
        逆传播

        Args:
            dout: 上游梯度

        Returns:
            np.ndarray: 损失关于输入 x 的梯度
        """

        batch_size = self.t.shape[0]
        if self.t.size == self.y.size:  # 监督数据是one-hot-vector的情况
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size

        return dx

测试 SoftmaxWithLoss 层的正向传播与逆传播:

softmax_with_loss = SoftmaxWithLoss()

# 示例数据
a = np.array([[1.0, 2.0, 0.5], [0.0, 1.0, 1.0]])  # 未归一化输出
t = np.array([[1, 0, 0], [0, 1, 0]])  # one-hot 编码标签

# 前向传播
loss = softmax_with_loss.forward(a, t)

# 逆传播
da = softmax_with_loss.backward()

print("Softmax Result:\n", softmax_with_loss.y)
print("Cross Entropy Loss:", loss)
print("Gradient:\n", da)
# Softmax Result:
#  [[0.2312239  0.62853172 0.14024438]
#  [0.1553624  0.4223188  0.4223188 ]]
# Cross Entropy Loss: 1.1631814594485623
# Gradient:
#  [[-0.38438805  0.31426586  0.07012219]
#  [ 0.0776812  -0.2888406   0.2111594 ]]

将以上逆传播求梯度的过程套用在神经网络损失函数关于权重参数的梯度求解上,我们就实现一个高效的神经网络学习算法,这就是 BP 算法。

交叉熵误差概念回顾

以多分类任务为例,我们知道其交叉熵误差的计算公式为:

L ( y , t ) = − ∑ i t i log ⁡ ( y i ) L(y, t) = -\sum_{i} t_i \log(y_i) L(y,t)=itilog(yi)

其中:

  • y y y 是模型的输出,通常是经过 softmax 函数处理得到的预测概率分布。
  • t t t 是真实标签,通常是 one-hot 编码表示的实际结果。

使用 Python 代码实现的交叉熵误差函数:

import numpy as np

def cross_entropy_error(y, t):
    """
    交叉熵误差函数

    Args:
        y: 神经网络的输出
        t: 监督数据

    Returns:
        float: 交叉熵误差
    """

    # 监督数据是 one-hot-vector 的情况下,转换为正确解标签的索引
    if t.size == y.size:
        t = t.argmax(axis=1)

    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

Softmax 函数实现回顾

def softmax(x):
   """归一化指数函数"""
   if x.ndim == 2:
       x = x.T
       x = x - np.max(x, axis=0)
       y = np.exp(x) / np.sum(np.exp(x), axis=0)
       return y.T

   x = x - np.max(x)  # 溢出对策
   return np.exp(x) / np.sum(np.exp(x))

结语

我们通过 BP 算法的正向传播与逆传播过程,演示了如何高效计算神经网络的梯度。通过 BP 算法,我们可以直接计算损失函数关于权重参数的梯度,而不需要通过数值微分法进行梯度计算,这将大大提高神经网络的训练效率。

BP 算法是迄今最成功的神经网络学习算法,通常神经网络(不限于前馈神经网络)都使用 BP 算法进行训练。“BP 网络”特指使用 BP 算法训练的多层前馈神经网络。

BP 算法实质是 LMS(Least Mean Square)算法的推广。LMS 试图使网络的输出均方误差最小化,用于神经元激活函数可微的感知机学习,LMS 推广到由非线性可微神经元组成的多层前馈网络,就是 BP 算法。


PS:感谢每一位志同道合者的阅读,欢迎关注、点赞、评论!


  • 上一篇:深度学习|损失函数:网络参数优化基准
  • 专栏:「数智通识」 | 「机器学习」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2161846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于区块链的安全和隐私

背景 区块链技术在近年来发展迅速,被认为是安全计算的突破,但其安全和隐私问题在不同应用中的部署仍处于争论焦点。 目的 对区块链的安全和隐私进行全面综述,帮助读者深入了解区块链的相关概念、属性、技术和系统。 结构 首先介绍区块链…

Python酷库之旅-第三方库Pandas(124)

目录 一、用法精讲 551、pandas.DataFrame.notna方法 551-1、语法 551-2、参数 551-3、功能 551-4、返回值 551-5、说明 551-6、用法 551-6-1、数据准备 551-6-2、代码示例 551-6-3、结果输出 552、pandas.DataFrame.notnull方法 552-1、语法 552-2、参数 552-3…

BitSet-解决数据压缩问题

一、问题引入 假设QQ音乐服务器上有9000万首音乐,用户按照歌名来搜索歌曲,如何使得满足这一需求所需的数据占用的内存空间最小以及用户搜索歌曲速度更快 二、分析问题 1、为了满足使得数据占用的内存更小,可以采用映射的思路,按…

项目实战bug修复

实操bug修复记录 左侧侧边栏切换,再次切换侧边栏,右侧未从顶部初始位置展示。地图定位展示,可跳转到设置的对应位置。一个页面多个el-dialog弹出框导致渲染层级出现问题。锚点滚动定位错位问题。动态类名绑定。el-tree树形通过 draggable 属性…

Linux 进程与进程状态

目录 1.进程。 1.进程的概念 2.并行和并发 3.并行和并发的区别: 4.PCB(程序控制块) 5.进程组与会话。 6.进程状态。 1.进程。 1.进程的概念 进程是操作系统进行资源分配和调度的一个独立单位。每个进程都运行在操作系统的控制之下&…

游戏化在电子课程中的作用:提高参与度和学习成果

游戏化,即游戏设计元素在非游戏环境中的应用,已成为电子学习领域的强大工具。通过将积分、徽章、排行榜和挑战等游戏机制整合到教育内容中,电子课程可以变得更具吸引力、激励性和有效性。以下是游戏化如何在转变电子学习中发挥重要作用&#…

git命令将已经commit的代码push到其他分支

文章目录 一:对于多分支的代码库,将提交记录从一个分支转移到另一个分支是常见需求方法1:撤销commit操作方法2:实用命令git cherry-pick 来移动commit 二、不小心revert导致代码消失的问题 一:对于多分支的代码库&…

U8集成网页开发的数据查询(二)

前言 根据上一篇的开发,最近又做了一些单据查询的开发。 效果展示图片 结语 目前网页查询已经完善功能: 1.与U8的账号密码保持一致,定时从U8同步账号密码。 2.角色管理,权限分配。 3.U8基础档案数据查询(示例&#…

828华为云征文 | 解锁企业级邮件服务,在华为云Flexus x实例上部署Mailcow开源方案

前言 华为云Flexus X实例携手Mailcow开源邮件方案,为企业打造了一个既高效又安全的邮件服务解决方案。Flexus X实例的柔性算力与高性能,是这一方案的坚实基石。它提供CPU内存的灵活定义,以经济型价格实现旗舰级性能,确保邮件服务的…

实例讲解电动汽车故障分级处理策略及Simulink建模方法

电动汽车的故障有很多种,每种故障发生时产生危害性是不同的,因此对于不同故障应采取不同的处理方式。目前一般有两种故障处理方式,一种是针对每一种故障对其故障危害性进行判断,然后针对不同故障设定不同的故障处理机制&#xff1…

day-59 四数之和

思路 双指针&#xff1a;类似16. 最接近的三数之和&#xff0c;将数组排序后&#xff0c;只需要枚举第一个数&#xff0c;则会变为与第16题相似的解题思路 解题过程 枚举选取的第一个数&#xff0c;0<i<len-3,然后就是第16题的解题思路 Code class Solution {public L…

【Linux实践】实验三:LINUX系统的文件操作命令

【Linux实践】实验三&#xff1a;LINUX系统的文件操作命令 实验目的实验内容实验步骤及结果1. 切换和查看目录2. 显示目录下的文件3. 创建和删除目录① mkdir② rm③ rmdir 4. 输出和重定向① 输出② 重定向 > 和 >> 5. 查看文件内容① cat② head 6. 权限7. 复制8. 排…

Kali nmap扫描

物理机 ipconfig 扫描物理机 nmap 192.168.0.198 扫描物理机所有开放的端口&#xff08;TCP半开扫描 nmap -sS 192.168.0.198 扫描物理机所有开放的端口&#xff08;TCP全开扫描 nmap -sT 192.168.0.198 扫描物理机主机系统 nmap -O 192.168.0.198 扫描物理机所在网段所有…

C++ STL容器(三) —— 迭代器底层剖析

本篇聚焦于STL中的迭代器&#xff0c;同样基于MSVC源码。 文章目录 迭代器模式应用场景实现方式优缺点 UML类图代码解析list 迭代器const 迭代器非 const 迭代器 vector 迭代器const 迭代器非const迭代器 反向迭代器 迭代器失效参考资料 迭代器模式 首先迭代器模式是设计模式中…

YOLOv8——测量高速公路上汽车的速度

引言 在人工神经网络和计算机视觉领域&#xff0c;目标识别和跟踪是非常重要的技术&#xff0c;它们可以应用于无数的项目中&#xff0c;其中许多可能不是很明显&#xff0c;比如使用这些算法来测量距离或对象的速度。 测量汽车速度基本步骤如下&#xff1a; 视频采集&#x…

江协科技STM32学习- P18 实验-PWM输入捕获测频率PWMI输入捕获模式测频率和占空比

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

分布式光伏的发电监控

国拥有丰富的清洁可再生能源资源储量&#xff0c;积极开发利用可再生能源&#xff0c;为解决当前化石能源短缺与环境污染严重的燃眉之急提供了有效途径[1]。但是可再生能源的利用和开发&#xff0c;可再生能源技术的发展和推广以及可再生能源资源对环境保护的正向影响&#xff…

Qt窗口——QMenuBar

文章目录 QMenuBar示例演示给菜单栏设置快捷键给菜单项设置快捷键添加子菜单添加分割线添加图标 QMenuBar Qt中采用QMenuBar来创建菜单栏&#xff0c;一个主窗口&#xff0c;只允许有一个菜单栏&#xff0c;位于主窗口的顶部、主窗口标题栏下面&#xff1b;一个菜单栏里面有多…

计算机毕业设计之:基于微信小程序的电费缴费系统(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

鸿蒙OpenHarmony【小型系统基础内核(进程管理调度器)】子系统开发

调度器 基本概念 OpenHarmony LiteOS-A内核采用了高优先级优先 同优先级时间片轮转的抢占式调度机制&#xff0c;系统从启动开始基于real time的时间轴向前运行&#xff0c;使得该调度算法具有很好的实时性。 OpenHarmony 的调度算法将 tickless 机制天然嵌入到调度算法中&…