本篇笔记的完整代码:https://github.com/ChenWentai/PyTorch/blob/master/task6.py
1. 优化器(Optimizer)简介
在backprop更新权重时,一般采用SGD(Stochastic Gradient Descent)作为优化器。其实除了SGD以外,还有很多其他的优化器可供选择。
1.1 BGD (Batch Gradient Descent)和 SGD
在每一轮的训练过程中,BDG算法用整个训练集的数据计算cost fuction的梯度,并用该梯度对模型参数进行更新。这样在cost function为凸函数的情况下,可以收敛到全局最优。但是由于每轮迭代都需要在整个数据集上计算一次,所以批量梯度下降可能非常慢解。
相比之下,SGD每次只采用一个样本来计算cost function,大大节省了计算空间,提升了效率。
在BGD和SGD之间还有一种折中的方法:Mini-batch Gradient Descent,即每次只是用一部分样本来计算cost function对W的梯度。
1.2 SGD with momentum
SGD方法的一个缺点是其更新方向完全依赖于当前batch计算出的梯度,若batch选取不具有代表性,则结果会十分不稳定。而Momentum机制会观察历史梯度,若当前梯度的方向与历史梯度一致(表明当前样本不太可能为异常点),则会增强这个方向的梯度,若当前梯度与历史梯方向不一致,则梯度会衰减,如公式(1.2)和图1所示
1.3 Nesterov Momentum
Nesterov Momentum通过将一部分梯度更新加入到现有的求梯度公式中,在Momentum的基础上减小了误差,如式(1.3)和图2所示:
1.4 Adagrad
Adagrad的思想是根据现有的学习率(learning rate)来更新权重,能够在学习的过程中自适应地对学习率进行调整,可以看做是learning rate schedular的一个类型。
Adagrad的缺点是在训练的中后期,分母上梯度平方的累加将会越来越大,从而梯度趋近于0,使得训练提前结束。
1.5 RMSprop
RMSprop也是一种学习率调整的算法。Adagrad会累加之前所有的梯度平方,而RMSprop仅仅是计算对应的平均值,因此可缓解Adagrad算法学习率下降较快的问题。RMSprop其更新公式如下:
1.6 Adam
Adam的思想是在RMSprop的基础上加入Momentum, 利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。
2 使用numpy模拟不同的优化器
#构造数据:J为损失函数,J_prime为损失函数的导数
import numpy as np
import matplotlib.pyplot as plot
J = lambda w1, w2: w1**2+10*w2**2
J_prime1 = lambda w1: 2*w1
J_prime2 = lambda w2: 2*w2
w1 = 1
w2 = -1
epoch = 200
lr = 0.1
#SGD
Loss_sgd = []
W1_sgd = []
W2_sgd = []
for i in range(epoch):
w1 -= lr*J_prime1(w1)
w2 -= lr*J_prime2(w2)
W1_sgd.append(w1)
W2_sgd.append(w2)
Loss_sgd.append(J(w1, w2))
# Momentum
gamma = 0.5
v1 = 0
v2 = 0
s = 0
Loss_moment = []
W1_moment = []
W2_moment = []
for i in range(epoch):
v1 = gamma*v1 + lr*J_prime1(w1)
w1 -= v1
v2 = gamma*v2 + lr*J_prime2(w2)
w2 -= v2
W1_moment.append(w1)
W2_moment.append(w2)
Loss_moment.append(J(w1, w2))
#Adagrad
v = 0
s = 0
Loss_ada = []
W1_ada = []
W2_ada = []
s1=s2=0
for i in range(epoch):
s1 += J_prime1(w1)**2
w1 -= lr*(J_prime1(w1)/np.sqrt(s1))
s2 += J_prime2(w2)**2
w2 -= lr*(J_prime2(w2)/np.sqrt(s2))
W1_ada.append(w1)
W2_ada.append(w2)
Loss_ada.append(J(w1, w2))
#RMSprop
epoch = 200
lambda0 = 0.01
gamma = 0.5
v = 0
s = 0
Loss_RMS = []
W1_RMS = []
W2_RMS = []
s1=s2=0
for i in range(epoch):
s1 = gamma*s1 + (1-gamma)*(J_prime1(w1)**2)
w1 -= lambda0*(J_prime1(w1)/np.sqrt(s1))
s2 = gamma*s2 + (1-gamma)*(J_prime2(w2)**2)
w2 -= lambda0*(J_prime2(w2)/np.sqrt(s2))
W1_RMS.append(w1)
W2_RMS.append(w2)
Loss_RMS.append(J(w1, w2))
#画出loss和weight的曲线
LOSS = [Loss_sgd, Loss_moment, Loss_ada, Loss_RMS]
labels = ['SGD', 'Momentum','Adagrad','RMSprop']
for i, loss in enumerate(LOSS):
plt.plot(loss, label=labels[i])
plt.legend()
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('Loss')
plt.savefig('./task6/Loss.jpg', dpi=500)
plt.show()
W1 = [W1_sgd, W1_moment, W1_ada, W1_RMS]
for i, w1 in enumerate(W1):
plt.plot(w1, label=labels[i])
plt.legend()
plt.xlabel('epoch')
plt.ylabel('W1')
plt.title('W1')
plt.savefig('./task6/W1.jpg', dpi=500)
plt.show()
W2 = [W2_sgd, W2_moment, W2_ada, W2_RMS]
for i, w2 in enumerate(W2):
plt.plot(w2, label=labels[i])
plt.legend()
plt.xlabel('epoch')
plt.ylabel('W2')
plt.title('W2')
plt.savefig('./task6/W2.jpg', dpi=500)
plt.show()
作出不同优化器loss和权值更新曲线:
由图3-图5可以看到,对于loss函数,SGD有着最快的收敛速度和最陡峭的下降曲线;排在后面的依次是Adagrad, Momentum 和RMSprop[1].
[1] 按照优化器理论,SGD不应该是收敛最快的。可能是因为模拟的损失函数过于简单,其他优化器方法减慢收敛速度。具体原因有待进一步探究。