Optimization 课程笔记翻译

Table of Contents:

  • Introduction
  • Visualizing the loss function
  • Optimization
  • Strategy #1: Random Search
  • Strategy #2: Random Local Search
  • Strategy #3: Following the gradient
  • Computing the gradient
  • Numerically with finite differences
  • Analytically with calculus
  • Gradient descent
  • Summary

一、引言(Introduction)

在前面的章节中,我们介绍了在两个图像分类任务中的核心概念:

  1. 计分函数:计分函数是一个包含参数的函数,它的主要作用是把原始的像素信息映射为各个类别上的分数(比如 线性函数)
  2. 损失函数:如果我们把“训练数据集中通过计分函数得到的分数和真实的标签之间的差距”定义为“吻合度”,那么损失函数就是基于“吻合度”这个概念而被定义出来的。损失函数可以用来评价某一组参数的好坏【原话是衡量某一组参数的质量,其实我们就是希望某一组参数的取值能够让损失函数最小嘛,如果有两组参数,一组参数对应的损失函数大,一组参数对应的损失函数小,那么小的那个就比大的那个好。所以我这里翻译成参数的好坏】。有很多中方法都能定义一个损失函数(比如softmax或者SVM)。

具体来说,我们定义计分函数是形式为$f(x_i, W) = W x_i $,这是一个线性函数。同时使用支持向量机损失函数,其形式为:
L = 1 N ∑ i ∑ j ≠ y i [ max ⁡ ( 0 , f ( x i ; W ) j − f ( x i ; W ) y i + 1 ) ] + α R ( W ) L = \frac{1}{N} \sum_i \sum_{j\neq y_i} \left[ \max(0, f(x_i; W)j - f(x_i; W){y_i} + 1) \right] + \alpha R(W) L=N1ij=yi[max(0,f(xi;W)jf(xi;W)yi+1)]+αR(W)
我们可以看到,预测值和真值一致的时候,损失函数是最小的。而参数的取值对预测值是有影响的。【原话直译是:样本 x i x_i xi通过某一组参数产生的预测值与真值 y i y_i yi一致时损失函数也最小。我还是把它拆开说顺溜点】我们现在引出第三个关键的概念最优化。最优化是一个过程,这个过程的目标是找到参数的一组取值,能够让损失函数最小。

预告:一旦我们了解了这三个概念之间是如何交互起作用的,我们就会重新回顾第一个概念(包含参数的函数映射),将其从线性函数扩展为更加复杂的函数:首先是神经网络,之后是卷积神经网络。但是损失函数和优化的过程基本保持不变【这句话非常重要,直接点出了整门课程的脉络。这门课就可以看成就在讲三个概念:计分函数、损失函数、最优化过程。计分函数从线性函数转变为常规神经网络函数以及卷积神经网络】

二、损失函数可视化(Visualizing the loss function)

我们在这门课里所见到损失函数通常都定义在非常高的维度空间中(比如在CIFAR-10数据集中,线性分类器的权重矩阵大小为10X3072,一共30730个维度),因此要想把它们可视化是很难的。但是通过沿着高维空间的一条线(一个维度)或者一个平面(2个维度)进行切片,我们可以获得它们的一些直观感受。举个例子:我们可以生成一个随机的权重矩阵 W W W(这个 W W W相当于空间的一个点),之后沿着一条线方向行进,记录损失函数在这条线上的值。具体的操作方法是:生成一个随机的方向 W 1 W_1 W1,当a取不同的值时候, W + a W 1 W + a W_1 W+aW1直观上相当于一条线从 W W W这个点出发,沿着 W 1 W_1 W1这个方向不断的往前延伸, L ( W + a W 1 ) L(W + a W_1) L(W+aW1)的含义是把 W + a W 1 W + a W_1 W+aW1带入到L中计算相当于计算损失函数在这条线上的值【原话是:也就是说,生成一个随机的方向 W 1 W_1 W1,沿着这个方向计算损失,通过这样的方式,当a取不同的值的时候,计算 L ( W + a W 1 ) L(W+a W_1) L(W+aW1)】。这个过程生成一个简单的图表,这个图标以a作为x轴的值,以损失函数的值作为y轴上的值。类似的,我们可以定义两个变量 a , b a,b a,b,通过计算 L ( W + a W 1 + b W 2 ) L(W + a W_1 + b W_2) L(W+aW1+bW2)的值得到一个二维的切片。在坐标轴上, a , b a,b ab相当于x轴和y轴,损失函数的值可以用颜色表示出来。
在这里插入图片描述
多分类SVM(不带正则化)模型中单个样本的损失函数形态【原话中的landscape原意是地形,这里其实指的是在高维空间中损失函数的形态】(左边、中间)以及100个样本(右边)的损失函数形态,上述的样本都取自CIFAR-10。左边:只有一个变量 a a a的时候,一维的损失。中间,右边:二维损失的切片,蓝色代表低损失值,红色代表高损失值。注意损失函数的分段线性结构特点。多样本的损失做了平均【原话是用平均的方式混合起来,那不就是平均的意思嘛】,所以右边图中看起来光滑的碗状线条是很多分段线性的碗状线条平均得到的。【其实每个样本的碗状线都是最左侧那种折线段的样式,不是光滑曲线,但是多个样本的折线一座平均就变成了光滑曲线了,大概是这个意思】

