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

2017-12-18
作者 |  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 tf
import tensorflow.
        contrib.eager as tfe

tfe.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 = 0
while 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 -x

grad = 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_gradient
def log1pexp(x):
  e = tf.exp(x)
  def grad(dy):
    return dy * (1 - 1 / (1 + e))
  return tf.log(1 + e), grad
grad_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

没有评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注