吴恩达《深度学习专项》笔记+代码实战(六):改进梯度下降算法(mini-batch, Momentum, Adam)

学习提示

一直以来,我们都用梯度下降法作为神经网络的优化算法。但是,这个优化算法还有很多的改进空间。这周,我们将学习一些更高级的优化技术,希望能够从各个方面改进普通的梯度下降算法。

我们要学习的改进技术有三大项:分批梯度下降、高级更新方法、学习率衰减。这三项是平行的,可以同时使用。

分批梯度下降是从数据集的角度改进梯度下降。我们没必要等遍历完了整个数据集后再进行参数更新,而是可以遍历完一小批数据后就进行更新。

高级更新方法指不使用参数的梯度值,而是使用一些和梯度相关的中间结果来更新参数。通过使用这些更高级的优化算法,我们能够令参数的更新更加平滑,更加容易收敛到最优值。这些高级的算法包括gradient descent with momentum, RMSProp, Adam。其中Adam是前两种算法的结合版,这是目前最流行的优化器之一。

学习率衰减指的是随着训练的进行,我们可以想办法减小学习率的值,从而减少参数的震荡,令参数更快地靠近最优值。

在这周的课里,我们要更关注每种优化算法的单独、组合使用方法,以及应该在什么场合用什么算法,最后再去关注算法的实现原理。对于多数技术,“会用”一般要优先于“会写”。

课堂笔记

分批梯度下降

这项技术的英文名称取得极其糟糕。之前我们使用的方法被称为"batch gradient descent", 改进后的方法被称为"mini-batch gradient descent"。但是,这两种方法的本质区别是是否把整个数据集分成多个子集。因此,我们认为我的中文翻译“分批梯度下降”、“整批梯度下降”比原来的英文名词或者“小批量梯度下降”等中文翻译要更贴切名词本身的意思。

使用mini-batch

在之前的学习中,我们都是用整个训练集的平均梯度来更新模型参数的。而如果训练集特别大的话,遍历整个数据集要花很长时间,梯度下降的速度将十分缓慢。

其实,我们不一定要等遍历完了整个数据集再做梯度下降。相较于每次遍历完所有 m m m个训练样本再更新,我们可以遍历完一小批次(mini-batch)的样本就更新。让我们来看课件里的一个例子:

假设整个数据集大小 m = 5 , 000 , 000 m=5,000,000 m=5,000,000。我们可以把数据集划分成5000个mini-batch,其中每一个batch包含1000个数据。做梯度下降时,我们每跑完一个batch里的1000个数据,就用它们的平均梯度去更新参数,再去跑下一个batch。

这里要介绍一个新的标记。设整个数据集 X X X的形状是 ( n x , m ) ( m = 5 , 000 , 000 ) (n_x, m)(m=5,000,000) (nx,m)(m=5,000,000),则第** i i i个数据集的标记**为 X { i } X^{\lbrace i \rbrace} X{ i} ,形状为 ( n x , 1000 ) (n_x, 1000) (nx,1000)

再次总结一下标记: x ( i ) [ j ] { k } x^{(i)[j]\lbrace k\rbrace} x(i)[j]{ k}中的上标分别表示和第i个样本相关、和第j层相关、和第k个批次的样本集相关。实际上这三个标记几乎不会同时出现。

使用了分批梯度下降后,算法的写法由

for i in range(m):
    update parameters

变成

for i in range(m / batch_size)
  for j in range(batch_size):
      update parameters

。现在的梯度下降法每进行一次内层循环,就更新一次参数。我们还是把一次内层循环称为一个"step(步)“。此外,我们把一次外层循环称为一个"epoch(直译为’时代’,简称‘代’)”,因为每完成一次外层循环就意味着训练集被遍历了一次。

mini-batch 的损失函数变化趋势

使用分批梯度下降后,损失函数的变化趋势会有所不同:

请添加图片描述

如图所示,如果是使用整批梯度下降,则损失函数会一直下降。但是,使用分批梯度下降后,损失函数可能会时升时降,但总体趋势保持下降。

这种现象主要是因为之前我们计算的是整个训练集的损失函数,而现在计算的是每个mini-batch的损失函数。每个mini-batch的损失函数时高时低,可以理解为:某批数据比较简单,损失函数较低;另一批数据难度较大,损失函数较大。

选择批次大小

批次大小(batch size)对训练速度有很大的影响。

如果批次过大,甚至极端情况下batch_size=m,那么这等价于整批梯度下降。我们刚刚也学过了,如果数据集过大,整批梯度下降是很慢的。

如果批次过小,甚至小到batch_size=1(这种梯度下降法有一个特别的名字:随机梯度下降(Stochastic Gradient Descent)),那么这种计算方法又会失去向量化计算带来的加速效果。

回想一下第二周的内容:向量化计算指的是一次对多个数据做加法、乘法等运算。这种计算方式比用循环对每个数据做计算要快。

出于折中的考虑,我们一般会选用一个介于1-m之间的数作为批次大小。

如果数据集过小(m<2000),那就没必要使用分批梯度下降,直接拿整个数据集做整批梯度下降即可。

如果数据集再大一点,就可以考虑使用64, 128, 256, 512这些数作为batch_size。这几个数都是2的次幂。由于电脑的硬件容量经常和2的次幂相关,把batch_size恰好设成2的次幂往往能提速。

当然,刚刚也讲了,使用较大batch_size的一个目的是充分利用向量化计算。而向量化计算要求参与运算的数据全部在CPU/GPU内存上。如果设备的内存不够,则设过大的batch_size也没有意义。

一段数据的平均值

