Paddle关键概念

Paddle关键概念


正文共:6452 字 0 图

预计阅读时间: 17 分钟

本文讨论一下PaddlePaddle框架中几个重要的概念,在使用Paddle进行开发时,弄清楚这几个概念是使用Paddle进行开发的一个前提。

Tensor张量

与当前主流框架相同,Paddle同样使用Tensor张量来表示数据,你可以将不同维度的Tensor理解成对应维度的矩阵,当然,两者是有差异的。

在Fluid中所有的数据类型都为LoD-Tensor,对于不存在序列信息的数据(如此处的变量X),其lod_level=0。

在Paddle Fluid版本中,有3个比较重要的Tensor

可学习参数

神经网络中的可学习参数(模型权重、偏置等)其生命周期与整个训练周期一样长,在训练过程中,可学习参数会被修改,最常见的就是模型权重这一可学习参数会被梯度下降算法修改,在Fluid中可以使用 fluid.layers.create_parameter来创建可学习参数

  1. w = fluid.layers.create_parameter(name='w', shape=[1], dtype='float32')

在日常编写模型中,该方法并不常用,因为Paddle已经为大部分常见的神经网络基本计算模型进行了封装,如创建全链接层,并不需要自己手动创建链接权重w与偏置b

  1. y = fluid.layers.fc(input=x, size=128, bias_attr=True)

其实进入 fluid.layers.create_parameter()方法的源码和 fluid.layers.fc()方法的源码,可以发现两者最终都是调用 helper.create_parameter()方法来创建可学习参数的,其中helper是LayerHelper的实例。

输入数据Tensor

Fluid使用 fluid.layers.data()方法来接受输入数据,与TensorFlow中的placeholder占位符作用是相同的,同样,fluid.layers.data()方法需要定义好输入Tensor的形状,如果遇到无法确定形状的维度,则设置为None

  1. x = fluid.layers.data(name='x', shape=[3, None], dtype='int64')


  2. # Batch size 无需显示指定, Paddle会自动填充

  3. a = fluid.layers.data(name='a', shape=[3,6], dtype='int64')


  4. # 输入数据为宽、高不是固定的图像, 主要图像数据类型通常为float32

  5. img = fluid.layers.data(name='img', shape=[3, None, None], dtype='float32')

上述代码中,一个tick就是Batch size,无需自己显示的定义。(通常训练模型时,都以一Batch size大小的数据为一次训练数据的量)

可以从源码看出(如下),data()方法中使用appendbatchsize变量判断是否要为shape自动加上batch size,但正是的创建输入,使用的是 helper.create_global_variable()方法。而helper依旧是LayerHelper的实例。

  1. def data(name,

  2.         shape,

  3.         append_batch_size=True,

  4.         dtype='float32',

  5.         lod_level=0,

  6.         type=core.VarDesc.VarType.LOD_TENSOR,

  7.         stop_gradient=True):

  8.    helper = LayerHelper('data', **locals())

  9.    shape = list(shape)

  10.    for i in six.moves.range(len(shape)):

  11.        if shape[i] is None:

  12.            shape[i] = -1

  13.            append_batch_size = False

  14.        elif shape[i] < 0:

  15.            append_batch_size = False


  16.    if append_batch_size:

  17.        shape = [-1] + shape  # append batch size as -1


  18.    data_var = helper.create_global_variable(

  19.        name=name,

  20.        shape=shape,

  21.        dtype=dtype,

  22.        type=type,

  23.        stop_gradient=stop_gradient,

  24.        lod_level=lod_level,

  25.        is_data=True)

  26.    return data_var

