HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

关于即将发布的 TensorFlow 2.0,你需要知道这几件事

  • 2018-11-12
  • 本文字数:8007 字

    阅读完需:约 26 分钟

关于即将发布的TensorFlow 2.0,你需要知道这几件事

AI 前线导读: 对于最流行的机器学习框架来说,TensorFlow 2.0 将是一个重要的里程碑:大量的更改即将到来,所有的一切都以人人可以使用 ML 为目标。但是,这些更改要求老用户完全重新学习如何使用框架:本文介绍了 1.x 和 2.x 版本之间的所有(已知的)差异,主要是思维方式的改变,并着重介绍了新实现的优缺点。


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


对于最流行的机器学习框架来说,TensorFlow 2.0 将是一个重要的里程碑:大量的更改即将到来,所有的一切都以人人可以使用 ML 为目标。但是,这些更改要求老用户完全重新学习如何使用框架:本文介绍了 1.x 和 2.x 版本之间的所有(已知的)差异,主要是思维方式的改变,并着重介绍了新实现的优缺点。


对于新手来说,本文也是一个很好的起点:现在就开始以 TensorFlow 2.0 的方式思考,这样你就不必重新学习一个新的框架(除非 TensorFlow 3.0 发布)。

TensorFlow 2.0:为什么?何时?

TensorFlow 2.0 的核心思想是使 TensorFlow 更易于学习和应用。


公告邮件列表中,谷歌大脑工程师 Martin Wicke 对 TensorFlow 2.0 做了初步介绍。简而言之:


  • Eager Execution 将是 2.0 的核心特性。它将用户对编程模型的期望与 TensorFlow 实践更好地结合起来,使 TensorFlow 更易于学习和应用。

  • 支持更多的平台和语言,通过交换格式的标准化和 API 的对齐,改进这些组件之间的兼容性和对等性。

  • 删除弃用的 API 并减少重复,避免给用户带来混乱。

  • 公开的 2.0 设计过程:社区现在可以与 TensorFlow 开发人员合作,使用TensorFlow讨论组讨论新特性。

  • 兼容性和延续性:提供一个与 TensorFlow 1.x 兼容的模块,这意味着 TensorFlow 2.0 将有一个包含所有 TensorFlow 1.x API 的模块。

  • On-disk 兼容性:TensorFlow 1.x 中导出的模型(检查点和冻结模型)将与 TensorFlow 2.0 兼容,只需要重命名某些变量。

  • tf.contrib:完全删除。大型的维护中的模块将移动到独立的存储库;未使用和未维护的模块将被删除。


实际上,如果你是一个 TensorFlow 新手,那么你很幸运。如果像我一样,从 0.x 版本开始使用 TensorFlow,那么必须重写所有的代码库(与从 0.x 向 1.x 转换不同,更改的地方特别多);不过,TensorFlow 的作者声称,将会发布一个转换工具来帮助转换。然而,转换工具并不完美,需要人工干预。


此外,你必须改变你的思维方式;这很有挑战性,但每个人都喜欢挑战,不是吗?


让我们面对这个挑战,从第一个巨大的差异开始详细查看这次新版本设计的更改:删除tf.get_variablef.variable_scopetf.layers和强制转换为基于 Keras 的方法,使用tf.keras


请注意,发布日期还没有确定。但是,从 TensorFlow 讨论组中,我们知道,2018 年底发布可能会发布一个预览版本,2.0 的正式版本可能会在 2019 年春天发布。


因此,最好是在 RFC 被接受后立即更新所有现有的代码库,以便顺利过渡到这个新的 TensorFlow 版本。

Keras(OOP)与 TensorFlow 1.x 比较

RFC:TensorFlow 2.0中的变量”已经被接受。这个 RFC 可能是对现有代码库影响最大的一个,而且,TensorFlow 的老用户需要换一种新的思维方式。


正如文章“使用Go来理解TensorFlow”中描述的那样,每个变量在计算图中都有一个唯一的名称。


