斯坦福大学卷积神经网络----Module 1 Lesson 3 优化

原文地址:http://cs231n.github.io/optimization-1/
标题:Optimization: Stochastic Gradient Descent
随手翻译,不当之处请指正

介绍

前面的章节中我们介绍了图像分类任务的两个关键部分:

  1. 将原始图像映射成一系列类别得分的(参数化的)评分函数(例如线性函数)。
  2. 基于预测值与真实标签的吻合程度而衡量一系列参数的损失函数。损失函数有非常多种(比如SVM、Softmax)。

线性方程的形式为 f(xi,W)=Wxi ,我们公式化的SVM分类器为:

L=1Nijyi[max(0,f(xi;W)jf(xi;W)yi+1)]+αR(W)

我们注意到,设置的 W 若产生了与真实值不相符的预测值也可以产生一些很小的损失L。我们现在开始介绍第三个部分: 优化。优化就是寻找使得最终损失最小的一组权重 W

伏笔: 一旦我们了解了这三个部分之间如何交互,我们就会回到第一个部分(参数化的映射函数),将它扩展为一个比线性映射复杂的多的函数。首先是完整的神经网络,然后是卷积神经网络。而损失函数和优化过程则相对的保持不变。

将损失函数可视化

我们将要看到的损失函数一般都存在在高维空间中(比如CIFAR-10数据集中,一个线性分类器权重矩阵有[10x3073],也就是30730个参数),这使它非常难以可视化地表达出来。但是我们仍旧可以通过将高维空间限制(映射)在一维数轴或者二维平面中得到一些启发。比如,我们随机生成一个权重矩阵W(代表高维空间中的一个点),然后沿着数轴前进,记录损失函数的值。我们可以生成一个随机方向 W1 ,根据不同的 a 值用L(W+aW1)沿着这个方向计算损失。这个过程生成了一个简单的以 a 值为x轴、以损失值为y轴的简单的图。或者我们也可以将这个过程展现在二维空间中,也就是计算L(W+aW1+bW2),也就是改变 a,b 。这个时候, a,b 分别依赖x,y轴,而损失函数的值则用颜色表达。


这里写图片描述这里写图片描述这里写图片描述
CIFAR-10数据集中,多类SVM分类器(未正则化)损失函数的可视化结果,左边和中间是一个样本的结果,右边是100个样本的结果。左:只刻画a的一维损失。中,右:二维损失,蓝色代表数值低,红色代表数值高。注意到损失函数的逐段线性结构。多样本的损失以平均值的形式叠加在一起,所以右图中的碗形是许多逐段线性碗形(中)平均叠加而成的。


我们通过数学方法理解一下逐段线性的损失函数。对于一个样本的损失,我们有:

Li=jyi[max(0,wTjxiwTyixi+1)]

很显然,某单个样本是一系列 W 中权值的线性函数的和(零阈值是因为max(0,)函数的钳位作用)。还有, W 的每一行wj前面有点时候会有一个负号(代表正确的分类),有的时候回有一个正号(代表错误的分类)。为了能讲的更清楚,我们假设有一个数据集,包扩三个一维向量以及三个分类。完整的SVM损失(未正则化)表达式如下:
L0=max(0,wT1x0wT0x0+1)+max(0,wT2x0wT0x0+1)

L1=max(0,wT0x1wT1x1+1)+max(0,wT2x1wT1x1+1)

L2=max(0,wT0x2wT2x2+1)+max(0,wT1x2wT2x2+1)

L=(L0+L1+L2)/3

由于这些样例都是一维的,数据 xi 和权值 wj 都是数字。 w0 线性表达式都被钳位在0。


这里写图片描述
一维数据损失图例。x轴是简单的权重,y轴是损失。数据损失是多个表达式的和,每个表达式不是独立的特定的权重,就是被钳位在0的线性函数。完整的SVM数据损失是上述图像的30730维版本


从这个碗形的图像可以猜测得到,SVM损失函数(这里原文为cost function)是凸函数。大量的文献致力于高效地寻找这类函数的最小值,你也可以上一门叫做凸优化的斯坦福课程。一旦我们将评分函数 f 扩展到神经网络,我们的目标函数变得非凸,而可视化的结果并不是碗形,而是复杂且崎岖的。

不可微分损失函数 由于max函数的缘故,损失函数中存在一些转折点,这些转折点不可微分,因为这种地方没有梯度的定义。然而次梯度仍然存在且我们比较常用。这节课里我们会交替使用梯度和次梯度。

优化

再次强调,损失函数允许我们对任意权重集W量化以及评价。优化的目的是找到让损失函数最小的权重集 W 。现在我们慢慢地开始“开发”优化损失函数的方法。在座的如果有些经验,会感觉非常古怪,我们使用的损失(SVM损失)是一个凸问题。但是注意的是,我们最后希望优化神经网络,而不可能简单的使用我们讲述的凸函数优化方法。

战略1:第一个不成熟的想法:随机搜索

由于检查一个参数集W到底好不好是很简单的,浮现在脑海中的第一个想法就是随机地尝试不同的权重,跟踪一下哪个权重集表现最好,这个过程如下:

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

从上面的代码中看出,我们尝试了几个随机的权重向量 W ,其中一些比另一些表现的好。我们把这几次尝试中最好的权重W取出来,放在测试集里:

