1. 程式人生 > >[乾貨|實踐] Tensorflow視覺化

[乾貨|實踐] Tensorflow視覺化

TensorBoard

  1. 簡介:TensorBoard是tensorflow官方推出的視覺化工具,它可以將模型訓練過程中的各種彙總資料展示出來,包括標量(Scalars)、圖片(Images)、音訊(Audio)、計算圖(Graphs)、資料分佈(Distributions)、直方圖(Histograms)和潛入向量(Embeddigngs)。
  2. 作用:tensorflow程式碼執行過程是先構建圖,然後在執行,所以對中間過程的除錯不太方便;除此之外,在使用tensorflow訓練大型深度學習神經網路時,中間的計算過程可能非常複雜,因此為了理解、除錯和優化網路,可以使用TensorBoard觀察訓練過程中的各種視覺化資料。

TensorBoard視覺化過程

在將視覺化過程之前,為了方便理解,對之中設計的一些概念做一個簡要介紹。

什麼是Graph和Session

graph定義了computation,它不計算任何東西,不包含任何值,只是定義了你在程式碼中指定的操作。關於graph的官方文件地址:tf.Graph。若不建立graph,TensorFlow在載入庫的時候會地建立圖,並且將這個圖指定為預設圖。可以通過使用tf.get_default_graph()函式獲得預設圖的控制代碼。在大多數的TensorFlow程式中,都只是用預設圖(graph)來處理。不過,當你定義的多個模型沒有相互內在的依賴的情況下,建立多個圖的時候很有用。下面,我們一個變數和三個操作定義一個圖形:==variable==返回變數的當前值。 ==initialize==將42的初始值賦給那個變數。 ==assign==給該變數賦值13的新值。

#Defining the Graph
graph = tf.Graph()
with graph.as_default():
  variable = tf.Variable(42, name='foo')
  initialize = tf.global_variables_initializer()
  assign = variable.assign(13)

Session會話允許執行graph或graph的一部分。它為此分配資源(在一臺或多臺機器上)並儲存中間結果和變數的實際值。要執行上面三個定義的操作中的任何一個時,我們需要為該graph建立一個會話Session。 因此會話Session需要分配記憶體來儲存變數的當前值。

#Running Computations in a Session
with tf.Session(graph=graph) as sess:
  sess.run(initialize)
  sess.run(assign)
  print(sess.run(variable))
# Output: 13

視覺化過程

  1. 先建立一個graph
  2. 確定要在graph中的哪些節點放置summary operations以記錄資訊
    使用tf.summary.scalar記錄標量
    使用tf.summary.histogram記錄資料的直方圖
    使用tf.summary.distribution記錄資料的分佈圖
    使用tf.summary.image記錄影象資料
    ……
  3. operations並不會去真的執行計算,除非你告訴他們需要去run,或者它被其他的需要run的operation所依賴。而我們上一步建立的這些summary operations其實並不被其他節點依賴,因此,我們需要特地去執行所有的summary節點。但是呢,一份程式下來可能有超多這樣的summary 節點,要手動一個一個去啟動自然是及其繁瑣的,因此我們可以使用tf.summary.merge_all去將所有summary節點合併成一個節點,只要執行這個節點,就能產生所有我們之前設定的summary data。
  4. 使用tf.summary.FileWriter將執行後輸出的資料都儲存到本地磁碟中
  5. 執行整個程式,並在命令列輸入執行tensorboard的指令,之後開啟web端可檢視視覺化的結果

Tensorboard使用案例

使用最基礎的識別手寫字型的案例,建立一個簡單的神經網路,讓大家瞭解如何使用Tensorboard。可以從github獲得原始碼。

匯入包,定義超引數,載入資料

  1. 首先還是匯入需要的包:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import sys
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
  1. 定義固定的超引數,方便待使用時直接傳入。如果你問,這個超引數為啥要這樣設定,如何選擇最優的超引數?這個問題此處先不討論,超引數的選擇在機器學習建模中最常用的方法就是“交叉驗證法”。而現在假設我們已經獲得了最優的超引數,設定學利率為0.001,dropout的保留節點比例為0.9,最大迴圈次數為1000.
    另外,還要設定兩個路徑,第一個是資料下載下來存放的地方,一個是summary輸出儲存的地方。
max_step = 1000  # 最大迭代次數
learning_rate = 0.001   # 學習率
dropout = 0.9   # dropout時隨機保留神經元的比例

data_dir = os.path.join('data', 'mnist')# 樣本資料儲存的路徑
if not os.path.exists('log'):
    os.mkdir('log')
