【連載】深度學(xué)習(xí)筆記6:神經(jīng)網(wǎng)絡(luò)優(yōu)化算法之從SGD到Adam

????? 從前面的學(xué)習(xí)中,帶大家一起學(xué)會了如何手動搭建神經(jīng)網(wǎng)絡(luò),以及神經(jīng)網(wǎng)絡(luò)的正則化等實用層面的內(nèi)容。這些都使得我們能夠更深入的理解神經(jīng)網(wǎng)絡(luò)的機制,而并不是初次接觸深度學(xué)習(xí)就上手框架,雖然對外宣稱神經(jīng)網(wǎng)絡(luò)是個黑箱機制,但是作為學(xué)習(xí)者我們極度有必要搞清楚算法在每個環(huán)節(jié)到底都干了些什么。

????? 今天講的是深度學(xué)習(xí)的一個大的主題——優(yōu)化算法。采用何種方式對損失函數(shù)進行迭代優(yōu)化,這是機器學(xué)習(xí)的一大主題之一,當(dāng)一個機器學(xué)習(xí)問題有了具體的模型和評估策略,所有的機器學(xué)習(xí)問題都可以形式化為一個最優(yōu)化問題。這也是為什么我們說優(yōu)化理論和凸優(yōu)化算法等學(xué)科是機器學(xué)習(xí)一大支柱的原因所在。從純數(shù)學(xué)的角度來看,所有的數(shù)學(xué)模型盡管形式不一,各有頭面,但到最后幾乎到可以歸約為最優(yōu)化問題。所以,有志于奮戰(zhàn)在機器學(xué)習(xí)和深度學(xué)習(xí)領(lǐng)域的各位,學(xué)好最優(yōu)化,責(zé)無旁貸啊。

????? 要說機器學(xué)習(xí)和深度學(xué)習(xí)的優(yōu)化算法,梯度下降必然是核心所在。神經(jīng)網(wǎng)絡(luò)發(fā)展至今,優(yōu)化算法層出不窮,但大底是出不了梯度下降的框框架架。這一篇筆記,筆者就和大家一起學(xué)習(xí)和回顧深度學(xué)習(xí)中常用的優(yōu)化算法。在前面手動搭建神經(jīng)網(wǎng)絡(luò)的代碼實踐中,我們對于損失函數(shù)的優(yōu)化采用了一般的梯度下降法,所以本篇總結(jié)就從梯度下降法開始。

梯度下降法 Gradient Descent

????? 想必大家對于梯度下降是很熟悉了,選擇負梯度方向進行參數(shù)更新算是常規(guī)操作了。話不多說,對于多層神經(jīng)網(wǎng)絡(luò)如何執(zhí)行梯度下降:

def update_parameters_with_gd(parameters, grads, learning_rate):

? ?"""

? ?Update parameters using one step of gradient descent

? ?Arguments:

? ?parameters -- python dictionary containing your parameters to be updated:

? ? ? ? ? ? ? ? ? ?parameters['W' + str(l)] = Wl

? ? ? ? ? ? ? ? ? ?parameters['b' + str(l)] = bl

? ?grads -- python dictionary containing your gradients to update each parameters:

? ? ? ? ? ? ? ? ? ?grads['dW' + str(l)] = dWl

? ? ? ? ? ? ? ? ? ?grads['db' + str(l)] = dbl

? ?learning_rate -- the learning rate, scalar.

? ?Returns:

? ?parameters -- python dictionary containing your updated parameters

? ?""" ? ?L = len(parameters) // 2 # number of layers in the neural networks ? ?# Update rule for each parameter ? ?for l in range(L): ?

? ? ? ?parameters['W' + str(l+1)] = parameters['W' + str(l+1)] - learning_rate * grads['dW' + str(l+1)]

? ? ? ?parameters['b' + str(l+1)] = parameters['b' + str(l+1)] - learning_rate * grads['db' + str(l+1)] ?

? ?return parameters

????? 在上述代碼中,我們傳入含有權(quán)值和偏置的字典、梯度字段和更新的學(xué)習(xí)率作為參數(shù),按照開頭的公式編寫權(quán)值更新代碼,一個簡單的多層網(wǎng)絡(luò)的梯度下降算法就寫出來了。

