全连接层的数学表述可以参见Maples丶丶的《详解神经网络的前向传播和反向传播(从头推导)》的“前向传播”一节。

为了容易理解,我还是从经典的波士顿房价谈起。波士顿房价的详细内容请参见https://www.paddlepaddle/documentation/docs/zh/beginners_guide/basics/fit_a_line/README.html

简化一下,假设房价y与犯罪率x1、教育资源x2、建设时间x3、收入水平x4这四个因素线性相关:

则 y = a1*x1 + a2*x2 + a3*x3 + a4*x4 + b  

我通过建立 Paddle Fluid 的全连接层fc,就是根据大量的房价的调研数据,即把已知大量的 y 与 x1、x2、x3、x4对应关系的数据告诉FC,FC通过学习求出a1、a2、a3、a4、b。通过一段的学习后,即使给FC一组为见过的x1、x2、x3、x4,FC也能预测出y。  

常量 b 由 fc 的 bias_attr 参数指定。为了简化,我使用了默认值None,表示该参数由 param_attr 参数决定。而 param_attr 也使用了默认值None,意味着a1、a2、a3、a4采用Xavier初始化方式以及偏置参数 b = 0。  

 

所谓全连接层是指每个输入节点都与每个输出节点相连。这里只有一个输出节点,所以 fc 的 size = 1,因不考虑非线性的激活函数,因而 fc 的 act = None。fc 函数的使用可写为如下格式:

y = paddle.fluid.layers.fc(input = x,size=1,act=None)      #语句1

上式中的参数input=x中的x是一个Tensor(对于复杂问题可以是多个Tensor),本例指有四个输入节点(shape=4),每个输入节点可以接受32bit的浮点数(dtype=float32)的Tensor。

我想给输入端喂入的数据就是犯罪率x1、教育资源x2、建设时间x3、收入水平x4,假设x1=0.00632,x2=18,x3=2.31,x4=0.538

则在程序中可以如下定义数据:

input_data = numpy.array([[0.00632,18,2.31,0.538]]).astype('float32')    #语句2

我如果定义成numpy.array([0.00632,18,2.31,0.538]),则喂入数据时报错。我想,这是因为全连接层是为了接收多组数据而设计的。我虽然只提供了一组数据,也不能shape=(4),而必须使得shape=(n,4)   n>=1。参见https://www.paddlepaddle/documentation/docs/zh/api_cn/layers_cn/fc_cn.html

但是这组数据不能直接喂入到全连接层的输入节点,必须通过输入层才能将数据输入到神经网络中。输入层可以使用data算子创建,本例的输入层在程序中可以如下定义,注意输入层必须与输入的数据完全匹配才行:

x = paddle.fluid.data(name='datax',shape=[-1,4],dtype='float32')  #语句3

其中语句3的x与语句1的input值保持一致。上句中的name='datax'是输入层输出的前缀标识。

注:老版本使用的是 paddle.fluid.layers.data,fluid1.6改为 paddle.fluid.data后,Executor运行时检查输入数据的维度和数据类型。详见

https://www.paddlepaddle/documentation/docs/zh/api_cn/fluid_cn/data_cn.html

https://www.paddlepaddle/documentation/docs/zh/api_cn/layers_cn/data_cn.html

语句3和语句2就搭建了一个简单的神经网络,语句2准备了输入数据。欲使网络工作,还需要定义执行器:

cpu = paddle.fluid.core.CPUPlace()
exe = paddle.fluid.Executor(cpu)一致。
exe.run(paddle.fluid.default_startup_program())

下面就可以喂入数据,指定需要观察的中间过程:

outs = exe.run( feed={'datax':input_data},   # datax与语句3的name值保持一致,input_data与语句2的input_data一致。 

                                fetch_list=[y])                            # 对全连接层的输出值感兴趣

这个例子的完整程序如下:

 

import paddle.fluid as fluid
import numpy

#准备原始数据
input_data=numpy.array([[0.00632,18,2.31,0.538]]).astype('float32')
#print(input_data.shape) # 查看原始数据的shape

