优化器(optim)
优化算法模块(torch.optim)
torch.optim 实现了丰富的优化算法,包括SGD, Adam, L-BFGS 等等。假设我们要
一个优化算法每步迭代可抽象成:
image.png
其中是学习率(learning rate),而是一个迭代方向,这个方向的目的是减少目标函数的值,即粗略的使得
不同的优化算法的区别就在于的选取,我们将在接下来的章节详细的讨论不同的算法。
测试案例
我们在这里通过一个简单的案例熟悉 torch.optim 的API。考虑极小化经典的 Rosenbrock 函数:
该函数的 landscape 如下:
image.png
全局极小点为:.
加载相关包
import torch
import torch.nn as nn
from torch.autograd import Variable
import matplotlib.pyplot as plt
定义 Rosenbrock 函数
注意,PyTorch 的 Variable 不能是标量
def rosenbrock(x):
y = 100*(x[1] - x[0]*x[0])**2 + (x[0]-1)**2
return y
构建优化算法(Optimizer)每个优化算法有两组参数:
- params: 一个可迭代的对象,该迭代器返回的必须求导 Variable。譬如一个列表
,其中每一个
必须是可求导的 Variable。
- 超参数(hyper-parameters): 主要包括学习率,动量等等。不同的算法的超参数不完全一致。
# 随机初始化
value = torch.rand(2)
# 要优化的变量
x = Variable(value,requires_grad = True)
y = rosenbrock(x)
# 定义优化器,这里我们选择随机梯度法
optimizer = torch.optim.SGD(params = [x],lr = 0.001) #params = x 会报错
运行算法,迭代10步
我们可以看到函数值单调下降。
nstep = 10
for i in range(nstep):
optimizer.zero_grad() # 因为 backward 自动求导,导数会累加,所以每次迭代前需要导数清零
y = rosenbrock(x)
y.backward() # 求导
optimizer.step() # 算法更新 x,对于SGD它等同于 x = x - lr*x.grad
print('iter: %d\t fun_val: %.2e' %(i, y.data))
iter: 0 fun_val: 7.43e+01
iter: 1 fun_val: 1.96e+00
iter: 2 fun_val: 5.78e-01
iter: 3 fun_val: 2.78e-01
iter: 4 fun_val: 2.03e-01
iter: 5 fun_val: 1.83e-01
iter: 6 fun_val: 1.78e-01
iter: 7 fun_val: 1.76e-01
iter: 8 fun_val: 1.75e-01
iter: 9 fun_val: 1.75e-01
SGD 与 Adam 比较
Adam 是一种自动调节步长的 SGD 。这里我们比较一下两个算法的收敛速度。
两个算法从同一点初始化。
x0 = torch.rand(2)
SGD
其中学习率已将调到最优
x = Variable(x.clone(),requires_grad = True)
sgd = torch.optim.SGD(params = [x],lr = 0.003)
y_sgd = []
for i in range (500):
sgd.zero_grad() # 因为 backward 自动求导,导数会增加,所以每次迭代前需要导数清零
y = rosenbrock(x)
y.backward() # 求导
sgd.step() # 算法更新 x,对于SGD它等同于 x = x - lr*x.grad
y_sgd.append(y.data)
print('converged solution ({},{})'.format(x.data[0],x.data[1])) # 等价于 print('(%.2e,%.2e)'%(x.data[0],x.data[1]))
converged solution (0.7859570384025574,0.5668709874153137)
Adam
其中学习率已经达到最优
x = Variable(x0.clone(),requires_grad = True)
adam = torch.optim.Adam(params = [x],lr = 0.4)
y_adam = []
for i in range(500):
adam.zero_grad() # 因为 backward 自动求导,导数会累加,所以每次迭代前需要导数清零
y = rosenbrock(x)
y.backward() # 求导
adam.step() # 算法更新 x,对于 SGD 它等同于 x = x - lr*x.grad
y_adam.append(y.data)
print('converged solution({},{})'.format(x.data[0],x.data[1]))
converged solution(0.9853293895721436,0.9707741737365723)
可视化算法收敛过程
plt.plot(y_sgd,'-',label = 'SGD')
plt.plot(y_adam,'-',label = 'Adam')
plt.legend()
plt.ylabel('F(x)')
plt.xlabel('number of iteration');
这种方法画出来的图并不是很直观,采用取对数后画出来的图比较直观:
plt.semilogy(y_sgd,'-',label = 'SGD')
plt.semilogy(y_adam,'-',label = 'Adam')
plt.legend()
plt.ylabel('F(x)')
plt.xlabel('number of iteration');
根据上图所示,Adam收敛得比SGD快得多,特别是迭代的后期。
当我们建立神经网络时,经常会需要对不同的层采取不同的学习率,接下来,我们将介绍如何调整学习率
# 首先定义一个只有两个卷积层的网络
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.conv1 = nn.Conv2d(3, 6, 3)
self.conv2 = nn.Conv2d(6, 6, 3)
def forward(self, x):
out = self.conv2(self.conv1(x))
return out
net = Net()
x = Variable(torch.randn(1,3,5,5))
net(x)
tensor([[[[-0.3710]],
[[-0.1051]],
[[ 0.3007]],
[[ 0.1005]],
[[-0.3409]],
[[-0.1685]]]], grad_fn=<ThnnConv2DBackward>)
# 设置第一层的学习率为 0.1 第二层为 0.01
optimizer = torch.optim.SGD([
{'params': net.conv1.parameters()},# predict 的学习率为0.1
{'params': net.conv2.parameters(), 'lr': 0.01}],lr = 0.1)# hidden 层学习











网友评论