小批量梯度下降法 mini-batch Gradient Descent

????? 在工業(yè)數(shù)據(jù)環(huán)境下,直接對大數(shù)據(jù)執(zhí)行梯度下降法訓(xùn)練往往處理速度緩慢,這時候?qū)⒂?xùn)練集分割成小一點的子集進行訓(xùn)練就非常重要了。這個被分割成的小的子集就叫做 mini-batch,意為小批量。對每一個小批量同時執(zhí)行梯度下降會大大提高訓(xùn)練效率。在實際利用代碼實現(xiàn)的時候,小批量梯度下降算法通常包括兩個步驟:充分打亂數(shù)據(jù)(shuffle)和分組組合數(shù)據(jù)(partition)。如下圖所示。

shuffle

partition

????? 具體代碼實現(xiàn)為:

def random_mini_batches(X, Y, mini_batch_size =64, seed =0):

? ?"""

? ?Creates a list of random minibatches from (X, Y)

? ?Arguments:

? ?X -- input data, of shape (input size, number of examples)

? ?Y -- true "label" vector (1 for blue dot / 0 for red dot), of shape (1, number of examples)

? ?mini_batch_size -- size of the mini-batches, integer

? ?Returns:

? ?mini_batches -- list of synchronous (mini_batch_X, mini_batch_Y)

? ?""" ? ?np.random.seed(seed) ? ? ? ?

? ?m = X.shape[1] ? ? ? ? ? ? ? ?

? ?mini_batches = [] ? ?# Step 1: Shuffle (X, Y) ? ?permutation = list(np.random.permutation(m))

? ?shuffled_X = X[:, permutation]

? ?shuffled_Y = Y[:, permutation].reshape((1,m)) ? ?# Step 2: Partition (shuffled_X, shuffled_Y). Minus the end case. ? ?num_complete_minibatches = math.floor(m/mini_batch_size)

? ?for k in range(0, num_complete_minibatches):

? ? ? ?mini_batch_X = shuffled_X[:, 0:mini_batch_size]

? ? ? ?mini_batch_Y = shuffled_Y[:, 0:mini_batch_size]

? ? ? ?mini_batch = (mini_batch_X, mini_batch_Y)

? ? ? ?mini_batches.append(mini_batch) ? ?# Handling the end case (last mini-batch < mini_batch_size) ? ?if m % mini_batch_size != 0:

? ? ? ?mini_batch_X = shuffled_X[:, 0: m-mini_batch_size*math.floor(m/mini_batch_size)]

? ? ? ?mini_batch_Y = shuffled_Y[:, 0: m-mini_batch_size*math.floor(m/mini_batch_size)]

? ? ? ?mini_batch = (mini_batch_X, mini_batch_Y)

? ? ? ?mini_batches.append(mini_batch) ? ?

? ?return mini_batches

????? 小批量梯度下降的實現(xiàn)思路非常清晰,先打亂數(shù)據(jù)在分組數(shù)據(jù),需要注意的細節(jié)在于最后一個小批量所含的訓(xùn)練樣本數(shù),通常而言最后一個小批量會少于前面批量所含樣本數(shù)。

隨機梯度下降 Stochastic Gradient Descent

當(dāng)小批量所含的訓(xùn)練樣本數(shù)為 1 的時候,小批量梯度下降法就變成了隨機梯度下降法(SGD)。SGD雖然以單個樣本為訓(xùn)練單元訓(xùn)練速度會很快,但犧牲了向量化運算所帶來的便利性,在較大數(shù)據(jù)集上效率并不高。

我們可以看一下梯度下降和隨機梯度下降在實現(xiàn)上的差異:

# GD

X = data_input

Y = labels

parameters = initialize_parameters(layers_dims)

for i in range(0, num_iterations): ? ?# Forward propagation ? ?a, caches = forward_propagation(X, parameters) ? ?# Compute cost. ? ?cost = compute_cost(a, Y) ? ?# Backward propagation. ? ?grads = backward_propagation(a, caches, parameters) ? ?# Update parameters. ? ?parameters = update_parameters(parameters, grads)

# SGDX = data_input

Y = labels

parameters = initialize_parameters(layers_dims)

for i in range(0, num_iterations): ? ?

