特别注明:本文内容包括但不限于代码,图片均来自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框架,还有很多不懂的地方,再接再厉!