作为一个早期的 TensorFlow 用户,我习惯于按照以下模式设计我的计算图:


  1. 哪些操作连接了变量节点?将图定义为多个连接的子图。为了定义不同图的变量,在单独的tf.variable_scope中定义每个子图。在不同的范围内定义子图,可以在Tensorboard中得到一个清晰的图表示。

  2. 在相同的执行步骤中,我是否需要多次使用子图?为了避免创建一个以_n 为前缀的新图,一定要利用tf.variable_scopereuse参数。

  3. 图已经定义了?创建变量初始化 op(看看tf.global_variables_initializer()调用了多少次?)

  4. 把图加载到 Session 中并运行。


在我看来,示例“如何在 TensorFlow 中实现简单的GAN”可以更好地说明这些步骤的合理性。

通过 GAN 了解 TensorFlow 1.x

GAN 判别器 D 必须使用tf.variable_scope reuse参数定义,因为,我们希望首先给 D 提供真样本,然后提供假样本,最后计算 D 相关参数的梯度。


相反,生成网络 G 在一次迭代中从未使用两次,因此,不需要担心其变量重用。


def generator(inputs):    """生成器网络    Args:        inputs: 一个(None, latent_space_size) tf.float32张量    Returns:        G: 生成器输出节点    """a    with tf.variable_scope("generator"):        fc1 = tf.layers.dense(inputs, units=64, activation=tf.nn.elu, name="fc1")        fc2 = tf.layers.dense(fc1, units=64, activation=tf.nn.elu, name="fc2")        G = tf.layers.dense(fc1, units=1, name="G")    return G
def discriminator(inputs, reuse=False): """判别器网络 Args: inputs: 一个(None, 1) tf.float32张量 reuse: Python布尔值, 说明是希望重用(True)还是声明(False) Returns: D: 判别器输出节点 """ with tf.variable_scope("discriminator", reuse=reuse): fc1 = tf.layers.dense(inputs, units=32, activation=tf.nn.elu, name="fc1") D = tf.layers.dense(fc1, units=1, name="D") return D
复制代码


当调用这两个函数时,在默认图中定义了两个不同的子图,每个子图都有自己的作用域(生成器或判别器)。请注意,这个函数返回的是定义子图的输出张量,而不是图本身。


为了共用 D 图,我们定义了 2 个输出(真和假),并定义了训练 G 和 D 所需的损失函数。


# 定义真输入,一组从真实数据的抽样值real_input = tf.placeholder(tf.float32, shape=(None,1))# 定义判别器网络及其参数D_real = discriminator(real_input)
# 任意大小的噪声先验向量latent_space_size = 100# 定义输入噪声,定义生成器input_noise = tf.placeholder(tf.float32, shape=(None,latent_space_size))G = generator(input_noise)
# 现在,我们已经定义了生成器输出G,我们可以把它提供给D的输入# `discriminator`的这个调用不会定义一个新图,但会**重用**之前定义的变量
复制代码


最后要做的只是定义训练 D 和 G 所需的 2 个损失函数和 2 个优化器。


D_loss_real = tf.reduce_mean(    tf.nn.sigmoid_cross_entropy_with_logits(logits=D_real, labels=tf.ones_like(D_real)))
D_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake, labels=tf.zeros_like(D_fake)))
# D_loss:当第一次调用时会使用D_loss_real做一次前向传递# 然后使用D_loss_fake再做一次,共享同样的D参数。D_loss = D_loss_real + D_loss_fake
G_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake, labels=tf.ones_like(D_fake)))
复制代码


损失函数很容易定义。对抗式训练的特点是首先要用真样本和由 G 生成的样本对 D 进行训练,然后用 D 评估的结果作为输入信号对对抗性的 G 进行训练。


对抗性训练的这两个训练步骤需要单独运行,但是,我们在同一个图中定义了模型,我们不想在训练 D 时更新 G 变量,反之亦然。


这样,由于我们在默认图中定义了每个变量,所以每个变量都是全局的,我们必须使用两个不同的列表获得正确的变量,并确保定义了优化器,以便计算梯度,并仅对恰当的子图应用更新。


