MXNet网络模型(二)—— 多层感知器
上一节展示了MXNet框架使用多层感知器来学习线性回归的代码(传送门),这一节进一步将多层感知器用于图像分类。
MNIST数据集
MNIST是机器学习中的经典数据集,他的主页是:http://yann.lecun.com/exdb/mnist/
MNIST数据集中有70,000个手写数字(从0到9)图像。其中60,000个属于训练集,10,000个属于测试集。每个图像只有1个数字,是28x28的灰度图。
MNIST数据集是图片分类中最基础的数据集,它比较简单,很容易达到一个很高的准确率。MNIST数据集中也有少量的数字很潦草,模棱两可,肉眼也很难区分,因此也很难达到100%的准确率。
网络模型
感知器对应神经网络中的全连接层,MNIST的数据都是图片,图片不能直接输入到全连接层,所以要先将28x28的图片转变为普通的数据。
把28x28的图片理解为28x28的二维矩阵,将二维矩阵摊平就可以得到长度为784的一维矩阵。接着把一维矩阵输入到神经网络,看图。
神经网络有2个中间层,中间层一般叫做隐藏层,还有一个输出层。输出层使用了独热(One-Hot)编码。
bit9 | bit8 | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 | 类别 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 2 |
0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 3 |
0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 4 |
0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 5 |
0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 6 |
0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 7 |
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 9 |
使用mx.viz.print_summary()函数可以查看网络的参数和规模:
网络层 | 网络类型 | 输出数据类型 | 训练参数 | 前一层 |
---|---|---|---|---|
data | 1x28x28 | 0 | ||
flatten | 摊平图像 | 784 | 0 | data |
layer1_fc | 全连接 | 128 | 100,480 | flatten |
layer1_act | 激活层 | 128 | 0 | layer1_fc |
layer2_fc | 全连接 | 64 | 8,256 | layer1_act |
layer2_act | 激活层 | 64 | 0 | layer2_fc |
layer3_fc | 全连接 | 10 | 650 | layer2_act |
softmax | Softmax输出 | 10 | 0 | layer3_fc |
统计 | 109,386 |
其中,训练参数 = (前一层输出数据个数 + 1) x 本层输出数据个数
( 784 + 1 ) x 128 = 100,480
( 128 + 1 ) x 64 = 8,256
( 64 + 1 ) x 10 = 650
Show me the code!
# -*- coding: utf-8 -*-
import logging
import struct
import gzip
import numpy as np
import mxnet as mx
logging.getLogger().setLevel(logging.DEBUG)
# 批大小
batch_size = 32
# 学习轮数
train_epoch = 20
'''
************************************************************
* 数据准备
************************************************************
'''
# 定义读取数据的函数
def read_data( label_url, image_url ):
with gzip.open( label_url ) as flbl:
magic, num = struct.unpack(">II", flbl.read(8)) # 读入标签文件头
label = np.fromstring( flbl.read(), dtype = np.int8 ) # 读入标签内容
with gzip.open( image_url, 'rb' ) as fimg:
magic, num, rows, cols = struct.unpack( ">IIII", fimg.read(16) ) # 读入图像文件头,rows和cols都是28
image = np.fromstring( fimg.read(), dtype = np.uint8 ) # 读入图像内容
image = image.reshape( len(label), 1, rows, cols ) # 设置为正确的数组格式
image = image.astype( np.float32 ) / 255.0 # 归一化到 0~1
return (label, image)
# 读入数据
# 注意路径的问题
( train_lbl, train_img ) = read_data( 'train-labels-idx1-ubyte.gz', 'train-images-idx3-ubyte.gz' )
( eval_lbl , eval_img ) = read_data( 't10k-labels-idx1-ubyte.gz' , 't10k-images-idx3-ubyte.gz' )
# 迭代器
train_iter = mx.io.NDArrayIter( train_img, train_lbl, batch_size, shuffle=True )
eval_iter = mx.io.NDArrayIter( eval_img , eval_lbl , batch_size ) # 验证集可以省略shuffle
'''
************************************************************
* 定义神经网络模型
************************************************************
'''
# 输入层
net = mx.sym.var( 'data' )
# 第1层隐藏层
net = mx.sym.Flatten ( data=net, name='flatten' ) # 将图像摊平
net = mx.sym.FullyConnected( data=net, name='layer1_fc' , num_hidden=128 )
net = mx.sym.Activation ( data=net, name='layer1_act', act_type='relu' )
# 第2层隐藏层
net = mx.sym.FullyConnected( data=net, name='layer2_fc' , num_hidden=64 )
net = mx.sym.Activation ( data=net, name='layer2_act', act_type='relu' )
# 输出层
net = mx.sym.FullyConnected( data=net, name='layer3_fc' , num_hidden=10 )
net = mx.sym.SoftmaxOutput ( data=net, name='softmax' ) # Softmax也是激活层
# 网络模型
module = mx.mod.Module( symbol=net )
# 网络模型可视化
# shape = {'data':(batch_size, 1, 28, 28)}
# mx.viz.plot_network(symbol=net, shape=shape).view() # 图片显示
# mx.viz.print_summary(symbol=net, shape=shape) # 表格显示
'''
************************************************************
* 训练神经网络
************************************************************
'''
# 定义评价标准(Evaluation Metric)
eval_metrics = mx.metric.CompositeEvalMetric()
eval_metrics.add( mx.metric.Accuracy() ); # 添加准确率作为标准
eval_metrics.add( mx.metric.CrossEntropy() ); # 添加交叉熵作为标准
# 开始训练
module.fit(
train_iter, # 前面定义的数据集
eval_data = eval_iter, # 前面定义的测试集
eval_metric = eval_metrics, # 前面定义的评价标准
initializer = mx.initializer.Xavier(), # Xavier初始化策略
num_epoch = train_epoch, # 训练轮数
optimizer = 'sgd', # “随机梯度下降”求解器
optimizer_params = {
'learning_rate':0.01, # 学习速率
'momentum': 0.9} # 惯性动量
)
# end
运行后,我们可以得到类似这样的输出:
INFO:root:Epoch[0] Train-accuracy=0.915450
INFO:root:Epoch[0] Train-cross-entropy=0.286579
INFO:root:Epoch[0] Time cost=2.580
INFO:root:Epoch[0] Validation-accuracy=0.946486
INFO:root:Epoch[0] Validation-cross-entropy=0.167986
INFO:root:Epoch[1] Train-accuracy=0.963650
INFO:root:Epoch[1] Train-cross-entropy=0.120830
INFO:root:Epoch[1] Time cost=2.750
INFO:root:Epoch[1] Validation-accuracy=0.952476
INFO:root:Epoch[1] Validation-cross-entropy=0.144074
...
INFO:root:Epoch[19] Train-accuracy=0.999983
INFO:root:Epoch[19] Train-cross-entropy=0.000889
INFO:root:Epoch[19] Time cost=2.714
INFO:root:Epoch[19] Validation-accuracy=0.981030
INFO:root:Epoch[19] Validation-cross-entropy=0.086616
和上一节相比,图片分类问题中,我们还可以用准确率评价神经网络。
经过20轮的训练,得到一个训练集准确率为99.998%,验证集98.103%的神经网络模型。