1. 程式人生 > >機器學習筆記(二十):TensorFlow實戰十二(TensorBoard視覺化)

機器學習筆記(二十):TensorFlow實戰十二(TensorBoard視覺化)

1 - 引言

前面已經介紹到TensorFlow可以實現許多非常常用的神經網路結構,有的網路結構十分複雜,裡面的引數關係更是難以管理。因此,TensorFlow提供了一個視覺化工具TensorBoard。可以有效的展示執行過程中的計算圖、各種指標隨著時間的變化趨勢以及訓練中使用到的影象等資訊。

2 - TensorBoard簡介

TensorBoard是TensorFlow的視覺化工具,可以呈現當前TensorFlow程式執行的最新狀態。以下程式碼展示了一個簡單的TensorFlow程式,在這個程式中完成了TensorBoard日誌輸出的功能

import tensorflow as tf

input1 = tf.constant([1.0, 2.0, 3.0], name="input1")

input2 = tf.Variable(tf.random_uniform([3], name="input2"))

output = tf.add_n([input1, input2], name="add")

writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())

writer.close()

在Anaconda Prompt輸入以下程式碼生成TensorBoard網址
(首先啟用tensorflow環境,再cd進存放Log的目錄)
在這裡插入圖片描述
在瀏覽器中輸入網址,得到計算圖的視覺化介面
在這裡插入圖片描述

3 - TensorFlow計算圖視覺化

下面將更加詳細的介紹如何利用TensorBoard將計算圖上的各個資料視覺化

3.1 - 名稱空間與TensorBoard圖上節點

為了很好的組織視覺化效果圖中的計算節點,讓視覺化結果更加便於理解,TensorBoard支援通過TensorFlow名稱空間來整理視覺化效果圖上的節點。在TensorBoard的預設檢視中,TensorFlow計算圖中同一個名稱空間下的所有節點會被縮略成一個節點,只有頂層名稱空間的節點才會被顯示在TensorBoard視覺化效果圖上。

下面給出一個示例將一個神經網路結構圖可視化出來。

import os
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#載入mnist_inference.py中定義的常量和前向傳播的函式。
import mnist_inference

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最開始的學習率
LEARNING_RATE_DECAY = 0.99 # 在指數衰減學習率的過程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型複雜度的正則化項在損失函式中的係數
TRAINING_STEPS = 30000 # 訓練輪數,注意,訓練一個Batch就是一個step
MOVING_AVERAGE_DECAY = 0.99 # 滑動平均模型的衰減率,最後我會講解滑動平均模型


def train(mnist):
    #將處理輸入資料的計算都放在名字為“input"的名稱空間下
    with tf.name_scope('input'):
        # 定義輸入輸出placeholder。
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 直接使用mnist_inference.py中定義的前向傳播過程
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)
    #將處理滑動平均相關的計算都放在名為moving_average的名稱空間下
    with tf.name_scope('moving_average'):

        # 定義損失函式、學習率、滑動平均操作以及訓練過程
        variable_averages = tf.train.ExponentialMovingAverage(
            MOVING_AVERAGE_DECAY, global_step
        )
        variable_averages_op = variable_averages.apply(
            tf.trainable_variables()
        )
    #將損失函式相關的計算都放在loss_function的名稱空間下
    with tf.name_scope('loss_function'):
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=y, labels=tf.argmax(y_, 1)
        )
        cross_entropy_mean = tf.reduce_mean(cross_entropy)
        loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    #將定義學習率、優化方法以及每一輪訓練需要執行的操作都放在名字為“train_step”的名稱空間下
    with tf.name_scope("train_step"):
        learning_rate = tf.train.exponential_decay(
            LEARNING_RATE_BASE,
            global_step,
            mnist.train.num_examples / BATCH_SIZE,
            LEARNING_RATE_DECAY
        )
        train_step = tf.train.GradientDescentOptimizer(learning_rate)\
                       .minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')


    startTime = time.time()

    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        # 在訓練過程中不再測試模型在驗證資料上的表現,驗證和測試的過程將會有一個獨
        # 立的程式來完成。
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000輪儲存一次模型
            if i % 1000 == 0:
                # 輸出當前的訓練情況。這裡只輸出了模型在當前訓練batch上的損失
                # 函式大小。通過損失函式的大小可以大概瞭解訓練的情況。在驗證數
                # 據集上正確率的資訊會有一個單獨的程式來生成
                print("After %d training step(s), loss on training "
                      "batch is %g." % (step, loss_value))

        print("Time taken: %f" % (time.time() - startTime))
        writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())
        writer.close()
