本人仅以 PaddlePaddle 深度学习 101 官网教程为指导,添加个人理解和笔记,仅作为学习练习使用,若有错误,还望批评指教。–ZJ
原文地址:PaddlePaddle 官网|PaddlePaddle 深度学习 101
环境:
- Python 2.7
- Ubuntu 16.04
本教程源代码目录在book/recognize_digits, 初次使用请参考PaddlePaddle安装教程,更多内容请参考本教程的视频课堂。
当我们学习编程的时候,编写的第一个程序一般是实现打印”Hello World”。而机器学习(或深度学习)的入门教程,一般都是 MNIST 数据库上的手写识别问题。原因是手写识别属于典型的图像分类问题,比较简单,同时MNIST数据集也很完备。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如图1所示的手写数字图片和对应的标签。图片是28x28的像素矩阵,标签则对应着0~9的10个数字。每张图片都经过了大小归一化和居中处理。
MNIST数据集是从 NIST 的Special Database 3(SD-3)和Special Database 1(SD-1)构建而来。由于SD-3是由美国人口调查局的员工进行标注,SD-1是由美国高中生进行标注,因此SD-3比SD-1更干净也更容易识别。Yann LeCun等人从SD-1和SD-3中各取一半作为MNIST的训练集(60000条数据)和测试集(10000条数据),其中训练集来自250位不同的标注员,此外还保证了训练集和测试集的标注员是不完全相同的。
Yann LeCun早先在手写字符识别上做了很多研究,并在研究过程中提出了卷积神经网络(Convolutional Neural Network),大幅度地提高了手写字符的识别能力,也因此成为了深度学习领域的奠基人之一。如今的深度学习领域,卷积神经网络占据了至关重要的地位,从最早Yann LeCun提出的简单LeNet,到如今ImageNet大赛上的优胜模型VGGNet、GoogLeNet、ResNet等(请参见图像分类 教程),人们在图像分类领域,利用卷积神经网络得到了一系列惊人的结果。
有很多算法在MNIST上进行实验。1998年,LeCun 分别用单层线性分类器、多层感知器(Multilayer Perceptron, MLP)和多层卷积神经网络LeNet进行实验,使得测试集上的误差不断下降(从12%下降到0.7%)[1]。此后,科学家们又基于K近邻(K-Nearest Neighbors)算法[2]、支持向量机(SVM)[3]、神经网络[4-7]和 Boosting方法[8]等做了大量实验,并采用多种预处理方法(如去除歪曲、去噪、模糊等)来提高识别的准确率。
本教程中,我们从简单的模型 Softmax 回归开始,带大家入门手写字符识别,并逐步进行模型优化。
基于MNIST数据训练一个分类器,在介绍本教程使用的三个基本图像分类网络前,我们先给出一些定义:
最简单的 Softmax 回归模型是先将输入层经过一个全连接层得到的特征,然后直接通过 softmax 函数进行多分类[9]。
输入层的数据 X X 传到输出层,在激活操作之前,会乘以相应的权重 W W ,并加上偏置变量 b b ,具体如下:
其中 softmax(xi)=exi∑jexj softmax ( x i ) = e x i ∑ j e x j
在分类问题中,我们一般采用交叉熵代价损失函数(cross entropy),公式如下:
图2为 softmax 回归的网络图,图中权重用蓝线表示、偏置用红线表示、+1 代表偏置参数的系数为1。
Softmax 回归模型采用了最简单的两层神经网络,即只有输入层和输出层,因此其拟合能力有限。为了达到更好的识别效果,我们考虑在输入层和输出层中间加上若干个隐藏层[10]。
图3为多层感知器的网络结构图,图中权重用蓝线表示、偏置用红线表示、+1 代表偏置参数的系数为1。
在多层感知器模型中,将图像展开成一维向量输入到网络中,忽略了图像的位置和结构信息,而卷积神经网络能够更好的利用图像的结构信息。LeNet-5是一个较简单的卷积神经网络。
图4显示了其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用 softmax 分类作为输出层。下面我们主要介绍卷积层和池化层。
卷积层是卷积神经网络的核心基石。在图像识别里我们提到的卷积是二维卷积,即离散二维滤波器(也称作卷积核)与二维图像做卷积操作。
简单的讲是二维滤波器滑动到二维图像上所有位置,并在每个位置上与该像素点及其领域像素点做内积。
卷积操作被广泛应用与图像处理领域,不同卷积核可以提取不同的特征,例如边沿、线性、角等特征。在深层卷积神经网络中,通过卷积操作可以提取出图像低级到复杂的特征。
在卷积操作中卷积核是可学习的参数,经过上面示例介绍,每层卷积的参数大小为 D×F×F×K D × F × F × K 。在多层感知器模型中,神经元通常是全部连接,参数较多。
而卷积层的参数较少,这也是由卷积层的主要特性即局部连接和共享权重所决定。
局部连接:每个神经元仅与输入神经元的一块区域连接,这块局部区域称作感受野(receptive field)。
权重共享:计算同一个深度切片的神经元时采用的滤波器是共享的。
通过介绍卷积计算过程及其特性,可以看出卷积是线性操作,并具有平移不变性(shift-invariant),平移不变性即在图像每个位置执行相同的操作。卷积层的局部连接和权重共享使得需要学习的参数大大减小,这样也有利于训练较大卷积神经网络。
池化是非线性下采样的一种形式,主要作用是通过减少网络的参数来减小计算量,并且能够在一定程度上控制过拟合。通常在卷积层的后面会加上一个池化层。池化包括最大池化、平均池化等。其中最大池化是用不重叠的矩形框将输入层分成不同的区域,对于每个矩形框的数取最大值作为输出层,如图6所示。
更详细的关于卷积神经网络的具体知识可以参考斯坦福大学公开课和图像分类教程。
sigmoid激活函数: f(x)=sigmoid(x)=11+e−x f ( x ) = s i g m o i d ( x ) = 1 1 + e − x
tanh激活函数: f(x)=tanh(x)=ex−e−xex+e−x f ( x ) = t a n h ( x ) = e x − e − x e x + e − x
实际上,tanh 函数只是规模变化的 sigmoid 函数,将 sigmoid 函数值放大2倍之后再向下平移1个单位: tanh(x)=2sigmoid(2x)−1 t a n h ( x ) = 2 s i g m o i d ( 2 x ) − 1 。
ReLU激活函数: f(x)=max(0,x) f ( x ) = m a x ( 0 , x )
更详细的介绍请参考维基百科激活函数。
PaddlePaddle在API中提供了自动加载MNIST数据的模块paddle.dataset.mnist
。加载后的数据位于/home/username/.cache/paddle/dataset/mnist
下:
文件名称 | 说明 |
---|---|
train-images-idx3-ubyte | 训练数据图片,60,000条数据 |
train-labels-idx1-ubyte | 训练数据标签,60,000条数据 |
t10k-images-idx3-ubyte | 测试数据图片,10,000条数据 |
t10k-labels-idx1-ubyte | 测试数据标签,10,000条数据 |
首先,加载 PaddlePaddle 的V2 api包。
import paddle.v2 as paddle
其次,定义三个不同的分类器:
def softmax_regression(img):
predict = paddle.layer.fc(input=img,
size=10,
act=paddle.activation.Softmax())
return predict
def multilayer_perceptron(img):
# 第一个全连接层,激活函数为ReLU
hidden1 = paddle.layer.fc(input=img, size=128, act=paddle.activation.Relu())
# 第二个全连接层,激活函数为ReLU
hidden2 = paddle.layer.fc(input=hidden1,
size=64,
act=paddle.activation.Relu())
# 以softmax为激活函数的全连接输出层,输出层的大小必须为数字的个数10
predict = paddle.layer.fc(input=hidden2,
size=10,
act=paddle.activation.Softmax())
return predict
def convolutional_neural_network(img):
# 第一个卷积-池化层
conv_pool_1 = paddle.networks.simple_img_conv_pool(
input=img,
filter_size=5,
num_filters=20,
num_channel=1,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
# 第二个卷积-池化层
conv_pool_2 = paddle.networks.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=50,
num_channel=20,
pool_size=2,
pool_stride=2,
act=paddle.activation.Relu())
# 以softmax为激活函数的全连接输出层,输出层的大小必须为数字的个数10
predict = paddle.layer.fc(input=conv_pool_2,
size=10,
act=paddle.activation.Softmax())
return predict
接着,通过layer.data
调用来获取数据,然后调用分类器(这里我们提供了三个不同的分类器)得到分类结果。训练时,对该结果计算其损失函数,分类问题常常选择交叉熵损失函数。
# 该模型运行在单个CPU上
paddle.init(use_gpu=False, trainer_count=1)
images = paddle.layer.data(
name='pixel', type=paddle.data_type.dense_vector(784))
label = paddle.layer.data(
name='label', type=paddle.data_type.integer_value(10))
# predict = softmax_regression(images) # Softmax回归
# predict = multilayer_perceptron(images) #多层感知器
predict = convolutional_neural_network(images) #LeNet5卷积神经网络
cost = paddle.layer.classification_cost(input=predict, label=label)
然后,指定训练相关的参数。
- 训练方法(optimizer): 代表训练过程在更新权重时采用动量优化器 Momentum
,其中参数0.9代表动量优化每次保持前一次速度的0.9倍。
- 训练速度(learning_rate): 迭代的速度,与网络的训练收敛速度有关系。
- 正则化(regularization): 是防止网络过拟合的一种手段,此处采用L2正则化。
parameters = paddle.parameters.create(cost)
optimizer = paddle.optimizer.Momentum(
learning_rate=0.1 / 128.0,
momentum=0.9,
regularization=paddle.optimizer.L2Regularization(rate=0.0005 * 128))
trainer = paddle.trainer.SGD(cost=cost,
parameters=parameters,
update_equation=optimizer)
下一步,我们开始训练过程。paddle.dataset.movielens.train()
和paddle.dataset.movielens.test()
分别做训练和测试数据集。这两个函数各自返回一个reader——PaddlePaddle中的reader是一个Python函数,每次调用的时候返回一个Python yield generator。
下面shuffle
是一个reader decorator,它接受一个reader A,返回另一个reader B —— reader B 每次读入buffer_size
条训练数据到一个buffer里,然后随机打乱其顺序,并且逐条输出。
batch
是一个特殊的decorator,它的输入是一个reader,输出是一个batched reader —— 在PaddlePaddle里,一个reader每次yield一条训练数据,而一个batched reader每次yield一个minibatch。
event_handler_plot
可以用来在训练过程中画图如下:
from paddle.v2.plot import Ploter
train_title = "Train cost"
test_title = "Test cost"
cost_ploter = Ploter(train_title, test_title)
step = 0
# event_handler to plot a figure
def event_handler_plot(event):
global step
if isinstance(event, paddle.event.EndIteration):
if step % 100 == 0:
cost_ploter.append(train_title, step, event.cost)
cost_ploter.plot()
step += 1
if isinstance(event, paddle.event.EndPass):
# save parameters
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f)
result = trainer.test(reader=paddle.batch(
paddle.dataset.mnist.test(), batch_size=128))
cost_ploter.append(test_title, step, result.cost)
event_handler
用来在训练过程中输出训练结果
lists = []
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "Pass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
if isinstance(event, paddle.event.EndPass):
# save parameters
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f)
result = trainer.test(reader=paddle.batch(
paddle.dataset.mnist.test(), batch_size=128))
print "Test with Pass %d, Cost %f, %s\n" % (
event.pass_id, result.cost, result.metrics)
lists.append((event.pass_id, result.cost,
result.metrics['classification_error_evaluator']))
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(
paddle.dataset.mnist.train(), buf_size=8192),
batch_size=128),
event_handler=event_handler_plot,
num_passes=5)
训练过程是完全自动的,event_handler里打印的日志类似如下所示:
# Pass 0, Batch 0, Cost 2.780790, {'classification_error_evaluator': 0.9453125}
# Pass 0, Batch 100, Cost 0.635356, {'classification_error_evaluator': 0.2109375}
# Pass 0, Batch 200, Cost 0.326094, {'classification_error_evaluator': 0.1328125}
# Pass 0, Batch 300, Cost 0.361920, {'classification_error_evaluator': 0.1015625}
# Pass 0, Batch 400, Cost 0.410101, {'classification_error_evaluator': 0.125}
# Test with Pass 0, Cost 0.326659, {'classification_error_evaluator': 0.09470000118017197}
训练之后,检查模型的预测准确度。用 MNIST 训练的时候,一般 softmax回归模型的分类准确率为约为 92.34%,多层感知器为97.66%,卷积神经网络可以达到 99.20%。
可以使用训练好的模型对手写体数字图片进行分类,下面程序展示了如何使用paddle.infer接口进行推断。
from PIL import Image
import numpy as np
import os
def load_image(file):
im = Image.open(file).convert('L')
im = im.resize((28, 28), Image.ANTIALIAS)
im = np.array(im).astype(np.float32).flatten()
im = im / 255.0 * 2.0 - 1.0
return im
test_data = []
cur_dir = os.getcwd()
test_data.append((load_image(cur_dir + '/image/infer_3.png'),))
probs = paddle.infer(
output_layer=predict, parameters=parameters, input=test_data)
lab = np.argsort(-probs) # probs and lab are the results of one batch data
print "Label of image/infer_3.png is: %d" % lab[0][0]
I0417 02:15:57.141983 6099 Util.cpp:166] commandline: --use_gpu=False --trainer_count=1
[INFO 2018-04-17 02:15:57,146 layers.py:2689] output for __conv_pool_0___conv: c = 20, h = 24, w = 24, size = 11520
[INFO 2018-04-17 02:15:57,147 layers.py:2829] output for __conv_pool_0___pool: c = 20, h = 12, w = 12, size = 2880
[INFO 2018-04-17 02:15:57,148 layers.py:2689] output for __conv_pool_1___conv: c = 50, h = 8, w = 8, size = 3200
[INFO 2018-04-17 02:15:57,149 layers.py:2829] output for __conv_pool_1___pool: c = 50, h = 4, w = 4, size = 800
I0417 02:15:57.157016 6099 GradientMachine.cpp:94] Initing parameters..
I0417 02:15:57.160969 6099 GradientMachine.cpp:101] Init parameters done.
Pass 0, Batch 0, Cost 2.987751, {'classification_error_evaluator': 0.921875}
Pass 0, Batch 100, Cost 0.556791, {'classification_error_evaluator': 0.1484375}
Pass 0, Batch 200, Cost 0.321648, {'classification_error_evaluator': 0.109375}
Pass 0, Batch 300, Cost 0.297841, {'classification_error_evaluator': 0.1015625}
Pass 0, Batch 400, Cost 0.309449, {'classification_error_evaluator': 0.0546875}
Test with Pass 0, Cost 0.210825, {'classification_error_evaluator': 0.05849999934434891}
Pass 1, Batch 0, Cost 0.300221, {'classification_error_evaluator': 0.078125}
Pass 1, Batch 100, Cost 0.159468, {'classification_error_evaluator': 0.046875}
Pass 1, Batch 200, Cost 0.118705, {'classification_error_evaluator': 0.0390625}
Pass 1, Batch 300, Cost 0.056896, {'classification_error_evaluator': 0.015625}
Pass 1, Batch 400, Cost 0.138569, {'classification_error_evaluator': 0.0390625}
Test with Pass 1, Cost 0.068874, {'classification_error_evaluator': 0.020400000736117363}
Pass 2, Batch 0, Cost 0.007586, {'classification_error_evaluator': 0.0}
Pass 2, Batch 100, Cost 0.032192, {'classification_error_evaluator': 0.0078125}
Pass 2, Batch 200, Cost 0.064767, {'classification_error_evaluator': 0.0234375}
Pass 2, Batch 300, Cost 0.185792, {'classification_error_evaluator': 0.046875}
Pass 2, Batch 400, Cost 0.057707, {'classification_error_evaluator': 0.015625}
Test with Pass 2, Cost 0.057466, {'classification_error_evaluator': 0.01720000058412552}
Pass 3, Batch 0, Cost 0.073687, {'classification_error_evaluator': 0.015625}
Pass 3, Batch 100, Cost 0.041405, {'classification_error_evaluator': 0.0078125}
Pass 3, Batch 200, Cost 0.042164, {'classification_error_evaluator': 0.015625}
Pass 3, Batch 300, Cost 0.068776, {'classification_error_evaluator': 0.0234375}
Pass 3, Batch 400, Cost 0.091678, {'classification_error_evaluator': 0.0234375}
Test with Pass 3, Cost 0.052247, {'classification_error_evaluator': 0.017400000244379044}
Pass 4, Batch 0, Cost 0.040702, {'classification_error_evaluator': 0.015625}
Pass 4, Batch 100, Cost 0.054435, {'classification_error_evaluator': 0.0234375}
Pass 4, Batch 200, Cost 0.044445, {'classification_error_evaluator': 0.0234375}
Pass 4, Batch 300, Cost 0.031175, {'classification_error_evaluator': 0.015625}
Pass 4, Batch 400, Cost 0.024161, {'classification_error_evaluator': 0.0078125}
Test with Pass 4, Cost 0.048175, {'classification_error_evaluator': 0.01600000075995922}
Best pass is 4, testing Avgcost is 0.0481753485592
The classification accuracy is 98.40%
Label of image/infer_3.png is: 3
softmax_regression()结果
Best pass is 4, testing Avgcost is 0.616509411097
The classification accuracy is 88.13% (准确率是 88.13% )
Label of image/infer_3.png is: 3
multilayer_perceptron()结果
Best pass is 4, testing Avgcost is 0.226120037252
The classification accuracy is 93.82% (准确率是 93.82% )
Label of image/infer_3.png is: 3
convolutional_neural_network() 结果
Best pass is 4, testing Avgcost is 0.0481753485592
The classification accuracy is 98.40% (分类的准确率是 98.40% )
预测结果:
Label of image/infer_3.png is: 3 (手写数据 3 预测结果是 3 )
本教程的 softmax 回归、多层感知器和卷积神经网络是最基础的深度学习模型,后续章节中复杂的神经网络都是从它们衍生出来的,因此这几个模型对之后的学习大有裨益。
同时,我们也观察到从最简单的 softmax 回归变换到稍复杂的卷积神经网络的时候,MNIST 数据集上的识别准确率有了大幅度的提升,原因是卷积层具有局部连接和共享权重的特性。
在之后学习新模型的时候,希望大家也要深入到新模型相比原模型带来效果提升的关键之处。此外,本教程还介绍了 PaddlePaddle 模型搭建的基本流程,从 dataprovider 的编写、网络层的构建,到最后的训练和预测。对这个流程熟悉以后,大家就可以用自己的数据,定义自己的网络模型,并完成自己的训练和预测任务了。
本教程 由 PaddlePaddle 创作,采用 知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。