TensorFlow 笔记(十五):tf.contrib.learn日志和监控

本文介绍了tf.contrib.learn中的日志(Logging)监控(Monitoring)的基础知识。当我们训练一个模型时,实时的追踪和评估训练进度往往是很有用的。在本教程中,我们将学会如何使用TensorFLow的日志功能和监控器MonitorAPI来监控一个神经网络分类器的训练过程,该分类器对鸾尾花(Iris)数据集进行分类。

该笔记的代码基于我之前的一篇笔记《TensorFlow 笔记(十三):tf.contrib.learn入门》改进而来,如果大家还没看那篇笔记,可以先去看完再来看这篇。

教程地址:https://www.tensorflow.org/get_started/monitors


基础

如下代码来源于笔记《TensorFlow 笔记(十三):tf.contrib.learn入门》,我们将在该代码的基础上改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import numpy as np
import tensorflow as tf
# Data sets
IRIS_TRAINING = os.path.join(os.path.dirname(__file__), "iris_training.csv")
IRIS_TEST = os.path.join(os.path.dirname(__file__), "iris_test.csv")
def main(unused_argv):
# Load datasets.
training_set = tf.contrib.learn.datasets.base.load_csv_with_header(
filename=IRIS_TRAINING, target_dtype=np.int, features_dtype=np.float32)
test_set = tf.contrib.learn.datasets.base.load_csv_with_header(
filename=IRIS_TEST, target_dtype=np.int, features_dtype=np.float32)
# Specify that all features have real-value data
feature_columns = [tf.contrib.layers.real_valued_column("", dimension=4)]
# Build 3 layer DNN with 10, 20, 10 units respectively.
classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,
hidden_units=[10, 20, 10],
n_classes=3,
model_dir="/tmp/iris_model")
# Fit model.
classifier.fit(x=training_set.data,
y=training_set.target,
steps=2000)
# Evaluate accuracy.
accuracy_score = classifier.evaluate(x=test_set.data,
y=test_set.target)["accuracy"]
print('Accuracy: {0:f}'.format(accuracy_score))
# Classify two new flower samples.
new_samples = np.array(
[[6.4, 3.2, 4.5, 1.5], [5.8, 3.1, 5.0, 1.7]], dtype=float)
y = list(classifier.predict(new_samples, as_iterable=True))
print('Predictions: {}'.format(str(y)))
if __name__ == "__main__":
tf.app.run()

下面,我们将在这个代码基础上,一点点更新,最终使得它具有日志和监控的功能。


概述

上述代码仅仅是实现了一个神经网络分类器的功能,把鸾尾花样本正确分类。但是该代码没有打印任何记录模型训练过程的日志,仅仅展示出print语句中的结果:

1
2
Accuracy: 0.933333
Predictions: [1 2]

没有任何日志记录,模型的训练就让人感觉像是一个黑箱子,在TensorFlow每次执行梯度下降算法时,我们都不知道里面发生了什么,也不了解模型是否正确收敛,或者确定是否应该提前停止训练(early stopping)

其中一个解决方法是将模型训练分成多个fit来调用,以更小的步数来逐步评估模型的准确性。然而,这种方法在实践中并不推荐,因为这会使得训练过程变得很漫长。幸运的是,tf.contrib.learn提供了另外一个解决方法——Monitor API,我们可以用它在训练过程中打印出训练日志同时评估我们的模型。

在接下来的小节我们将学习如何在TensorFlow中打印日志,如何建立一个验证监控器(ValidationMonitor)来做评估,同时在TensorBoard中进行可视化。


打印日志

TensorFlow定义了五种不同层次的日志信息。以升序排列分别为DEBUG, INFO, WARN, ERROR, FATAL。当我们配置了其中一个层次的日志,TensorFlow不仅会输出该层次的日志,同时会输出比它高层次的日志记录。例如,当我们配置日志层次为ERROR,我们将会得到包含ERRORFATAL的日志信息;当我们设置日志层次为DEBUG,我们将得到全部五种层次的日志信息。

TensorFlow默认的配置层次为WARN,但是当我们追踪模型的训练时,我们最好把它调整为INFO,这会给我们提供训练时fit节点的信息。

在代码的开始阶段(import 之后)添加以下代码:

1
tf.logging.set_verbosity(tf.logging.INFO)

现在当我们执行代码,我们会看到如下额外的输出信息:

1
2
3
INFO:tensorflow:loss = 1.18812, step = 1
INFO:tensorflow:loss = 0.210323, step = 101
INFO:tensorflow:loss = 0.109025, step = 201

当我们用INFO层次的日志时,tf.contirb.learn会默认每100步迭代输出一次训练损失。


配置验证监控器进行评估

