Deep learning系列(十)随机梯度下降

1. 梯度下降

梯度下降是常用的神经网络模型参数求解方法,根据每次参数更新使用样本数量的多少,可以分为以下三类:

  • 批量梯度下降(batch gradient descent);
  • 小批量梯度下降(mini-batch gradient descent);
  • 随机梯度下降(stochastic gradient descent);

批量梯度下降计算全部训练集样本梯度的平均,然后更新梯度,伪码如下:

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

对于大规模数据库来说,比如ILSVRC,可以包含几百万个样本,若采用批量梯度下降的话,需要计算几百万次梯度计算,然后计算平均,才能更新一次参数,无论从时间效率还是内存占用来说都是不可取的,一种常用的做法将训练数据分批(batch)进行训练,称为小批量梯度下降。比如对于卷积神经网络来说,每次在训练集中选择包含256个样本的一批数据,然后使用这批数据计算梯度,完成参数更新,代码如下:

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

通常来说,通过小批量样本计算出来的梯度是对使用整个训练集计算出来的梯度一个非常好的近似,使用小批量梯度下降,可以极大地提高算法收敛速度。

小批量梯度下降的极端是每批数据只包含一个样本,这时算法称为随机梯度下降,也称为在线梯度下降(on-line gradient descent)。其伪码为:

while True:
  data_batch = sample_training_data(data, 1) # use a single example
  weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
  weights += - step_size * weights_grad # perform parameter update

在神经网络训练过程中,很少使用随机梯度下降,因为小批量梯度下降可以利用矩阵和向量计算进行加速。通常我们讲随机梯度下降(SGD)时,也可以指代小批量梯度下降。

2. 随机梯度下降加速

如果把要优化的目标函数看成山谷的话,可以把要优化的参数看成滚下山的石头,参数随机化为一个随机数可以看做在山谷的某个位置以0速度开始往下滚。目标函数的梯度可以看做给石头施加的力,由力学定律知: F=ma ,所以梯度与石头下滚的加速度成正比。因而,梯度直接影响速度,速度的累加得到石头的位置,对这个物理过程进行建模,可以得到参数更新过程为:

# Momentum update
v = momentum * v - learning_rate * dx # integrate velocity
x += v # integrate position

代码中v指代速度,其计算过程中有一个超参数momentum,称为动量(momentum)。虽然名字为动量,其物理意义更接近于摩擦,其可以降低速度值,降低了系统的动能,防止石头在山谷的最底部不能停止情况的发生。动量的取值范围通常为[0.5, 0.9, 0.95, 0.99],一种常见的做法是在迭代开始时将其设为0.5,在一定的迭代次数(epoch)后,将其值更新为0.99。

在实践中,一般采用SGD+momentum的配置,相比普通的SGD方法,这种配置通常能极大地加快收敛速度。

3. 学习率的更新

在算法迭代过程中逐步降低学习率(step_size)通常可以加快算法的收敛速度。常用的用来更新学习率的方法有三种:

  • 逐步降低(Step decay),即经过一定迭代次数后将学习率乘以一个小的衰减因子。典型的做法包括经过5次迭代(epoch)后学习率乘以0.5,或者20次迭代后乘以0.1。
  • 指数衰减(Exponential decay),其数学表达式可以表示为: α=α0ekt ,其中, α0 k 是需要设置的超参数, t 是迭代次数。
  • 倒数衰减(1/t decay),其数学表达式可以表示为: α=α0/(1+kt) ,其中, α0 k 是需要设置的超参数, t 是迭代次数。

实践中发现逐步衰减的效果优于另外两种方法,一方面在于其需要设置的超参数数量少,另一方面其可解释性也强于另两种方法。

4. 二阶更新方法

提升梯度下降法收敛速度的方法还包括将其由一阶提升为二阶,也就是牛顿法或者拟牛顿法,比如常用的 L-BFGS。然而,牛顿法和L-BFGS不适用于解决大规模训练数据集和大规模问题。

比如,常用的深度网络包括数百万个参数,每次迭代都要计算大小为 [1,000,000 x 1,000,000] 的Hessian矩阵,需要占用3G多的内存,严重影响了计算效率。

L-BFGS法不需要计算完全的Hessian矩阵,虽然没有了内存的担忧,但这种方法通常类似于批量梯度下降法,需要在计算整个训练集(通常为几百万个样本)的梯度后才能更新一次参数,严重影响了收敛速度。因而在深度神经网络领域很少使用L-BFGS来优化目标函数。

5. SGD+momentum的代码

参考的SGD+momentum的代码是Andrew Ng的深度学习教程给出的matlab代码。

function [opttheta] = minFuncSGD(funObj,theta,data,labels,options)
% Runs stochastic gradient descent with momentum to optimize the
% parameters for the given objective.
%
% Parameters:
%  funObj     -  function handle which accepts as input theta,
%                data, labels and returns cost and gradient w.r.t
%                to theta.
%  theta      -  unrolled parameter vector
%  data       -  stores data in m x n x numExamples tensor
%  labels     -  corresponding labels in numExamples x 1 vector
%  options    -  struct to store specific options for optimization
%
% Returns:
%  opttheta   -  optimized parameter vector
%
% Options (* required)
%  epochs*     - number of epochs through data
%  alpha*      - initial learning rate
%  minibatch*  - size of minibatch
%  momentum    - momentum constant, defualts to 0.9

%%======================================================================
%% Setup
assert(all(isfield(options,{'epochs','alpha','minibatch'})),'Some options not defined');
if ~isfield(options,'momentum')
    options.momentum = 0.9;
end;
epochs = options.epochs;
alpha = options.alpha;
minibatch = options.minibatch;
m = length(labels); % training set size
% Setup for momentum
mom = 0.5;
momIncrease = 20;
velocity = zeros(size(theta));

%%======================================================================
%% SGD loop
it = 0;
for e = 1:epochs
    % randomly permute indices of data for quick minibatch sampling
    rp = randperm(m);
    for s=1:minibatch:(m-minibatch+1)
        it = it + 1;
        % increase momentum after momIncrease iterations
        if it == momIncrease
            mom = options.momentum;
        end;
        % get next randomly selected minibatch
        mb_data = data(:,:,rp(s:s+minibatch-1));
        mb_labels = labels(rp(s:s+minibatch-1));
        % evaluate the objective function on the next minibatch
        [cost grad] = funObj(theta,mb_data,mb_labels);
        % update the current weights theta 
        velocity = mom*velocity+alpha*grad; 
        theta = theta-velocity;
        fprintf('Epoch %d: Cost on iteration %d is %f\n',e,it,cost);
    end;
    % aneal learning rate by factor of two after each epoch
    alpha = alpha/2.0;
end;
opttheta = theta;
end

动量mom在迭代开始时将其设为0.5,在20次迭代(epoch)后,其值更新为0.99。
学习率的更新采用逐步降低(Step decay)法,即每次迭代(epoch)后将学习率乘以0.5。

参考资料:
1. http://cs231n.stanford.edu/syllabus.html
2. http://ufldl.stanford.edu/tutorial/
3. Advances in optimizing Recurrent Networks by Yoshua Bengio, Section 3.5
4. SGD tips and tricks from Leon Bottou
5. Efficient BackProp (pdf) from Yann LeCun
6. Practical Recommendations for Gradient-Based Training of Deep Architectures from Yoshua Bengio

你可能感兴趣的:(deep-learning)