6.6 動量法
小批量梯度下降法從訓(xùn)練樣本的使用角度改善了隨機梯度下降法的效果。但在追求極致的優(yōu)化算法道路上,科學(xué)家們遠(yuǎn)未停止前進的步伐。他們不停的找問題,想新思路,進一步改善算法性能。前面三種算法都使用了梯度計算,能不能從這里入手改變呢。真有人就這么干了,而且效果不錯。這就是本節(jié)要介紹的動量法了。
6.6.1?基本思想

6.6.2?優(yōu)缺點
先說優(yōu)點:
??動量法可以幫助算法在局部最優(yōu)解附近更快地收斂。這是因為動量法可以幫助算法“跳過”局部最小值,它可以保持一定的加速度,使算法能夠跨越那些低點。
??可以減少學(xué)習(xí)率的需求。因為動量法可以幫助算法跳過局部最小值,所以我們不需要設(shè)置過大的學(xué)習(xí)率來更快地收斂。這可以減少學(xué)習(xí)率過大導(dǎo)致的振蕩的風(fēng)險。
??可以幫助算法更快地從平凡的初始權(quán)重開始收斂。這是因為動量法可以幫助算法跳過局部最小值,因此算法可以更快地從平凡的初始權(quán)重開始收斂。
缺點方面:
動量法可能會使算法“超調(diào)”。如果我們設(shè)置的動量因子過大,那么動量就會變得過大,這可能會導(dǎo)致算法“超調(diào)”。
6.6.3?發(fā)展歷史
動量法是由深度學(xué)習(xí)先驅(qū)?Geoffrey Hinton 在 1986 年提出的。在當(dāng)時,他提出了一種名為“快速動量法”的優(yōu)化方法,用于解決深度神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中的梯度消失問題。
在?1990 年代,動量法得到了廣泛應(yīng)用,并在許多研究中得到了證明。在 2000 年代,動量法在深度學(xué)習(xí)中得到了廣泛應(yīng)用,并成為了許多深度學(xué)習(xí)框架的默認(rèn)優(yōu)化方法。
近年來,動量法也在不斷發(fā)展,人們提出了許多改進版本,比如?Nesterov 動量法,AdaGrad 動量法等。這些改進版本在某些情況下表現(xiàn)得更好,因此也得到了廣泛使用。
6.6.4?代碼示例
在?PyTorch 中,你可以使用 torch.optim 庫中的 SGD 類來實現(xiàn)動量法。下面是一個簡單的例子,展示了如何使用動量法來優(yōu)化一個簡單的神經(jīng)網(wǎng)絡(luò),我們同時對比了不使用動量法的情況。
import?torch
import?torch.nn?as?nn
import?numpy?as?np
import?matplotlib.pyplot?as?plt
from?tqdm?import?*
#?定義模型和損失函數(shù)
class?Model(nn.Module):
????def?__init__(self):
????????super().__init__()
????????self.hidden1?=?nn.Linear(1,?32)
????????self.hidden2?=?nn.Linear(32,?32)
????????self.output?=?nn.Linear(32,?1)
????def?forward(self, x):
????????x?=?torch.relu(self.hidden1(x))
????????x?=?torch.relu(self.hidden2(x))
????????return?self.output(x)
loss_fn?=?nn.MSELoss()
#?生成隨機數(shù)據(jù)
np.random.seed(0)
n_samples?=?200
x?=?np.linspace(-5,?5, n_samples)
y?=?0.3?*?(x?**?2)?+?np.random.randn(n_samples)
#?轉(zhuǎn)換為Tensor
x?=?torch.unsqueeze(torch.from_numpy(x).float(),?1)
y?=?torch.unsqueeze(torch.from_numpy(y).float(),?1)
names?=?["momentum",?"sgd"]?#?一個使用動量法,一個不使用
losses?=?[[], []]
#?超參數(shù)
learning_rate?=?0.0005
n_epochs?=?500
#?分別訓(xùn)練
for?i?in?range(len(names)):
????model?=?Model()
????optimizer?=?torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9?if?i?==?0?else?0)?#?一個使用動量法,一個不使用
????for?epoch?in?range(n_epochs):
????????optimizer.zero_grad()
????????out?=?model(x)
????????loss?=?loss_fn(out, y)
????????loss.backward()
????????optimizer.step()
????????losses[i].append(loss.item())
#?繪制損失值的變化趨勢
plt.figure()
plt.plot(losses[0],?'r-', label='momentum')
plt.plot(losses[1],?'b-', label='sgd')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training loss')
plt.ylim((0,?12))
plt.legend()
plt.show()
?

在上述代碼中,我們使用了?torch.optim.SGD 類來實現(xiàn)動量法。我們傳入了網(wǎng)絡(luò)的參數(shù)以及學(xué)習(xí)率和動量參數(shù),然后在訓(xùn)練循環(huán)中調(diào)用 optimizer.step() 來更新參數(shù)。通常來說,使用動量法可以幫助優(yōu)化算法更快地收斂到最優(yōu)解。它通過給梯度添加一個“動量”來幫助優(yōu)化算法更快地擺脫局部最優(yōu)解,并最終收斂到全局最優(yōu)解。但是要注意,動量法僅僅是在某些情況下可以比其他優(yōu)化算法更快地收斂,可以讓你的訓(xùn)練更快地完成。換句話說,僅僅使用動量法并不能保證優(yōu)于其他算法。動量法通過維護上一次更新的加速度來減少梯度下降時的震蕩,但這同樣需要適當(dāng)?shù)膶W(xué)習(xí)率。如果學(xué)習(xí)率過小,那么即使使用動量法也會出現(xiàn)震蕩,因為學(xué)習(xí)率過小會導(dǎo)致更新過慢。上面的例子就可能出現(xiàn)這種情況。你可以嘗試調(diào)整不同的參數(shù)改善不同方法的效果。
梗直哥提示:上面例子暴露的問題也顯示了對深度學(xué)習(xí)算法而言,僅僅知道原理的皮毛是不夠的,還要深刻領(lǐng)會其中的思想,掌握調(diào)參的經(jīng)驗,才能無往不勝。再好的模型,也得看會不會用,用的是不是地方。這就如同武器一樣,光靠它不行,還要看手拿武器的人能不能把它用好。如果你對這方面的故事和經(jīng)驗體會感興趣,希望進階學(xué)習(xí),歡迎來到我的課堂(微信:gengzhige99)。
?
