2018百度之星开发者大赛-paddlepaddle学习

前言

本次比赛赛题是进行人流密度的估计,因为之前看过很多人体姿态估计和目标检测的论文,隐约感觉到可以用到这次比赛上来,所以趁着现在时间比较多,赶紧报名参加了一下比赛,比赛规定用paddlepaddle来进行开发,所以最近几天先学习一下paddlepaddle的相关流程,在这里记录一下,也好让自己真正的能够学到东西。

流程前瞻

在我看来,设计一个深度学习网络(主要是基于CNN的,其他的没怎么接触),主要有以下几方面:

  1. 数据的读取(这里主要是图片数据和它的“标签”)。
  2. 数据的预处理(包含数据集增强和你需要的操作)。
  3. 如何将你的数据送入网络。
  4. 网络结构的设计(layer的使用)。
  5. 损失函数的计算(这部分是根据你的任务决定的)。
  6. 优化器的选择(我想一般是adam吧)这里有一篇博客分享了各种优化算法的不同。
  7. 模型的存储以及加载(加载这部分其实挺重要的,之前看过一篇论文说应用ImageNet上预训练的模型进行迁移学习,往往能在新的任务上取得更好的效果,当然也不是绝对的)。
  8. 如何进行测试(可以说就是如何进行前向传播)。
  9. 可选:可视化。

接下来就以上几部分进行学习,在次非常感谢Charlotte77夜雨飘零1。他们的博文给予了我莫大的帮助,向大佬叩首。

一、数据的读取

对于本次比赛来说,我的数据是图片(各种监控的图片,大小不同),标注是json格式的文件,所以接下来要讨论一下在paddlepaddle中如何以图片为输入。

参见大佬Charlotte77的博文,paddlepaddle主要是通过reader来进行数据的输入,这里我参考了paddlepaddle github 上的SSD的例子的例子,先看他们的代码:

train_reader = paddle.batch(
    reader.train(data_args, train_file_list), batch_size=batch_size)
test_reader = paddle.batch(
    reader.test(data_args, val_file_list), batch_size=batch_size)

其中reader是import来的,我们以reader.train来看一下:

def train(settings, file_list, shuffle=True):
    file_list = os.path.join(settings.data_dir, file_list)
    if 'coco' in settings.dataset:
        train_settings = copy.copy(settings)
        if '2014' in file_list:
            sub_dir = "train2014"
        elif '2017' in file_list:
            sub_dir = "train2017"
        train_settings.data_dir = os.path.join(settings.data_dir, sub_dir)
        return coco(train_settings, file_list, 'train', shuffle)
    else:
        return pascalvoc(settings, file_list, 'train', shuffle)

这里看得出来,是利用了之前定义的coco函数或者pascalvoc函数,就是从不同的数据集读取数据,以coco为例,看一下,到底返回了什么,这里代码有点长,我们主要看返回的是什么:

...
            if 'cocoMAP' in settings.ap_version:
                yield im, boxes, lbls, iscrowd, \
                    [im_id, im_width, im_height]
            else:
                yield im, boxes, lbls, iscrowd

    return reader

balabala一大堆,终于发现,返回的是一个生成器reader,可见,主要就在于生成这个生成器,下面来总结一下padlepaddle输入数据的生成:

  1. 把你的数据(图片,标签)搞出来,然后用yield来产生一个生成器:reader。

  2. 将此reader生成batch,也就是train_reader = paddle.batch(reader, batch_size=batch_size)这样子。

  3. 接下来就是送入网络了。

二、如何将数据送入网络

上面第三步就是将数据送入网络,这是如何办到的呢,用过tensorflow的童鞋们可能知道,我们可以用一个palceholder(占位符)来链接我们的原始数据和我们的网络,在这里,也是同样的方法:

image = fluid.layers.data(name='image', shape=image_shape, dtype='float32')
gt_box = fluid.layers.data(name='gt_box', shape=[4], dtype='float32', lod_level=1)
gt_label = fluid.layers.data(name='gt_label', shape=[1], dtype='int32', lod_level=1)

用的是fluid.layers.data,等一下,lod_level是啥,这里paddlepaddle有个序列输入格式,这里lod_level为1说明这条数据是序列格式,但是这里感觉应该不用加,这部分等我做过再说把。具体查看大佬Charlotte77的博文吧。

有了这个“占位符”之后,只需将我们之前的那个batch_size的train_reader feed进去就好了,具体如下:

feeder = fluid.DataFeeder(place=place, feed_list=[image, gt_box, gt_label])
if args.parallel:
    loss_v, = train_exe.run(fetch_list=[loss.name],feed=feeder.feed(data))#这里的data就是之前train_reader的数据,fetch_list就是要执行的operation的名称,feed的顺序就是上面feed_list指定的
else:
    loss_v, = exe.run(fluid.default_main_program(),
                      feed=feeder.feed(data),
                      fetch_list=[loss])
#train_exe和exe是之前定义的,类似与tensorflow的session(个人感觉,实际上还是不一样的)如下:
#exe = fluid.Executor(place)
#train_exe = fluid.ParallelExecutor(use_cuda=args.use_gpu, loss_name=loss.name)
#其中place为指定设备(CPU GPU)

好了,总结一下,如何将数据送入网络(在有了reader的前提下):

  1. 定义一个“占位符”,也就是fluid.layers.data。
  2. 定义一个feeder(fluid.DataFeeder),来指定设备和feed顺序。
  3. 运用执行器(这个后面再说)的run,指定你需要运行的operation,然后feed数据。