? ?for j in range(0, m): ? ? ? ?# Forward propagation ? ? ? ?a, caches = forward_propagation(X[:,j], parameters) ? ? ? ?# Compute cost ? ? ? ?cost = compute_cost(a, Y[:,j]) ? ? ? ?# Backward propagation ? ? ? ?grads = backward_propagation(a, caches, parameters) ? ? ? ?# Update parameters. ? ? ? ?parameters = update_parameters(parameters, grads)

????? 所以,從本質(zhì)上看,梯度下降法、小批量梯度下降法和隨機梯度下降法,并沒有區(qū)別。唯一的區(qū)別就在于它們執(zhí)行一次訓(xùn)練過程所需要用到的訓(xùn)練樣本數(shù)。梯度下降法用到的是全集訓(xùn)練數(shù)據(jù),隨機梯度下降則是單個樣本數(shù)據(jù),而小批量則是介于二者之間。

帶動量的梯度下降法(momentum)

????? 正如上圖中看到的一樣,我們假設(shè)梯度下降的橫向為參數(shù) W 的下降方向,而偏置 b 的下降方向為縱軸,我們總是希望在縱軸上的震蕩幅度小一點,學(xué)習(xí)速度慢一點,而在橫軸上學(xué)習(xí)速度快一點,無論是小批量梯度下降還是隨機梯度下降,好像都不能避免這個問題。為了解決這個問題,帶動量的梯度下降法來了。帶動量的梯度下降考慮歷史梯度的加權(quán)平均值作為速率進行優(yōu)化。執(zhí)行公式如下:

????? 根據(jù)上述公式編寫帶動量的梯度下降法實現(xiàn)代碼:

def update_parameters_with_momentum(parameters, grads, v, beta, learning_rate):

? ?"""

? ?Update parameters using Momentum

? ?Arguments:

? ?parameters -- python dictionary containing your parameters:

? ? ? ? ? ? ? ? ? ?parameters['W' + str(l)] = Wl

? ? ? ? ? ? ? ? ? ?parameters['b' + str(l)] = bl

? ?grads -- python dictionary containing your gradients for each parameters:

? ? ? ? ? ? ? ? ? ?grads['dW' + str(l)] = dWl

? ? ? ? ? ? ? ? ? ?grads['db' + str(l)] = dbl

? ?v -- python dictionary containing the current velocity:

? ? ? ? ? ? ? ? ? ?v['dW' + str(l)] = ...

? ? ? ? ? ? ? ? ? ?v['db' + str(l)] = ...

? ?beta -- the momentum hyperparameter, scalar

? ?learning_rate -- the learning rate, scalar

? ?Returns:

? ?parameters -- python dictionary containing your updated parameters

? ?v -- python dictionary containing your updated velocities

? ?""" ? ?L = len(parameters) // 2 # number of layers in the neural networks ? ?# Momentum update for each parameter ? ?for l in range(L): ? ? ? ?# compute velocities ? ? ? ?v['dW' + str(l+1)] = beta * v['dW' + str(l+1)] + (1-beta)* grads['dW' + str(l+1)]

? ? ? ?v['db' + str(l+1)] = beta * v['db' + str(l+1)] + (1-beta)* grads['db' + str(l+1)] ? ? ? ?# update parameters ? ? ? ?parameters['W' + str(l+1)] = parameters['W' + str(l+1)] - learning_rate*v['dW' + str(l+1)]

? ? ? ?parameters['b' + str(l+1)] = parameters['b' + str(l+1)] - learning_rate*v['db' + str(l+1)]

? ?return parameters, v

????? 實現(xiàn)帶動量的梯度下降的關(guān)鍵點有兩個:一是動量是考慮歷史梯度進行梯度下降的,二是這里的需要指定的超參數(shù)變成了兩個:一個是學(xué)習(xí)率 learning_rate,一個是梯度加權(quán)參數(shù)beta。

Adam算法

????? Adam 全稱為 Adaptive Moment Estimation,是在帶動量的梯度下降法的基礎(chǔ)上融合了一種稱為 RMSprop(加速梯度下降)的算法而成的。相較于帶動量的梯度下降法,無論是RMSprop 還是 Adam,其中的改進思路都在于如何讓橫軸上的學(xué)習(xí)更快以及讓縱軸上的學(xué)習(xí)更慢。RMSprop 和 Adam 在帶動量的梯度下降法的基礎(chǔ)上,引入了平方梯度,并對速率進行了偏差糾正。具體計算公式如下:

