TensorFlow可微分编程实践3---计算图模型

在这篇博文中,我们将探讨怎样通过可微分编程技术,实现深度学习中最常用的多层感知器(MLP)模型。我们在这里使用TensorFlow Eager Execution API,并使用多层感知器模型来进行MNIST手写数字识别任务。如果我们单纯想尝试一下自动微分和可微分编程,以及如何用TensorFlow来调用这些技术,我们可以使用TensorFlow内置类来做这个工作,但是这样大家就无从了解实现的细节了,对于深刻掌握可微分编程来说是不利的。因此我们在这篇博文,会尝试从头开始,利用自动微分技术,实现一个简单的多层感知器模型。
我们可以构造一个最简的多层感知器(MLP)模型,来做MNIST手写数字识别工作,如下所示:
这里写图片描述
因为MNIST图片为 28 × 28 的黑白图片,所以输入向量为 x   i n R 784 ,这里的 n = 784 ,即共有784维。对第i个样本,我们用 x ( i ) 来表示,在本例中,为了讨论问题方便,我们省略的上标仅用 x 表示,但是大家要注意这代表的是某一个样本。对于图中的每个像素点,我们将28行串接起来,组成一个784个的长数列,用下标表示某个像素点的取值,例如第2行第5列的下标为 28 × 2 + 5 = 61 ,可以用 x 61 来表示。
输入层与第1层采用全连接方式,第1层第i个节点的输入值我们用 z i 1 ,其为输入层所有神经元的输出值,与该神经元与第1层第i个神经元连接权值相乘再相加的结果,我们假设输入层第j个神经元指向第1层第i个神经元的连接权值用 W i , j 1 表示,上标代表为第1层,下标第一个代表是第1层第i个神经元,第二个代表是输入层第j个神经元,我们可以得出第1层第i个神经元的输入值公式:

(1) z i 1 = W i , 1 1 x 1 + W i , 2 1 x 2 + . . . + W i , j 1 x j + . . . + W i , 784 1 x 784 + b i 1

或者简写为:
(2) z i 1 = j = 1 784 W i , j 1 x j + b i 1

我们通常将所有第1层神经元的输入值串起来形成一个向量,如下所示:
z 1 = [ z 1 1 z 2 1 . . . z 512 1 ]

我们将第1层神经元的偏置值 b i 1 与串在一起形成一个向量,如下所示:
b 1 = [ b 1 1 b 2 1 . . . b 512 1 ]

我们将输入层与第1层的连接权值表示为矩阵形式,如下所示:
W 1 = [ W 1 , 1 1 W 1 , 2 1 . . . W 1 , 784 1 W 2 , 1 1 W 2 , 2 1 . . . W 2 , 784 1 . . . . . . . . . . . . W 512 , 1 1 W 512 , 2 1 . . . W 512 , 784 1 ]

输入信号也表示为向量形式:
(3) x = [ x 1 x 2 . . . x 584 ]

则第1层神经元的输入信号可以表示矩阵向量的运算,如下所示:
(e000001) z 1 = W 1 x + b 1

我们假设第1层第i个神经元的激活函数为ReLU函数,则其输出为:
(4) a i 1 = R e L U ( z i 1 )

我们同样将第1层所有神经元的输出串在一起形成一个向量,如下所示:
(5) a 1 = R e L U ( z 1 )

将式( e 000001 )代入得到:
(e000002) a 1 = R e L U ( z 1 ) = R e L U ( W 1 x + b 1 )

以上我们讨论的是输入导到第1层,我们可以很容易的将其推广为从第 l 1 到第 l 层:
(e000003) a l = R e L U ( z l ) = R e L U ( W l a l 1 + b l )

