Tensorflow

本文翻译自: 《Debugging TensorFlow models》, 如有侵权请联系删除, 仅限于学术交流, 请勿商用。 如有谬误, 请联系指出。

与常规 python 代码相比, TensorFlow 的符号特性使的 TensorFlow 的代码调试变得相对困难。 这里我介绍一些 TensorFlow 附带的工具, 使调试更容易。

使用 TensorFlow 时最常见的错误可能是传递形状错误的张量。 许多 TensorFlow 操作可以在不同秩(rank)和形状(shape)的张量上操作。 这在使用 API 时很方便, 但在出现问题时可能会导致额外的麻烦。

例如, 考虑下面这个 tf.matmul 操作, 它可以使两个矩阵相乘:

a = tf.random_uniform([2, 3])
b = tf.random_uniform([3, 4])
c = tf.matmul(a, b)  # c is a tensor of shape [2, 4]

但是下面这个函数也可以实现矩阵乘法:

a = tf.random_uniform([10, 2, 3])
b = tf.random_uniform([10, 3, 4])
tf.matmul(a, b)  # c is a tensor of shape [10, 2, 4]

下面是我们之前在广播部分谈到的一个支持广播的添加操作的例子:

a = tf.constant([
    [1.],
    [2.]
])
b = tf.constant([1., 2.])
c = a + b# c is a tensor of shape[2, 2]

使用 tf.assert * 操作验证您的张量

减少不必要行为可能性的一种方法是使用 tf.assert * 操作验证中间张量的秩(rank)或形状(shape)。

a = tf.constant([[1.], [2.]])
b = tf.constant([1., 2.])
check_a = tf.assert_rank(a, 1)  # This will raise an InvalidArgumentError exception
check_b = tf.assert_rank(b, 1)
with tf.control_dependencies([check_a, check_b]):
    c = a + b  # c is a tensor of shape [2, 2]

请记住, 断言节点和其他操作一样都属于 TensorFlow 中图(Graph)的一部分, 如果不进行评估, 则会在执行 Session.run() 期间进行剔除。 因此, 请确保为断言操作创建显式依赖项, 以强制 TensorFlow 执行它们。

你还可以在运行时使用断言验证张量的值:

check_pos = tf.assert_positive(a)

有关断言操作的详细信息, 请参阅官方文档。

使用 tf.Print 打印张量值

另一个对调试有帮助的内置函数是 tf.Print , 它可以将给定的张量记录到标准错误堆栈中:

input_copy = tf.Print(input, tensors_to_print_list)

注意一下, tf.Print 函数将其第一个参数的副本作为返回值输出。 一种让 tf.Print 强制运行的方式是将其输出传递给另一个操作去执行。 例如, 如果我们想在添加它们之前就打印张量 a 和 b 的值, 我们可以这样做:

a = ...
b = ...
a = tf.Print(a, [a, b])
c = a + b

或者, 我们可以手动定义控件依赖项。

利用 tf.compute_gradient_error 检查梯度变化的值

并不是 TensorFlow 中的所有操作都有梯度变化, 并且很容易在无意中构建出 TensorFlow 无法计算梯度变化的图。

让我们来看个例子:

import tensorflow as tf

def non_differentiable_softmax_entropy(logits):
    probs = tf.nn.softmax(logits)
    return tf.nn.softmax_cross_entropy_with_logits(labels=probs, logits=logits)

w = tf.get_variable("w", shape=[5])
y = -non_differentiable_softmax_entropy(w)

opt = tf.train.AdamOptimizer()
train_op = opt.minimize(y)

sess = tf.Session()
sess.run(tf.global_variables_initializer())
for i in range(10000):
    sess.run(train_op)

print(sess.run(tf.nn.softmax(w)))

我们正在使用 tf.nn.softmax_cross_entropy_with_logits 定义一个分类分布上的熵。 然后我们使用 Adam 优化器来找到具有最大熵的权重。 如果你通过了信息论的课程, 你就会知道均匀分布包含最大熵。 所以你预计他的结果应该会是 [0.2, 0.2, 0.2, 0.2, 0.2] 。 但是你执行这段代码的话会得到一个你意想不到的结果:

[ 0.34081486  0.24287023  0.23465775  0.08935683  0.09230034]

事实证明, tf.nn.softmax_cross_entropy_with_logits 对标签有未定义的梯度变化! 但是, 如果我们不知道这个现象, 我们又怎么能发现这个问题呢?

幸运的是, TensorFlow 带有一个数值微分器, 可用于查找符号梯度误差。 让我们看看我们如何使用它:

with tf.Session():
    diff = tf.test.compute_gradient_error(w, [5], y, [])
    print(diff)

如果你运行它, 你会发现数值和符号之间的差异非常大(我试了下大约为 0.06 - 0.1)。

现在让我们更改下我们的函数并再次执行下:

import tensorflow as tf
import numpy as np

def softmax_entropy(logits, dim=-1):
    plogp = tf.nn.softmax(logits, dim) * tf.nn.log_softmax(logits, dim)
    return -tf.reduce_sum(plogp, dim)

w = tf.get_variable("w", shape=[5])
y = -softmax_entropy(w)

print(w.get_shape())
print(y.get_shape())

with tf.Session() as sess:
    diff = tf.test.compute_gradient_error(w, [5], y, [])
    print(diff)

差异应该在 0.0001 左右, 这个结果看起来好多了。

现在, 如果再次使用正确的版本运行优化器, 你可以看到最终权重为:

[ 0.2  0.2  0.2  0.2  0.2]

这就是我们想要的答案。

TensorFlow summariestfdbg(TensorFlow Debugger)是另外两个用于调试的工具, 请参阅官方文档以了解更多信息。