特别注明:本文内容包括但不限于代码,图片均来自AI Studio 官网课程中
一个完整的深度学习模型一般包含以下几个内容:
- 数据获取与处理
- 模型设计:网络结构和损失函数
- 训练配置:优化器与资源配置
- 训练过程
- 模型保存与测试
下面以手写数字识别项目为例展开讲解:
数据获取与处理 本次学习使用百度提供的公开的数据集,因此自行获取数据的过程暂不涉及。 飞桨提供了多个封装好的数据集API,涵盖计算机视觉、自然语言处理、推荐系统等多个领域,帮助读者快速完成机器学习任务。如在手写数字识别任务中,通过paddle.dataset.mnist可以直接获取处理好的MNIST训练集、测试集,飞桨API支持如下常见的学术数据集:
- mnist
- cifar
- Conll05
- imdb
- imikolov
- movielens
- sentiment
- uci_
- housing
- wmt14
- wmt16 通过paddle.dataset.mnist.train()函数设置数据读取器,batch_size设置为8,即一个批次有8张图片和8个标签,代码如下所示。
import paddle
# 如果~/.cache/paddle/dataset/mnist/目录下没有MNIST数据,API会自动将MINST数据下载到该文件夹下
# 设置数据读取器,读取MNIST数据训练集
trainset = paddle.dataset.mnist.train()
# 包装数据读取器,每次读取的数据数量设置为batch_size=8
train_reader = paddle.batch(trainset, batch_size=8)
我们通过调用飞桨提供的API(paddle.dataset.mnist)加载MNIST数据集。但在工业实践中,我们面临的任务和数据环境千差万别,通常需要自己编写适合当前任务的数据处理程序,一般涉及如下五个环节:
- 读入数据
- 划分数据集
- 生成批次数据
- 训练样本集乱序
- 校验数据有效性
#数据处理部分之前的代码,加入部分数据处理的库
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Linear
import numpy as np
import os
import gzip
import json
import random
在实际应用中,保存到本地的数据存储格式多种多样,如MNIST数据集以json格式存储在本地,其数据存储结构如 图2 所示。
在本地./work/目录下读取文件名称为mnist.json.gz的MINST数据,并拆分成训练集、验证集和测试集,实现方法如下所示。
def load_data(mode='train'):
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
# 加载json数据文件
data = json.load(gzip.open(datafile))
print('mnist dataset load done')
# 读取到的数据区分训练集,验证集,测试集
train_set, val_set, eval_set = data
if mode=='train':
# 获得训练数据集
imgs, labels = train_set[0], train_set[1]
elif mode=='valid':
# 获得验证数据集
imgs, labels = val_set[0], val_set[1]
elif mode=='eval':
# 获得测试数据集
imgs, labels = eval_set[0], eval_set[1]
else:
raise Exception("mode can only be one of ['train', 'valid', 'eval']")
print("训练数据集数量: ", len(imgs))
# 校验数据
imgs_length = len(imgs)
assert len(imgs) == len(labels), \
"length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(label))
# 获得数据集长度
imgs_length = len(imgs)
# 定义数据集每个数据的序号,根据序号读取数据
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:
# 将数据处理成希望的格式,比如类型为float32,shape为[1, 28, 28]
img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
label = np.reshape(labels[i], [1]).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)
return data_generator
模型设计之网络结构:完成网络结构类的编写,包含__init__()函数和前向计算函数forward()
下面以经典的全连接神经网络为例:
# 多层全连接神经网络实现
class MNIST(fluid.dygraph.Layer):
def __init__(self):
super(MNIST, self).__init__()
# 定义两层全连接隐含层,输出维度是10,激活函数为sigmoid
self.fc1 = Linear(input_dim=784, output_dim=10, act='sigmoid') # 隐含层节点为10,可根据任务调整
self.fc2 = Linear(input_dim=10, output_dim=10, act='sigmoid')
# 定义一层全连接输出层,输出维度是1,不使用激活函数
self.fc3 = Linear(input_dim=10, output_dim=1, act=None)
# 定义网络的前向计算
def forward(self, inputs, label=None):
inputs = fluid.layers.reshape(inputs, [inputs.shape[0], 784])
outputs1 = self.fc1(inputs)
outputs2 = self.fc2(outputs1)
outputs_final = self.fc3(outputs2)
return outputs_final
模型设计之损失函数:均方误差常用于回归问题,对于分类问题人们比较习惯于使用交叉熵误差。交叉熵损失函数的设计是基于最大似然思想:最大概率得到观察结果的假设是真的。softmax函数:又名归一化指数函数,实际是sigmoid函数二分类问题到多分类的推广。能够将多个输出转化为对应的概率,且概率和为1,具体可以参考一分钟理解softmax函数(超简单)交叉熵损失函数:loss = -ln(对应正确解的输出概率)
用交叉熵作为损失函数时需要改变部分代码:
- 数据处理部分,将标签改为int64(飞桨框架默认将标签处理成int64)
- 网络结构部分,输出层改为self.fc = Linear(input_dim=980, output_dim=1, act=‘softmax’)
- 训练过程,损失函数设置loss = fluid.layers.cross_entropy(predict, label)
训练配置:优化器,资源配置
- 优化器:找到一个合适的学习率以使训练效果最佳学习率是优化器的一个参数,调整学习率看似是一件非常麻烦的事情,需要不断的调整步长,观察训练时间和Loss的变化。经过研究员的不断的实验,当前已经形成了四种比较成熟的优化算法:SGD、Momentum、AdaGrad和Adam,每种优化算法均有更多的参数设置,详情可查阅飞桨的官方API文档
- 资源配置:多级多卡训练,分布式训练(模型并行,数据并行)
训练过程:可视化分析,校验等
- 过拟合: 情况1:训练数据存在噪音,导致模型学到了噪音,而不是真实规律。 情况2:使用强大模型(表示空间大)的同时训练数据太少,导致在训练数据上表现良好的候选假设太多,锁定了一个“虚假正确”的假设。对于情况1,我们使用数据清洗和修正来解决。 对于情况2,我们或者限制模型表示能力,或者收集更多的训练数据。而清洗训练数据中的错误,或收集更多的训练数据往往是一句“正确的废话”,在任何时候我们都想获得更多更高质量的数据。在实际项目中,更快、更低成本可控制过拟合的方法,只有限制模型的表示能力。
模型测试 模型测试需要注意的方放就是,测试的数据同样要转化为飞桨tensor变量 传入模型才能正常进行工作,并且转化为飞桨tensor变量之前,要先将数据转化为网络期待接受的格式。
# 读取一张本地的样例图片,转变成模型输入的格式
def load_image(img_path):
# 从img_path中读取图像,并转为灰度图
im = Image.open(img_path).convert('L')
im.show()
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
# 定义预测过程
with fluid.dygraph.guard():
model = MNIST()
params_file_path = 'mnist'
img_path = './work/example_0.jpg'
# 加载模型参数
model_dict, _ = fluid.load_dygraph("mnist")
model.load_dict(model_dict)
model.eval()
tensor_img = load_image(img_path)
#模型反馈10个分类标签的对应概率
results = model(fluid.dygraph.to_variable(tensor_img))
#取概率最大的标签作为预测输出
lab = np.argsort(results.numpy())
print("本次预测的数字是: ", lab[0][-1])
刚刚接触深度学习与paddle框架,还有很多不懂的地方,再接再厉!