cs231n 2023春季课程理解——lecture_3

引言

  在上篇cs231n 2023春季课程理解——lecture_2中,我们谈到了图像分类以及一些线性分类方法。为了更好地进行分类,我们介绍了损失函数。但如何让损失函数去寻找最合适的权重呢,这就需要应用到正则化以及优化方法。正则化在上篇文章中已经讲过了(由于17年的公开课的每讲课程和23年的ppt排版不一样,因此将正则化也放到了第2讲,实际上ppt中是第三讲的内容),本篇就不再讲述。故本篇内容主要讲述优化策略梯度下降以及学习率

优化

  上篇文章中,我们已经学习了损失函数。那么,我们接下来我们应该想办法来找到一个最好的权重W来使得损失值最小。这就是优化。

损失函数的可视化

  在讲优化之前,我们先来看看损失函数的可视化。损失函数一般都是在高维空间中定义的(如在CIFAR-10数据集中,它的权重矩阵一般为10×3073(包括了偏置项b)),这就让我们很难去彻底的可视化。为此,我们可以将其切片,然后映射到1维或者2维空间。这样的话就可以比较直观的去观看。例如,我们随机生成一个权重矩阵W,该矩阵就是高维空间中的某个点。然后沿着该点的某个维度方向一直前进,同时记录着损失函数值的变化。也就是说,我们随机生成了一个方向 W 1 W_1 W1,并沿着这个方向去计算损失值。计算方法是根据不同的 a a a来计算 L ( W + a W 1 ) L(W+aW_1) L(W+aW1)。在这个过程中,我们能得到一张平面图(1维),这张图的x轴即为a(自变量),而y轴即为损失函数计算的值( L ( W + a W 1 ) L(W+aW_1) L(W+aW1))。类似地,我们可以用a和b来表示不同的值来计算 L ( W + a W 1 ) + b W 2 L(W+aW_1)+bW_2 L(W+aW1)+bW2,从而给出一个二维图像(但我感觉是三维,只不过它用了颜色的变化来替代z轴),其中,x轴和y轴分别代表着a和b。
  下图将用无正则化的多类SVM的损失函数来展示。其中,左边(一个维度上的切片)和中间(两个维度上的切片)均只有一个样本数据,而右边则有100个CIFAR-10数据集中的样本数据。在右边(两个维度上的切片)和中间两图中,蓝色表示这部分区域损失函数值比较低,红色表示这部分损失值比较高。
损失函数可视化
  对于上图,我们应该注意损失函数的分段线性结构(单个样本)。而有图由于是多个样本,其损失值相当于每个样本损失值的一个平均值,因此它看起来是圆形的(其实相当于多个中间的结构平均后的图)。对于损失函数的分段线性结构,我们可以通过数学公式来解释。如一个样本(一条数据)的损失函数,它的计算方式为: L i = ∑ j ≠ y i [ max ⁡ ( 0 , W j T x i − W y i T x i + 1 ) ] L_i=\sum_{j\neq y_i}\left[\max(0, W_j^Tx_i-W_{y_i}^Tx_i+1)\right] Li=j=yi[max(0,WjTxiWyiTxi+1)]
