學習TensorFlow,TensorBoard可視化網絡結構和參數
在學習深度網絡框架的過程中,我們發現一個問題,就是如何輸出各層網絡參數,用於更好地理解,調試和優化網絡?針對這個問題,TensorFlow開發了一個特別有用的可視化工具包:TensorBoard,既可以顯示網絡結構,又可以顯示訓練和測試過程中各層參數的變化情況。本博文分為四個部分,第一部分介紹相關函數,第二部分是代碼測試,第三部分是運行結果,第四部分介紹相關參考資料。
一. 相關函數
TensorBoard的輸入是tensorflow保存summary data的日誌文件。日誌文件名的形式如:events.out.tfevents.1467809796.lei-All-Series 或 events.out.tfevents.1467809800.lei-All-Series。TensorBoard可讀的summary data有scalar,images,audio,histogram和graph。那麽怎麽把這些summary data保存在日誌文件中呢?
數值如學習率,損失函數用scalar_summary函數。tf.scalar_summary(節點名稱,獲取的數據)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) tf.scalar_summary(‘accuracy‘, accuracy)
各層網絡權重,偏置的分布,用histogram_summary函數
preactivate = tf.matmul(input_tensor, weights) + biases tf.histogram_summary(layer_name + ‘/pre_activations‘, preactivate)
其他幾種summary data也是同樣的方式獲取,只是對應的獲取函數名稱換一下。這些獲取summary data函數節點和graph是獨立的,調用的時候也需要運行session。當需要獲取的數據較多的時候,我們一個一個去保存獲取到的數據,以及一個一個去運行會顯得比較麻煩。tensorflow提供了一個簡單的方法,就是合並所有的summary data的獲取函數,保存和運行只對一個對象進行操作。比如,寫入默認路徑中,比如/tmp/mnist_logs (by default)
merged = tf.merge_all_summaries() train_writer= tf.train.SummaryWriter(FLAGS.summaries_dir + ‘/train‘, sess.graph) test_writer = tf.train.SummaryWriter(FLAGS.summaries_dir + ‘/test‘)
SummaryWriter從tensorflow獲取summary data,然後保存到指定路徑的日誌文件中。以上是在建立graph的過程中,接下來執行,每隔一定step,寫入網絡參數到默認路徑中,形成最開始的文件:events.out.tfevents.1467809796.lei-All-Series 或 events.out.tfevents.1467809800.lei-All-Series。
for i in range(FLAGS.max_steps): if i % 10 == 0: # Record summaries and test-set 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: # Record train set summarieis, and train summary, _ = sess.run([merged, train_step], feed_dict=feed_dict(True)) train_writer.add_summary(summary, i)
tensorflow 可視化
tensorflow
的可視化是使用summary
和tensorboard
合作完成的.
基本用法
首先明確一點,summary
也是op
.
輸出網絡結構
with tf.Session() as sess:
writer = tf.summary.FileWriter(your_dir, sess.graph)
- 1
- 2
- 1
- 2
命令行運行tensorboard --logdir=your_dir
,然後瀏覽器輸入127.0.1.1:6006
這樣你就可以在tensorboard
中看到你的網絡結構圖了
可視化參數
#ops
loss = ...
tf.summary.scalar("loss", loss)
merged_summary = tf.summary.merge_all()
init = tf.global_variable_initializer()
with tf.Session() as sess:
writer = tf.summary.FileWriter(your_dir, sess.graph)
sess.run(init)
for i in xrange(100):
_,summary = sess.run([train_op,merged_summary], feed_dict)
writer.add_summary(summary, i)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
這時,打開tensorboard
,在EVENTS
可以看到loss
隨著i
的變化了,如果看不到的話,可以在代碼最後加上writer.flush()
試一下,原因後面說明。
函數介紹
tf.summary.merge_all
: 將之前定義的所有summary op
整合到一起FileWriter
: 創建一個file writer
用來向硬盤寫summary
數據,tf.summary.scalar(summary_tags, Tensor/variable)
: 用於標量的summary
tf.summary.image(tag, tensor, max_images=3, collections=None, name=None)
:tensor,必須4維,形狀[batch_size, height, width, channels],max_images
(最多只能生成3張圖片的summary
),覺著這個用在卷積中的kernel
可視化很好用.max_images
確定了生成的圖片是[-max_images: ,height, width, channels],還有一點就是,TensorBord
中看到的image summary
永遠是最後一個global step
的tf.summary.histogram(tag, values, collections=None, name=None)
:values,任意形狀的tensor
,生成直方圖summary
tf.summary.audio(tag, tensor, sample_rate, max_outputs=3, collections=None, name=None)
FileWriter
註意:add_summary
僅僅是向FileWriter
對象的緩存中存放event data
。而向disk
上寫數據是由FileWrite對象
控制的。下面通過FileWriter
的構造函數來介紹這一點!!!
tf.summary.FileWriter.__init__(logdir, graph=None, max_queue=10, flush_secs=120, graph_def=None)
Creates a FileWriter and an event file.
# max_queue: 在向disk寫數據之前,最大能夠緩存event的個數
# flush_secs: 每多少秒像disk中寫數據,並清空對象緩存
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
註意
如果使用
writer.add_summary(summary,global_step)
時沒有傳global_step
參數,會使scarlar_summary
變成一條直線。只要是在計算圖上的
Summary op
,都會被merge_all
捕捉到, 不需要考慮變量生存空間問題!- 如果執行一次,
disk
上沒有保存Summary
數據的話,可以嘗試下file_writer.flush()
小技巧
如果想要生成的summary有層次的話,記得在summary
外面加一個name_scope
with tf.name_scope("summary_gradients"):
tf.summary.histgram("name", gradients)
- 1
- 2
- 1
- 2
這樣,tensorboard
在顯示的時候,就會有一個sumary_gradients
一集目錄。
主旨
在TensorFlow中每開發一個模型,都可以使用可視化調試工具TensorBoard得到這個session的Graph,這張圖的結構和內容都不同於機器學習教材上介紹的典型神經網絡結構圖。本文試圖通過代碼實驗理解Graph的含義,用以指導日常調試。
代碼和運行環境
代碼:https://github.com/wangyaobupt/TF_Graph singleNerualNode.py
運行環境:
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
>>> tf.__version__
‘1.0.0-rc2’
問題介紹
在TensorFlow開發中,TensorBoard是一項很有用的可視化調試工具。在TensorBoard中,除了開發者自定義輸出的數據結構之外,還包括表征神經網絡模型的GRAPH。
在常見的機器學習教材中,神經網絡的結構一般通過類似於如下的圖形表示,下圖引用自http://www.extremetech.com/extreme/215170-artificial-neural-networks-are-changing-the-world-what-are-they
但是,TensorBoard生成的Graph與上述形態完全不同,我自己開發的某個神經網絡生成的Graph如下圖所示
如何理解這張圖?本文試圖通過代碼實驗做一些嘗試。
案例1:單神經元
為了簡化分析場景,我們設計一個由單個神經元構成的神經網絡,這個神經元存在numberOfInputDims
輸入,神經元的每條輸入邊都有權重因子wi,此外神經元還有bias偏置項,激活函數為sigmoid. 這個結構可以用如下的結構圖描述
在TensorFlow中,如下代碼即可定義出滿足上述結構的神經網絡,其中a就是上圖中的Y
inputTensor = tf.placeholder(tf.float32, [None, numberOfInputDims], name=‘inputTensor‘)
labelTensor=tf.placeholder(tf.float32, [None, 1], name=‘LabelTensor‘)
W = tf.Variable(tf.random_uniform([numberOfInputDims, 1], -1.0, 1.0), name=‘weights‘)
b = tf.Variable(tf.zeros([1]), name=‘biases‘)
a = tf.nn.sigmoid(tf.matmul(inputTensor, W) + b, name=‘activation‘)
- 1
- 2
- 3
- 4
- 5
使用TensorBoard生成的Graph如下圖所示
問題1:Graph中的邊(Edge)代表什麽
根據TF官方文檔對於Graph的說明,上圖中實線表示數據依賴(tensor在運算符之間的流動關系),而虛線表示控制依賴關系,原文引用如下
TensorFlow graphs have two kinds of connections: data dependencies and control dependencies. Data dependencies show the flow of tensors between two ops and are shown as solid arrows, while control dependencies use dotted lines.
但是,我們得到的Graph中所有的實線都沒有標出箭頭方向,他們之間是誰依賴誰呢?
回答這個問題需要回到代碼中,從代碼可以知道:weight是由tf.random_uniform([numberOfInputDims, 1]初始化得到的;weight和inputTensor做矩陣乘法得到中間變量;中間變量再加上bias得到激活函數的輸入;以此類推。
因此,TensorBoard Graph的上下方位代表了數據依賴的方向:數據總是從下方的節點流向上方的節點,上方節點依賴於下方節點
接下來討論控制依賴。
上圖中weight和bias節點都存在依賴於init運算的虛線,這說明weight和bias節點都需要初始化。虛線指向的運算符(op)是被依賴的運算符(op)
問題2:Graph中的節點代表什麽
官方文檔對於Graph的說明給出了如下圖例表格
從中可以看出,不考慮summary node的情況下,節點要麽是常數Constant,要麽是運算符Operation Node,要麽是前兩者的組合。
回到我們這個具體問題,random_uniform/weight/bias就是組合節點,而其他節點就是運算符。註意:從上圖可以看到,inputTensor也是被視作一個獨立的運算符。
放大其中一個節點weights,我們可以看到其內部結構如下,包含:賦值(assign)運算符、(weight)運算符、讀取(read)運算符
結合上述實驗和分析,可以初步判斷運算符Operation Node包含以下情況
- 具體的運算操作:例如矩陣乘法(上圖中的matmul),賦值(assign),讀取(read)
- 某個Tensor本身:例如上圖中的inputTensor和(weights)節點
關於Operation Node代表常量的情況,在本文的案例2中會有體現
案例2:單神經元+損失函數+誤差反向傳播梯度下降調整參數
案例1中的神經網絡只包含了前向計算邏輯,作為神經網絡,其最主要的功能在於被訓練滿足某一目標,因此損失函數和誤差反向傳播梯度下降調整參數是必不可少的。
在此前的代碼基礎上新增如下兩行就可以實現上述功能。這裏損失函數使用預測值與目標值的L2距離
loss = tf.nn.l2_loss(a - labels, name=‘L2Loss‘)
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)
- 1
- 2
加入這兩個功能後,新生成的Graph如下所示
對比上圖和案例1中的Graph,在案例2 Graph的Main View(圖像左側)新增了如下信息
- 節點
- 常量y:代表label
- sub運算符
- L2Loss Tensor
- gradient命名空間,這個命名空間與除了bias/add/L2Loss之外所有節點都有數據依賴
- 邊
- Weights和Bias都新增了對Adam節點的控制依賴
接下來重點討論Gradient(梯度)和Adam(尋優算法)節點的內部結構
Gradient(梯度)節點
Gradient(梯度)節點內部是一個鏈式結構,分為對L2Loss求導、對減法求導、對激活函數求導……
總結起來,這就是數學上求導數的鏈式法則的圖形化表示,我們的最終目的是求出損失函數對某個參數的導數,那麽根據鏈式法則,只要從損失函數L2Loss出發,將每一層求導結果相乘就可以得到,筆者前一個系列利用Python實現神經網絡中也使用了類似的求導方法
Adam(尋優算法)節點
Adam算法本身的原理和實現可以參閱筆者此前的文章:Python實現神經網絡Part 2: 訓練單個神經元找到最優解
上述圖形就是根據Adam算法原理,使用梯度、beta1\beta2,結合每一輪訓練中weights和bias的原始值,計算weight和bias的更新值,通過控制依賴關系進一步調節weights和bias
總結
TensorBoard中的Graph不同於一般的神經網絡結構圖,它是一種計算圖。圖中每個節點要麽是Tensor本身,要麽是運算符,每一條邊要麽代表Tensor的流動,要麽代表控制關系。這張圖完備的表達了通過代碼定義的神經網絡中所有計算步驟,可以據此說明前向計算、誤差反相傳播、梯度下降調整參數等過程。
在實際工作中,理解了上述含義,就可以將Graph利用起來,在Debug過程中可視化的發現網絡計算流程中的問題。復雜程序的調試總是困難的,引入可視化工具對於調試效率會非常有幫助。
參考文獻:
http://www.360doc.com/content/17/0414/13/10408243_645540404.shtml
http://blog.csdn.net/helei001/article/details/51842531
學習TensorFlow,TensorBoard可視化網絡結構和參數