基於DL的計算機視覺(4)-- SGD

1. 引言

上一節深度學習與計算機視覺系列(3)_線性SVM與SoftMax分類器中提到兩個對圖像識別至關重要的概念:

  1. 用於把原始像素信息映射到不同類別得分的得分函數/score function
  2. 用於評估參數W效果(評估該參數下每類得分和實際得分的吻合度)的損失函數/loss function

其中對於線性SVM,我們有:

  1. 得分函數f(xi,W)=Wxi
  2. 損失函數L=1Nijyi[max(0,f(xi;W)jf(xi;W)yi+1)]+αR(W)

在取到合適的參數W的情況下,我們根據原始像素計算得到的預測結果和實際結果吻合度非常高,這時候損失函數得到的值就很小。

這節我們就講講,怎麼得到這個合適的參數W,使得損失函數取值最小化。也就是最優化的過程。

2. 損失函數可視化

我們在計算機視覺中看到的損失函數,通常都是定義在非常高維的空間裏的(比如CIFAR-10的例子裏一個線性分類器的權重矩陣W是10 x 3073維的,總共有30730個參數 -_-||),人要直接『看到』它的形狀/變化是非常困難的。但是機智的同學們,總是能想出一些辦法,把損失函數在某種程度上可視化的。比如說,我們可以把高維投射到一個向量/方向(1維)或者一個面(2維)上,從而能直觀地『觀察』到一些變化。

舉個例子說,我們可以對一個權重矩陣W(例如CIFAR-10中是30730個參數),可以找到W維度空間中的一條直線,然後沿着這條線,計算一下損失函數值的變化情況。具體一點說,就是我們找到一個向量W1(維度要和W一樣,這樣W1才能表示W的維度空間的一個方向),然後我們給不同的a值,計算L(W+aW1),這樣,如果a取得足夠密,其實我們就能夠在一定程度上描繪出損失函數沿着這個方向的變化了。

同樣,如果我們給兩個方向W1W2,那麼我們可以確定一個平面,我們再取不同值的a和b,計算L(W+aW1+bW2)的值,那麼我們就可以大致繪出在這個平面上,損失函數的變化情況了。

根據上面的方法,我們畫出了下面3個圖。最上面的圖是調整a的不同取值,繪出的損失函數變化曲線(越高值越大);中間和最後一個圖是調整a與b的取值,繪出的損失函數變化圖(藍色表示損失小,紅色表示損失大),中間是在一個圖片樣本上計算的損失結果,最下圖爲100張圖片上計算的損失結果的一個平均。顯然沿着直線方向得到的曲線底端爲最小的損失值點,而曲面呈現的碗狀圖形碗底爲損失函數取值最小處。 


損失函數沿直線投影圖 
損失函數沿平面投影圖2 
損失函數沿平面投影圖2 

我們從數學的角度,來嘗試解釋一下,上面的凹曲線是怎麼出來的。對於第i個樣本,我們知道它的損失函數值爲: 


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

所有的樣本上的損失函數值,是它們損失函數值(max(0,-),因此最小值爲0)的平均值。爲了更好理解,我們假定訓練集裏面有3個樣本,都是1維的,同時總共有3個類別。所以SVM損失(暫時不考慮正則化項)可以表示爲如下的式子:

L0=L1=L2=L=max(0,wT1x0wT0x0+1)+max(0,wT2x0wT0x0+1)max(0,wT0x1wT1x1+1)+max(0,wT2x1wT1x1+1)max(0,wT0x2wT2x2+1)+max(0,wT1x2wT2x2+1)(L0+L1+L2)/3

因爲這個例子裏的樣本都是1維的,因此其實xiwj都是實數。拿w0舉例,損失函數裏,大於0的值其實都和w0是線性關係的,而最小值爲0。因此,我們可以想象成,三條折線『合體』得到的最終曲線,如下圖所示: 
曲線的形成

插幾句題外話,從之前碗狀結構的示意圖,你可能會猜到SVM損失函數是一個凸函數,而對於凸函數的最小值求解方法有很多種。但之後當我們把損失函數f擴充到神經網絡之後,損失函數將變成一個非凸函數,而如果依舊可視化的話,我們看到的將不再是一個碗狀結構,而是凹凸不平的曲面。

