前言
本次比赛赛题是进行人流密度的估计, 因为之前看过很多人体姿态估计和目标检测的论文, 隐约感觉到可以用到这次比赛上来, 所以趁着现在时间比较多, 赶紧报名参加了一下比赛, 比赛规定用 paddlepaddle 来进行开发, 所以最近几天先学习一下 paddlepaddle 的相关流程, 在这里记录一下, 也好让自己真正的能够学到东西.
流程前瞻
在我看来, 设计一个深度学习网络(主要是基于 CNN 的, 其他的没怎么接触), 主要有以下几方面:
数据的读取(这里主要是图片数据和它的 "标签").
数据的预处理(包含数据集增强和你需要的操作).
如何将你的数据送入网络.
网络结构的设计(layer 的使用).
损失函数的计算(这部分是根据你的任务决定的).
优化器的选择 (我想一般是 adam 吧) 这里 https://blog.csdn.net/u010089444/article/details/76725843 有一篇博客分享了各种优化算法的不同.
模型的存储以及加载(加载这部分其实挺重要的, 之前看过一篇论文说应用 ImageNet 上预训练的模型进行迁移学习, 往往能在新的任务上取得更好的效果, 当然也不是绝对的).
如何进行测试(可以说就是如何进行前向传播).
可选: 可视化.
接下来就以上几部分进行学习, 在次非常感谢 Charlotte77 和夜雨飘零 1 https://my.csdn.net/qq_33200967 . 他们的博文给予了我莫大的帮助, 向大佬叩首.
一, 数据的读取
对于本次比赛来说, 我的数据是图片(各种监控的图片, 大小不同), 标注是 json 格式的文件, 所以接下来要讨论一下在 paddlepaddle 中如何以图片为输入.
参见大佬 Charlotte77 的博文, paddlepaddle 主要是通过 reader 来进行数据的输入, 这里我参考了 paddlepaddle github 上的 SSD 的例子 https://github.com/PaddlePaddle/models/tree/develop/fluid/object_detection 的例子, 先看他们的代码:
- 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 为例, 看一下, 到底返回了什么, 这里 https://github.com/PaddlePaddle/models/blob/develop/fluid/object_detection/reader.py 代码有点长, 我们主要看返回的是什么:
- ...
- 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 输入数据的生成:
把你的数据 (图片, 标签) 搞出来, 然后用 yield 来产生一个生成器: reader.
将此 reader 生成 batch, 也就是 train_reader = paddle.batch(reader, batch_size=batch_size)这样子.
接下来就是送入网络了.
二, 如何将数据送入网络
上面第三步就是将数据送入网络, 这是如何办到的呢, 用过 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说明这条数据是序列格式, 那为啥我们的图片不是序列格式? 这里图片的 size 第一维应该就是 batch_size, 我们注意到后面的 gt_box 和 gt_label 没有制定第一维, 他们就是一个向量或者整数, 为啥不像 tensorflow 里面直接就指定第一维为 batch_size? 因为 paddlepaddle 输入数据格式里面没有 QAQ! 具体查看大佬 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 的前提下):
定义一个 "占位符", 也就是 fluid.layers.data.
定义一个 feeder(fluid.DataFeeder), 来指定设备和 feed 顺序.
运用执行器 (这个后面再说) 的 run, 指定你需要运行的 operation, 然后 feed 数据.
看到这里, 我总是感觉 paddlepaddle 的 fluid 和 tensorflow 很像, 先定义图模型, 然后运行, 但是看到官方说 fluid 是和 TensorFlow Eager Execution 很像, 据我了解 (没有用过, 所以有可能是错误的, 望批评指正)TensorFlow Eager Execution 是针对之前 tensoflow 不能实时出结果(必须 sess.run) 来设计的, 但是现在看好像不是很像, 以后看懂了再来解释. 留坑.
三, 网络结构的设计(包含损失函数和优化器)
这部分我们直接看代码吧, 在 SSD 的例子中:
locs, confs, box, box_var = mobile_net(num_classes, image, image_shape)
调用了 mobile_net 模型 https://github.com/PaddlePaddle/models/blob/develop/fluid/object_detection/mobilenet_ssd.py , 这个有兴趣自己看吧, 主要是 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 https://github.com/PaddlePaddle/book/blob/high-level-api-branch/03.image_classification/train.py ):
- 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 https://blog.csdn.net/qq_33200967/article/details/79126897 的经验, 是因为新版本 Fluid 的原因, 现在大部分都是用 executor 来进行编写的. 所以以后也不用烦恼了, 这里吐槽一下官方文档, 感觉维护人员要少吃一个鸡腿, 不同版本变化太大, 然而官方只给最新的示例, 但是对于之前的代码并没有进行版本的说明, 导致我们学习起来有点混乱, 希望能够重新写一下 book.
四, 模型的存储和加载
这部分官方文档 http://paddlepaddle.org/docs/0.14.0/documentation/fluid/zh/new_docs/user_guides/howto/training/save_load_variables.html 资料挺全的, 当然对于大家比较关心的如何加载 ImageNet 预训练模型, 也是有的, 这里 https://github.com/PaddlePaddle/models/tree/develop/image_classification 有例子, 但是说实话这里有点问题, 大佬在这里 https://github.com/PaddlePaddle/Paddle/issues/9274 也做了讨论, 本来想参考官方文档进行 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
来源: https://www.cnblogs.com/bobxxxl/p/9320751.html