谷歌大脑团队发布TensorFlow新功能:交互性提升并加入动态图机制

2017-11-02阅读
http://5b0988e595225.cdn.sohucs.com/images/20171102/d3da29d228494449ada900dcfa52f919.jpeg

作者 | Asim Shankar and Wolff Dobson

译者 | 核子可乐

编辑 | Vincent

AI 前线导语:近日,Google Brain 团队的工程师发布了 TensorFlow 的一项新功能,名为 Eager Execution,一个由运行定义的新接口,可以简化 TensorFlow 的开发。对于用户来说这是个好消息,因为 TensorFlow 终于也拥有了动态图机制。

更多干货内容请关注微信公众号“AI 前线”(ID:ai-front)

今天,我们正式发布面向 TensorFlow 的 Eager Execution 界面。Eager Execution 是一套命令式运行定义界面,其中的操作如同由 Python 调用般立即执行。如此一来,各项操作即可在 TensorFlow 当中快速启动,从而进一步提升研究与开发工作的直观性。

Eager Execution 的优势具体如下:

  • 利用即时运行时错误信息并配合 Python 工具实现快速调试。

  • 利用易于使用的 Python 控制流支持动态模型。

  • 为定制化与高阶梯度提供强大支持。

  • 几乎适用于一切 TensorFlow 操作。

Eager Execution 目前已经作为实验性功能向大家开放,因此我们期待着能够从社区内获得更多反馈,从而指导我们的下一步改进工作。

欲了解更多与 Eager Execution 行相关的知识,我们可以从代码示例入手。这部分内容包含不少技术知识点,可能要求读者朋友对 TensorFlow 拥有一定了解。

使用 Eager Execution

当大家启用 Eager Execution 时,该操作将立即得到执行,且相关值被返回给 Python——无需使用 Session.run ()。举例来说,要将两个矩阵相乘,我们使用以下代码:

import tensorflow as tfimport tensorflow.

contrib.eager as tfetfe.enable_eager_execution()x = [[2.]]m = tf.matmul(x, x)

我们可以轻松利用 print 或者 Python 调试器检查中间结果。

print(m)# The 1x1 matrix [[4.]]

动态模型可以利用 Python 流控制加以构建。以下示例为利用 TensorFlow 的运算操作进行 Collatz 推理:

a = tf.constant(12)counter = 0while not tf.equal(a, 1): if tf.equal(a % 2, 0): a = a / 2 else: a = 3 * a + 1 print(a)

在这里,使用 tf.constant(12) Tensor 对象将把所有数学运算转化为张量运算,因此所有返回值皆将为张量。

梯度

大多数 TensorFlow 用户对于自动化区分都很感兴趣。这是因为每次调用期间都会发生不同的运算,因此我们需要将全部正向运算记录在磁带之上,而后在计算梯度时再次播放。在梯度计算完成后,我们将丢弃该磁带。

如果大家熟悉 autograd 软件包,那么这里使用的 API 与其非常相似。举例来说:

def square(x): return tf.multiply(x, x)grad = tfe.gradients_function(square)print(square(3.)) # [9.]print(grad(3.)) # [6.]

这里的 gradients_function 调用会将 Python 函数 square() 视为一项参数,同时返回一个 Python 可调用函数——此函数负责计算出 square() 的偏导数,结果则作为其输入内容。如此一来,为了在 3.0 条件下计算出 square() 导数,这里需要调用 grad(3.0),结果为 6。

同样的 gradients_function 调用亦可用于获取平方的二阶导数:

gradgrad =

tfe.gradients_function(

lambda x: grad(x)[0])print(gradgrad(3.)) # [2.]

需要强调的是,控制流可能导致不同的运算操作,具体如下例所示。

def abs(x): return x if x > 0. else -xgrad = tfe.gradients_function(abs)print(grad(2.0)) # [1.]print(grad(-2.0)) # [-1.]

自定义梯度

用户可能希望在某一运算或者函数中使用自定义梯度。这种处理方式适用于多种场景,具体包括为一系列运算提供更为有效或者数值更为稳定的梯度。

以下示例展示了自定义梯度的使用方式。让我们首先来看函数 log(1 + e x ) ,其通常使用于交叉熵与对数似然性计算当中。

def log1pexp(x): return tf.log(1 + tf.exp(x))grad_log1pexp =

tfe.gradients_function(log1pexp)# 当 x=0 时,此梯度计算效果正常。print(grad_log1pexp(0.))# [0.5]# 然而当 x=100 时,由于数值不稳定,

其会返回一条‘nan’。print(grad_log1pexp(100.))# [nan]

我们可以在上述函数中使用自定义梯度,从而以分析方式简化梯度表达式。请注意以下梯度函数实现如何复用正向传递过程中的计算表达式 (tf.exp(x) )。通过避免冗余计算,这种作法提高了梯度计算的执行效率。

@tfe.custom_gradientdef log1pexp(x): e = tf.exp(x) def grad(dy): return dy * (1 - 1 / (1 + e)) return tf.log(1 + e), gradgrad_log1pexp =

tfe.gradients_function(log1pexp)# 当 x=0 时,梯度效果与原先一致。print(grad_log1pexp(0.))# [0.5]# 现在当 x=100 时,梯度计算仍然效果正常。print(grad_log1pexp(100.))# [1.0]

构建模型