# 主程式入口
def main(argv=None):
    # 宣告處理MNIST資料集的類,這個類在初始化時會自動下載資料。
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一個主程式入口,tf.app.run會呼叫上面定義的main函式
if __name__ == "__main__":

    tf.app.run()

執行之後我們會得到這樣一個計算圖
在這裡插入圖片描述

3.2 - 節點資訊

除了展示計算圖的結構外,還可以將節點的基本資訊以及執行時消耗的時間和空間都顯示出來,這可以幫助我們有針對性的優化我們的神經網路模型。我們還是同樣以MNIST神經網路為例,將計算節點的執行時間和消耗的記憶體寫入TenosrBoard的日誌檔案中。

import os
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

#載入mnist_inference.py中定義的常量和前向傳播的函式。
import mnist_inference

BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8 # 最開始的學習率
LEARNING_RATE_DECAY = 0.99 # 在指數衰減學習率的過程中用到
REGULARIZATION_RATE = 0.0001 # 描述模型複雜度的正則化項在損失函式中的係數
TRAINING_STEPS = 30000 # 訓練輪數,注意,訓練一個Batch就是一個step
MOVING_AVERAGE_DECAY = 0.99 # 滑動平均模型的衰減率,最後我會講解滑動平均模型
log_dir='/path/to/log'

def train(mnist):
    #將處理輸入資料的計算都放在名字為“input"的名稱空間下
    with tf.name_scope('input'):
        # 定義輸入輸出placeholder。
        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 直接使用mnist_inference.py中定義的前向傳播過程
    y = mnist_inference.inference(x, regularizer)
    global_step = tf.Variable(0, trainable=False)
    #將處理滑動平均相關的計算都放在名為moving_average的名稱空間下
    with tf.name_scope('moving_average'):

        # 定義損失函式、學習率、滑動平均操作以及訓練過程
        variable_averages = tf.train.ExponentialMovingAverage(
            MOVING_AVERAGE_DECAY, global_step
        )
        variable_averages_op = variable_averages.apply(
            tf.trainable_variables()
        )
    #將損失函式相關的計算都放在loss_function的名稱空間下
    with tf.name_scope('loss_function'):
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
            logits=y, labels=tf.argmax(y_, 1)
        )
        cross_entropy_mean = tf.reduce_mean(cross_entropy)
        loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    #將定義學習率、優化方法以及每一輪訓練需要執行的操作都放在名字為“train_step”的名稱空間下
    with tf.name_scope("train_step"):
        learning_rate = tf.train.exponential_decay(
            LEARNING_RATE_BASE,
            global_step,
            mnist.train.num_examples / BATCH_SIZE,
            LEARNING_RATE_DECAY
        )
        train_step = tf.train.GradientDescentOptimizer(learning_rate)\
                       .minimize(loss, global_step=global_step)
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')


    startTime = time.time()

    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        train_writer = tf.summary.FileWriter(log_dir + '/train', sess.graph)
        # 在訓練過程中不再測試模型在驗證資料上的表現,驗證和測試的過程將會有一個獨
        # 立的程式來完成。
        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step],
                                           feed_dict={x: xs, y_: ys})
            # 每1000輪儲存一次模型
            if i % 1000 == 0:
                #配置執行時需要記錄的資訊
                run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
                #執行時記錄執行資訊的proto
                run_metadata = tf.RunMetadata()
                #將配置資訊和記錄執行資訊的proto傳入執行的過程,從而記錄執行時每一個節點的時間、空間開銷資訊
                _,loss_value,step = sess.run(
                    [train_op,loss,global_step],feed_dict={x:xs,y_:ys},
                    options=run_options,run_metadata=run_metadata)
                #將節點在執行時的資訊寫入日誌檔案
                train_writer.add_run_metadata(run_metadata,'setp%03d'%i)
                # 輸出當前的訓練情況。這裡只輸出了模型在當前訓練batch上的損失
                # 函式大小。通過損失函式的大小可以大概瞭解訓練的情況。在驗證數
                # 據集上正確率的資訊會有一個單獨的程式來生成
                print("After %d training step(s), loss on training "
                      "batch is %g." % (step, loss_value))
            else:
                _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
        print("Time taken: %f" % (time.time() - startTime))
        writer = tf.summary.FileWriter("/path/to/log", tf.get_default_graph())
        writer.close()