# 获得D和G变量D_vars = tf.trainable_variables(scope="discriminator")G_vars = tf.trainable_variables(scope="generator")
# 定义优化器和训练操作train_D = tf.train.AdamOptimizer(1e-5).minimize(D_loss, var_list=D_vars)train_G = tf.train.AdamOptimizer(1e-5).minimize(G_loss, var_list=G_vars)
复制代码


好了,我们到了第三步,图定义最后要做的是定义变量初始化 op:


init_op = tf.global_variables_initializer()
复制代码


优点 / 缺点


图已经正确定义,当在训练循环和会话中使用时,它可以正常工作。然而,从软件工程的角度来看,有一些特性值得注意:


  1. 使用上下文管理器tf.variable_scope更改由 tf.layers 定义的变量的(完整)名称:在不同的变量作用域内,对 tf.layers.* 方法的相同调用会在不同的变量作用域内定义一组新的变量。

  2. 布尔标识 reuse 可以完全改变 tf.layers.*方法任何调用的行为(定义或重用)。

  3. 每个变量都是全局的: tf.layers 调用tf.get_variable(在 tf.layers 内部使用)定义的变量可以从任何地方访问:上面使用 tf.trainable_variables(prefix)来获得两个变量列表就是对这种情况的一个很好说明。

  4. 定义子图并不简单:只是调用 discriminator 并不能获得一个新的、独立的判别器。有点违反直觉。

  5. 子图定义的返回值不是其唯一的输出向量,其中也没有包含图的所有信息(虽然可以追溯到输入,但并不简单)。

  6. 定义变量初始化 op 太无趣(但这刚刚通过 tf.train.MonitoredSessiontf.train.MonitoredTrainingSession得到了解决)。


这 6 条大概全是缺点。


我们使用 TensorFlow 1.x 的方式定义了 GAN:下面让我们迁移到 TensorFlow 2.0。

通过 GAN 了解 TensorFlow 2.x

如前一节所述,在 TensorFlow 2.x 中,思维方式改变了。tf.get_variabletf.variable_scopetf.layers被移除并强制转换为基于 Keras 的方法,使用tf.keras会迫使 TensorFlow 开发人员改变其思维方式。


我们必须使用 tf.keras 定义生成器 G 和判别器 D:这将为我们提供变量共享特性,我们曾经使用该特性来定义 D,但是底层实现的方式不同。


请注意:tf.layers 将被移除,因此,请现在就开始使用 tf.keras 定义你的模型,这是为 2.x 做准备所必须的。


def generator(input_shape):    """生成器王国    Args:        input_shape:期望的输入形状(如: (latent_space_size))    Returns:        G:生成器模型    """    inputs = tf.keras.layers.Input(input_shape)    net = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name="fc1")(inputs)    net = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name="fc2")(net)    net = tf.keras.layers.Dense(units=1, name="G")(net)    G = tf.keras.Model(inputs=inputs, outputs=net)    return G
def discriminator(input_shape): """判别器网络 Args: input_shape:期望的输入形状(如: (latent_space_size)) Returns: D:判别器模型 """ inputs = tf.keras.layers.Input(input_shape) net = tf.keras.layers.Dense(units=32, activation=tf.nn.elu, name="fc1")(inputs) net = tf.keras.layers.Dense(units=1, name="D")(net) D = tf.keras.Model(inputs=inputs, outputs=net) return D
复制代码


看下该方法的不同:生成器和判别器都返回一个 tf.keras.Model,而不仅仅是一个输出张量。


这意味着,使用 Keras,我们可以实例化我们的模型,并在源代码的不同部分使用相同的模型,我们可以有效地使用模型变量,而无需定义以_n 为前缀的新子图。实际上,和 1.x 版本不同,我们只定义一个 D 模型,但使用了两次。


# 定义真输入,一组从真实数据抽取的值real_input = tf.placeholder(tf.float32, shape=(None,1))
# 定义判别器模型D = discriminator(real_input.shape[1:])
# 设置任意形状的噪声先验向量latent_space_size = 100# 定义输入噪声形状,定义生成器input_noise = tf.placeholder(tf.float32, shape=(None,latent_space_size))G = generator(input_noise.shape[1:])
复制代码


同样:不需要像前面那样定义 D_fake,也不需要在定义图时提前考虑变量共享问题。


