CS231n課程筆記翻譯:最優化筆記(下)
原文如下
內容列表:
- 簡介
- 損失函數可視化
- 最優化
- 策略#1:隨機搜索
- 策略#2:隨機局部搜索
- 策略#3:跟隨梯度
- 梯度計算 譯者註:下篇起始處
- 使用有限差值進行數值計算
- 微分分析計算梯度
- 梯度下降
- 小結
梯度計算
計算梯度有兩種方法:一個是緩慢的近似方法(數值梯度法),但實現相對簡單。另一個方法(分析梯度法)計算迅速,結果精確,但是實現時容易出錯,且需要使用微分。現在對兩種方法進行介紹:
利用有限差值計算梯度
上節中的公式已經給出數值計算梯度的方法。下面代碼是一個輸入為函數f和向量x,計算f的梯度的通用函數,它返回函數f在點x處的梯度:
def eval_numerical_gradient(f, x):n """ n 一個f在x處的數值梯度法的簡單實現n - f是只有一個參數的函數n - x是計算梯度的點n """ nn fx = f(x) # 在原點計算函數值n grad = np.zeros(x.shape)n h = 0.00001nn # 對x中所有的索引進行迭代n it = np.nditer(x, flags=[multi_index], op_flags=[readwrite])n while not it.finished:nn # 計算x+h處的函數值n ix = it.multi_indexn old_value = x[ix]n x[ix] = old_value + h # 增加hn fxh = f(x) # 計算f(x + h)n x[ix] = old_value # 存到前一個值中 (非常重要)nn # 計算偏導數n grad[ix] = (fxh - fx) / h # 坡度n it.iternext() # 到下個維度nn return gradn
根據上面的梯度公式,代碼對所有維度進行迭代,在每個維度上產生一個很小的變化h,通過觀察函數值變化,計算函數在該維度上的偏導數。最後,所有的梯度存儲在變數grad中。
實踐考量:注意在數學公式中,h的取值是趨近於0的,然而在實際中,用一個很小的數值(比如例子中的1e-5)就足夠了。在不產生數值計算出錯的理想前提下,你會使用儘可能小的h。還有,實際中用中心差值公式(centered difference formula)效果較好。細節可查看wiki。
可以使用上面這個公式來計算任意函數在任意點上的梯度。下面計算權重空間中的某些隨機點上,CIFAR-10損失函數的梯度:
# 要使用上面的代碼我們需要一個只有一個參數的函數n# (在這裡參數就是權重)所以也包含了X_train和Y_trainndef CIFAR10_loss_fun(W):n return L(X_train, Y_train, W)nnW = np.random.rand(10, 3073) * 0.001 # 隨機權重向量ndf = eval_numerical_gradient(CIFAR10_loss_fun, W) # 得到梯度n
梯度告訴我們損失函數在每個維度上的斜率,以此來進行更新:
loss_original = CIFAR10_loss_fun(W) # 初始損失值nprint original loss: %f % (loss_original, )nn# 查看不同步長的效果nfor step_size_log in [-10, -9, -8, -7, -6, -5,-4,-3,-2,-1]:n step_size = 10 ** step_size_logn W_new = W - step_size * df # 權重空間中的新位置n loss_new = CIFAR10_loss_fun(W_new)n print for step size %f new loss: %f % (step_size, loss_new)nn# 輸出:n# original loss: 2.200718n# for step size 1.000000e-10 new loss: 2.200652n# for step size 1.000000e-09 new loss: 2.200057n# for step size 1.000000e-08 new loss: 2.194116n# for step size 1.000000e-07 new loss: 2.135493n# for step size 1.000000e-06 new loss: 1.647802n# for step size 1.000000e-05 new loss: 2.844355n# for step size 1.000000e-04 new loss: 25.558142n# for step size 1.000000e-03 new loss: 254.086573n# for step size 1.000000e-02 new loss: 2539.370888n# for step size 1.000000e-01 new loss: 25392.214036n
在梯度負方向上更新:在上面的代碼中,為了計算W_new,要注意我們是向著梯度df的負方向去更新,這是因為我們希望損失函數值是降低而不是升高。
步長的影響:梯度指明了函數在哪個方向是變化率最大的,但是沒有指明在這個方向上應該走多遠。在後續的課程中可以看到,選擇步長(也叫作學習率)將會是神經網路訓練中最重要(也是最頭痛)的超參數設定之一。還是用蒙眼徒步者下山的比喻,這就好比我們可以感覺到腳朝向的不同方向上,地形的傾斜程度不同。但是該跨出多長的步長呢?不確定。如果謹慎地小步走,情況可能比較穩定但是進展較慢(這就是步長較小的情況)。相反,如果想儘快下山,那就大步走吧,但結果也不一定盡如人意。在上面的代碼中就能看見反例,在某些點如果步長過大,反而可能越過最低點導致更高的損失值。
————————————————————————————————————————
將步長效果視覺化的圖例。從某個具體的點W開始計算梯度(白箭頭方向是負梯度方向),梯度告訴了我們損失函數下降最陡峭的方向。小步長下降穩定但進度慢,大步長進展快但是風險更大。採取大步長可能導致錯過最優點,讓損失值上升。步長(後面會稱其為學習率)將會是我們在調參中最重要的超參數之一。
————————————————————————————————————————
效率問題:你可能已經注意到,計算數值梯度的複雜性和參數的量線性相關。在本例中有30730個參數,所以損失函數每走一步就需要計算30731次損失函數的梯度。現代神經網路很容易就有上千萬的參數,因此這個問題只會越發嚴峻。顯然這個策略不適合大規模數據,我們需要更好的策略。
微分分析計算梯度
使用有限差值近似計算梯度比較簡單,但缺點在於終究只是近似(因為我們對於h值是選取了一個很小的數值,但真正的梯度定義中h趨向0的極限),且耗費計算資源太多。第二個梯度計算方法是利用微分來分析,能得到計算梯度的公式(不是近似),用公式計算梯度速度很快,唯一不好的就是實現的時候容易出錯。為了解決這個問題,在實際操作時常常將分析梯度法的結果和數值梯度法的結果作比較,以此來檢查其實現的正確性,這個步驟叫做梯度檢查。
用SVM的損失函數在某個數據點上的計算來舉例:
可以對函數進行微分。比如,對進行微分得到:
譯者註:原公式中1為空心字體,嘗試mathbb{}等多種方法仍無法實現,請知友指點。
其中是一個示性函數,如果括弧中的條件為真,那麼函數值為1,如果為假,則函數值為0。雖然上述公式看起來複雜,但在代碼實現的時候比較簡單:只需要計算沒有滿足邊界值的分類的數量(因此對損失函數產生了貢獻),然後乘以就是梯度了。注意,這個梯度只是對應正確分類的W的行向量的梯度,那些行的梯度是:
一旦將梯度的公式微分出來,代碼實現公式並用於梯度更新就比較順暢了。
梯度下降
現在可以計算損失函數的梯度了,程序重複地計算梯度然後對參數進行更新,這一過程稱為梯度下降,他的普通版本是這樣的:
# 普通的梯度下降nnwhile True:n weights_grad = evaluate_gradient(loss_fun, data, weights)n weights += - step_size * weights_grad # 進行梯度更新n
這個簡單的循環在所有的神經網路核心庫中都有。雖然也有其他實現最優化的方法(比如LBFGS),但是到目前為止,梯度下降是對神經網路的損失函數最優化中最常用的方法。課程中,我們會在它的循環細節增加一些新的東西(比如更新的具體公式),但是核心思想不變,那就是我們一直跟著梯度走,直到結果不再變化。
小批量數據梯度下降(Mini-batch gradient descent):在大規模的應用中(比如ILSVRC挑戰賽),訓練數據可以達到百萬級量級。如果像這樣計算整個訓練集,來獲得僅僅一個參數的更新就太浪費了。一個常用的方法是計算訓練集中的小批量(batches)數據。例如,在目前最高水平的卷積神經網路中,一個典型的小批量包含256個例子,而整個訓練集是多少呢?一百二十萬個。這個小批量數據就用來實現一個參數更新:
# 普通的小批量數據梯度下降nnwhile True:n data_batch = sample_training_data(data, 256) # 256個數據n weights_grad = evaluate_gradient(loss_fun, data_batch, weights)n weights += - step_size * weights_grad # 參數更新n
這個方法之所以效果不錯,是因為訓練集中的數據都是相關的。要理解這一點,可以想像一個極端情況:在ILSVRC中的120萬個圖像是1000張不同圖片的複製(每個類別1張圖片,每張圖片有1200張複製)。那麼顯然計算這1200張複製圖像的梯度就應該是一樣的。對比120萬張圖片的數據損失的均值與只計算1000張的子集的數據損失均值時,結果應該是一樣的。實際情況中,數據集肯定不會包含重複圖像,那麼小批量數據的梯度就是對整個數據集梯度的一個近似。因此,在實踐中通過計算小批量數據的梯度可以實現更快速地收斂,並以此來進行更頻繁的參數更新。
小批量數據策略有個極端情況,那就是每個批量中只有1個數據樣本,這種策略被稱為隨機梯度下降(Stochastic Gradient Descent 簡稱SGD),有時候也被稱為在線梯度下降。這種策略在實際情況中相對少見,因為向量化操作的代碼一次計算100個數據 比100次計算1個數據要高效很多。即使SGD在技術上是指每次使用1個數據來計算梯度,你還是會聽到人們使用SGD來指代小批量數據梯度下降(或者用MGD來指代小批量數據梯度下降,而BGD來指代則相對少見)。小批量數據的大小是一個超參數,但是一般並不需要通過交叉驗證來調參。它一般由存儲器的限制來決定的,或者乾脆設置為同樣大小,比如32,64,128等。之所以使用2的指數,是因為在實際中許多向量化操作實現的時候,如果輸入數據量是2的倍數,那麼運算更快。
小結
————————————————————————————————————————
信息流的總結圖例。數據集中的(x,y)是給定的。權重從一個隨機數字開始,且可以改變。在前向傳播時,評分函數計算出類別的分類評分並存儲在向量f中。損失函數包含兩個部分:數據損失和正則化損失。其中,數據損失計算的是分類評分f和實際標籤y之間的差異,正則化損失只是一個關於權重的函數。在梯度下降過程中,我們計算權重的梯度(如果願意的話,也可以計算數據上的梯度),然後使用它們來實現參數的更新。
—————————————————————————————————————————
在本節課中:
將損失函數比作了一個高維度的最優化地形,並嘗試到達它的最底部。最優化的工作過程可以看做一個蒙著眼睛的徒步者希望摸索著走到山的底部。在例子中,可見SVM的損失函數是分段線性的,並且是碗狀的。
提出了迭代優化的思想,從一個隨機的權重開始,然後一步步地讓損失值變小,直到最小。
函數的梯度給出了該函數最陡峭的上升方向。介紹了利用有限的差值來近似計算梯度的方法,該方法實現簡單但是效率較低(有限差值就是h,用來計算數值梯度)。
參數更新需要有技巧地設置步長。也叫學習率。如果步長太小,進度穩定但是緩慢,如果步長太大,進度快但是可能有風險。
討論權衡了數值梯度法和分析梯度法。數值梯度法計算簡單,但結果只是近似且耗費計算資源。分析梯度法計算準確迅速但是實現容易出錯,而且需要對梯度公式進行推導的數學基本功。因此,在實際中使用分析梯度法,然後使用梯度檢查來檢查其實現正確與否,其本質就是將分析梯度法的結果與數值梯度法的計算結果對比。
介紹了梯度下降演算法,它在循環中迭代地計算梯度並更新參數。
預告:這節課的核心內容是:理解並能計算損失函數關於權重的梯度,是設計、訓練和理解神經網路的核心能力。下節中,將介紹如何使用鏈式法則來高效地計算梯度,也就是通常所說的反向傳播(backpropagation)機制。該機制能夠對包含卷積神經網路在內的幾乎所有類型的神經網路的損失函數進行高效的最優化。
最優化筆記全文翻譯完。
譯者反饋
- 轉載須全文轉載並註明原文鏈接,否則保留維權權利;
- 請知友們通過評論和私信等方式批評指正,貢獻者均會補充提及
- 感謝知友roach sinai的評論。
推薦閱讀:
※機器學習演算法之手寫邏輯回歸
※《to buy or not to buy》筆記
※極簡機器學習入門
※局部敏感判別分析
※淺識 Batch Normalization