TensorFlow可微分编程实践3---向量微分和Jacobian矩阵
程序员文章站
2024-01-19 09:21:40
...
在这篇博文中,我们将利用TensorFlow Eager Execution API来实现一个完整多层感知器(MLP)模型。在具体实现多层感知器模型之前,我们首先来看,怎样用TensorFlow Eager Execution API来求向量与矩阵运算的导数。
我们知道在多层感知器模型中,最基本的运算是由第层输出信号求出第层神经元的输入信号,公式如下所示:
为了下面讨论方便,我们假设第层有3个神经元,第层有2个神经元,式(3.2.001)中的各个值定义如下。
第层输出信号:
第层到第层连接权值矩阵:
第层偏置值:
为了进行学习,我们需要求出以下导数:、、,我们分别来进行讨论。
我们首先来看第一项,根据Jacobian矩阵定义得:
我们接下来再来求对第层偏置求微分:
下面是一个向量对矩阵求偏导,而我们对这个操作没有定义,所以我们需要以一种变通的方式来进行,我们将视为由的行向量组成:
其实是指向第层第个神经元所有连接权值组成的向量。
有了式(3.2.007)的定义,我们就可以将视为向量,这样根据Jacobian矩阵定义:
与前面不同的是,式(3.2.008)的矩阵中的每个元素都是一个标量对向量的求偏导,根据我们上篇博文介绍,标量对向量求偏导,结果为一个行向量,我们以为例进行讨论。
如果时,是指向第层第个神经元的,不与第行第个神经元相接,因此所有偏层均为0,如下所示:
如果时,是由指向第层第个神经元的所有连接权值组成的,根据输入信号定义可得:
因此式(3.2.008)矩阵的每个元素为指向纸里的向量,当不在对角线上时,所有元素值为零,当在对角线上时,元素为第层输出值。
下面我们来看,怎样通过TensorFlow Eager Excecution API来求出这些偏导的值。
@tf.custom_gradient
def f002(W, a, b):
def grad_fn(dy):
ws = W.shape
pz_pW = np.zeros((2, 2, 3))
a1 = tf.reshape(a, [3])
for idx in range(ws[0]):
pz_pW[idx][idx] = a1
diag = tf.ones([W.shape[0]])
d_b = tf.matrix_diag(diag)
return tf.constant(pz_pW), W, d_b
return tf.matmul(W, a) + b, grad_fn
def test001(args={}):
tf.enable_eager_execution()
tfe = tf.contrib.eager
W = tf.constant([[4.0, 5.0, 6.0],[7.0, 8.0, 9.0]])
a = tf.reshape(tf.constant([1.0, 2.0, 3.0]), [3, 1])
i_debug = 2
if 1 == i_debug:
f003(W, a)
return
b = tf.reshape(tf.constant([1001.0, 1002.0]), [2, 1])
z = f002(W, a, b)
print('z=Wa+b={0}'.format(z))
grad_f1 = tfe.gradients_function(f002)
dv = grad_f1(W, a, b)
print('pz_pW={0}'.format(dv[0].numpy()))
print('pz_pa={0}'.format(dv[1].numpy()))
print('pz_pb={0}'.format(dv[2].numpy()))
print('v0.0.1')
在上面的代码中,需要说明的是第16行,将向量定义为3行1列的矩阵形式,这是为了与连接权值矩阵做矩阵相乘。
在前向计算阶段,可以直接调用TensorFlow的矩阵乘法和加法,我们就可以取得正确的结果,但是在反向求导阶段,如果我们直接利用TensorFlow进行求导,例如求时,TensorFlow返回结果维数与相同,而Jacobian矩阵的维数为,所以我们需要自己定义求导函数,根据上面的理论分析,是一个3维的张量,维数为,我们可以将其视为一个的矩阵,矩阵中每个元素均为一个数组,长度为,且除对角线上的元素外,数组元素为0,而对角线上的元素,数组元素为第导神经元的输出值。为第层到第层的连接权值矩阵,而为的单位阵,运行结果如下所示:
至此我们完成了第层到第层正向传输和反向求导工作,基本上按照数学理论要求,我们就可以完全处理一个多层感知器模型了。