從前面的學(xué)習(xí)中,帶大家一起學(xué)會了如何手動搭建神經(jīng)網(wǎng)絡(luò),以及神經(jīng)網(wǎng)絡(luò)的正則化等實(shí)用層面的內(nèi)容。這些都使得我們能夠更深入的理解神經(jīng)網(wǎng)絡(luò)的機(jī)制,而并不是初次接觸深度學(xué)習(xí)就上手框架,雖然對外宣稱神經(jīng)網(wǎng)絡(luò)是個(gè)黑箱機(jī)制,但是作為學(xué)習(xí)者我們極度有必要搞清楚算法在每個(gè)環(huán)節(jié)到底都干了些什么。
今天筆者需要講的是深度學(xué)習(xí)的一個(gè)大的主題——優(yōu)化算法。采用何種方式對損失函數(shù)進(jìn)行迭代優(yōu)化,這是機(jī)器學(xué)習(xí)的一大主題之一,當(dāng)一個(gè)機(jī)器學(xué)習(xí)問題有了具體的模型和評估策略,所有的機(jī)器學(xué)習(xí)問題都可以形式化為一個(gè)最優(yōu)化問題。這也是為什么我們說優(yōu)化理論和凸優(yōu)化算法等學(xué)科是機(jī)器學(xué)習(xí)一大支柱的原因所在。從純數(shù)學(xué)的角度來看,所有的數(shù)學(xué)模型盡管形式不一,各有頭面,但到最后幾乎到可以歸約為最優(yōu)化問題。所以,有志于奮戰(zhàn)在機(jī)器學(xué)習(xí)和深度學(xué)習(xí)領(lǐng)域的各位,學(xué)好最優(yōu)化,責(zé)無旁貸啊。
要說機(jī)器學(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í)踐中,我們對于損失函數(shù)的優(yōu)化采用了一般的梯度下降法,所以本篇總結(jié)就從梯度下降法開始。
梯度下降法 Gradient Descent
想必大家對于梯度下降是很熟悉了,選擇負(fù)梯度方向進(jìn)行參數(shù)更新算是常規(guī)操作了。話不多說,對于多層神經(jīng)網(wǎng)絡(luò)如何執(zhí)行梯度下降:
defupdate_parameters_with_gd(parameters,grads,learning_rate): """ Updateparametersusingonestepofgradientdescent Arguments: parameters--pythondictionarycontainingyourparameterstobeupdated: parameters['W'+str(l)]=Wl parameters['b'+str(l)]=bl grads--pythondictionarycontainingyourgradientstoupdateeachparameters: grads['dW'+str(l)]=dWl grads['db'+str(l)]=dbl learning_rate--thelearningrate,scalar. Returns: parameters--pythondictionarycontainingyourupdatedparameters """ L=len(parameters)//2#numberoflayersintheneuralnetworks #Updateruleforeachparameter forlinrange(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)] returnparameters
在上述代碼中,我們傳入含有權(quán)值和偏置的字典、梯度字段和更新的學(xué)習(xí)率作為參數(shù),按照開頭的公式編寫權(quán)值更新代碼,一個(gè)簡單的多層網(wǎng)絡(luò)的梯度下降算法就寫出來了。
小批量梯度下降法 mini-batch Gradient Descent
在工業(yè)數(shù)據(jù)環(huán)境下,直接對大數(shù)據(jù)執(zhí)行梯度下降法訓(xùn)練往往處理速度緩慢,這時(shí)候?qū)⒂?xùn)練集分割成小一點(diǎn)的子集進(jìn)行訓(xùn)練就非常重要了。這個(gè)被分割成的小的子集就叫做 mini-batch,意為小批量。對每一個(gè)小批量同時(shí)執(zhí)行梯度下降會大大提高訓(xùn)練效率。在實(shí)際利用代碼實(shí)現(xiàn)的時(shí)候,小批量梯度下降算法通常包括兩個(gè)步驟:充分打亂數(shù)據(jù)(shuffle)和分組組合數(shù)據(jù)(partition)。如下圖所示。
shuffle
partition
具體代碼實(shí)現(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
小批量梯度下降的實(shí)現(xiàn)思路非常清晰,先打亂數(shù)據(jù)在分組數(shù)據(jù),需要注意的細(xì)節(jié)在于最后一個(gè)小批量所含的訓(xùn)練樣本數(shù),通常而言最后一個(gè)小批量會少于前面批量所含樣本數(shù)。
隨機(jī)梯度下降 Stochastic Gradient Descent
當(dāng)小批量所含的訓(xùn)練樣本數(shù)為 1 的時(shí)候,小批量梯度下降法就變成了隨機(jī)梯度下降法(SGD)。SGD雖然以單個(gè)樣本為訓(xùn)練單元訓(xùn)練速度會很快,但犧牲了向量化運(yùn)算所帶來的便利性,在較大數(shù)據(jù)集上效率并不高。
我們可以看一下梯度下降和隨機(jī)梯度下降在實(shí)現(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ì)上看,梯度下降法、小批量梯度下降法和隨機(jī)梯度下降法,并沒有區(qū)別。唯一的區(qū)別就在于它們執(zhí)行一次訓(xùn)練過程所需要用到的訓(xùn)練樣本數(shù)。梯度下降法用到的是全集訓(xùn)練數(shù)據(jù),隨機(jī)梯度下降則是單個(gè)樣本數(shù)據(jù),而小批量則是介于二者之間。
帶動量的梯度下降法(momentum)
正如上圖中看到的一樣,我們假設(shè)梯度下降的橫向?yàn)閰?shù) W 的下降方向,而偏置 b 的下降方向?yàn)榭v軸,我們總是希望在縱軸上的震蕩幅度小一點(diǎn),學(xué)習(xí)速度慢一點(diǎn),而在橫軸上學(xué)習(xí)速度快一點(diǎn),無論是小批量梯度下降還是隨機(jī)梯度下降,好像都不能避免這個(gè)問題。為了解決這個(gè)問題,帶動量的梯度下降法來了。帶動量的梯度下降考慮歷史梯度的加權(quán)平均值作為速率進(jìn)行優(yōu)化。執(zhí)行公式如下:
根據(jù)上述公式編寫帶動量的梯度下降法實(shí)現(xiàn)代碼:
defupdate_parameters_with_momentum(parameters,grads,v,beta,learning_rate): """ UpdateparametersusingMomentum Arguments: parameters--pythondictionarycontainingyourparameters: parameters['W'+str(l)]=Wl parameters['b'+str(l)]=bl grads--pythondictionarycontainingyourgradientsforeachparameters: grads['dW'+str(l)]=dWl grads['db'+str(l)]=dbl v--pythondictionarycontainingthecurrentvelocity: v['dW'+str(l)]=... v['db'+str(l)]=... beta--themomentumhyperparameter,scalar learning_rate--thelearningrate,scalar Returns: parameters--pythondictionarycontainingyourupdatedparameters v--pythondictionarycontainingyourupdatedvelocities """ L=len(parameters)//2#numberoflayersintheneuralnetworks #Momentumupdateforeachparameter forlinrange(L):#computevelocities 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)]#updateparameters 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)] returnparameters,v
實(shí)現(xiàn)帶動量的梯度下降的關(guān)鍵點(diǎn)有兩個(gè):一是動量是考慮歷史梯度進(jìn)行梯度下降的,二是這里的需要指定的超參數(shù)變成了兩個(gè):一個(gè)是學(xué)習(xí)率 learning_rate,一個(gè)是梯度加權(quán)參數(shù)beta。
Adam算法
Adam 全稱為 Adaptive Moment Estimation,是在帶動量的梯度下降法的基礎(chǔ)上融合了一種稱為 RMSprop(加速梯度下降)的算法而成的。相較于帶動量的梯度下降法,無論是RMSprop 還是 Adam,其中的改進(jìn)思路都在于如何讓橫軸上的學(xué)習(xí)更快以及讓縱軸上的學(xué)習(xí)更慢。RMSprop 和 Adam 在帶動量的梯度下降法的基礎(chǔ)上,引入了平方梯度,并對速率進(jìn)行了偏差糾正。具體計(jì)算公式如下:
實(shí)現(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)資料。最后用一個(gè)圖來展示各種優(yōu)化算法的效果:
本文由《自興動腦人工智能》項(xiàng)目部 凱文 投稿。
-
人工智能
+關(guān)注
關(guān)注
1792文章
47525瀏覽量
239264 -
機(jī)器學(xué)習(xí)
+關(guān)注
關(guān)注
66文章
8428瀏覽量
132851 -
深度包檢測
+關(guān)注
關(guān)注
0文章
2瀏覽量
5717
發(fā)布評論請先 登錄
相關(guān)推薦
評論