????? 實現(xiàn)代碼如下:

def update_parameters_with_adam(parameters, grads, v, s, t, learning_rate =0.01, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?beta1 =0.9, beta2 =0.999, ?epsilon =1e-8):

? ?"""

? ?Update parameters using Adam

? ?Arguments:

? ?parameters -- python dictionary containing your parameters:

? ? ? ? ? ? ? ? ? ?parameters['W' + str(l)] = Wl

? ? ? ? ? ? ? ? ? ?parameters['b' + str(l)] = bl

? ?grads -- python dictionary containing your gradients for each parameters:

? ? ? ? ? ? ? ? ? ?grads['dW' + str(l)] = dWl

? ? ? ? ? ? ? ? ? ?grads['db' + str(l)] = dbl

? ?v -- Adam variable, moving average of the first gradient, python dictionary

? ?s -- Adam variable, moving average of the squared gradient, python dictionary

? ?learning_rate -- the learning rate, scalar.

? ?beta1 -- Exponential decay hyperparameter for the first moment estimates

? ?beta2 -- Exponential decay hyperparameter for the second moment estimates

? ?epsilon -- hyperparameter preventing division by zero in Adam updates

? ?Returns:

? ?parameters -- python dictionary containing your updated parameters

? ?v -- Adam variable, moving average of the first gradient, python dictionary

? ?s -- Adam variable, moving average of the squared gradient, python dictionary

? ?""" ? ?L = len(parameters) // 2 ? ? ? ? ? ? ? ?

? ?v_corrected = {} ? ? ? ? ? ? ? ? ? ? ? ?

? ?s_corrected = {} ? ? ? ? ? ? ? ? ? ? ? ?

? ?# Perform Adam update on all parameters ? ?for l in range(L):

? ? ? ?v["dW" + str(l+1)] = beta1 * v["dW" + str(l+1)] + (1 - beta1) * grads['dW'+str(l+1)]

? ? ? ?v["db" + str(l+1)] = beta1 * v["db" + str(l+1)] + (1 - beta1) * grads['db'+str(l+1)] ? ? ? ?# Compute bias-corrected first moment estimate. Inputs: "v, beta1, t". Output: "v_corrected". ?? ? ? ? ?v_corrected["dW" + str(l+1)] = v["dW" + str(l+1)] / (1 - beta1**t)

? ? ? ?v_corrected["db" + str(l+1)] = v["db" + str(l+1)] / (1 - beta1**t) ? ? ? ?# Moving average of the squared gradients. Inputs: "s, grads, beta2". Output: "s". ? ? ? ?s["dW" + str(l+1)] = beta2 * s["dW" + str(l+1)] + (1 - beta2) * (grads["dW" + str(l+1)])**2 ? ? ? ?s["db" + str(l+1)] = beta2 * s["db" + str(l+1)] + (1 - beta2) * (grads["db" + str(l+1)])**2 ? ? ? ?# Compute bias-corrected second raw moment estimate. Inputs: "s, beta2, t". Output: "s_corrected". ? ? ? ?s_corrected["dW" + str(l+1)] = s["dW" + str(l+1)] / (1 - beta2**t)

? ? ? ?s_corrected["db" + str(l+1)] = s["db" + str(l+1)] / (1 - beta2**t) ? ? ? ?# Update parameters. Inputs: "parameters, learning_rate, v_corrected, s_corrected, epsilon". Output: "parameters". ? ? ? ?parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate * v_corrected["dW" + str(l+1)] / (np.sqrt(s_corrected["dW" + str(l+1)]) + epsilon)

? ? ? ?parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate * v_corrected["db" + str(l+1)] / (np.sqrt(s_corrected["db" + str(l+1)]) + epsilon) ? ?

? ?return parameters, v, s

????? 除了以上這些算法,還有一些像 Adadelta 之類的算法我們沒有提到,有需要了解的同學(xué)可以自行查找相關(guān)資料。最后用一個圖來展示各種優(yōu)化算法的效果:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容