购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 全连接层详解

学完前面的内容后,读者对TensorFlow程序设计应该有了比较感性的认识。不过这里有一个问题,前面反复提及的全连接层到底是什么呢?本节将详细讲解一下。

2.2.1 全连接层的定义与实现

图2.11 全连接网络

全连接层的每一个节点都与上一层的所有节点相连,用来把前面提取的特征综合起来。由于其全相连的特性,一般全连接层的参数也是最多的。图2.11所示的是一个简单的全连接网络。

其推导过程如下:

w 11× x 1+ w 12× x 2+ w 13× x 3= a 1

w 21× x 1+ w 22× x 2+ w 23× x 3= a 2

w 31× x 1+ w 32× x 2+ w 33× x 3= a 3

将推导公式转化一下,写法如下:

可以看到,全连接的核心操作就是矩阵向量乘积: w * x = y

下面举一个例子,使用TensorFlow自带的API实现一个简单的矩阵计算。

[1,1] [1]

[2,2]* [1]=[?]

首先,通过公式计算对数据进行先行验证,按推导公式计算如下:

(1×1+1×1)+0.17=2.17

(2×1+2×1)+0.17=4.17

这样最终形成了一个新的矩阵[2.17,4.17],代码如下:

【程序2-9】

最终打印结果如下:

     tf.Tensor([[2.17] [4.17]], shape=(2, 1), dtype=float32)

可以看到,最终计算出一个Tensor,大小为shape=(2,1),类型为float32,其值为[[2.17][4.17]]。

计算本身非常简单,全连接的计算方法相信读者也很容易掌握,现在回到代码中,注意我们在定义参数和定义输入值的时候采用了不同的写法:

     weight = tf.Variable([[1.],[1.]])
     input_xs = tf.constant([[1.,1.],[2.,2.]])

这里对参数的定义使用的是Variable函数,而对输入值的定义使用的是constant函数,将其对应内容打印如下:

input_xs打印如下:

通过对比可以看到,这里的weight被定义成一个可变参数Variable类型,以便在后续的反向计算中进行调整。constant函数用于直接读取数据,可以将其定义成Tensor格式。

2.2.2 使用TensorFlow自带的API实现全连接层

读者千万不要有错误的理解,程序2-9中仅仅是为了向读者介绍全连接层的计算方法,而不是介绍全连接层。全连接的本质就是由一个特征空间线性变换到另一个特征空间。目标空间的任一维——也就是隐层的一个节点——都认为会受到源空间的每一维的影响。可以不那么严谨地说,目标向量是源向量的加权和。

全连接层一般接在特征提取网络之后,用作对特征的分类器。全连接常出现在最后几层,用于对前面设计的特征做加权和。前面的网络相当于做特征工程,后面的全连接相当于做特征加权。

具体的神经网络差值反馈算法将在第3章介绍。

下面使用自定义的方法实现某一个可以加载到model中的“自定义全连接层”。

1.自定义层的继承

在TensorFlow中,任何一个自定义的层都是继承自tf.keras.layers.Layer,我们将其称为“父层”,如图2.12所示。这里所谓的自定义层实际上是父层的一个具体实现。

图2.12 父层

从图2.12可以看到,Layer层中由多个函数构成,因此基于继承的关系,如果想要实现自定义的层,那么必须实现其中的函数。

2.“父层”函数介绍

所谓“父层”,就是这里自定义的层继承自哪里,告诉TensorFlow可以使用父层定义好的函数,或者添加自定义的其他函数。

Layer层中需要自定义的函数有很多,但是在实际使用时一般只需要定义那些必须使用的函数,例如build、call函数以及初始化所必需的__init__函数。

· __init__函数:首先进行一些必要参数的初始化,这些参数的初始化写在def__init__(self,)中。写法如下:

可以看到,init函数中最重要的就是显式地确定所需要的一些参数。值得注意的是,对于输入的init中的参数,输入Tensor不会在这里进行标注,init值初始化的是模型参数。输入值不属于“模型参数”。

· build函数:build函数主要用于声明需要更新的参数部分,如权重等。一般使用self.kernel=tf.Variable(shape=[])等来声明需要更新的参数变量:

build函数参数中的input_shape形参是固定不变的写法,读者不需修改,其中自定义的参数需要加上self,表明是在类中使用的全局参数。

代码最后的super(MyLayer,self).build(input_shape),目前读者只需要记得这种写法,在build的最后确定参数定义结束。

· call函数:最重要的函数,这部分代码包含主要层的实现。

init是对参数进行定义和声明,build函数是对权重可变参数进行声明。

这两个函数只是定义了一些初始化的参数以及一些需要更新的参数变量,而真正实现所定义类的作用是在call方法中。

可以看到call中的一系列操作是对__init__和build方法中变量参数的应用,所有的计算都在call函数中完成。需要注意的是,输入的参数也在这里出现,经过计算后返回计算值。

下面使用自定义的层修改iris模型,代码如下:

【程序2-10】

我们首先定义了MyLayer作为全连接层,之后正如使用TensorFlow自带的层一样,直接生成类函数并显式指定输入参数,最终将所有的层加入Model中。最终打印结果如图2.13所示。

图2.13 打印结果

2.2.3 打印显示已设计的Model结构和参数

程序2-10使用自定义层实现了Model。如果读者认真学习了这部分内容,那么相信你一定可以实现自己的自定义层。

但是还有一个问题,对于自定义的层来说,这里的参数名,也就是在build中定义的参数名称都是一样的。而在层生成的过程中似乎并没有对每个层进行重新命名,或者将其归属于某个命名空间中。这看起来与传统的TensorFlow 1.X模型的设计结果相冲突。

实践是解决疑问的最好办法。TensorFlow中提供了打印模型结构的函数,代码如下:

     print(model.summary())

使用这个函数,将其置于构建后的model下,即可打印模型的结构与参数。

【程序2-11】

打印结果如图2.14所示。

图2.14 打印结果

从打印出的模型结果可以看到,这里每一层都根据层的名称重新命名,而且由于名称相同,TensorFlow框架自动根据其命名方式对其进行层数的增加(名称)。

对于读者更关心的参数问题,从对应行的第三列Param可以看到,不同的层,其参数个数也不相同,因此可以认为在TensorFlow中,重名的模型被自动赋予一个新的名称,并存在于不同的命名空间之中。 Pw89WL1BcN5mAc/Yd+Kvvpd95fpcy4GhiHGGuFPkqajc6vl36A7A/A+7V05X/QBQ

点击中间区域
呼出菜单
上一章
目录
下一章
×