tensorflow2.0 之 快速入门(上)

TensorFlow虽是深度学习领域最广泛使用的框架,但是对比PyTorch这一动态图框架,采用静态图(Graph模式)的TensorFlow确实是难用。好在最近TensorFlow支持了eager模式,对标PyTorch的动态执行机制。更进一步地,Google在最近推出了全新的版本TensorFlow 2.0,2.0版本相比1.0版本不是简单地更新,而是一次重大升级(虽然目前只发布了preview版本)。简单地来说,TensorFlow 2.0默认采用eager执行模式,而且重整了很多混乱的模块。毫无疑问,2.0版本将会逐渐替换1.0版本,所以很有必要趁早入手TensorFlow 2.0。这篇文章将简明扼要地介绍TensorFlow 2.0,以求快速入门。

Eager执行

import tensorflow as tf
import numpy as np
import timeit
#Eager执行
x = tf.ones((2, 2), dtype=tf.dtypes.float32) #创建一个所有的参数为1的tensor对象,形状为2×2的矩阵
y = tf.constant([[1, 2], [3, 4]], dtype=tf.dtypes.float32)
z = tf.matmul(x, y)
print(z)
print(z.numpy())
在eager执行下,每个操作后的返回值是tf.Tensor,其包含具体值,不再像Graph模式下那样只是一个计算图节点的符号句柄。由于可以立即看到结果,这非常有助于程序debug。更进一步地,调用tf.Tensor.numpy()方法可以获得Tensor所对应的numpy数组。

eager执行的另外一个好处是可以使用Python原生功能,比如下面的条件判断:

random_value = tf.random.uniform([], 0, 1) #从均匀分布中返回随机值,在0~1之间,形状为【】,即一个数
x = tf.reshape(tf.range(0, 4), [2, 2]) #tf.range产生一个0~4的序列,reshape将这个序列变为一个2*2的矩阵
print(random_value)
if random_value.numpy() > 0.5:
    y=tf.matmul(x, x)
else:
    y=tf.add(x, x)
print(y)
这种动态控制流主要得益于eager执行得到Tensor可以取出numpy值,这避免了使用Graph模式下的tf.cond和tf.while等算子。

另外一个重要的问题,在egaer模式下如何计算梯度。在Graph模式时,我们在构建模型前向图时,同时也会构建梯度图,这样实际喂数据执行时可以很方便计算梯度。但是eager执行是动态的,这就需要每一次执行都要记录这些操作以计算梯度,这是通过tf.GradientTape来追踪所执行的操作以计算梯度,下面是一个计算实例:

w = tf.Variable([[3.0]])
with tf.GradientTape() as tape:
    loss = w * w + 2. * w + 5.
grad = tape.gradient(loss, w)  #gradient用来对loss求导,即2×w+2
print(grad)

对于eager执行,每个tape会记录当前所执行的操作,这个tape只对当前计算有效,并计算相应的梯度。PyTorch也是动态图模式,但是与TensorFlow不同,它是每个需要计算Tensor会拥有grad_fn以追踪历史操作的梯度。

TensorFlow 2.0引入的eager提高了代码的简洁性,而且更容易debug。但是对于性能来说,eager执行相比Graph模式会有一定的损失。这不难理解,毕竟原生的Graph模式是先构建好静态图,然后才真正执行。这对于在分布式训练、性能优化和生产部署方面具有优势。但是好在,TensorFlow 2.0引入了tf.function和AutoGraph来缩小eager执行和Graph模式的性能差距,其核心是将一系列的Python语法转化为高性能的graph操作。

 

AutoGraph
AutoGraph在TensorFlow 1.x已经推出,主要是可以将一些常用的Python代码转化为TensorFlow支持的Graph代码。一个典型的例子是在TensorFlow中我们必须使用tf.while和tf.cond等复杂的算子来实现动态流程控制,但是现在我们可以使用Python原生的for和if等语法写代码,然后采用AutoGraph转化为TensorFlow所支持的代码,如下面的例子:

def square_if_positive(x):
    if x > 0:
        x = x * x
    else:
        x = 0.0
    return x
# eager模式
print('Eager results: %2.2f, %2.2f' % (square_if_positive(tf.constant(9.0)),
                                       square_if_positive(tf.constant(-9.0))))
#graph模式
tf_square_if_positive = tf.autograph.to_graph(square_if_positive)
with tf.Graph().as_default():
    g_out1 = tf_square_if_positive(tf.constant(9.0))
    g_out2 = tf_square_if_positive(tf.constant(-9.0))
    with tf.compat.v1.Session() as sess:
        print('Graph results: %2.2f, %2.2f\n' % (sess.run(g_out1),sess.run(g_out2)))

上面我们定义了一个square_if_positive函数,它内部使用的Python的原生的if语法,对于TensorFlow 2.0的eager执行,这是没有问题的。然而这是TensorFlow 1.x所不支持的,但是使用AutoGraph可以将这个函数转为Graph函数,你可以将其看成一个常规TensorFlow op,其可以在Graph模式下运行(tf2 没有Session,这是tf1.x的特性,想使用tf1.x的话需要调用tf.compat.v1)。大家要注意eager模式和Graph模式的差异,尽管结果是一样的,但是Graph模式更高效。

AutoGraph支持很多Python特性,比如循环:
 

def sum_even(items):
    s = 0
    for c in items:
        if c % 2 > 0:
            continue #continue用来跳出本次循环,而break用来跳出整个循环
        s += c
    return s
print('Eager result: %d' % sum_even(tf.constant([10, 12, 15, 20])))
tf_sum_even = tf.autograph.to_graph(sum_even)
with tf.Graph().as_default(), tf.compat.v1.Session() as sess:
    print('Graph result: %d\n\n' % sess.run(tf_sum_even(tf.constant([10, 12, 15, 20]))))

此外,要注意的一点是,经过AutoGraph转换的新函数是可以eager模式下执行的,但是性能却并不会比转换前的高,你可以对比:

x = tf.constant([10, 12, 15, 20])
print('Eager at original code:', timeit.timeit(lambda:sum_even(x), number=100))
print('Eager at autograph code:', timeit.timeit(lambda:tf_sum_even(x),number=100))
with tf.Graph().as_default(), tf.compat.v1.Session() as sess:
    graph_op = tf_sum_even(tf.constant([10, 12, 15, 20]))
    sess.run(graph_op)
    print("Graph at autograph code:", timeit.timeit(lambda:sess.run(graph_op), number=100))


从结果上看,Graph模式下的执行效率是最高的,原来的代码在eager模式下效率次之,经AutoGraph转换后的代码效率最低。所以,在TensorFlow 2.0,我们一般不会直接使用tf.autograph,因为eager执行下效率没有提升。要真正达到Graph模式下的效率,要依赖tf.function这个更强大的利器。

你可能感兴趣的:(tensorflow2.0 之 快速入门(上))