# 主程式入口
def main(argv=None):
    # 宣告處理MNIST資料集的類,這個類在初始化時會自動下載資料。
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一個主程式入口,tf.app.run會呼叫上面定義的main函式
if __name__ == "__main__":

    tf.app.run()

在這裡插入圖片描述

3.3 - 監控指標視覺化

TensorBoard除了可以視覺化計算圖,還可以視覺化執行過程各種有助於瞭解程式執行狀態的監控指標。
下面介紹SCALARS 、IMAGES、AUDIO、DISTRIBUTIONS、HISTOGRAMS這四個欄目並且新增進監控指標中

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


# ####  1. 生成變數監控資訊並定義生成監控資訊日誌的操作。

SUMMARY_DIR = "/path/to/log"
BATCH_SIZE = 100
TRAIN_STEPS = 30000


# var給出了需要記錄的張量,name給出了在視覺化結果中顯示的圖表名稱,這個名稱一般和變數名一致
def variable_summaries(var, name):
    # 將生成監控資訊的操作放在同一個名稱空間下
    with tf.name_scope('summaries'):
        # 通過tf.histogram_summary函式記錄張量中元素的取值分佈
        # tf.summary.histogram函式會生成一個Summary protocol buffer.
        # 將Summary 寫入TensorBoard 門志文件後,在HISTOGRAMS 欄,和
        # DISTRIBUTION 欄下都會出現對應名稱的圖表。和TensorFlow 中其他操作類似,
        # tf.summary.histogram 函式不會立刻被執行,只有當sess.run 函式明確呼叫這個操作時, TensorFlow
        # 才會具正生成並輸出Summary protocol buffer.

        tf.summary.histogram(name, var)

        # 計算變數的平均值,並定義生成平均值資訊日誌的操作,記錄變數平均值資訊的日誌標籤名
        # 為'mean/'+name,其中mean為名稱空間,/是名稱空間的分隔符
        # 在相同名稱空間中的監控指標會被整合到同一欄中,name則給出了當前監控指標屬於哪一個變數

        mean = tf.reduce_mean(var)
        tf.summary.scalar('mean/' + name, mean)

        # 計算變數的標準差,並定義生成其日誌檔案的操作
        stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
        tf.summary.scalar('stddev/' + name, stddev)


# #### 2. 生成一層全連結的神經網路。
def nn_layer(input_tensor, input_dim, output_dim, layer_name, act=tf.nn.relu):
    # 將同一層神經網路放在一個統一的名稱空間下
    with tf.name_scope(layer_name):
        # 宣告神經網路邊上的權值,並呼叫權重監控資訊日誌的函式
        with tf.name_scope('weights'):
            weights = tf.Variable(tf.truncated_normal([input_dim, output_dim], stddev=0.1))
            variable_summaries(weights, layer_name + '/weights')

        # 宣告神經網路邊上的偏置,並呼叫偏置監控資訊日誌的函式
        with tf.name_scope('biases'):
            biases = tf.Variable(tf.constant(0.0, shape=[output_dim]))
            variable_summaries(biases, layer_name + '/biases')
        with tf.name_scope('Wx_plus_b'):
            preactivate = tf.matmul(input_tensor, weights) + biases
            # 記錄神經網路節點輸出在經過啟用函式之前的分佈
            tf.summary.histogram(layer_name + '/pre_activations', preactivate)
        activations = act(preactivate, name='activation')

        # 記錄神經網路節點輸出在經過啟用函式之後的分佈。

        """
        對於layerl ,因為使用了ReLU函式作為啟用函式,所以所有小於0的值部被設為了0。於是在啟用後
        的layerl/activations 圖上所有的值都是大於0的。而對於layer2 ,因為沒有使用啟用函式,
        所以layer2/activations 和layer2/pre_activations 一樣。
        """
        tf.summary.histogram(layer_name + '/activations', activations)
        return activations


