《PaddlePaddle从入门到炼丹》四——卷积神经网络

2020-05-21 21:45:22 浏览数 (1)

原文博客:Doi技术团队 链接地址:https://blog.doiduoyi.com/authors/1584446358138 初心:记录优秀的Doi技术团队学习经历

前言

上一章我们通过学习线性回归例子入门了深度学习,同时也熟悉了PaddlePaddle的使用方式,那么我们在本章学习更有趣的知识点卷积神经网络。深度学习之所以那么流行,很大程度上是得益于它在计算机视觉上得到非常好的效果,而在深度学习上几乎是使用卷积神经网络来提取图像的特征的。在PaddlePaddle上如何定义一个卷积神经网络,并使用它来完成一个图像识别的任务呢。在本章我们通过学习MNIST图像数据集的分类例子,来掌握卷积神经网络的使用。

训练模型

创建一个mnist_classification.py文件,首先导入所需得包,这次使用到了MNIST数据集接口,也使用了处理图像得工具包。

代码语言:txt复制
import numpy as np
import paddle as paddle
import paddle.dataset.mnist as mnist
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt

在图像识别上,使用得算法也经过了多次的迭代更新,比如多层感知器,在卷积神经网络广泛使用之前,多层感知器在图像识别上是非常流行的,从这方面来看,多层感知器在当时也是有一定的优势的。那么如下使用PaddlePaddle来定义一个多层感知器呢,我们可以来学习一下。以下的代码判断就是定义一个简单的多层感知器,一共有三层,两个大小为100的隐层和一个大小为10的输出层,因为MNIST数据集是手写0到9的灰度图像,类别有10个,所以最后的输出大小是10。最后输出层的激活函数是Softmax,所以最后的输出层相当于一个分类器。加上一个输入层的话,多层感知器的结构是:输入层-->>隐层-->>隐层-->>输出层

代码语言:txt复制
# 定义多层感知器
def multilayer_perceptron(input):
    # 第一个全连接层,激活函数为ReLU
    hidden1 = fluid.layers.fc(input=input, size=100, act='relu')
    # 第二个全连接层,激活函数为ReLU
    hidden2 = fluid.layers.fc(input=hidden1, size=100, act='relu')
    # 以softmax为激活函数的全连接输出层,大小为label大小
    fc = fluid.layers.fc(input=hidden2, size=10, act='softmax')
    return fc

卷积神经网络普遍用在图像特征提取上,一些图像分类、目标检测、文字识别几乎都回使用到卷积神经网络作为图像的特征提取方式。卷积神经网络通常由卷积层、池化层和全连接层,有时还有Batch Normalization层和Dropout层。下面我们就创建一个简单卷积神经网络,一共定义了5层,加上输入层的话,它的结构是:输入层-->>卷积层-->>池化层-->>卷积层-->>池化层-->>输出层。我们可以通过调用PaddlePaddle的接口fluid.layers.conv2d()来做一次卷积操作,我们可以通过num_filters参数设置卷积核的数量,通过filter_size设置卷积核的大小,还有通过stride来设置卷积操作时移动的步长。使用fluid.layers.pool2d()接口做一次池化操作,通过参数pool_size可以设置池化的大小,通过参数pool_stride设置池化滑动的步长,通过参数pool_type设置池化的类型,目前有最大池化和平均池化,下面使用的时最大池化,当值为avg时是平均池化。

代码语言:txt复制
# 卷积神经网络
def convolutional_neural_network(input):
    # 第一个卷积层,卷积核大小为3*3,一共有32个卷积核
    conv1 = fluid.layers.conv2d(input=input,
                                num_filters=32,
                                filter_size=3,
                                stride=1)

    # 第一个池化层,池化大小为2*2,步长为1,最大池化
    pool1 = fluid.layers.pool2d(input=conv1,
                                pool_size=2,
                                pool_stride=1,
                                pool_type='max')

    # 第二个卷积层,卷积核大小为3*3,一共有64个卷积核
    conv2 = fluid.layers.conv2d(input=pool1,
                                num_filters=64,
                                filter_size=3,
                                stride=1)

    # 第二个池化层,池化大小为2*2,步长为1,最大池化
    pool2 = fluid.layers.pool2d(input=conv2,
                                pool_size=2,
                                pool_stride=1,
                                pool_type='max')

    # 以softmax为激活函数的全连接输出层,大小为label大小
    fc = fluid.layers.fc(input=pool2, size=10, act='softmax')
    return fc