在课堂上,这段内容是从数学的角度切入介绍的。我认为这种介绍方式比较突兀。我将从计算机科学的角度切入,用更好理解的方式介绍“指数加权移动平均”。

背景

假设我们绘制了某年每日气温的散点图:

请添加图片描述

假如让你来描述全年气温的趋势,你会怎么描述呢?

作为人类,我们肯定会说:“这一年里,冬天的气温较低。随后气温逐渐升高,在夏天来到最高值。夏天过后,气温又逐渐下降,直至冬天的最低值。”

但是,要让计算机看懂天气的变化趋势,应该怎么办呢?直接拿相邻的天气的差作为趋势可不行。冬天也会出现第二天气温突然升高的情况,夏天也会出现第二天气温突然降低的情况。我们需要一个能够概括一段时间内气温情况的指标。

移动平均数

一段时间里的值,其实就是几天内多个值的总体情况。多个值的总体情况,可以用平均数表示。严谨地来说,假如这一年有365天,我们用 t t t表示这一年每天的天气,那么:

t i = { 第 i 天 的 天 气 ( 1 ≤ i ≤ 365 ) 0 ( i 取 其 他 值 ) t_i=\left\{ \begin{aligned} &第i天的天气 &(1 \leq i \leq 365) \\ &0 &(i取其他值) \end{aligned} \right. ti={ i0(1i365)(i)

我们可以定义一种叫做移动平均数(Moving Averages) 的指标,表示某天及其前几天温度的平均值。比如对于5天移动平均数 m a ma ma,其定义如下:

m a i = t i + t i − 1 + t i − 2 + t i − 3 + t i − 4 5 ( 1 ≤ i ≤ 365 ) ma_i=\frac{t_i+t_{i-1}+t_{i-2}+t_{i-3}+t_{i-4}}{5} (1 \leq i \leq 365) mai=5ti+ti1+ti2+ti3+ti4(1i365)

假如要让计算机依次输出每天的移动平均数,该怎么编写算法呢?我们来看几个移动平均数的例子:
m a 5 = ( t 5 + t 4 + t 3 + t 2 + t 1 ) / 5 m a 6 = ( t 6 + t 5 + t 4 + t 3 + t 2 ) / 5 m a 7 = ( t 7 + t 6 + t 5 + t 4 + t 3 ) / 5 \begin{aligned} ma_5=(t_5+t_4+t_3+t_2+t_1)/5 \\ ma_6=(t_6+t_5+t_4+t_3+t_2)/5 \\ ma_7=(t_7+t_6+t_5+t_4+t_3)/5 \end{aligned} ma5=(t5+t4+t3+t2+t1)/5ma6=(t6+t5+t4+t3+t2)/5ma7=(t7+t6+t5+t4+t3)/5
通过观察,我们可以发现 m a 6 = m a 5 + ( t 6 − t 1 ) / 5 ma_6=ma_5+(t_6-t_1)/5 ma6=ma5+(t6t1)/5 m a 7 = m a 6 + ( t 7 − t 2 ) / 5 ma_7=ma_6+(t_7-t_2)/5 ma7=ma6+(t7t2)/5

也就是说,在算n天里的m天移动平均数(我们刚刚计算的是5天移动平均数)时,我们不用在n次的外层循环里再写一个m次的循环,只需要根据前一天的移动平均数,减一个值加一个值即可。这种依次输出移动平均数的算法如下:

input temperature[0:n]
input m

def get_temperature(i):
    return temperature[i] if i >= 0 and i < n else 0

ma = 0
for i in range(n):
    ma += (get_temperature(i) - get_temperature(i - m)) / m
    ma_i = ma
    output ma_i

这种求移动平均数的方法确实很高效。但是,我们上面这个算法是基于所有温度值一次性给出的情况。假如我们正在算今年每天温度的移动平均数,每天的温度是一天一天给出的,而不是一次性给出的,上面的算法应该怎么修改呢?让我们来看修改后的算法:

input m
temp_i_day_ago = zeros((m))

def update_temperature(t):
    for i in range(m - 1):
        temp_i_day_ago[i+1] = temp_i_day_ago[i]
    temp_i_day_ago[0] = t

ma = 0
for i in range(n):
    input t_i
    update_temperature(t_i)
    ma += (temp_i_day_ago[0] - temp_i_day_ago[m]) / m
    ma_i = ma
    output ma_i

由于我们不能提前知道每天的天气,我们需要一个大小为m的数组temp_i_day_ago记录前几天的天气,以计算m天移动平均数。

上述代码的时间复杂度还是有优化空间的。可以用更好的写法去掉update_temperature里的循环,把计算每天移动平均数的时间复杂度变为 O ( 1 ) O(1) O(1)。但是,这份代码的空间复杂度是无法优化的。为了算m天移动平均数,我们必须要维护一个长度为m的数组,空间复杂度一定是 O ( m ) O(m) O(m)

对于一个变量的m移动平均数, O ( m ) O(m) O(m)的空间复杂度还算不大。但假如我们要同时维护l个变量的m移动平均数,整个算法的空间复杂度就是 O ( m l ) O(ml) O(ml)。在l很大的情况下,m对空间的影响是很大的。哪怕m取5这种很小的数,也意味着要多花4倍的空间去存储额外的数据。空间复杂度里这多出来的这个 m m m是不能接受的。

指数加权移动平均

作为移动平均数的替代,人们提出了指数加权移动平均数(Exponential Weighted Moving Average) 这种表示一段时期内数据平均值的指标。其计算公式为:

v i = β v i − 1 + ( 1 − β ) t i v_i=\beta v_{i-1} + (1 - \beta)t_i vi=βvi1+(1β)ti

这个公式直观上的意义为:一段

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值