5,梯度下降優化器
????????
5,1 梯度下降在深度學習中的作用?
? ? ? ? 在深度學習中,權重W的值是否合理是由損失函數L來判斷的。L越小,表示W的設置越happy。L越大,表示W的值越unhappy。?為了讓L越來越小,常用的方法是梯度下降法。
5,2 梯度下降法的基本原理?
? ? ? ? 梯度下降法的原理是基于函數f在點P處的梯度一定是函數f在P點處的所有方向導數中增加最大的方向導數。因此,只要沿著梯度方向移動自變量 x,函數值 f 就會以最快的速度增加。要想讓x沿著梯度方向移動,只需讓自變量x加上梯度。且,不論梯度是正還是負,函數值f都會增加。?
? ? ? ? ?對梯度下降法而言,則正好相反。我們希望盡快找到函數的最小值,以及此時的自變量x。因此,我們應該讓自變量x不斷地朝著梯度的反方向移動,這樣函數值就會很快減小。而讓x沿著梯度的反方向移動的方法,則是讓x減去梯度。
以一元一次函數y=x和y=-x為例:
????????圖中x0表示自變量x的初始位置,紅點表示x0加梯度后的坐標,藍點表示x0減去梯度后的位置。對函數y=x而言,梯度為正1,x0=2加上梯度后會朝著x軸的正向移動,函數值增加。對函數y=-x而言,梯度為負1,x0=0加上梯度后會朝著x軸的反方向移動,函數值還是在增加。
? ? ? ? 對梯度下降法而言,則是希望把x0朝著藍點方向移動。對于兩幅圖中的兩個函數,我同時讓x0減去梯度,得到了圖中的藍點。如果繼續移動,則函數值會越來越低,直到函數的最小值。
? ? ? ?
import numpy as np
import matplotlib.pyplot as plt#y=x
def f(x):return xdef df(x):return 1#y=-x
def ff(x):return -xdef dff(x):return -1x=np.linspace(-np.pi,np.pi,300)#畫y=x
fig,axs=plt.subplots(1,2, figsize=(14, 6))
y=f(x)
axs[0].plot(x,y,label='y=x')
axs[0].set_title('y = x (update x with grad=1)')
axs[0].set_xlabel('x')
axs[0].set_ylabel('y')
axs[0].axhline(0, color='black', linewidth=0.5)
axs[0].axvline(0, color='black', linewidth=0.5)#當前x的位置
x0=2
axs[0].scatter(x0,f(x0),color='black', s=100,label='x0')#沿著函數增加的方向移動x(移動lr個單位的梯度)
#注意我這里是用自變量加梯度
lr=0.5
x1=x0+lr*df(x0)
x2=x0-lr*df(x0)
axs[0].scatter(x1,f(x1),color='red', s=100,label='x0+grad')
axs[0].scatter(x2,f(x2),color='cyan', s=100,label='x0-grad')
axs[0].legend()#畫y=-x
y=ff(x)
axs[1].plot(x,y,label='y=-x')
axs[1].set_title('y = -x (update x with grad=-1)')
axs[1].set_xlabel('x')
axs[1].set_ylabel('y')
axs[1].axhline(0, color='black', linewidth=0.5)
axs[1].axvline(0, color='black', linewidth=0.5)#當前x的位置
x0=0
axs[1].scatter(x0,ff(x0),color='black', s=100,label='x0')#沿著函數增加的方向移動x(移動lr個單位的梯度)
#注意我這里依然是用自變量加梯度
lr=1.5
x1=x0+lr*dff(x0)
x2=x0-lr*dff(x0)
axs[1].scatter(x1,ff(x1),color='red', s=100,label='x0+grad')
axs[1].scatter(x2,ff(x2),color='cyan', s=100,label='x0-grad')
axs[1].legend()
? ? ? ? 如果要以深度學習的損失函數為例,下圖中權重W為自變量,損失函數L(x,W)所對應的梯度如下圖中Grad(L(x,W))所示。現在,為了讓目標函數L(損失函數)的值迅速減小,就要讓自變量W沿著梯度的反方向移動。這樣一來,損失函數L的函數值就會迅速減小。即,通過改變自變量W的值,使得函數L的值小于當前值,直至等于0或更小。
? ? ? ? 換句話說,梯度下降法就是要找到能夠令損失函數L的值最小值的W。只不過在找到這一W的過程是循序漸進的(通過調整學習率),并非一蹴而就。
????????下面是我用python寫的一個二元函數的梯度下降法的例子,為了凸顯函數關于某一個維度的變化,即,為了模擬損失函數L(W,x)只關于W去更新,使得損失函數L最小化。我的這個例子是讓二元函數f(x,y)只關于x更新的demo。
import numpy as np
import matplotlib.pyplot as plt#目標函數
def f(x,y):return (x-1)**2+(y-3)**2#目標函數關于x的梯度
def f_prime(x):pfpx=2*(x-1)return pfpx#SGD只針對 x 變量進行梯度下降
def SGD(x0,y0,lr,it):points = [[x0,y0]]#x catchx=x0for _ in range(it):grad=f_prime(x)x-=lr*gradpoints.append([x,y0])return np.array(points)#main
x0=5
y0=5
lr=0.1
it=20points=SGD(x0,y0,lr,it)# 繪制目標函數
x = np.linspace(-3, 7, 400)
y = np.linspace(0, 7, 400)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)#繪制等高線圖
plt.figure(figsize=(10, 6))
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 20), cmap='jet')# 繪制梯度下降的點
plt.plot(points[:, 0], points[:, 1], 'ro-')# 顯示起點和終點
plt.plot(x0, y0, 'go', label='Starting point')
plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')#顯示令原函數為0的點
plt.plot(1,3,'k^',label='f(x,y)=0')# 圖形設置
plt.xlabel('x')
plt.ylabel('y')
plt.title('Gradient Descent Optimization for $f(x, y) = (x - 1)^2 + (y - 3)^2$ (Only updating x)')
plt.legend()
plt.grid(True)
plt.show()# 繪制三維圖像
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5, edgecolor='none')# 設置視角
ax.view_init(elev=30, azim=100) # 例如,仰角為30度,方位角為45度# 繪制梯度下降的點
points_z = f(points[:, 0], points[:, 1])
ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')# 顯示起點和終點
ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')#顯示令原函數為0的點
ax.scatter(1,3,f(1,3),color='k',marker='^',s=100,label='f(x,y)=0')# 添加顏色條
fig.colorbar(surface, shrink=0.5, aspect=10)# 圖形設置
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f(x, y)')
ax.set_title('Gradient Descent on $f(x, y) = (x - 1)^2 + (y - 3)^2$ (Only updating x)')
ax.legend()
plt.show()
運行結果:?
????????從運行結果中可以看到,因為整個迭代過程只用到了f(x,y)關于x的偏導(限制了y的更新),因此,本來應該沿著令f(x,y)為0的點[x=1,y=3](此時函數值最小等于0)移動的start point,現在只有x方向的移動,即朝著x=1移動。
????????如果能同時更新兩個自變量,則自變量會朝著目標點,即朝著函數值為0的黑三角移動。
?相應的python代碼為:
import numpy as np
import matplotlib.pyplot as plt#目標函數
def f(x,y):return (x-1)**2+(y-3)**2#目標函數關于全部自變量的梯度
def f_prime(x,y):pfpx=2*(x-1)pfpy=2*(y-3)grad=np.array([pfpx,pfpy])return grad#SGD
def SGD(x0,y0,lr,it):points = [[x0,y0]]#x catchx=x0y=y0for _ in range(it):grad=f_prime(x,y)x-=lr*grad[0]y-=lr*grad[1]points.append([x,y])return np.array(points)#main
x0=5
y0=2
lr=0.1
it=20points=SGD(x0,y0,lr,it)# 繪制目標函數
x = np.linspace(-1, 7, 400)
y = np.linspace(-1, 7, 400)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)#繪制等高線圖
plt.figure(figsize=(10, 6))
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 20), cmap='jet')# 繪制梯度下降的點
plt.plot(points[:, 0], points[:, 1], 'ro-')# 顯示起點和終點
plt.plot(x0, y0, 'go', label='Starting point')
plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')#顯示令原函數為0的點
plt.plot(1,3,'k^',label='f(x,y)=0')# 圖形設置
plt.xlabel('x')
plt.ylabel('y')
plt.title('Gradient Descent Optimization for $f(x, y) = (x - 1)^2 + (y - 3)^2$ ')
plt.legend()
plt.grid(True)
plt.show()# 繪制三維圖像
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5, edgecolor='none')# 設置視角
ax.view_init(elev=30, azim=60) # 例如,仰角為30度,方位角為45度# 繪制梯度下降的點
points_z = f(points[:, 0], points[:, 1])
ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')# 顯示起點和終點
ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')#顯示令原函數為0的點
ax.scatter(1,3,f(1,3),color='k',marker='^',s=100,label='f(x,y)=0')# 添加顏色條
fig.colorbar(surface, shrink=0.5, aspect=5)# 圖形設置
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f(x, y)')
ax.set_title('Gradient Descent on $f(x, y) = (x - 1)^2 + (y - 3)^2$ ')
ax.legend()
plt.show()
5,3?常規梯度下降法的不足之處
? ? ? ? 由于梯度下降法本身就是在不斷地沿著梯度的反方向下降,直到找到最低點。因此,當該點下降到局部最低點時,或者是下降到一個平坦區域時,就無法繼續下降了。而此時所對應的函數值并不是函數的最小值。
例如下面這種情況:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D# 目標函數
def f(x, y):return x**4 - 2 * x**2 + y**2# 目標函數關于全部自變量的梯度
def f_prime(x, y):df_dx = 4 * x**3 - 4 * xdf_dy = 2 * yreturn np.array([df_dx, df_dy])# SGD
def SGD(x0, y0, lr, it):points = [[x0, y0]]x, y = x0, y0for _ in range(it):grad = f_prime(x, y)x -= lr * grad[0]y -= lr * grad[1]points.append([x, y])return np.array(points)# 參數設置
x0 = 0
y0 = 1.5
lr = 0.5 # 較大的學習率
it = 30# 執行梯度下降法
points = SGD(x0, y0, lr, it)# 繪制等高線圖
plt.figure(figsize=(10, 6))
x = np.linspace(-2, 2, 400)
y = np.linspace(-2, 2, 400)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
contour=plt.contour(X, Y, Z, levels=np.linspace(-2, 2, 100), cmap='jet')
plt.colorbar(contour, shrink=0.8, extend='both') # 添加顏色條# 繪制梯度下降的點
plt.plot(points[:, 0], points[:, 1], 'ro-')# 顯示起點和終點
plt.plot(x0, y0, 'go', label='Starting point')
plt.plot(points[-1, 0], points[-1, 1], 'bo', label='End point')# 圖形設置
plt.xlabel('x')
plt.ylabel('y')
plt.title('Gradient Descent Optimization for $f(x, y) = x^4 - 2x^2 + y^2$ with High Learning Rate')
plt.legend()
plt.grid(True)
plt.show()# 繪制三維圖像
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surface = ax.plot_surface(X, Y, Z, cmap='RdYlBu', alpha=0.5)# 繪制梯度下降的點
points_z = f(points[:, 0], points[:, 1])
ax.plot(points[:, 0], points[:, 1], points_z, 'ro-', markersize=5, label='Gradient Descent Path')# 顯示起點和終點
ax.scatter(x0, y0, f(x0, y0), color='g', s=100, label='Starting Point')
ax.scatter(points[-1, 0], points[-1, 1], points_z[-1], color='b', s=100, label='End Point')# 添加顏色條
fig.colorbar(surface, shrink=0.5, aspect=5)# 圖形設置
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('f(x, y)')
ax.set_title('Gradient Descent on $f(x, y) = x^4 - 2x^2 + y^2$ with High Learning Rate')
ax.legend()
plt.show()
這是起始點的選擇不恰當引起的的:?
這是學習率的選擇引起的:
?5,4 SGD+momentum
? ? ? ? 為了克服傳統梯度下降法的缺點,即,碰到local min或saddle points時失效的情況。
????????SGD+Momentum相對于之前的變化在于,原有的SGD是走一步算一步梯度,然后再按這個梯度更新,因此如果走到了局部最小值處或者鞍點,當前點的梯度就為0。梯度為0,就走不下去了,只能在原點大轉。
? ? ? ? SGD+Momentum為了讓點繼續走下去,就引入了“慣性”的概念。具體來說,Momentum在更新時不光考慮當前點的梯度,也會考慮前一步的梯度。也就是在走這一步之前,把上一步的慣性也考慮進去。
? ? ? ? 比如說前面遇到的鞍點或局部最低點,因為按照前一點的梯度去更新,正好走到了這里。但如果前一點所使用的梯度是加上了上上一點的梯度的,也就是加上了慣性,那么這一步就比SGD邁的大,邁的遠。
????????就好像下圖中的黑圈,如果按照SGD的梯度走,則正好走到紅點的位置。但如果加上了上一步的慣性,步子邁的要比SGD大,就能成功的越過局部低點和鞍點繼續往下走。
?對于SGD+Momentum而言,下面兩個公式 是等價的:
5,5 Nesterov Momentum
? ? ? ? ?SGD+Momentum的出現是為了能夠越過局部最低點和鞍點,但有時候如果步子邁的太大也不好,即,沖過頭了。例如,在已經快接近全局最低點的地方,需要反復幾次才能走到最低點,也就是會出現震蕩。
????????Nesterov Momentum的做法是,不再按照當前點的梯度+前一點的梯度去走。而是按照下一步的梯度+前一點的梯度去跟新。
??
????????這樣就能防止步子邁的過大,使初始點在下降的過程中,既能越過鞍點和局部最低點,也能避免震蕩。
小結:
? ? ? ? 不論是SGD+Momentum還是Nesterov Momentum算法都是借助物理中動量的概念設計的算法。下面會介紹一些別的算法如Adagrad, RMSprop和Adam等,他們都屬于自適應算法,通過自適應的調整學習率,處理不同梯度的變化,幫助越過鞍點。
5,6?AdaGrad(Adaptive Gradient Algorithm)
????????AdaGrad(Adaptive Gradient Algorithm)是一種自適應學習率優化算法,它根據歷史的梯度來調整每次的學習率。
? ? ? ? 他的整體思路跟SGD一樣,還是用原始梯度乘以學習率去更新W。所不同的是,他用梯度的平方和的平方根作為對學習率的懲罰(懲罰就是讓學習率除以這個值)去動態的調整學習率。走的步數越多,梯度的平方和就越大,懲罰的就越厲害。?
? ? ? ? 因此,剛開始的時候學習率的衰減小,即步伐大。越是到了后面,學習率的衰減就越來越大,下降的步伐也就會越來越小。這也符合梯度下降的構想,剛開始的時候步伐大,容易越過鞍點和局部小值點,到了后面越是接近全局最小值點了,步子也正好應該小了,免得出現震蕩。
5,7 RMSProp
????????AdaGrad的效果很好,但有一個潛在的問題,也就是他的那個自適應的懲罰項。隨著步數增多,對學習率的懲罰越來越大,這也是我們希望看到了,因為,大概了這個時候已經下降到函數的最低點了。但如果沒有呢?
? ? ? ? 也就是說,如果還沒有走到谷底,對學習率的懲罰就已經很大了呢?這個時候,就好像梯度消失一樣,學習率幾乎為0,w無法更新了。相當于是眼看著就要到谷底了,卻腳崴了,走不動道了。因此,RMSProp就對AdaGrad的這一問題進行了改進。
? ? ? ? 在grad_sqared的計算中RMSProp加入了一個系數decay_rate。根據公式可以看出,當decay_rate為0時,RMSProp就退化成了AdaGrad算法,此時學習率的懲罰項依然是梯度的平方和開根號。當decay_rate為1時,每一步學習率的懲罰項都等于當前梯度的平方根,即只與當前梯度有關。
? ? ? ? 這也就是說,隨著decay_rate這個參數從0逐漸增加到1,對學習率的懲罰也越來越弱。這樣就能避免步數太多了以后崴腳的情況。
(全文完)?
--- 作者,松下J27
?參考文獻(鳴謝):?
1,Stanford University CS231n: Deep Learning for Computer Vision
2,訓練神經網絡(第二部分)_嗶哩嗶哩_bilibili
3,10 Training Neural Networks I_嗶哩嗶哩_bilibili
4,Schedule | EECS 498-007 / 598-005: Deep Learning for Computer Vision?
版權聲明:所有的筆記,可能來自很多不同的網站和說明,在此沒法一一列出,如有侵權,請告知,立即刪除。歡迎大家轉載,但是,如果有人引用或者COPY我的文章,必須在你的文章中注明你所使用的圖片或者文字來自于我的文章,否則,侵權必究。 ----松下J27?