神經網絡模型隨機梯度下降法—簡單實現與Torch應用

備份自:http://blog.rainy.im/2016/02/26/sgd-with-python-and-torch/

About

本文以及后續關于 Torch 應用及機器學習相關的筆記文章,均基于牛津大學2015機器學習課程,課件和視頻可從官網下載。本文主要關于神經網絡模型中的隨機梯度下降法,介紹其原理及推導過程,并比較 Python 簡單實現和 Torch 的應用。對應課件為L2-Linear-prediction.ipynb

梯度下降法(gradient descent)

為了確定神經網絡模型中參數(權值)的好壞,需要先指定一個衡量標準(訓練誤差,損失函數,目標函數),例如以均方差(Mean Square Error, MSE)公式作為損失函數:

$$J(\mathbf{\theta}) = MSE = \frac{1}{n} \sum_{i = 1}^n(\widehat{\mathbf{Y_i}} - \mathbf{Y_i})^2$$

其中,$\widehat{y_i} = \sum_{j = 1}^d x_{ij}\theta_j$,矩陣表示法為$\widehat{\mathbf{Y}} = \mathbf{X}\theta$,為線性模型(神經網絡)擬合結果。

模型最優化實際上是最小化損失函數的過程,梯度下降法的原理是:

若函數 $F(x)$ 在點 $a$ 可微且有定義,則 $F(x)$ 在 $a$ 點沿著梯度相反方向 $-\nabla F(a)$ 下降最快。梯度下降法 - 維基百科

損失函數 $J$ 對于權重向量 $\mathbf{\theta}$ 的梯度(gradient):

$$\nabla J(\mathbf{\theta}) = [\frac{\partial J}{\partial \theta_0}, \frac{\partial J}{\partial \theta_1}, ..., \frac{\partial J}{\partial \theta_n}]$$

則根據梯度下降法則,參數的變化應根據:

$$\Delta \theta_i = -\alpha\frac{\partial J}{\partial \theta_i}$$

