5 小批量随机梯度下降
5.1 向量化和缓存
尽量使用向量化操作,并行化计算
例如: 计算 $\mathbf{ A } = \mathbf{ B } \mathbf{ C }$我们有很多方法来计算A:
- 一次计算: $\mathbf{ A } _ { i j } = \mathbf{ B } _ { i , : } \cdot \mathbf{ C } _ { : , j }$ (一次将B的一行,C的一列放到cpu, 逐个计算)
- 一次计算: $\mathbf{ A } _ {:, j } = \mathbf{ B } \cdot \mathbf{ C } _ { : , j }$ (一次将整个B, C的一列放到cpu, 逐列计算)
- 一次计算: $\mathbf{ A } = \mathbf{ B } \cdot \mathbf{ C }$ (一次将整个B, C放到cpu, 一次计算)
- 将B, C分块, 逐块计算
1 | %matplotlib inline |
逐元素计算: 0.9588320255279541 sec
逐列计算: 0.003960132598876953 sec
一次性计算: 0.000997781753540039 sec
逐元素计算: 2.0858710876900006 Gflops
逐列计算: 505.0335942203492 Gflops
一次性计算: 2004.4463560334527 Gflops
5.2 小批量
处理单个观测值需要我们执行许多单一矩阵‐矢量(甚至矢量‐矢量)乘法,这耗费相当大,而且对应深度学习框架也要巨大的开销。这既适用于计算梯度以更新参数时,也适用于用神经网络预测。也就是说,每当我们执行$\mathbf{ W } \leftarrow \mathbf{ W } - \eta _ t g _ t$时, 消耗巨大. 其中
$$ \mathbf{ g } _ t = \partial _ w f ( \mathbf{ x } _ t , \mathbf{ w } ) $$通过将其应用于一个小批量观测值来提高此操作的计算效率:
$$ \mathbf{ g } _ t = \partial _ w \frac{ 1 }{ | \mathcal{ B } _ t | } \sum _ { i \in \mathcal{ B } _ t } f ( \mathbf{ x } _ i , \mathbf{ w } ) $$由于$\mathbf{ x }_ t$和小批量$\mathbf{B}_ t$的所有元素都是从训练集中随机抽出的,因此梯度的期望保持不变。另一方面,方差显著降低。由于小批量梯度由正在被平均计算的b:=|Bt|个独立梯度组成,其标准差降低了$b ^ {− \frac{1}{2}}$。这本身就是一件好事,因为这意味着更新与完整的梯度更接近了。
执行相同的矩阵乘法, 这次将其一次性分为64列的小批量:
1 | timer.start() |
分块计算: 0.001994609832763672 sec
分块计算: 552.8641666117445 Gflops
5.3 读取数据集
- 如何从数据中有效地生成小批量。使用NASA开发的测试机翼的数据集’不同飞行器产生的噪声’来比较这些优化算法。为方便起见,我们只使用前1,500样本。数据已作预处理:我们移除了均值并将方差重新缩放到每个坐标为1。
1 | #@save |
5.4 从零开始实现
1 | def sgd(params ,states, hyperparams): |
loss: 0.248, 0.023 sec/epoch
- 上面的batch=n, 计算loss使用了全部数据, 属于梯度下降法, 每个epoch只更新一次参数
- 这里batch=1, 计算loss使用了一个数据, 属于随机梯度下降法, 每个epoch更新n次参数
1 | sgd_res = train_sgd(0.005, 1) |
loss: 0.248, 0.054 sec/epoch
尽管两个例子在一个迭代轮数内都处理了1500个样本,但实验中随机梯度下降的一个迭代轮数耗时更多。这是因为随机梯度下降更频繁地更新了参数,而且一次处理单个观测值效率较低。
设置batch_size = 100, 随机梯度下降速度甚至优于梯度下降法
1 | mini1_res = train_sgd(.4, 100) |
loss: 0.245, 0.003 sec/epoch
- 尽管在处理的样本数方面,随机梯度下降的收敛速度快于梯度下降,但与梯度下降相比,它需要更多的时间来达到同样的损失,因为逐个样本来计算梯度并不那么有效。
- 小批量随机梯度下降能够平衡收敛速度和计算效率。大小为10的小批量比随机梯度下降更有效;大小为100的小批量在运行时间上甚至优于梯度下降。
1 | d2l.set_figsize([6, 3]) |
5.5 简洁实现
1 | #@save |
loss: 0.243, 0.008 sec/epoch