【零基础实践深度学习】使用飞桨构建数字识别实践作业及知识回顾

第一周实践作业

作业内容

课程中以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结构如下图:
在这里插入图片描述
课程中采用的卷积层,输入为28
28,卷积核为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%。
在这里插入图片描述
总结:使用飞桨平台可以很快速地入门深度学习,降低了学习难度,通过一周的学习,了解了实践一个深度学习项目的基本步骤。深度学习,飞桨是一个不错的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值