#定义输入层
#以下2条语句都可以用。任选其中一条
x=fluid.data(name="datax",shape=[-1,4],dtype='float32')  # 喂入的数据组数不确定 
# x=fluid.data(name="datax",shape=[1,4],dtype='float32') # 只喂入一组数据

#定义全连接层
y=fluid.layers.fc(input=x,size=1,act=None)

#定义执行器:cpu版
cpu = fluid.core.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program()) # 运行默认的启动程序

# 运行并打印结果
out = exe.run(feed={'datax':input_data},fetch_list=[x,y]) 
print(out)
 

某次的运行结果(因为有学习能力,所以每次的结果不同)如下:

那么a1、a2、a3、a4到底等于多少呢?那是机器学习自己调整的模型参数,我看不到。我只关心给一组x1、x2、x3、x4后,y是多少。

为了达到实用效果,我还要对这个FC进行考核,把考核的结果通知FC,FC就会不断进步了。这牵涉到误差计算和优化方法,相关的学习笔记我会另外再写。

最后,选取 fluid1.5 上的一个官方代码实例,作为本篇的结尾。

#!/usr/bin/python
#_*_ coding: utf-8 _*_

'''
官网上的一个fluid 配置网络的代码示例,略作改编
https://www.paddlepaddle/documentation/docs/zh/1.5/beginners_guide/programming_guide/programming_guide.html#id2
CSY 2019-2-30

'''

#加载库
import paddle.fluid as fluid
import numpy

'''
已知:
当x=1时,y=2;
当x=2时,y=4;
当x=3时,y=6;
当x=4时,y=8;
'''
#定义X数值
train_data=numpy.array([[1.0],[2.0],[3.0],[4.0]]).astype('float32')
#定义期望预测的真实值y_true
# y_true = numpy.array([[2.0],[4.0],[6.0],[8.0]]).astype('float32') # 用于损失函数,暂忽略

#定义输入层
x = fluid.data(name="x",shape=[-1,1],dtype='float32')
#定义全连接层
y_predict = fluid.layers.fc(input=x,size=1,act=None)

#参数初始化
cpu = fluid.core.CPUPlace()
exe = fluid.Executor(cpu)
exe.run(fluid.default_startup_program())

#开始训练
outs = exe.run(
    feed={'x':train_data},
    fetch_list=[y_predict.name])
#观察结果
print (outs) # 一组随机数字,与 y_true 相差甚远

运行结果如下:

最后,附上官方的相关解释:

关于paddle.fluid.data
paddle.fluid.data()是一个OP(算子),作用就是创建一个全局变量,可供计算图中的算子访问,可作为占位符用于数据输入。
name 是paddle.fluid.data()创建的全局变量的名字,是输入层输出的前缀标识。   
shape 声明了paddle.fluid.data()创建的全局变量的维度信息。  
shape中的None 表示不确定该维的元素数量,待程序执行中确定。  
shape中的-1 只能在shape的最前面,表示可以适应任何 batch size  
dtype 是paddle.fluid.data()创建的全局变量的数据类型,支持 bool,float16,float32,float64,int8,int16,int32,int64。  
用户 feed 的数据必须与 paddle.fluid.data() 创建的变量具有相同的 shape  
虽然feed的数据,其类型是unsigned Byte,但softmax 回归是要进行浮点运算的,所以数据类型都转换成了float32

关于paddle.fluid.layers.fc
paddle.fluid.layers.fc()是一个OP,作用就是建立一个全连接层。为每个输入的Tensor创建一个权重变量,即一个从每个输入单元到每个输出单元的全连接权重矩阵。  
FC层将每个输入Tensor和其对应的权重(weights)相乘得到shape为 [M,size] 输出Tensor,其中 M 为batch_size大小。如果有多个输入Tensor,则多个shape为 [M,size] 的Tensor计算结果会被累加起来,作为最终输出。

更多推荐

从简单的线性方程开始了解Paddle Fluid 的全连接层 FC