梯度下降
由於梯度下降法中負梯度方向作為變量的變化方向,所以有可能導 致最終求解的值是局部最優解,所以在使用梯度下降的時候,一般需 要進行一些調優策略: 學習率的選擇:
- 學習率過大,表示每次迭代更新的時候變化比較大,有可能 會跳過最優解;
- 學習率過小,表示每次迭代更新的時候變化比較小,就會導 致迭代速度過慢,很長時間都不能結束;
算法初始參數值的選擇:
- 初始值不同,最終獲得的最小值也有可能不同,因為梯度下降法求解的是局部最優解,所以一般情況下,選擇多次不同初始值 運行算法,並最終返回損失函數最小情況下的結果值;
標准化:由於樣本不同特征的取值范圍不同,可能會導致在各個不同參數上 迭代速度不同,為了減少特征取值的影響,可以將特征進行標准化操作。
梯度下降二維案例
import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl mpl.rcParams['font.sans-serif'] = [u'simHei'] mpl.rcParams['axes.unicode_minus'] = False def f(x): return 0.5*(x-0.25)**2 def h(x): return x-1/4 X = [] Y = [] x =2 # 此時的學習率,是由一定區間的,過小會導致性能變慢,過大可能發散 step = 0.8 f_change = f(x) f_current = f(x) X.append(x) Y.append(f_current) # 當變化值小於1e-10的時候停止,也就是梯度 while f_change > 1e-10 and len(X)<100: # 這里是梯度下降的變化程度x = x - k*f(x)' x = x - step * h(x) tmp = f(x) f_change = np.abs(f_current - tmp) f_current = tmp X.append(x) Y.append(f_current) fig = plt.figure() X2 = np.arange(-2.1,2.65,0.05) Y2 = 0.5*(X2-0.25)**2 plt.plot(X2,Y2,'-',color="#666666",linewidth = 2) plt.plot(X,Y,'bo--') plt.title("$y= 0.5*(x-0.25)^2$通過梯度下降法得到目標值x=%.2f,y=%.2f迭代次數%d"%(x,f_current,len(X))) plt.show()
梯度下降三維案例
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D def f(x, y): return x ** 2 + y ** 2 def h(t): return 2 * t X = [] Y = [] Z = [] x = 2 y = 2 f_change = x ** 2 + y ** 2 f_current = f(x, y) step = 0.1 X.append(x) Y.append(y) Z.append(f_current) while f_change > 1e-10: # 對於各自未知數求偏導 x = x - step * h(x) y = y - step * h(y) f_change = np.abs(f_current - f(x,y)) f_current = f(x,y) X.append(x) Y.append(y) Z.append(f_current) fig = plt.figure() ax = Axes3D(fig) X2 = np.arange(-2,2,0.2) Y2 = np.arange(-2,2,0.2) X2,Y2 = np.meshgrid(X2,Y2) Z2 = X2**2 + Y2**2 ax.plot_surface(X2,Y2,Z2,rstride=1,cstride=1,cmap='rainbow') ax.plot(X,Y,Z,'ro--') ax.set_title("") plt.show()
再機器學習中梯度算法簡直是一個利刃,能夠幫助我們求解很多參數
分析解題步驟
目標函數θ求解 初始化θ(隨機初始化,可以初始為0) 沿着負梯度方向迭代,更新后的θ使J(θ)更小
梯度下降研究每次迭代N個數
BGD
from matplotlib import pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() w = np.arange(-5, 8, .25) b = np.arange(-15, 15, .25) x = np.array([1,2,3,4]) y = np.array([3.2,4.7,7.3,8.5]) w, b = np.meshgrid(w, b) R = 0 for i in range(len(x)): R += (w*x[i]+b-y[i])**2 R /= len(x) a = R<50 R = ~a*50+R*a ax = plt.subplot() plt.contourf(w, b, R,10,alpha=0.5) plt.title("cost(w,b) = 1/N * Σ(w*x_i+b-y_i)^2") w = 3.5 b = 3.5 W = [] B = [] for i in range(2000): W.append(w) B.append(b) w -= 0.02*1/len(x)*sum((w*x+b-y)*x) b -= 0.02*1/len(x)*sum((w*x+b-y)) print(w,b) plt.plot(W,B,"r*") plt.xlabel("w") plt.ylabel("b") plt.show()
SGD
from matplotlib import pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D import random fig = plt.figure() w = np.arange(-2, 5, .25) b = np.arange(-15, 15, .25) x = np.array([1,2,3,4]) y = np.array([3.2,4.7,7.3,8.5]) w, b = np.meshgrid(w, b) R = 0 for i in range(len(x)): R += (w*x[i]+b-y[i])**2 R /= len(x) a = R<50 R = ~a*50+R*a ax = plt.subplot() plt.contourf(w, b, R,10,alpha=0.5) plt.title("cost(w,b) = 1/N * Σ(w*x_i+b-y_i)^2") w = 3.5 b = 3.5 W = [] B = [] for i in range(2000): W.append(w) B.append(b) p = random.randint(0, len(x)-1) w -= 0.02*(w*x[p]+b-y[p])*x[p] b -= 0.02*(w*x[p]+b-y[p]) print(w,b) plt.plot(W,B,"r*") plt.xlabel("w") plt.ylabel("b") plt.show()
- SGD速度比BGD快(迭代次數少)
- SGD在某些情況下(全局存在多個相對最優解/J(θ)不是一個二次),SGD有可能跳 出某些小的局部最優解,所以不會比BGD壞
- BGD一定能夠得到一個局部最優解(在線性回歸模型中一定是得到一個全局最優 解),SGD由於隨機性的存在可能導致最終結果比BGD的差
- 注意:優先選擇SGD
MBGD
如果即需要保證算法的訓練過程比較快,又需要保證最終參數訓練的准確率,而 這正是小批量梯度下降法(Mini-batch Gradient Descent,簡稱MBGD)的初 衷。MBGD中不是每拿一個樣本就更新一次梯度,而且拿b個樣本(b一般為10)的 平均梯度作為更新方向。
BGD、SGD、MBGD的區別:
- 當樣本量為m的時候,每次迭代BGD算法中對於參數值更新一次,SGD算法 中對於參數值更新m次,MBGD算法中對於參數值更新m/n次,相對來講 SGD算法的更新速度最快;
- SGD算法中對於每個樣本都需要更新參數值,當樣本值不太正常的時候,就 有可能會導致本次的參數更新會產生相反的影響,也就是說SGD算法的結果 並不是完全收斂的,而是在收斂結果處波動的;
- SGD算法是每個樣本都更新一次參數值,所以SGD算法特別適合樣本數據量 大的情況以及在線機器學習(Online ML)。
三個概念
導數反映的是函數y=f(x)在某一點處沿x軸正方向的變化率。
比如y=x2,在x=1處的導數=2。
導數是通過極限來定義的,某一點的導數=tanψ,但是前提是△x趨近於0,此時tanψ=tanα=該點導數,公式如下:
偏導
在多元函數中,偏導數指的是函數y(x1,x2,…,xn)沿某一坐標軸(x1,x2,…,xn)正方向的變化率。
比如,在(1,2)處的在x方向上的偏導數:
截取y=2的曲線,可以發現在x方向的導數=2
導數和偏導數都是沿坐標軸正方向的變化率。那么當我們討論函數沿任意方向的變化率時,也就引出了方向導數的定義,即:某一點在某一趨近方向上的導數值。
比如,可以計算函數在點A(2,2,8)的導數。
梯度
梯度是一個向量,表示某一函數在該點處的方向導數沿着該方向取得最大值,即函數在該點處沿着該方向(此梯度的方向)變化最快,變化率最大(為該梯度的模)。
比如z=x2+y2+xy在點A(2,2,12)處的梯度為
對所有的參數更新使用同樣的學習率。對於稀疏數據或者特征,有時我們可能想更新快一些對於不經常出現的特征,對於常出現的特征更新慢一些,這時候SGD就不太能滿足要求了
3.SGD容易收斂到局部最優
最直觀的理解就是,若當前的梯度方向與累積的歷史梯度方向一致,則當前的梯度會被加強,從而這一步下降的幅度更大。若當前的梯度方向與累積的梯度方向不一致,則會減弱當前下降的梯度幅度。
其中,μ是動量因子
- 下降初期,使用上一次參數更新,下降方向一致則乘上較大的μ能夠進行很好的加速
- 下降中后期時,在局部最小值來回震盪的時候,gradient→0,μ使得更新幅度增大,跳出局部最優解
- 在梯度改變方向的時候,μ能夠減少更新
總而言之,momentum項能夠在相關方向加速SGD,抑制振盪,從而加快收斂。
代碼:
from matplotlib import pyplot as plt import numpy as np fig = plt.figure() x = np.arange(-0.8, 1.2, 0.025) plt.plot(x,2*x**4-x**3-x**2) plt.title("y = 2*x^4-x^3-x^2") def f(x): return 2*x**4-x**3-x**2 def h(x): return 8*x**3 - 3*x**2 - 2*x η = 0.05 α = 0.9 v = 0 x = -0.8 iters = 0 X = [] Y = [] while iters<12: iters+=1 X.append(x) Y.append(f(x)) v = α*v - η*h(x) x = x + v print(iters,x) plt.plot(X,Y) plt.scatter(X[-1],Y[-1]) plt.show()
Adagrad法
Adagrad其實是對學習率進行了一個約束。即:
特點:
- 前期根號下的值較小的時候, 學習率較大,能夠放大梯度
- 后期根號下的值較大的時候,學習率較小,能夠約束梯度
- 適合處理稀疏梯度
缺點:
- 公式上可以看出,仍依賴於人工設置一個全局學習率η設置過大整個式子過於敏感,對梯度的調節太大
- 中后期,分母上梯度平方的累加將會越來越大,使gradient→0,訓練過慢。
from matplotlib import pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() x = np.arange(-4, 4, 0.025) plt.plot(x,x**2) plt.title("y = x^2") def f(x): return x**2 def h(x): return 2*x η = 0.5 ε = 0.1 x = 4 iters = 0 sum_square_grad = 0 X = [] Y = [] while iters<40: iters+=1 X.append(x) Y.append(f(x)) sum_square_grad += h(x)**2 x = x - η/np.sqrt(sum_square_grad+ε)*h(x) print(iters,x) plt.plot(X,Y,"ro") ax = plt.subplot() for i in range(len(X)): if i%8==0: ax.text(X[i], (X[i])**2, "({:.3f},{:.3f})".format(X[i], (X[i])**2), color='red') plt.show()
RMSprop
當β等於0.5的時候,g再求根的話就變成了求RMSE(均方根)
from matplotlib import pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() x = np.arange(-4, 4, 0.025) plt.plot(x,x**2) plt.title("y = x^2") def f(x): return x**2 def h(x): return 2*x g = 1 x = 4 ρ = 0.9 η = 0.01 ε = 10e-10 iters = 0 X = [] Y = [] while iters<420: iters+=1 X.append(x) Y.append(f(x)) g = ρ*g+(1-ρ)*h(x)**2 x = x - η/np.sqrt(g+ε)*h(x) print(iters,x) ax = plt.subplot() for i in range(len(X)): if i % 40==0: plt.scatter(X[i], Y[i]) ax.text(X[i], (X[i])**2, "({:.3f},{:.3f})".format(X[i], (X[i])**2), color='red') plt.show()
優點:
- RMSprop依然依賴於全局學習率
- RMSprop算是Adagrad的變體,效果趨於二者之間
- 適合處理非平穩目標
- 對於RNN效果很好
Adam實際上是把momentum和RMSprop結合起來的一種算法也就是帶有動量項的RMSprop
假設N元函數f(x),針對一個自變量研究Adam梯度下降的迭代過程,
優點:
- 結合了Adagrad善於處理稀疏梯度和RMSprop善於處理非平穩目標的優點
- 對內存需求較小
- 為不同的參數計算不同的自適應學習率
- 也適用於大多非凸優化
- 適用於大數據集和高維空間
from matplotlib import pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() x = np.arange(-4, 4, 0.025) plt.plot(x,x**2) plt.title("y = x^2") def f(x): return x**2 def h(x): return 2*x x = 4 m = 0 v = 0 β1 = 0.9 β2 = 0.999 η = 0.061 ε = 10e-8 iters = 0 X = [] Y = [] while iters<120: iters+=1 X.append(x) Y.append(f(x)) m = β1*m + (1-β1)*h(x) v = β2*v + (1-β2)*h(x)**2 m_het = m/(1-β1**iters) v_het = v/(1-β2**iters) x = x - η/np.sqrt(v_het+ε)*m_het print(iters,x) ax = plt.subplot() for i in range(len(X)): if i %20==0: plt.scatter(X[i],Y[i],) ax.text(X[i], (X[i])**2, "({:.3f},{:.3f})".format(X[i], (X[i])**2), color='red') plt.show()