每个样本的数据损失值是以W为参数的线性函数的总和。更明确地说,我们假设有一个简单的数据集,它包含了三个一维的点和对应的类别标签,那么完整的不带正则化的SVM损失函数值的计算为:
L 0 = max ⁡ ( 0 , W 1 T x 0 − W 0 T x 0 + 1 ) + max ⁡ ( 0 , W 2 T x 0 − W 0 T x 0 + 1 ) L_0=\max(0, W_1^Tx_0-W_{0}^Tx_0+1) + \max(0, W_2^Tx_0-W_{0}^Tx_0+1) L0=max(0,W1Tx0W0Tx0+1)+max(0,W2Tx0W0Tx0+1)
L 1 = max ⁡ ( 0 , W 0 T x 1 − W 1 T x 1 + 1 ) + max ⁡ ( 0 , W 2 T x 1 − W 1 T x 1 + 1 ) L_1=\max(0, W_0^Tx_1-W_{1}^Tx_1+1) + \max(0, W_2^Tx_1-W_{1}^Tx_1+1) L1=max(0,W0Tx1W1Tx1+1)+max(0,W2Tx1W1Tx1+1)
L 2 = max ⁡ ( 0 , W 0 T x 2 − W 2 T x 2 + 1 ) + max ⁡ ( 0 , W 1 T x 2 − W 2 T x 2 + 1 ) L_2=\max(0, W_0^Tx_2-W_{2}^Tx_2+1) + \max(0, W_1^Tx_2-W_{2}^Tx_2+1) L2=max(0,W0Tx2W2Tx2+1)+max(0,W1Tx2W2Tx2+1)
L = ( L 0 + L 1 + L 2 ) / 3 L = (L_0+L_1+L_2)/3 L=(L0+L1+L2)/3
由于例子都是一维的,所以数据 x i x_i xi和权重 W i W_i Wi都是数字。现在我们看向 W 0 W_0 W0,上面的三个式子中,每一个都是含 W 0 W_0 W0的线性函数。且每一项都会与0比较来取其中的最大值。其可视化结果如下图:
可视化结果
注:

  • 一旦我们将损失函数应用到神经网络中,它的可视化结果图就不再是上面展示的线性函数或者是类似于碗状的结构,而是更加负责的类似于地形的形状
  • 由于在损失函数中使用了max()函数(例如上面给的损失函数),因为损失函数中会存在一些不可导点。比如折点处,这些点使得损失函数不可微,因为在这些不可导点处,梯度是没有定义的。但其次梯度(该点左右导数之间的任意值)依然存在,且常常被使用。在这个教程中,次梯度和梯度会交换使用。

优化策略

  现在终于到了讲解优化的时候了,但不要急,我们先回忆一下损失函数的目的是什么。损失函数可以让我们衡量任意一组权重参数W的效果的好坏。那么优化呢?它其实是让我们能够找到能够最小化损失函数的权重参数W。所以,优化策略到底是什么呢?

策略1:随机搜索(Random search)

  也许有人会说,想要找到一个最好的W,让损失函数最小化,这很容易啊。只要我们随机出足够多的W,那么就能测试这些W中哪个是最好的,就能找到它了。没错,这是一种方法,也就是所谓的随机搜索策略,但这种方法一般每次使用都是针对特定的问题来制定,而且需要反复地去设计。当你放到了其他数据集上(或者说换了个问题),它就不行了。因此,这是一种非常不好的策略。
当然,既然提到了,那我们就来看看它的代码实现(python):

# 假设X_train中的每一列都是一个数据样本(例如:3073 x 50,000)
# 假设Y_train是对应的标签(例如:1 × 50,000)
# 假设函数L为损失函数

bestloss = float("inf") # 由Python赋值最大可能得损失值(float类型)
for num in range(1000): 
  W = np.random.randn(10, 3073) * 0.0001 # 生成随机参数
  loss = L(X_train, Y_train, W) # 获得损失值
  if loss < bestloss: # 追踪最好的结果
    bestloss = loss
    bestW = W
  print('in attempt %d the loss was %f, best %f' % (num, loss, bestloss))

# 输出:
# in attempt 0 the loss was 9.401632, best 9.401632
# in attempt 1 the loss was 8.959668, best 8.959668
# in attempt 2 the loss was 9.044034, best 8.959668
# in attempt 3 the loss was 9.278948, best 8.959668
# in attempt 4 the loss was 8.857370, best 8.857370
# in attempt 5 the loss was 8.943151, best 8.857370
# in attempt 6 the loss was 8.605604, best 8.605604
# ... (trunctated: continues for 1000 lines)

  在上述的代码中,我们随机生成了1000次权重W,并同时去寻找并确定让损失值最小的W。将其找到之后,就可以用它来对测试集进行测试,看看最终结果怎么样了。测试的代码如下:

