zoukankan      html  css  js  c++  java
  • 百度PaddlePaddle入门-13(网络优化)

    神经网络所拟合的函数是高度非凸函数,理想的训练目标是,优化这类函数,达到函数最小值点或接近最小值的极小值点。选择合适的优化器和学习率是实现训练目标的关键因素。接下来主要探讨如何设置合理的优化器和学习率参数,保证训练结果达到理想目标。

    注:设置模型的优化算法在启动训练前,所以加载数据和网络结构的代码均不变


      1 # 加载相关库
      2 import os
      3 import random
      4 import paddle
      5 import paddle.fluid as fluid
      6 from paddle.fluid.dygraph.nn import Conv2D, Pool2D, FC
      7 import numpy as np
      8 from PIL import Image
      9 
     10 import gzip
     11 import json
     12 
     13 # 定义数据集读取器
     14 def load_data(mode='train'):
     15 
     16     # 读取数据文件
     17     datafile = './work/mnist.json.gz'
     18     print('loading mnist dataset from {} ......'.format(datafile))
     19     data = json.load(gzip.open(datafile))
     20     # 读取数据集中的训练集,验证集和测试集
     21     train_set, val_set, eval_set = data
     22 
     23     # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
     24     IMG_ROWS = 28
     25     IMG_COLS = 28
     26     # 根据输入mode参数决定使用训练集,验证集还是测试
     27     if mode == 'train':
     28         imgs = train_set[0]
     29         labels = train_set[1]
     30     elif mode == 'valid':
     31         imgs = val_set[0]
     32         labels = val_set[1]
     33     elif mode == 'eval':
     34         imgs = eval_set[0]
     35         labels = eval_set[1]
     36     # 获得所有图像的数量
     37     imgs_length = len(imgs)
     38     # 验证图像数量和标签数量是否一致
     39     assert len(imgs) == len(labels), 
     40           "length of train_imgs({}) should be the same as train_labels({})".format(
     41                   len(imgs), len(labels))
     42 
     43     index_list = list(range(imgs_length))
     44 
     45     # 读入数据时用到的batchsize
     46     BATCHSIZE = 100
     47 
     48     # 定义数据生成器
     49     def data_generator():
     50         # 训练模式下,打乱训练数据
     51         if mode == 'train':
     52             random.shuffle(index_list)
     53         imgs_list = []
     54         labels_list = []
     55         # 按照索引读取数据
     56         for i in index_list:
     57             # 读取图像和标签,转换其尺寸和类型
     58             img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
     59             label = np.reshape(labels[i], [1]).astype('int64')
     60             imgs_list.append(img) 
     61             labels_list.append(label)
     62             # 如果当前数据缓存达到了batch size,就返回一个批次数据
     63             if len(imgs_list) == BATCHSIZE:
     64                 yield np.array(imgs_list), np.array(labels_list)
     65                 # 清空数据缓存列表
     66                 imgs_list = []
     67                 labels_list = []
     68 
     69         # 如果剩余数据的数目小于BATCHSIZE,
     70         # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
     71         if len(imgs_list) > 0:
     72             yield np.array(imgs_list), np.array(labels_list)
     73 
     74     return data_generator
     75 
     76 
     77 # 定义模型结构
     78 class MNIST(fluid.dygraph.Layer):
     79      def __init__(self, name_scope):
     80          super(MNIST, self).__init__(name_scope)
     81          name_scope = self.full_name()
     82          # 定义卷积层,输出通道20,卷积核大小为5,步长为1,padding为2,使用relu激活函数
     83          self.conv1 = Conv2D(name_scope, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
     84          # 定义池化层,池化核为2,采用最大池化方式
     85          self.pool1 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
     86          # 定义卷积层,输出通道20,卷积核大小为5,步长为1,padding为2,使用relu激活函数
     87          self.conv2 = Conv2D(name_scope, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
     88          # 定义池化层,池化核为2,采用最大池化方式
     89          self.pool2 = Pool2D(name_scope, pool_size=2, pool_stride=2, pool_type='max')
     90          # 定义全连接层,输出节点数为10,激活函数使用softmax
     91          self.fc = FC(name_scope, size=10, act='softmax')
     92          
     93     # 定义网络的前向计算过程
     94      def forward(self, inputs):
     95          x = self.conv1(inputs)
     96          x = self.pool1(x)
     97          x = self.conv2(x)
     98          x = self.pool2(x)
     99          x = self.fc(x)
    100          return x

    最优的学习率需要多轮调试

    在深度学习神经网络模型中,通常使用标准的随机梯度下降算法更新参数,学习率代表参数更新幅度的大小当学习率最优时,模型的有效容量最大。学习率设置和当前深度学习任务有关,合适的学习率往往需要调参经验和大量的实验,总结来说,学习率选取需要注意以下两点:

    • 学习率不是越小越好。学习率越小,损失函数的变化速度越慢,意味着我们需要花费更长的时间进行收敛。
    • 学习率不是越大越好。因为只根据总样本集中的一个批次计算梯度,抽样误差会导致计算出的梯度不是全局最优的方向,且存在波动。同时,在接近最优解时,过大的学习率会导致参数在最优解附近震荡,导致损失难以收敛。

    上图左边,学习率合理下降均匀;右边,学习率过大,导致在底部最小点附近震荡。

     1 #仅优化算法的设置有所差别
     2 with fluid.dygraph.guard():
     3     model = MNIST("mnist")
     4     model.train()
     5     #调用加载数据的函数
     6     train_loader = load_data('train')
     7     
     8     #设置不同初始学习率
     9     optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01)    #018
    10     #optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001)  #0.23
    11     #optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.1)    #0.07
    12     
    13     EPOCH_NUM = 5
    14     for epoch_id in range(EPOCH_NUM):
    15         for batch_id, data in enumerate(train_loader()):
    16             #准备数据,变得更加简洁
    17             image_data, label_data = data
    18             image = fluid.dygraph.to_variable(image_data)
    19             label = fluid.dygraph.to_variable(label_data)
    20             
    21             #前向计算的过程
    22             predict = model(image)
    23             
    24             #计算损失,取一个批次样本损失的平均值
    25             loss = fluid.layers.cross_entropy(predict, label)
    26             avg_loss = fluid.layers.mean(loss)
    27             
    28             #每训练了100批次的数据,打印下当前Loss的情况
    29             if batch_id % 200 == 0:
    30                 print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
    31             
    32             #后向传播,更新参数的过程
    33             avg_loss.backward()
    34             optimizer.minimize(avg_loss)
    35             model.clear_gradients()
    36 
    37     #保存模型参数
    38     fluid.save_dygraph(model.state_dict(), 'mnist')
    loading mnist dataset from ./work/mnist.json.gz ......
    epoch: 0, batch: 0, loss is: [2.6056936]
    epoch: 0, batch: 200, loss is: [0.47590914]
    epoch: 0, batch: 400, loss is: [0.23337086]
    epoch: 1, batch: 0, loss is: [0.29190642]
    epoch: 1, batch: 200, loss is: [0.27118027]
    epoch: 1, batch: 400, loss is: [0.1074637]
    epoch: 2, batch: 0, loss is: [0.158496]
    epoch: 2, batch: 200, loss is: [0.30373794]
    epoch: 2, batch: 400, loss is: [0.1660601]
    epoch: 3, batch: 0, loss is: [0.15808547]
    epoch: 3, batch: 200, loss is: [0.26393268]
    epoch: 3, batch: 400, loss is: [0.09973648]
    epoch: 4, batch: 0, loss is: [0.08508419]
    epoch: 4, batch: 200, loss is: [0.10338296]
    epoch: 4, batch: 400, loss is: [0.04516026]

    选择适合的优化算法训练模型

    学习率是优化器的一个参数,虽然参数更新都是采用梯度下降算法,但是不同的梯度下降算法影响着神经网络的收敛效果。当随机梯度下降算法SGD无法满足我们的需求时,可以尝试如下三个思路选取优化器。

    1. 加入“动量”,参数更新的方向更稳定,比如Momentum优化器。每个批次的数据含有抽样误差,导致梯度更新的方向波动较大。如果我们引入物理动量的概念,给梯度下降的过程加入一定的“惯性”累积,就可以减少更新路径上的震荡!即每次更新的梯度由“历史多次梯度的累积方向”和“当次梯度”加权相加得到。历史多次梯度的累积方向往往是从全局视角更正确的方向,这与“惯性”的物理概念很像,也是为何其起名为“Momentum”的原因。类似不同品牌和材质的篮球有一定的重量差别,街头篮球队中的投手(擅长中远距离投篮)喜欢稍重篮球的比例较高。一个很重要的原因是,重的篮球惯性大,更不容易受到手势的小幅变形或风吹的影响。

    2. 根据不同参数距离最优解的远近,动态调整学习率,比如AdaGrad优化器。通过调整学习率的实验可以发现:当某个参数的现值距离最优解较远时(表现为梯度的绝对值较大),我们期望参数更新的步长大一些,以便更快收敛到最优解。当某个参数的现值距离最优解较近时(表现为梯度的绝对值较小),我们期望参数的更新步长小一些,以便更精细的逼近最优解。类似于打高尔夫球,专业运动员第一杆开球时,通常会大力打一个远球,让球尽量落在洞口附近。当第二杆面对离洞口较近的球时,他会更轻柔而细致的推杆,避免将球打飞。与此类似,参数更新的步长应该随着优化过程逐渐减少,减少的程度与当前梯度的大小有关。根据这个思想编写的优化算法称为“AdaGrad”,Ada是Adaptive的缩写,表示“适应环境而变化”的意思。

    3. 因为上述两个优化思路是正交的,所以可以将两个思路结合起来,这就是当前广泛应用的Adam算法。

     1 #仅优化算法的设置有所差别
     2 with fluid.dygraph.guard():
     3     model = MNIST("mnist")
     4     model.train()
     5     #调用加载数据的函数
     6     train_loader = load_data('train')
     7     
     8     #四种优化算法的设置方案,可以逐一尝试效果
     9     opti_SGD = fluid.optimizer.SGDOptimizer(learning_rate=0.01)   #0.12 
    10     #opti_Mom = fluid.optimizer.MomentumOptimizer(learning_rate=0.01, momentum=0.9)
    11     #opti_Adag = fluid.optimizer.AdagradOptimizer(learning_rate=0.01)#0.2
    12     #opti_Adam = fluid.optimizer.AdamOptimizer(learning_rate=0.01) #0.03
    13     x=[]
    14     y1=[]
    15     y2=[]
    16     y3=[]
    17     y4=[]
    18     iter_num=0
    19     
    20     EPOCH_NUM = 5
    21     for epoch_id in range(EPOCH_NUM):
    22         for batch_id, data in enumerate(train_loader()):
    23             #准备数据,变得更加简洁
    24             image_data, label_data = data
    25             image = fluid.dygraph.to_variable(image_data)
    26             label = fluid.dygraph.to_variable(label_data)
    27             
    28             #前向计算的过程
    29             predict = model(image)
    30             
    31             #计算损失,取一个批次样本损失的平均值
    32             loss1 = fluid.layers.cross_entropy(predict, label)
    33             avg_loss1 = fluid.layers.mean(loss1)
    34             #loss2 = fluid.layers.cross_entropy(predict, label)
    35             #avg_loss2 = fluid.layers.mean(loss2)
    36             #loss3 = fluid.layers.cross_entropy(predict, label)
    37             #avg_loss3 = fluid.layers.mean(loss3)
    38             #loss4 = fluid.layers.cross_entropy(predict, label)
    39             #avg_loss4 = fluid.layers.mean(loss4)
    40             
    41             #每训练了100批次的数据,打印下当前Loss的情况
    42             if batch_id % 200 == 0:
    43                 print("epoch: {}, batch: {}, loss is: {},iter: {}".format(epoch_id, batch_id, avg_loss1.numpy(),iter_num))
    44                 y1.append(avg_loss1.numpy())
    45                 #y2.append(avg_loss2.numpy())
    46                 #y3.append(avg_loss3.numpy())
    47                 #y4.append(avg_loss4.numpy())
    48                 iter_num+=1
    49             #y1.append(avg_loss1.numpy())
    50             #y2.append(avg_loss2.numpy())
    51             #y3.append(avg_loss3.numpy())
    52             #y4.append(avg_loss4.numpy())
    53             
    54             #后向传播,更新参数的过程
    55             avg_loss1.backward()
    56             #avg_loss2.backward()
    57             #avg_loss3.backward()
    58             #avg_loss4.backward()
    59             opti_SGD.minimize(avg_loss1)
    60             #opti_Mom.minimize(avg_loss2)
    61             #opti_Adag.minimize(avg_loss3)
    62             #opti_Adam.minimize(avg_loss4)
    63             model.clear_gradients()
    64             
    65     x=range(iter_num)
    66     import matplotlib.pyplot as plt
    67     #plt.title('Compare loss tendency with different Optimizer')
    68     '''plt.plot(x,y1,'b',label="SGD")
    69     plt.plot(x,y2,'g',label="Mom")
    70     plt.plot(x,y3,'r',label="Adag")
    71     plt.plot(x,y4,'y',label="Adam")
    72     plt.legend()
    73     plt.xlabel("Every 2400 Batch")
    74     plt.ylabel("Loss")
    75     plt.show()'''
    76     fig,ax=plt.subplots()
    77     #ax.plot(x,y1,label='SGD')
    78     #ax.plot(x,y2,label='Mom')
    79     #ax.plot(x,y3,label='Adag')
    80     #ax.plot(x,y4,label='Adam')
    81     ax.legend(loc='upper right',frameon=False)
    82     plt.plot(x,y1,label='SGD')
    83     #plt.plot(x,y2,label='second')
    84     #plt.plot(x,y[:,2])
    85     plt.legend(frameon=True,framealpha=1)
    86 
    87     #保存模型参数
    88     fluid.save_dygraph(model.state_dict(), 'mnist')

    学习率相同,SGD的测试效果曲线如下(暂时无法同时在一张图上画出四个曲线)。

    loading mnist dataset from ./work/mnist.json.gz ......
    epoch: 0, batch: 0, loss is: [2.5861013],iter: 0
    epoch: 0, batch: 200, loss is: [0.3419642],iter: 1
    epoch: 0, batch: 400, loss is: [0.3126796],iter: 2
    epoch: 1, batch: 0, loss is: [0.2929134],iter: 3
    epoch: 1, batch: 200, loss is: [0.22857675],iter: 4
    epoch: 1, batch: 400, loss is: [0.2534462],iter: 5
    epoch: 2, batch: 0, loss is: [0.16139439],iter: 6
    epoch: 2, batch: 200, loss is: [0.23658308],iter: 7
    epoch: 2, batch: 400, loss is: [0.15185605],iter: 8
    epoch: 3, batch: 0, loss is: [0.27450126],iter: 9
    epoch: 3, batch: 200, loss is: [0.20561613],iter: 10
    epoch: 3, batch: 400, loss is: [0.14927392],iter: 11
    epoch: 4, batch: 0, loss is: [0.21470158],iter: 12
    epoch: 4, batch: 200, loss is: [0.10310929],iter: 13
    epoch: 4, batch: 400, loss is: [0.07067939],iter: 14
    
    No handles with labels found to put in legend.
    
    下面就是测试效果:

     1 # 预测100张图片准确率
     2 with fluid.dygraph.guard():
     3     print('start evaluation .......')
     4     datafile = './work/mnist.json.gz'
     5     print('loading mnist dataset from {} ......'.format(datafile))
     6     data = json.load(gzip.open(datafile))
     7     # 读取数据集中的训练集,验证集和测试集
     8     _, _, eval_set = data
     9     # 随机抽取100张测试数据(图片)
    10     num_img = 100
    11     test_imgs = eval_set[0]
    12     test_labs = eval_set[1]
    13     index = list(range(len(test_imgs)))
    14     random.shuffle(index) # 随机图片排序
    15     imgs_list = []
    16     labels_list = []
    17     # 按照索引读取数据
    18     for i in range(num_img):
    19         # 读取图像和标签,转换其尺寸和类型
    20         img = np.reshape(test_imgs[index[i]], [1, 28, 28]).astype('float32')
    21         label = np.array(test_labs[index[i]]).astype('int64')
    22         imgs_list.append(img) 
    23         labels_list.append(label)
    24     test_img = np.array(imgs_list)
    25     test_lab = np.array(labels_list)
    26     
    27     print(f"There are {test_img.shape[0]} eval images in total.")
    28     
    29     # 加载模型
    30     model = MNIST("mnist")
    31     model_state_dict, _ = fluid.load_dygraph('mnist')
    32     model.load_dict(model_state_dict)
    33     model.eval()
    34     
    35     # 预测图片
    36     test_img = fluid.dygraph.to_variable(test_img) # 转化为paddle数据格式
    37     results = model(test_img)
    38     results_num = np.argmax(results.numpy(), axis=1) # 获取概率最大值的标签
    39     correct_cls = (results_num == test_lab)
    40     acc = np.sum(correct_cls) / num_img
    41     
    42     print(f"{num_img}张测试图片测试的准确率是: {acc*100}%。")
    start evaluation .......
    loading mnist dataset from ./work/mnist.json.gz ......
    There are 100 eval images in total.
    100张测试图片测试的准确率是: 97.0%。

     
  • 相关阅读:
    简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他
    "听话"的品格的症状
    PHP中extract()函数的妙用
    分析源码的感悟
    JWT 多网站单点登录,放弃session
    How to use php serialize() and unserialize()
    [转]很好的文章,收藏一下
    Learning from the CakePHP source code
    Learning from the CakePHP source code
    关于PHP的一小段代码求解如下求解"%2$s"
  • 原文地址:https://www.cnblogs.com/yuzaihuan/p/12304803.html
Copyright © 2011-2022 走看看