# 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

表现最好的权重 W 最终给出的精度为大约15.5%。考虑到如果纯随机猜测的话,精度大约是10%,这种方法居然比死脑筋的随机略胜一筹!

核心思想:迭代求精。当然,事实表明我们完全可以做的更好。核心思想就是寻找最好的权重W是非常困难,甚至不可能的问题(特别是当 W 包括了整个复杂神经网络的时候),但是让具体的权重W稍微优化一些的求精问题就明显简单很多。也就是说,我们的方法从一个随机的权重 W 开始,迭代地对其求精,每次让他的表现稍微好一点。

我们的策略是从随机权重开始,迭代的对其求精,以使得损失降低

被蒙上眼的远足者(这翻译好愚蠢,原谅)。一个可能帮助理解的比喻就是把自己想象成一个被蒙上眼的远足者,在丘陵地带远足,并且想到达最低的地方。在CIFAR-10的例子中,这些“丘陵”是30730维的,丘陵上每个点都代表着一个特定的损失。

策略2:随机本地(邻近)搜索

第一个策略可以理解成朝随机方向迈一脚,但只有海拔会降低的时候才前进。我们从随机生成的W开始,生成随机扰动 δ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:跟随梯度(的方向)

前节我们尝试找到权重空间中的一个方向,希望可以优化我们的权重向量(并且给出更低的损失)。事实上我们不需要随机搜索好的方向:我们可以沿着数学证明的下降最快的方向调整我们的权重向量。这个方向与损失函数的梯度相关。在我们的比喻中,这种方法类似沿着斜坡向下,也就是找到下降最快的方法。

在一维函数中,这个倾斜就是任何一点的斜率。而梯度下降就是将一个数字的输入泛化到以一个向量输入函数中。另外,梯度就是一个矢量,输入空间中每个维度对应一个倾斜(度)(也就是导数)。一维导数定义:

df(x)dx=limh0f(x+h)f(x)h

当我们感兴趣的函数输入变成向量而不是一个数字,我们就把导数称为偏导数,梯度就是每个维度里的偏导向量。

计算梯度

计算梯度有两种方法:一个非常慢、近似计算但是很简单的方法(数值梯度),和一个速度快,精确,但是易错的微分方法(分析梯度)。两这里种方法都会提到。

有限差分梯度数值计算

上面给的式子可以计算数值梯度。这里是一个简单的输入函数f,向量x计算梯度,并返回fx处梯度的函数:

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

上面的代码在所有维度上迭代,改变量为h,通过函数值改变大小,计算该维度上损失函数的偏导数。变量grad最终保存了所有梯度。

实际操作中的思考 注意到在数学表达式中,梯度的定义限制在h趋近0的情况下,但是实际上一个非常小的值就已经很充分了(比如例中的1e-5)。理想的情况下,应该使用不导致数值问题的最小步长。另外,在实际中一般用中心差分公式 [f(x+h)f(xh)]/2h 计算梯度。

上面的函数可以在任何点计算任何函数的梯度。下面在权重空间里计算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

副梯度方向更新 上面的代码中我们用梯度df的负值更新W_new值,因为我们希望我们的损失函数下降,而不是上升。

步长的影响 梯度告诉我们哪个方向上上升最快,但是没有告诉我们要前进多长的距离。我们后面会看到选择步长(也叫学习率)会是训练神经网络中一个非常重要的(也是非常令人头疼的)超参数设置。在我们的比喻中,方向使我们要考虑的,但是步长是不确定的。如果小心地随机下脚我们会有连续的但是很微小的进步(因为步长很小)。相反地,可以选择大的步长,自信可以让梯度下降得更快,但是这可能适得其反。像上面看到的例子,有些地方我们选择了较大的步长,损失也会增大,这称作“overstep”。


这里写图片描述
可视化的步长影响。我们从 W 的特定点开始评估告诉我们损失函数下降最快的方向梯度(或者说是梯度的负,也就是白色的箭头)。小步长可以获得连续的,较小的进步。大步长可能过程比较好,但是有风险。大步长可能越过了下降,让损失更差了。步长(后面称为学习率)会成为后面调整超参数最重要的一环之一。


效率问题 你可能注意到,评估数值梯度复杂度与参数数目线性相关。在例子中我们有30370个参数,因此更新一个参数时我们需要进行30731次损失函数梯度评估。这个问题可能更严重,因为现代神经网络随随便便就达到几千万参数。显然,这个东西并不好,我们需要更好的办法。

用微分方法计算分析梯度

数值梯度可以通过差分逼近的方法很简单地实现,但是坏处就是近似(因为我们必须选择一个非常小的值h,然而真正的梯度定义是h趋向于0),并且计算成本非常高。第二种方法是使用微分方法计算分析梯度,我们可以为梯度导出一个粗暴的方程,计算起来非常快。但是不像数值梯度,它实现起来很容易出错。所以在实际运作中,常常先计算分析梯度,再使用数值梯度检查是否正确实现。这就是叫梯度检查

我们对一个数据点使用SVM损失函数做例子:

Li=jyi[max(0,wTjxiwTyixi+Δ)]

我们可以区别这个函数和权重。例如,将梯度与 wyi 放在一起,我们得到:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值