打印出来训练模型可以帮助我们了解模型是否收敛,但是如果我们想要进一步的了解训练过程中发生了什么,tf.contrib.learn提供了一些高水平的Monitor,我们可以把它们运用到fit操作中来追踪训练过程或者调试一些低水平的TensorFlow操作。Monitor主要有以下几种:

Monitor Description
CaptureVariable 每N次迭代把一个指定变量值保存到一个收集器中。
PrintTensor 每N次迭代打印出来指定tensor的值。
SummarySaver 对于一个指定tensor每N次迭代保存一次 tf.Summary protocol buffers,通过使用tf.summary.FileWriter 指令。
ValidationMonitor 每N次迭代打印一次评估结果,还可以选择什么情况下提前停止训练(early stopping)。


每N次迭代做一次评估

对于鸾尾花神经网络分类器,当我们打印训练损失时,我们可能还想要同步的在测试数据集上进行评估,进而得知模型的泛化能力。我们可以通过配置ValidationMonitor来实现这个功能,同时还可以设置每多少次迭代评估一次,every_n_steps默认值是100,这里我们设置every_n_steps为50,即让它每50次迭代来对测试集进行一次评估。

1
2
3
4
validation_monitor = tf.contrib.learn.monitors.ValidationMonitor(
test_set.data,
test_set.target,
every_n_steps=50)

把这行代码放在初始化classifier之前。

ValidationMonitor依赖于保存的checkpoint文件来进行评估操作,因此我们需要在实例化classifier时添加tf.contirb.learn.RunConfig项,它包含了save_checkpoints_secs值来定义我们每多少秒保存一次checkpoint文件。因为鸾尾花数据集十分小,并且训练的特别快,我们可以把save_checkpoints_secs设置为1,也就是每秒钟保存一次checkpoint文件,这样我们可以确保有足够数量的checkpoint文件:

1
2
3
4
5
6
classifier = tf.contrib.learn.DNNClassifier(
feature_columns=feature_columns,
hidden_units=[10, 20, 10],
n_classes=3,
model_dir="/tmp/iris_model",
config=tf.contrib.learn.RunConfig(save_checkpoints_secs=1))

注意:model_dir参数定义了一个绝对地址来保存模型数据。每次运行代码时,该路径下的所有现有数据将被加载,并且模型训练将从上次停止的地方继续运行(例如,若训练期间每次fit操作迭代2000次,则连续执行脚本两次将迭代4000次 )。

最后,我们来把之前定义好的监控器validation_monitor添加进fit操作里,把它赋值给monitor参数,该参数会接受一个包含了所有监控器的列表,并在训练过程中执行它们:

1
2
3
4
classifier.fit(x=training_set.data,
y=training_set.target,
steps=2000,
monitors=[validation_monitor])

现在,当我们运行该代码,我们可以看到打印出来的评估结果:

1
2
3
4
5
INFO:tensorflow:Validation (step 50): loss = 1.71139, global_step = 0, accuracy = 0.266667
...
INFO:tensorflow:Validation (step 300): loss = 0.0714158, global_step = 268, accuracy = 0.966667
...
INFO:tensorflow:Validation (step 1750): loss = 0.0574449, global_step = 1729, accuracy = 0.966667


用MetricSpec改进评估指标

如果我们没有定义评估指标,在验证阶段ValidationMonitor将会默认同时打印出损失值和准确率。但是我们可以更改指标列表,定义我们想要的评估指标,我们可以向ValidationMonitor函数添加metircs参数。 metircs采用字典的键/值对来定义,其中每个键都是我们想要记录的指标的名字,相应的值是MetricSpec对象。

MetricSpec对象使用函数tf.contirb.learn.MetricSpec()来定义,它接受四个参数:


  • metric_fn:函数计算并且返回一组指标的值,可以是tf.contrib.metrics模块中已经预定义好的函数,例如tf.contrib.metrics.streaming_precisiontf.contrib.metrics.streaming_recall。同时我们可以定义我们自己需要的指标函数,这需要把pridictionslabels张量作为参数(weights参数是可选的)。该函数必须返回两种形式的指标值
    • 一个tensor
    • 一组ops(value_op, update_op),其中value_op返回指标的值,update_op执行一个相关的操作来更新内部模型状态。


  • prediction_key:该tensor包含了模型返回的预测值。如果模型返回单个张量或具有单个条目的字典,则可以省略此参数。对于DNNClassifier模型,类预测将使用关键字tf.contrib.learn.PredictionKey.CLASSES在张量中返回。


  • label_key:该tensor包含了模型返回的标签,它被模型的input_fn函数定义。与prediction_key一样,如果input_fn返回单个张量或具有单个条目的字典,则可以省略此参数。在本教程中,DNNClassifier没有input_fn(x,y数据直接传递给fit),因此不需要提供label_key


  • weights_key:可选项。tensor(由input_fn返回)包含metric_fn的权重输入。