我们用 N l 1 代表第 l 1 层神经元数量,用 N l 表示第 l 层神经元数量,则第 l 1 层输出信号 a l 1 R N l 1 ,第 l 1 层到第 l 层连接权值矩阵 W l R N l × N l 1 ,第 l 层偏置值 b l R N l ,第 l 层输入信息 z l R N l ,第 l 层的输出值 a l R N l
前向传播各层计算公式一样,直到我们的输出层(这里是第2层),我们有10个神经元,分别代表取0~9这10个数字的概率,激活函数采用Softmax函数,取概率最大的那个作为整个网络的分类结果。
神经网络的训练可以采用BP算法,这里有很多成熟的算法库可用。但是我们在这里要采用计算的方式来讲解,同时我们在讲解了计算图的基本原理之后,我们会用TensorFlow Eager Execution API,采用可微分编程方式,实现这一经典算法。
采用计算图方式的话,我们需要引入一种网络的另一种表示方式,如图所示:
这里写图片描述
我们将输入信号向量 x 、输入层到第1层的连接权值矩阵 W 1 、第1层神经元偏置值向量 b 1 放在图的最左侧,将这三个值进行如下运算:
(6) z 1 = W 1 x + b 1

经过计算得到节点 z 1 ,我们再经过激活函数得到第1层神经元输出信号 a 1 = R e L U ( z 1 ) ,得到 a 1 节点。
我们将第1层输出信号 a 1 、第1层到第2层连接权值矩阵 W 2 、第2层神经元偏置值向量 b 2 放在一起,经过如下运算:
(7) z 2 = W 2 a 1 + b 2

第2层也就是输出层的激活函数为Softmax函数:
(8) y i = a i 2 = e z i 2 j = 1 N 2 e z j 2

其向量形式表示为:
(9) y i = [ e z 1 2 j = 1 N 2 e z j 2 e z 2 2 j = 1 N 2 e z j 2 . . . e z N 2 2 j = 1 N 2 e z j 2 ]

而我们的希望的结果表示为:
(10) y ^ i = [ 0 0 1 0 . . . 0 ]

如上所示,其用one-hot向量形式表示,即只有正确的数字处为1,其余位置为0,例如本例中,就代表其识别结果应该为2。

  • 向量运算的微分
    我们先来定义向量微分,假设有向量 y R m 和向量 x R n ,微分 y x 定义为:

    (11) y x = [ y 1 x 1 y 1 x 2 . . . y 1 x n y 2 x 1 y 2 x 2 . . . y 2 x n . . . . . . . . . . . . y m x 1 y m x 2 . . . y m x n ]

    这就是Jacobian矩阵 j R m × n

  • 代价函数求导
    我们首先从计算图最右侧开始反向求导,如图所示:
    这里写图片描述
    我们首先处理损失函数,这里我们假设不考虑添加调整项的情况,我们的代价函数取交叉熵(cross entropy)函数,根据交叉熵定义:

    (12) H ( p , q ) = E p ( log q ) = H ( p ) + K L ( p q )

    对离散值情况,交叉熵(cross entropy)可以表示为:
    (13) H ( p , q ) = k = 1 K p ( k ) log q ( k )

    在这里我们设正确值 y ^ 的分布为p,而计算值 y = a 2 的分布为q,假设共有 K = 10 个类别,并且假设第 r 维为正确数字,则代价函数的值为:
    (14) C = H ( p , q ) = k = 1 K p ( k ) log q ( k ) = ( 0 log y 1 + 0 log y 2 + . . . + 1 log y r + . . . + 0 log y 10 ) = log y r

    我们可以将代价函数值视为 R 1 的向量,我们对 y 求偏导,根据Jacobian矩阵定义,结果为 R 1 × N 2 = R 1 × 10 的1行10列的矩阵。结果如下所示:
    (15) C y = [ 0 0 . . . 1 y r . . . 0 ]

    其只有正确数字对应的第r维不为0,其余均为零。
    接下来我们来求: y z 2 ,因为 y 和$\boldsymbol{a}^2均为向量,可以直接使用Jacobian矩阵定义得:

(16) y z 2 = [ y 1 z 1 2 y 1 z 2 2 . . . y 1 z N 2 2 y 2 z 1 2 y 2 z 2 2 . . . y 2 z N 2 2 . . . . . . . . . . . . y N 2 z 1 2 y N 2 z 2 2 . . . y N 2 z N 2 2 ]

式中 N 2 = 10 为第2层即输出层神经元个数。由此可见 y z 2 R N 2 × N 2 ( R 10 × 10 ) 的方阵。
如果我们输出层采用 σ 函数,那么第i个神经元的输出只与其输入有关,与其他神经元无关,因此该矩阵就变为一个对角阵,如下所示:

(17) y z 2 = [ σ ( z 1 2 ) 0 . . . 0 0 σ ( z 2 2 ) . . . 0 . . . . . . . . . . . . 0 0 . . . σ ( z 10 2 ) ]