看到这里,我总是感觉paddlepaddle的fluid和tensorflow很像,先定义图模型,然后运行,但是看到官方说fluid是和TensorFlow Eager Execution很像,据我了解(没有用过,所以有可能是错误的,望批评指正)TensorFlow Eager Execution是针对之前tensoflow不能实时出结果(必须sess.run)来设计的,但是现在看好像不是很像,以后看懂了再来解释。留坑。
对于如何将数据保存成RecordIO文件并读取,这篇会详细解释。

三、网络结构的设计(包含损失函数和优化器)

这部分我们直接看代码吧,在SSD的例子中:

locs, confs, box, box_var = mobile_net(num_classes, image, image_shape)

调用了mobile_net模型,这个有兴趣自己看吧,主要是fluid.layers中各种层的应用,这个估计各个深度学习框架都差不多,这部分实现的还是挺全的。

loss = fluid.layers.ssd_loss(locs, confs, gt_box, gt_label, box,box_var)
loss = fluid.layers.reduce_sum(loss)
...
optimizer = fluid.optimizer.RMSProp(
    learning_rate=fluid.layers.piecewise_decay(boundaries, values),
    regularization=fluid.regularizer.L2Decay(0.00005), )
optimizer.minimize(loss)
place = fluid.CUDAPlace(0) if args.use_gpu else fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())

我们来看这一部分,定义了loss,然后指定了优化器,然后最小化loss,指定设备,然后启动我们的程序。我感觉这里是个大坑!有没有发现有些文档里面不是这么个流程,而是这样子的(来源paddlepaddle 03.image_classification):

place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
trainer = fluid.Trainer(
    train_func=train_program, optimizer_func=optimizer_program, place=place)
trainer.train(
    reader=train_reader,
    num_epochs=EPOCH_NUM,
    event_handler=event_handler,
    feed_order=['pixel', 'label'])

指定了一个trainer然后调用train。

还有一种

parameters = paddle.parameters.create(cost)
trainer = paddle.trainer.SGD(cost=cost,
                             parameters=parameters,update_equation=momentum_optimizer)

先根据cost(loss)产生要优化的参数,然后指定这些参数进行优化。

这到底用哪一种呢?幸好有大佬夜雨飘零1的经验,是因为新版本Fluid的原因,现在大部分都是用executor来进行编写的。所以以后也不用烦恼了,这里吐槽一下官方文档,感觉维护人员要少吃一个鸡腿,不同版本变化太大,然而官方只给最新的示例,但是对于之前的代码并没有进行版本的说明,导致我们学习起来有点混乱,希望能够重新写一下book。

四、模型的存储和加载

这部分官方文档资料挺全的,当然对于大家比较关心的如何加载ImageNet 预训练模型,也是有的,这里有例子,但是说实话这里有点问题,大佬在这里也做了讨论,本来想参考官方文档进行resnet的加载,但是一方面官方脚本执行时连接不上,再看模型加载会出现各种问题,所以暂时放弃了这种想法,等一下官方的优化。

这部分的主要代码:

exe = fluid.Executor(fluid.CPUPlace())
param_path = "./my_paddle_model"
prog = fluid.default_main_program()
fluid.io.save_params(executor=exe, dirname=param_path, main_program=None)
...
exe = fluid.Executor(fluid.CPUPlace())
param_path = "./my_paddle_model"
prog = fluid.default_main_program()
fluid.io.load_params(executor=exe, dirname=param_path,
                     main_program=prog)

可见是通过fluid.io来实现的。

五、如何进行测试

这部分应该是paddlepaddle的优势了,一方面我们训练的过程中希望能够进行测试,一方面当我们的模型训练完以后我们也希望能够利用前向传播进行预测。paddlepaddle都有这两方面实现:第一种官方给了很好的示例,这里就不赘述了。对于第二种,paddlepaddle也进行了很好的封装:

inferencer = fluid.Inferencer(
# infer_func=softmax_regression, # uncomment for softmax regression
# infer_func=multilayer_perceptron, # uncomment for MLP
infer_func=convolutional_neural_network,  # uncomment for LeNet5
param_path=params_dirname,
place=place)
results = inferencer.infer({'img': img})

convolutional_neural_network就是你的模型里面生成predict的那个函数,params_dirname是保存参数的路径,可见,用paddlepaddle来进行前向传播十分简单,定义好数据之后,加载参数,然后调用infer就可以预测了。

总结

paddlepaddle还有很好的部署能力,但是局限于我现在用的功能,这部分并没有研究,这篇博客主要是串一下如何用paddlepadle搭建深度学习模型,其中有很多细节没有注意,而且有很多地方也不一定准确,希望各位多批评指正。

参考:

https://blog.csdn.net/u010089444/article/details/76725843

https://www.cnblogs.com/charlotte77/p/7802226.html

http://www.cnblogs.com/charlotte77/p/7906363.html

https://github.com/PaddlePaddle/models/tree/develop/fluid/object_detection

https://github.com/PaddlePaddle/book/blob/high-level-api-branch/03.image_classification/train.py

https://blog.csdn.net/qq_33200967/article/details/79126897

http://paddlepaddle.org/docs/0.14.0/documentation/fluid/zh/new_docs/user_guides/howto/training/save_load_variables.html

posted @ 2018-07-16 22:16  bobxxxl  阅读(2036)  评论(5编辑  收藏  举报