LayerHelper以出现多次,可以看出LayerHelper在Paddle中起着关键作用,从Paddle的设计文档中可以了解到LayerHelper,它的作用主要是在各个layers函数之间共享代码,设计LayerHelper主要考虑到如果开发全局辅助函数有几个缺点:

  • 1.需要提供这些方法的命名空间,方便开发人员快速定位并使用

  • 2.全局函数迫使图层开发人员需要逐个传递参数

  • 为了避免以上缺点,才定义出了LayerHelper,但它通常在layers开发中使用,对应创建模型结构的搭建不会使用到LayerHelper,关于LayerHelper的更多细节可以参考Design Doc:Python API,当然后面的文章我也打算讲讲Paddle设计层面以及底层点的东西,到时也会有所提及。

    常量Tensor

    Fluid通过 fluid.layers.fill_constant()实现常量Tensor,常量即常量,在模型训练过程中,其值不会改变。

    1. data = fluid.layers.fill_constant(shape=[1], value=0, dtype='int64')

    该方法的内部依旧是使用LayerHelper类来实现常量Tensor

    1. helper = LayerHelper("fill_constant", **locals())

    Paddle数据传入

    Fluid版本的Paddle支持两种传入数据的方式,分别是

  • Python Reader同步读入数据:使用 fluid.layers.data()定义输入层,并在 fluid.Executor或 fluid.ParallelExecutor中使用 executor.run(feed=...)来传入数据,与TensorFlow类似,定义好Placeholder占位符,再在具体训练时,将具体的数据喂养给模型

  • py_reader异步读入数据:异步读入数据,先需要使用 fluid.layers.py_reader()配置异步数据输入层,再使用 py_reader()的 decorate_paddle_reader或 decorate_tensor_provider方法配置数据,配置完数据后,最总通过 fluid.layers.read_file读取数据。

  • 对于常用模型,比较常见的事同步读入数据的方式。Paddle的数据读入会在后一章进行讲解。

    Operator表示对数据的操作

    Fluid版本的Paddle中,所有的数据操作都由Operator表示,在python端,Operator操作被进一步封装在 paddle.fluid.layerspaddle.fluid.nets等模块中,简单而言,所谓构建神经网络其实就是使用框架提供的各种Operator来操作数据,不必想的过于复杂。一个简单的加法运算如下:

    1. import paddle.fluid as fluid


    2. #输入层

    3. a = fluid.layers.data(name='a', shape=[2], dtype='float32')

    4. b = fluid.layers.data(name='b', shape=[2], dtype='float32')


    5. result = fluid.layers.elementwise_add(a,b)


    6. cpu = fluid.CPUPlace() # 定义运算场所

    7. exe = fluid.Executor(cpu) # 创建执行器

    8. exe.run(fluid.default_startup_program()) # 网络参数初始化


    9. # 准备数据

    10. import numpy


    11. x = numpy.array([1,2])

    12. y = numpy.array([2,3])


    13. #执行计算

    14. outs = exe.run(

    15.    feed={'a':x,'b':y},

    16.    fetch_list=[a,b,result.name])

    17. #查看输出结果

    18. print(outs)

    一开始使用 fluid.layers.data()定义数据输入层,具体的数据通过 numpy.array()生成,因为 fluid.layers.data()定义了shape=[2],那么此时numpy.array()需要定义成[1,2],即第一维的形状为2,如果shape=[1,2],那么第一维的形状为1,第二维的形状为2,即numpy.array()需要定义成[[1,2]]。

    动态图机制

    Fluid使用的是动态图机制,因为静态图机制让使用者在编写过程中丧失了对网络结构修改的灵活性,所以很多优秀的框架都引入了动态图这种设计(TensorFlow 2.0动态图机制也大幅加强了)。

    动态图 vs 静态图

    动态计算意味着程序将按照我们编写命令的顺序进行执行。这种机制将使得调试更加容易,并且也使得我们将大脑中的想法转化为实际代码变得更加容易。而静态计算则意味着程序在编译执行时将先生成神经网络的结构,然后再执行相应操作。从理论上讲,静态计算这样的机制允许编译器进行更大程度的优化,但是这也意味着你所期望的程序与编译器实际执行之间存在着更多的代沟。这也意味着,代码中的错误将更加难以发现(比如,如果计算图的结构出现问题,你可能只有在代码执行到相应操作的时候才能发现它)。尽管理论上而言,静态计算图比动态计算图具有更好的性能,但是在实践中我们经常发现并不是这样的。

    来源:动态图 vs 静态图

    Program描述模型

    Fluid版本的Paddle中使用Program的形式描述计算过程,开发者所有写入在Program中的Operator操作都会自动转为ProgramDesc描述语言。

    Fluid通过提供顺序、分支和循环三种执行结构的支持

    其中顺序执行,写法与静态图的形式没什么差别,如下:

    1. # 顺序执行的方式搭建网络

    2. x = fluid.layers.data(name='x', shape=[13], dtype='float32')

    3. y_predict = fluid.layers.fc(input=x, size=1, act=None)

    4. y = fluid.layers.data(name='y', shape=[1], dtype='float32')

    5. cost = fluid.layers.square_error_cost(input=y_predict, label=y)

    分支条件switch的使用如下:

    1. # 条件分支——switch、if else

    2. lr = fluid.layers.tensor.create_global_var(

    3.    shape=[1],

    4.    value=0.0,

    5.    dtype='float32',

    6.    persistable=True,

    7.    name='learning_rate'

    8. )

    9. one_var = fluid.layers.fill_constant(

    10.    shape=[1], dtype='float32', value=1.0

    11. )

    12. two_var = fluid.layers.fill_constant(

    13.    shape=[1], dtype='float32', value=2.0

    14. )


    15. # switch

    16. with fluid.layers.control_flow.Switch() as switch:

    17.    with switch.case(global_step == one_var):

    18.        fluid.layers.tensor.assign(input=one_var, output=lr)

    19.        with switch.default():

    20.            fluid.layers.tensor.assign(input=two_var, output=lr)

    其中流程控制方面的内容都放在了 fluid.layers.control_flow模块下,里面包含了While、Block、Conditional、Switch、if、ifelse等跟中操作,这样可以让在编写模型时,想编写普通的python程序一样。

    Executor执行Program

    Program相当于你模型的整体结构,Fluid中程序执行分为编译与执行两个阶段,当你定义编写完Program后,还需要定义Executor,Executor会接收Program,然后将其转为FluidProgram,这一步称为编译,然后再使用C++编写的后端去执行它,执行的过程也由Executor完成,相关代码片段如下:

    1. cpu = fluid.core.CPUPlace()

    2. exe = fluid.Executor(cpu) #执行器

    3. exe.run(fluid.default_startup_program()) #初始化Program


    4. outs = exe.run(

    5.    feed={'a':x, 'b':y},

    6.    fetch_list=[result.name]

    7. )

    在Fluid中使用Executor.run来运行一段Program。

    正式进行网络训练前,需先执行参数初始化。其中 defalut_startup_program中定义了创建模型参数,输入输出,以及模型中可学习参数的初始化等各种操作。

    由于传入数据与传出数据存在多列,因此 fluid 通过 feed 映射定义数据的传输数据,通过 fetch_list 取出期望结果

    整体实践

    写过简单结构,预测一组数据,使用单个全连接层来实现预测,核心的逻辑就是喂养为模型一些训练数据,模型输出预测结果,该预测结果与真实结果直接会有个误差,作为损失,通过平方差损失来定义这两个损失,然后再最小化该损失,整体逻辑如下:

    1. import paddle.fluid as fluid

    2. import numpy as np


    3. '''

    4. 真实数据

    5. '''

    6. #训练数据

    7. train_data = np.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')

    8. #真实标签

    9. y_true = np.array([[2.0], [4.0], [6.0], [8.0]]).astype('float32')


    10. # 输入层,接受真实数据

    11. x = fluid.layers.data(name='x', shape=[1], dtype='float32')

    12. y = fluid.layers.data(name='y', shape=[1], dtype='float32')


    13. # 模型结构,这里就一个简单的全连接层

    14. y_predict = fluid.layers.fc(input=x, size=1, act=None)


    15. # 定义损失,真实标签值与模型预测值之间的损失

    16. cost = fluid.layers.square_error_cost(input=y_predict, label=y)

    17. #取平均,因为通常有batch个数据,取平均值作为损失则可

    18. avg_cost = fluid.layers.mean(cost)


    19. # 定义执行设备,CUP或GPU

    20. cpu = fluid.CPUPlace()

    21. # Executor执行,该方法只能实现单设备执行

    22. exe = fluid.Executor(cpu)


    23. # 初始化整个结构中的节点

    24. exe.run(fluid.default_startup_program())


    25. for i in range(100):

    26.    outs = exe.run(

    27.        feed = {'x': train_data, 'y': y_true}, #将真实数据喂养给模型,注意传入数据与输入层的关系

    28.        fetch_list=[y_predict.name, avg_cost.name] #获取列表,即执行完后,outs会获得的数据

    29.    )


    30. print(outs)

    ©著作权归作者所有,转载或内容合作请联系作者
    • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
      沈念sama阅读 211,948评论 6 492
    • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
      沈念sama阅读 90,371评论 3 385
    • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
      开封第一讲书人阅读 157,490评论 0 348
    • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
      开封第一讲书人阅读 56,521评论 1 284
    • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
      茶点故事阅读 65,627评论 6 386
    • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
      开封第一讲书人阅读 49,842评论 1 290
    • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
      沈念sama阅读 38,997评论 3 408
    • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
      开封第一讲书人阅读 37,741评论 0 268
    • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
      沈念sama阅读 44,203评论 1 303
    • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
      茶点故事阅读 36,534评论 2 327
    • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
      茶点故事阅读 38,673评论 1 341
    • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
      沈念sama阅读 34,339评论 4 330
    • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
      茶点故事阅读 39,955评论 3 313
    • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
      开封第一讲书人阅读 30,770评论 0 21
    • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
      开封第一讲书人阅读 32,000评论 1 266
    • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
      沈念sama阅读 46,394评论 2 360
    • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
      茶点故事阅读 43,562评论 2 349

    推荐阅读更多精彩内容