我们可以利用数学公式来说明损失函数的分段线性特点,对于单个样本,我们有:
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 W W的线性函数的和(由于max(0,-)这个函数的作用,这些线性函数以0为阈值,也就是线性函数的结果超过0的部分才会被保留下来,小于0当0处理)。 W W W的每一行(也就是 w j w_j wj)当分类分错的时候,其符号是正的,而当它分类分对的时候,其符号是负的【参考:分类正确的时候参数前的符号是负的,分类正确的时候参数前的符号是正的.md】。为了更清楚一点,假设有一个简单的数据集里面包括3个一维的点【样本只有一个维度,样本就分布在一条数轴上】以及三个分类。完整的SVM分类(不包括正则化)为:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ L_0 = & \max(0…

m a x ( 0 , − ) max(0,-) max(0,) 这样函数,它的图形有一个特点,其图形的大概轮廓由“-”决定,其中“-”是一个函数,而“-”图形中大于0的部分会被保留下来,小于0的部分就被删去(所以也被称为被0夹了一下)。在我们举的这个例子中“-”就是 w j T x i − w y i T x i + Δ w_j^T x_i - w_{y_i}^T x_i + \Delta wjTxiwyiTxi+Δ。由于样本是一维的, x i x_i xi和权重 w j w_j wj都是标量,因此 w j T x i − w y i T x i + Δ w_j^T x_i - w_{y_i}^T x_i + \Delta wjTxiwyiTxi+Δ这是一个线性函数,它的图形是一条直线。因此 m a x ( 0 , w j T x i − w y i T x i + Δ ) max(0,w_j^T x_i - w_{y_i}^T x_i + \Delta) max(0,wjTxiwyiTxi+Δ)的图形是,一条直线被X轴截断了,X轴以上的保持不变,X轴下面的图形全部变成0。上面损失公式是由六个 m a x ( 0 , − ) max(0,-) max(0,)类型的项相加构成。因此它的图形是6条折线相加而成,参考下面的图实例,这个图上只展示了3条折线相加。但是6条相加也是类似的。【原话是:因为这些样本是一维的,因此 x i x_i xi和权重 w j w_j wj都是标量。咱们看 w 0 w_0 w0,上面的中的某些项是 w 0 w_0 w0的线性函数,每一个项被0夹了一下。我们可以用下面的方法可视化。这个原话说的太零碎了,所以我按照我的理解重新组织了一下】

在这里插入图片描述
【这张图原来还有一个注解,但是我不想写出来,因为原文写得不好,看了反而会影响理解。为了表示完整性,我在这里写下来,但是可以不用看。原文:数据损失的一维展示。X轴是单个权重,Y轴是损失。数据损失是多个项的和,这些项要么就和某些权重无关,要么就是某些权重的线性组合(同时还以0为阈值)。完整的SVM数据损失是这种形状的30730维的版本。】

顺带一提,你也许从他的这种碗状形态中能够猜出来,SVM的损失函数是一种凸函数【原话是,“SVM的损失函数是一个凸函数的例子”非常的别扭,SVM的损失函数是一个凸函数的例子,这个从逻辑上说是没有问题,但是不太符合中文的习惯,所以我就翻译成,“是一种凸函数”就可以了】。有大量的材料专门讨论如何更有效的对这类函数进行优化,你也可以上一门斯坦福开的关于凸优化的课程【原话是:有大量的材料专门讨论如何更有效的对这类函数进行最小化,把最小化改成了优化】。一旦我们把计分函数f扩展到神经网络,我们的目标函数就变成了非凸函数,这时函数的形状就不是碗型,而是更加复杂的,像崎岖不平的地形一样。
不可微的损失函数。作为技术说明,由于max操作的存在,损失函数具有不可微点(也叫奇异点),正是这些点的存在使得损失函数不可微【原文是:你可以在损失函数上看到有奇异点(由于max操作)技术上使得损失函数不可微,因为这些奇异点上导数不存在】。尽管如此,次梯度还是存在e,而且我们常用它来代替梯度。在这门课里,我们对“梯度”和“次梯度”这两个术语不加区分【原文中的interchangeably 意思是可以互换的,也就是这个词可以替换成词而意思不变】。

三、优化(Optimization)

再次重申,损失函数能够让我衡量一组权重W的好坏【原文还是衡量权重的质量,我觉得还是用“好坏”这个词比较好,好的权重组合能够让损失函数的值更小】。优化的目标是找到一组W让损失函数最小。我们现在开发一种优化损失函数方法并对其不断的进行改进【motivate激励这个词我实在不知道怎么翻译出来,在这个语境里】。对于那些之前有过一定经验的同学来说,这一小节会显得有点奇怪。因为在这一节中我们用来作例子的模型(SVM)是一个凸函数,但是记住我们的最终目标是优化神经网络,而神经网络是无法直接挪用凸优化中的各种工具和方法的。

3.1 策略#1:一种不好的解决方案:随机搜索(A first very bad idea solution: Random search)

由于我们很容易评判一组权重是好还是不好,所以我们最先想到的方法是——随机找很多个点,然后看哪个点对应的值最小【原文:由于衡量一组权重有多好是很容易的,第一个(非常糟糕的)到我们头脑里的想法就是简单是很多个损失的权重并且跟踪哪个工作起来最好】。这个过程看起来如下面这样:

# assume X_train is the data where each column is an example (e.g. 3073 x 50,000)
# assume Y_train are the labels (e.g. 1D array of 50,000)
# assume the function L evaluates the loss function

bestloss = float("inf") # Python assigns the highest possible float value
for num in xrange(1000):
  W = np.random.randn(10, 3073) * 0.0001 # generate random parameters
  loss = L(X_train, Y_train, W) # get the loss over the entire training set
  if loss < bestloss: # keep track of the best solution
    bestloss = loss
    bestW = W
  print 'in attempt %d the loss was %f, best %f' % (num, loss, bestloss)

# prints:
# 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)

【注释:这个方法虽然不好,但是特别的形象,你可以用这样的一种形象的想法,在空间中撒一把石头,每个石头落在空间上的位置,都记录下来,计算哪个位置的值最好,就选他。真的是一个特别简单粗暴但是有效的方法。】
在上面的代码中,我们看到我们尝试了若干随机生成权重向量,而且其中有一部分效果比其它部分好。我们可以从中挑选出最好的那组向量并且在测试集上进行测试:

# Assume X_test is [3073 x 10000], Y_test [10000 x 1]
scores = Wbest.dot(Xte_cols) # 10 x 10000, the class scores for all test examples
# find the index with max score in each column (the predicted class)
Yte_predict = np.argmax(scores, axis = 0)
# and calculate accuracy (fraction of predictions that are correct)
np.mean(Yte_predict == Yte)
# returns 0.1555

通过这种方法得到的最好的权重能够达到的精度是15.5%。比起乱猜来说,这种完全不动脑筋的方法还不赖【注:为啥完全瞎猜的成功率是10%呢?因为一共就10个类别,没有任何先验信息的情况下,我只能认为这10钟类别出现的可能都一样】。

核心思想:迭代优化。 “直接找到”一组最优的权重组合W是非常困难的或者是几乎不可能的(特别对于复杂的神经网络时),但是我们把问题转化为——“通过逐渐的优化”使一组参数变得更好——这个问题就变得容易多了。也就是说,我们的方法是这样——一开始我们随机生成一个W,反复对其优化,让它每一次变得都比上一次更好【注:整段基本就是按照我自己的理解重新叙述了一下。比如我把“Of course, it turns out that we can do much better.”这句话删掉了,因为没啥用。其次,refine愿意是提炼,精练,我这里翻译成优化,因为从形象上来看不是一个去粗取精的过程,它是不断的靠近更好的位置的过程】

我们的策略是:从一个随机的初始值开始,不断的迭代优化,使其得到更小的损失值。

比喻:蒙上双眼的徒步旅行者。有一种比喻能够帮你理解,设想你被蒙上双眼,需要在一片丘陵地带找到最低点。在CIFAR-10的例子中,这个丘陵是30730维的,权重W的维度是10 X 3073。山上的一个点都对应一个损失值(地形的海拔)。

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

也许你想到的第一个策略是,试探地向某一个方向伸出一只脚,如果这个方向是往山下走的,那么就迈下去。具体来说,我们先随机初始化一个权重向量 W W W(相当于一个随机的方向),之后生成一个微小的扰动 δ W \delta W δW,如果在扰动后的位置 W + δ W W + \delta W W+δW的损失值更少一点,那么就更新 W W W,将 W + δ W W + \delta W W+δW替代原有的值【这不就是摸着石头过河嘛】。这个过程的代码如下:

W = np.random.randn(10, 3073) * 0.001 # generate random starting W
bestloss = float("inf")
for i in xrange(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)

利用相同的次数(1000)计算损失函数【原文:使用跟之前计算损失函数次数一样的数字】,通过这种方法在测试集达到的分类精度是21.4%。这好多了,但是,因为计算开销比较大所以比较浪费。

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

在前面的部分,我们试图在权重所在的高维空间里找一个方向,沿着这个方向权重向量能够不断的被优化(得到更小的损失值)。找一个好的方向似乎并不需要随机的搜索:从数学上可以证明,最佳方向就是坡度最陡的方向,至少是从极限角度来说肯定是这样【原话是:我们能够计算出最好的方向,我们应该把权重向量的指向变到沿着这个方向,我们可以从数学上证明这个方向就是朝着最陡峭的方向下降的,至少能从极限的角度上证明,也就是步幅趋近于0的那个小临域内这个方向是最佳的】。这个方向和损失函数的梯度有关。拿徒步旅行者做类比,这种方法大致相当于感觉一下你脚下哪块最陡,然后沿着最陡的方向走。
在一维的函数中,斜率是某一点函数值的瞬时变化率。梯度将函数斜率概念进行了推广,让它的适用范围不仅局限于标量而是扩展到向量。具体点说,梯度仅仅是输入空间各个维度上斜率(更常用称呼是导数)构成的向量。一维函数对它变量的导数的数学表达是是:
d f ( x ) d x = lim ⁡ h   → 0 f ( x + h ) − f ( x ) h \frac{df(x)}{dx} = \lim_{h\ \to 0} \frac{f(x + h) - f(x)}{h} dxdf(x)=h 0limhf(x+h)f(x)
当我们研究的函数有多个自变量的时候,我们称导数为偏导数,梯度就是各个维度的偏导数构成的向量。

四、计算梯度(Computing the gradient)

计算梯度有两种方法:一种是计算起来有点慢,从精确程度来说是一种近似解,但是实现起来比较容易的方法(数值解)。还有一种是计算起来很快,精确度也高,但是很容易出错,而且需要利用微积分(解析解)。我们下面对这两种方法进行介绍:

4.1 利用有限差分方法计算梯度的数值解(Computing the gradient numerically with finite differences)

上面出现过的公式可以计算梯度的数值解。这有一个通用的函数,接受的参数为函数f,向量x,计算f在x上的梯度并把这个结果返回:

def eval_numerical_gradient(f, x):
  """ 
  a naive implementation of numerical gradient of f at x 
  - f should be a function that takes a single argument
  - x is the point (numpy array) to evaluate the gradient at
  """ 

  fx = f(x) # evaluate function value at original point
  grad = np.zeros(x.shape)
  h = 0.00001

  # iterate over all indexes in x
  it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
  while not it.finished:

    # evaluate function at x+h
    ix = it.multi_index
    old_value = x[ix]
    x[ix] = old_value + h # increment by h
    fxh = f(x) # evalute f(x + h)
    x[ix] = old_value # restore to previous value (very important!)

    # compute the partial derivative
    grad[ix] = (fxh - fx) / h # the slope
    it.iternext() # step to next dimension

  return grad

根据我们上面给出的梯度公式,在每一次迭代过程中,在所有的维度上从头到尾对其中的每一个维度做这样的操作:沿着这个维度方向做一个小的变化,通过计算损失函数的变化求得损失函数在这个维度上的偏导数。变量grad存储完整的梯度。

实践中的考量。注意公式中,梯度是定义在 h h h趋近于0的极限中的。但是在实践中用一个非常小的数(比如1e-5)通常就足够了。理想中,你应该在不出现数值问题的情况下使用尽可能小的数。而且在现实中使用中心对称差分公式 [ f ( x + h ) − f ( x − h ) ] / 2 h [f(x+h) - f(x-h)] / 2 h [f(x+h)f(xh)]/2h效果会更好一线。更多的细节参考wiki

我们可以利用上面的公式计算任何函数在任意一点的梯度。让我们来计算CIFAR-10损失函数在权重空间中一些随机点上的梯度:

# to use the generic code above we want a function that takes a single argument
# (the weights in our case) so we close over X_train and Y_train
def CIFAR10_loss_fun(W):
  return L(X_train, Y_train, W)

W = np.random.rand(10, 3073) * 0.001 # random weight vector
df = eval_numerical_gradient(CIFAR10_loss_fun, W) # get the gradient

梯度告诉我们损失函数在各个维度上斜率,利用这点我们可以进行一次更新:

loss_original = CIFAR10_loss_fun(W) # the original loss
print 'original loss: %f' % (loss_original, )

# lets see the effect of multiple step sizes
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 # new position in the weight space
  loss_new = CIFAR10_loss_fun(W_new)
  print 'for step size %f new loss: %f' % (step_size, loss_new)

# prints:
# 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

沿着负梯度方向更新。在上面的代码中,注意到我们计算W_new是沿着梯度df的负方向进行更新,这是因为我希望损失函数值减少而不是增加。

步长效应。梯度告诉我们函数增加最快的方向,但是它并没有告诉我们沿这个方向迈多大的步子。我们在这门课程后面会看到,选择步长(也成为学习率)将会是训练神经网络中最重要的(也是最令人头疼的)参数设置。在“蒙眼下山”这个比喻里,我们感觉到我们脚下的山朝某个方向倾斜,但是我们应该迈多大的步子是不确定的。如果我小心翼翼地滑着地面走我们可以始终保持最好的方向【原文是保持一致性】但是进度非常缓慢(对应于非常小的步子)。相反,如果我们为了下降的更快,如果迈着坚定的大步,但是这不一定就能如我们所愿。你可以的上面的示例代码中看到,在某些点,更大的步子得到的损失值反而更高,这就是“迈大了”。
在这里插入图片描述
步长效应的可视化。我们从某个点W开始,计算它梯度(更准确的说是它的负梯度 - 白色箭头),梯度告诉我们损失函数减小最快的地方。小步伐似乎可以保证稳定【方向的一致】但是进度太缓慢了。大的步伐可以让进度加快但是可能更危险。注意,实际上,大的步伐往往会“走过”,并且得到更不好损失值。步长(或者说学习率)将会成为最重要的超参数,我们需要在上面进行精细的调整。

效率问题。你也许注意到计算梯度的数值解的复杂度和参数的数量是一个线性关系。在我们的例子中,我们总共有30730个参数,因此为了得到梯度我们得执行30731次计算,而这仅仅完成一个参数的更新。由于现代神经网络的参数数量能够轻松破亿,这个问题就变得越来越严重了。很明显,这种策略并不具备良好的扩展性,我们需要更好方式。

4.2 利用微积分计算梯度的解析解(Computing the gradient analytically with Calculus)

梯度的数值解利用有限差分近似很容易求得,但是缺点是不精确(因为我们利用了一个小的h,但是实际上梯度是定义在h趋近于0的极限上的),而且计算开销很大。第二种方法是利用微积分计算梯度的解析解,利用微积分我们可以推导出梯度的准确公式(不是近似公式),这个公式计算起来很快。但是不像梯度的数值解,它实现起来非常易错,这就是为什么在现实中,常见的做法是计算解析解之后和数值解进行对比,来检查实现的正确性。这个叫做梯度检查

在某个数据点上,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} \left[ \max(0, w_j^Tx_i - w_{y_i}^Tx_i + \Delta) \right] 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 \nabla_{w_{y_i}} L_i = - \left( \sum_{j\neq y_i} \mathbb{1}(w_j^Tx_i - w_{y_i}^Tx_i + \Delta > 0) \right) x_i wyiLi=j=yi1(wjTxiwyiTxi+Δ>0)xi

