1. 程式人生 > >Deep learning系列(十)隨機梯度下降

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,其中,α0k是需要設定的超引數,t是迭代次數。
  • 倒數衰減(1/t decay),其數學表示式可以表示為:α=α0/(1+kt),其中,α0k是需要設定的超引數,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。