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

qnjw8186 8年前發布 | 49K 次閱讀 梯度下降法 神經網絡

來自: 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
"""</pre>

梯度下降法中計算 $\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
"""</pre>

可以看出,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
--]]</pre>

Torch 的 Neural Network Package

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

ML , torch , Note , Python

- END -

 本文由用戶 qnjw8186 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!