作业内容
课程中以1张图片为例,测试了预测效果,请从原始mnist数据集中,随机抽取出100张图片,测试下模型的分类准确率。
完整代码如下:
# 获取数据
import os
import random
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
import numpy as np
from PIL import Image
from visualdl import LogWriter
log_writer = LogWriter("./log")
import gzip
import json
#通过API直接获取mnist数据
train_data=paddle.dataset.mnist.train()
test_data=paddle.dataset.mnist.test()
train_data=paddle.reader.shuffle(train_data,100)
test_data=paddle.reader.shuffle(test_data,100)
train_data=paddle.batch(train_data,100)
test_data=paddle.batch(test_data,100)
# 定义模型结构
class MNIST(fluid.dygraph.Layer):
def __init__(self):
super(MNIST, self).__init__()
# 定义一个卷积层,使用relu激活函数
self.conv1 = Conv2D(num_channels=1, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
# 定义一个池化层,池化核为2,步长为2,使用最大池化方式
self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
# 定义一个卷积层,使用relu激活函数
self.conv2 = Conv2D(num_channels=20, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
# 定义一个池化层,池化核为2,步长为2,使用最大池化方式
self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
# 定义一个全连接层,输出节点数为10
self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
# 定义网络的前向计算过程
def forward(self, inputs, label):
x = self.conv1(inputs)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = fluid.layers.reshape(x, [x.shape[0], 980])
x = self.fc(x)
if label is not None:
acc = fluid.layers.accuracy(input=x, label=label)
return x, acc
else:
return x
#模型训练
#在使用GPU机器时,可以将use_gpu变量设置成True
use_gpu = True
place = fluid.CUDAPlace(0) if use_gpu else fluid.CPUPlace()
with fluid.dygraph.guard(place):
model = MNIST()
model.train()
EPOCH_NUM = 5
BATCH_SIZE = 100
# 定义学习率,并加载优化器参数到模型中
total_steps = (int(60000//BATCH_SIZE) + 1) * EPOCH_NUM
lr = fluid.dygraph.PolynomialDecay(0.01, total_steps, 0.001)
# 使用Adam优化器
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=lr, parameter_list=model.parameters())
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_data()):
#准备数据,变得更加简洁
img_data = np.array([x[0] for x in data]).astype('float32').reshape(-1,1,28,28)
# 获得图像标签数据,并转为float32类型的数组
label_data = np.array([x[1] for x in data]).astype('int64').reshape(-1, 1)
image = fluid.dygraph.to_variable(img_data)
label = fluid.dygraph.to_variable(label_data)
#前向计算的过程,同时拿到模型输出值和分类准确率
predict, acc = model(image, label)
avg_acc = fluid.layers.mean(acc)
#计算损失,取一个批次样本损失的平均值
loss = fluid.layers.cross_entropy(predict, label)
avg_loss = fluid.layers.mean(loss)
#每训练了200批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}, acc is {}".format(epoch_id, batch_id, avg_loss.numpy(),avg_acc.numpy()))
iter = iter + batch_id
log_writer.add_scalar(tag='acc', step=iter, value=acc.numpy())
log_writer.add_scalar(tag='loss', step=iter, value=avg_loss.numpy())
#后向传播,更新参数的过程
avg_loss.backward()
optimizer.minimize(avg_loss)
model.clear_gradients()
# 保存模型参数和优化器的参数
fluid.save_dygraph(model.state_dict(), './checkpoint/mnist_epoch{}'.format(epoch_id))
fluid.save_dygraph(optimizer.state_dict(), './checkpoint/mnist_epoch{}'.format(epoch_id))
#随机从训练集抽取100个图像进行识别,计算准确率
with fluid.dygraph.guard():
print('start evaluation .......')
#加载模型参数
model = MNIST()
model_state_dict, _ = fluid.load_dygraph('checkpoint/mnist_epoch4.pdopt')
model.load_dict(model_state_dict)
model.eval()
acc_set = []
avg_loss_set = []
for batch_id, data in enumerate(test_data()):
x_data = np.array([x[0] for x in data]).astype('float32').reshape(-1,1,28,28)
# 获得图像标签数据,并转为float32类型的数组
y_data = np.array([x[1] for x in data]).astype('int64').reshape(-1, 1)
img = fluid.dygraph.to_variable(x_data)
label = fluid.dygraph.to_variable(y_data)
prediction, acc = model(img, label)
loss = fluid.layers.cross_entropy(input=prediction, label=label)
avg_loss = fluid.layers.mean(loss)
acc_set.append(float(acc.numpy()))
avg_loss_set.append(float(avg_loss.numpy()))
#计算多个batch的平均损失和准确率
acc_val_mean = np.array(acc_set).mean()
avg_loss_val_mean = np.array(avg_loss_set).mean()
print('loss={}, acc={}'.format(avg_loss_val_mean, acc_val_mean))
所用知识回顾
1.数据处理
MNIST数据集以json格式存储在本地,其数据存储结构如下:
百度飞桨提供了非常方便的API可以直接获取数据
train_data=paddle.dataset.mnist.train()
2.模型结构设计
手写数字识别是一个典型的分类任务,输入的是2828的像素值,输出的是0-9的数字标签。线性回归模型很难准确完成分类任务,因此需要采用更强大的网络去构建模型。
CNN即卷积神经网络,对于计算机视觉问题能够得到非常好的效果。CNN由多个卷积层和池化层组成。在本实践中采用的CNN结构如下图:
课程中采用的卷积层,输入为2828,卷积核为5*5,卷积步长为1,padding为2.
原始图片的尺寸是28x28,卷积核的大小是5x5,滑动步长为1,那么按照卷积核的滑动方式,我们可以得到feature map的尺寸为 (28−5+1)×(28−5+1),通过一次卷积,尺寸变为了24×24,尺寸变小了,同时由于边框的像素在一次卷积中只用了一次,在多次卷积后,丢失了图像边缘信息。
所有通过设置padding,在原始图像的边框上填充像素,设置为2,即图像变为32×32,卷积之后还是28×28。下图表示卷积过程,蓝色框为28×28,紫色框为5x5。计算卷积核输出一个值,然后紫色矩形框向右移动一格,输出一个值,以此类推,最终输出28×28
定义一个池化层,池化核为2,步长为2,使用最大池化方式。输入为28×28,输出为14×14。如下图,池化核为2×2的紫色框,然后取其中最大的作为输出,然后右移两格,最后得到了14×14的输出。相当于原始图像变小为原来的一半。
第三层再卷积,输出还是14×14,第四层为池化层,输出为7×7,最后第五层为全连接层,然后输出最后的分类结果。
这五层网络构成了手写数字识别网络结构。通过卷积层使激活函数的权重数量大大减少,只关注核中的少量输入,同时还能了手写数字的边框特征,池化层缩小了像素规模,使得最终的参数量大大减少,提高了网络速度。
3.训练配置
设置学习率
在深度学习神经网络模型中,通常使用标准的随机梯度下降算法更新参数,学习率代表参数更新幅度的大小,即步长。当学习率最优时,模型的有效容量最大,最终能达到的效果最好。学习率和深度学习任务类型有关,合适的学习率往往需要大量的实验和调参经验。探索学习率最优值时需要注意如下两点:
学习率不是越小越好。学习率越小,损失函数的变化速度越慢,意味着我们需要花费更长的时间进行收敛。
学习率不是越大越好。只根据总样本集中的一个批次计算梯度,抽样误差会导致计算出的梯度不是全局最优的方向,且存在波动。在接近最优解时,过大的学习率会导致参数在最优解附近震荡,损失难以收敛。
主流学习率优化算法有四种:
SGD: 随机梯度下降算法,每次训练少量数据,抽样偏差导致参数收敛过程中震荡。
Momentum: 引入物理“动量”的概念,累积速度,减少震荡,使参数更新的方向更稳定。
每个批次的数据含有抽样误差,导致梯度更新的方向波动较大。如果我们引入物理动量的概念,给梯度下降的过程加入一定的“惯性”累积,就可以减少更新路径上的震荡,即每次更新的梯度由“历史多次梯度的累积方向”和“当次梯度”加权相加得到。历史多次梯度的累积方向往往是从全局视角更正确的方向,这与“惯性”的物理概念很像,也是为何其起名为“Momentum”的原因。类似不同品牌和材质的篮球有一定的重量差别,街头篮球队中的投手(擅长中远距离投篮)喜欢稍重篮球的比例较高。一个很重要的原因是,重的篮球惯性大,更不容易受到手势的小幅变形或风吹的影响。
AdaGrad: 根据不同参数距离最优解的远近,动态调整学习率。学习率逐渐下降,依据各参数变化大小调整学习率。
通过调整学习率的实验可以发现:当某个参数的现值距离最优解较远时(表现为梯度的绝对值较大),我们期望参数更新的步长大一些,以便更快收敛到最优解。当某个参数的现值距离最优解较近时(表现为梯度的绝对值较小),我们期望参数的更新步长小一些,以便更精细的逼近最优解。类似于打高尔夫球,专业运动员第一杆开球时,通常会大力打一个远球,让球尽量落在洞口附近。当第二杆面对离洞口较近的球时,他会更轻柔而细致的推杆,避免将球打飞。与此类似,参数更新的步长应该随着优化过程逐渐减少,减少的程度与当前梯度的大小有关。根据这个思想编写的优化算法称为“AdaGrad”,Ada是Adaptive的缩写,表示“适应环境而变化”的意思。RMSProp是在AdaGrad基础上的改进,AdaGrad会累加之前所有的梯度平方,而RMSprop仅仅是计算对应的梯度平均值,因而可以解决AdaGrad学习率急剧下降的问题。
Adam: 由于动量和自适应学习率两个优化思路是正交的,因此可以将两个思路结合起来,这就是当前广泛应用的算法。
训练可以采用GPU来训练,能够大幅度提高训练速度,飞桨提供的AIstudio支持GPU训练,本地版本也能在NVIDIA显卡上进行训练。
飞桨支持从上一次保存状态开始继续训练,只要我们随时保存训练过程中的模型状态,就不用从初始状态重新训练。
#保存模型参数和优化器的参数
fluid.save_dygraph(model.state_dict(), ‘./checkpoint/mnist_epoch{}’.format(epoch_id))
fluid.save_dygraph(optimizer.state_dict(), ‘./checkpoint/mnist_epoch{}’.format(epoch_id))
恢复训练
params_dict, opt_dict = fluid.load_dygraph(params_path)
训练中可以采用Matplotlib库和飞桨VisualDL进行可视化分析。
使用VisualDL非常简单,只需引入visualdl
from visualdl import LogWriter
log_writer = LogWriter("./log")
在训练工程中增加以下语句
log_writer.add_scalar(tag=‘acc’, step=iter, value=acc.numpy())
log_writer.add_scalar(tag=‘loss’, step=iter, value=avg_loss.numpy())
即可完成
训练完成后使用visualdl --logdir ./log --port 8080即可在http://127.0.0.1:8080/看到训练的信息。
下图为训练过程准确率变化
损失率变化如下:
从损失下降趋势看,经过3000个batch训练loss值已经非常低了,模型效果应该不错。通过准确率趋势可以看出此网络结构能够很好地识别手写数字。
最后随机100个图像验证准确率达到98.5%。
总结:使用飞桨平台可以很快速地入门深度学习,降低了学习难度,通过一周的学习,了解了实践一个深度学习项目的基本步骤。深度学习,飞桨是一个不错的选择。