? ? ? ? 本文采用Paddle深度学习框架实现手写数字识别,用python语言实现,包含一些其他第三方库。代码和数据均来源于百度AIstudio。
目录
前言
一、数据处理
数据集介绍
?数据集导入
? 效验数据有效性
二、网络结构
三、训练过程
调用GPU进行训练
?损失函数
反向传播
?学习率设置
训练总代码
四、模型的保存和加载
效果展示
?
?五、模型测试
?
前言
? ? ? ? 如果说房价预测是机器学习的“Hello world”,那么手写数字识别就是深度学习的“Hello world”。采用MNIST数据集包括50 000条训练样本、10 000条验证样本、10 000条测试样本。每个样本包含手写数字图片和对应的标签。本文将重点从数据处理,网络结构,训练过程,模型的保存和加载等几个方面讲解。
一、数据处理
数据集介绍
????????data包含三个元素的列表:train_set、val_set、 test_set,包括50 000条训练样本、10 000条验证样本、10 000条测试样本。每个样本包含手写数字图片和对应的标签。其中训练集样本用于确定模型参数;验证集样本用于调节模型超参数;测试机样本用于评估模型的质量。训练集train_set包含两个元素的列表:train_images、train_labels。其中train_images是[50 000, 784]的二维列表,包含50 000张图片。每张图片用一个长度为784的向量表示,内容是28*28尺寸的像素灰度值(黑白图片)。train_labels是[50 000, ]的列表,表示这些图片对应的分类标签,即0~9之间的一个数字。
如图所示:
?数据集导入
# 定义数据集读取器
def load_data(mode='train'):
# 读取数据文件
datafile = 'mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
data = json.load(gzip.open(datafile))
# 读取数据集中的训练集,验证集和测试集
train_set, val_set, eval_set = data
# 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
IMG_ROWS = 28
IMG_COLS = 28
# 根据输入mode参数决定使用训练集,验证集还是测试
if mode == 'train':
imgs = train_set[0]
labels = train_set[1]
elif mode == 'valid':
imgs = val_set[0]
labels = val_set[1]
elif mode == 'eval':
imgs = eval_set[0]
labels = eval_set[1]
????????datafile为文件的路径名。gzip.open表示打开此压缩文件,因为文件是json格式,所以用json.load加载数据并赋值给data。此时的data为一个列表,内包含三个元素分别是训练集数据,验证集数据和测试集数据。分别用train_set,val_set和eval_set表示这三个数据样本。因为本次使用的数据集的图片是28*28的灰度图。所以设置图片的长IMG_ROWS,IMG_COLS分别为28。再根据模型是训练模式,验证模式还是测试模式分别读取对应的数据。
# 获得所有图像的数量
imgs_length = len(imgs)
# 验证图像数量和标签数量是否一致
assert len(imgs) == len(labels), \
"length of train_imgs({}) should be the same as train_labels({})".format(
len(imgs), len(labels))
? 效验数据有效性
? ? ? ? 在实际的生产中,我们所获得的数据可能会存在一些问题?如数据标注不正确,数据格式不统一等。因此,我们需要增加一些操作来验证数据是否正确。
# 校验数据
imgs_length = len(imgs)
assert len(imgs) == len(labels), \
"length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(labels))
????????imgs是若干行,784列的矩阵。也可以理解为二维数组或者是一个具有若干个元素的列表。使用len()来求出元素的个数即样本的数量,与其标签的数量进行对比,若相等,则表明数据格式没有问题,若不相等,则表明数据格式存在问题。assert是python语言里的关键字,相当于if,如果assert后面的语句为真,则跳过;若果为假,则执行后面的语句。
# 定义数据集每个数据的序号,根据序号读取数据
index_list = list(range(imgs_length))
# 读入数据时用到的批次大小
BATCHSIZE = 100
# 定义数据生成器
def data_generator():
if mode == 'train':
# 训练模式下打乱数据
random.shuffle(index_list)
imgs_list = []
labels_list = []
for i in index_list:
# 将数据处理成希望的类型
img = np.array(imgs[i]).astype('float32')
label = np.array(labels[i]).astype('float32')
imgs_list.append(img)
labels_list.append(label)
if len(imgs_list) == BATCHSIZE:
# 获得一个batchsize的数据,并返回
yield np.array(imgs_list), np.array(labels_list)
# 清空数据读取列表
imgs_list = []
labels_list = []
# 如果剩余数据的数目小于BATCHSIZE,
# 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
if len(imgs_list) > 0:
yield np.array(imgs_list), np.array(labels_list)
? ? ? ?这里batchsize定义为100,表示每次向网络里喂入100组数据,即100个样本。用list()定义一个列表index_list为从0到样本数量的列表。之后定义一个数据生成器。本文不重点去介绍数据生成器,读者只需知道它可以提高训练速度即可。数据读取和模型训练并行。读取到的数据不断的放入缓存区,无需等待模型训练就可以启动下一轮数据读取。当模型训练完一个批次后,不用等待数据读取过程,直接从缓存区获得下一批次数据进行训练,从而加快了数据读取速度。用random.shuffle()将index_list列表乱序。之后重新定义2组空的列表。
? ? ? ? 之后依次遍历index_list列表。此时的列表是乱序以后的对应的序号,从imgs取出对应序号的元素,即784个元素的列表并将其转化为ndarray格式类型为“float32”,标签同理;并添加到对应元素的列表中去。当样本数够一个batchsize时,返回一组数据。注意这里也可以先转化格式,之后在进行乱序。因为shuffle只会对最外层的元素进行乱序。
? ? ? ? 最后的if语句是说,当数据不够以一个batch的时候,将剩余的数据返回。
二、网络结构
? ? ? ? 对于之前的房价预测问题。我们可以简单地去定义一个全连接神经网络,因为是一组数据可以线性输入,线性输出;但对于手写数字识别来说,如果简单的使用全连接神经网络,不会达到很好的预测效果。这里采用卷积层,池化层和全连接层来搭建神经网络。
代码如下:
# 多层卷积神经网络实现
class MNIST(paddle.nn.Layer):
def __init__(self):
super(MNIST, self).__init__()
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
# 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
# 定义池化层,池化核的大小kernel_size为2,池化步长为2
self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
# 定义一层全连接层,输出维度是10
self.fc = Linear(in_features=980, out_features=10)
# 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
# 卷积层激活函数使用Relu,全连接层激活函数使用softmax
def forward(self, inputs):
x = self.conv1(inputs)
x = F.relu(x)
x = self.max_pool1(x)
x = self.conv2(x)
x = F.relu(x)
x = self.max_pool2(x)
x = paddle.reshape(x, [x.shape[0], 980])
x = self.fc(x)
return x
首先定义一个MNIST类,并继承paddle.nn.Layer这个类。?supper()._init_()表示初始化父类的一些参数。Con2D表示卷积层,MaxPool2D表示池化层,Linear表示全连接层,他们全都是类。在MNIST类里定义一个forward方法表示前向传播,relu为激活函数。因为最后一层是全连接层,维度只能是1,所以之前要reshape一下。
三、训练过程
调用GPU进行训练
? ? ? ? 在计算机视觉这一领域,使用GPU来训练显然会获得比CPU训练更快的训练速度。paddle也提供了一些使用GPU的接口。
# 开启GPU
use_gpu = True
paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
? ? ? ? 一台计算机或者服务器可以有多个GPU,每个GPU都有与之对应的编号gpu:0表示使用第0号GPU进行训练。
?损失函数
# 计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predicts, labels)
avg_loss = paddle.mean(loss)
这里使用的是cross_entropy这一损失函数,并计算平均值。
反向传播
# 后向传播,更新参数的过程
avg_loss.backward()
opt.step()
opt.clear_grad()
?学习率设置
# 设置不同初始学习率
opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters())
这里的学习率设置为0.01,parameters = model.parameters()表示对所有参数进行梯度下降。
训练总代码
def train(model):
# 开启GPU
use_gpu = True
paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
model.train()
# 调用加载数据的函数
train_loader = load_data('train')
# 设置不同初始学习率
opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters())
EPOCH_NUM = 5
for epoch_id in range(EPOCH_NUM):
for batch_id, data in enumerate(train_loader()):
# 准备数据,变得更加简洁
images, labels = data
images = paddle.to_tensor(images)
labels = paddle.to_tensor(labels)
# 前向计算的过程
predicts = model(images)
# 计算损失,取一个批次样本损失的平均值
loss = F.cross_entropy(predicts, labels)
avg_loss = paddle.mean(loss)
# 每训练了100批次的数据,打印下当前Loss的情况
if batch_id % 200 == 0:
print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
# 后向传播,更新参数的过程
avg_loss.backward()
opt.step()
opt.clear_grad()
? ? ? ? ?训练迭代5轮,每轮当读取200份样本时打印loss
四、模型的保存和加载
# 保存模型参数
paddle.save(model.state_dict(), 'mnist.pdparams')
model.state_dict表示读取模型的参数,后面的字符串为模型保存的地址
效果展示
?五、模型测试
import numpy as np
import paddle
from PIL import Image
from Number import MNIST
def load_image(img_path):
# 从img_path中读取图像,并转为灰度图
im = Image.open(img_path).convert('L')
im = im.resize((28, 28), Image.ANTIALIAS)
im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
# 图像归一化
im = 1.0 - im / 255.
return im
# 定义预测过程
model = MNIST()
params_file_path = 'mnist.pdparams'
img_path = 'example_6.jpg'
# 加载模型参数
param_dict = paddle.load(params_file_path)
model.load_dict(param_dict)
# 灌入数据
model.eval()
tensor_img = load_image(img_path)
#模型反馈10个分类标签的对应概率
results = model(paddle.to_tensor(tensor_img))
#取概率最大的标签作为预测输出
lab = np.argsort(results.numpy())
print("本次预测的数字是: ", lab[0][-1])
|