Tensorflow 8之可视化工具Tensorboard

Tensorboard是一个用于可视化和理解tensorflow训练过程的一个web工具,可以通过保存tensorflow在训练过程中的graph的信息,然后利用tensorboard进行可视化,并在浏览器中显示出来。一般在安装tensorflow的过程中会默认安装tensorboard,可以在github上找到tensorboard的源码。

github tensorboard

本文主要以tensorflow提供的MNIST例子来进行了解tensorboard的使用

tf.summary API介绍

对于tensorboard而言,用于其可视化的数据是利用tensorflow在训练过程中保存下来的一些event信息,可以利用tensorflow的summary API来进行收集。比如MNIST利用cnn来训练的例子,这个例子主要是训练一个卷积神经网络cnn来识别MNIST数据中的像素点,我们需要记录学习率和目标函数的变化,就可以使用tf.summary.scalar操作来收集信息,可以获得如学习率损失函数等信息。

tf.summary.histogram可以用于收集来自特定激活(activations)的分布以及梯度或者权重的分布,

tf.summary.merge_all用于将summary的操作集合成一个op,用以产生summary数据,tf.summary.FileWriter用于将Protobuf数据写入硬盘,为了减少数据所以在每隔n步才做一次整合数据收集op。具体操作如下

def variable_summaries(var):
  """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
  with tf.name_scope('summaries'):
    mean = tf.reduce_mean(var)
    tf.summary.scalar('mean', mean)
    with tf.name_scope('stddev'):
      stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
    tf.summary.scalar('stddev', stddev)
    tf.summary.scalar('max', tf.reduce_max(var))
    tf.summary.scalar('min', tf.reduce_min(var))
    tf.summary.histogram('histogram', var)

MNIST训练收集summary过程

我们之前介绍的MNIST的cnn方法,采用的是两层卷积层、两层池化层以及最后一层全连接层,这一次貌似有点不同。首先看一下参数初始化过程,这里可以设置输入的数据的位置,也可以设置产生的log记录的目录,利用argparse进行转换,然后就是对输入的数据进行解压。

if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument(
    '--data_dir',
    type=str,
    default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
                         'tensorflow/mnist/input_data'),
    help='Directory for storing input data')

之后读入数据并读入数据到mnist,创建一个InteractiveSession()关于InteractiveSession(),与Session最大的区别就在于,Session需要在启动之前就构建整个计算图,而InteractiveSession()可以在运行图的时候插入一些op,对于此例需要在训练过程中记录信息就需要插入summary的op。

# Import data
mnist = input_data.read_data_sets(FLAGS.data_dir,
                                  fake_data=FLAGS.fake_data)
sess = tf.InteractiveSession()
# Create a multilayer model.

如下的变量创建过程也跟之前不同,这里使用了name_scope,意思是命名空间,目的是要区分不同的变量名,因为在整张图中很有可能会有重名的变量,所以这样可以进行区分,并且在可视化的时候这样可以更清晰。关于name_scope还可以参看:

# Input placeholders
with tf.name_scope('input'):
  x = tf.placeholder(tf.float32, [None, 784], name='x-input')
  y_ = tf.placeholder(tf.int64, [None], name='y-input')

with tf.name_scope('input_reshape'):
  image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
  tf.summary.image('input', image_shaped_input, 10)

这里解释一下这个input和input_reshape,我们知道mnist数据集是28x28的图片,输入的x是784(=28*28),需要将其转成二维矩阵,所以输入的tensor其实是有四个维度[batch_size, image_width,image_height, channels],也就是对应着tf.reshape(x, [-1, 28, 28, 1])中的第二个参数,这里的-1表示不用指定这一维度。这里的channel表示颜色信息,这里黑白就是1或0。

神经网络层

如下所示,此例采用函数nn_layer来进行每一层神经网络的初始化,其中包括对variablesummary的设置,默认采用线性整流函数relu为激活函数,

def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
  """Reusable code for making a simple neural net layer.

  It does a matrix multiply, bias add, and then uses ReLU to nonlinearize.
  It also sets up name scoping so that the resultant graph is easy to read,
  and adds a number of summary ops.
  """
  # Adding a name scope ensures logical grouping of the layers in the graph.
  with tf.name_scope(layer_name):
    # This Variable will hold the state of the weights for the layer
    with tf.name_scope('weights'):
      weights = weight_variable([input_dim, output_dim])
      variable_summaries(weights)
    with tf.name_scope('biases'):
      biases = bias_variable([output_dim])
      variable_summaries(biases)
    with tf.name_scope('Wx_plus_b'):
      preactivate = tf.matmul(input_tensor, weights) + biases
      tf.summary.histogram('pre_activations', preactivate)
    activations = act(preactivate, name='activation')
    tf.summary.histogram('activations', activations)
    return activations

下面看一下其中提到的summary操作,对于变量的summary操作如下,其中有一个mean = tf.reduce_mean(var),这里reduce的意思其实就是降维的意思,表示在指定的维度上求平均。这里的几个scalar操作其实相当于保存了如上调用了这个函数的weight维和biases维的平均值、最小值、最大值和stddev等,这些都是针对变量也就是variable而言的。对于histogram的信息,上面神经网络层中记录了activation的信息,这里采用的激活函数是relu,对于每个变量也保存了histogram。

def variable_summaries(var):
  """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
  with tf.name_scope('summaries'):
    mean = tf.reduce_mean(var)
    tf.summary.scalar('mean', mean)
    with tf.name_scope('stddev'):
      stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
    tf.summary.scalar('stddev', stddev)
    tf.summary.scalar('max', tf.reduce_max(var))
    tf.summary.scalar('min', tf.reduce_min(var))
    tf.summary.histogram('histogram', var)