这里 1 \mathbb{1} 1是指示函数,也就是当条件为真的时候函数值为1,否则为0。尽管这个表达式看起来很吓人,但是当你用代码实现它时,你完全可以通过对那些不满足约束条件(也就是没有达到最大边界,因此产生了损失累计)样本进行计数来实现。之后在用这个数乘以 x i x_i xi得到梯度。注意,这个梯度是损失函数相对于权重矩阵中的某一行的梯度。那么到底是权重矩阵中的哪一行呢?我们知道多分类SVM的每一行权重,其实对应了某一个分类的判定边界。所以这一行对应的是 x i x_i xi所属正确分类那个那一行权重。对于其他行(也就是$j \neq y_i $ 的行)来说,梯度为:
∇ w j L i = 1 ( w j T x i − w y i T x i + Δ > 0 ) x i \nabla_{w_j} L_i = \mathbb{1}(w_j^Tx_i - w_{y_i}^Tx_i + \Delta > 0) x_i wjLi=1(wjTxiwyiTxi+Δ>0)xi

当你推导出梯度的表达式,就可以直接把这个表达式实现,并且利用它对梯度进行更新【对于一些简单函数,这样确实是一个不错的方法,如果存在解析解何必也数值解,如果存在确定值何必用概率】。

五、梯度下降(Gradient Descent)

