# Fluid编程指南 本文档将指导您如何用Fluid API编程并搭建一个简单的神经网络。阅读完本文档,您将掌握: - Fluid有哪些核心概念 - 如何在fluid中定义运算过程 - 如何使用executor运行fluid操作 - 如何从逻辑层对实际问题建模 - 如何调用API(层,数据集,损失函数,优化方法等等) 在进行模型搭建之前,首先需要明确几个Fluid核心使用概念: ## 使用Tensor表示数据 Fluid和其他主流框架一样,使用Tensor数据结构来承载数据。 在神经网络中传递的数据都是Tensor,Tensor可以简单理解成一个多维数组,一般而言可以有任意多的维度。不同的Tensor可以具有自己的数据类型和形状,同一Tensor中每个元素的数据类型是一样的,Tensor的形状就是Tensor的维度。 下图直观地表示1~6维的Tensor:
在 Fluid 中存在三种特殊的 Tensor: **1. 模型中的可学习参数** 模型中的可学习参数(包括网络权重、偏置等)生存期和整个训练任务一样长,会接受优化算法的更新,在 Fluid 中以 Variable 的子类 Parameter 表示。 在Fluid中可以通过`fluid.layers.create_parameter`来创建可学习参数: ```python w = fluid.layers.create_parameter(name="w",shape=[1],dtype='float32') ``` 一般情况下,您不需要自己来创建网络中的可学习参数,Fluid 为大部分常见的神经网络基本计算模块都提供了封装。以最简单的全连接模型为例,下面的代码片段会直接为全连接层创建连接权值(W)和偏置( bias )两个可学习参数,无需显式地调用 Parameter 相关接口来创建。 ```python import paddle.fluid as fluid y = fluid.layers.fc(input=x, size=128, bias_attr=True) ``` **2. 输入输出Tensor** 整个神经网络的输入数据也是一个特殊的 Tensor,在这个 Tensor 中,一些维度的大小在定义模型时无法确定(通常包括:batch size,如果 mini-batch 之间数据可变,也会包括图片的宽度和高度等),在定义模型时需要占位。 Fluid 中使用 `fluid.layers.data` 来接收输入数据, `fluid.layers.data` 需要提供输入 Tensor 的形状信息,当遇到无法确定的维度时,相应维度指定为 None ,如下面的代码片段所示: ```python import paddle.fluid as fluid #定义x的维度为[3,None],其中我们只能确定x的第一的维度为3,第二个维度未知,要在程序执行过程中才能确定 x = fluid.layers.data(name="x", shape=[3,None], dtype="int64") #batch size无需显示指定,框架会自动补充第0维为batch size,并在运行时填充正确数值 a = fluid.layers.data(name="a",shape=[3,4],dtype='int64') #若图片的宽度和高度在运行时可变,将宽度和高度定义为None。 #shape的三个维度含义分别是:channel、图片的宽度、图片的高度 b = fluid.layers.data(name="image",shape=[3,None,None],dtpye="float32") ``` 其中,dtpye=“int64”表示有符号64位整数数据类型,更多Fluid目前支持的数据类型请查看:[Fluid目前支持的数据类型](../../user_guides/howto/prepare_data/feeding_data.html#fluid)。 **3. 常量 Tensor** Fluid 通过 `fluid.layers.fill_constant` 来实现常量Tensor,用户可以指定Tensor的形状,数据类型和常量值。代码实现如下所示: ```python import paddle.fluid as fluid data = fluid.layers.fill_constant(shape=[1], value=0, dtype='int64') ``` 需要注意的是,上述定义的tensor并不具有值,它们仅表示将要执行的操作,如您直接打印data将会得到描述该data的一段信息: ```python print data ``` 输出结果: ``` name: "fill_constant_0.tmp_0" type { type: LOD_TENSOR lod_tensor { tensor { data_type: INT64 dims: 1 } } } persistable: false ``` 具体输出数值将在Executor运行时得到,详细过程会在后文展开描述。 ## 数据传入 Fluid有特定的数据传入方式: 您需要使用 `fluid.layers.data` 配置数据输入层,并在 `fluid.Executor` 或 `fluid.ParallelExecutor` 中,使用 executor.run(feed=...) 传入训练数据。 具体的数据准备过程,请阅读[准备数据](../../user_guides/howto/prepare_data/index.html) ## 使用Operator表示对数据的操作 在Fluid中,所有对数据的操作都由Operator表示,您可以使用内置指令来描述他们的神经网络。 为了便于用户使用,在Python端,Fluid中的Operator被一步封装入`paddle.fluid.layers`,`paddle.fluid.nets` 等模块。 这是因为一些常见的对Tensor的操作可能是由更多基础操作构成,为了提高使用的便利性,框架内部对基础 Operator 进行了一些封装,包括创建 Operator 依赖可学习参数,可学习参数的初始化细节等,减少用户重复开发的成本。 例如用户可以利用`paddle.fluid.layers.elementwise_add()`实现两个输入Tensor的加法运算: ```python #定义网络 import paddle.fluid as fluid a = fluid.layers.data(name="a",shape=[1],dtype='float32') b = fluid.layers.data(name="b",shape=[1],dtype='float32') result = fluid.layers.elementwise_add(a,b) #定义Exector cpu = fluid.core.CPUPlace() #定义运算场所,这里选择在CPU下训练 exe = fluid.Executor(cpu) #创建执行器 exe.run(fluid.default_startup_program()) #网络参数初始化 #准备数据 import numpy data_1 = input("a=") data_2 = input("b=") x = numpy.array([[data_1]]) y = numpy.array([[data_2]]) #执行计算 outs = exe.run( feed={'a':x,'b':y}, fetch_list=[result.name]) #验证结果 print "%d+%d=%d" % (data_1,data_2,outs[0][0]) ``` 输出结果: ``` a=7 b=3 7+3=10 ``` 本次运行时,输入a=7,b=3,得到outs=10。 您可以复制这段代码在本地执行,根据指示输入其他数值观察计算结果。 如果想获取网络执行过程中的a,b的具体值,可以将希望查看的变量添加在fetch_list中。 ```python ... #执行计算 outs = exe.run( feed={'a':x,'b':y}, fetch_list=[a,b,result.name] #查看输出结果 print outs ``` 输出结果: ``` [array([[7]]), array([[3]]), array([[10]])] ``` ## 使用Program描述神经网络模型 Fluid不同于其他大部分深度学习框架,去掉了静态计算图的概念,代之以Program的形式动态描述计算过程。这种动态的计算描述方式,兼具网络结构修改的灵活性和模型搭建的便捷性,在保证性能的同时极大地提高了框架对模型的表达能力。 开发者的所有 Operator 都将写入 Program ,在Fluid内部将自动转化为一种叫作 ProgramDesc 的描述语言,Program 的定义过程就像在写一段通用程序,有开发经验的用户在使用 Fluid 时,会很自然的将自己的知识迁移过来。 其中,Fluid通过提供顺序、分支和循环三种执行结构的支持,让用户可以通过组合描述任意复杂的模型。 **顺序执行:** 用户可以使用顺序执行的方式搭建网络: ```python x = fluid.layers.data(name='x',shape=[13], dtype='float32') y_predict = fluid.layers.fc(input=x, size=1, act=None) y = fluid.layers.data(name='y', shape=[1], dtype='float32') cost = fluid.layers.square_error_cost(input=y_predict, label=y) ``` **条件分支——switch、if else:** Fluid 中有 switch 和 if-else 类来实现条件选择,用户可以使用这一执行结构在学习率调节器中调整学习率或其他希望的操作: ```python lr = fluid.layers.tensor.create_global_var( shape=[1], value=0.0, dtype='float32', persistable=True, name="learning_rate") one_var = fluid.layers.fill_constant( shape=[1], dtype='float32', value=1.0) two_var = fluid.layers.fill_constant( shape=[1], dtype='float32', value=2.0) with fluid.layers.control_flow.Switch() as switch: with switch.case(global_step == zero_var): fluid.layers.tensor.assign(input=one_var, output=lr) with switch.default(): fluid.layers.tensor.assign(input=two_var, output=lr) ``` 关于 Fluid 中 Program 的详细设计思想,可以参考阅读[Fluid设计思想](../../user_guides/design_idea/fluid_design_idea.html) 更多 Fluid 中的控制流,可以参考阅读[API文档](http://www.paddlepaddle.org/documentation/api/zh/1.0.0/layers.html#permalink-1-control_flow) ## 使用Executor执行Program Fluid的设计思想类似于高级编程语言C++和JAVA等。程序的执行过程被分为编译和执行两个阶段。 用户完成对 Program 的定义后,Executor 接受这段 Program 并转化为C++后端真正可执行的 FluidProgram,这一自动完成的过程叫做编译。 编译过后需要 Executor 来执行这段编译好的 FluidProgram。 例如上文实现的加法运算,当构建好 Program 后,需要创建 Executor,进行初始化 Program 和训练 Program: ```python #定义Exector cpu = fluid.core.CPUPlace() #定义运算场所,这里选择在CPU下训练 exe = fluid.Executor(cpu) #创建执行器 exe.run(fluid.default_startup_program()) #初始化Program #训练Program,开始计算 #feed以字典的形式定义了数据传入网络的顺序 #fetch_list定义了网络的输出 outs = exe.run( feed={'a':x,'b':y}, fetch_list=[result.name]) ``` ## 代码实例 至此,您已经对Fluid核心概念有了初步认识了,不妨尝试配置一个简单的网络吧。如果感兴趣的话可以跟随本部分,完成一个非常简单的数据预测。已经掌握这部分内容的话,可以跳过本节阅读[What's next](#what_next)。 从逻辑层面明确了输入数据格式、模型结构、损失函数以及优化算法后,需要使用 PaddlePaddle 提供的 API 及算子来实现模型逻辑。一个典型的模型主要包含4个部分,分别是:输入数据格式定义,模型前向计算逻辑,损失函数以及优化算法。 1. 问题描述 给定一组数据 $