寫在前面:梯度下降法是深度學習優化的基礎,因此本文首先探討一維優化演算法,然後擴充套件到多維。本文根據《最優化導論》(孫志強等譯)的內容整理而來,由於筆者水平和精力有限,在此只是在簡單層面做一個理解,如果要追求更嚴謹的數學理論,請大家參考相關書籍。在本文中,我們討論目標函式為一元單值函式 \(f:R\rightarrow R\) 的最優化問題(即一維問題)的迭代求解方法。多維函式的梯度優化演算法將在後續給出。
一維搜尋方法從初始搜尋點 \(x^{(0)}\) 出發,在迭代過程中,根據當前搜尋點 \(x^{(k)}\) 和目標函式 \(f\) 構建一下一個搜尋點 \(x^{(k+1)}\),從而產生一個迭代序列 \(x^{(1)}, x^{(2)},...\)。本小節主要介紹以下演算法:
- 黃金分割法(只使用目標函式值 \(f\))
- 二分法(只使用目標函式的一階導數 \(f'\))
- 牛頓法(使用目標函式的一階和二階導數 \(f',f''\))
一、 黃金分割法
黃金分割法可以求解一元單值函式\(f:R \rightarrow R\) 在閉區間 \([a_{0},b_{0}]\) 上的極小值點,該方法的前提就是在區間 \([a_{0},b_{0}]\) 記憶體在唯一單峰,即存在唯一的極小值點 \(x^{*}\)。該方法的思路如下:
- 在區間內尋找兩個點 \(a_{0}, b_{0}\),並使其滿足:\(a_{0}-a_{1}=b_{0}-b_{1}=\lambda(b_{0}-a_{0}),\lambda<\dfrac{1}{2}\);
- 計算目標函式在\(a_{1}, b_{1}\)處的值,如果 \(f(a_{1})<f(b_{1})\),則 \(x^{*}\in[a_{0},b_{1}]\);如果 \(f(a_{1})>f(b_{1})\),則 \(x^{*}\in[a_{1},b_{0}]\)。如此往復,繼續在區間內進行壓縮,直到滿足誤差條件(可以設定為壓縮後的區間長度,上一次和下一次之間的差值等);
- 以 \(f(a_{1})<f(b_{1})\) 為例,此時 \(x^{*}\in[a_{0},b_{1}]\),由於 \(a_{1}\) 已經在區間內,因此可令 \(b_{2}=a_{1}, f(b_{2})=f(a_{1})\),這樣下一次迭代時便只需要重新計算 \(a_{2}\) 和 \(f(a_{2})\) 即可。整個過程如下圖所示:
我們假設 \([a_{0}, b_{0}]\) 的區間長度為1, 進一步地,我們將上述的區間收縮關係表示成下圖:
從圖中我們可以得到如下的關係:\(\lambda(1-\lambda)=1-2\lambda\),由於 \(\lambda<\dfrac{1}{2}\),解得:\(\lambda = \dfrac{3-\sqrt{5}}{2} \approx 0.382\)。
又由於\(\dfrac{|b_{0}b_{1}|}{|a_{0}b_{1}|}=\dfrac{|a_{0}b_{1}|}{|a_{0}b_{0}|}\),即 \(\dfrac{\lambda}{1-\lambda}=\dfrac{1-\lambda}{1}\),因此這種劃分空間的方式服從黃金分割法則,所以此方法被稱為黃金分割法。
利用黃金分割法求解函式 \(f(x)=x^{4}-14x^{3}+60x^{2}-70x\) 的MATLAB程式碼如下:
function [xmin, ymin] = goldensearch( Start, End, e )
% 黃金分割法在閉區間進行一維搜尋
% 輸入引數:Start代表區間開始位置,End代表區間結束位置,e代表目標區間長度
% 輸出引數:xmin表示取得最小值時的座標,ymin表示求得的最小值
left = Start; right = End; %收斂區間
length = e; %收斂精度
r = (sqrt(5)-1)/2; %收斂比例
step = 0; % 迭代次數初始化
f = f = @(x)2*x^2+5*x-4;;
while right-left>length
step = step+1;
a1 = left+(1-r)*(right-left);
b1 = left+r*(right-left);
ya1 = feval(f, a1); %計算兩端的函式值
yb1 = feval(f, b1);
if ya1 < yb1
right = b1;
b1 = a1;
yb1 = ya1;
a1 = left+r*(right-left);
ya1 = feval(f, a1);
else
left = a1;
b1 = a1;
yb1 = ya1;
a1 = left+(1-r)*(right-left);
ya1 = feval(f, a1);
end
end
%% 輸出
xmin = (left+right)/2;
ymin = feval(f, xmin);
fprintf('程式經過%d次迭代的最小值點為%d,最小值為%d\n ',step,xmin,ymin)
%% 繪製影像
x = Start:0.01:End;
y = 2*x^2+5*x-4;
plot(x,y)
hold on
plot(xmin,ymin,'r*') % 在影像中標出極小值點
end
二、二分法
與黃金分割法相同,二分法同樣是解決一元單值函式\(f:R \rightarrow R\) 在閉區間 \([a_{0},b_{0}]\) 上的極小值點,該方法的前提就是在區間 \([a_{0},b_{0}]\) 記憶體在唯一單峰,即存在唯一的極小值點 \(x^{*}\)。但是二分法使用目標函式的一階導數來壓縮區間,因此要求函式 \(f(x)\) 是連續可微的。二分法的思路比較簡單,具體如下:
- 確定區間的中點 \(x_{0}=(left+right)/2\)
- 判斷目標函式 \(f(x)\) 的導數 \(f'(x)\) 在 \(x_{0}\) 處的正負,如果 \(f'(x_{0})>0\),那麼極小值點 \(x^{*}\in [a_{0},x_{0}]\);如果 \(f'(x_{0})<0\),那麼極小值點 \(x^{*}\in [x_{0},b_{0}]\);
- 收縮區間,繼續進行迭代求解。
利用二分法求解函式 \(f(x)=cos(x)\) 在區間 \([0, 2\pi]\) 的最小值的MATLAB程式碼如下:
function [xmin, ymin] = binarysearch( Start, End, e )
% 二分法在閉區間進行一維搜尋
% 輸入引數:Start代表區間開始位置,End代表區間結束位置,e代表目標區間長度
% 輸出引數:xmin表示取得最小值時的座標,ymin表示求得的最小值
left = Start; right = End; %收斂區間
length = e; %收斂精度
step = 0; % 迭代次數初始化
f = @(x)cos(x); % 目標函式
F = @(x)-sin(x); % 目標函式的一階導數
while right-left>length
step =step+1;
x0=(left+right)/2;
if feval(F, x0) > 0
right=x0;
else
left=x0;
end
end
%% 輸出
xmin = (left+right)/2;
ymin = feval(f, xmin);
fprintf('程式經過%d次迭代的最小值點為%d,最小值為%d\n ',step,xmin,ymin)
%% 繪製影像
x = Start:0.01:End;
y = cos(x);
plot(x,y)
hold on
plot(xmin,ymin,'r*') % 在影像中標出極小值點
end
三、牛頓法
牛頓法假設目標函式一階和二階可微,即在 \(x^{(k)}\) 處的 \(f'(x^{(k)}),f''(x^{(k)})\) 均可求得,根據泰勒級數的展開式,任意的函式都可以近似於在任意一點 \(x^{k}\) 處:
因此,求解函式 \(f(x)\) 的極小值點可以近似為求解函式 \(q(x)\) 的極小值點,設 \(q(x)\) 的極小值點為 \(x^{k+1}\),那麼 \(x^{k+1}\) 滿足:
解得:\(x^{k+1}=x^{k}-\dfrac{f'(x^{k})}{f''(x^{k})}\)
牛頓法的更新公式類似於梯度下降法:\(x^{k+1} = x^{k}-\alpha f'(x^{k})\),只不過在牛頓法中,學習率 \(\alpha\) 變成了二階導數的倒數 \(\dfrac{1}{f''(x^{k})}\)。
對於區間內所有的自變數,當 \(f''(x)>0\) 時,牛頓法能夠正常執行,但是當 \(f''(x)<0\) 時,牛頓法可能收斂到極大值點,如下圖所示:
利用牛頓法求解函式 \(f(x)=\dfrac{x^{2}}{2}\) 的MATLAB程式碼如下:
function [xmin, ymin] = newton( start, e )
% 牛頓法求解區域性最優解
% 輸入引數:start代表自變數初始值,e表示x^{k+1}與x^{k}之間的差值
% 輸出引數:xmin表示取得最小值時的座標,ymin表示求得的最小值
length = e; %收斂精度
step = 0; % 迭代次數初始化
f = @(x)x^2/2;
F = @(x)x;
while step<=50
step =step+1;
x1 = start-feval(F, start);
f_x0 = feval(f, start); f_x1 = feval(f, x1);
if abs(f_x0-f_x1) <= length
break;
end
start = x1;
x1 = start-feval(F, start);
end
%% 輸出
xmin = (start+x1)/2;
ymin = feval(f, xmin);
fprintf('程式經過%d次迭代的最小值點為%d,最小值為%d\n ',step,xmin,ymin)
%% 繪製影像
x = -20:0.0001:20;
y = x.^2/2;
plot(x,y)
hold on
plot(xmin,ymin,'r*') % 在影像中標出極小值點
end