目标
通过构建ANN,实现对手写数字识别,并对自己手写的数字进行预测
源码地址
数据分析
数据集为60000个训练样本和10000个测试数据,每个样本为图片和标签,图片大小为28*28,标签为0-9的数字
代码实现
导包
import numpy as np
import paddle
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt
import os
获取数据并创建数据读取器
# 准备处理数据
# 数据分为60000个训练样本和10000个测试数据。
# 每一个样本分为图片和标签,图片为28*28,标签为0-9的数字。
BATCH_SIZE = 128
BUF_SIZE = 512
# 训练数据提供器,每次随机读取batch_size大小的数据
# shuffle 的第二个参数是在缓冲区中存放数据的个数
train_reader = paddle.batch(
paddle.reader.shuffle(paddle.dataset.mnist.train(),
buf_size=BUF_SIZE), # 随机获取获取mnist的训练数据
batch_size=BATCH_SIZE
)
# 测试样本提供器
test_reader = paddle.batch(
paddle.dataset.mnist.test(),
batch_size=BATCH_SIZE
)
查看一个样本的样子
# 查看一个样本
train_data = paddle.dataset.mnist.train()
one_data = next(train_data())
print('image shape: ', one_data[0].shape)
print(one_data)
# 打印出图片
one_data = np.array(one_data[0], dtype='float32').reshape([28, 28])
plt.imshow(one_data, cmap='gray')
plt.show()
构建网络
这里使用全连接网络,隐藏层有两层,使用relu激活函数,输出层使用softmax激活函数。
第一层有100个神经元,输入为28 * 28 * 1 = 783,输出为100维的向量。
第二层也是100个神经元,输入为100维,输出为100维
输入层为10个神经元,输入100维,输出10
def Model(input):
# 第一个全连接层
h1 = fluid.layers.fc(input=input, size=100, act='relu')
# 第二个全连接层,输入为第一层的输出
h2 = fluid.layers.fc(input=h1, size=100, act='relu')
# 输出层
pre = fluid.layers.fc(input=h2, size=10, act='softmax')
return pre
创建输入的数据格式和输出的数据格式
# 图片为灰度图片,所以是单通道的
paddle.enable_static() # 这个可以注释试试,不报错就不用加
x = fluid.data(name='x', shape=[None, 1, 28, 28], dtype='float32')
# 标签,对应图片的类别,共10类,类型为整型
y = fluid.data(name='y', shape=[None, 1], dtype='int64')
定义损失函数等
# 获取模型
pre = Model(x)
# 定义损失函数
# 这里是多分类,因此使用交叉熵函数
cost = fluid.layers.cross_entropy(input=pre, label=y)
avg_cost = fluid.layers.mean(cost)
# 计算分类准确率
# loss 是计算实际值和真实值之间差距的一种方式
# acc 是看这次分类中正确分类在全体样本中的比例
acc = fluid.layers.accuracy(input=pre, label=y)
定义优化函数
# 定义优化函数
# 优化函数是怎么把最后一层算出来的损失值给传递到前面的网络
# 有梯度下降,随机梯度下降,批量梯度下降,带动量的梯度下降,adagrad
# adam等等。一般来说使用adam就能让数据好又快的收敛
LR = 0.001
optimizer = fluid.optimizer.Adam(learning_rate=LR)
opts = optimizer.minimize(avg_cost) # 这里就是最小化损失值,参数就是损失值变量
克隆main_program
# 克隆main_program 得到test_program,也就是共享一下训练数据训练出来的网络权重
# 或者就是使用一下训练数据刚刚训练出来的网络
test_program = fluid.default_main_program().clone(for_test=True)
创建运行的环境和Executor
# 创建运行用的Executor
# 这里我们使用gpu,如果使用的是cpu版本就改为False
use_cuda = True
# 创建运算场所
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 创建Executor
exe = fluid.Executor(place)
# 在训练前,对网络参数进行初始化
exe.run(fluid.default_startup_program())
创建数据提供器
# 创建数据提供器,使用DataFeeder创建一个Executor支持的输入结构
# 喂给它的参数为[x, y]形式
feeder = fluid.DataFeeder(place=place, feed_list=[x, y])
损失函数曲线
# 绘制训练的曲线
iter = 0
iters = []
costs = []
accs = []
def draw_cost_acc():
plt.title('train cost and acc', fontsize=24)
plt.xlabel('tier', fontsize=20)
plt.ylabel('cost/acc', fontsize=20)
plt.plot(iters, costs, color='red', label='cost')
plt.plot(iters, accs, color='green', label='acc')
plt.legend()
plt.grid()
plt.show()
开始训练
# 开始训练
# 共进行十个epoch
EPOCH = 10
for epoch in range(EPOCH): # 训练EPOCH轮
for batch, data in enumerate(train_reader()): # 遍历train
train_cost, train_acc = exe.run(
program=fluid.default_main_program(), # 执行主程序
feed=feeder.feed(data), # 将数据喂给模型
fetch_list=[avg_cost, acc]
)
# 保存迭代次数,cost和acc方便一会画图使用
iter = iter + 1
iters.append(iter)
costs.append(train_cost[0])
accs.append(train_acc[0])
# 每200个batch输出一个信息
if batch % 200 == 0:
print('Pass: %d, Batch:%d, Cost:%0.5f, Acc: %0.5f'%
(epoch, batch, train_cost[0], train_acc[0]))
test_costs = []
test_accs = []
# 每一个epoch进行一次验证
for batch, data in enumerate(test_reader()):
test_cost, test_acc = exe.run(
program=test_program, # 使用刚才克隆的Executor
feed=feeder.feed(data),
fetch_list=[avg_cost, acc]
)
# 保存下来test cost and test acc ,为了一会计算整体的cost和acc
test_costs.append(test_cost[0])
test_accs.append(test_acc[0])
# 计算这次验证的cost和acc
test_cost = (np.sum(test_costs) / len(test_costs))
test_acc = (np.sum(test_acc) / len(test_acc))
# 输出test信息
print('Test:%d, Cost:%0.5f, Acc: %0.5f' %(epoch, test_cost, test_acc))
# 绘制训练的曲线
draw_cost_acc()
保存训练模型,供测试使用(paddle.fluid.io.save_inference_model)
paddle.fluid.io.save_inference_model(dirname, feeded_var_names, target_vars, executor, main_program=None, model_filename=None, params_filename=None, export_for_deployment=True, program_only=False)
- dirname 模型和参数文件目录
- feeded_var_names 预测时需要提供的变量名称, 也就是我们上面的输入变量名称
- target_vars 保存预测结果的变量
- executor 用于保存预测模型的 executor
- 后面就自己看文档嘛 官方文档
# 保存模型
model_save_dir = r'\home\model\mnistmodel.model'
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print('save models to %s' %(model_save_dir))
fluid.io.save_inference_model(
model_save_dir,
feeded_var_names=['x'],
target_vars=[pre],
executor=exe
)
模型预测
infer_path = '/home/aistudio/data/data57711/手写数字3.png'
# 展示图片
img = Image.open(infer_path)
print(img.size)
plt.imshow(img)
plt.show()
# 读取图片并进行预处理
def load_img():
im = Image.open(infer_path).convert('L') # 将图片转为灰度图片
im = im.resize((28, 28), Image.ANTIALIAS) # resize image with high-quality
# 由于训练的时候是批次读取数据的,所以图片从三维变为四维,这里要和训练对应上
im = np.array(im).reshape(28, 28).astype(np.float32)
# 归一化
im = im / 255.0
# 将自己写的图片转换为黑底白字
for i in range(im.shape[0]):
for j in range(im.shape[1]):
if im[i][j] > 0.5:
im[i][j] = 0
else:
im[i][j] = 1
plt.imshow(im.reshape(28, 28), cmap='gray')
plt.show()
return im.reshape(1, 1, 28, 28)
load_img()
运行结果可以去上面的ai stdio网址里面看
创建测试用的executor
# 这里的计算环境还是使用上面定义好的
infer_exe = fluid.Executor(place)
# 创建一个作用域,通过with语句切换后,新创建的和上面同名的变量就不会冲突了
# 就相当于for循环中的变量
inference_scope = fluid.core.Scope()
加载模型(fluid.io.load_inference_model)
paddle.fluid.io.load_inference_model(dirname, executor, model_filename=None, params_filename=None, pserver_endpoints=None)
- 参数
- dirname : 模型文件路径
- executor : 测试的executor
- 自己看文档嘛 文档地址
- 返回值
- program 返回训练的神经网络的结构, 相当于fulid.default_main_program()
- feed_targer_names 字符串列表,包含所有需要提供数据的变量名称。
- fetch_targets Variable类型列表,包含着模型的所有输出变量。
# 加载模型并进行预测
with fluid.scope_guard(inference_scope): # 切换作用域
[inference_program, feed_target_name,
fetch_targets] = fluid.io.load_inference_model(model_save_dir, infer_exe)
img = load_img()
results = infer_exe.run(
program=inference_program,
feed={feed_target_name[0]: img},
fetch_list=fetch_targets
)
results = np.argsort(results)
print('图片预测结果为: ', results[0][0][-1])
print('图片真实结果为:3')