# 十八、调试 TensorFlow 模型

正如我们在本书中所学到的，TensorFlow 程序用于构建和训练可用于各种任务预测的模型。在训练模型时，您可以构建计算图，运行图以进行训练，并评估图以进行预测。重复这些任务，直到您对模型的质量感到满意为止，然后将图与学习的参数一起保存。在生产中，图是从文件构建或恢复的，并使用参数填充。

构建深度学习模型是一项复杂的技术，TensorFlow API 及其生态系统同样复杂。当我们在 TensorFlow 中构建和训练模型时，有时我们会得到不同类型的错误，或者模型不能按预期工作。例如，您经常看到自己陷入以下一种或多种情况：

*   在损失和指标输出中得到了 NaN
*   即使经过多次迭代，损失或其他指标也没有改善

在这种情况下，我们需要调试使用 TensorFlow API 编写的代码。

要修复代码以使其正常工作，可以使用调试器或平台提供的其他方法和工具，例如 Python 中的 Python 调试器（`pdb`）和 Linux OS 中的 GNU 调试器（`gdb`）。当出现问题时，TensorFlow API 还提供一些额外的支持来修复代码。

在本章中，我们将学习 TensorFlow 中可用的其他工具和技术，以帮助调试：

*   使用`tf.Session.run()`获取张量值
*   使用`tf.Print()`打印张量值
*   用`tf.Assert()`断言条件
*   使用 TensorFlow 调试器进行调试（`tfdbg`）

# 使用`tf.Session.run()`获取张量值

您可以使用`tf.Session.run()`获取要打印的张量值。这些值作为 NumPy 数组返回，可以使用 Python 语句打印或记录。这是最简单和最简单的方法，最大的缺点是计算图执行所有相关路径，从获取的张量开始，如果这些路径包括训练操作，那么它前进一步或一个周期。

因此，大多数情况下你不会调用`tf.Session.run()`来获取图中间的张量，但你会执行整个图并获取所有张量，你需要调试的那些张量以及你不需要的张量调试。

函数`tf.Session.partial_run()`也适用于您可能想要执行图的一部分的情况，但它是一个高度实验性的 API，尚未准备好用于生产。

# 使用`tf.Print()`打印张量值

为调试目的打印值的另一个选项是使用`tf.Print()`。当执行包含`tf.Print()`节点的路径时，您可以在`tf.Print()`中包含张量以在标准错误控制台中打印其值。`tf.Print()`函数具有以下签名：

```py
tf.Print(
     input_,
     data,
     message=None,
     first_n=None,
     summarize=None,
     name=None
    )
```

该函数的参数如下：

*   `input_`是一个从函数返回的张量，没有任何操作
*   `data`是要打印的张量列表
*   `message`是一个字符串，它作为打印输出的前缀打印出来
*   `first_n`表示打印输出的步骤数；如果此值为负，则只要执行路径，就始终打印该值
*   `summarize`表示从张量打印的元素数量；默认情况下，仅打印三个元素

您可以按照 Jupyter 笔记本中的代码`ch-18_TensorFlow_Debugging`。

让我们修改之前创建的 MNIST MLP 模型来添加`print`语句：

```py
model = tf.Print(input_=model,
                 data=[tf.argmax(model,1)],
                 message='y_hat=',
                 summarize=10,
                 first_n=5
  )
```

当我们运行代码时，我们在 Jupyter 的控制台中获得以下内容：

```py
I tensorflow/core/kernels/logging_ops.cc:79] y_hat=[0 0 0 7 0 0 0 0 0 0...]
I tensorflow/core/kernels/logging_ops.cc:79] y_hat=[0 7 7 1 8 7 2 7 7 0...]
I tensorflow/core/kernels/logging_ops.cc:79] y_hat=[4 8 0 6 1 8 1 0 7 0...]
I tensorflow/core/kernels/logging_ops.cc:79] y_hat=[0 0 1 0 0 0 0 5 7 5...]
I tensorflow/core/kernels/logging_ops.cc:79] y_hat=[9 2 2 8 8 6 6 1 7 7...]
```

使用`tf.Print()`的唯一缺点是该函数提供了有限的格式化功能。

# `tf.Assert()`

调试 TensorFlow 模型的另一种方法是插入条件断言。`tf.Assert()`函数需要一个条件，如果条件为假，则打印给定张量的列表并抛出`tf.errors.InvalidArgumentError`。

1.  `tf.Assert()`函数具有以下特征：

```py
tf.Assert(
    condition,
    data,
    summarize=None,
    name=None
)
```

1.  断言操作不会像`tf.Print()`函数那样落入图的路径中。为了确保`tf.Assert()`操作得到执行，我们需要将它添加到依赖项中。例如，让我们定义一个断言来检查所有输入是否为正：

```py
assert_op = tf.Assert(tf.reduce_all(tf.greater_equal(x,0)),[x])
```

1.  在定义模型时将`assert_op`添加到依赖项，如下所示：