def main(_):
    mnist = input_data.read_data_sets("/path/to/MNIST_data", one_hot=True)

    with tf.name_scope('input'):
        x = tf.placeholder(tf.float32, [None, 784], name='x-input')
        y_ = tf.placeholder(tf.float32, [None, 10], 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)
        # 將輸入變數還原成圖片的畫素矩陣,並通過tf.iamge_summary函式定義將當前的圖片資訊寫入日誌的操作

    hidden1 = nn_layer(x, 784, 500, 'layer1')
    y = nn_layer(hidden1, 500, 10, 'layer2', act=tf.identity)

    # 計算交叉熵並定義生成交叉熵監控日誌的操作。
    with tf.name_scope('cross_entropy'):
        cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y, labels=y_))
        tf.summary.scalar('cross_entropy', cross_entropy)

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

    """
    計算模型在當前給定資料上的正確率,並定義生成正確率監控日誌的操作。如果在sess.run()
    時給定的資料是訓練batch,那麼得到的正確率就是在這個訓練batch上的正確率;如果
    給定的資料為驗證或者測試資料,那麼得到的正確率就是在當前模型在驗證或者測試資料上
    的正確率。
    """
    with tf.name_scope('accuracy'):
        with tf.name_scope('correct_prediction'):
            correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
        with tf.name_scope('accuracy'):
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        tf.summary.scalar('accuracy', accuracy)

    # tf.scalar_summary,tf.histogram_summary,tf.image_summary函式都不會立即執行,需要通過sess.run來呼叫這些函式
    # 因為程式重定義的寫日誌的操作非常多,一一呼叫非常麻煩,所以Tensorflow提供了tf.merge_all_summaries函式來整理所有的日誌生成操作。
    # 在Tensorflow程式執行的過程中只需要執行這個操作就可以將程式碼中定義的所有日誌生成操作全部執行一次,從而將所有日誌檔案寫入檔案。

    merged = tf.summary.merge_all()

    with tf.Session() as sess:
        # 初始化寫日誌的writer,並將當前的Tensorflow計算圖寫入日誌
        summary_writer = tf.summary.FileWriter(SUMMARY_DIR, sess.graph)
        tf.global_variables_initializer().run()

        for i in range(TRAIN_STEPS):
            if i % 1000 == 0:
                print(i)
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            # 執行訓練步驟以及所有的日誌生成操作,得到這次執行的日誌。
            summary, _ = sess.run([merged, train_step], feed_dict={x: xs, y_: ys})
            # 將得到的所有日誌寫入日誌檔案,這樣TensorBoard程式就可以拿到這次執行所對應的
            # 執行資訊。
            summary_writer.add_summary(summary, i)

    summary_writer.close()


if __name__ == '__main__':
    tf.app.run()

(1)SCALARS
展示的是標量的資訊,我程式中用tf.summary.scalars()定義的資訊都會在這個視窗。
在這裡插入圖片描述

(2)IMAGES
在程式中我們設定了一處儲存了影象資訊,就是在轉變了輸入特徵的shape,然後記錄到了image中,於是在tensorflow中就會還原出原始的圖片了:
在這裡插入圖片描述
(3)AUDIO
這裡展示的是聲音的資訊,但本案例中沒有涉及到聲音的。
(5)DISTRIBUTIONS
這裡檢視的是神經元輸出的分佈,有啟用函式之前的分佈,啟用函式之後的分佈等。
在這裡插入圖片描述
(6)HISTOGRAMS
也可以看以上資料的直方圖
在這裡插入圖片描述

4 - 總結

利用TensorBoard可以很好的幫助我們視覺化神經網路的結構、引數資訊、計算代價等我們需要了解的資訊。進而進一步的幫助我們改善神經網路。