#假设X_test的形状是3073 x 10000, Y_test是10000 x 1
scores = Wbest.dot(Xte_cols) # 形状为10 x 10000, 所有测试样例的分类分数
# 找到在每列中评分值最大的索引(即预测的分类)
Yte_predict = np.argmax(scores, axis = 0)
# 计算精度(就是准确率,一个评价算法效果好坏的指标)
np.mean(Yte_predict == Yte)
# returns 0.1555

  通过上面的代码我们可以看到,它在测试集上的准确度只有0.1555(这个是李飞飞教授团队的学生做的实验,可能自己去做结果又不一样了)。这个准确度看上去比完全随机猜测图像类别的准确度(10%)要高,感觉上好像还不错?但其实目前在CIFAR-10数据集上最高的准确度能达到99.7%左右了(现在还觉得它还不错吗)。

策略2:随机本地搜索(Random Local Search)

  第一个策略我们可以类比成自己在山上走路,没走一步,下一步都是随机选择方向走。如果发现找到的这个方向是上山的方向,那么停下来不走,重新选择方向;如果是下山的方向,那么就往下走。这种方法效率不高,事实上效果也不是很好。那么现在我们可以对其进行一些改进,如从一个随机的 W W W开始,然后生成一个随机的 δ W \delta W δW,只有当 W + δ W W+\delta W W+δW的损失值更新时,才更新权重。具体代码如下:

W = np.random.randn(10, 3073) * 0.001 #随机生成一个W
bestloss = float("inf")
for i in range(1000):
  step_size = 0.0001
  Wtry = W + np.random.randn(10, 3073) * step_size
  loss = L(Xtr_cols, Ytr, Wtry)
  if loss < bestloss:
    W = Wtry
    bestloss = loss
  print('iter %d loss is %f' % (i, bestloss))

  上述方法运行完成之后,测试的结果为21.4%(看起来确实比之前随机的好多了)。

策略3:跟随梯度(Following the Gradient)

  前面两个策略其实都是在权重空间中找到合适的方向,使得损失值能够降低。其实并不需要去寻找这些方向,也能够计算出最好的方向。这个方向就是梯度。我们可以将其看做是在下山时,我们先感受脚下山体的一个倾斜度,然后沿着最陡峭的方向下去。
梯度

  在一个一维函数中,斜率是函数在某一点的瞬时变化率。而梯度则是它的一般化表达,是一个向量。另外,梯度就是输入空间(应该是输入样本数据吧)中各个维度(单个样本)的斜率组成的向量(或者可以称之为导数)。对一维函数的求导公式如下:
d f ( x ) f ( x ) = lim ⁡ h → 0 f ( x + h ) − f ( x ) h \frac{df(x)}{f(x)}=\lim_{h \to 0}\frac{f(x+h)-f(x)}{h} f(x)df(x)=h0limhf(x+h)f(x)
当函数有多个自变量的时候,我们将导数称为偏导数。而梯度则是由这些偏导数所形成的向量。(偏导数的求导具体自己去查找相关资料)。

梯度计算

  上面我们讲了优化的策略,其中一个就是跟随梯度来进行优化。那么梯度是怎么计算的呢?下面我们来讲解一下它的两种计算方式:

  1. 数值梯度法:计算慢,结果近似,但实现简单。
  2. 分析梯度法:计算快,结果精确,但容易出错,且经常需要使用微分。

数值梯度法

  数值梯度法实际上就是利用了梯度的定义来求结果。其过程如下图:
数值梯度过程1
数值梯度过程2
数值梯度过程3
数值梯度过程4
  在上述图片中,我们首先计算了当前权重W下的一个损失值,然后设置h=0.0001,并以此来分别计算W中每个数值对应的损失值(只变化当前的数值,其他的不变),最终通过导数的定义来计算梯度dW。