现在我们可以计算损失函数的梯度,反复计算梯度以及更新参数的过程称之为梯度下降,“串行版(vanilla)”的梯度下降是这样的:

# Vanilla Gradient Descent

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

这个简单的循环是所有神经网络库函数的核心。实现优化也有其它的方法(比如:LBFGS),但是梯度下降是迄今为止普及最为广泛而且最为深入人心的方法,特别是在神经网络的优化方面。在整门课程当中,我们会以这个循环为基本框架不断的填充新的东西进去(这些不断填充的东西,可以认为是对原有实现的改进和优化,比如更新公式就是不断再变化的,动量方法,adam方法之类的),但是核心思想——也就是跟随梯度直到获得我们满意的结果——这个是不变的【原话是:我们会对循环中的一些细节增加一些花里胡哨的东西(比如 更新公式的具体细节)】。

小批量梯度下降。在大规模数据量的应用中(比如 ILSVRC 挑战赛),训练数据中差不多有上百万个样本。为了仅仅对其中的一个参数进行更新,就要在全部的训练数据集上计算梯度【原文是损失函数,应该是梯度】,这种做法实在是太费事了。既然在全部的样本上都跑一遍实在太费事了,你自然而然就能想到能不能只跑一部分【注,这部分是我自己加上去的】?对,解决这个问题的一个常见方法是,只在训练集上的一小部分上计算梯度。这个一小部分叫做“批量”(batch)。比如,在最新的卷积网络中,一个典型的批量大小包含256个样本,而全部的样本则有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的数据集是这样来的,一开始有1000张图片,这1000张图片之间相互独立,也就是说这1000张图片中的每一张图片都和1000张里的其它图片不同,之后每一张图片复制1200份,最后就得到了120万张图片(当然你也可以,有些图片复制100份,有些图片复制2300份,总之凑够120万张就行)。我们还是以每一个图片都有1200个副本为例,很明显,在这种情况下,我们计算的梯度在这1200个图片上的结果都一样。也就是说,我们在120万张图片上计算损失值,其实和在原始的1000张图片上计算的损失值一样。当然在现实中,数据集不会有那么多重复,所以说了是一种极端的假设。但是小批量上得到的梯度确实可以看成是整体数据集上得到的梯度值的一个不错的近似。此外,由于在小批量数据上计算的梯度能够更快的收敛,参数更新得以更快的更新。
【原文:这种方法之所以效果不错是因为训练中的样本之间是相关的。120万张图片实际上是仅仅1000个张唯一的图片重复组成的(1000张图片中一个是一类,或者说每一张图片有1200个一样的副本)。然后很明显我们需要计算的梯度在这1200张一样的副本上会得到一样的结果,并且当我们讲数据损失在120万张图片上进行平均,我们得到的损失值和我们在1000张图片上计算出来的值是一样的。当然在现实当中,数据集中不会有那么多重复的图片,在小批量上的梯度是在整个数据集上梯度的一个很好的近似。因此,在小批量上计算梯度能够更快的收敛,这样可以让参数的更新速度更快。】
小批量下降的一种极端情况是,这个批量里只包含一个样本。这个时候这个过程就退化为随机梯度下降(SGD)(有的时候也被称为在线梯度下降)。这种情况很少见。这是因为在实践中,为了计算得更快,代码在实现都是以向量为单位进行运算的,这个实现的方法叫做“向量化”【参考:Vectorization】。因此在100个不同的样本上计算一次要比在同一个样本上计算一次要快。可以说SGD是MGD的一种特殊情况,所以有的时候人们虽然在说SGD,但是实际上他指的是MGD。尽管批次包含样本的个数是一个超参数,但是我们一般不对它进行交叉验证。批次设置为多大一般取决于内存的大小(如果有的话)。一般设置为2的n次方,比如32、64、128之类的。之所以设置成这样,也是因为向量化的原因。
【原文:上述的极端情况的一种设置是小批量里其实只有一个单一的样本。这个过程被称为随机梯度下降(SGD)(有的时候也被称为在线梯度下降)。这种相对来说不是那么常见,因为在实践当中,为了优化计算过程,我们代码都实现了向量化,因此比起在同一个样本上计算100次,可能在100个不同的样本上计算一次来得更快一点。尽管SGD代表了这样一种技术:计算梯度的时候每次用一个样本,但是人们有的时候会用SGD的术语即使他们其实说的是小批量梯度下降(MGD代表小批量梯度下降,BGD代表批量梯度下降)。小批量的大小是一个超参数,但是并不对它进行交叉验证。它主要基于内存的限制(如果有的话),或者设置一些值,比如32,64,128.我一般用以2为底的指数,这是因为代码向量化的原因,输入大小是2的n次方会比较快。】

