文章目錄
聲明
本文參考何寬、念師
在開始之前,說明一下我們要做什麼。我們需要做的是分割數據集和優化梯度下降算法,所以需要做以下幾件事:
1、分割數據集
2、優化梯度下降算法:
- 2.1、不使用任何優化算法
- 2.2、mini-batch梯度下降法
- 2.3、使用具有動量的梯度下降算法
- 2.4、使用adam算法
到目前爲止,我們始終都是在使用梯度下降法學習,本文中,將使用一些更加高級的優化算法,利用這些優化算法,可以提高我們算法的收斂速度,並在最終得到更好的分離結果。這些方法可以加快學習速度,甚至可以爲成本函數提供更好的最終值,在相同的結果下,有一個好的優化算法可以是等待幾天和幾個小時之間的差異。
我們想象一下成本函數J,最小化成本就像找到丘陵的最低點,在訓練的每一步中,都會按照某個方向更新參數,以儘可能達到最低點,它類似於最快的下山的路,如下圖:
導入庫函數
import numpy as np
import matplotlib.pyplot as plt
import scipy.io
import math
import sklearn
import sklearn.datasets
import opt_utils
import testCase
#%matplotlib inline
plt.rcParams['figure.figsize']=(7.0,4.0)
plt.rcParams['image.interpolation']='nearest'
plt.rcParams['image.cmap']='gray'
梯度下降
def update_parameters_with_gd(parameters,grads,learning_rate):
"""
使用梯度下降更新參數
參數:
parameters - 字典,包含待更新的參數
parameters['W' + str(l)] = Wl
parameters['b' + str(l)] = bl
grads - 字典,包含每一個梯度值用以更新參數
learning_rate - 學習率
返回:
parameters - 字典,包含更新後的參數
"""
L = len(parameters)//2
for l in range(1,L):
parameters['W'+str(l)] = parameters['W'+str(l)] - learning_rate*grads['dW'+str(l)]
parameters['b'+str(l)] = parameters['b'+str(l)] - learning_rate*grads['db'+str(l)]
return parameters
測試:
parameters,grads,learning_rate = testCase.update_parameters_with_gd_test_case()
parameters = update_parameters_with_gd(parameters,grads,learning_rate)
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
結果:
[[ 1.63535156 -0.62320365 -0.53718766]
[-1.07799357 0.85639907 -2.29470142]] [[ 1.74604067]
[-0.75184921]] [[ 0.3190391 -0.24937038 1.46210794]
[-2.06014071 -0.3224172 -0.38405435]
[ 1.13376944 -1.09989127 -0.17242821]] [[-0.87785842]
[ 0.04221375]
[ 0.58281521]]
由梯度下降算法演變而來的有隨機梯度下降(SGD)
算法和小批量梯度下降
算法,隨機梯度下降(SGD),相當於小批量梯度下降,但是和mini-bacth不同的是其中每個小批量mini-batch僅有1個樣本,和梯度下降不同的是你一次只能在一個訓練樣本上計算梯度,而不是在整個訓練集上計算梯度。來看它們的差異:
# 不運行
# 批量梯度下降算法,又叫梯度下降
X = data_input
Y = labels
parameters = initializa_parameters(layers_dims)
for i in range(0,num_iterations):
A,cache = forward_propagation(X,parameters)
cost = compit_cosy(A,Y)
grads = backward_propagation(X,Y.cache)
parameters = update_parameters(parameters,grads)
# 隨即梯度下降算法
X = data_input
Y = labels
parameters = initializa_parameters(layers_dims)
for i in range(0,num_iterations):
for j in m:
A,cache = forward_propagation(X,parameters)
cost = compit_cosy(A,Y)
grads = backward_propagation(X,Y.cache)
parameters = update_parameters(parameters,grads)
- 梯度下降算法:無震盪,平穩地逼近最低點,但迭代次數較多。
- 隨機梯度下降法:上下波動大。在更新梯度之前,只使用1個訓練樣本。當訓練集較大時,隨機梯度下降可以更快,但是參數會向最小值擺動,而不是平穩地收斂。
- mini-batch梯度下降法:上下波動小。綜合了梯度下降法和隨機梯度下降法,在每次迭代中,既不是選擇全部的數據來學習,也不是選擇一個樣本來學習,而是把所有的數據集分割爲一小塊一小塊來學習,它會隨機選擇一小塊mini-batch,塊大小一般爲2的n次方倍。一方面,充分利用GPU的並行性,另一方面,不會讓計算時間特別長。如下圖所示:
mini-batch梯度下降法
要使用mini-batch梯度下降,得經過兩個步驟:
1、把訓練集打亂,但是X和Y依舊是一一對應,之後,X的第i列是與Y中的第i個標籤對應的樣本。亂序步驟確保將樣本被隨機分成不同的小批次。如下圖,X和Y的每一列代表一個樣本。
2、切分,把訓練集打亂後,就可以對它進行切分了。這裏切分的大小是64,如下圖:
def random_mini_batches(X,Y,mini_batch_size=64,seed=0):
"""
從(X,Y)中創建一個隨機的mini_batch列表
參數:
X - 輸入數據,維度爲(輸入節點數量,樣本的數量)
Y - 對應的是x的標籤,[1|0](藍|紅),維度爲(1,樣本的數量)
mini_bacthes - 每個mini_bacth的樣本數量
返回:
mini_bacthes - 一個同步列表,維度爲(mini_batch_X,mini_batch_Y)
"""
np.random.seed(seed)
m = X.shape[1]
mini_batches = []
permutation = list(np.random.permutation(m))
shuffled_X = X[:,permutation]
shuffled_Y = Y[:,permutation].reshape((1,m))
num_complete_minibatches = math.floor(m/mini_batch_size)
for k in range(0,num_complete_minibatches):
mini_batch_X = shuffled_X[:,k*mini_batch_size:(k+1)*mini_batch_size]
mini_batch_Y = shuffled_Y[:,k*mini_batch_size:(k+1)*mini_batch_size]
mini_batch = (mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
if m%mini_batch_size!=0:
mini_batch_X = shuffled_X[:,mini_batch_size*num_complete_minibatches:m]
mini_batch_Y = shuffled_Y[:,mini_batch_size*num_complete_minibatches:m]
mini_batch = (mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
測試:
X_assess,Y_assess,mini_batch_size = testCase.random_mini_batches_test_case()
mini_batches = random_mini_batches(X_assess,Y_assess,mini_batch_size)
print(mini_batches[0][0].shape,mini_batches[0][1].shape,mini_batches[1][0].shape,mini_batches[1][1].shape,mini_batches[2][0].shape,mini_batches[2][1].shape)
結果:
(12288, 64) (1, 64) (12288, 64) (1, 64) (12288, 20) (1, 20)
具有動量的梯度下降法
由於小批量梯度下降只看到了一個子集的參數更新,更新的方向有一定的差異,所以小批量梯度下降的路徑將“震盪地”走向收斂,使用動量可以減小這些震盪,動量考慮了過去的梯度以平滑更新,我們將把以前梯度的方向
存儲在變量v
中,從形式上講,這將是前面的梯度的指數加權平均值
。我們也可以把v看作是滾下坡的速度,根據山坡的坡度建立動量。如下圖所示:
我們不僅要觀察梯度,還要讓v影響梯度,然後朝着v方向前進一步,儘量讓前進的方向指向最小值,既然我們要影響梯度的方向,而梯度需要使用到dW和db,那麼我們就要建立一個和dW、db相同結構的變量來影響他們,我們現在來進行初始化:
def initialize_velocity(parameters):
"""
初始化速度,velocity是一個字典:
- keys:‘dW1’,‘db1’,...,'dWL','dbL'
- values:與相應的梯度/參數 維度相同的值爲零的矩陣。
參數:
parameters - 一個字典,包含了以下參數:
parameters['W'+str(1)] = W1
parameters['b'+str(1)] = b1
返回:
v - 一個字典變量,包含了以下參數:
v['dW'+str(1)] = dW1的速度
v['db'+str(1)] = db1的速度
"""
L = len(parameters)//2
v = {}
for l in range(L):
v['dW'+str(l+1)] = np.zeros_like(parameters['W'+str(l+1)])
v['db'+str(l+1)] = np.zeros_like(parameters['b'+str(l+1)])
return v
測試:
parameters = testCase.initialize_velocity_test_case()
v = initialize_velocity(parameters)
print(v['dW1'],v['db1'],v['dW2'],v['db2'])
結果:
[[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]] [[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]
[0.]]
既然初始化完成了,我們就開始影響梯度的方向,需要使用以下公式:
其中:
- l是當前神經網絡的層數
- 是動量,是一個實數,通常爲0.9
- 是學習率
def update_parameters_with_momentum(parameters,grads,v,beta,learning_rate):
"""
使用動量更新參數
參數:
parameters - 一個字典變量,具有以下字段
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
grads - 一個包含梯度值的字典變量,具有以下字段
grads["dW" + str(l)] = dWl
grads["db" + str(l)] = dbl
v - 包含當前速度的字典變量,具有以下字段
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
beta - 超參數,動量,實數
learning_rate - 學習率,實數
返回:
parameters - 更新後的參數字典
v - 更新後的速度變量
"""
L = len(parameters)//2
for l in range(L):
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)]
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
測試:
parameters,grads,v = testCase.update_parameters_with_momentum_test_case()
parameters,v = update_parameters_with_momentun(parameters,grads,v,beta=0.9,learning_rate=0.01)
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
print(v['dW1'],v['db1'],v['dW2'],v['db2'])
結果:
[[ 1.62544598 -0.61290114 -0.52907334]
[-1.07347112 0.86450677 -2.30085497]] [[ 1.74493465]
[-0.76027113]] [[ 0.31930698 -0.24990073 1.4627996 ]
[-2.05974396 -0.32173003 -0.38320915]
[ 1.13444069 -1.0998786 -0.1713109 ]] [[-0.87809283]
[ 0.04055394]
[ 0.58207317]]
[[-0.11006192 0.11447237 0.09015907]
[ 0.05024943 0.09008559 -0.06837279]] [[-0.01228902]
[-0.09357694]] [[-0.02678881 0.05303555 -0.06916608]
[-0.03967535 -0.06871727 -0.08452056]
[-0.06712461 -0.00126646 -0.11173103]] [[0.02344157]
[0.16598022]
[0.07420442]]
需要注意的是速度v是用0來初始化的,因此,該算法需要經過幾次迭代才能把速度提升上來並開始跨越更大步伐。當=0時,該算法相當於沒有使用動量梯度下降算法的標準的梯度下降算法。當越大時,說明平滑的作用越明顯,通常0.9是比較合適的值,那如何才能在開始的時候就保持很快的速度向最小誤差哪裏前進呢?接下來可以看看Adam算法。
Adam優化後的梯度下降法
本算法結合了RMSProp算法與Momentum算法。步驟:
1、計算以前的梯度的指數加權平均值,並將其存儲在變量v(偏正校驗前)和(偏正校驗後)中。
2、計算以前梯度的平方的指數加權平均值,並將其存儲在變量s(偏正校驗前)和(偏正校驗後)中。
3、根據1和2更新參數。
公式如下:
其中:
- t:當前迭代次數
- l:當前神經網絡的層數
- :學習率
- :一個非常小的數,用於避免除零操作,一般爲。
def initialize_adam(parameters):
"""
初始化v和s,它們都是字典類型的變量,都包含了以下字段:
- keys:‘dW1’,‘db1’,...,'dWL','dbL'
- values:與相應的梯度/參數 維度相同的值爲零的矩陣。
參數:
parameters - 包含了以下參數的字典變量
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
返回:
v - 包含梯度的指數加權平均值,字段如下:
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
s - 包含平方梯度的指數加權平均值,字段如下:
s["dW" + str(l)] = ...
s["db" + str(l)] = ...
"""
L = len(parameters)//2
v = {}
s = {}
for l in range(L):
v['dW'+str(l+1)] = np.zeros_like(parameters['W'+str(l+1)])
v['db'+str(l+1)] = np.zeros_like(parameters['b'+str(l+1)])
s['dW'+str(l+1)] = np.zeros_like(parameters['W'+str(l+1)])
s['db'+str(l+1)] = np.zeros_like(parameters['b'+str(l+1)])
return v,s
測試:
parameters = testCase.initialize_adam_test_case()
v,s = initialize_adam(parameters)
print(v['dW1'],v['db1'],v['dW2'],v['db2'])
print(s['dW1'],s['db1'],s['dW2'],s['db2'])
結果:
[[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]] [[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]
[0.]]
[[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]] [[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]
[0.]]
def update_parameters_with_adam(parameters,grads,v,s,t,beta1=0.9,beta2=0.999,epsilon=1e-8,learning_rate=0.01):
"""
使用adam更新參數:
參數:
parameters - 一個字典變量,具有以下字段
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
grads - 一個包含梯度值的字典變量,具有以下字段
grads["dW" + str(l)] = dWl
grads["db" + str(l)] = dbl
v - Adam的變量,第一個梯度的移動平均值,是一個字段類型的變量
s - Adam的變量,平方梯度的移動平均值,是一個字典類型的變量
t - 當前迭代的次數
beta1 - 超參數,動量,用於第一階段,使得曲線的Y值不從0開始
beta2 - RMSprop的一個參數,超參數
learning_rate - 學習率,實數
epsilon - 防止除零操作(分母爲0)
返回:
parameters - 更新後的參數
v - 第一個梯度的移動平均值,是一個字典類型的變量
s - 平方梯度的移動平均值,是一個字典類型的變量
"""
L = len(parameters)//2
v_corrected = {}
s_corrected = {}
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)]
v_corrected['dW'+str(l+1)] = v['dW'+str(l+1)]/(1-np.power(beta1,t))
v_corrected['db'+str(l+1)] = v['db'+str(l+1)]/(1-np.power(beta1,t))
s['dW'+str(l+1)] = beta2*s['dW'+str(l+1)]+(1-beta2)*np.square(grads['dW'+str(l+1)])
s['db'+str(l+1)] = beta2*s['db'+str(l+1)]+(1-beta2)*np.square(grads['db'+str(l+1)])
s_corrected['dW'+str(l+1)] = s['dW'+str(l+1)]/(1-np.power(beta2,t))
s_corrected['db'+str(l+1)] = s['db'+str(l+1)]/(1-np.power(beta2,t))
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
測試:
parameters , grads , v , s = testCase.update_parameters_with_adam_test_case()
parameters,v,s = update_parametes_with_adam(parameters,grads,v,s,t=2)
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
print(v['dW1'],v['db1'],v['dW2'],v['db2'])
print(s['dW1'],s['db1'],s['dW2'],s['db2'])
結果:
[[ 1.63178673 -0.61919778 -0.53561312]
[-1.08040999 0.85796626 -2.29409733]] [[ 1.75225313]
[-0.75376553]] [[ 0.32648046 -0.25681174 1.46954931]
[-2.05269934 -0.31497584 -0.37661299]
[ 1.14121081 -1.09245036 -0.16498684]] [[-0.88529978]
[ 0.03477238]
[ 0.57537385]]
[[-0.11006192 0.11447237 0.09015907]
[ 0.05024943 0.09008559 -0.06837279]] [[-0.01228902]
[-0.09357694]] [[-0.02678881 0.05303555 -0.06916608]
[-0.03967535 -0.06871727 -0.08452056]
[-0.06712461 -0.00126646 -0.11173103]] [[0.02344157]
[0.16598022]
[0.07420442]]
[[0.00121136 0.00131039 0.00081287]
[0.0002525 0.00081154 0.00046748]] [[1.51020075e-05]
[8.75664434e-04]] [[7.17640232e-05 2.81276921e-04 4.78394595e-04]
[1.57413361e-04 4.72206320e-04 7.14372576e-04]
[4.50571368e-04 1.60392066e-07 1.24838242e-03]] [[5.49507194e-05]
[2.75494327e-03]
[5.50629536e-04]]
測試
加載數據集
train_X, train_Y = opt_utils.load_dataset(is_plot=True)
定義模型
def model(X,Y,layers_dims,optimizer,learning_rate=0.007,mini_batch_size=64,beta=0.9,beta1=0.9,beta2=0.999,epsilon=1e-8,num_epochs=10000,print_cost=True,is_plot=True):
"""
可以運行在不同優化器模式下的3層神經網絡模型
參數:
X - 輸入數據,維度爲(2,樣本的數量)
Y - 對應的是x的標籤,[1|0](藍|紅),維度爲(1,樣本的數量)
layers_dims - 包含層數和節點數量的列表
optimizer - 字符串類型的參數,用於選擇優化類型,['gd'|'momentum'|'adam']
learning_rate - 學習率
mini_batch_size - 每個小批量數據集的大小
beta - 用於動量優化的一個超參數
beta1 - 用於計算梯度後的指數衰減的估計的超參數
beta2 - 用於平方梯度後的指數衰減的估計的超參數
epsilon - 用於在Adam中避免除零操作的超參數,一般不更改
num_epochs - 整個訓練集的遍歷次數,相當於之前的num_iteration
print_cost - 是否打印誤差值,每遍歷1000次數據集打印一次,但是每100次記錄一個誤差值,又稱每1000代打印一次
is_plot - 是否繪製出曲線圖
返回:
parameters - 包括了學習後的參數
"""
L = len(layers_dims)
costs = []
t = 0
seed = 10
parameters = opt_utils.initialize_parameters(layers_dims)
if optimizer=='gd':
pass
elif optimizer=='momentum':
v = initialize_velocity(parameters)
elif optimizer=='adam':
v,s = initialize_adam(parameters)
else:
print('optimizer參數錯誤,程序退出。')
exit(1)
for i in range(num_epochs):
seed = seed + 1
minibatches = random_mini_batches(X,Y,mini_batch_size,seed)
for minibatch in minibatches:
minibatch_X,minibatch_Y = minibatch
A3,caches = opt_utils.forward_propagation(minibatch_X,parameters)
cost = opt_utils.compute_cost(A3,minibatch_Y)
grads = opt_utils.backward_propagation(minibatch_X,minibatch_Y,caches)
if optimizer=='gd':
parameters = update_parameters_with_gd(parameters,grads,learning_rate)
elif optimizer=='momentum':
parameters,v = update_parameters_with_momentum(parameters,grads,v,beta,learning_rate)
elif optimizer=='adam':
t = t + 1
parameters,v,s = update_parameters_with_adam(parameters,grads,v,s,t,learning_rate,beta1,beta2,epsilon)
if i%100==0:
costs.append(cost)
if print_cost and i%1000==0:
print('i=',i,',cost=',cost)
if is_plot:
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('epochs(per 100)')
plt.title("learning rate = "+str(learning_rate))
plt.show()
return parameters
mini-batch梯度下降測試
layers_dims = [train_X.shape[0],5,2,1]
parameters = model(train_X,train_Y,layers_dims,optimizer='gd')
predictions = opt_utils.predict(train_X,train_Y,parameters)
結果:
i= 0 ,cost= 0.6906348872353164
i= 1000 ,cost= 0.529515191486897
i= 2000 ,cost= 0.6179369981287265
i= 3000 ,cost= 0.42394384381259426
i= 4000 ,cost= 0.45474164616291884
i= 5000 ,cost= 0.4904732180685857
i= 6000 ,cost= 0.4318205596870036
i= 7000 ,cost= 0.45309933341108904
i= 8000 ,cost= 0.4469028252590609
i= 9000 ,cost= 0.45757140084424625
Accuracy: 0.797
具有動量的梯度下降測試
layers_dims = [train_X.shape[0],5,2,1]
parameters = model(train_X,train_Y,layers_dims,optimizer='momentum')
predictions = opt_utils.predict(train_X,train_Y,parameters)
結果:
i= 0 ,cost= 0.6907324373984821
i= 1000 ,cost= 0.4492654516390006
i= 2000 ,cost= 0.3758336613787371
i= 3000 ,cost= 0.2615252863821062
i= 4000 ,cost= 0.30240281858054535
i= 5000 ,cost= 0.14262280299160454
i= 6000 ,cost= 0.14335101507523898
i= 7000 ,cost= 0.041849280242154195
i= 8000 ,cost= 0.13333187378180045
i= 9000 ,cost= 0.19900064349545424
Accuracy: 0.797
Adam優化後的梯度下降測試
layers_dims = [train_X.shape[0],5,2,1]
parameters = model(train_X,train_Y,layers_dims,optimizer='adam')
predictions = opt_utils.predict(train_X,train_Y,parameters)
結果:
i= 0 ,cost= 0.6907422847195794
i= 1000 ,cost= 0.7279949298531373
i= 2000 ,cost= 0.6852897046889893
i= 3000 ,cost= 0.6895006058785884
i= 4000 ,cost= 0.7039897353411535
i= 5000 ,cost= 0.7040415668136771
i= 6000 ,cost= 0.6989152351921109
i= 7000 ,cost= 0.7169684815916847
i= 8000 ,cost= 0.6927794629984747
i= 9000 ,cost= 0.6933613021850875
Accuracy: 0.94
總結
優化算法 | 準確度 | 曲線平滑度 |
---|---|---|
mini-batch梯度下降 | 79.7% | 震盪 |
具有動量的梯度下降 | 79.7% | 震盪 |
Adam優化後的梯度下降 | 94% | 平滑 |