PaddlePaddle中的Fuild是0.11提出的,Fluid是设计用来让用户像Pytorch和Tensorflow一样执行程序。在这些系统中,不再有模型这个概念,应用也不再包含一个用于描述Operator图或者一系列层的符号描述,而是像通用程序那样描述训练或者预测的过程。而Fluid与Pytorch或tensorflow的区别在于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时,需要先get一个trainer,然后开始trainer,利用trainer.train()
使用Fluid版本进行训练模型。
1:训练模型
定义神经网络
使用VGG16神经网络模型,这个模型之前实现过Cifar彩色图像识别,这里仍然使用cifar10,通过对比paddle1和fluid版本VGG16的定义,观察他们的不同。
通过对比这两个网络的定义发现,img_conv_group的接口位置已经变了,Fluid的相关接口都在fluid下,而原来都是在paddle下,同时改变最大的是Fluid取消了num_channels图像的通道数。
在Fluid版本中使用激活函数不再是调用一个函数,而是传入一个字符串,比如在BN层指定一个Relu激活函数act='relu',在paddle1中act=paddle.activation.Relu()
paddle1的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
通过上面获取的全连接,可以生成一个分类器
#定义图像的类别数
class_dim=10
#获取神经网络的分类器
predict=vgg16_bn_drop(image,class_dim)
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
定义数据
在数据定义方式上,Fluid和之前的paddle 1定义方式有很大的差异,比如不再是根据图像的大小定义,而是传图像的形状,包括通道数,同时制定数据的类型。
Fluid版本的定义方式:
# 定义图像的通道数和大小
image_shape = [3, 32, 32]
# 定义输入数据大小,指定图像的形状,数据类型是浮点型
image = fluid.layers.data(name='image', shape=image_shape, dtype='float32')
# 定义标签,类型是整型
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
Paddle版本的定义方式
# 获取输入数据模式
image = paddle.layer.data(name="image",
type=paddle.data_type.dense_vector(datadim))
# 获得图片对于的信息标签
label = paddle.layer.data(name="label",
type=paddle.data_type.integer_value(type_size))
定义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相关的放在一起,以下是两个优化方法的定义
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了,paddle1用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())
如果要指定Gpu个数和编号的话,可以在终端输入以下命令:
export CUDA_VISIBLE_DEVICES=0,1
使用上面的方法如果是换一个终端,就没有上面的效果了,如果想设计持久化,要在~/.bashrc最后加上如下代码:
cudaid=${cudaid_num:=0,1}
export CUDA_VISIBLE_DEVICES=$cudaid
在paddle中通过使用paddle.init(use_gpu= ,trainer_counter= )
获取数据
在读取数据成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)
保存预测模型
在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):
加载训练好的模型
加载模型跟之前paddle1 有很大差距,Paddle 1的是parameters=paddle.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)