六、总结(Summary)

在这里插入图片描述
信息流汇总。数据集(x,y)是给定的【给定就是given and fixed的意思,也就是已知并且固定的意思】。权重一开始是随机数而且是可以改变的。在前向传播过程中,计分函数计算各个类别上分数,存储在向量f中。损失函数包括两个部分:数据损失衡量得分f和标签y之间的匹配程度。正则化损失仅仅是权重的函数。在梯度下降过程中,我们计算权重的梯度(如果你愿意也可以计算数据上的梯度)并利用梯度对参数进行更新。

在这一节中:

  • 我们对损失函数建立了这样一个直观的印象。我们把损失看成高维空间中的地形,并且我们希望到达这个地形的底部。我们拿蒙眼的徒步者作为类别描述了寻找底部的工作机制。我们看到SVM的损失函数是分段线性以及碗状的。
  • 我们得到一个优化函数的灵感:迭代优化。在这个过程中,我们的权重从一组随机数开始,不断的对其优化,直到损失值最小。
  • 我们看到函数的梯度能够指出最陡的下降方向。我们讨论了一个简单但是低效的计算梯度的方法,也就是通过有限差分计算梯度的数值解(有限差分指的就是求梯度的数值解的计算公式中用到的那个“h”)
  • 我们看到在参数更新的时候,需要精心的设置步长大小(或者叫学习率)。如果步长设置的太小,进度虽然很稳定但是很慢,如果设置的太大,进度虽然快但是容易“越过”。在后续的章节中,我们会继续深入研究如何权衡这两者之间的关系【直译是:在未来的章节中我们会继续探索权衡】。
  • 我们比较了计算梯度的数值解和解析解的各自的优劣。【直译:我们讨论了计算数值和解析梯度之间的权衡】。数值解的求解过程比较简单,但是不精确而且计算开销大。解析解的求解过程快,结果也精确但是非常容易出错,因为需要用到求导。因此在实践中我们主要是用数值解的方法作为求梯度的主要方法,而用解析解来进行梯度检查
  • 我们介绍了梯度下降这个算法。它的核心步骤是计算梯度之后更新参数,之后不断的重复这个过程。【直译:重复地在循环中计算梯度以及更新参数】

接下来讨论的是:这一节的核心要点是:知道怎么计算梯度对于理解、设计和训练神经网络来说非常重要【这一节的核心要点是计算损失函数对权重的梯度的能力是最重要的技能对于理解、设计和训练神经网络来说】。在下一节中,我们将介绍一种方法,通过它我们可以快速的得到梯度的解析解,这种方法被称为反向传播,它用到了链式法则【直译:我们将用链式法则开发在计算梯度解析解时的熟练技术】。它能够让我们非常有效的优化几乎任意形式的损失函数,包括卷积神经网络【原文是表表达所有类型神经网络的损失函数,或者表达任意类型神经网络的损失函数,为了中文说起来通顺,把神经网络略去了】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值