定义输入层,输入的是图像数据。图像是28*28的灰度图,所以输入的形状是[1, 28, 28],如果图像是32*32的彩色图,那么输入的形状是[3. 32, 32],因为灰度图只有一个通道,而彩色图有RGB三个通道。理论上它还有一个维度是Batch的,不过这个是PaddlePaddle帮我们默认设置的,我们可以不用理会。

代码语言:txt复制
# 定义输入层
image = fluid.layers.data(name='image', shape=[1, 28, 28], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')

上面定义了多层感机器和卷积神经网络,我们可以在这里调用定义好的网络来获取分类器,读者可以尝试这两种不同的网络进行训练,观察一下他们的准确率如何。

代码语言:txt复制
# 获取分类器
# model = multilayer_perceptron(image)
model = convolutional_neural_network(image)

接着是定义损失函数,这次使用的是交叉熵损失函数,该函数在分类任务上比较常用。定义了一个损失函数之后,还有对它求平均值,因为定义的是一个Batch的损失值。同时我们还可以定义一个准确率函数,这个可以在我们训练的时候输出分类的准确率。

代码语言:txt复制
# 获取损失函数和准确率函数
cost = fluid.layers.cross_entropy(input=model, label=label)
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)

然后我们从主程序中克隆一个程序作为预测程序,之后可以使用这个预测程序预测测试的准确率和预测自己的图像。

代码语言:txt复制
# 获取测试程序
test_program = fluid.default_main_program().clone(for_test=True)

接着是定义优化方法,这次我们使用的是Adam优化方法,同时指定学习率为0.001。

代码语言:txt复制
# 定义优化方法
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.001)
opts = optimizer.minimize(avg_cost)

定义读取MNIST数据集的reader,指定一个Batch的大小为128,也就是一次训练128张图像。

代码语言:txt复制
# 获取MNIST数据
train_reader = paddle.batch(mnist.train(), batch_size=128)
test_reader = paddle.batch(mnist.test(), batch_size=128)

接着也是定义一个执行器和初始化参数,Fluid版本使用的流程都差不多。

代码语言:txt复制
# 定义一个使用CPU的执行器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())

输入的数据维度是图像数据和图像对应的标签,每个类别的图像都要对应一个标签,这个标签是从0递增的整型数值。

代码语言:txt复制
# 定义输入数据维度
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

最后就可以开始训练了,我们这次训练5个Pass,读者可以根据自己的情况自由设置。在上面我们已经定义了一个求准确率的函数,所以我们在训练的时候让它输出当前的准确率,计算准确率的原理很简单,就是把训练是预测的结果和真实的值比较,求出准确率。每一个Pass训练结束之后,再进行一次测试,使用测试集进行测试,并求出当前的Cost和准确率的平均值。

代码语言:txt复制
# 开始训练和测试
for pass_id in range(5):
    # 进行训练
    for batch_id, data in enumerate(train_reader()):
        train_cost, train_acc = exe.run(program=fluid.default_main_program(),
                                        feed=feeder.feed(data),
                                        fetch_list=[avg_cost, acc])
        # 每100个batch打印一次信息
        if batch_id % 100 == 0:
            print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
                  (pass_id, batch_id, train_cost[0], train_acc[0]))

    # 进行测试
    test_accs = []
    test_costs = []
    for batch_id, data in enumerate(test_reader()):
        test_cost, test_acc = exe.run(program=test_program,
                                      feed=feeder.feed(data),
                                      fetch_list=[avg_cost, acc])
        test_accs.append(test_acc[0])
        test_costs.append(test_cost[0])
    # 求测试结果的平均值
    test_cost = (sum(test_costs) / len(test_costs))
    test_acc = (sum(test_accs) / len(test_accs))
    print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))

输出信息:

代码语言:txt复制
Pass:0, Batch:0, Cost:3.50138, Accuracy:0.07812
Pass:0, Batch:100, Cost:0.14832, Accuracy:0.96875
Pass:0, Batch:200, Cost:0.13408, Accuracy:0.96875
Pass:0, Batch:300, Cost:0.11601, Accuracy:0.97656
Pass:0, Batch:400, Cost:0.27977, Accuracy:0.92969
Test:0, Cost:0.08879, Accuracy:0.97379
Pass:1, Batch:0, Cost:0.11175, Accuracy:0.96875
Pass:1, Batch:100, Cost:0.07854, Accuracy:0.97656
Pass:1, Batch:200, Cost:0.04025, Accuracy:0.99219
Pass:1, Batch:300, Cost:0.09936, Accuracy:0.98438
Pass:1, Batch:400, Cost:0.19245, Accuracy:0.95312
Test:1, Cost:0.10123, Accuracy:0.97241
Pass:2, Batch:0, Cost:0.13749, Accuracy:0.96094
Pass:2, Batch:100, Cost:0.06074, Accuracy:0.98438
Pass:2, Batch:200, Cost:0.01982, Accuracy:0.99219
Pass:2, Batch:300, Cost:0.06725, Accuracy:0.97656
Pass:2, Batch:400, Cost:0.10043, Accuracy:0.96875
Test:2, Cost:0.13354, Accuracy:0.96776
Pass:3, Batch:0, Cost:0.08895, Accuracy:0.98438
Pass:3, Batch:100, Cost:0.06339, Accuracy:0.96875
Pass:3, Batch:200, Cost:0.05107, Accuracy:0.98438
Pass:3, Batch:300, Cost:0.08062, Accuracy:0.97656
Pass:3, Batch:400, Cost:0.07631, Accuracy:0.96875
Test:3, Cost:0.11465, Accuracy:0.97449
Pass:4, Batch:0, Cost:0.01259, Accuracy:1.00000
Pass:4, Batch:100, Cost:0.01203, Accuracy:1.00000
Pass:4, Batch:200, Cost:0.08451, Accuracy:0.97656
Pass:4, Batch:300, Cost:0.16532, Accuracy:0.98438
Pass:4, Batch:400, Cost:0.09657, Accuracy:0.98438
Test:4, Cost:0.14624, Accuracy:0.97211

预测图像

训练完成之后,我们可以使用从主程序中克隆的test_program来预测我们自己的图像。再预测之前,要对图像进行预处理,处理方式要跟训练的时候一样。首先进行灰度化,然后压缩图像大小为28*28,接着将图像转换成一维向量,最后再对一维向量进行归一化处理。

代码语言:txt复制
# 对图片进行预处理
def load_image(file):
    im = Image.open(file).convert('L')
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
    im = im / 255.0 * 2.0 - 1.0
    return im

我们从网上下载一张图像,并将它命名为infer_3.png

代码语言:txt复制
!wget https://github.com/yeyupiaoling/LearnPaddle2/blob/master/note4/infer_3.png?raw=true -O 'infer_3.png'

我们可以使用Matplotlib工具显示这张图像。

代码语言:txt复制
img = Image.open('infer_3.png')
plt.imshow(img)
plt.show()

输出的图片:

最后把图像转换成一维向量并进行预测,数据从feed中的image传入,label设置一个假的label值传进去。fetch_list的值是网络模型的最后一层分类器,所以输出的结果是10个标签的概率值,这些概率值的总和为1。

代码语言:txt复制
# 加载数据并开始预测
img = load_image('./infer_3.png')
results = exe.run(program=test_program,
                  feed={'image': img, "label": np.array([[1]]).astype("int64")},
                  fetch_list=[model])

拿到每个标签的概率值之后,我们要获取概率最大的标签,并打印出来。

代码语言:txt复制
# 获取概率最大的label
lab = np.argsort(results)
print("该图片的预测结果的label为: %d" % lab[0][0][-1])

输出信息:

代码语言:txt复制
该图片的预测结果的label为: 3

到处为止,本章就结束了。经过学完这一章节,是不是觉得PaddlePaddle非常好用呢,借助PaddlePaddle我们很容易就定义了一个卷积神经网络,并完成了图像分类的训练和预测。卷积神经网络在图像识别上发挥着巨大的作用,而在自然语言处理上,循环神经网络同样起着巨大的作用,我们下一章就学习一下循环神经网络。

同步到百度AI Studio平台:http://aistudio.baidu.com/aistudio/projectdetail/29346

同步到科赛网K-Lab平台:https://www.kesci.com/home/project/5bf8c998954d6e001066d780

项目代码GitHub地址:https://github.com/yeyupiaoling/LearnPaddle2/tree/master/note4

注意: 最新代码以GitHub上的为准

上一章:《PaddlePaddle从入门到炼丹》三——线性回归
下一章:《PaddlePaddle从入门到炼丹》五——循环神经网络

参考资料

  1. https://blog.csdn.net/m_buddy/article/details/80224409
  2. http://www.paddlepaddle.org/documentation/docs/zh/1.0/beginners_guide/quick_start/recognize_digits/README.cn.html

0 人点赞