从上图可以看出,这种方法非常的慢,因为它需要去循环迭代每一个维度。而且最终的结果也是近似结果,不会太准确。
它的代码实现为:

def eval_numerical_gradient(f, x):
  """
  关于f在x处的数值梯度的简单实现
  - f 是只有一个参数(就是x)的函数
  - x 是计算梯度的点(一个numpy矩阵)
  """

  fx = f(x) # 计算x处的函数值
  grad = np.zeros(x.shape)
  h = 0.00001

  # 对x中的索引进行迭代
  it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
  while not it.finished:

    # 计算x+h处的函数值
    ix = it.multi_index
    old_value = x[ix]
    x[ix] = old_value + h # 增加h
    fxh = f(x) # 计算f(x + h)
    x[ix] = old_value # 将x(0,0)处改回原值(非常重要)

    # 计算偏导数
    grad[ix] = (fxh - fx) / h # 该点处的导数
    it.iternext() # 进入下一个维度

  return grad

  实际上,使用中心差值公式 [f(x+h)−f(x−h)]/2h 来计算梯度效果更好。其代码实现如下:

# 为了使用上面的代码,需要一个只有一个参数(就是权重W)的函数,因此封装了X_train和Y_train
def CIFAR10_loss_fun(W):
  return L(X_train, Y_train, W)

W = np.random.rand(10, 3073) * 0.001 # 随机权重向量
df = eval_numerical_gradient(CIFAR10_loss_fun, W) # 获得梯度
loss_original = CIFAR10_loss_fun(W) # 获得初始损失值
print('original loss: %f' % (loss_original, ))

# 查看不同步长的效果
for step_size_log in [-10, -9, -8, -7, -6, -5,-4,-3,-2,-1]:
  step_size = 10 ** step_size_log
  W_new = W - step_size * df # 权重空间中的新位置,使用负梯度
  loss_new = CIFAR10_loss_fun(W_new)
  print('for step size %f new loss: %f' % (step_size, loss_new))

# 输出:
# original loss: 2.200718
# for step size 1.000000e-10 new loss: 2.200652
# for step size 1.000000e-09 new loss: 2.200057
# for step size 1.000000e-08 new loss: 2.194116
# for step size 1.000000e-07 new loss: 2.135493
# for step size 1.000000e-06 new loss: 1.647802
# for step size 1.000000e-05 new loss: 2.844355
# for step size 1.000000e-04 new loss: 25.558142
# for step size 1.000000e-03 new loss: 254.086573
# for step size 1.000000e-02 new loss: 2539.370888
# for step size 1.000000e-01 new loss: 25392.214036

  在上面这段代码中,需要注意的事情:

  1. 在梯度的负方向进行更新:为了计算W_new,我们沿着梯度的负方向(减去梯度,即梯度下降方向)去更新的,这是因为我们的目的是让损失值变得更小。
  2. 步长(也就是深度学习中的学习率)的影响:上面的代码是从某个具体的点 W开始计算梯度,梯度指明了函数在哪个方向是变化率最大的,即损失函数下降最陡峭的方向,但是没有指明在这个方向上应该迈多大的步子(即我们需要更改多少)。同时,如果步长比较小,那么梯度更新比较慢,但是梯度的下降也会比较稳定。如果步长比较大,那么梯度的更新也会比较快,同样地,梯度的下降也会比较快,这就会导致可能错过了最佳的梯度。由上面的结果我们也可以看到,如果步长过大,反而可能会要损失值变得更大(例如损失值从1.64变到2.84)。因此,需要确定一个合适的步长(学习率),这其实在深度学习中是最重要的(好像也是最麻烦的)超参数之一。
  3. 效率:由于使用这种方法来计算梯度,需要一直循环。因此,损失函数每变化一次(走一步)就需要计算N+1(W具有N个参数,梯度需要计算N次,第N+1次启动更新)。这让得这种方法在深度学习中几乎不使用(深度学习中的参数量动辄上千万,想想就头疼)。