现在,我们可以继续定义 G 和 D 的损失函数了。


D_real = D(real_input)D_loss_real = tf.reduce_mean(    tf.nn.sigmoid_cross_entropy_with_logits(logits=D_real, labels=tf.ones_like(D_real)))
G_z = G(input_noise)
D_fake = D(G_z)D_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake, labels=tf.zeros_like(D_fake)))
D_loss = D_loss_real + D_loss_fake
G_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=D_fake, labels=tf.ones_like(D_fake)))
复制代码


到目前为止还不错。最后要做的是定义两个分别优化 D 和 G 的优化器。因为我们用的是 tf.keras,所以不需要手动创建需要更新的变量列表,因为 tf.keras.Models 对象本身具有这个属性:


# 定义优化器和训练操作train_D = tf.train.AdamOptimizer(1e-5).minimize(D_loss, var_list=D.trainable_variables)train_G = tf.train.AdamOptimizer(1e-5).minimize(G_loss, var_list=G.trainable_variables)
复制代码


我们已经准备好了:我们到达了第 3 步,由于我们仍然在使用静态图模式,我们必须定义变量初始化 op:


init_op = tf.global_variables_initializer()
复制代码


优点/缺点


  • 从 tf.layers 转换到 tf.keras 很简单:所有的 tf.layers 方法都有对等的 tf.keras.layers 方法。

  • f.keras.Model 完全解决了变量重用以及图重定义的困扰。

  • tf.keras.Model 不是一个输出张量,但是一个包含自有变量的完整模型。

  • 我们还是必须初始化所有变量,但就像我们前面说过的那样,tf.train.MonitoredSession 可以帮我们完成。


不管是在 TensorFlow 1.x 中,还是在 2.x 中,GAN 示例都是首先使用“旧”的图定义范式,然后在会话中执行(不管是现在还是将来,这都是一个很好且有效的范式,并且是——个人观点——最好的)。


然而,TensorFlow 2.x 的另一个大变化是让 Eager 模式成为默认执行模式。在 TensorFlow 1.x 中,我们必须显式地启用 Eager Execution,而在 TensorFlow 2.x 中,我们要做相反的事情。

Eager 模式优先

以下是Eager Execution指南的解释:


TensorFlow 的 Eager Execution 是一种必要的编程环境,它可以立即评估操作,而不需要构建图:操作返回具体的值,而不是构建一个计算图并稍后运行。这使得开始 TensorFlow 入门和模型调试变得很容易,同时也减少了模板文件。请按照本指南在交互式 Python 解释器中运行下面的代码示例。


Eager Execution 是一个灵活的机器学习研究和试验平台,提供以下特性:


  • 直观的接口——自然地构造代码并使用 Python 数据结构。快速迭代小模型和小样本。

  • 更易于调试——直接调用 ops 检查正在运行的模型和测试更改。使用标准的 Python 调试工具进行即时错误报告。

  • 自然的控制流——使用 Python 控制流而不是图控制流,简化了动态模型的规范。


简而言之:不需要首先定义图,然后在会话中计算它。在 Eager 模式中使用 TensorFlow 可以混合定义和执行,就像标准的 Python 程序一样。


这与静态图版本并不是一一对应的,因为有些在图中很自然的东西并不存在于这样一个命令式环境中。


这里,最重要的例子是tf.GradientTape上下文管理器,这只存在于 Eager 模式下。


当我们有一个图,我们知道节点是如何连接的,当我们要计算某个函数的梯度时,我们可以从输出回溯到图的输入,计算梯度并得到结果。


在 Eager 模式下,我们不能这样。使用自动微分法计算函数梯度的唯一方法是构建一个图。构建在 tf.GradientTape 上下文管理器中执行的、对一些可观察的元素(比如变量)进行操作的图,然后,可以由 tf.GradientTape 来计算我们需要的梯度。


tf.GradientTape文档页上,我们可以找到例子,清楚地说明如何使用 tf.GradientTape 以及为什么需要它:


x = tf.constant(3.0)with tf.GradientTape() as g:  g.watch(x)  y = x * xdy_dx = g.gradient(y, x) # Will compute to 6.0
复制代码


