系統學習《動手學深度學習》點擊下面這個鏈接,有全目錄哦~
https://blog.csdn.net/Shine_rise/article/details/104754764
文章目錄
11.6 Momentum
在 Section 11.4 中,我們提到,目標函數有關自變量的梯度代表了目標函數在自變量當前位置下降最快的方向。因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根據自變量當前位置,沿着當前位置的梯度更新自變量。然而,如果自變量的迭代方向僅僅取決於自變量當前位置,這可能會帶來一些問題。對於noisy gradient,我們需要謹慎的選取學習率和batch size, 來控制梯度方差和收斂的結果。
An ill-conditioned Problem
Condition Number of Hessian Matrix:
where is the maximum amd minimum eignvalue of Hessian matrix.
讓我們考慮一個輸入和輸出分別爲二維向量和標量的目標函數:
Maximum Learning Rate
- For , according to convex optimizaiton conclusions, we need step size .
- To guarantee the convergence, we need to have .
Supp: Preconditioning
在二階優化中,我們使用Hessian matrix的逆矩陣(或者pseudo inverse)來左乘梯度向量 ,這樣的做法稱爲precondition,相當於將 映射爲一個單位矩陣,擁有分佈均勻的Spectrum,也即我們去優化的等價標函數的Hessian matrix爲良好的identity matrix。
與Section 11.4一節中不同,這裏將係數從減小到了。下面實現基於這個目標函數的梯度下降,並演示使用學習率爲時自變量的迭代軌跡。
%matplotlib inline
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
import torch
eta = 0.4
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2, s1, s2):
return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
epoch 20, x1 -0.943467, x2 -0.000073
可以看到,同一位置上,目標函數在豎直方向(軸方向)比在水平方向(軸方向)的斜率的絕對值更大。因此,給定學習率,梯度下降迭代自變量時會使自變量在豎直方向比在水平方向移動幅度更大。那麼,我們需要一個較小的學習率從而避免自變量在豎直方向上越過目標函數最優解。然而,這會造成自變量在水平方向上朝最優解移動變慢。
下面我們試着將學習率調得稍大一點,此時自變量在豎直方向不斷越過最優解並逐漸發散。
Solution to ill-condition
- Preconditioning gradient vector: applied in Adam, RMSProp, AdaGrad, Adelta, KFC, Natural gradient and other secord-order optimization algorithms.
- Averaging history gradient: like momentum, which allows larger learning rates to accelerate convergence; applied in Adam, RMSProp, SGD momentum.
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
epoch 20, x1 -0.387814, x2 -1673.365109
Momentum Algorithm
動量法的提出是爲了解決梯度下降的上述問題。設時間步 的自變量爲 ,學習率爲 。
在時間步 ,動量法創建速度變量 ,並將其元素初始化成 0。在時間步 ,動量法對每次迭代的步驟做如下修改:
Another version:
其中,動量超參數 滿足 。當 時,動量法等價於小批量隨機梯度下降。
在解釋動量法的數學原理前,讓我們先從實驗中觀察梯度下降在使用動量法後的迭代軌跡。
def momentum_2d(x1, x2, v1, v2):
v1 = beta * v1 + eta * 0.2 * x1
v2 = beta * v2 + eta * 4 * x2
return x1 - v1, x2 - v2, v1, v2
eta, beta = 0.4, 0.5
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
epoch 20, x1 -0.062843, x2 0.001202
可以看到使用較小的學習率 和動量超參數 時,動量法在豎直方向上的移動更加平滑,且在水平方向上更快逼近最優解。下面使用較大的學習率 ,此時自變量也不再發散。
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
epoch 20, x1 0.007188, x2 0.002553
Exponential Moving Average
爲了從數學上理解動量法,讓我們先解釋一下指數加權移動平均(exponential moving average)。給定超參數 ,當前時間步 的變量 是上一時間步 的變量 和當前時間步另一變量 的線性組合:
我們可以對 展開:
Supp
Approximate Average of Steps
令 ,那麼 。因爲
所以當 時,,如 。如果把 當作一個比較小的數,我們可以在近似中忽略所有含 和比 更高階的係數的項。例如,當 時,
因此,在實際中,我們常常將 看作是對最近 個時間步的 值的加權平均。例如,當 時, 可以被看作對最近20個時間步的 值的加權平均;當 時, 可以看作是對最近10個時間步的 值的加權平均。而且,離當前時間步 越近的 值獲得的權重越大(越接近1)。
由指數加權移動平均理解動量法
現在,我們對動量法的速度變量做變形:
Another version:
由指數加權移動平均的形式可得,速度變量 實際上對序列 做了指數加權移動平均。換句話說,相比於小批量隨機梯度下降,動量法在每個時間步的自變量更新量近似於將前者對應的最近 個時間步的更新量做了指數加權移動平均後再除以 。所以,在動量法中,自變量在各個方向上的移動幅度不僅取決當前梯度,還取決於過去的各個梯度在各個方向上是否一致。在本節之前示例的優化問題中,所有梯度在水平方向上爲正(向右),而在豎直方向上時正(向上)時負(向下)。這樣,我們就可以使用較大的學習率,從而使自變量向最優解更快移動。
Implement
相對於小批量隨機梯度下降,動量法需要對每一個自變量維護一個同它一樣形狀的速度變量,且超參數裏多了動量超參數。實現中,我們將速度變量用更廣義的狀態變量states
表示。
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_momentum_states():
v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
v_b = torch.zeros(1, dtype=torch.float32)
return (v_w, v_b)
def sgd_momentum(params, states, hyperparams):
for p, v in zip(params, states):
v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data
p.data -= v.data
我們先將動量超參數momentum
設0.5
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.5}, features, labels)
loss: 0.243297, 0.057950 sec per epoch
將動量超參數momentum
增大到0.9
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.9}, features, labels)
loss: 0.260418, 0.059441 sec per epoch
可見目標函數值在後期迭代過程中的變化不夠平滑。直覺上,10倍小批量梯度比2倍小批量梯度大了5倍,我們可以試着將學習率減小到原來的1/5。此時目標函數值在下降了一段時間後變化更加平滑。
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.004, 'momentum': 0.9}, features, labels)
loss: 0.243650, 0.063532 sec per epoch
Pytorch Class
在Pytorch中,torch.optim.SGD
已實現了Momentum。
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr': 0.004, 'momentum': 0.9},
features, labels)
loss: 0.243692, 0.048604 sec per epoch
11.7 AdaGrad
在之前介紹過的優化算法中,目標函數自變量的每一個元素在相同時間步都使用同一個學習率來自我迭代。舉個例子,假設目標函數爲,自變量爲一個二維向量,該向量中每一個元素在迭代時都使用相同的學習率。例如,在學習率爲的梯度下降中,元素和都使用相同的學習率來自我迭代:
在“動量法”一節裏我們看到當和的梯度值有較大差別時,需要選擇足夠小的學習率使得自變量在梯度值較大的維度上不發散。但這樣會導致自變量在梯度值較小的維度上迭代過慢。動量法依賴指數加權移動平均使得自變量的更新方向更加一致,從而降低發散的可能。本節我們介紹AdaGrad算法,它根據自變量在每個維度的梯度值的大小來調整各個維度上的學習率,從而避免統一的學習率難以適應所有維度的問題 [1]。
Algorithm
AdaGrad算法會使用一個小批量隨機梯度按元素平方的累加變量。在時間步0,AdaGrad將中每個元素初始化爲0。在時間步,首先將小批量隨機梯度按元素平方後累加到變量:
其中是按元素相乘。接着,我們將目標函數自變量中每個元素的學習率通過按元素運算重新調整一下:
其中是學習率,是爲了維持數值穩定性而添加的常數,如。這裏開方、除法和乘法的運算都是按元素運算的。這些按元素運算使得目標函數自變量中每個元素都分別擁有自己的學習率。
Feature
需要強調的是,小批量隨機梯度按元素平方的累加變量出現在學習率的分母項中。因此,如果目標函數有關自變量中某個元素的偏導數一直都較大,那麼該元素的學習率將下降較快;反之,如果目標函數有關自變量中某個元素的偏導數一直都較小,那麼該元素的學習率將下降較慢。然而,由於一直在累加按元素平方的梯度,自變量中每個元素的學習率在迭代過程中一直在降低(或不變)。所以,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。
下面我們仍然以目標函數爲例觀察AdaGrad算法對自變量的迭代軌跡。我們實現AdaGrad算法並使用和上一節實驗中相同的學習率0.4。可以看到,自變量的迭代軌跡較平滑。但由於的累加效果使學習率不斷衰減,自變量在迭代後期的移動幅度較小。
%matplotlib inline
import math
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def adagrad_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6 # 前兩項爲自變量梯度
s1 += g1 ** 2
s2 += g2 ** 2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
eta = 0.4
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1 -2.382563, x2 -0.158591
下面將學習率增大到2。可以看到自變量更爲迅速地逼近了最優解。
eta = 2
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1 -0.002295, x2 -0.000000
Implement
同動量法一樣,AdaGrad算法需要對每個自變量維護同它一樣形狀的狀態變量。我們根據AdaGrad算法中的公式實現該算法。
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_adagrad_states():
s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
s_b = torch.zeros(1, dtype=torch.float32)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
s.data += (p.grad.data**2)
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
使用更大的學習率來訓練模型。
d2l.train_ch7(adagrad, init_adagrad_states(), {'lr': 0.1}, features, labels)
loss: 0.242258, 0.061548 sec per epoch
Pytorch Class
通過名稱爲“adagrad”的Trainer
實例,我們便可使用Pytorch提供的AdaGrad算法來訓練模型。
d2l.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features, labels)
loss: 0.243800, 0.060953 sec per epoch
11.8 RMSProp
我們在“AdaGrad算法”一節中提到,因爲調整學習率時分母上的變量一直在累加按元素平方的小批量隨機梯度,所以目標函數自變量每個元素的學習率在迭代過程中一直在降低(或不變)。因此,當學習率在迭代早期降得較快且當前解依然不佳時,AdaGrad算法在迭代後期由於學習率過小,可能較難找到一個有用的解。爲了解決這一問題,RMSProp算法對AdaGrad算法做了修改。該算法源自Coursera上的一門課程,即“機器學習的神經網絡”。
Algorithm
我們在“動量法”一節裏介紹過指數加權移動平均。不同於AdaGrad算法裏狀態變量是截至時間步所有小批量隨機梯度按元素平方和,RMSProp算法將這些梯度按元素平方做指數加權移動平均。具體來說,給定超參數計算
和AdaGrad算法一樣,RMSProp算法將目標函數自變量中每個元素的學習率通過按元素運算重新調整,然後更新自變量
其中是學習率,是爲了維持數值穩定性而添加的常數,如。因爲RMSProp算法的狀態變量是對平方項的指數加權移動平均,所以可以看作是最近個時間步的小批量隨機梯度平方項的加權平均。如此一來,自變量每個元素的學習率在迭代過程中就不再一直降低(或不變)。
照例,讓我們先觀察RMSProp算法對目標函數中自變量的迭代軌跡。回憶在“AdaGrad算法”一節使用的學習率爲0.4的AdaGrad算法,自變量在迭代後期的移動幅度較小。但在同樣的學習率下,RMSProp算法可以更快逼近最優解。
%matplotlib inline
import math
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def rmsprop_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
s1 = beta * s1 + (1 - beta) * g1 ** 2
s2 = beta * s2 + (1 - beta) * g2 ** 2
x1 -= alpha / math.sqrt(s1 + eps) * g1
x2 -= alpha / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
alpha, beta = 0.4, 0.9
d2l.show_trace_2d(f_2d, d2l.train_2d(rmsprop_2d))
epoch 20, x1 -0.010599, x2 0.000000
Implement
接下來按照RMSProp算法中的公式實現該算法。
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_rmsprop_states():
s_w = torch.zeros((features.shape[1], 1), dtype=torch.float32)
s_b = torch.zeros(1, dtype=torch.float32)
return (s_w, s_b)
def rmsprop(params, states, hyperparams):
gamma, eps = hyperparams['beta'], 1e-6
for p, s in zip(params, states):
s.data = gamma * s.data + (1 - gamma) * (p.grad.data)**2
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
我們將初始學習率設爲0.01,並將超參數設爲0.9。此時,變量可看作是最近個時間步的平方項的加權平均。
d2l.train_ch7(rmsprop, init_rmsprop_states(), {'lr': 0.01, 'beta': 0.9},
features, labels)
loss: 0.243334, 0.063004 sec per epoch
Pytorch Class
通過名稱爲“rmsprop”的Trainer
實例,我們便可使用Gluon提供的RMSProp算法來訓練模型。注意,超參數通過gamma1
指定。
d2l.train_pytorch_ch7(torch.optim.RMSprop, {'lr': 0.01, 'alpha': 0.9},
features, labels)
loss: 0.244934, 0.062977 sec per epoch
11.9 AdaDelta
除了RMSProp算法以外,另一個常用優化算法AdaDelta算法也針對AdaGrad算法在迭代後期可能較難找到有用解的問題做了改進 [1]。有意思的是,AdaDelta算法沒有學習率這一超參數。
Algorithm
AdaDelta算法也像RMSProp算法一樣,使用了小批量隨機梯度按元素平方的指數加權移動平均變量。在時間步0,它的所有元素被初始化爲0。給定超參數,同RMSProp算法一樣計算
與RMSProp算法不同的是,AdaDelta算法還維護一個額外的狀態變量,其元素同樣在時間步0時被初始化爲0。我們使用來計算自變量的變化量:
其中是爲了維持數值穩定性而添加的常數,如。接着更新自變量:
最後,我們使用來記錄自變量變化量按元素平方的指數加權移動平均:
可以看到,如不考慮的影響,AdaDelta算法與RMSProp算法的不同之處在於使用來替代超參數。
Implement
AdaDelta算法需要對每個自變量維護兩個狀態變量,即和。我們按AdaDelta算法中的公式實現該算法。
def init_adadelta_states():
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
delta_w, delta_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((s_w, delta_w), (s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
s[:] = rho * s + (1 - rho) * (p.grad.data**2)
g = p.grad.data * torch.sqrt((delta + eps) / (s + eps))
p.data -= g
delta[:] = rho * delta + (1 - rho) * g * g
d2l.train_ch7(adadelta, init_adadelta_states(), {'rho': 0.9}, features, labels)
loss: 0.243485, 0.084914 sec per epoch
Pytorch Class
通過名稱爲“adadelta”的Trainer
實例,我們便可使用pytorch提供的AdaDelta算法。它的超參數可以通過rho
來指定。
d2l.train_pytorch_ch7(torch.optim.Adadelta, {'rho': 0.9}, features, labels)
loss: 0.267756, 0.061329 sec per epoch
11.10 Adam
Adam算法在RMSProp算法基礎上對小批量隨機梯度也做了指數加權移動平均 [1]。下面我們來介紹這個算法。
Algorithm
Adam算法使用了動量變量和RMSProp算法中小批量隨機梯度按元素平方的指數加權移動平均變量,並在時間步0將它們中每個元素初始化爲0。給定超參數(算法作者建議設爲0.9),時間步的動量變量即小批量隨機梯度的指數加權移動平均:
和RMSProp算法中一樣,給定超參數(算法作者建議設爲0.999),
將小批量隨機梯度按元素平方後的項做指數加權移動平均得到:
由於我們將和中的元素都初始化爲0,
在時間步我們得到。將過去各時間步小批量隨機梯度的權值相加,得到 。需要注意的是,當較小時,過去各時間步小批量隨機梯度權值之和會較小。例如,當時,。爲了消除這樣的影響,對於任意時間步,我們可以將再除以,從而使過去各時間步小批量隨機梯度權值之和爲1。這也叫作偏差修正。在Adam算法中,我們對變量和均作偏差修正:
接下來,Adam算法使用以上偏差修正後的變量和,將模型參數中每個元素的學習率通過按元素運算重新調整:
其中是學習率,是爲了維持數值穩定性而添加的常數,如。和AdaGrad算法、RMSProp算法以及AdaDelta算法一樣,目標函數自變量中每個元素都分別擁有自己的學習率。最後,使用迭代自變量:
Implement
我們按照Adam算法中的公式實現該算法。其中時間步通過hyperparams
參數傳入adam
函數。
%matplotlib inline
import torch
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
def get_data_ch7():
data = np.genfromtxt('/home/kesci/input/airfoil4755/airfoil_self_noise.dat', delimiter='\t')
data = (data - data.mean(axis=0)) / data.std(axis=0)
return torch.tensor(data[:1500, :-1], dtype=torch.float32), \
torch.tensor(data[:1500, -1], dtype=torch.float32)
features, labels = get_data_ch7()
def init_adam_states():
v_w, v_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
s_w, s_b = torch.zeros((features.shape[1], 1), dtype=torch.float32), torch.zeros(1, dtype=torch.float32)
return ((v_w, s_w), (v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad.data
s[:] = beta2 * s + (1 - beta2) * p.grad.data**2
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p.data -= hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
hyperparams['t'] += 1
d2l.train_ch7(adam, init_adam_states(), {'lr': 0.01, 't': 1}, features, labels)
loss: 0.242722, 0.089254 sec per epoch
Pytorch Class
d2l.train_pytorch_ch7(torch.optim.Adam, {'lr': 0.01}, features, labels)
loss: 0.242389, 0.073228 sec per epoch