分析梯度法

  上面的数值分析法真的是太麻烦了,这要是用在了神经网络中,那不是单纯计算梯度就要好久。理论上来说确实如此,但不要急,因为出现了那么两个神(我认为可以称之为神),一个叫莱布尼茨(可能没怎么听过,我也没听过,但是他发现并完善了二进制,大家应该知道二进制对于计算机的作用吧,这么想来,成为神好像也没错),另一个叫牛顿(这个就更厉害了,相信你们都听过。对的,他就是那个因为被苹果砸到就发现了万有引力的神),他们两个提出了微积分。正式是这个微积分的提出,让我们可以使用微分来分析,最终计算出一个精确的梯度值,不过计算的时候好像比较容易出错。不过不怕,我们可以用分析梯度法的结果与数值分析法的结果进行比较来检查结果的准确性。这就是梯度检查(gradient check)。
梯度检查

  我们现在用SVM的损失函数来举例:
L i = ∑ j ≠ y i [ max ⁡ ( 0 , w j T x i − w y i T x i + Δ ) ] L_i=\sum_{j\neq y_i}[\max(0, w_j^Tx_i-w_{y_i}^Tx_i+\Delta)] Li=j=yi[max(0,wjTxiwyiTxi+Δ)]
现在我们来对这个损失函数的权重求导,如对 w y i w_{y_i} wyi求导,可得到:
▽ w y i L i = − ( ∑ j ≠ y i 1 ( w j T x i − w y i T x i + Δ > 0 ) ) x i \bigtriangledown_{w_{y_i}}L_i=-(\sum_{j\neq y_i}1(w_j^Tx_i-w_{y_i}^Tx_i+\Delta > 0))x_i wyiLi=(j=yi1(wjTxiwyiTxi+Δ>0))xi
其中,1是示性函数(事件发生与否与0,1两值函数的对应关系),即如果括号内的条件为真,则返回1,否则返回0。这个代码看着好像挺复杂,但是代码实现比较简单(只需要计算没有满足边界值的分类的数量(因此贡献了损失函数),然后乘以 x i x_i xi就是梯度了)。当然,上面这个公式只是计算出了正确分类的W的行向量的梯度(即 j = y i j=y_i j=yi),那么那些 j ≠ y i j\neq y_i j=yi的行向量的梯度是:
▽ w y i L i = 1 ( w j T x i − w y i T x i + Δ > 0 ) x i \bigtriangledown_{w_{y_i}}L_i=1(w_j^Tx_i-w_{y_i}^Tx_i+\Delta > 0)x_i wyiLi=1(wjTxiwyiTxi+Δ>0)xi
  一旦导出了梯度表达式,就可以直接实现表达式并使用它们执行梯度更新。

梯度下降

  现在我们可以计算损失函数的梯度了,程序可以重复的计算梯度然后对参数不断进行更新权重(参数),这个过程就叫做梯度下降。

批梯度下降(BGD)

# Vanilla Gradient Descent

while True:
  weights_grad = evaluate_gradient(loss_fun, data, weights)
  weights += - step_size * weights_grad # perform parameter update

  这个简单的循环是所有神经网络的核心。虽然也有其他的优化方法(比如LBFGS),但到目前为止,梯度下降是目前神经网络中最常用的优化方法。之后的一些优化算法也是在这上面更新一些东西(比如说更新具体公式),但其核心思想永远不变,那就是一直沿着梯度走,知道结果不变。这种梯度下降方法的优点为它利用了所有训练集,因此,当损失函数达到最小值时,它计算出的梯度能够为0,保证收敛,而不需要使用学习率衰减。当然,它正因为一次性使用所有的训练集,所以如果训练集比较大的话,它的运行速度会变慢。

