1c8b: 概述
機器學習如此復雜筒捺,訓練模型的時候,摸不清背后到底是如何運行的纸厉。自己設置的參數(shù)和關鍵變量系吭,如果能看到在訓練時的變化情況,可以為后面的參數(shù)調(diào)優(yōu)階段提供很大的便利残腌。
Tensorboard
就是這樣一個工具村斟。
它刻意將模型抽象成圖像,tensor
每一步是如何流動的抛猫,一目了然蟆盹。
通過適當?shù)拇a設置,還能將指定的關鍵變量在訓練時的變化情況繪制成曲線圖闺金,以便訓練完成后觀察變量的變化情況逾滥,來更加準確定位問題。
這篇文章簡單介紹一下tensorboard
的基本用法败匹。
1c8c: Tensorboard使用
?tensorboard
(以下簡稱tb
)的操作寨昙,從創(chuàng)建一個FileWriter
開始。
在接下來的代碼中掀亩,我參照CS231N課程的數(shù)據(jù)集例子舔哪,用tensorflow
(以下簡稱tf
)寫了一個Logistic Regression
,并以此來說明tb
的基本用法槽棍。
用到的notebook
在我的github上可以找到捉蚤。使用之前,請確保執(zhí)行
conda env create -f environment_tf.yml
來創(chuàng)建和我一樣的運行環(huán)境炼七。如有問題缆巧,可以留言或者ISSUE。
創(chuàng)建好環(huán)境之后豌拙,運行
source activate tb-lab
激活conda
環(huán)境陕悬,然后運行
jupyter notebook
來使用本例中的notebook
。
下面的示例程序用tf
做了一個兩層的分類網(wǎng)絡按傅。將下圖中的數(shù)據(jù)集分類捉超。
最終分類效果是這樣的。
tb
的使用逞敷,大致歸納為三步:
- 調(diào)用
tf
中的FileWriter
將自己關注的數(shù)據(jù)寫到磁盤 - 在命令行使用
tensorboard --logdir /path/to/log
啟動tb
的web app
- 然后在本地瀏覽器輸入
localhost:6006
來使用tb
下面具體看一下怎么使用狂秦。
1.生成模型圖
生成模型圖只需要一句話就行。比如說推捐,現(xiàn)在已經(jīng)初始化好了變量裂问,處理好了數(shù)據(jù),部分代碼如下:
# 數(shù)據(jù)初始化
N = 100 # number of points per class
D = 2 # dimensionality
K = 3 # number of classes
X = np.zeros((N * K, D)) # data matrix (each row = single example)
y = np.zeros(N * K, dtype='uint8') # class labels
for j in range(K):
ix = range(N * j, N * (j + 1))
r = np.linspace(0.0, 1, N) # radius
t = np.linspace(j * 4, (j + 1) * 4, N) + np.random.randn(N) * 0.2 # theta
X[ix] = np.c_[r * np.sin(t), r * np.cos(t)]
y[ix] = j
# lets visualize the data:
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.show()
# 輸入數(shù)據(jù)
inputs = tf.placeholder(tf.float32, [N * K, D])
# 數(shù)據(jù)標簽
targets = tf.placeholder(tf.float32, [None, K])
softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')
softmax_b_1 = tf.Variable(tf.zeros((1, h)), dtype=tf.float32)
softmax_w_2 = tf.Variable(tf.truncated_normal((h, K), stddev=0.1), dtype=tf.float32, name='weight_2')
softmax_b_2 = tf.Variable(tf.zeros((1, K)), dtype=tf.float32)
省略部分代碼......
prediction = tf.nn.softmax(logits_2)
準備開始訓練的時候,加上一句
tf.summary.FileWriter('./logs/summary', session.graph)
例如堪簿,在session
開始的時候添加就好痊乾。
with tf.Session() as session:
session.run(init)
merged = tf.summary.merge_all()
writer = tf.summary.FileWriter('./logs/summary', session.graph)
我們要看整個模型的圖像,因此傳入session.graph
對象椭更。
這時哪审,來到命令行,切換到notebook
所在的目錄虑瀑,然后執(zhí)行
tensorboard --logdir logs/summary
logs/summary
就是代碼中定義的目錄路徑湿滓。tb
啟動后會提示到瀏覽器用localhost:6006
去打開tb
應用。
在瀏覽器打開后舌狗,切換到GRAPH
標簽叽奥,看到的模型圖是這樣的。
這個時候其他標簽還沒有內(nèi)容痛侍,因為還沒有在代碼中進行添加朝氓。
上面的圖片,就是現(xiàn)在這個模型的原始圖像主届,沒有進行任何分組和加工赵哲。看起來很亂君丁,有一些標簽枫夺,例如slice
什么的,都不知道是什么意思绘闷。
圖中筷屡,
- 每條曲線代表
tensor
的流向 - 每個橢圓代表一個操作,如
add
簸喂,matmul
- 每個圓角矩形代表一組操作,可以雙擊放大燎潮,看到這個組里面的細節(jié)
- 整張圖片可以放大縮小喻鳄,隨意拖動;點開每個節(jié)點确封,右上角都會有這個節(jié)點的詳細信息
下面來加工一下除呵,為模型圖分組,讓圖像更加清晰有條理爪喘。
2.使用name_scope分組
調(diào)用tf.name_scope()
方法來為graph
分組颜曾。
我想清楚看到
- 輸入
Inputs
- 標簽
Targets
- 兩組
Weight
和bias
變量 - 兩個隱藏層的輸出
Logits_1
和Logits_2
- 損失函數(shù)
loss
- 訓練準確率
Accuracy
以及 - 整個訓練過程
Train
示例代碼如下。
輸入Inputs
with tf.name_scope('Inputs') as scope:
inputs = tf.placeholder(tf.float32, [N * K, D], name='inputs')
標簽
Targets
with tf.name_scope('Targets') as scope:
targets = tf.placeholder(tf.float32, [None, K], name='targets')
兩組Weight和bias變量
# 創(chuàng)建第一層的weights和bias
with tf.name_scope('Weight_Set_1') as scope:
softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')
softmax_b_1 = tf.Variable(tf.zeros((1, h)), dtype=tf.float32, name='bias_1')
# 創(chuàng)建第二層的weights和bias
with tf.name_scope('Weight_Set_2') as scope:
softmax_w_2 = tf.Variable(tf.truncated_normal((h, K), stddev=0.1), dtype=tf.float32, name='weight_2')
softmax_b_2 = tf.Variable(tf.zeros((1, K)), dtype=tf.float32, name='bias_2')
兩個隱藏曾的輸出Logits_1和Logits_2
with tf.name_scope('Logits_1') as scope:
# 第一層的輸出logits_1,使用ReLU作為activation function
logits_1 = tf.nn.relu(tf.add(tf.matmul(X, softmax_w_1), softmax_b_1), name='logits_1')
with tf.name_scope('Logits_2') as scope:
logits_2 = tf.add(tf.matmul(logits_1, softmax_w_2), softmax_b_2, name='logits_2')
# 計算最終的預測分數(shù),使用softmax計算最后的分數(shù)
prediction = tf.nn.softmax(logits_2, name='prediction')
損失函數(shù)loss
with tf.name_scope('Loss') as scope:
loss = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction, name='loss'))
訓練準確率Accuracy
with tf.name_scope('Accuracy') as scope:
# Determine if the predictions are correct
is_correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y_, 1))
# Calculate the accuracy of the predictions
accuracy = tf.reduce_mean(tf.cast(is_correct_prediction, tf.float32), name='accuracy')
訓練過程Train
with tf.name_scope('Train') as scope:
# 使用adam最為optimizer
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
設置好這些代碼之后秉剑,就可以跟第一步中一樣泛豪,使用
with tf.Session() as session:
session.run(init)
writer = tf.summary.FileWriter('./logs/summary', session.graph)
......
來生成模型圖。
訓練完成后,再次執(zhí)行tensorboard --logdir logs/summary
诡曙,在瀏覽器中看到的模型圖就是這樣的了臀叙。
跟剛才相比,是不是清晰了很多价卤,我所需的所有關注點劝萤,這個圖上都有了。
這時如何為圖中的節(jié)點分組慎璧。
3.添加變量詳情
添加變量詳情床嫌,刻意在訓練結(jié)束后,直接看到變量的變化情況胸私。上面兩步中厌处,只有GRAPH
標簽下面有內(nèi)容,其他標簽下面都沒有盖文。這一步就是往其他標簽下面添加內(nèi)容嘱蛋。
添加變量詳情,使用
tf.summary.histogram()
tf.summary.scalar()
這兩個方法五续。
比如洒敏,我想關注:
- 兩組Weight和bias的變化情況
- 每一次的預測結(jié)果的變化情況
- 訓練過程中l(wèi)oss的變化情況
- 訓練過程中準確率的變化情況
我可以通過上述兩個方法來添加代碼。
注意在使用這兩個方法之前疙驾,要為變量都加上name
關鍵字參數(shù)凶伙。
看上面的代碼中,有
softmax_w_1 = tf.Variable(tf.truncated_normal((D, h), stddev=0.1), dtype=tf.float32, name='weight_1')
最后這個name='weight_1'
就是用來標識softmax_w_1
的名稱它碎。
為了使結(jié)果更加準確函荣,務必為分組中的變量都添加相應的名稱標識。
名稱都添加了之后扳肛,就可以使用下面的代碼來添加我們想要的結(jié)果了傻挂。
# 兩組Weight和bias的直方圖,用histogram
w_1_hist = tf.summary.histogram('weight_1', softmax_w_1)
b_1_hist = tf.summary.histogram('bias_1', softmax_b_1)
w_2_hist = tf.summary.histogram('weight_2', softmax_w_2)
b_2_hist = tf.summary.histogram('bias_2', softmax_b_2)
# 每次預測值的直方圖
logits = tf.summary.histogram('prediction', prediction)
# 損失loss和準確率accuracy的變化挖息,這里用scalar就好金拒,具體原因還沒有深究
# 用histogram應該也沒有問題的,用了scalar之后套腹,scalar標簽下面就有內(nèi)容了
loss_hist = tf.summary.scalar('loss', loss)
acc_hist = tf.summary.scalar('accuracy', accuracy)
然后绪抛,在訓練的時候,要調(diào)用merge_all()
电禀,并用session
去執(zhí)行merge
幢码,最后將merge_all()
返回的對象(本例中叫summary
)添加到FileWriter
中才行〖夥桑看著復雜症副,一步步看代碼還是很清晰的店雅。
# 初始化Variable
init = tf.global_variables_initializer()
# 打開session
with tf.Session() as session:
# 運行初始化
session.run(init)
# 調(diào)用merge_all(),將前面添加的所有histogram和scalar合并到一起瓦糕,方便觀察
merged = tf.summary.merge_all()
# 同樣寫入到logs中
writer = tf.summary.FileWriter('./logs/summary', session.graph)
training_feed_dict = {inputs: X, targets: y_.eval()}
for i in range(epochs):
# 訓練的時候底洗,第一個傳入merged對象,返回summary
summary, _, l = session.run(
[merged, optimizer, loss],
feed_dict=training_feed_dict)
if not i % 50:
print('Epoch {}/{} '.format(i + 1, epochs),
'Training loss: {:.4f}'.format(l))
# 每50步咕娄,將summary對象添加到writer寫入磁盤亥揖,最后來觀察變化
writer.add_summary(summary, i)
在這里,就可以通過觀察這些變量的行為來找問題所在了圣勒。
訓練完成后费变,同理打開tb
。
可以看到SCALAR
標簽下,就有剛才添加的loss
和accuracy
了。
看這兩個變量的行為不錯视事,loss
一直降低瓶殃,accuracy
最后達到了%99
左右泡嘴。
這個圖片時可以通過選定指定一個區(qū)域來放大的,放大之后如下圖。
這是accuracy
放大之后的效果圖,可以更加清晰矮慕。
接下來到DISTRIBUTION
和HISTOGRAM
里看看。
上圖是兩組Weights
在訓練過程中的行為啄骇,這里分布均勻痴鳄,挺正常。如果有問題的話缸夹,會保持不變痪寻,也就是說模型沒有進行學習。
這里是兩組bias
的行為虽惭。這里可以看出點問題了橡类,這個實例模型在訓練的時候,雖然準確率能到%99
芽唇,但是loss
降到0.5
左右就下不去了猫态。
如果在notebook
中多次嘗試運行整個模型,會發(fā)現(xiàn)最后分類完成的那張圖片披摄,無法很精準的分類。
看上圖勇凭,bias_2
的分布有點可疑疚膊,我還不確定,但是這樣異常的不均勻的情況可以給予一定的方向虾标。我現(xiàn)在要做的就是就去看看什么影響了bias_2
的正常更新寓盗,并且這個影響是不是引起了最終的loss
瓶頸。
上圖是HISTOGRAM
中Weights
的分布,看起來還是比較正常的傀蚌,分布很均勻基显。
再來看看bias
的。
這是兩個bias
的分部情況善炫,第一個看起來還不錯撩幽。但是第二個就異常了÷嵋眨刻意點一下圖像左下角那個按鈕窜醉,就可以將圖像放大。我放大了第二個bias
的圖像艺谆,如下榨惰。
簡直是找不到規(guī)律,雖然不是很明白為什么會這樣静汤,但是感覺問題就出在這里了琅催。
這里看到的是2D圖像,點左邊欄中的OFFSET
虫给,就能看到和Weights
那一樣的3D圖像了藤抡。
1c8d: 總結(jié)
就像上面的例子,如果沒有tb
狰右,我并不能準確知道到底是哪一個變量有異常杰捂。tb
的可視化功能大大提高了訓練網(wǎng)絡的效率。
這里是tb
的基本使用方式棋蚌,還有很多有待研究嫁佳。