前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实战派 | PaddlePaddle 你其实也可以真正地上手

实战派 | PaddlePaddle 你其实也可以真正地上手

作者头像
用户1107453
发布2018-06-21 16:28:59
6770
发布2018-06-21 16:28:59
举报
文章被收录于专栏:UAI人工智能
视频内容

0. 写在前面:

百度开发的PaddlePaddle 作为一款开源深度学习框架,刚刚问世两年左右,虽然现在使用者数量和普及程度并不及 Caffe, TensorFlow 或者 Pytorch,但是毕竟是国产,这说明我们正在紧跟时代的浪潮,所以很有必要体验并且支持下百度的PaddlePaddle。这也是我写的第一篇深度学习框架体验笔记,如果大家有任何问题,也欢迎并期待大家可以和我多多交流。

百度官网上对这个框架的介绍是:PaddlePaddle (PArallel Distributed Deep Learning)是百度研发的深度学习平台,具有易用,高效,灵活和可伸缩等特点,为百度内部多项产品提供深度学习算法支持。这样一句话看起来也许可以很好的定义但似乎又抽象了一点。因为如何开始是摆在一些从未有过PaddlePaddle经验的工程师面前的问题。

作为一个资深工程师,我本人习惯以代码的角度切入,但我并不打算写一篇事无巨细的代码分析报告,在这里,我更愿意跟大家分享一种方法,我正在用这种方法学习 PaddlePaddle。当然,这种方法也有一定的适用范围,但是对我来说不管学习什么开源软件都是屡试不爽。更多的内容也许你们可以在观看视频的时候获得一些灵感。

在文章中我会显示或隐示地回答下面的一些问题:我们应该以一种什么方式开始学习?如何利用paddlepaddle?我们在学习之前应该具备哪些基本的能力?

本文将以一个具体例子来说明vgg模型和图像识别的流程(这也是PaddlePaddle官方的一个例子),虽然PaddlePaddle官网上也有一些说明,但是在这里我会以一个程序员的角度,从文档和源代码层面对一些比较重要的函数进行说明。

1. 一些习惯及方法提炼

对于上文所述的方法,我先用一个大家比较好懂的语言概括一下,那就是直接用工程师的方法看代码,再上手代码,如果需要学习paddlepaddle,我还同时建议大家可以再多会一两个其他框架,也便于更好的互相理解(这里先假定大家应该也已经有一定的其他框架的经验了,所以我就不详细展开了).

那应该具体怎么看代码,怎么上手代码呢?

首先我建议的是大家可以“粗略”浏览下代码先就好,看一个类的代码也要注意,如果类的命名不怎么好理解,这个时候就别吝啬自己动手记下来,如果有考虑修改,也可以做个标记,以后项目上手了,就直接动刀子,改成符合规范、命名清晰的。再次,对于看代码,需要粗看,就是看方法,看看这个类都干了什么,怎么跟其他的类进行数据交互等等这些。不要钻进去研究每句代码的实现,我们需要的是快速上手,所以,对代码有个整体的认识。

在对代码有了一定程度的了解之后,我们需要在实践中去熟悉代码,一行行的看代码,只会让你感觉疲倦,如果有人工智能项目相关的开发需求和任务,那么我们就可以充分利用起来,一边写代码,然后对关联类加以了解,这个时候,一行行的看代码,就不会疲倦了,因为有目的性,完成之后也有成就感。而代码就是在这样一点点的积累中熟悉起来的。

接下去就是上手了,既然是开源软件,所以我强烈建议大家下载源码并采用源码安装。还有一点就是要养成阅读各种文档的好习惯,比如源码路径下 doc/howto 中的文档,闲来无事的时候可以读一下,里面既有 build 整个工程的方法也有教大家贡献代码的方法(比如,new_op_cn.md 这个文件就如何写新的Operator进行了详细的说明,这对于阅读源代码也会有很大的帮助)。

下面闲话少叙,直奔主题

2. 图像识别入门