3. 最優化

在我們現在這個問題中,所謂的『最優化』其實指的就是找到能讓損失函數最小的參數W。如果大家看過或者瞭解凸優化的話,我們下面介紹的方法,對你而言可能太簡單了,有點原始,但是大家別忘了,我們後期要處理的是神經網絡的損失函數,那可不是一個凸函數哦,所以我們還是一步步來一起看看,如果去實現最優化問題。

3.1 策略1:隨機搜尋(不太實用)

以一個笨方法開始,我們知道,當我們手頭上有參數W後,我們是可以計算損失函數,評估參數合適程度的。所以最直接粗暴的方法就是,我們儘量多地去試參數,然後從裏面選那個讓損失函數最小的,作爲最後的W。代碼當然很簡單,如下:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 假設 X_train 是訓練集 (例如. 3073 x 50,000)</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 假設 Y_train 是類別結果 (例如. 1D array of 50,000)</span>

bestloss = float(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"inf"</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 初始化一個最大的float值</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> num <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> xrange(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>):
  W = np.random.randn(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3073</span>) * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.0001</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 隨機生成一組參數</span>
  loss = L(X_train, Y_train, W) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算損失函數</span>
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> loss < bestloss: <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 比對已搜尋中最好的結果</span>
    bestloss = loss
    bestW = W
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'in attempt %d the loss was %f, best %f'</span> % (num, loss, bestloss)

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># prints:</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 0 the loss was 9.401632, best 9.401632</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 1 the loss was 8.959668, best 8.959668</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 2 the loss was 9.044034, best 8.959668</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 3 the loss was 9.278948, best 8.959668</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 4 the loss was 8.857370, best 8.857370</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 5 the loss was 8.943151, best 8.857370</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># in attempt 6 the loss was 8.605604, best 8.605604</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># ... (trunctated: continues for 1000 lines)</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul>

一通隨機試驗和搜尋之後,我們會拿到試驗結果中最好的參數W,然後在測試集上看看效果:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 假定 X_test 爲 [3073 x 10000], Y_test 爲 [10000 x 1]</span>
scores = Wbest.dot(Xte_cols) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 10 x 10000, 計算類別得分</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 找到最高得分作爲結果</span>
Yte_predict = np.argmax(scores, axis = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>)
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算準確度</span>
np.mean(Yte_predict == Yte)
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 返回 0.1555</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

隨機搜尋得到的參數W,在測試集上的準確率爲15.5%,總共10各類別,我們不做任何預測只是隨機猜的結果應該是10%,好像稍高一點,但是…大家也看到了…這個準確率…實在是沒辦法在實際應用中使用。

3.2 策略2:隨機局部搜索

上一個策略完全就是盲搜,要想找到全局最優的那個結果基本是不可能的。它最大的缺點,就在於下一次搜索完全是隨機進行的,沒有一個指引方向。那我們多想想,就能想出一個在上個策略的基礎上,優化的版本,叫做『隨機局部搜索』。

這個策略的意思是,我們不每次都隨機產生一個參數矩陣W了,而是在現有的參數W基礎上,搜尋一下週邊臨近的參數,有沒有比現在參數更好的W,然後我們用新的W替換現在的W,接着在周圍繼續小範圍搜尋。這個過程呢,可以想象成,我們在一座山上,現在要下山,然後我們每次都伸腳探一探周邊,找一個比現在的位置下降一些的位置,然後邁一步,接着在新的位置上做同樣的操作,一步步直至下山。

從代碼實現的角度看,以上的過程,實際上就是對於一個當前W,我們每次實驗和添加δW,然後看看損失函數是否比當前要低,如果是,就替換掉當前的W,代碼如下:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">W = np.random.randn(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3073</span>) * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.001</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 初始化權重矩陣W</span>
bestloss = float(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"inf"</span>)
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> xrange(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>):
  step_size = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.0001</span>
  Wtry = W + np.random.randn(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3073</span>) * step_size
  loss = L(Xtr_cols, Ytr, Wtry)
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> loss < bestloss:
    W = Wtry
    bestloss = loss
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'iter %d loss is %f'</span> % (i, bestloss)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>

我們做了這麼個小小的修正之後,我們再拿剛纔一樣的測試集來測一下效果,結果發現準確率提升至21.4%,雖然離實際應用差很遠,但只是比剛纔要進步一點點了。

但是還是有個問題,我們每次測試周邊點的損失函數,是一件非常耗時的事情。我們有沒有辦法能夠直接找到我們應該迭代的方向呢?

3.3 策略3:順着梯度下滑

剛纔的策略,我們說了,最大的缺點是非常耗時,且計算量也很大。我們一直在做的事情,就是在當前的位置基礎上,想找到一個最合適的下降方向。我們依舊回到我們假設的那個情境,如果我們在山頂,要以最快的方式下山,我們會怎麼做?我們可能會環顧四周,然後找到最陡的方向,邁一小步,然後再找當前位置最陡的下山方向,再邁一小步…

而這裏提到的最陡的方向,其實對應的就是數學裏『梯度』的概念,也就是說,其實我們無需『伸腳試探』周邊的陡峭程度,而是可以通過計算損失函數的梯度,直接取得這個方向。

我們知道在1個變量的函數裏,某點的斜率/導數代表其變化率最大的方向。而對於多元的情況,梯度是上面情況的一個擴展,只不過這時候的變量不再是一個,而是多個,同時我們計算得到的『梯度方向』也是一個多維的向量。大家都知道數學上計算1維/元函數『梯度/導數』的表達式如下: 

df(x)dx=limh 0f(x+h)f(x)h

對於多元的情況,這個時候我們需要求的東西擴展成每個方向的『偏導數』,然後把它們合在一塊組成我們的梯度向量。

我們用幾張圖來說明這個過程: 


梯度下降1 
梯度下降2 
各種下降算法 

4. 計算梯度

有兩種計算梯度的方法:

  1. 慢一些但是簡單一些的數值梯度/numerical gradient
  2. 速度快但是更容易出錯的解析梯度/analytic gradient

4.1 數值梯度

根據上面提到的導數求解公式,我們可以得到數值梯度計算法。下面是一段簡單的代碼,對於一個給定的函數f和一個向量x,求解這個點上的梯度:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">eval_numerical_gradient</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(f, x)</span>:</span>
  <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">""" 
  一個最基本的計算x點上f的梯度的算法 
  - f 爲參數爲x的函數
  - x 是一個numpy的vector
  """</span> 

  fx = f(x) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算原始點上函數值</span>
  grad = np.zeros(x.shape)
  h = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.00001</span>

  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 對x的每個維度都計算一遍</span>
  it = np.nditer(x, flags=[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'multi_index'</span>], op_flags=[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'readwrite'</span>])
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> it.finished:

    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算x+h處的函數值</span>
    ix = it.multi_index
    old_value = x[ix]
    x[ix] = old_value + h <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 加h</span>
    fxh = f(x) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算f(x + h)</span>
    x[ix] = old_value <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 存儲之前的函數值</span>

    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算偏導數</span>
    grad[ix] = (fxh - fx) / h <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 斜率</span>
    it.iternext() <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 開始下一個維度上的偏導計算</span>

  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> grad</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li></ul>

代碼的方法很簡單,對每個維度,都在原始值上加上一個很小的h,然後計算這個維度/方向上的偏導,最後組在一起得到梯度grad

4.1.1 實際計算中的提示

我們仔細看看導數求解的公式,會發現數學定義上h是要趨於0的,但實際我們計算的時候我們只要取一個足夠小的數(比如1e-5)作爲h就行了,所以我們要精準計算偏導的話,要儘量取到不會帶來數值計算問題,同時又能很小的h。另外,其實實際計算中,我們用另外一個公式用得更多[f(x+h)f(xh)]/2h

下面我們用上面的公式在CIFAR-10數據集上,試一試吧:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">CIFAR10_loss_fun</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(W)</span>:</span>
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> L(X_train, Y_train, W)

W = np.random.rand(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3073</span>) * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.001</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 隨機權重向量</span>
df = eval_numerical_gradient(CIFAR10_loss_fun, W) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算梯度</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

計算到的梯度(準確地說,梯度的方向是函數增大方向,負梯度纔是下降方向)告訴我們,我們應該『下山』的方向是啥,接着我們就沿着它小步邁進:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">loss_original = CIFAR10_loss_fun(W) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 原始點上的損失</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'original loss: %f'</span> % (loss_original, )

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 多大步伐邁進好呢?我們選一些步長試試</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> step_size_log <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> [-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, -<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9</span>, -<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">8</span>, -<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">7</span>, -<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>, -<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span>,-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>,-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>,-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>,-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>]:
  step_size = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> ** step_size_log
  W_new = W - step_size * df <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 新的權重</span>
  loss_new = CIFAR10_loss_fun(W_new)
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'for step size %f new loss: %f'</span> % (step_size, loss_new)

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 輸出:</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># original loss: 2.200718</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-10 new loss: 2.200652</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-09 new loss: 2.200057</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-08 new loss: 2.194116</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-07 new loss: 2.135493</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-06 new loss: 1.647802</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-05 new loss: 2.844355</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-04 new loss: 25.558142</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-03 new loss: 254.086573</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-02 new loss: 2539.370888</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># for step size 1.000000e-01 new loss: 25392.214036</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>

4.1.2 關於迭代的細節

如果大家仔細看上述代碼的話,會發現我們step_size設的都是負的,確實我們每次update權重W的時候,是用原來的W減掉梯度方向的一個較小的值,這樣損失函數才能減小。

4.1.3 關於迭代的步長

我們計算得到梯度之後,就確定了幅度變化最快(負梯度是下降方向)的方向,但是它並沒有告訴我們,我朝着這個方向,應該邁進多遠啊。之後的章節會提到,選擇正確的迭代步長(有時候我們也把它叫做學習速率)是訓練過程中最重要(也是最讓人頭疼)的一個待設定參數。就像我想以最快的速度下山,我們能感知到最陡的方向,卻不知道應該邁多大的步子。如果我們小步邁進,那確實每一步都能比上一步下降一些,但是速度太慢了親!!但是如果我們以非常非常大的步伐邁進(假如腿巨長 -_-||),那你猜怎麼着,你一不小心可能就邁過山腳邁到另一座山山腰上了…

下圖是對以上情況的一個描述和解釋: 


梯度下降 

圖上紅色的值很大,藍色的值很小,我們想逐步下降至藍色中心。如果邁進的步伐太小,收斂和行進的速度就會很慢,如果邁進的步伐太大,可能直接越過去了。

4.1.4 效率問題

如果你再回過頭去看看上面計算數值梯度的程序,你會發現,這個計算方法的複雜度,基本是和我們的參數個數成線性關係的。這意味着什麼呢?在我們的CIFAR-10例子中,我們總共有30730個參數,因此我們單次迭代總共就需要計算30731次損失函數。這個問題在之後會提到的神經網絡中更爲嚴重,很可能兩層神經元之間就有百萬級別的參數權重,所以,計算機算起來都很耗時…人也要等結果等到哭瞎…

4.2 解析法計算梯度

數值梯度發非常容易實現,但是從公式裏面我們就看得出來,梯度實際上是一個近似(畢竟你沒辦法把h取到非常小),同時這也是一個計算非常耗時的算法。第二種計算梯度的方法是解析法,它可以讓我們直接得到梯度的一個公式(代入就可以計算,非常快),但是呢,不像數值梯度法,這種方法更容易出現錯誤。so,聰明的同學們,就想了一個辦法,我們可以先計算解析梯度和數值梯度,然後比對結果和校正,在確定我們解析梯度實現正確之後,我們就可以大膽地進行解析法計算了(這個過程叫做梯度檢查/檢測)

我們拿一個樣本點的SVM損失函數舉例: 

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

我們可以求它對每個權重的偏導數,比如說,我們求它對wyi的偏導,我們得到: 

wyiLi=jyi1(wTjxiwTyixi+Δ>0)xi

其中1是一個bool函數,在括號內的條件爲真的時候取值爲1,否則爲0。看起來似乎很嚇人,但實際上要寫代碼完成的話,你只需要計算不滿足指定SVM最小距離的類(對損失函數有貢獻的類)的個數,然後用這個值會對數據向量xi做縮放即可得到梯度。但是要注意只是W中對應正確的類別的列的梯度。對於其他的jyi的情況,梯度爲: 

wjLi=1(wTjxiwTyixi+Δ>0)xi

一旦得到梯度的表達式,那計算梯度和調整權重就變得非常直接和簡單。熟練掌握如何在loss expression下計算梯度是非常重要的一個技巧,貫穿整個神經網絡的訓練實現過程,關於這個內容,下次會詳細講到。

5. 梯度下降

在我們有辦法計算得到梯度之後,使用梯度去更新已有權重參數的過程叫做『梯度下降』,僞代碼其實就是如下的樣子:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">True</span>:
  weights_grad = evaluate_gradient(loss_fun, data, weights)
  weights += - step_size * weights_grad <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 梯度下降更新參數</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

這個簡單的循環實質上就是很多神經網絡庫的核心。當然,我們也有其他的方式去實現最優化(比如說L-BFGS),但是梯度下降確實是當前使用最廣泛,也相對最穩定的神經網絡損失函數最優化方法。

5.1 Mini-batch gradient descent

在大型的應用當中(比如ILSVRC),訓練數據可能是百萬千萬級別的。因此,對整個訓練數據集的樣本都算一遍損失函數,以完成參數迭代是一件非常耗時的事情,一個我們通常會用到的替代方法是,採樣出一個子集在其上計算梯度。現在比較前沿的神經網絡結構基本都是這麼做的,例如ConvNets是每256張作爲一個batch去完成參數的更新。參數更新的代碼如下:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">True</span>:
  data_batch = sample_training_data(data, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">256</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 抽樣256個樣本作爲一個batch</span>
  weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
  weights += - step_size * weights_grad <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 參數更新</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>

之所以可以這麼做,是因爲訓練數據之間其實是關聯的。我們簡化一下這個問題,你想想,如果ILSVRC中的120w圖片,如果只是1000張不同的圖片,一直複製1200次得到的。那麼其實我們在這1000張圖片上算得的損失函數和120w的平均其實是一致的。當然,當然,在實際場景中,我們肯定很少遇到這種多次重複的情況,但是原數據的一個子集(mini-batch)上的梯度,其實也是對整體數據上梯度的一個很好的近似。因此,只在mini-batch上計算和更新參數,會有快得多的收斂速度。

上述算法的一個極端的情況是,如果我們的一個mini-batch裏面只有一張圖片。那這個過程就變成『隨機梯度下降/Stochastic Gradient Descent (SGD)』,說起來,這個其實在實際應用中倒也沒那麼常見,原因是向量化之後,一次計算100張圖片,其實比計算一張圖片100次,要快得多。所以即使從定義上來說,SGD表示我們用一張圖片上的梯度近似全局梯度,但是很多時候人們提到SGD的時候,其實他們指的是mini-batch梯度下降,也就是說,我們把一個batch當做1份了。額,還要稍微提一句的是,有些同學可能會問,這個batch size本身不是一個需要實驗的參數嗎,取多大的batch size好啊?但實際應用中,我們倒很少會用cross-validation去選擇這個參數。這麼說吧,我們一般是基於我們內存限制去取這個值的,比如設成100左右。

6. 總結

  • 把損失函數在各參數上的取值,想象成我們所在山峯的高度。那麼我們要最小化損失函數,實際上就是『要想辦法下山』。
  • 我們採取的下山策略是,一次邁一小步,只要每次都往下走了,那麼最後就會到山底
  • 梯度對應函數變化最快的方向,負梯度的方向就是我們下山,環顧四周之後,發現最陡的下山路方向。
  • 我們的步長(也叫學習率),會影響我們的收斂速度(下山速度),如果步伐特別特別大,甚至可能躍過最低點,跑到另外一個高值位置了。
  • 我們用mini-batch的方式,用一小部分的樣本子集,計算和更新參數,減少計算量,加快收斂速度。

參考資料與原文

cs231n 最優化與隨機梯度下降

作者:寒小陽 
時間:2015年12月。 
出處:http://blog.csdn.net/han_xiaoyang/article/details/50178505 
聲明:版權所有,轉載請聯繫作者並註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章