此外,控制流操作就是 Python 的控制流操作(比如 for loop、if 语句……),与 tf.while_loop、tf.map_fn、tf.cond 不同,那些方法我们必须在静态图版本中使用。


有一个工具,叫做Autograph,它可以帮助你使用普通的 Python 编写复杂的图代码。在后台,AutoGraph 自动将代码转换为等效的 TensorFlow 图代码。


不过,你需要编写的 Python 代码不是纯 Python(例如,如果你要声明一个函数返回一个指定 TensorFlow 数据类型的元素列表,那会用到在标准 Python 函数中不会使用的操作),而其功能至少在本文写作时是有限的。


之所以创建这个工具,是因为图版本有一个很大的优势,即一旦导出,它就成为“单个文件”,而在生产环境中交付经过训练的机器学习模型,使用静态图模式要容易得多。另外,静态图模式更快。


就我个人而言,我不太喜欢 Eager 模式。可能是因为我已经习惯了静态图版本,并且我发现,Eager 模式是 PyTorch 的粗糙模仿。另外,尝试将 GAN 从 PyTorch 实现转成 TensorFlow 2.x 版本,同时使用静态图和 Eager 模式时,我无法让 Eager 模式发挥作用,我还不知道为什么(虽然静态图实现工作得很好)。我在 GitHub 上提交了一个 Bug 报告(当然,这个错误可能是我自己的):TensorFlow Eager版本失败了,而TensorFlow静态图可以正常运行


转换到 TensorFlow 2.x 还需要做其他的修改,我将在下一节“该怎么办?”中总体介绍。

该怎么办?

关于转换到 TensorFlow 2.x,下面是我根据自己的理解整理的 F.A.Q 列表。


如果我的项目使用了****tf.contrib,该怎么办?


所有关于 tf.contrib 内部项目命运的信息可以在这里找到:tf.contrib日落


你可能只需要安装一个新的 Python 包,或者将 tf. instrument .something 重命名为 tf.something。


如果我的项目在 TensorFlow 1.x 中可以运行,而在 2.x 中无法运行了,该怎么办?


不应该出现这种情况:请再次检查转换实现是否正确,如果是,则在 GitHub 上提交一个 Bug 报告。


如果项目在静态图模式下可以运行,而在 Eager 模式下无法运行,该怎么办?


这是我目前遇到的问题,我已经提交了报告:TensorFlow Eager版本失败了,而TensorFlow静态图可以正常运行


现在我还不知道这是我自己的 Bug,还是实际的 TensorFlow Eager 版本有什么问题。但是,由于我习惯于静态图的思考方式,所以我将避免使用 Eager 版本。


如果某个 tf.方法在 2.x 中被删除了,该怎么办?


这个方法很可能只是被移动了。在 TensorFlow 1.x 中,有很多方法的别名。而在 TensorFlow 2.x 中,我们的目标是(如果RFC: TensorFlow名称空间如我所愿被接受的话)删除许多别名,并将方法移动到更好的位置,以提高整体的一致性。


在 RFC 中,你可以找到新提议的名称空间、要删除的名称空间列表以及所有其他为增强框架的一致性(可能)要进行的更改。


另外,即将发布的转换工具可能可以正确地为你应用所有这些更新(这只是我对该转换工具的猜测,但由于这是一项简单的任务,那是很可能会出现的一个特性)。

小结

本文的写作目的是阐明 TensorFlow 2.0 将给框架用户带来的变化和挑战。


TensorFlow 1 中的 GAN 实现以及到 TensorFlow 2.x 的转换应该可以清楚地说明使用新版本所需要的心态改变。


总的来说,我认为,TensorFlow 2.x 将改进框架的质量,标准化并简化它的用法。从未见过静态图方法且习惯于使用命令式语言的新用户可能会发现,Eager 模式是进入 TensorFlow 世界的一个很好的切入点。


