从零开始学习神经网络反向传播

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

从零开始构建反向传播

正向传播中,我们将输入层与隐藏层连接到输出层。 在反向传播中,我们使用相反的过程。 每次将神经网络中的每个权重进行少量更改。权重值的变化将对最终损失值(增加或减少的损失)产生影响,我们需要朝着减少损失的方向更新权重。通过每次轻微更新权重并测量权重更新导致的误差变化,我们可以完成以下操作:

  • 确定权重更新的方向
  • 确定权重更新的幅度

在实施反向传播之前,我们首先了解神经网络的另一重要概念:学习率。学习率有助于我们建立更稳定的算法。例如,在确定权重更新的大小时,我们不会一次性就对其进行大幅度更改,而是采取更谨慎的方法来缓慢地更新权重。这使模型获得更高的稳定性;在之后的学习中,我们还将研究学习率如何帮助提高稳定性。 更新权重以减少误差的整个过程称为梯度下降技术,随机梯度下降是将误差最小化的手段。更直观地讲,梯度代表差异(即实际值和预测值之间的差异),而下降则表示差异减小;随机代表选择随机样本进行训练,并据此做出决策。除了随机梯度下降外,还有许多其他优化技术可以用于减少损失值。之后的学习中,将讨论不同的优化技术。

反向传播的工作原理如下:

  • 利用前向传播过程计算损失值。
  • 略微改变所有的权重。
  • 计算权重变化对损失函数的影响。
  • 根据权重更新是增加还是减少了损失值,在损失减少的方向上更新权重值。

对数据集中的所有数据执行 1 次训练过程(前向传播+反向传播),称为 1 个 epoch。 为了进一步巩固我们对神经网络中反向传播的理解,让我们拟合一个已知的简单函数,查看如何得出权重。假设,待拟合函数为 y = 3 x y = 3x ,我们期望得出权重值和偏置值(分别为 3 和 0)。

x 1 3 4 8 10
y 3 9 12 24 30

以上数据集可以表示为线性回归 y = a x + b y = ax + b ,我们将尝试计算 a a b b 的值(虽然我们已知它们分别是 2 和 0,但我们的目的是研究如何使用梯度下降获得这些值),将 a a b b 参数随机初始化为 2.269 2.269 1.01 1.01 的值。接下来,我们将从零构建反向传播算法,以便清楚地了解如何在神经网络中计算权重。简单起见下,将构建一个没有隐藏层的简单神经网络。

  1. 初始化数据集,如下所示:
x = np.array([[1], [3], [4], [8], [10]])
y = np.array([[3], [9], [12], [24], [30]])
复制代码
  1. 随机初始化权重和偏差值(在尝试确定 y = a x + b y = ax + b 方程中 a a b b 的最优值时,只需要一个权重和一个偏置值):
w = np.array([[[2.269]], [[1.01]]])
复制代码
  1. 定义神经网络并计算平方误差损失值:
import numpy as np
def feed_forward(inputs, outputs, weights):
    out = np.dot(inputs, weights[0]) + weights[1]
    squared_error = np.square(out - outputs)
    return squared_error
复制代码

在上述代码中,对输入与随机初始化的权重值进行了矩阵乘法,然后将其与随机初始化的偏置值相加。得到输出值后,便可以计算出实际值与预测值之差的平方误差值。

  1. 少量增加每个权重和偏置值,并针对每个权重和偏差更新一次计算一个平方误差损失值。

如果平方误差损失值随权重的增加而减小,则权重值应增加,权重值应增加的大小与权重变化减少的损失值的大小成正比。反之亦然。另外,通过学习率确保增加的权重值小于因权重变化而导致的损失值变化,这样可以确保损失值更平稳地减小。 接下来,创建一个名为 update_weights 的函数,该函数执行反向传播过程以更新在权重,该函数运行 epochs 次:

from copy import deepcopy
def update_weights(inputs, outputs, weights, epochs):  
    for epoch in range(epochs):
复制代码
  1. 将输入通过神经网络传递,以计算权重未更新时的损失:
        org_loss = feed_forward(inputs, outputs, weights)
复制代码
  1. 确保对权重列表进行深复制,由于权重将在后续步骤中进行操作,深复制可解决由于子变量的更改而影响父变量的问题:
        wts_tmp = deepcopy(weights)
        wts_tmp2 = deepcopy(weights)
复制代码
  1. 循环遍历所有权重值,然后对其进行较小的更改 (+0.0001):
        for ix, wt in enumerate(weights): 
            wts_tmp[ix] += 0.0001
复制代码
  1. 当权重修改后,计算更新的前向传播损失。计算由于权重的微小变化而造成的损失变化,因为我们要计算所有输入采样的均方误差,因此将损失的变化除以输入的数据数量:
            loss = feed_forward(inputs, outputs, wts_tmp)
            del_loss = np.sum(org_loss - loss)/(0.0001*len(inputs))
复制代码

以较小的值更新权重,然后计算其对损失值的影响,等效于计算权重变化的导数(即反向梯度传播)。

  1. 通过损失变化来更新权重。通过将损失的变化乘以一个很小的数字(0.01)来缓慢更新权重,这就是学习率参数:
            wts_tmp2[ix] += del_loss*0.01
            wts_tmp = deepcopy(weights)
复制代码
  1. 返回更新的权重和偏差值:
        weights = deepcopy(wts_tmp2)
return wts_tmp2
复制代码

整体 update_weights() 函数如下所示:

from copy import deepcopy
def update_weights(inputs, outputs, weights, epochs):  
    for epoch in range(epochs):
        org_loss = feed_forward(inputs, outputs, weights)
        wts_tmp = deepcopy(weights)
        wts_tmp2 = deepcopy(weights)
        for ix, wt in enumerate(weights): 
            wts_tmp[ix] += 0.0001
            loss = feed_forward(inputs, outputs, wts_tmp)
            del_loss = np.sum(org_loss - loss)/(0.0001*len(inputs))
            wts_tmp2[ix] += del_loss*0.01
            wts_tmp = deepcopy(weights)

        weights = deepcopy(wts_tmp2)
return wts_tmp2
复制代码

通过更新网络 1000 次,查看训练后网络中的参数和偏置值:

weights = update_weights(x, y, w, 1000)
print(weights)
复制代码

打印权重如下所示,可以看到其与预期的结果 (w=3.0, b=0.0) 非常接近:

[[[2.99929065]]
 [[0.00478785]]]
复制代码

神经网络中的还有一个重要参数是在计算损失值时需要考虑的批大小 (batch size)。在以上示例中,我们同时为所有数据计算损失值。但是,当我们有成千上万个数据时,在计算损失值时增加大量数据的增量贡献将导致训练困难,甚至可能超出内存上限无法计算,因此通常在一个 epoch 中将数据分为多个 batch 送入网络进行训练,建立模型时常用的 batch 大小在 16 到 512 之间。

Guess you like

Origin juejin.im/post/7083995063532912677