优化器

作者: Vector_Wan | 来源:发表于2019-05-02 11:47 被阅读0次

优化器(optim)

优化算法模块(torch.optim)

torch.optim 实现了丰富的优化算法,包括SGD, Adam, L-BFGS 等等。假设我们要
\begin{align} \min_{x} F(x), \end{align}
一个优化算法每步迭代可抽象成:

image.png

其中是学习率(learning rate),而是一个迭代方向,这个方向的目的是减少目标函数的值,即粗略的使得

不同的优化算法的区别就在于的选取,我们将在接下来的章节详细的讨论不同的算法。

测试案例

我们在这里通过一个简单的案例熟悉 torch.optim 的API。考虑极小化经典的 Rosenbrock 函数:
\begin{align} F(x_1,x_2)= 100 (x_2-x_1^2)^2 + (x_1-1)^2 \end{align}
该函数的 landscape 如下:

image.png

全局极小点为:~F(1,1) = 0~.


加载相关包

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。譬如一个列表~[x_1,x_2,\dots,x_m]~,其中每一个~x_i~必须是可求导的 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 层学习

相关文章

网友评论

      本文标题:优化器

      本文链接:https://www.haomeiwen.com/subject/iohnnqtx.html