我们可以将各模型进行分类。下面这套模型类创建出一套(简单的)双层网络,其可对标准 MNIST 手写数字进行分类。

class MNISTModel(tfe.Network): def __init__(self): super(MNISTModel, self).__init__() self.layer1 =

self.track_layer(

tf.layers.Dense(units=10)) self.layer2 =

self.track_layer(

tf.layers.Dense(units=10)) def call(self, input): """Actually runs the model.""" result = self.layer1(input) result = self.layer2(result) return result

我们建议大家在 tf.layvers 当中使用类(而非函数),因为类能够创建并容纳模型参数(变量)。变量生命周期与层对象的生命周期相关,因此请确保对其进行追踪。

为什么要使用 tfe.Network?因为 Network 属于层容器,而其本身则为 tf.layer.Layer,其允许各 Network 对象嵌入至其它 Network 对象当中。另外,其还包含可帮助进行检查、保存与恢复的各类实用工具。

即使不对模型进行训练,我们亦能够强制对其调用并检查输出结果:

# 让我们首先构建一份空白的输入图像model = MNISTModel()batch = tf.zeros([1, 1, 784])print(batch.shape)# (1, 1, 784)result = model(batch)print(result)# tf.Tensor([[[ 0. 0., ...., 0.]]],

shape=(1, 1, 10), dtype=float32)

请注意,我们不需要使用任何占位符或者会话。当我们首次传递输入内容时,该层参数的大小即被设定完毕。

要进行模型训练,我们需要定义一项有待优化的丢失函数、计算梯度,而后使用优化器对各变量进行更新。首先来看丢失函数:

def loss_function(model, x, y): y_ = model(x) return

tf.nn.softmax_cross_entropy_with_logits(

labels=y, logits=y_)

接下来是我们的训练循环:

optimizer =

tf.train.GradientDescentOptimizer(

learning_rate=0.001)for (x, y) in tfe.Iterator(dataset): grads =

tfe.implicit_gradients(

loss_function)(model, x, y) optimizer.apply_gradients(grads)

implicit_gradients() 会计算 loss_function 的导数,并在计算过程中使用全部对应 TensorFlow 变量。

我们可以将这一计算过程迁移至 GPU 处——这也是我们使用 TensorFlow 时采取的常规方式:

with tf.device("/gpu:0"): for (x, y) in tfe.Iterator(dataset): optimizer.minimize(lambda:

loss_function(model, x, y))

请注意,我们简化了丢失函数的存储并直接调用 optimizer.minimize ,但大家也可以使用以上 apply_gradients() 方法 ; 二者的效果完全一致。

配合图形使用 Eager Execution

Eager Execution 机制除了能够提升开发与调试工作的交互性,在 TensorFlow 图形的分布式训练、性能优化以及生产部署领域同样拥有着良好的表现。

当 Eager Execution 启用时所执行的运算代码能够在此项功能未被开启时构成一份图形,用以描述整个计算过程。要将您的模型转化为图形,大家只需要在新的 Python 会话中运行同样的代码即可(请确认未启用 Eager Execution 功能)。模型变量的值将被保存,且可立足检查点进行恢复,这意味着我们将能够在不同及早(命令式)与图形(声明式)编程模式之间往来切换。通过这种方式,大家将能够轻松导出利用 Eager Execution 机制开发出的模型,以供生产部署所用。

在不久的未来,我们还将提供更多实用工具,从而有选择地将模型中的特定部分转化为图形。通过这种方式,您将能够将部分计算(例如自定义 RNN 单元中的某些部分)进行融合,从而在实现高性能水平的同时,保持 Eager Execution 的灵活性与可读性。

我的代码需要作出怎样的变更?

当前 TensorFlow 用户应该能够轻松上手 Eager Execution 功能。只有少数需要使用特定 API 的用户除外 ; 而大多数现有 API 与运算完全能够在启用 Eager Execution 功能的情况下正常运作。以下为相关注意事项:

  • 与 TensorFlow 一样,我们建议大家尽快从队列机制转换为利用 tf.data 进行输入内容处理。其更易于使用,且速度通常更快。欲了解更多帮助信息,请点击此处参阅博文与说明文档。

  • 使用面向对象层,例如 tf.layer.Conv2D() 或者 Keras 层 ; 其能够为各变量提供明确存储机制。

  • 对于大多数模型,大家可以编写代码以保证其在 Eager Execution 与图形构建模式下正常工作。但也存在一些例外,例如对于使用 Python 控制流的动态模型,您可能需要变更基于输入内容的计算方式。

  • 一旦您调用 tfe.enable_eager_execution(),其将无法被关闭。为了获取图形活动,您需要启动一个新的 Python 会话。

现在开始与未来展望

我们目前发布的只是 Eager Execution 功能的预览版本,因此您可能会发现一些问题。要马上体验,您需要:

  • 安装 TensorFlow 的 nightly build。

  • 查看 README (其中包含各项已知问题)。

  • 参阅 Eager Execution 的用户指南以了解更多详细说明。

  • 在 GitHub 中浏览 Eager Execution 示例。

  • 关注 changelog # 以及时获取更新资讯。

我们对于 Eager Execution 功能感到振奋,也期待着大家能够试用并提供宝贵的反馈意见!我们静候您的点评。

用户指南:

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/g3doc/guide.md

GitHub 地址:

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager

原文链接

https://research.googleblog.com/2017/10/eager-execution-imperative-define-by.html