文章目录


前言

​PaddlePaddle​​​的​​Fluid​​​是​​0.11.0​​​提出的,​​Fluid​​​是设计用来让用户像​​Pytorch​​​和​​Tensorflow Eager Execution​​​一样执行程序。在这些系统中,不再有模型这个概念,应用也不再包含一个用于描述​​Operator​​​图或者一系列层的符号描述,而是像通用程序那样描述训练或者预测的过程。而​​Fluid​​​与​​PyTorch​​​或​​Eager Execution​​​的区别在于​​Fluid​​​不依赖​​Python​​​提供的控制流,例如 ​​if-else-then​​​或者​​for​​​,而是提供了基于​​C++​​​实现的控制流并暴露了对应的用​​with​​​语法实现的​​Python​​接口。例如我们会在例子中使用到的代码片段:

with fluid.program_guard(inference_program):
test_accuracy = fluid.evaluator.Accuracy(input=out, label=label)
test_target = [avg_cost] + test_accuracy.metrics + test_accuracy.states
inference_program = fluid.io.get_inference_program(test_target)

在​​Fluid​​​版本中,不再使用​​trainer​​​来训练和测试模型了,而是使用了一个​​C++​​​类​​Executor​​​用于运行一个​​Fluid​​​程序,​​Executor​​​类似一个解释器,​​Fluid​​将会使用这样一个解析器来训练和测试模型,如:

loss, acc = exe.run(fluid.default_main_program(),
feed=feeder.feed(data),
fetch_list=[avg_cost] + accuracy.metrics)

对于这个​​Fluid​​​版本,我们在此之前都没有使用过,那么接下来就让我们去使用​​Fluid​​​版本,同时对比一下之前所写的,探讨​​Fluid​​版本的改变。

定义神经网络

我们这次使用的是比较熟悉的​​VGG16​​​神经模型,这个模型在之前的​​CIFAR​​​彩色图像识别,为了方便比较,我们也是使用​​CIFAR10​​​数据集,以下代码就是​​Paddle 1​​​和​​Fluid​​​版本的​​VGG16​​​的定义,把它们都拿出来对比,看看​​Fluid​​版本的改动。

通过对比这个两神经网络的定义可以看到​​img_conv_group​​​的接口位置已经不一样了,​​Fluid​​​的相关接口都在​​fluid​​​下了。同时我们看到改变最大的是​​Fluid​​​取消了​​num_channels​​图像的通道数。

在​​Fluid​​​版本中使用的激活函数不再是调用一个函数了,而是传入一个字符串,比如在​​BN​​​层指定一个​​Relu​​​激活函数​​act='relu'​​​,在​​Paddle 1​​​版本中是这样的:​​act = paddle.activation.Relu()​

Paddle 1的VGG16

def vgg_bn_drop(input,class_dim):
# 定义卷积块
def conv_block(ipt, num_filter, groups, dropouts, num_channels=None):
return paddle.networks.img_conv_group(
input=ipt,
num_channels=num_channels,
pool_size=2,
pool_stride=2,
conv_num_filter=[num_filter] * groups,
conv_filter_size=3,
conv_act=paddle.activation.Relu(),
conv_with_batchnorm=True,
conv_batchnorm_drop_rate=dropouts,
pool_type=paddle.pooling.Max())
# 定义一个VGG16的卷积组
conv1 = conv_block(input, 64, 2, [0.3, 0], 3)
conv2 = conv_block(conv1, 128, 2, [0.4, 0])
conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])
conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])
conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])
# 定义第一个drop层
drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5)
# 定义第一层全连接层
fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear())
# 定义BN层
bn = paddle.layer.batch_norm(input=fc1,
act=paddle.activation.Relu(),
layer_attr=paddle.attr.Extra(drop_rate=0.5))
# 定义第二层全连接层
fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear())
# 获取全连接输出,获得分类器
predict = paddle.layer.fc(input=fc2,
size=class_dim,
act=paddle.activation.Softmax())
return predict

Fluid版本的VGG16

