对于常用模型,比较常见的事同步读入数据的方式。Paddle的数据读入会在后一章进行讲解。
Operator表示对数据的操作
Fluid版本的Paddle中,所有的数据操作都由Operator表示,在python端,Operator操作被进一步封装在 paddle.fluid.layers
与 paddle.fluid.nets
等模块中,简单而言,所谓构建神经网络其实就是使用框架提供的各种Operator来操作数据,不必想的过于复杂。一个简单的加法运算如下:
import paddle.fluid as fluid
a = fluid.layers.data(name='a', shape=[2], dtype='float32')
b = fluid.layers.data(name='b', shape=[2], dtype='float32')
result = fluid.layers.elementwise_add(a,b)
cpu = fluid.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())
import numpy
x = numpy.array([1,2])
y = numpy.array([2,3])
outs = exe.run(
feed={
'a':x,'b':y},
fetch_list=[a,b,result.name])
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通过提供顺序、分支和循环三种执行结构的支持
其中顺序执行,写法与静态图的形式没什么差别,如下:
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的使用如下:
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 == one_var):
fluid.layers.tensor.assign(input=one_var, output=lr)
with switch.default():
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完成,相关代码片段如下:
cpu = fluid.core.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())
outs = exe.run(
feed={
'a':x, 'b':y},
fetch_list=[result.name]
)
在Fluid中使用Executor.run来运行一段Program。
正式进行网络训练前,需先执行参数初始化。其中 defalut_startup_program
中定义了创建模型参数,输入输出,以及模型中可学习参数的初始化等各种操作。
由于传入数据与传出数据存在多列,因此 fluid 通过 feed 映射定义数据的传输数据,通过 fetch_list 取出期望结果
整体实践
写过简单结构,预测一组数据,使用单个全连接层来实现预测,核心的逻辑就是喂养为模型一些训练数据,模型输出预测结果,该预测结果与真实结果直接会有个误差,作为损失,通过平方差损失来定义这两个损失,然后再最小化该损失,整体逻辑如下:
import paddle.fluid as fluid
import numpy as np
'''
真实数据
'''
train_data = np.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')
y_true = np.array([[2.0], [4.0], [6.0], [8.0]]).astype('float32')
x = fluid.layers.data(name='x', shape=[1], dtype='float32')
y = fluid.layers.data(name='y', shape=[1], dtype='float32')
y_predict = fluid.layers.fc(input=x, size=1, act=None)
cost = fluid.layers.square_error_cost(input=y_predict, label=y)
avg_cost = fluid.layers.mean(cost)
cpu = fluid.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())
for i in range(100):
outs = exe.run(
feed = {
'x': train_data, 'y': y_true},
fetch_list=[y_predict.name, avg_cost.name]
)
print(outs)