小批量梯度下降(Mini-batch gradient descent)

  有了上面的梯度下降方法,就可以优化损失函数,让损失函数最小了。但是在大规模的应用中(比如说ILSVRC挑战赛),训练数据量N可能会达到百万量级。那么此时如果用上面的方法来训练整个训练集,最终却得到一个参数的更新(而且还慢),这无疑会浪费大量的计算资源。因此,可以使用训练集中的小批量来计算。
  例如,在目前最好的卷积神经网络中,典型的小批量中有 256 个样本(当然也可能是32/64/128等),而整个训练集是一120万个样本。这个小批量的数据就会用来实现一个参数更新。具体代码如下:

# Vanilla Minibatch Gradient Descent

while True:
  data_batch = sample_training_data(data, 256) # sample 256 examples
  weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
  weights += - step_size * weights_grad # perform parameter update

  这种方法之所以不错,是因为训练集中的数据都是相关的。
  现在我们考虑一个非常极端的情况:ILSVRC中的120万张图片是通过1000张不同的图片复制而来的(每张复制1200张),那么很明显这1200张图片的梯度都是一样的(所有东西都一样,结果又怎么会不一样呢)。所以,我们计算这120万张图片的平均损失值和只计算这1000张不同图片的平均损失值结果是一样的。
  当然了,在实际情况中,数据集里面肯定不会有重复的图片啊(一般来说是这样的,因为没必要),这个时候小批量数据的梯度就是对整个数据集的梯度的一个近似值(所有图片都有一定的关联性嘛)。因此,在实际中通过计算小批量梯度下降可以更快地让网络收敛,并以此来进行频繁的参数更新。

随机梯度下降(Stochastic Gradient Descent, SGD)

  当小批量小到每次都只有一个数据样本时,上面的梯度下降方法就是随机梯度下降了,但是在平时,我们都会称小批量梯度下降为随机梯度下降。为什么会这样说呢?随机梯度下降是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次(是不是有些眼熟,它其实就是小批量梯度下降的方式嘛。而且随机取出的样本都会进行打乱,这样就会更好的学习整体的梯度了)。
  当然,SGD也存在着它的不足,比如说它收敛速度慢(训练轨迹成锯齿状,能不慢吗,难道不知道两点之间直线最短么),而且可能会陷入局部最优(能走到极点,二阶导为0了,默认为它最优了,其实还有更好的)。
不足1
不足2

Momentum算法

  在上面我们可以看到,SGD训练时间很长,它的轨迹像一个锯齿状。那么如何解决这个问题呢。一个很简单的方法就是尽可能的让损失函数的轨迹变直,且稳定的向前走,不会像锯齿状(至少幅度别那么大)。为了实现这个想法,我们需要知道梯度在过去的一段时间内的大致走向,以消除当前轮迭代梯度向量存在的方向抖动。这就是Momentum算法。它的本质是将一段时间内的梯度向量进行了加权平均,分别计算得到梯度更新过程中w和b的大致走向,一定程度上消除了更新过程中的不确定性因素(如上图中的摆动现象),使得梯度更新朝着一个越来越明确的方向前进。 当然在实际上,它经常和SGD一起使用。一起使用后的结果,如图所示:
结合后的结果
它的代码实现如下:

vx=0
while True:
    dx =  compute_gradient(x)
    vx = rho * vx + dx # rho表示每回合vx的衰减程度
    x -= learning_rate * vx

代码实现

Nesterov Momentum

Nesterov Momentum1
如上图(图中左半部分)所示,当我们进行动量更新时,实际上是对两个向量进行求和(实际上是加权求平均),但既然既然我们知道动量将会将质点带到一个新的位置,那么我们就可以不在原来的位置上做梯度计算了,而是直接到那个新的位置上做梯度计算,从而更新参数(如上图又半部分),这就是Nesterov Momentum。
Nesterov Momentum2