def vgg16_bn_drop(input):
# 定义卷积块
def conv_block(input, num_filter, groups, dropouts):
return fluid.nets.img_conv_group(
input=input,
pool_size=2,
pool_stride=2,
conv_num_filter=[num_filter] * groups,
conv_filter_size=3,
conv_act='relu',
conv_with_batchnorm=True,
conv_batchnorm_drop_rate=dropouts,
pool_type='max')
# 定义一个VGG16的卷积组
conv1 = conv_block(input, 64, 2, [0.3, 0])
conv2 = conv_block(conv1, 128, 2, [0.4, 0])
conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])
conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])
conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])
# 定义第一个drop层
drop = fluid.layers.dropout(x=conv5, dropout_prob=0.5)
# 定义第一层全连接层
fc1 = fluid.layers.fc(input=drop, size=512, act=None)
# 定义BN层
bn = fluid.layers.batch_norm(input=fc1, act='relu')
# 定义第二层全连接层
drop2 = fluid.layers.dropout(x=bn, dropout_prob=0.5)
# 定义第二层全连接层
fc2 = fluid.layers.fc(input=drop2, size=512, act=None)
# 获取全连接输出,获得分类器
predict = fluid.layers.fc(
input=fc2,
size=class_dim,
act='softmax',
param_attr=ParamAttr(name="param1", initializer=NormalInitializer()))
return predict

通过上面获取的全连接,可以生成一个分类器

# 定义图像的类别数量
class_dim = 10
# 获取神经网络的分类器
predict = vgg16_bn_drop(image,class_dim)

定义batch平均错误

在​​Fluid​​​版本中,多了一个​​batch_acc​​的程序,这个是在训练过程或者是测试中计算平均错误率的。这个需要定义在优化方法之前。

# 每个batch计算的时候能取到当前batch里面样本的个数,从而来求平均的准确率
batch_size = fluid.layers.create_tensor(dtype='int64')
print(batch_size)
batch_acc = fluid.layers.accuracy(input=predict, label=label, total=batch_size)

定义测试程序

这个一个定义预测的一个程序,这个是在主程序中获取的一个程序,专门用来做测试的,这个定义要放在定义方法之前,因为测试程序是训练程序的前半部分(不包括优化器和​​backward​​),所以要定义在优化方法之前。

# 测试程序
inference_program = fluid.default_main_program().clone(for_test=True)

定义优化方法

在优化方法的定义上也有很大的不同,​​Fluid​​把​​learning_rate​​相关的都放在一起了,以下是两个优化方法的定义,这不是本章项目使用到的​​optimizer​​,本章使用的​​optimizer​​比较简单,差别不大。

Fluid版本的定义优化方法

optimizer = fluid.optimizer.Momentum(
learning_rate=fluid.layers.exponential_decay(
learning_rate=learning_rate,
decay_steps=40000,
decay_rate=0.1,
staircase=True),
momentum=0.9,
regularization=fluid.regularizer.L2Decay(0.0005), )
opts = optimizer.minimize(loss)

Paddle 1版本的定义优化方法

momentum_optimizer = paddle.optimizer.Momentum(
momentum=0.9,
regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),
learning_rate=0.1 / 128.0,
learning_rate_decay_a=0.1,
learning_rate_decay_b=50000 * 100,
learning_rate_schedule='discexp')

定义调试器

在前言有讲到,在​​Fluid​​​版本中,不会在有​​trainer​​​了,​​Paddle 1​​​用 ​​trainer.train(...)​​​,​​Fluid​​​用​​fluid.Executor(place).Run(...)​​​,所以在​​Fluid​​起关键作用的是调试器。

# 是否使用GPU
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 创建调试器
exe = fluid.Executor(place)
# 初始化调试器
exe.run(fluid.default_startup_program())

获取数据

在读取数据成​​reader​​​上没有什么区别,这要说的是​​feeder​​​,这里定义的更之前的​​feeding = {"image": 0, "label": 1}​​差距有点大了。不过这样看起了更加明了。

# 获取训练数据
train_reader = paddle.batch(
paddle.dataset.cifar.train10(), batch_size=BATCH_SIZE)
# 获取测试数据
test_reader = paddle.batch(
paddle.dataset.cifar.test10(), batch_size=BATCH_SIZE)

# 指定数据和label的对于关系
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

开始训练和测试

在这里就有很大的不一样了,在​​Paddle 1​​​中,使用的是​​trainer​​​,通过​​num_passes​​​来指定训练的​​Pass​​​,而​​Fluid​​​的是使用一个循环来处理的,这样就大大方便了我们在训练过程中所做的一些操作了,而在此之前是使用一个​​event​​训练时间的,虽然也可以做到一些操作,不过相对循环来说,笔者还是觉得循环用起来比较方便。