第一层网络

如下为利用nn_layer()函数构建的第一层网络,输入x为人一个784像素点的数据,输出为500维的数据,具体来看一下在nn_layer()函数中的操作过程,首先创建了一个784x500的权重矩阵变量weights,然后创建了500维的偏差变量biases,然后创建了一个W*x+b的操作,或者说是x*W+b,这样对应维数才是正确的,然后将这个线性函数作为结果输入到激活函数relu中,作为一个op返回。

hidden1 = nn_layer(x, 784, 500, 'layer1')

第二层网络

这里介绍一下tf.nn.dropout,这是tensorflow中用于防止或减轻过拟合而使用的函数,一般用于全连接层,其中的参数keep_prob就是是否保留数据的概率,这样每一个计算得到的结果都以一定的概率进行保留。

所以对于第二层网络的输入就是第一层输出的n个500维的结果,这n个结果经过一定的概率进行随机筛选为dropped,作为第二层网络的输入,第二层网络其实就是一个全连接层,输出为判断的10个数字。

with tf.name_scope('dropout'):
  keep_prob = tf.placeholder(tf.float32)
  tf.summary.scalar('dropout_keep_probability', keep_prob)
  dropped = tf.nn.dropout(hidden1, keep_prob)

# Do not apply softmax activation yet, see below.
y = nn_layer(dropped, 500, 10, 'layer2', act=tf.identity)

训练并保存summary

最后计算交叉熵cross_entropy,并将减少交叉熵作为每一次训练的目标,用summary保存交叉熵和精确度,利用tf.summary.FileWriter来保存到文件中。

with tf.name_scope('cross_entropy'):
  with tf.name_scope('total'):
    cross_entropy = tf.losses.sparse_softmax_cross_entropy(
        labels=y_, logits=y)
tf.summary.scalar('cross_entropy', cross_entropy)

with tf.name_scope('train'):
  train_step = tf.train.AdamOptimizer(FLAGS.learning_rate).minimize(
      cross_entropy)

with tf.name_scope('accuracy'):
  with tf.name_scope('correct_prediction'):
    correct_prediction = tf.equal(tf.argmax(y, 1), y_)
  with tf.name_scope('accuracy'):
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
tf.summary.scalar('accuracy', accuracy)

# Merge all the summaries and write them out to
# /tmp/tensorflow/mnist/logs/mnist_with_summaries (by default)
merged = tf.summary.merge_all()
train_writer = tf.summary.FileWriter(FLAGS.log_dir + '/train', sess.graph)
test_writer = tf.summary.FileWriter(FLAGS.log_dir + '/test')
tf.global_variables_initializer().run()

如下可以看到整个训练的graph

TensorBoard 使用

按照如下步骤运行mnist_with_summries.py的程序便收集了足够的summary信息,然后启动tensorboard,在localhost:6006端口便可以访问,用浏览器打开即可

python mnist_with_summaries.py --data_dir=MNIST_data --log_dir=MNIST_log
tensorboard --logdir=MNIST_log

tensorboard中提供几个方面的可视化信息,包括scalars、images、graphs、distributions和histograms。

scalars 展示标量信息

上述表示准确率和交叉熵,其中横坐标为执行的步数,可以看出随着训练的步数增加,准确率在提高,而交叉熵在减小。同时也可以看出收敛速度比较慢,而且最后的准确率只有80%左右,说明只用两层神经网络没有达到很好的效果,可以加上池化层进行处理。其中红线表示训练过程,蓝线表示测试过程。

这个是第一层神经网络的biases的数据,也就是偏差,可以看出最大值在变大,最小值在变小,说明神经元之间的参数差异越来越大,这是我们希望得到的结果,因为不同的神经元应该去关注不同的特征,所以参数应该不同。

另外还可以看到权重矩阵weights也有着相似的变化趋势,stddev其实是标准差,也是反映了神经元之间的区分度。

images 和 graphs

images中会显示我们开始时进行了reshape的输入数据,显示784维的点数据进行变换为28x28的图片数据,类似的若进行声音处理还会有audio信息

graphs中会显示整个图以及其中的各个节点,点开各个op节点可以看到其输入以及输出特征。

distributions 和 histograms

distributions 表示的是神经元输出的分布,包括激活函数之前的分布和之后的分布。

histograms表示的是直方图,相比于distributions只是换了种显示方式而已。

最后附一篇tutorials: https://github.com/yongyehuang/Tensorflow-Tutorial