如何优化网络,提高准确率(来自百度培训)
神经网络所拟合的函数是高度非凸函数,理想的训练目标是,优化这类函数,达到函数最小值点或接近最小值的极小值点。选择合适的优化器和学习率是实现训练目标的关键因素。接下来主要探讨如何设置合理的优化器和学习率参数,保证训练结果达到理想目标。
注:设置模型的优化算法在启动训练前,所以加载数据和网络结构的代码均不变。
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无法满足我们的需求时,可以尝试如下三个思路选取优化器。
- 加入“动量”,参数更新的方向更稳定,比如Momentum优化器。每个批次的数据含有抽样误差,导致梯度更新的方向波动较大。如果我们引入物理动量的概念,给梯度下降的过程加入一定的“惯性”累积,就可以减少更新路径上的震荡!即每次更新的梯度由“历史多次梯度的累积方向”和“当次梯度”加权相加得到。历史多次梯度的累积方向往往是从全局视角更正确的方向,这与“惯性”的物理概念很像,也是为何其起名为“Momentum”的原因。类似不同品牌和材质的篮球有一定的重量差别,街头篮球队中的投手(擅长中远距离投篮)喜欢稍重篮球的比例较高。一个很重要的原因是,重的篮球惯性大,更不容易受到手势的小幅变形或风吹的影响。
- 根据不同参数距离最优解的远近,动态调整学习率,比如AdaGrad优化器。通过调整学习率的实验可以发现:当某个参数的现值距离最优解较远时(表现为梯度的绝对值较大),我们期望参数更新的步长大一些,以便更快收敛到最优解。当某个参数的现值距离最优解较近时(表现为梯度的绝对值较小),我们期望参数的更新步长小一些,以便更精细的逼近最优解。类似于打高尔夫球,专业运动员第一杆开球时,通常会大力打一个远球,让球尽量落在洞口附近。当第二杆面对离洞口较近的球时,他会更轻柔而细致的推杆,避免将球打飞。与此类似,参数更新的步长应该随着优化过程逐渐减少,减少的程度与当前梯度的大小有关。根据这个思想编写的优化算法称为“AdaGrad”,Ada是Adaptive的缩写,表示“适应环境而变化”的意思。
- 因为上述两个优化思路是正交的,所以可以将两个思路结合起来,这就是当前广泛应用的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: 14No 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%。
















