参见:https://www.tensorflow.org/guide/eager
有很多名词翻译和算法和函数的理解有待进一步研究,忘读者不吝赐教。
TensorFlow的即时执行是一种命令式编程环境,它可以立即计算操作,而不构建图形:操作返回具体的值,而不是构建稍后运行的计算图形。这使得开始使用TensorFlow和调试模型变得很容易,而且还减少了样板文件。要跟随本指南,请在交互式python解释器中运行下面的代码示例。
Eager execution是一个用于研究和实验的灵活机器学习平台,提供:
直观的界面结构,您的代码自然和使用Python数据结构。在小模型和小数据上快速迭代。
更容易地调试调用ops直接检查运行的模型和测试更改。使用标准的Python调试工具来立即报告错误。
自然控制流——使用Python控制流代替图形控制流,简化了动态模型的规范。
即时执行支持大部分的TensorFlow操作和GPU加速。
注意:一些模型在启用急执行时可能会遇到开销增加的情况。性能改进正在进行中,但如果发现问题,请提交一个bug并分享您的基准测试。
设置和基本用法
import os
import tensorflow as tf
import cProfile
在Tensorflow 2.0中,默认情况下启用即时执行
tf.executing_eagerly()
现在你可以运行TensorFlow操作,结果将立即返回:
x = [[2.]]
m = tf.matmul(x, x)
print("hello, {}".format(m))
hello, [[4.]]
即时执行将改变TensorFlow操作的行为,现在它们立即进行评估并将其值返回给Python。 tf.Tensor对象引用具体值,而不是计算图中的节点的符号句柄。 由于没有在会话中稍后构建和运行的计算图,因此使用print()或调试器检查结果很容易评估,打印和检查张量值,不会中断计算梯度的流程。即时执行与NumPy配合得很好。 NumPy操作接收tf.Tensor参数。 TensorFlow tf.math操作将Python对象和NumPy数组转换为tf.Tensor对象。 tf.Tensor.numpy方法将对象的值作为NumPy ndarray返回。
a = tf.constant([[1, 2],
[3, 4]])
print(a)
tf.Tensor(
[[1 2]
[3 4]], shape=(2, 2), dtype=int32)
# Broadcasting support
b = tf.add(a, 1)
print(b)
tf.Tensor(
[[2 3]
[4 5]], shape=(2, 2), dtype=int32)
# Operator overloading is supported
print(a * b)
tf.Tensor(
[[ 2 6]
[12 20]], shape=(2, 2), dtype=int32)
# Use NumPy values
import numpy as np
c = np.multiply(a, b)
print(c)
[[ 2 6]
[12 20]]
# Obtain numpy value from a tensor:
print(a.numpy())
# => [[1 2]
# [3 4]]
[[1 2]
[3 4]]
即时执行的主要好处是,在执行模型时,可以使用宿主语言的所有功能。 因此,例如,可以很容易地编写fizzbuzz:
def fizzbuzz(max_num):
counter = tf.constant(0)
max_num = tf.convert_to_tensor(max_num)
for num in range(1, max_num.numpy()+1):
num = tf.constant(num)
if int(num % 3) == 0 and int(num % 5) == 0:
print('FizzBuzz')
elif int(num % 3) == 0:
print('Fizz')
elif int(num % 5) == 0:
print('Buzz')
else:
print(num.numpy())
counter += 1
运行
fizzbuzz(15)
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
它依赖于张量的值,并在运行时打印这些值。
计算梯度
自动微分对于实现机器学习算法(例如用于训练神经网络的反向传播)非常有用。 在急切执行期间,请使用tf.GradientTape跟踪用于稍后计算梯度的操作。
您可以使用tf.GradientTape来急切地训练和/或计算渐变。 对于复杂的训练循环特别有用。
由于在每个操作期间可能发生不同的操作,因此所有前向通过操作都被记录到“磁带”中。 要计算梯度,请向后播放磁带,然后丢弃。 特定的tf.GradientTape只能计算一个微分; 随后的调用将引发运行时错误。
w = tf.Variable([[1.0]])
with tf.GradientTape() as tape:
loss = w * w
grad = tape.gradient(loss, w)
print(grad) # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)
tf.Tensor([[2.]], shape=(1, 1), dtype=float32)
下面的示例创建一个多层模型,该模型对标准MNIST手写数字进行分类。 它演示了在急切的执行环境中构建可训练图的优化器和层API。
# Fetch and format the mnist data
(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()
dataset = tf.data.Dataset.from_tensor_slices(
(tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),
tf.cast(mnist_labels,tf.int64)))
dataset = dataset.shuffle(1000).batch(32) #将数据打乱,数值越大,混乱程度越大,按照顺序取出32行数据,最后一次输出可能小于batch
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
# Build the model
mnist_model = tf.keras.Sequential([
tf.keras.layers.Conv2D(16,[3,3], activation='relu',
input_shape=(None, None, 1)),
tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(10)
])
即使没有经过训练,也可以调用模型并在急执行时检查输出:
for images,labels in dataset.take(1):
print("Logits: ", mnist_model(images[0:1]).numpy())
Logits: [[ 0.06289896 -0.03877686 -0.07346137 -0.03169462 0.02922358 -0.02436475
-0.00588411 0.03256026 0.01715117 -0.02714448]]
虽然keras模型有一个内置的训练循环(使用fit方法),但有时您需要更多的定制。下面是一个使用eager实现的训练循环的例子:
optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
loss_history = []
注意:在tf.debugging中使用assert函数检查条件是否成立。 这在渴望执行和执行图形时起作用。
def train_step(images, labels):
with tf.GradientTape() as tape:
logits = mnist_model(images, training=True)
# Add asserts to check the shape of the output.
tf.debugging.assert_equal(logits.shape, (32, 10))
loss_value = loss_object(labels, logits)
loss_history.append(loss_value.numpy().mean())
grads = tape.gradient(loss_value, mnist_model.trainable_variables)
optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables))
def train(epochs):
for epoch in range(epochs):
for (batch, (images, labels)) in enumerate(dataset):
train_step(images, labels)
print ('Epoch {} finished'.format(epoch))
train(epochs = 3)
Epoch 0 finished
Epoch 1 finished
Epoch 2 finished
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.xlabel('Batch #')
plt.ylabel('Loss [entropy]')
Text(0, 0.5, 'Loss [entropy]')
变量对象存储可变的类似张量的值,在训练过程中访问,从而使自动区分变得更加容易。变量的集合以及对它们进行操作的方法可以封装到层或模型中。 有关详细信息,请参见自定义Keras图层和模型。 图层和模型之间的主要区别在于模型添加了诸如Model.fit,Model.evaluate和Model.save之类的方法。例如,上面的自动微分示例可以重写:
class Linear(tf.keras.Model):
def __init__(self):
super(Linear, self).__init__()
self.W = tf.Variable(5., name='weight')
self.B = tf.Variable(10., name='bias')
def call(self, inputs):
return inputs * self.W + self.B
# A toy dataset of points around 3 * x + 2
NUM_EXAMPLES = 2000
training_inputs = tf.random.normal([NUM_EXAMPLES])
noise = tf.random.normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise
# The loss function to be optimized
def loss(model, inputs, targets):
error = model(inputs) - targets
return tf.reduce_mean(tf.square(error))
def grad(model, inputs, targets):
with tf.GradientTape() as tape:
loss_value = loss(model, inputs, targets)
return tape.gradient(loss_value, [model.W, model.B])
下一步:
创建模型
关于模型参数的损失函数的导数
一种基于导数更新变量的策略
model = Linear()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
steps = 300
for i in range(steps):
grads = grad(model, training_inputs, training_outputs)
optimizer.apply_gradients(zip(grads, [model.W, model.B]))
if i % 20 == 0:
print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))
Initial loss: 68.602
Loss at step 000: 65.948
Loss at step 020: 30.156
Loss at step 040: 14.091
Loss at step 060: 6.878
Loss at step 080: 3.637
Loss at step 100: 2.180
Loss at step 120: 1.525
Loss at step 140: 1.231
Loss at step 160: 1.098
Loss at step 180: 1.038
Loss at step 200: 1.011
Loss at step 220: 0.999
Loss at step 240: 0.994
Loss at step 260: 0.991
Loss at step 280: 0.990
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.990
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 3.001922369003296, B = 2.0047335624694824
注意:变量一直存在,直到删除对python对象的最后一个引用,并且该变量被删除为止。
tf.keras.Model包含一个方便的save_weights方法,使您可以轻松创建检查点:
model.save_weights('weights')
status = model.load_weights('weights')
This section is an abbreviated version of the guide to training checkpoints.
x = tf.Variable(10.)
checkpoint = tf.train.Checkpoint(x=x)
x.assign(2.) # Assign a new value to the variables and save.
checkpoint_path = './ckpt/'
checkpoint.save('./ckpt/')
'./ckpt/-1'
x.assign(11.) # Change the variable after saving.
# Restore values from the checkpoint
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))
print(x) # => 2.0
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0>
要保存和加载模型,tf.train.Checkpoint可以存储对象的内部状态,而无需隐藏变量。 要记录模型,优化器和全局步骤的状态,请将它们传递给tf.train.Checkpoint:
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dense(10)
])
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
checkpoint_dir = 'path/to/model_dir'
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
model=model)
root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f9abd45f1d0>
注意:在许多训练循环中,将在调用tf.train.Checkpoint.restore之后创建变量。 这些变量在创建后将立即恢复,并且可以使用断言来确保检查点已完全加载。 有关详细信息,请参阅培训检查点指南。
面向对象的指标
tf.keras.metrics作为对象存储。 通过将新数据传递给可调用对象来更新指标,并使用tf.keras.metrics.result方法检索结果,例如:
m = tf.keras.metrics.Mean("loss")
m(0)
m(5)
m.result() # => 2.5
m([8, 9])
m.result() # => 5.5
摘要和张量板
TensorBoard是用于了解,调试和优化模型训练过程的可视化工具。 它使用在执行程序时写入的摘要事件。
您可以使用tf.summary记录急切执行中的变量摘要。 例如,要每100个训练步骤记录一次损失摘要:
logdir = "./tb/"
writer = tf.summary.create_file_writer(logdir)
steps = 1000
with writer.as_default(): # or call writer.set_as_default() before the loop.
for i in range(steps):
step = i + 1
# Calculate loss with your real train function.
loss = 1 - 0.001 * step
if step % 100 == 0:
tf.summary.scalar('loss', loss, step=step)
ls tb/
events.out.tfevents.1602033841.kokoro-gcp-ubuntu-prod-892356553.1670.619697.v2
动态模型tf.GradientTape也可以在动态模型中使用。 回溯线搜索算法的此示例看起来像普通的NumPy代码,除了存在梯度并且可区分之外,尽管控制流程复杂:
def line_search_step(fn, init_x, rate=1.0):
with tf.GradientTape() as tape:
# Variables are automatically tracked.
# But to calculate a gradient from a tensor, you must `watch` it.
tape.watch(init_x)
value = fn(init_x)
grad = tape.gradient(value, init_x)
grad_norm = tf.reduce_sum(grad * grad)
init_value = value
while value > init_value - rate * grad_norm:
x = init_x - rate * grad
value = fn(x)
rate /= 2.0
return x, value
@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
y = tf.identity(x)
def grad_fn(dresult):
return [tf.clip_by_norm(dresult, norm), None]
return y, grad_fn
自定义渐变通常用于为一系列操作提供数值稳定的渐变:
def log1pexp(x):
return tf.math.log(1 + tf.exp(x))
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
# The gradient computation works fine at x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
0.5
# However, x = 100 fails because of numerical instability.
grad_log1pexp(tf.constant(100.)).numpy()
nan
在这里,log1pexp函数可以通过自定义梯度进行分析简化。 下面的实现重复使用了在前向传递期间计算的tf.exp(x)的值-通过消除冗余计算来提高效率:
@tf.custom_gradient
def log1pexp(x):
e = tf.exp(x)
def grad(dy):
return dy * (1 - 1 / (1 + e))
return tf.math.log(1 + e), grad
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
# As before, the gradient computation works fine at x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
0.5
# And the gradient computation also works at x = 100.
grad_log1pexp(tf.constant(100.)).numpy()
1.0
即时执行期间,计算会自动转移到GPU。 如果要控制计算的运行位置,可以将其包含在tf.device(’/ gpu:0’)块(或等效于CPU)中:
import time
def measure(x, steps):
# TensorFlow initializes a GPU the first time it's used, exclude from timing.
tf.matmul(x, x)
start = time.time()
for i in range(steps):
x = tf.matmul(x, x)
# tf.matmul can return before completing the matrix multiplication
# (e.g., can return after enqueing the operation on a CUDA stream).
# The x.numpy() call below will ensure that all enqueued operations
# have completed (and will also copy the result to host memory,
# so we're including a little more than just the matmul operation
# time).
_ = x.numpy()
end = time.time()
return end - start
shape = (1000, 1000)
steps = 200
print("Time to multiply a {} matrix by itself {} times:".format(shape, steps))
# Run on CPU:
with tf.device("/cpu:0"):
print("CPU: {} secs".format(measure(tf.random.normal(shape), steps)))
# Run on GPU, if available:
if tf.config.experimental.list_physical_devices("GPU"):
with tf.device("/gpu:0"):
print("GPU: {} secs".format(measure(tf.random.normal(shape), steps)))
else:
print("GPU: not found")
Time to multiply a (1000, 1000) matrix by itself 200 times:
CPU: 0.9788374900817871 secs
GPU: 0.04241943359375 secs
可以将tf.Tensor对象复制到其他设备以执行其操作:
if tf.config.experimental.list_physical_devices("GPU"):
x = tf.random.normal([10, 10])
x_gpu0 = x.gpu()
x_cpu = x.cpu()
_ = tf.matmul(x_cpu, x_cpu) # Runs on CPU
_ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0
WARNING:tensorflow:From <ipython-input-1-876293b5769c>:4: _EagerTensorBase.gpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.identity instead.
WARNING:tensorflow:From <ipython-input-1-876293b5769c>:5: _EagerTensorBase.cpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.identity instead.
对于大量计算的模型,例如在GPU上进行ResNet50培训,渴望的执行性能可与tf.function执行媲美。 但是,对于计算量较少的模型,此差距会增大,并且有很多工作需要为具有许多小操作的模型优化热代码路径。
即时执行使开发和调试更具交互性,但TensorFlow 1.x样式图的执行在分布式培训,性能优化和生产部署方面具有优势。 为了弥合这一差距,TensorFlow 2.0通过tf.function API引入了函数。 有关更多信息,请参见tf功能指南。