其中 $\alpha$ 為學習速率(Learning Rate)。由此可得梯度下降算法如下:

  • GD(training_examples, alpha)
    • training_examples 是訓練集合,$\lt \vec{inputs}, output \gt$
    • 初始化每個權值 $\theta_i$ 為隨機值
      • 終止條件前,迭代:
        • 初始化權值的變化梯度 $\Delta\theta_i = 0$
        • 對每條訓練數據:
          • 根據 $\vec{input}$ 計算模型輸出為 o
          • 對每個權值梯度 $\Delta \theta_i$:
            • $\Delta \theta_i = \Delta \theta_i + \alpha (output - o) * x_i$ ==>(A
        • 對每個權值 $\theta_i$:
          • $\theta_i = \theta_i + \Delta \theta_i$ ==>(B

根據算法描述可以簡單實現(完整代碼):

def GD(training_examples, alpha):
    # init thetas
    thetas = np.random.rand(NPAMATERS)
    for i in range(LOOPS):
        deltas = np.zeros(NPAMATERS)
        for record in training_examples:
            inputs = [1] + list(record[1:])
            output = record[0]
            o = NN(inputs, thetas)
            for j in range(NPAMATERS):
                # -- Step (A
                deltas[j] = deltas[j] + alpha * (output - o) * inputs[j]
        for j in range(NPAMATERS):
            # -- Step (B
            thetas[j] = thetas[j] + deltas[j]
    return thetas
thetas = GD(training_examples, 0.00001)
test(thetas, training_examples)
"""
#No Target  Prediction
0   40  20.55
1   44  37.96
2   46  44.42
3   48  48.66
4   52  52.89
5   58  54.89
6   60  67.83
7   68  63.13
8   74  69.59
9   80  89.00
"""

梯度下降法中計算 $\Delta \theta_i$ 時匯總了所有訓練樣本數據的誤差,在實踐過程中可能出現以下問題:

  1. 收斂過慢
  2. 可能停留在局部最小值

需要注意的是,學習速率的選擇很重要,$\alpha$ 越小相當于沿梯度下降的步子越小。很顯然,步子越小,到達最低點所需要迭代的次數就越多,或者說收斂越慢;但步子太大,又容易錯過最低點,走向發散的高地。在我寫的這一個簡單實現的測試中,取 $\alpha = 1e-3$ 時導致無法收斂,而取 $\alpha = 1e-5$ 時可收斂,但下降速度肯定更慢。

常見的改進方案是隨機梯度下降法(stochatic gradient descent procedure, SGD),SGD 的原理是根據每個單獨的訓練樣本的誤差對權值進行更新,針對上面的算法描述,刪除 $(B$,將$(A$ 更新為:

$$\theta_i = \theta_i + \alpha (output - o) * x_i$$

代碼如下:

def SGD(training_examples, alpha):
    # init thetas
    thetas = np.random.rand(NPAMATERS)
    for i in range(LOOPS):
        for record in training_examples:
            inputs = [1] + list(record[1:])
            output = record[0]
            o = NN(inputs, thetas)
            for j in range(NPAMATERS):
                thetas[j] = thetas[j] + alpha * (output - o) * inputs[j]
    return thetas
thetas = SGD(training_examples, 0.001)
test(thetas, training_examples)
"""
#No Target  Prediction
0   40  41.45
1   44  42.71
2   46  44.82
3   48  48.42
4   52  52.02
5   58  57.11
6   60  61.34
7   68  70.88
8   74  72.99
9   80  79.33
"""

可以看出,SGD 可以用較大的 $\alpha$ 獲得較好的優化結果。

Torch的應用

清楚了 SGD 的原理后,再來應用 Torch 框架完成上上述過程,其中神經網絡模型的框架由torch/nn提供。

require 'torch'
require 'optim'
require 'nn'

model = nn.Sequential()                 -- 定義容器
ninputs = 2; noutputs = 1
model:add(nn.Linear(ninputs, noutputs)) -- 向容器中添加一個組塊(層),本例中只有一個組塊。
  
criterion = nn.MSECriterion()

-- 獲取初始化參數
x, dl_dx = model:getParameters()
-- print(help(model.getParameters))
--[[
[flatParameters, flatGradParameters] getParameters()
  返回兩組參數,flatParameters 學習參數(flattened learnable
parameters);flatGradParameters 梯度參數(gradients of the energy
wrt)
]]--

feval = function(x_new)
  -- 用于SGD求值函數
  -- 輸入:設定權值
  -- 輸出:損失函數在該訓練樣本點上的損失 loss_x,
  --       損失函數在該訓練樣本點上的梯度值 dl_dx
  if x ~= x_new then
    x:copy(x_new)
  end
  -- 每次調用 feval 都選擇新的訓練樣本
  _nidx_ = (_nidx_ or 0) + 1
  if _nidx_ > (#data)[1] then _nidx_ = 1 end
  
  local sample = data[_nidx_]
  local target = sample[{ {1} }]
  local inputs = sample[{ {2, 3} }]
  dl_dx:zero() -- 每次訓練新樣本時都重置dl_dx為0
  
  local loss_x = criterion:forward(model:forward(inputs), target))
  -- print(help(model.forward))
  --[[
  [output] forward(input)
    接收 input 作為參數,返回經該模型計算得到的 output,調用 forward() 方法后,模型的 output 狀態更新。
  ]]--
  -- print(help(criterion.forward))
  --[[
  [output] forward(input, target)
    給定 input 和(要擬合的)目標 target,根據損失函數公式求出損失值。
    狀態變量 self.output 會更新。
  --]]
  model:backward(inputs, criterion:backward(model.output, target))
  -- print(help(criterion.backward))
  --[[
  [gradInput] backward(input, target)
    給定 input 和(要擬合的)目標 target,根據損失函數公式求出梯度值。
    狀態變量 self.gradInput 會更新。
  --]]
  -- @ https://github.com/torch/nn/blob/948ac6a26cc6c2812e04718911bca9a4b641020e/doc/module.md#nn.Module.backward
  --[[
  [gradInput] backward(input, gradOutput)
    調用下面兩個函數:
      1. updateGradInput(input, gradOutput)
      2. accGradParameters(input, gradOutput, scale)
  --]]
  return loss_x, dl_dx
end
-- 設置 SGD 算法所需參數
sgd_params = {
  learningRate = 1e-3,
  learningRateDecay = 1e-4,
  weightDecay = 0,
  momentum = 0
}

for i = 1, 1e4 do
  for i = 1, (#data)[1] do
    -- optim.sgd@https://github.com/torch/optim/blob/master/sgd.lua
    _, fs = optim.sgd(feval, x, sgd_params)
  end
end

-- Test
test = {40.32, 42.92, 45.33, 48.85, 52.37, 57, 61.82, 69.78, 72.19, 79.42}
print('id\tapprox\ttext')
for i = 1, (#data)[1] do
    local myPrediction = model:forward(data[i][{{2,3}}])
    print(string.format("%2d\t%.2f\t%.2f", i, myPrediction[1], test[i]))
end
--[[
id  approx  text    
 1  40.10   40.32   
 2  42.77   42.92   
 3  45.22   45.33   
 4  48.78   48.85   
 5  52.34   52.37   
 6  57.02   57.00   
 7  61.92   61.82   
 8  69.95   69.78   
 9  72.40   72.19   
10  79.74   79.42   
--]]

Torch 的 Neural Network Package

關于 Torch 的 Neural Network Package 在 GitHub 上有更詳細的文檔介紹,這里暫時不作深入學習,根據后續課程進度再做補充。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容