一、优化器:optimizer


管理并更新模型中可学习参数的值,使得模型输出更接近真实标签 ;


导数:函数在指定坐标轴上的变化率;



方向导数:指定方向上的变化率;



梯度:一个向量,方向为方向导数,取得最大值的方向;



 



梯度下降法的计算过程就是沿梯度下降的方向求解极小值,也可以沿梯度上升方向求解最大值。 




 



pytorch只优化网络的一部分参数 pytorch中的优化器_pytorch只优化网络的一部分参数

图1.梯度下降法

 

二、优化器基本属性

  • defaults:优化器超参数
  • state:参数的缓存,如momentum的缓存
  • params_groups:管理的参数组
  • _step_count:记录更新次数,学习率调整中使用

三、优化器基本方法

  • zero_grad():清空所管理参数的梯度
  • step():执行一步更新操作
  • add_param_group():添加参数组
  • state_dict():获取优化器当前状态信息字典
  • load_state_dict():加载状态信息字典

pytorch特性:张量梯度不自动清零。

四、以SGD优化器为例(随机梯度下降)

import torch.optim as optim

optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

|  Args:
 |      params (iterable): iterable of parameters to optimize or dicts defining:管理参数组
 |          parameter groups
 |      lr (float): learning rate:初始化学习率
 |      momentum (float, optional): momentum factor (default: 0):动量系数,贝塔
 |      weight_decay (float, optional): weight decay (L2 penalty) (default: 0):L2正则化系数
 |      dampening (float, optional): dampening for momentum (default: 0):
 |      nesterov (bool, optional): enables Nesterov momentum (default: False):是否采用NAG

原始的梯度下降法:没有添加momentum的情况下的计算公式:

pytorch只优化网络的一部分参数 pytorch中的优化器_optimizer_02

      ------------------------------------------------(1)其中的 

pytorch只优化网络的一部分参数 pytorch中的优化器_深度学习_03

 表示学习率,

pytorch只优化网络的一部分参数 pytorch中的优化器_optimizer_04

 表示 

pytorch只优化网络的一部分参数 pytorch中的优化器_Pytorch_05

 的梯度,然后依次进行更新参数;

以实际代码来看一下不同的学习对梯度下降的影响!

以简单的二次函数:

pytorch只优化网络的一部分参数 pytorch中的优化器_python_06

  ,对这个函数的x进行求导数:

pytorch只优化网络的一部分参数 pytorch中的优化器_pytorch只优化网络的一部分参数_07

import torch
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

def func(x):
    """
    y = (2x)^2 = 4*x^2 dy/dx = 8x
    """
    return torch.pow(2*x,2)
# 初始化
x = torch.tensor([2.],requires_grad=True)

# 绘制图
x_t = torch.linspace(-3, 3, 100)
y = func(x_t)
plt.plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
plt.grid()
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.show()


pytorch只优化网络的一部分参数 pytorch中的优化器_python_08

图2.二次函数:y = 4*x^2

 使用梯度下降法,即添加一个学习率的情况:这里我们选择从上图二次函数x = 2的时刻开始进行更新;

学习率具体作用是干嘛的呢?:可以理解为控制更新的步伐;

参考博文:

  1. 知乎:学习率
  2. 学习率的理解及如何调整学习率
import torch
import numpy as np
import matplotlib.pyplot as plt
torch.manual_seed(1)

def func(x_t):
    """
    y = (2x)^2 = 4*x^2      dy/dx = 8x
    """
    return torch.pow(2*x_t, 2)

# 最开始的位置,依次进行更新权重
x = torch.tensor([2.], requires_grad=True)
# ------------------------------ gradient descent ------------------------------
flag = 1
if flag:
    iter_rec, loss_rec, x_rec = list(), list(), list()

    # 设置学习率
    lr = 0.02    # /1. /.5 /.2 /.1 /.125
    max_iteration = 20   # /1. 4     /.5 4   /.2 20 200

    for i in range(max_iteration):
        y = func(x)
        y.backward()
        print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
            i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
        x_rec.append(x.item())
        x.data.sub_(lr * x.grad)    # x -= x.grad  数学表达式意义:  x = x - x.grad    # 0.5 0.2 0.1 0.125
        x.grad.zero_()
        iter_rec.append(i)
        loss_rec.append(y)

    plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
    plt.xlabel("Iteration")
    plt.ylabel("Loss value")

    x_t = torch.linspace(-3, 3, 100)
    y = func(x_t)
    plt.subplot(122).plot(x_t.numpy(), y.numpy(), label="y = 4*x^2")
    plt.grid()
    y_rec = [func(torch.tensor(i)).item() for i in x_rec]
    plt.subplot(122).plot(x_rec, y_rec, '-ro')
    plt.legend()
    plt.show()
