前言
本篇文章以手写数字识别为例,快速上手paddle。
文章内容包含:
- 从paddle中加载mnist数据集
- 用class快速搭建模型
- 选择优化器,加入正则化项
- 训练过程中打印训练集的损失,验证集的损失及准确率,early_stop
- 模型的保存与加载
- 将训练损失和测试损失作图
文中API的使用可以查询官网:https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/index_cn.html
导入模块
import paddle
from paddle import fluid
import numpy as np
import matplotlib.pyplot as plt
MNIST数据集加载
#从paddle中加载数据集
train_set = paddle.dataset.mnist.train() #返回训练数据的reader creator(generator)
test_set = paddle.dataset.mnist.test() #测试用数据
#通过下面几行代码可以看到训练集中有60000条数据
#测试集中有10000条数据
#data 为一个tuple,data[0]为一张被reshape成了(28*28,)维的图片,data[1]为其对应的标签
#data[0]的值在[-1, 1],是经过归一化处理后的
i = 0
for data in test_set():
i += 1
print(i)
print(type(data))
print(type(data[0]))
print(data[0].shape)
#训练样本乱序、生成批次数据
train_set = fluid.io.shuffle(train_set, 60000)
train_reader = fluid.io.batch(train_set, 100)
#train_reader() 为生成器
for data in train_reader():
print(data[0]) #请自行检查data
break
print(type(data))
print(len(data))
定义模型结构
用定义类的方式定义模型
这里先随便定义个卷积网络
此部分代码中用到的API可从这里查询:
https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/dygraph_cn.html
class MNIST(fluid.dygraph.Layer):
def __init__(self):
super(MNIST, self).__init__()
#定义一个卷积层
self.conv1 = fluid.dygraph.Conv2D(num_channels=1, num_filters=8, filter_size=3, stride=1, padding=1)
#再定义一个卷积层
self.conv2 = fluid.dygraph.Conv2D(num_channels=8, num_filters=16, filter_size=3, stride=1, padding=1)
#定义个池化层
self.pool = fluid.dygraph.Pool2D(pool_size=2, pool_stride=2, pool_type='max')
#定义两个全连接层
self.fc1 = fluid.dygraph.Linear(input_dim=784, output_dim=100, act='relu')
self.fc2 = fluid.dygraph.Linear(input_dim=100, output_dim=10)
# 定义网络的前向计算过程
def forward(self, inputs, label=None):
#将输入的shape=(batch_size, 28*28)reshape成正确的形状
x = fluid.layers.reshape(inputs, [-1, 1, 28, 28]) #paddle 中为 channel_first
x = self.conv1(x)
#在卷积之后加入BN层,之后接relu激活函数
x = fluid.dygraph.BatchNorm(8, act='relu')(x)
x = self.pool(x)
x = self.conv2(x)
x = fluid.dygraph.BatchNorm(16, act='relu')(x)
x = self.pool(x)
x = fluid.layers.reshape(x, [-1, 784])
x = self.fc1(x)
x = fluid.dygraph.Dropout(p=0.5)(x)
x = self.fc2(x) #这里没有加入softmax
return x
开始训练
NUM_EPOCH = 50 #训练总次数
batch_sz = 100 #批次大小
init_lr = 0.001 #初始学习率
patience = 3 #early——stop
use_gpu = True #是否使用GPU
params_path = "./model_data/mnist" #定义保存路径
place = fluid.CPUPlace() if not use_gpu else fluid.CUDAPlace(0)
with fluid.dygraph.guard(place=place):
# params_dict, opt_dict = fluid.load_dygraph(params_path)
model = MNIST()
# model.load_dict(params_dict)
#尝试不同的优化器。 前边的数字是在测试集上的最好的准确率,后面是epoch
#同一个优化器每次训练在测试集上的表现略有不同
opt = fluid.optimizer.SGDOptimizer(learning_rate=init_lr,
parameter_list=model.parameters()) #0.929 34
# opt = fluid.optimizer.MomentumOptimizer(learning_rate=init_lr, momentum=0.9,
# parameter_list=model.parameters()) #0.974 23
# opt = fluid.optimizer.AdagradOptimizer(learning_rate=init_lr,
# parameter_list=model.parameters()) #0.953 38
# opt = fluid.optimizer.RMSPropOptimizer(learning_rate=init_lr,
# parameter_list=model.parameters()) #0.983 10
# opt = fluid.optimizer.AdamOptimizer(learning_rate=init_lr,
# parameter_list=model.parameters()) #0.982 9
#正则化
#飞桨支持为所有参数加上统一的正则化项,也支持为特定的参数添加正则化项。前者的实现如下代码所示,
#仅在优化器中设置regularization参数即可实现。使用参数regularization_coeff调节正则化项的权重,
#权重越大时,对模型复杂度的惩罚越高。
opt = fluid.optimizer.AdamOptimizer(learning_rate=init_lr,
#regularization=fluid.regularizer.L2Decay(regularization_coeff=0.1),
parameter_list=model.parameters())
#total_steps = (int(60000//BATCH_SIZE) + 1) * EPOCH_NUM
#lr = fluid.dygraph.PolynomialDecay(0.01, total_steps, 0.001) 学习率多项式衰减
#可将init_lr 替换为lr从而加入 学习率多项式衰减
# optimizer.set_dict(opt_dict)
train_loss = [] #将训练集每个batch的loss放在这里
test_loss = [] #将测试的的loss放在这里
test_acc = [] #测试集上的准去率
best_acc = 0 #early_stop用到的
for epoch in range(1, NUM_EPOCH+1):
model.train() # 训练模式 和 测试模式 中模型部分算子的表现不相同
train_set = fluid.io.shuffle(train_set, 60000) #随机打乱数据
for batch_id, batch in enumerate(fluid.io.batch(train_set, batch_size=batch_sz)()):
image_data = np.array([x[0] for x in batch], dtype='float32')
label_data = np.array([x[1] for x in batch], dtype='int').reshape(len(image_data), 1)
# 转换成paddle需要的格式
image = fluid.dygraph.to_variable(image_data)
label = fluid.dygraph.to_variable(label_data)
preds = model(image) #执行一次模型的前向传播
loss = fluid.layers.softmax_with_cross_entropy(preds, label) #softmax --> 计算损失
avg_loss = fluid.layers.mean(loss)
#每过一定次数将结果打印出来
if batch_id % 100 == 0:
#f-string 格式化字符串以 f 开头,后面跟着字符串,字符串中的表达式用大括号 {} 包起来,它会将变量或表达式计算后的值替换进去
print(f'epoch: {epoch:4d} | batch_id = {batch_id:4d} | batch_loss = {avg_loss.numpy()[0]:4.3f}')
train_loss.append(avg_loss.numpy()[0])
# 反向传播,优化参数
avg_loss.backward()
opt.minimize(avg_loss)
model.clear_gradients()
model.eval() #测试模式
acc = []
avg_loss = []
for batch_id, batch in enumerate(fluid.io.batch(test_set, batch_size=batch_sz)()):
image_data = np.array([x[0] for x in batch], dtype='float32')
label_data = np.array([x[1] for x in batch], dtype='int').reshape(len(image_data), 1)
image = fluid.dygraph.to_variable(image_data)
label = fluid.dygraph.to_variable(label_data)
preds = model(image)
loss = fluid.layers.softmax_with_cross_entropy(preds, label)
avg_loss.append(fluid.layers.mean(loss).numpy()) #所有batch存起来求平均
acc.append(fluid.layers.accuracy(preds, label).numpy()) #所有batch存起来求平均
#打印出测试集的loss 和 acc
print(f'test_data loss: {np.array(avg_loss).mean()}, test_data acc: {np.array(acc).mean()}')
test_acc.append(np.array(acc).mean())
test_loss.append(np.array(avg_loss).mean())
# 设定early_stop 当连续patience次best_acc没有更新则停止循环
if best_acc <= test_acc[-1]:
best_acc = test_acc[-1]
early_stop = 0
#如果test——acc变好则保存模型
# 保存模型参数和优化器的参数
fluid.save_dygraph(model.state_dict(), './model_data/mnist_epoch{}'.format(epoch))
fluid.save_dygraph(opt.state_dict(), './model_data/mnist_epoch{}'.format(epoch))
else:
early_stop += 1
if early_stop == patience:
print(f'early_stop at epoch = {epoch}')
break
print()
print('Done!')
#将训练集和测试集的损失画出来
train_x = np.arange(1, len(train_loss)+1)
test_x = np.arange(6, len(train_loss)+1, 6)
plt.plot(train_x, train_loss, 'red')
plt.plot(test_x, test_loss, 'blue')
总结
paddle中dataset读出来的是generator,在数据量非常大的时候可以显著节省内存。
本文将paddle的多篇文章总结为一篇,方便日后的查阅。
感谢阅读!