AdaGrad

  前面的优化算法一般都需要手动的去设置学习率(之前说的步长),但是,不同的参数,梯度不一样。因此,我们想能不能自动的适应学习率(人嘛,就是不满足),这就有了AdaGrad算法。
Adagrad优化算法就是在每次使用一个 batch size 的数据进行参数更新的时候,算法计算所有参数的梯度,那么其想法就是对于每个参数,初始化一个变量 s 为 0,然后每次将该参数的梯度平方求和累加到这个变量 s 上,然后在更新这个参数的时候,学习率就变为:
η s + ε \frac{\eta }{\sqrt{s+\varepsilon } } s+ε η
其中, ε \varepsilon ε是为了稳定数值而加上的,因为s的值有可能为0,而0出现在分母上时就会出现无穷大的情况。通常 ϵ 取 10的负7次方,这样不同的参数由于梯度不同,他们对应的 s 大小也就不同,上面的公式得到的学习率也就不同,这也就实现了自适应的学习率。
其代码的实现为:

grad_squared = 0
while True:
	dx = compute_gradient(×)
	grad_squared += dx * dx
	x -= learning_rate ** dx /(np.sqrt(grad_squared)+1e-7)

  Adagrad 的核心想法就是,如果一个参数的梯度一直都非常大,那么其对应的学习率就变小一点,防止震荡,而一个参数的梯度一直都非常小,那么这个参数的学习率就变大一点,使得其能够更快地更新。

RMSProp

  RMSProp算法其实与AdaGrad差不多,唯一的区别在于累积平方梯度的求法不同。它不像AdaGrad算法那样暴力直接的累加平方梯度,而是加了一个衰减系数来控制历史信息的获取多少。这样的话在参数空间更为平缓的方向,会取得更大的进步(因为平缓,所以历史梯度平方和较小,对应学习下降的幅度较小),并且能够使得陡峭的方向变得平缓,从而加快训练速度。
  它的代码实现如下:

grad_squared = 0
while True:
	dx = compute_gradient(×)
	grad_squared = decay_rate * grad_squared + (1 - decay_rate) * dx * dx
	x -= learning_rate ** dx /(np.sqrt(grad_squared)+1e-7)

Adam

  讲到这个,你应该非常熟悉了,因为它是目前神经网络中用的最为广泛的优化算法(好像没有之一)。如果你还没有接触(大概率也没有接触),没关系,之后你就会发现,几乎所有的方法里面用的优化算法都是它。
  Adam吸收了Adagrad(自适应学习率的梯度下降算法)和动量梯度下降算法的优点,既能适应稀疏梯度,又能缓解梯度震荡的问题。
  其具体代码如下:

first_moment = 0
second_moment = 0
for t in range(1, num_iterations):
	dx = compute_gradient(×)
	first_moment = beta1 * first_moment + (1- beta1) * dx
	second_moment = beta2 * second_moment + (1 - beta2) * dx * dx
	first_unbias = first_moment / (1- beta1 ** t)
	second_unbias = second_moment / (1 - beta2 ** t)
	x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7))

  以上代码是Adam的完整版本。但目前绝大多数Adam的实现中,都没有for循环中的第四行和第五行。现在在回过头分析,for循环中的first_moment = beta1 * first_moment + (1- beta1) * dx,是不是和之前的Momentum(见图)有一些像,而second_moment = beta2 * second_moment + (1 - beta2) * dx * dx以及x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7)),则像是AdaGrad(或者说是RMSProp)。
  目前,在使用Adam优化算法之后,可以将beta1beta2和** learning_rate** 分别设置为0.9、0.999以及 e − 3 e^{-3} e3或者 5 e − 4 5e^{-4} 5e4,它对于很多模型来说,是一个很好的开始。
adam

总结

  本篇中讲了损失函数的可视化,以及一些优化策略,最后,描述了一下梯度的计算方式以及一些梯度下降的方法。

文中所有图片来自于cs231n公开课的网站之中。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值