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

视频内容

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. 总结:

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

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

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

原文发布于微信公众号 - UAI人工智能(UniversityAI)

原文发表时间:2017-12-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开心的学习之路

神经网络体系搭建(四)——快速上手TensorFlow

本篇是神经网络体系搭建的第四篇,解决体系搭建的TensorFlow相关问题,详见神经网络体系搭建(序) ? TensorFlow安装 建议用Anacond...

3095
来自专栏鸿的学习笔记

写给开发者的机器学习指南(六)

在本节中,我们为您介绍一组在实际环境中的机器学习算法。 这些例子的想法是让你开始使用机器学习算法,而不深入解释底层算法。我们只专注于这些算法的特征方面,如何验证...

722
来自专栏分子生物和分子模拟计算

同源建模

902
来自专栏机器之心

终于!TensorFlow引入了动态图机制Eager Execution

42311
来自专栏成长道路

文本型数据的向量化:TF-IDF

1.对于文本型数据的分类处理(或者其他的处理),根据ik和jcseg等分词器先对它们进行分词处理之后,大家都知道,计算机是处理不了汉字的,对于文本型的词我们如何...

2070
来自专栏Y大宽

TBtools基因家族分析详细教程(1)

一共分为4个部分 TBtools基因家族分析详细教程(1) TBtools基因家族分析详细教程(2)基因家族成员的基本分析 TBtools基因家族分析详细...

2992
来自专栏算法channel

案例实战|泰坦尼克号船员获救预测(数据预处理部分)

01 — 背景介绍 已经推送了一些经典的机器学习和深度学习相关的算法,明白了这些算法的原理,对我们之后解决实际问题会打下很好的基础,如何将这些零散的知识综合起来...

3837
来自专栏Unity Shader

Shader初学笔记:等值线

http://www.cnblogs.com/lpcoder/p/7103634.html

5585
来自专栏人工智能LeadAI

TF使用例子-情感分类

这次改写一下,做一个简单的分类模型和探讨一下hidden layer在聚类的应用场景下会有什么效果。为了能写的尽可能让读者理解,本文也会写一下keras来实现(...

4473
来自专栏人工智能LeadAI

TensorFlow从1到2 | 第五章 非专家莫入!TensorFlow实现CNN

当看到本篇时,根据TensorFlow官方标准《Deep MNIST for Experts》(https://tensorflow.google.cn/get...

3948

扫码关注云+社区