log_dir = 'log'   # 輸出日誌儲存的路徑

3.接著載入資料,下載資料是直接呼叫了tensorflow提供的函式read_data_sets,輸入兩個引數,第一個是下載到資料儲存的路徑,第二個one_hot表示是否要將類別標籤進行獨熱編碼。它首先回去找制定目錄下有沒有這個資料檔案,沒有的話才去下載,有的話就直接讀取。所以第一次執行這個命令,速度會比較慢。

mnist = input_data.read_data_sets(data_dir,one_hot=True)

建立特徵與標籤的佔位符,儲存輸入的圖片資料到summary

  1. 建立tensorflow的預設會話:
sess = tf.InteractiveSession()
  1. 建立輸入資料的佔位符,分別建立特徵資料x,標籤資料y_
    在tf.placeholder()函式中傳入了3個引數,第一個是定義資料型別為float32;第二個是資料的大小,特徵資料是大小784的向量,標籤資料是大小為10的向量,None表示不定死大小,到時候可以傳入任何數量的樣本;第3個引數是這個佔位符的名稱。
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')
  1. 使用tf.summary.image儲存影象資訊
    特徵資料其實就是影象的畫素資料拉昇成一個1*784的向量,現在如果想在tensorboard上還原出輸入的特徵資料對應的圖片,就需要將拉昇的向量轉變成28 * 28 * 1的原始畫素了,於是可以用tf.reshape()直接重新調整特徵資料的維度:
    將輸入的資料轉換成[28 * 28 * 1]的shape,儲存成另一個tensor,命名為image_shaped_input。
    為了能使圖片在tensorbord上展示出來,使用tf.summary.image將圖片資料彙總給tensorbord。
    tf.summary.image()中傳入的第一個引數是命名,第二個是圖片資料,第三個是最多展示的張數,此處為10張
with tf.name_scope('input_reshape'):
    image_shaped_input = tf.reshape(x, [-1, 28, 28, 1])
    tf.summary.image('input', image_shaped_input, 10)

建立初始化引數的方法,與引數資訊彙總到summary的方法

  1. 在構建神經網路模型中,每一層中都需要去初始化引數w,b,為了使程式碼簡介美觀,最好將初始化引數的過程封裝成方法function。
    建立初始化權重w的方法,生成大小等於傳入的shape引數,標準差為0.1,正態分佈的隨機數,並且將它轉換成tensorflow中的variable返回。
def weight_variable(shape):
    """Create a weight variable with appropriate initialization."""
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

建立初始換偏執項b的方法,生成大小為傳入引數shape的常數0.1,並將其轉換成tensorflow的variable並返回

def bias_variable(shape):
    """Create a bias variable with appropriate initialization."""
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)
  1. 我們知道,在訓練的過程在引數是不斷地在改變和優化的,我們往往想知道每次迭代後引數都做了哪些變化,可以將引數的資訊展現在tenorbord上,因此我們專門寫一個方法來收錄每次的引數資訊。
def variable_summaries(var):
    """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
    with tf.name_scope('summaries'):
      # 計算引數的均值,並使用tf.summary.scaler記錄
      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.scaler記錄記錄下標準差,最大值,最小值
      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)

構建神經網路層

  1. 建立第一層隱藏層
    建立一個構建隱藏層的方法,輸入的引數有:
    input_tensor:特徵資料
    input_dim:輸入資料的維度大小
    output_dim:輸出資料的維度大小(=隱層神經元個數)
    layer_name:名稱空間
    act=tf.nn.relu:啟用函式(預設是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.
    """
    # 設定名稱空間
    with tf.name_scope(layer_name):
      # 呼叫之前的方法初始化權重w,並且呼叫引數資訊的記錄方法,記錄w的資訊
      with tf.name_scope('weights'):
        weights = weight_variable([input_dim, output_dim])
        variable_summaries(weights)
      # 呼叫之前的方法初始化權重b,並且呼叫引數資訊的記錄方法,記錄b的資訊
      with tf.name_scope('biases'):
        biases = bias_variable([output_dim])
        variable_summaries(biases)
      # 執行wx+b的線性計算,並且用直方圖記錄下來
      with tf.name_scope('linear_compute'):
        preactivate = tf.matmul(input_tensor, weights) + biases
        tf.summary.histogram('linear', preactivate)
      # 將線性輸出經過激勵函式,並將輸出也用直方圖記錄下來
      activations = act(preactivate, name='activation')
      tf.summary.histogram('activations', activations)

      # 返回激勵層的最終輸出
      return activations