但是我们在这里使用的是Softmax激活函数,每个输出与该层所有神经元的输入均有关,所以其不是对角阵。
接下来我们计算 z 2 a 1 ,根据Jacobian矩阵定义得:
(e000004) z 2 a 1 = [ z 1 2 a 1 1 z 1 2 a 2 1 . . . z 1 2 a N 1 1 z 2 2 a 1 1 z 2 2 a 2 1 . . . z 2 2 a N 1 1 . . . . . . . . . . . . z N 2 2 a 1 1 z N 2 2 a 2 1 . . . z N 2 2 a N 1 1 ]

我们知道:
z i 2 = W i , 1 2 a 1 1 + W i , 2 2 a 2 1 + . . . + W i , j 2 a j 1 + . . . + W i , N 1 2 a N 1 1

则其对第1层第j个神经元输出信号求导:
z i 2 a j 1 = W i , j 2

所以式(e000004)的最终结果为:
(e000004) z 2 a 1 = [ z 1 2 a 1 1 z 1 2 a 2 1 . . . z 1 2 a N 1 1 z 2 2 a 1 1 z 2 2 a 2 1 . . . z 2 2 a N 1 1 . . . . . . . . . . . . z N 2 2 a 1 1 z N 2 2 a 2 1 . . . z N 2 2 a N 1 1 ] = [ W 1 , 1 2 W 1 , 2 2 . . . W 1 , N 1 2 W 2 , 1 2 W 2 , 2 2 . . . W 2 , N 1 2 . . . . . . . . . . . . W N 2 , 1 2 W N 2 , 2 2 . . . W N 2 , N 1 2 ] = W 2

这个结果与我们直接对 z 2 = W 2 a 1 + b 2 a 1 求导得 W 2 一致。
接下来我们要求的 z 2 W 2 ,这里是向量对矩阵求偏导,结果将是一个张量(Tensor)。
我们可以将连接权值矩阵 W 2 视为由列向量组成:
(18) W 2 = [ w 1 w 2 . . . w N 1 ]

其中第 k 个列向量 w k 为:
(19) w k = [ W 1 , k 2 W 2 , k 2 . . . W N 2 , k 2 ]

这时 z 2 W 2 就可以转化为对一系列连接权值矩阵组成的列向量求导,就变为列向量求导,如下所示:
(20) z 2 W 2 = [ z 2 w 1 z 2 w 2 . . . z 2 w N 1 ]

式中的每一项均为向量对向量的导数,其为Jacobian矩阵,因为 z 2 R N 2 ,且 w k R N 2 ,根据Jacobian矩阵定义, z 2 w k R N 2 × N 2 的矩阵,如下所示:
(21) z 2 w k = [ z 1 2 w 1 k z 1 2 w 2 k . . . z 1 2 w k k . . . z 1 2 w N 2 k z 2 2 w 1 k z 2 2 w 2 k . . . z 2 2 w k k . . . z 2 2 w N 2 k . . . . . . . . . . . . . . . . . . z N 2 2 w 1 k z N 2 2 w 2 k . . . z N 2 2 w k k . . . z N 2 2 w N 2 k ]

由此可知其为 R N 2 × N 2 的方阵,对其中第 i 行第 j 列元素:
(e000005) z i 2 w j k = z i 2 W j , k 2

在式(e000005)中,如果 i j ,此时连接权值不指向第 i 个神经元,因此值为0。当 i = j 时, W i , k 2 是与第1层的第 k 个神经元的输出 a k 1 相乘,因此其导数为 a k 1 ,当 i = j 时对应的是式(e000005)的对角线,因此其为对角阵,而且其值均为 a k 1 ,如下所示:
(22) [ a k 1 0 . . . 0 0 a k 1 . . . 0 . . . . . . . . . . . . 0 0 . . . a k 1 ]

余下部分的偏导求法和上面的方法相同,我们在这里就不再一一列举了。读者可以自行补齐。
到此我们基本把多层感知器模型的计算图讲完了,下一步就是利用TensorFlow Eager Execution API来实现这个模型,我们将在下一篇博文中进行介绍。

猜你喜欢

转载自blog.csdn.net/yt7589/article/details/80747830