```py
with tf.control_dependencies([assert_op]):
    # x is input layer
  layer = x
    # add hidden layers
  for i in range(num_layers):
        layer = tf.nn.relu(tf.matmul(layer, w[i]) + b[i])
    # add output layer
  layer = tf.matmul(layer, w[num_layers]) + b[num_layers]
```

1.  为了测试这段代码，我们在第 5 周期之后引入了一个杂质，如下：

```py
if epoch > 5:
    X_batch = np.copy(X_batch)
    X_batch[0,0]=-2
```

1.  代码运行正常五个周期，然后抛出错误：

```py
epoch: 0000 loss = 6.975991
epoch: 0001 loss = 2.246228
epoch: 0002 loss = 1.924571
epoch: 0003 loss = 1.745509
epoch: 0004 loss = 1.616791
epoch: 0005 loss = 1.520804

-----------------------------------------------------------------
InvalidArgumentError           Traceback (most recent call last)
...
InvalidArgumentError: assertion failed: [[-2 0 0]...]
...
```

除了`tf.Assert()`函数，它可以采用任何有效的条件表达式，TensorFlow 提供以下断言操作，检查特定条件并具有简单的语法：

*   `assert_equal`
*   `assert_greater`
*   `assert_greater_equal`
*   `assert_integer`
*   `assert_less`
*   `assert_less_equal`
*   `assert_negative`
*   `assert_none_equal`
*   `assert_non_negative`
*   `assert_non_positive`
*   `assert_positive`
*   `assert_proper_iterable`
*   `assert_rank`
*   `assert_rank_at_least`
*   `assert_rank_in`
*   `assert_same_float_dtype`
*   `assert_scalar`
*   `assert_type`
*   `assert_variables_initialized`

作为示例，前面提到的示例断言操作也可以写成如下：

```py
assert_op = tf.assert_greater_equal(x,0)
```

# 使用 TensorFlow 调试器（`tfdbg`）的调试

TensorFlow 调试器（`tfdbg`）与其他常用调试器（如`pdb`和`gdb`）的工作方式相同。要使用调试器，该过程通常如下：

1.  在代码中的断点处设置要中断的位置并检查变量
2.  在调试模式下运行代码
3.  当代码在断点处中断时，检查它然后继续下一步

一些调试器还允许您在代码执行时以交互方式观察变量，而不仅仅是在断点处：

1.  为了使用`tfdbg`，首先导入所需的模块并将会话包装在调试器包装器中：

```py
from tensorflow.python import debug as tfd

with tfd.LocalCLIDebugWrapperSession(tf.Session()) as tfs:
```

1.  接下来，将过滤器附加到会话对象。附加过滤器与在其他调试器中设置断点相同。例如，以下代码附加`tfdbg.has_inf_or_nan`过滤器，如果任何中间张量具有`nan`或`inf`值，则会中断：

```py
tfs.add_tensor_filter('has_inf_or_nan_filter', tfd.has_inf_or_nan)
```

1.  现在，当代码执行`tfs.run()`时，调试器将在控制台中启动调试器接口，您可以在其中运行各种调试器命令来监视张量值。
2.  我们提供了在`ch-18_mnist_tfdbg.py`文件中试用`tfdbg`的代码。当我们用`python3`执行代码文件时，我们看到`tfdbg`控制台：

```py
python3 ch-18_mnist_tfdbg.py
```

![](img/028e301e-7560-4cc4-bc10-63625534c2c3.png)

1.  在`tfdbg>`提示符下输入命令`run -f has_inf_or_nan`。代码在第一个周期后中断，因为我们使用`np.inf`值填充数据：

![](img/5d59edaa-a409-4f87-ba9f-c690fbbe820a.png)

1.  现在您可以使用`tfdbg`控制台或可点击界面来检查各种张量的值。例如，我们查看其中一个梯度的值：

![](img/10bbefc3-6401-4e19-bbf2-9e0113f6e035.png)

[您可以在此链接中找到有关使用`tfdbg`控制台和检查变量的更多信息](https://www.tensorflow.org/programmers_guide/debugger)。

# 总结

在本章中，我们学习了如何在 TensorFlow 中调试用于构建和训练模型的代码。我们了解到我们可以使用`tf.Session.run()`将张量作为 NumPy 数组获取。我们还可以通过在计算图中添加`tf.Print()`操作来打印张量值。我们还学习了，在使用`tf.Assert()`和其他`tf.assert_*`操作执行期间，某些条件无法保持时如何引发错误。我们通过对 TensorFlow 调试器（`tfdbg`）的介绍结束本章，用于设置断点和观察张量值，就像我们在 Python 调试器（`pdb`）或 GNU 调试器（`gdb`中调试代码一样） ）。

本章将我们的旅程带入一个新的里程碑。我们不希望旅程在此结束，但我们相信旅程刚刚开始，您将进一步扩展和应用本书中获得的知识和技能。

我们非常期待听到您的经验，反馈和建议。

