在传统的TensorFlow开发中,我们需要首先通过变量和Placeholder来定义一个计算图,然后启动一个Session,通过TensorFlow引擎来执行这个计算图,最后给出我们需要的结果。相信大家在入门阶段,最困惑的莫过于想要打印某些向量或张量的值,在Session之外或未执行时,其值不可打印的问题。TensorFlow采用这种反人性的设计方式,主要是为了生成基于符号的计算图,然后通过C++的计算图执行引擎,进行各种性能优化、分布式处理,从而获取优秀的运行时性能。与此形成对照的是以PyTorch为代表的动态图方式,其不用生成基于符号表示的计算图,直接计算结果,与我们平常编程的处理方式类似,无疑这种方式学习曲线会低很多。TensorFlow实际上也注意到了这一问题,在2017年11月,推出的Eager Execution就是这种动态图机制在TensorFlow中的实现。目前虽然Eager Execution在性能上还没有达到静态计算图的效率,但是由于其编程调试的方便性,会在实际应用中得到越来越广泛的应用。
大家还记得深度学习大佬Yann Lecun在前一段时间说的“深度学习已死,可微编程永生”的话吧?实际上Yann Lecun所说的可微编程(Differentiable Programming)还处在非常早期的阶段,Lecun的意思是说这种编程范式具有比深度学习更好的灵活性,有希望解决更为复杂的问题,提醒大家重点关注这一领域。而可微编程这种范式,在TensorFlow的Eager Execution下是可以比较方便实现的。
def tfsess1(args={}):
v1 = tf.constant([[1, 2], [3, 4]])
v2 = tf.constant([[5, 6], [7, 8]])
v3 = tf.matmul(v1, v2)
with tf.Session() as sess:
rst = sess.run([v3])
print('v3:{0}'.format(rst))
运行结果如下所示:
由上面的代码可以看出,只有启动Session后,我们才能看到矩阵乘法的结果。正是这一点,是许多初学者非常不适应的地方。
def tfee1(args={}):
tf.enable_eager_execution()
v1 = [[1, 2],[3, 4]]
v2 = [[5, 6],[7, 8]]
v3 = tf.matmul(v1, v2)
print('v3={0}'.format(v3))
print('v3 type:{0}'.format(type(v3)))
print('v3 shape:{0}'.format(v3.shape))
print('v3 dtype:{0}'.format(v3.dtype))
print('v3=>ndarray:{0}'.format(v3.numpy()))
运行结果如下所示:
由上面的代码可以看出,我们直接采用普通的程序形式,就可以求出矩阵乘法的结果,而且TensorFlow中的Tensor和numpy中的ndarray可以互相无缝转换,非常方便使用。更加有用的是,我们还可以利用使用机器中的GPU。
我们将 x2 x 2 视为两个独立的变量x相乘。我们先来看正向传播,如下图所示:
我们将 x1=3.0 x 1 = 3.0 和 x2=3.0 x 2 = 3.0 代入左边两个节点,然后根据计算图到达右边的y节点,执行乘法操作 y=x1∗x2 y = x 1 ∗ x 2 ,最后得到最终的计算结果,与函数 x2 x 2 的计算值相同。
我们接下来看求导的反向传播,如下图所示:
因为在我们的计算图中的公式为 y=x1∗x2 y = x 1 ∗ x 2 ,所以 dydx1=x2=x d y d x 1 = x 2 = x ,同理 dydx2=x1=x d y d x 2 = x 1 = x ,我们将导数结果写在对应的边上。根据计算图求导规则,对从最终节点到输入节点的全部路径,每条路径上边的导数值相乘,再将所有路径的导数值相加,根据这个计算图,可以得到如下算式:
def f1(x):
return x**2
def ad1(args={}):
tf.enable_eager_execution()
tfe = tf.contrib.eager
grad_f1 = tfe.gradients_function(f1)
threshold = 0.001
delta_x = 0.0001
x = 3.0
done = False
while not done:
rst = grad_f1(x)
dval = rst[0]
if dval > threshold:
x -= delta_x
elif dval < -threshold:
x += delta_x
else:
done = True
y = f1(x)
rst = grad_f1(x)
print('x={0}, y={1}; d={2}!'.format(x, y, rst[0]))
运行结果如下所示:
需要指出的是,我们这里给出的代码实现,绝对是效率相当低的一种,如果我们真的想求极值,我们应该采用牛顿法等,收敛速度会成数量级的提高,具体可以参见我写的书《深度学习算法实践》。
我们在这篇博文里讨论了自动微分概念,这是可微编程的基础,在下面的博文中我们用这种新技术来实现各种机器学习和深度学习算法。