本笔记参照TensorFlow Guide官方教程,主要是对‘Effictive TensorFlow 2’教程内容翻译和内容结构编排,原文链接:Effictive TensorFlow 2
TensorFlow2.0 移除了冗余的API,也让API一致性更强了(统一的RNNs,统一的优化器),并且在即刻执行里集成了Python运行环境。
许多RFC已经解释了对Tensorflow 2.0de 变化。本指南展示了TensorFlow 2.0开发应该是什么样的并假设我们对TensorFlow 1.x 有一定的了解。
在TF2.0里许多API都移除了,其中几个大改动是移除了‘tf.app’,‘tf.flags’,‘tf.logging’来支持现在开源的absl-py,转移了保存在‘tf.contrib’中的项目,通过将更少用到的功能移动到子包(像tf.math)形式清理了主要‘tf.*’命名空间。一些API 已经在2.0里被等价替换了(tf.summary\tf.keras.metrics\tf.keras.optimizers)。自动应用这些重命名的最简单的方法是使用V2升级脚本(v2 upgrade script)
TensorFlow1.X要求用户通过生成‘tf.*’API 调用来手动地将抽象语法树(图)缝合在一起。然后,它需要用户通过向session.run()调用中传递一组输出张量和输入张量来手动编译抽象语法树。 TensorFlow 2.0里的即刻执行就像Python编译器那样,在2.0,图(graph)和会话(session)感觉像是实现细节。
即刻执行的一个显著副产品是不再需要‘tf.control_dependencies()’,因为所有代码行都是按顺序执行的。(在tf.function里,带有副作用的代码是按编写时的顺序执行的)
TensorFlow 1.X严重依赖显式地全局命名空间。当我们调用‘tf.Variable()’,它会被放进默认的图(graph)中,并且始终在那儿,即使我们丢失了指向它的Python变量轨迹。我们可以恢复‘tf.Variable’,但只有我们知道它被创建时使用的名字才行,如果我们没有控制变量的创建,这样就很困难。因此,各种各样的扩散机制再次试图帮助用户找到他们的变量,以及一些用来寻找用户创建变量的框架:变量作用域,全局集,辅助方法,像:‘tf.get_global_step()’,‘tf.global_variables_initializer()’,通过所有可训练变量静默计算梯度的优化器(optimizer)等等。TensorFlow 2.0为了支持默认机制,消除了上述所有机制。默认机制可以保持对变量的跟踪。如果我们丢了变量的轨迹,它会被垃圾桶收集
跟踪变量的需求给用户创建了一些额外的工作,但是使用Keras对(参见下面的内容),这种负担就会被最小化。
‘session.run()’调用几乎与函数调用一样:指定要调用的输入和函数,然后返回一组输出。在TensorFlow 2.0中,可以使用tf.function()修饰一个Python函数,将其标记为JIT编译,以便TensorFlow将其作为一个图运行(函数2.0RFC)。这个机制允许TensorFlow 2.0获得图模式的所有好处:
- 性能:功能可以优化(节点剪枝node pruning、核融合等)。
- 可移植性:功能可以被导出/重导入(‘SavedModel 2.0 RFC’),允许用户重用和共享模块化的TensorFlow函数。
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={
placeholder: input})
# TensorFlow 2.0
outputs = f(input)
随着可以自由地穿插Python和TensorFlow代码,用户可以利用Python的表达方式。但由于TensofFlow具有可移植性,在没有Python解释器的上下文中如mobile、C++、JavaScript,也可以执行。可以帮助用户避免在添加‘@tf.function’时重写代码。AutoGraph将Python结构的一个子集转换成他们的TensorFlow等价物:
- for/while->‘tf.while_loop’(break和continue都支持)
- if->‘tf.cond’
- ‘for _ in dataset’->‘dataset.reduce’
AutoGraph系列支持任意的控制流网络,这使得高效和简洁的实现许多复杂的ML程序成为可能,例如序列模型,强化学习,自定义训练循环等等。
TensoFlow 1.X中常见的使用模式是‘kitchen sink’策略,所有可能的计算的并集被预先安排好,然后通过session.run()对选择的张量进行评估。在TensorFlow2.0中,用户应该根据需要将代码重构为更小的函数。通常,没有必要用tf.function来修饰这些小函数;只用tf.function来修饰高级计算–例如,一个训练步骤或模型的前向传递。
Keras模型和层提供了非常方便的‘variables’和‘trainable_variables’属性,他们递归地收集所有的因变量,这使得在本地管理变量到使用它们的地方变得很容易。
对比:
def dense(x, W, b):
return tf.nn.sigmoid(tf.matmul(x, W) + b)
@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
x = dense(x, w0, b0)
x = dense(x, w1, b1)
x = dense(x, w2, b2)
...
# You still have to manage w_i and b_i, and their shapes are defined far away from the code.
使用Keras的版本:
# Each layer can be called, with a signature equivalent to linear(x)
layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)
# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]
Keras层和模型继承自tf.train.Checkpointable,并且集成有@tf.function,它使直接设置检查点或从Keras对象导出SavedModels成为可能,这样我们不必使用Keras的‘fit()’API来利用这些集成。
下面是一个转移学习示例,演示Keras如何简化收集相关变量子集的工作。假设我们正在训练一个具有共享主干的多头模型:
trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])
path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])
# Train on primary dataset
for x, y in main_dataset:
with tf.GradientTape() as tape:
prediction = path1(x)
loss = loss_fn_head1(prediction, y)
# Simultaneously optimize trunk and head1 weights.
gradients = tape.gradient(loss, path1.trainable_variables)
optimizer.apply_gradients(zip(gradients, path1.trainable_variables))
# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
with tf.GradientTape() as tape:
prediction = path2(x)
loss = loss_fn_head2(prediction, y)
# Only optimize head2 weights, not trunk weights
gradients = tape.gradient(loss, head2.trainable_variables)
optimizer.apply_gradients(zip(gradients, head2.trainable_variables))
# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)
当迭代适合内存的训练数据时,可以随意使用常规的Python迭代。否则‘tf.data.Dataset’是从磁盘传输训练数据的最佳方式。数据集是可迭代对象(iterables)(而不是迭代器),在即刻执行模式其工作方式与其他Python迭代器一样。通过将代码包装在‘tf.function()’中,我们可以充分利用dataset异步预抓取/流式(prefetching/streaming)特性,该方法用使用AutoGraph的等价图形操作代替Python迭代。
@tf.function
def train(model, dataset, optimizer):
for x, y in dataset:
with tf.GradientTape() as tape:
prediction = model(x)
loss = loss_fn(prediction, y)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
如果我们使用Keras‘.fit()’ API,就不必担心数据集迭代:
model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)
AutoGraph可以将数据依存控制流(data-dependent control flow)转换成图模式等价物(graph-mode equivalents),像‘tf.cond’和‘tf.while_loop’。
序列模型是数据依存控制流经常出现的地方。‘tf.keras.layers.RNN’包装了一个RNN单元,让我们可以静态或动态地展开递归。出于演示的原因,我们可以像下面这样执行动态展开:
class DynamicRNN(tf.keras.Model):
def __init__(self, rnn_cell):
super(DynamicRNN, self).__init__(self)
self.cell = rnn_cell
def call(self, input_data):
# [batch, time, features] -> [time, batch, features]
input_data = tf.transpose(input_data, [1, 0, 2])
outputs = tf.TensorArray(tf.float32, input_data.shape[0])
state = self.cell.zero_state(input_data.shape[1], dtype=tf.float32)
for i in tf.range(input_data.shape[0]):
output, state = self.cell(input_data[i], state)
outputs = outputs.write(i, output)
return tf.transpose(outputs.stack(), [1, 0, 2]), state
如果要记录总结,在TensorFlow 2.0 可以使用‘tf.summary.(scalar|histogram|…)’,并且使用上下文管理器(context manager)将总结重新直传给编写人员。(如果忽略了上下文管理器,什么也不会发生),不像TF 1.X,总结直接发送给编写人员,没有单独的‘merge’操作,也没有单独的‘add_summary()’调用,这意味着必须在callsite中提供步骤值。
summary_writer = tf.summary.create_file_writer('/tmp/summaries')
with summary_writer.as_default():
tf.summary.scalar('loss', 0.1, step=42)
为了在数据记录为摘要前聚合它们,我们可以使用‘tf.metrics’。指标是有状态的:它们累加值并在调用‘.result()’时返回累积结果。使用‘.reset-states()’清楚累积值。
def train(model, optimizer, dataset, log_freq=10):
avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32)
for images, labels in dataset:
loss = train_step(model, optimizer, images, labels)
avg_loss.update_state(loss)
if tf.equal(optimizer.iterations % log_freq, 0):
tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations)
avg_loss.reset_states()
def test(model, test_x, test_y, step_num):
loss = loss_fn(model(test_x), test_y)
tf.summary.scalar('loss', loss, step=step_num)
train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train')
test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test')
with train_summary_writer.as_default():
train(model, optimizer, dataset)
with test_summary_writer.as_default():
test(model, test_x, test_y, optimizer.iterations)
通过将TensorBoard指向摘要日志目录,可视化生成的摘要:
tensorboard --logdir /tmp/summaries
在TensorFlow 2.0中,即刻执行让我们可以逐步运行代码来检查形状(shape)、数据类型和值。某些API,比如tf.function,tf.keras等被设计成使用图执行来提高性能和可移植性。在调试时,使用‘tf.config.experimental_run_functions_eagerly(True)’在这段代码中使用立即执行。
例如:
@tf.function
def f(x):
if x > 0:
import pdb
pdb.set_trace()
x = x + 1
return x
tf.config.experimental_run_functions_eagerly(True)
f(tf.constant(1))
class CustomModel(tf.keras.models.Model):
@tf.function
def call(self, input_data):
if tf.reduce_mean(input_data) > 0:
return input_data
else:
import pdb
pdb.set_trace()
return input_data // 2
tf.config.experimental_run_functions_eagerly(True)
model = CustomModel()
model(tf.constant([-2, -4]))