使用飞桨Paddle搭建LeNet手写数字识别

作者: Kevin 分类: 深度学习笔记 发布时间: 2022-01-24 20:32

源于兴趣,虽本身从事FPGA开发,但依然对深度学习有浓厚兴趣,毕竟这确实属于风口。

虽然国外有很多优秀的深度学习训练框架,但由于国外存在各种禁用的风险,还是选择国内百度的飞桨Paddle框架。

在21年其实已经学过一段时间的Paddle使用,但因为没有持续使用这个平台,很多掌握的东西也会随着时间慢慢淡忘,所以特地在博客上记录一下学习Paddle的使用。

今天这篇文章,其实主要是参考Paddle官网(https://www.paddlepaddle.org.cn/tutorials/projectdetail/2291401)的学习教程,根据官网提供的代码,再自己对着敲一遍,熟悉下Paddle整个训练的搭建流程。

一、代码结构

paddle_pycharm_01

在工程中,包含3个文件,lenet_class.py test.py train.py,lenet_class是定义lenet的网络结构,test.py是查看定义的网络结构各个子层、以及各个子层的输入输出尺寸,train.py是进行训练的主文件。

lenet_class.py文件代码如下:

# 导入Paddle的包
import paddle
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F

# 导入其他包
import numpy as np

# 定义 LeNet 网络结构
class LeNet(paddle.nn.Layer):
    def __init__(self, num_classes = 1):
        super(LeNet, self).__init__()

        # 创建卷积层、池化层
        # 创建第一个卷积层
        self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5)  # 经过第一个卷积层,输出尺寸为:6*24*24
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2) # 输出尺寸为:6*12*12

        # 尺寸的逻辑:池化层未改变通道数,当前通道数为6
        # 创建第二个卷积层
        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5) # 16*8*8
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2) # 16*4*4
        # 创建第 3 个卷积层
        self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4)

        # 尺寸的逻辑:输入层将数据拉平 [B, C, H, W] -> [B, C*H*W]
        # 输入 size 是[28, 28], 经过三次卷积和两次池化后, C*H*W = 120
        self.fc1 = Linear(in_features=120, out_features=64)
        # 创建全连接层,第一个全连接层的输出神经元个数为64, 第二个全连接层输出神经元个数为分类标签的类别数
        self.fc2 = Linear(in_features=64, out_features=num_classes)
    # 网络的前向传播过程
    # def forward(self, *inputs, **kwargs):
    def forward(self, x):
        x = self.conv1(x)
        # 每个卷积层使用 Sigmoid激活函数,后面跟这一个2x2的池化
        x = F.sigmoid(x)
        x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.sigmoid(x)
        x = self.max_pool2(x)
        x = self.conv3(x)
        # 尺寸的逻辑:输入层将数据拉平[B, C, H, W] -> [B,C*H*W]
        x = paddle.reshape(x, [x.shape[0], -1])
        x = self.fc1(x)
        x = F.sigmoid(x)
        x = self.fc2(x)

        return x

test.py文件代码:

'''
该文件可以是看成对定义的LeNet 网络查看各个层尺寸的测试文件
'''

import paddle

from lenet_class import *
import numpy as np
# 输入数据形状是 [N, 1, H, W]
x = np.random.randn(*[5, 1, 28,28])
x = x.astype('float32')

# 创建 LeNet 类的实例, 指定模型名称和分类的类别数目
model = LeNet(num_classes=10)
# 通过调用 LeNet 从基类继承的sublayers()行数,查看LeNet中所包含的子层
# print(model.sublayers())
###### 打印结果 #########
'''
[Conv2D(1, 6, kernel_size=[5, 5], data_format=NCHW),
 MaxPool2D(kernel_size=2, stride=2, padding=0),
 Conv2D(6, 16, kernel_size=[5, 5], data_format=NCHW),
 MaxPool2D(kernel_size=2, stride=2, padding=0),
 Conv2D(16, 120, kernel_size=[4, 4], data_format=NCHW),
 Linear(in_features=120, out_features=64, dtype=float32),
 Linear(in_features=64, out_features=10, dtype=float32)]
 '''
x = paddle.to_tensor(x)

for item in model.sublayers():
    # item 是 LeNet类中的一个子层
    # 查看经过子层之后的输出数据形状
    try:
        x = item(x)
    except:
        x = paddle.reshape(x, [x.shape[0], -1])
        x = item(x)

    if len(item.parameters()) == 2:
        print(item.full_name(), x.shape, item.parameters()[0].shape, item.parameters()[1].shape)
    else:
        print(item.full_name(), x.shape)

train.py代码:

# LeNet 识别数字
import os
import random
import paddle
import numpy as np
from lenet_class import LeNet
import paddle.nn.functional as F

from paddle.vision.transforms import ToTensor
from paddle.vision.datasets import MNIST

# 定义训练过程
def train(model, opt, train_loader, valid_loader):
    # 开启0号GPU
    use_gpu = True
    paddle.device.set_device('gpu:0') if use_gpu else paddle.device.set_device('cpu')
    print('start training ...')
    model.train() # 此处是设置成训练模式,并非让模型进行训练

    for epoch in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader):
            img = data[0]
            label = data[1]

            # 计算模型输出
            logits = model(img)

            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            avg_loss = paddle.mean(loss)

            if batch_id % 2000 == 0:
                print("epoch: {}, batch_id:{}, loss is : {:.4f}".format(epoch, batch_id, float(avg_loss.numpy())))

            avg_loss.backward() # 反向传播,,,这么简单就进行了反向传播,,,,,佩服
            opt.step()
            opt.clear_grad()

        ################
        model.eval() # 设置成验证模式
        accuracies = []
        losses = []

        for batch_id, data in enumerate(valid_loader()):
            img = data[0]
            label = data[1]

            # 计算模型输出
            logits = model(img)
            pred = F.softmax(logits) #进行归一化处理

            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            acc = paddle.metric.accuracy(pred, label)
            accuracies.append(acc.numpy())
            losses.append(loss.numpy())

        print("[validation] accuracy/loss: {:.4f}/{:.4f}".format(np.mean(accuracies), np.mean(losses)))

        model.train()
    paddle.save(model.state_dict(), 'lenet_mnist.pdparams')


EPOCH_NUM = 20
model = LeNet(num_classes=10)

# 设置优化器
opt = paddle.optimizer.Momentum(learning_rate=0.001, momentum=0.9, parameters=model.parameters())
# 定义数据
train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)
valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)

# 启动训练
train(model, opt, train_loader, valid_loader)

对 train.py运行后的过程如下:

run_01

二、总结

使用这个例子,能正常运行产生最终的参数文件,这是好的一个方面。但因为所用数据集是Paddle内置的,相当于它以及把 MNIST 数据集的相关格式处理好了,这个如果后面需要训练自己的数据集,还需要学习如何进行处理。

发表评论

您的电子邮箱地址不会被公开。