Iter:0, X:     2.0, X.grad:    16.0, loss:      16.0
Iter:1, X:1.6800000667572021, X.grad:13.440000534057617, loss:11.28960132598877
Iter:2, X:1.4112000465393066, X.grad:11.289600372314453, loss:7.9659423828125
Iter:3, X:1.1854079961776733, X.grad:9.483263969421387, loss:5.6207685470581055
Iter:4, X:0.9957427382469177, X.grad:7.965941905975342, loss:3.9660143852233887
Iter:5, X:0.8364238739013672, X.grad:6.6913909912109375, loss:2.79841947555542
Iter:6, X:0.7025960683822632, X.grad:5.6207685470581055, loss:1.9745649099349976
Iter:7, X:0.5901806950569153, X.grad:4.721445560455322, loss:1.393252968788147
Iter:8, X:0.4957517981529236, X.grad:3.9660143852233887, loss:0.9830793738365173
Iter:9, X:0.4164315164089203, X.grad:3.3314521312713623, loss:0.6936608552932739
Iter:10, X:0.3498024642467499, X.grad:2.798419713973999, loss:0.48944705724716187
Iter:11, X:0.29383406043052673, X.grad:2.350672483444214, loss:0.3453538119792938
Iter:12, X:0.2468206137418747, X.grad:1.9745649099349976, loss:0.2436816543340683
Iter:13, X:0.20732931792736053, X.grad:1.6586345434188843, loss:0.17194178700447083
Iter:14, X:0.17415663599967957, X.grad:1.3932530879974365, loss:0.1213221326470375
Iter:15, X:0.1462915688753128, X.grad:1.1703325510025024, loss:0.08560489118099213
Iter:16, X:0.12288491427898407, X.grad:0.9830793142318726, loss:0.06040280684828758
Iter:17, X:0.10322332382202148, X.grad:0.8257865905761719, loss:0.0426202192902565
Iter:18, X:0.08670759201049805, X.grad:0.6936607360839844, loss:0.030072826892137527
Iter:19, X:0.07283437997102737, X.grad:0.582675039768219, loss:0.021219387650489807


pytorch只优化网络的一部分参数 pytorch中的优化器_深度学习_09

图3.左图表示Loss的值,右边表表示从2开始的梯度下降

 此时的学习率设置为0.02,虽然可以到达最小值,但是速度还是很慢,需要走很多步;尝试设置大一点学习率,其实就是调参数;

最佳的学习率设置为:0.125,观察一下最终的效果如何,从下图可以看出相比上一个学习率而言,这个只需要两步就可以达到最小值;


pytorch只优化网络的一部分参数 pytorch中的优化器_python_10

图4.学习率设置为0.125

那么我们如何能够最快找到合适的学习率呢?如果我们站在上帝视角绝对可以!!!

下面我们看一下在多个学习率情况下的效果如何:

我们这里设置学习的范围为:[0.01,0.2]  所有的学习率都设置在这个区间内

#多种学习率的情况下
# flag = 0
flag = 1
if flag:
    iteration = 100
    num_lr = 10
    lr_min, lr_max = 0.01, 0.2  # .5 .3 .2

    lr_list = np.linspace(lr_min, lr_max, num=num_lr).tolist()
    loss_rec = [[] for l in range(len(lr_list))]
    iter_rec = list()

    for i, lr in enumerate(lr_list):
        x = torch.tensor([2.], requires_grad=True)
        for iter in range(iteration):

            y = func(x)
            y.backward()
            x.data.sub_(lr * x.grad)  # x.data -= x.grad
            x.grad.zero_()

            loss_rec[i].append(y.item())

    for i, loss_r in enumerate(loss_rec):
        plt.plot(range(len(loss_r)), loss_r, label="LR: {}".format(lr_list[i]))
    plt.legend()
    plt.xlabel('Iterations')
    plt.ylabel('Loss value')
    plt.show()

 


pytorch只优化网络的一部分参数 pytorch中的优化器_Pytorch_11

图5.多种学习率的情况  从上图5可以看出,在iteration为0时,学习率为0.136时刻下降的最快!这就是针对二次函数 

pytorch只优化网络的一部分参数 pytorch中的优化器_python_06

 使用梯度下降法最佳的学习率!!!

但是梯度下降法存在两个缺点:

  1. 训练速度慢:每走一步都需要计算调整下一步的方向,下山的速度变慢。对于大数据集而言,每输入一个样本都需要更新一次参数,并且每次迭代都要遍历所有的样本。会使得训练过程相当缓慢,需要花费很长时间才能得到收敛。
  2. 容易陷入局部最优解:由于是在有限视距内寻找下山方向。当陷入平坦的洼地,会误以为到达了山地的最低点,从而不会继续走下去。所谓的局部最优解就是鞍点,梯度为0,使得模型参数不会再继续更新。