accuracy = fluid.average.WeightedAverage()
test_accuracy = fluid.average.WeightedAverage()
# 开始训练,使用循环的方式来指定训多少个Pass
for pass_id in range(num_passes):
# 从训练数据中按照一个个batch来读取数据
accuracy.reset()
for batch_id, data in enumerate(train_reader()):
loss, acc, weight = exe.run(fluid.default_main_program(),
feed=feeder.feed(data),
fetch_list=[avg_cost, batch_acc, batch_size])
accuracy.add(value=acc, weight=weight)
print("Pass {0}, batch {1}, loss {2}, acc {3}".format(
pass_id, batch_id, loss[0], acc[0]))

# 测试模型
test_accuracy.reset()
for data in test_reader():
loss, acc, weight = exe.run(inference_program,
feed=feeder.feed(data),
fetch_list=[avg_cost, batch_acc, batch_size])
test_accuracy.add(value=acc, weight=weight)


# 输出相关日志
pass_acc = accuracy.eval()
test_pass_acc = test_accuracy.eval()
print("End pass {0}, train_acc {1}, test_acc {2}".format(
pass_id, pass_acc, test_pass_acc))

# 每一个Pass就保存一次模型
# 指定保存模型的路径
model_path = os.path.join(model_save_dir, str(pass_id))
# 如果保存路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print('save models to %s' % (model_path))
# 保存模型
fluid.io.save_inference_model(model_path, ['image'], [predict], exe)

在训练过程中,会输出类型下的日志信息:

Pass 0, batch 0, loss 16.5825138092, acc 0.09375
Pass 0, batch 1, loss 15.7055978775, acc 0.1484375
Pass 0, batch 2, loss 15.8206882477, acc 0.0546875
Pass 0, batch 3, loss 14.6004362106, acc 0.1953125
Pass 0, batch 4, loss 14.9484052658, acc 0.1171875
Pass 0, batch 5, loss 13.0915336609, acc 0.078125

保存模型

在​​Fluid​​版本中,保存模型虽然复杂一点点,但是对于之后的预测是极大的方便了,因为在预测中,不需要再定义神经网络模型了,可以直接使用保存好的模型进行预测。还有要说一下的是,这个保存模型的格式跟之前的不一样,这个保存模型是不会压缩的。

Fluid版本的保存模型

# 指定保存模型的路径
model_path = os.path.join(model_save_dir, str(pass_id))
# 如果保存路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print('save models to %s' % (model_path))
# 保存预测模型
fluid.io.save_inference_model(model_path, ['image'], [net], exe)

Paddle 1的保存模型

with open(save_parameters_name, 'w') as f:
trainer.save_parameter_to_tar(f)

预测模型

获取调试器

在预测中,以前的​​Paddle 1​​是要使用到预测器​​infer​​的,而在​​Fluid​​中还是使用调试器,定义调试器如下

# 是否使用GPU
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 生成调试器
exe = fluid.Executor(place)

在预测中,所有的预测都要在这个控制流中执行

inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):

加载训练好的模型

加载模型,在这里,加载模型跟之前的差距也很大,在​​Paddle 1​​的是​​parameters = paddle.parameters.Parameters.from_tar(f)​​,因为之前使用的是参数,而在​​Fluid​​没有使用到参数这个概念。

# 加载模型
[inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(save_dirname, exe)

获取预测结果

获取预测数据

# 获取预测数据
img = Image.open(image_file)
img = img.resize((32, 32), Image.ANTIALIAS)
test_data = np.array(img).astype("float32")
test_data = np.transpose(test_data, (2, 0, 1))
test_data = test_data[np.newaxis, :] / 255

开始预测并打印结果

# 开始预测
results = exe.run(inference_program,
feed={feed_target_names[0]: test_data},
fetch_list=fetch_targets)

results = np.argsort(-results[0])
# 打印预测结果
print("The images/horse4.png infer results label is: ", results[0][0])

调用预测函数

if __name__ == '__main__':
image_file = '../images/horse4.png'
model_path = '../models/0/'
infer(image_file, False, model_path)

输出结果如下:

The images/horse4.png infer results label is:  7

项目代码

GitHub地址:https://github.com/yeyupiaoling/LearnPaddle

参考资料

​​

​​

​​