Pytorch总结十五之优化算法:AdaGrad、RMSProp、AdaDelta、Adam算法详解
简介:
- 继续解析优化算法!
1. AdaFrad算法
1.1 算法
1.2 特点
#Adagrad算法
import math
import torch
import sys
sys.path.append("..")
import d2lzh_pytorch 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))
output:
epoch 20, x1 -2.382563, x2 -0.158591
下⾯将学习率增⼤到2。可以看到⾃变量更为迅速地逼近了最优解。
eta=2
d2l.show_trace_2d(f_2d,d2l.train_2d(adagrad_2d))
output:
epoch 20, x1 -0.002295, x2 -0.000000
1.3 从零开始实现
同动量法⼀样,AdaGrad算法需要对每个⾃变量维护同它⼀样形状的状态变量。我们根据AdaGrad算
法中的公式实现该算法。
##从零实现adagrad算法
def get_data_ch7():
data = np.genfromtxt('data/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) # 前1500个样本(每个样本5个特征)
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)
def train_ch7(optimizer_fn, states, hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net, loss = linreg, squared_loss
w = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),
requires_grad=True)
b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)
def eval_loss():
return loss(net(features, w, b), labels).mean().item()
ls = [eval_loss()]
data_iter = torch.utils.data.DataLoader(
torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
for _ in range(num_epochs):
start = time.time()
for batch_i, (X, y) in enumerate(data_iter):
l = loss(net(X, w, b), y).mean() # 使用平均损失
# 梯度清零
if w.grad is not None:
w.grad.data.zero_()
b.grad.data.zero_()
l.backward()
optimizer_fn([w, b], states, hyperparams) # 迭代模型参数
if (batch_i + 1) * batch_size % 100 == 0:
ls.append(eval_loss()) # 每100个样本记录下当前训练误差
# 打印结果和作图
print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))
set_figsize()
plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
features,labels=d2l.get_data_ch7()
#使用更大的学习率来训练模型
d2l.train_ch7(adagrad,init_adagrad_states(),{'lr':0.1},features,labels)
output:
loss: 0.243252, 0.052886 sec per epoch
1.4 简洁实现
通过名称为 Adagrad
的优化器⽅法,我们便可使⽤PyTorch
提供的AdaGrad
算法来训练模型。
# 本函数与原书不同的是这里第一个参数优化器函数而不是优化器的名字
# 例如: optimizer_fn=torch.optim.SGD, optimizer_hyperparams={"lr": 0.05}
def train_pytorch_ch7(optimizer_fn, optimizer_hyperparams, features, labels,
batch_size=10, num_epochs=2):
# 初始化模型
net = nn.Sequential(
nn.Linear(features.shape[-1], 1)
)
loss = nn.MSELoss()
optimizer = optimizer_fn(net.parameters(), **optimizer_hyperparams)
def eval_loss():
return loss(net(features).view(-1), labels).item() / 2
ls = [eval_loss()]
data_iter = torch.utils.data.DataLoader(
torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)
for _ in range(num_epochs):
start = time.time()
for batch_i, (X, y) in enumerate(data_iter):
# 除以2是为了和train_ch7保持一致, 因为squared_loss中除了2
l = loss(net(X).view(-1), y) / 2
optimizer.zero_grad()
l.backward()
optimizer.step()
if (batch_i + 1) * batch_size % 100 == 0:
ls.append(eval_loss())
# 打印结果和作图
print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))
set_figsize()
plt.plot(np.linspace(0, num_epochs, len(ls)), ls)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
d2l.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features,labels)
output:
loss: 0.243147, 0.040675 sec per epoch 1
1.4 小结
AdaGrad
算法在迭代过程中不断调整学习率,并让⽬标函数⾃变量中每个元素都分别拥有⾃⼰
的学习率。- 使⽤
AdaGrad
算法时,⾃变量中每个元素的学习率在迭代过程中⼀直在降低(或不变)
2.Rmsprop算法
AdaGrad
算法中提到,因为调整学习率时分⺟上的变量 ⼀直在累加按元素平⽅的⼩批量随机梯度,所以⽬标函数⾃变量每个元素的学习率在迭代过程中⼀直在降低(或不变)。因此,当学习率在迭代早期降得较快且当前解依然不佳时,AdaGrad
算法在迭代后期由于学习率过⼩,可能较难找到⼀个有⽤的解。为了解决这⼀问题,RMSProp
算法对AdaGrad
算法做了⼀点⼩⼩的修改。
2.1 算法
#Rmsprop算法
import math
import torch
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
def rmsprop_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
s1 = gamma * s1 + (1 - gamma) * g1 ** 2
s2 = gamma * s2 + (1 - gamma) * 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, gamma = 0.4, 0.9
d2l.show_trace_2d(f_2d, d2l.train_2d(rmsprop_2d))
输出:
epoch 20, x1 -0.010599, x2 0.000000
2.2 从零开始实现
接下来按照RMSProp
算法中的公式实现该算法。
features, labels = d2l.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['gamma'], 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)
d2l.train_ch7(rmsprop, init_rmsprop_states(), {'lr': 0.01, 'gamma':0.9},features, labels)
output:
loss: 0.243452, 0.049984 sec per epoch 1
2.3 简洁实现
通过名称为 RMSprop
的优化器⽅法,我们便可使⽤PyTorch
提供的RMSProp
算法来训练模型。注意,超参数 通过 alpha
指定。
#简洁实现:
d2l.train_pytorch_ch7(torch.optim.RMSprop, {'lr': 0.01, 'alpha':0.9},features, labels)
output:
loss: 0.244027, 0.057885 sec per epoch
2.4 小结
RMSProp
算法和AdaGrad
算法的不同在于,RMSProp
算法使⽤了⼩批量随机梯度按元素平⽅的指数加权移动平均来调整学习率。
3.AdaDelta算法
除了RMSProp
算法以外,另⼀个常⽤优化算法AdaDelta
算法也针对AdaGrad
算法在迭代后期可能较难找到有⽤解的问题做了改进。有意思的是,AdaDelta
算法没有学习率这⼀超参数。
3.1 算法
3.2 从零开始实现
##AdaDelta算法
import torch
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
features,labels=d2l.get_data_ch7()
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
#使用超参数p=0.9来训练模型
d2l.train_ch7(adadelta,init_adadelta_states(),{'rho':0.9},features,labels)
output:
loss: 0.242700, 0.066821 sec per epoch
3.3 简洁实现
通过名称为 Adadelta
的优化器⽅法,我们便可使⽤PyTorch
提供的AdaDelta
算法。它的超参数可以通过 rho
来指定。
#简洁实现
d2l.train_pytorch_ch7(torch.optim.Adadelta,{'rho':0.9},features,labels)
输出:
loss: 0.273120, 0.062369 sec per epoch
3.4 小结
AdaDelta
算法没有学习率超参数,它通过使⽤有关⾃变量更新量平⽅的指数加权移动平均的项来替代RMSProp
算法中的学习率。
4.Adam算法
Adam
算法在RMSProp
算法基础上对⼩批量随机梯度也做了指数加权移动平均。下⾯我们来介绍这个算法。- 所以
Adam
算法可以看作是RMSProp
算法与动量法的组合
4.1 算法
4.2 从零开始实现
按照Adam
算法中的公式实现该算法,其中时间步t通过hyperparams
参数传入adam
函数。
#Adam算法从零开始实现
import torch
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
features,labels=d2l.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
#使用学习率为0.01的Adam算法来训练模型
d2l.train_ch7(adam,init_adam_states(),{'lr':0.01,'t':1},features,labels)
output:
loss: 0.244865, 0.069813 sec per epoch
4.3 简洁实现
通过名称为“adam”的 Trainer
实例,我们便可使⽤Gluon提供的Adam算法。
#简洁实现:
d2l.train_pytorch_ch7(torch.optim.Adam,{'lr':0.01},features,labels)
output:
loss: 0.242826, 0.065362 sec per epoch
4.4 小结
- Adam算法在RMSProp算法的基础上对⼩批量随机梯度也做了指数加权移动平均。
- Adam算法使⽤了偏差修正。