本文介绍的图像识别是 PaddlePaddle 官方的一个具体实例(http://www.paddlepaddle.org/docs/develop/book/03.image_classification/index.cn.html)这个例子的是利用 cifar-10 图像训练集对vgg 网络进行训练,整体流程在 train.py 文件里。实际上,这是个很通用的流程,从训练集来说,我们即可以用 cifar-10,也可以用其他训练集,比如ImageNet,甚至是自己标注的训练集;对于网络来说,我们即可以用 vgg16,也可以用ResNet,当然也可以用自己搭建的网络结构,在这个例子中,我们可以随意的使用各种我们想实现的网络和数据集的组合。

2.1 vgg 16网络

我们首先来看其中用到的网络模型 vgg16

图1(引用自http://www.paddlepaddle.org/docs/develop/book/03.image_classification/index.cn.html图六)

vgg 是一个非常简洁的深度神经网络,我们这里主要关注的是上图中的网络结构。

为了有个直观的理解,我们在看paddlepaddle代码之前先浏览下 keras 实现 vgg的代码,之后在对比 paddlepaddle的实现,大家可以下载keras源码查看,以下代码位于keras-master/keras/applications/vgg16.py:

111 # Block 1

112 x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)

113 x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)

114 x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

115

116 # Block 2

117 x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)

118 x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)

119 x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

120

121 # Block 3

122 x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x)

123 x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x)

124 x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x)

125 x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)

126

127 # Block 4

128 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x)

129 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x)

130 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x)

131 x = MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)

132

133 # Block 5

134 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x)

135 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x)

136 x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x)

137 x = MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x)

keras的一大优点就是接口封装的相当简洁,命名规则也是一目了然,从接口名字就可以猜到功能,从参数名字就能知道作用。以上代码就是一个典型的例子,对照图1从Block1 到 Block5的描述和图中的结构完全一致,每个 Block 都是由两到三个卷积层加上一个池化层构成。具体的接口说明在这里不做过多的说明,有兴趣的同学可以参看 keras 相关的文档对接口参数做更详细的了解。

我们下面主要看下paddlepaddle对 vgg 网络的描述,代码在 vgg.py文件。

其中只定义了一个函数vgg_bn_drop,这个函数把整个 vgg16 都封装在 conv_block 函数里面,而 conv_block 本身只用到了一个 paddlepaddle 内部定义的函数 img_conv_group。

21 def conv_block(ipt, num_filter, groups, dropouts, num_channels=None):

22 return paddle.networks.img_conv_group(

23 input=ipt,

24 num_channels=num_channels,

25 pool_size=2,

26 pool_stride=2,

27 conv_num_filter=[num_filter] * groups,

28 conv_filter_size=3,

29 conv_act=paddle.activation.Relu(),

30 conv_with_batchnorm=True,

31 conv_batchnorm_drop_rate=dropouts,

32 pool_type=paddle.pooling.Max())

33

34 conv1 = conv_block(input, 64, 2, [0.3, 0], 3)

35 conv2 = conv_block(conv1, 128, 2, [0.4, 0])

36 conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])

37 conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])

38 conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])

简单的说, vgg.py文件最需要说明的是 img_conv_group,我们对照文档来看下函数的相关参数(文档位置:Documentation →API → Networks → img_conv_group):

Image Convolution Group, Used for vgg net.

Parameters:

· conv_batchnorm_drop_rate (list) – if conv_with_batchnorm[i] is true, conv_batchnorm_drop_rate[i] represents the drop rate of each batch norm.· input (LayerOutput) – input layer.· conv_num_filter (list|tuple) – list of output channels num.· pool_size (int) – pooling filter size.· num_channels (int) – input channels num.· conv_padding (int) – convolution padding size.· conv_filter_size (int) – convolution filter size.· conv_act (BaseActivation) – activation funciton after convolution.· conv_with_batchnorm (list) – if conv_with_batchnorm[i] is true, there is a batch normalization operation after each convolution.· pool_stride (int) – pooling stride size.· pool_type (BasePoolingType) – pooling type.· param_attr (ParameterAttribute) – param attribute of convolution layer, None means default attribute.

Returns:

layer’s output

从 conv1 到 conv5 对应了vgg16 模型的5个 block(图1中颜色相同的层组成一个 block,除去最后三个全连接层一共有5个block)

第一个Block –-→ conv1 = conv_block(input, 64, 2, [0.3, 0], 3)