不过,更新中有些部分我不喜欢(这只是我个人的观点):


  • 把重点放在 Eager Execution 上,并使之成为默认模式:在我看来,这似乎是一种营销手段。TensorFlow 似乎是想要追赶 PyTorch(默认是 Eager);

  • 静态图和 Eager(以及混合它们的可能性)不是 1:1 兼容性的,在我看来,这可能会在大型项目中造成混乱,使得项目难以维护;

  • 切换到基于 Keras 的方法是一项很好的举措,但它使图在 Tensorboard 中的可视化变得非常难看。事实上,变量和图的定义是全局的,在 TensorFlow 图中创建新“块”的 tf.named_scope(为了共享变量更容易,每次调用 Keras Model 时都会调用)被图隔开,它是内部使用的,它的输入节点列表中包含所有的模型变量——这使得 Tensorboard 中图的可视化变得几乎没有用处,对于这样一个好工具,这真是个遗憾。


如果你喜欢这篇文章,请分享;如果文章中有什么问题/可以改进的地方,请随时告诉我。


感谢你的阅读!


查看英文原文:TensorFlow 2.0: models migration and new design


2018-11-12 17:584093
用户头像

发布了 1008 篇内容, 共 389.7 次阅读, 收获喜欢 344 次。

关注

评论

发布
暂无评论
发现更多内容

Android刘海屏、水滴屏全面屏适配方案

android 程序员 移动开发

消息队列RocketMQ应对双十一流量洪峰的“六大武器”

阿里巴巴云原生

阿里云 RocketMQ 云原生 消息队列 流量

Android备忘录《内存泄漏》

android 程序员 移动开发

Android常见问题及开发经验总结(一)

android 程序员 移动开发

Android开发 申请Mob的SMSSDK的短信验证码功能中获取MD5签名(更新中)

android 程序员 移动开发

Android命令Monkey压力测试,详解

android 程序员 移动开发

Android屏幕适配方案

android 程序员 移动开发

Android属性动画——ObjectAnimator类及浮动菜单的实现

android 程序员 移动开发

Android应用进程间通信之Messenger信使使用及源码浅析

android 程序员 移动开发

Android开发3年,4个月面试,终于拿到小米、京东

android 程序员 移动开发

Android中自定义下拉样式Spinner

android 程序员 移动开发

Android使用ViewPager实现图片轮播系列之四:手动滑动 + 左右箭头 + 删除数据

android 程序员 移动开发

Android企业级实战-界面篇-2

android 程序员 移动开发

Android修炼系列(十二),自定义一个超顺滑的回弹RecyclerView

android 程序员 移动开发

基于 OpenYurt & EdgeX Foundry 的云边端一体化解决方案

阿里巴巴云原生

云原生 边缘计算 openyurt EdgeX Foundry

Android刘海屏、水滴屏全面屏适配详解

android 程序员 移动开发

基于 Istio 的全链路灰度方案探索和实践

阿里巴巴云原生

阿里云 云原生 istio 灰度 全链路

android各种提示Dialog 弹出框(1)

android 程序员 移动开发

android各种提示Dialog 弹出框

android 程序员 移动开发

Android开发3年,九月份面试12家大厂跳槽成功,我有一些面试经验想分享给你们

android 程序员 移动开发

Android岗高频面试题二集,看你能答出几题?(含答案

android 程序员 移动开发

android之Fragment(官网资料翻译)

android 程序员 移动开发

Android事件分发机制三:事件分发工作流程

android 程序员 移动开发

Android低版本上APP首次启动时间减少80%(一)

android 程序员 移动开发

Android体系化进阶学习图谱:我们究竟还要学习哪些Android知识?(某大厂内部资料

android 程序员 移动开发

Android厂商推送冲突了。。。

android 程序员 移动开发

android图片加载库Glide4使用教程(项目中如何快速将Glide3替换成Glide4)

android 程序员 移动开发

Android仿QQ锁屏状态下消息提醒(震动+提示音)

android 程序员 移动开发

Android内存泄漏问题

android 程序员 移动开发

Android动画之补间动画

android 程序员 移动开发

Android平台HTTPS抓包全方案

android 程序员 移动开发

关于即将发布的TensorFlow 2.0,你需要知道这几件事_AI&大模型_Rita Lia_InfoQ精选文章