于是学者们又提出添加momentum(动量或者冲量):结合当前梯度与上一次更新信息,用于当前更新!

主要分为两个情况:

一、momentum

pytorch只优化网络的一部分参数 pytorch中的优化器_python_13

pytorch只优化网络的一部分参数 pytorch中的优化器_Pytorch_14

其中

pytorch只优化网络的一部分参数 pytorch中的优化器_pytorch只优化网络的一部分参数_15

 表示第i+1次更新的参数,lr表示学习率,

pytorch只优化网络的一部分参数 pytorch中的优化器_深度学习_16

 表示更新量,

pytorch只优化网络的一部分参数 pytorch中的优化器_python_17

 表示momentum系数,

pytorch只优化网络的一部分参数 pytorch中的优化器_optimizer_18

 表示 

pytorch只优化网络的一部分参数 pytorch中的优化器_python_19

 的梯度。(一般训练时的动量设置为0.9)

下面以100次更新为例:

pytorch只优化网络的一部分参数 pytorch中的优化器_python_20

二、Nesterov版本进行了类似的修改。参数和上面的定义是一样的!

以上两种情况都是动量优化法,引入物理中的动量思想,加速梯度下降,主要有上面两种方法。由上图梯度下降法,从山顶到山底,没有阻力的情况下,它的动量会越来越大,如果遇到阻力,速度就会变小。动量优化方法就是基于此思想,使得梯度方向在不变的维度上,参数更新变快,梯度有所改变时,更新参数变慢,这样就能加快收敛并且减少动荡。

下面整体再以代码的形式看一下optim.SGD:(以RMB二分类为例)

import torch
import torch.nn as nn
# 数据加载
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
import torch.optim as optim
from matplotlib import pyplot as plt

from model.lenet import LeNet
from tools.my_dataset import RMBDataset
from tools.common_tools import transform_invert,set_seed

set_seed(1)
rmb_label = {"1":0,"100":1}

#参数设置
MAX_EPOCH = 10
BATCH_SIZE = 16
LR = 0.0125
log_interval = 10
val_interval = 1

#--------------------------------------dataset-------------------------------------------
# 加载数据
split_dir = os.path.join("data","rmb_split")
train_dir = os.path.join(split_dir,"train")
valid_dir = os.path.join(split_dir,"valid")

norm_mean = [0.485,0.456,0.406]
norm_std = [0.229,0.224,0.225]

# 数据增强操作
train_transform = transforms.Compose([
    transforms.Resize((32,32)),
    transforms.RandomCrop(32,padding=4),
    transforms.RandomGrayscale(p=0.8),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean,norm_std),
])
valid_transform = transforms.Compose([
    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize(norm_mean, norm_std),
])

train_data = RMBDataset(data_dir=train_dir,transform=train_transform)
valid_data = RMBDataset(data_dir=valid_dir,transform=valid_transform)
# train and valid
train_loader = DataLoader(dataset=train_data,batch_size=BATCH_SIZE,shuffle=True)
valid_loader = DataLoader(dataset=valid_data,batch_size=BATCH_SIZE)

#--------------------------------------------model-------------------------------------------
net = LeNet(classes=2)
net.initialize_weights()

#---------------------------------------------loss function----------------------------------
criterion = nn.CrossEntropyLoss()

#-----------------------------------------optimizer-------------------------------------------
optimizer = optim.SGD(net.parameters(),lr=LR,momentum=0.9)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=10,gamma=0.1)

#---------------------------------------------train---------------------------------------
train_curve = list()
valid_curve = list()

for epoch in range(MAX_EPOCH):

    loss_mean = 0.
    correct = 0.
    total = 0.

    net.train()
    for i, data in enumerate(train_loader):

        # forward
        inputs, labels = data
        outputs = net(inputs)

        # backward
        optimizer.zero_grad()
        loss = criterion(outputs, labels)
        loss.backward()

        # update weights
        optimizer.step()

        # 统计分类情况
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).squeeze().sum().numpy()

        # 打印训练信息
        loss_mean += loss.item()
        train_curve.append(loss.item())
        if (i+1) % log_interval == 0:
            loss_mean = loss_mean / log_interval
            print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
            loss_mean = 0.

    scheduler.step()  # 更新学习率

    # validate the model
    if (epoch+1) % val_interval == 0:

        correct_val = 0.
        total_val = 0.
        loss_val = 0.
        net.eval()
        with torch.no_grad():
            for j, data in enumerate(valid_loader):
                inputs, labels = data
                outputs = net(inputs)
                loss = criterion(outputs, labels)

                _, predicted = torch.max(outputs.data, 1)
                total_val += labels.size(0)
                correct_val += (predicted == labels).squeeze().sum().numpy()

                loss_val += loss.item()

            valid_curve.append(loss_val)
            print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
                epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val, correct / total))