input 是输入图像;64代表了这一个 block 中的每个卷积层有 64 个 feature map;2 代表了有两个卷积层;[0.3 0] 分别代表每个卷积层 dropout 的概率;3 代表了输入图像的 chanel 个数(RGB或者 BGR,这里如果是第一层的输入要有个这个参数)。

第二个Block –-→ conv2 = conv_block(conv1, 128, 2, [0.4, 0]):

conv1 代表整个 block 的输入,也就是上一个 block的输出;128 代表了这一个 block 中的每个卷积层有 128 个 feature map; 2 代表了有两个卷积层;[0.4 0] 分别代表每个卷积层 dropout 的概率。

以下 conv3 ~ conv5 参数的含义相同,不再赘述。

但是这里有个问题,图中每个 block 都是有两个或三个卷积层加上一个 pooling 层组成的(在 keras 代码里非常清楚), 而 conv_block 中只表明了卷积层的个数以及与之相关的参数,并没有对 pooling 层做任何说明,所以猜测 pooling 层肯定是被封装到了 img_conv_group 里面,为了验证,我们看下 img_conv_group 函数的源代码(Paddle-develop/python/paddle/trainer_config_helpers/networks.py):

336 def img_conv_group(input,

337 conv_num_filter,

338 pool_size,

339 num_channels=None,

340 conv_padding=1,

341 conv_filter_size=3,

342 conv_act=None,

343 conv_with_batchnorm=False,

344 conv_batchnorm_drop_rate=0,

345 pool_stride=1,

346 pool_type=None,

347 param_attr=None):

434 return img_pool_layer(

435 input=tmp, stride=pool_stride, pool_size=pool_size, pool_type=pool_type)

果然, 我们发现img_conv_group函数最后有一层 pooling。这样的检查是有必要的,因为在不确定的情况下,我们需要检查源码以保证封装的正确性,以免后来补救,这样花费的时间成本极大。

再下面就是两个全连接层,中间夹着一个 batch_norm,vgg 就此结束。

40 drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5)

41 fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear())

42 bn = paddle.layer.batch_norm(

43 input=fc1,

44 act=paddle.activation.Relu(),

45 layer_attr=paddle.attr.Extra(drop_rate=0.5))

46 fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear())

47 return fc2

(这里的batch_norm其实并非是一个真正的层,它的作用就是将网络上一层的输出数据转换成正态分布)

这里还是有个问题,图1中vgg最后是三个全连接层,但是代码在这里只定义了两个,读到这里感觉很困惑。

其实在 train.py 文件里 vgg_bn_drop 被调用的下面紧接着还有一个全连接层:

39 net = vgg_bn_drop(image)

40

41 out = paddle.layer.fc(

42 input=net, size=classdim, act=paddle.activation.Softmax())

至此,vgg16 整个网络的所有结构才算全部定义完成。

2.2 classification_cost函数

cost = paddle.layer.classification_cost(input=out, label=lbl)

这里的 classification_cost函数,搜遍整个文档没有找到函数说明,所以只能上源代码(Paddle-develop/python/paddle/trainer_config_helpers/layers.py):

4576 def classification_cost(input,

4577 label,

4578 weight=None,

4579 name=None,

4580 evaluator=classification_error_evaluator,

4581 layer_attr=None,

4582 coeff=1.):

4612 Layer(

4613 name=name,

4614 type="multi-class-cross-entropy",

4615 inputs=ipts,

4616 coeff=coeff,

4617 **ExtraLayerAttribute.to_kwargs(layer_attr))

我想说明的是,这里使用了 Layer,并指明type="multi-class-cross-entropy",即多类交叉熵。

一般来说,像数学模型,比如multi-class-cross-entropy的真正实现都是底层c++(Paddle-develop/paddle/gserver/layers/Layer.cpp):

106 if (type == "multi-class-cross-entropy")

107 return LayerPtr(new MultiClassCrossEntropy(config));

MultiClassCrossEntropy定义(Paddle-develop/paddle/gserver/layers/CostLayer.h):