下面的代码创建一个validation_metrics字典,定义了三个在模型评估过程中需要打印的指标。

  • "accuracy",使用tf.contrib.metrics.streaming_accuracy作为metric_fn
  • "precision",使用tf.contrib.metrics.streaming_precision作为metric_fn
  • "recall"使用tf.contrib.metrics.streaming_recall作为metric_fn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
validation_metrics = {
"accuracy":
tf.contrib.learn.MetricSpec(
metric_fn=tf.contrib.metrics.streaming_accuracy,
prediction_key=tf.contrib.learn.PredictionKey.CLASSES),
"precision":
tf.contrib.learn.MetricSpec(
metric_fn=tf.contrib.metrics.streaming_precision,
prediction_key=tf.contrib.learn.PredictionKey.CLASSES),
"recall":
tf.contrib.learn.MetricSpec(
metric_fn=tf.contrib.metrics.streaming_recall,
prediction_key=tf.contrib.learn.PredictionKey.CLASSES)
}

把上述代码添加到ValidationMonitor的定义之前,然后修改关于ValidationMonitor的定义,添加一个metrics参数来打印准确率,预测值和召回率指标,这些都是之前在validation_metrics中定义过的。(损失值是始终都会被打印出来的,我们不需要明确去定义它)。

1
2
3
4
5
validation_monitor = tf.contrib.learn.monitors.ValidationMonitor(
test_set.data,
test_set.target,
every_n_steps=50,
metrics=validation_metrics)

执行该代码,我们可以看到预测值和召回率包含在输出中了:

1
2
3
4
5
INFO:tensorflow:Validation (step 50): recall = 0.0, loss = 1.20626, global_step = 1, precision = 0.0, accuracy = 0.266667
...
INFO:tensorflow:Validation (step 600): recall = 1.0, loss = 0.0530696, global_step = 571, precision = 1.0, accuracy = 0.966667
...
INFO:tensorflow:Validation (step 1500): recall = 1.0, loss = 0.0617403, global_step = 1452, precision = 1.0, accuracy = 0.966667


用验证监控器实现Early Stopping

注意在上述打印的日志中,到第600次迭代时,模型在测试集上已经到达接近于1的准确率和召回率,这种情况下我们可以使用early stopping

除了打印评估指标,ValidationMonitor实现early stopping也十分的简单。我们可以通过定义以下三个参数来实现:

Param Description
early_stopping_metric 引发early stopping的指标 (例如loss或accuracy),触发条件定义在early_stopping_roundsearly_stopping_metric_minimize中,默认的值是 "loss"
early_stopping_metric_minimize 如果模型是想要最小化early_stopping_metric则为True; 如果模型是想要最大化early_stopping_metric则为False。默认为True
early_stopping_rounds 设置一定的迭代次数,如果在这些次数内early_stopping_metric没有减少(early_stopping_metric_minimize值为True) 或者没有增加(early_stopping_metric_minimize值为False),训练将会停止。默认值为None,这意味着early stopping不会被触发。

我们对ValidationMonitor的定义做如下修改,当我们的损失持续了200次迭代都没有减小,模型的训练将会停止,将不会执行完fit中定义的2000次迭代。

1
2
3
4
5
6
7
8
validation_monitor = tf.contrib.learn.monitors.ValidationMonitor(
test_set.data,
test_set.target,
every_n_steps=50,
metrics=validation_metrics,
early_stopping_metric="loss",
early_stopping_metric_minimize=True,
early_stopping_rounds=200)

再次执行该代码,我们会看到模型提前停止了训练:

1
2
3
...
INFO:tensorflow:Validation (step 1150): recall = 1.0, loss = 0.056436, global_step = 1119, precision = 1.0, accuracy = 0.966667
INFO:tensorflow:Stopping. Best step: 800 with loss = 0.048313818872.

训练在第1150次迭代停止,这表明在之前的200次训练迭代中,损失都没有下降,总体来说,800次迭代就已经在测试集达到了最小的损失值。这表明通过减少训练次数可以进一步改进模型,同时改善超参数。


TensorBoard可视化日志数据

ValidationMonitor在训练期间生成了大量关于模型性能的原始数据,我们可以可视化它们来进一步了解训练情况——例如准确率是如何随着训练次数改变的。我们可以使用TensorBoard来实现这个功能,地址是我们保存模型数据的地址,打开命令行输入以下代码:

1
$ tensorboard --logdir=/tmp/iris_model/

然后打开浏览器在地址栏输入localhost:6006进入TensorBoard的操作面板。具体的使用方法可以参考我的笔记《TensorFlow 笔记(四):TensorBoard可视化》


完整代码

完整代码可以在TensorFlow官方GitHub下载:

iris_monitors.py


参考