Training:Epoch[000/010] Iteration[010/013] Loss: 0.6398 Acc:58.75%
Valid:	 Epoch[000/010] Iteration[004/004] Loss: 0.8873 Acc:59.30%
Training:Epoch[001/010] Iteration[010/013] Loss: 0.2964 Acc:91.25%
Valid:	 Epoch[001/010] Iteration[004/004] Loss: 0.0202 Acc:91.96%
Training:Epoch[002/010] Iteration[010/013] Loss: 0.1380 Acc:95.00%
Valid:	 Epoch[002/010] Iteration[004/004] Loss: 0.0786 Acc:95.48%
Training:Epoch[003/010] Iteration[010/013] Loss: 0.0578 Acc:98.12%
Valid:	 Epoch[003/010] Iteration[004/004] Loss: 0.0015 Acc:98.49%
Training:Epoch[004/010] Iteration[010/013] Loss: 0.0129 Acc:99.38%
Valid:	 Epoch[004/010] Iteration[004/004] Loss: 0.0003 Acc:99.50%
Training:Epoch[005/010] Iteration[010/013] Loss: 0.0550 Acc:98.12%
Valid:	 Epoch[005/010] Iteration[004/004] Loss: 0.0000 Acc:98.49%
Training:Epoch[006/010] Iteration[010/013] Loss: 0.0592 Acc:98.75%
Valid:	 Epoch[006/010] Iteration[004/004] Loss: 0.0047 Acc:98.99%
Training:Epoch[007/010] Iteration[010/013] Loss: 0.0196 Acc:98.75%
Valid:	 Epoch[007/010] Iteration[004/004] Loss: 0.0000 Acc:98.99%
Training:Epoch[008/010] Iteration[010/013] Loss: 0.0124 Acc:99.38%
Valid:	 Epoch[008/010] Iteration[004/004] Loss: 0.0002 Acc:99.50%
Training:Epoch[009/010] Iteration[010/013] Loss: 0.0078 Acc:100.00%
Valid:	 Epoch[009/010] Iteration[004/004] Loss: 0.0000 Acc:100.00%
train_x = range(len(train_curve))
train_y = train_curve

train_iters = len(train_loader)
valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
valid_y = valid_curve

plt.plot(train_x, train_y, label='Train')
plt.plot(valid_x, valid_y, label='Valid')
plt.legend(loc='upper right')
plt.ylabel('loss value')
plt.xlabel('Iteration')
plt.show()


pytorch只优化网络的一部分参数 pytorch中的优化器_Pytorch_21

LR=0.0125,momentum=0.9

测试效果:

test_dir = os.path.join("test_data")
test_data = RMBDataset(data_dir=test_dir,transform=valid_transform)
valid_loader = DataLoader(dataset=test_data,batch_size=1)
for i,data in enumerate(valid_loader):
    inputs,labels = data
    outputs = net(inputs)
    _,predicted = torch.max(outputs.data,1)
    
    rmb = 1 if predicted.numpy()[0] == 0 else 100
    img_tensor = inputs[0,...]
    img = transform_invert(img_tensor,train_transform)
    plt.imshow(img)
    plt.title("LeNet got {} yuan".format(rmb))
    plt.show()
    plt.pause(0.5)
    plt.close()

pytorch只优化网络的一部分参数 pytorch中的优化器_python_22


五、其他优化器

1. optim.SGD:随机梯度下降法《On the importance of initialization and momentum in deep learning 》

2. optim.Adagrad:自适应学习率梯度下降法


《Adaptive Subgradient Methods for Online Learning and Stochastic


Optimization》


3. optim.RMSprop: Adagrad的改进http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf

4. optim.Adadelta: Adagrad的改进《 AN ADAPTIVE LEARNING RATE METHOD》

5. optim.Adam:RMSprop结合Momentum《Adam: A Method for Stochastic Optimization》

6. optim.Adamax:Adam增加学习率上限《Adam: A Method for Stochastic Optimization》

7. optim.SparseAdam:稀疏版的Adam

8. optim.ASGD:随机平均梯度下降


《Accelerating Stochastic Gradient Descent using Predictive Variance


Reduction》


9. optim.Rprop:弹性反向传播《Martin Riedmiller und Heinrich Braun》

10. optim.LBFGS:BFGS的改进