59 /**

60 * The cross-entropy loss for multi-class classification task.

61 * The loss function is:

62 *

63 * \f[

64 * L = - \sum_{i}{t_{k} * log(P(y=k))}

65 * \f]

66 */

67 class MultiClassCrossEntropy : public CostLayer {

68 public:

69 explicit MultiClassCrossEntropy(const LayerConfig& config)

70 : CostLayer(config) {}

71

72 bool init(const LayerMap& layerMap,

73 const ParameterMap& parameterMap) override;

74

75 void forwardImp(Matrix& output, Argument& label, Matrix& cost) override;

76

77 void backwardImp(Matrix& outputValue,

78 Argument& label,

79 Matrix& outputGrad) override;

80 };

从函数说明的注释看,这个函数就是在实现多类交叉熵,注释中的公式说明这一点,而且这个类分别实现了forward和 backward。并且注意到 Layer.cpp里面每一层都有 forward和 backward的实现。

以上就从根本上解决了 classification_cost没有文档说明的问题。

2.3 Momentum更新方法

51 # Create optimizer

52 momentum_optimizer = paddle.optimizer.Momentum(

53 momentum=0.9,

54 regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),

55 learning_rate=0.1 / 128.0,

56 learning_rate_decay_a=0.1,

57 learning_rate_decay_b=50000 * 100,

58 learning_rate_schedule='discexp')

关于momentum 的算法文档里有数学描述(Documentation →API → Optimizer → Momentum):

这里参数sparse=False,这种是默认情况,momentum的更新公式:

其中 k 是 momentum,lambda 是衰减率,gamma_t是第 t 次迭代的学习率,有关momentum更新方式的说明,强烈推荐 stanford 公开课 cs231n,其中有关网络训练的说明(http://cs231n.github.io/neural-networks-3/)对各种参数更新方法有详细的说明。

就代码而言, paddle.optimizer.Momentum 中的第一个参数 momentum会用作实例化MomentumOptimizer(Paddle-develop/python/paddle/trainer_config_helpers)类型的对象,其他参数会被传递到 Optimizer类(Paddle-develop/python/paddle/v2)构造函数的__impl__里面(代码中的31行):

25 class Optimizer(object):

26 def __init__(self, **kwargs):

27 import py_paddle.swig_paddle as swig_api

28 if 'batch_size' in kwargs:

29 del kwargs['batch_size'] # not important for python library.

30

31 def __impl__():

32 v1_optimizers.settings(batch_size=1, **kwargs)

33

34 self.__opt_conf_proto__ = config_parser_utils.parse_optimizer_config(

35 __impl__)

36 self.__opt_conf__ = swig_api.OptimizationConfig.createFromProto(

37 self.__opt_conf_proto__)

v1_optimizers.settings的定义在Paddle-develop/python/paddle/trainer_config_helpers/optimizers.py

358 def settings(batch_size,

359 learning_rate=1e-3,

360 learning_rate_decay_a=0.,

361 learning_rate_decay_b=0.,

362 learning_rate_schedule='poly',

363 learning_rate_args='',

364 learning_method=None,

365 regularization=None,

366 is_async=False,

367 model_average=None,

368 gradient_clipping_threshold=None):

2.4 其他

后面就是一些中规中矩的流程,包括回调函数 event_handler 打印一些中间结果,并保存权重训练的中间结果,再后面就是对网络的训练以及 test 集的测试。

3. 总结:

本文对一个具体的图像识别的例子做了说明,但并不是每一个步骤都很具体,这是因为本文想带来一种方法而不是简单的代码解释。

这种方法就是通过示例代码---->文档---->源代码的阅读顺序一步一步理解示例中一些重要函数背后的实现,当文档描述不清楚甚至根本没有对示例代码中某些接口进行说明时,我们只有阅读源码才能理解示例中究竟用了什么数学方法,这就体现了源代码的价值。

由于准备时间比较仓促,其中可能会有些不清楚甚至错误,希望大家指出来共同进步。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 UAI人工智能 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
图像识别
腾讯云图像识别基于深度学习等人工智能技术,提供车辆,物体及场景等检测和识别服务, 已上线产品子功能包含车辆识别,商品识别,宠物识别,文件封识别等,更多功能接口敬请期待。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档