呼叫隱層建立函式建立一個隱藏層:輸入的維度是特徵的維度784,神經元個數是500,也就是輸出的維度。

hidden1 = nn_layer(x, 784, 500, 'layer1')
  1. 建立一個dropout層,,隨機關閉掉hidden1的一些神經元,並記錄keep_prob
 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)
  1. 建立一個輸出層,輸入的維度是上一層的輸出:500,輸出的維度是分類的類別種類:10,啟用函式設定為全等對映identity.(暫且先別使用softmax,會放在之後的損失函式中一起計算)
y = nn_layer(dropped, 500, 10, 'layer2', act=tf.identity)

建立損失函式

使用tf.nn.softmax_cross_entropy_with_logits來計算softmax並計算交叉熵損失,並且求均值作為最終的損失值。

with tf.name_scope('loss'):
    # 計算交叉熵損失(每個樣本都會有一個損失)
    diff = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y)
    with tf.name_scope('total'):
      # 計算所有樣本交叉熵損失的均值
      cross_entropy = tf.reduce_mean(diff)

tf.summary.scalar('loss', cross_entropy)

訓練,並計算準確率

  1. 使用AdamOptimizer優化器訓練模型,最小化交叉熵損失
with tf.name_scope('train'):
    train_step = tf.train.AdamOptimizer(learning_rate).minimize(
        cross_entropy)
  1. 計算準確率,並用tf.summary.scalar記錄準確率
with tf.name_scope('accuracy'):
    with tf.name_scope('correct_prediction'):
      # 分別將預測和真實的標籤中取出最大值的索引,弱相同則返回1(true),不同則返回0(false)
      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)

合併summary operation, 執行初始化變數

將所有的summaries合併,並且將它們寫到之前定義的log_dir路徑

# summaries合併
merged = tf.summary.merge_all()
# 寫到指定的磁碟路徑中
train_writer = tf.summary.FileWriter(log_dir + '/train', sess.graph)
test_writer = tf.summary.FileWriter(log_dir + '/test')

# 執行初始化所有變數
tf.global_variables_initializer().run()

準備訓練與測試的兩個資料,迴圈執行整個graph進行訓練與評估

  1. 現在我們要獲取之後要喂入的資料.
    如果是train==true,就從mnist.train中獲取一個batch樣本,並且設定dropout值;
    如果是不是train=false,則獲取minist.test的測試資料,並且設定keep_prob為1,即保留所有神經元開啟。
def feed_dict(train):
    """Make a TensorFlow feed_dict: maps data onto Tensor placeholders."""
    if train:
      xs, ys = mnist.train.next_batch(100)
      k = dropout
    else:
      xs, ys = mnist.test.images, mnist.test.labels
      k = 1.0
    return {x: xs, y_: ys, keep_prob: k}
  1. 開始訓練模型。
    每隔10步,就進行一次merge, 並列印一次測試資料集的準確率,然後將測試資料集的各種summary資訊寫進日誌中。
    每隔100步,記錄原資訊
    其他每一步時都記錄下訓練集的summary資訊並寫到日誌中。
for i in range(max_steps):
    if i % 10 == 0:  # 記錄測試集的summary與accuracy
      summary, acc = sess.run([merged, accuracy], feed_dict=feed_dict(False))
      test_writer.add_summary(summary, i)
      print('Accuracy at step %s: %s' % (i, acc))
    else:  # 記錄訓練集的summary
      if i % 100 == 99:  # Record execution stats
        run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
        run_metadata = tf.RunMetadata()
        summary, _ = sess.run([merged, train_step],
                              feed_dict=feed_dict(True),
                              options=run_options,
                              run_metadata=run_metadata)
        train_writer.add_run_metadata(run_metadata, 'step%03d' % i)
        train_writer.add_summary(summary, i)
        print('Adding run metadata for', i)
      else:  # Record a summary
        summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True))
        train_writer.add_summary(summary, i)
train_writer.close()
test_writer.close()

執行程式,tensorboard生成視覺化

  1. 執行整個程式,在程式中定義的summary node就會將要記錄的資訊全部儲存在指定的logdir路徑中了,訓練的記錄會存一份檔案,測試的記錄會存一份檔案。
  2. 進入linux命令列,執行以下程式碼,等號後面加上summary日誌儲存的路徑(在程式第一步中就事先自定義了)
tensorboard --logdir='log'

執行命令之後會出現一條資訊,上面有網址,將網址在瀏覽器中開啟就可以看到我們定義的視覺化資訊了。視覺化後的展示詳情見GitHub中README部分。

參考資料