主要內容爲:
- Name Scope
- Variable Scope
- 權重共享
- tf.train.Saver
- tf.summary
- 控制實驗過程的隨機性
- Autodiff(梯度計算)
1. Name Scope命名空間
TensorBoard中Word2Vec模型計算圖表示如圖,如果模型更復雜,計算圖也越來越亂,我們可以使用name scope
將相關的結點放到一個組裏來方便運算圖的理解。tf.name_scope
使用如下:
with tf.name_scope(name_of_that_scope):
# declare op_1
# declare op_2
# ...
在word2vec模型中,我可以把計算圖中的結點劃分到4個命名空間中:data、embed、loss和optimizer。
with tf.name_scope('data'):
iterator = dataset.make_initializable_iterator()
center_words, target_words = iterator.get_next()
with tf.name_scope('embed'):
embed_matrix = tf.get_variable('embed_matrix',
shape=[VOCAB_SIZE, EMBED_SIZE],initializer=tf.random_uniform_initializer())
embed = tf.nn.embedding_lookup(embed_matrix, center_words, name='embedding')
with tf.name_scope('loss'):
nce_weight = tf.get_variable('nce_weight',
shape=[VOCAB_SIZE, EMBED_SIZE], initializer=tf.truncated_normal_initializer())
nce_bias = tf.get_variable('nce_bias', initializer=tf.zeros([VOCAB_SIZE]))
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weight,
biases=nce_bias,
labels=target_words,
inputs=embed, num_sampled=NUM_SAMPLED,
num_classes=VOCAB_SIZE), name='loss')
with tf.name_scope('optimizer'):
optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
此時,TensorBoard中計算圖如下:
計算圖表示更清晰,可以點擊“embed“結點查看這個命名空間中包含的ops。
2. 變量空間Variable scope
命名空間和變量空間的區別在於,變量空間有助於變量共享。那爲什麼需要變量共享呢?
假設我們創建了一個雙層神經網絡,之後我們想不同的模型輸入能共享模型的權重參數。我們先看看正常情況下會發生什麼?
# 定義輸入
x1 = tf.truncated_normal([200,100], name='x1')
x2 = tf.truncated_normal([200,100], name='x2')
# 模型定義
def two_hidden_layers(x):
assert x.shape.as_list() == [200, 100]
w1 = tf.Variable(tf.random_normal([100, 50]), name="h1_weights")
b1 = tf.Variable(tf.zeros([50]), name="h1_biases")
h1 = tf.matmul(x, w1) + b1
assert h1.shape.as_list() == [200, 50]
w2 = tf.Variable(tf.random_normal([50, 10]), name="h2_weights")
b2 = tf.Variable(tf.zeros([10]), name="h2_biases")
logits = tf.matmul(h1, w2) + b2
return logits
# 模型調用
logits1 = two_hidden_layers(x1)
logits2 = two_hidden_layers(x2)
在TensorBoard中可視化結果中有兩個子圖,每個參數有兩套。
爲了實現權重共享,我們需要使用tf.get_variable()
聲明變量;每次調用其創建變量時,會先檢測對應變量是否存在,如果存在,則重用這個變量(不再創建);否則,創建變量。但是,這樣還是不能完成變量共享。如果調用,會拋出異常。
爲了避免這個異常,我們需要將所有要用到變量放到VarScope中,並將VarScope設置爲可重用的。
def fully_connected(x, output_dim, scope):
with tf.variable_scope(scope) as scope:
w = tf.get_variable("weights", [x.shape[1], output_dim], initializer=tf.random_normal_initializer())
b = tf.get_variable("biases", [output_dim], initializer=tf.constant_initializer(0.0))
return tf.matmul(x, w) + b
def two_hidden_layers(x):
h1 = fully_connected(x, 50, 'h1')
h2 = fully_connected(h1, 10, 'h2')
with tf.variable_scope('two_layers') as scope:
logits1 = two_hidden_layers(x1)
scope.reuse_variables() # 變量重用,實現共享
logits2 = two_hidden_layers(x2)
此時,模型的可視化結果如下:
運算圖中只有一套參數,而且輸入x1和x2使用的是同一套參數。
計算圖集合Graph collections
定義模型時可能把所有的變量放到了不同的運算子圖中。TensorFlow的中tf.get_collection
可以用於獲取特定運算字體中變量。使用方式爲:
tf.get_collection(
key,
scope=None
)
其中key爲集合名,scope爲想要獲取的變量空間。
默認情況下,所有的變量存儲在tf.GraphKeys.GLOBAL_VARIABLES
集合中,比如想要獲取‘my_scope’空間中的變量,使用:
tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='my_scope')
創建變量時如果trainable=True
(默認情況爲True)就會自動添加到集合tf.GraphKeys.TRAINABLE_VARIABLES
中。當然,也可以使用tf.add_to_collection(name, value)
創建內部不是變量的集合,比如可以創建一個初始化集合,然後將所有的初始化ops放到裏面。
3. tf.train.Saver()
實驗管理主要指的是能保存模型的參數,以便遇到機器奔潰等情況,模型能從之前的保存的參數中繼續訓練,而不是重新開始。這對於在大數據集上、複雜模型的訓練十分有效。此外,對於研究學者而言,實驗結果的可重複性是十分重要的,模型構建和訓練時經常需要隨機化,如參數的隨機初始化,樣本的隨機打亂。如何控制模型的隨機性也是需要解決的問題。
爲了當模型奔潰時能從特定時間步的參數恢復重新訓練,我們需要定期地對模型參數進行保存。tf.train.Saver()
類允許我們將計算圖的權重變量保存到二進制文件中。
當我們想每隔1000個訓練步進行一次參數保存,使用的代碼如下:
# 創建saver對象
saver = tf.train.Saver()
with tf.Session() as sess:
for step in range(training_steps):
sess.run([optimizer])
if (step + 1) % 1000 == 0:
saver.save(sess, 'checkpoint_directory/model_name', global_step=global_step)
在TF中,運算圖變量保存的時間步被稱爲一個checkpoint
。模型訓練過程中會保存多個checkpoints,我們使用變量global_step
來記錄模型完成的訓練次數。global_step變量以0初始化,同時聲明爲不可訓練,因爲我們不要對它進行優化,只是用來記錄訓練次數(可以當做一個時間)。
global_step = tf.Variable(0, dtype=tf.int32, trainable=False, name='global_step')
爲了記錄模型的訓練次數,我們需要將global_step
作爲參數傳遞給優化器,這樣優化每執行一次優化就會對global_step
進行修改。
optimizer = tf.train.GradientDescentOptimizer(lr).minimize(loss, global_step=global_step)
爲了恢復變量,我們可以使用tf.train.restore(sess, save_path)
,比如從第10000個step保存的checkpoints進行恢復:
saver.restore(sess, 'checkpoints/skip-gram-10000')
只有checkpoint文件是有效的,權重參數才能正常加載。TF中可以使用tf.train.get_checkpoint_state('directory_name')
檢查文件夾下checkpoints文件的狀態信息。
checkpoint
文件記錄最新的checkpoint,因此,如果可以從checkpoints/checkpoint
下找到最新的checkpoint,然後進行權重參數恢復。checkpoints/checkpoint
文件內容如下:
添加saver後,word2vec的訓練代碼如下:
saver = tf.train.Saver()
initial_step = 0
utils.safe_mkdir('checkpoints')
with tf.Session() as sess:
sess.run(self.iterator.initializer)
sess.run(tf.global_variables_initializer())
ckpt = tf.train.get_checkpoint_state(os.path.dirname('checkpoints/checkpoint'))
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
writer = tf.summary.FileWriter('graphs/word2vec' + str(self.lr), sess.graph)
for index in range(num_train_steps):
try:
sess.run(self.optimizer)
# save the model every 1000 steps
if (index + 1) % 1000 == 0:
saver.save(sess, 'checkpoints/skip-gram', index)
except tf.errors.OutOfRangeError:
sess.run(self.iterator.initializer)
writer.close()
checkpoints
文件夾下包含:
默認情況下,save.Saver()
會存儲計算圖中所有的變量;此外,我們也可以通過創建saver時通過一個列表/字典來自定義需要保存的變量。
需要注意的是,savers對象僅存儲變量,不存儲整個計算圖,我們仍需要自己創建運算圖,之後才能使用saver加載權重參數。
4.tf.summary
TensorBoard提供了一套用於可視化訓練過程統計數據的工具。可以用來可視化損失、準確率的變化,展現形式可以是散點圖、直方圖甚至是圖像。我們創建一個命名空間存放所有的summary ops。
需要將這個summary_op放到sess.run()
來執行。
接下來需要將統計數據寫入到FileWriter對象中,之後再用TensorBoard可視化,
writer.add_summary(summary, global_step=step)
可視化結果如下:
5. 控制隨機性
我們可以通過控制隨機過程來在實驗中得到穩定的結果。TF控制隨機性有兩種方式:
5.1 op level
設置op的隨機種子來控制其隨機性。所有的隨機tensor在初始化時可以接受一個隨機種子參數。eg:
my_var = tf.Variable(tf.truncated_normal(-1.,1.),stddev=0.1,seed=0)
TF的會話用於記錄隨機狀態,每創建一個新的會話都會從隨機種子開始重新開始。
在op level的隨機性中,每個op擁有自己的隨機數種子。
5.2 Graph Level
tf.set_random_seed(seed)
如果不關心運算圖中每個op的隨機性,只是爲了保證當創建另一個圖時計算結果能復現(這樣其他人就可以在他們的計算圖上覆現結果)。當前TF的隨機種子只會影響當前的默認計算圖.
比如,模型a.py
和b.py
內部代碼相同:
分別運行a、b後可以得到相同的計算結果。
6.Autodiff
我們創建的模型中只是構建模型的前向傳播過程,反向傳播過程由TF來自動實現。
TF使用反向模型來自動微分,它允許你用與cost大致相等的函數來計算梯度。梯度通過向運算圖中添加額外的nodes和edges來計算。比如,要計算C對I的梯度,TF首先會查找兩個nodes之間的路徑;一旦路徑確定後,TF會從C開始逐步反向傳播到I。反向傳播的每一步都會向運算圖中添加一個node,之後通過鏈式法則計算添加到計算圖的偏導數。
我們可以使用tf.gradients()
來計算偏導數。
tf.gradients(ys,[xs])
中[xs]
表示ys需要對其進行偏導數計算的tensors列表,返回結果爲梯度列表。
你可以自己驗證計算結果是否正確。
TF可以自動計算梯度,但是我們不能直觀地知道使用什麼函數,我們也不能判斷模型是否梯度消失或梯度爆炸。但我們需要知道梯度值以便判斷爲什麼某個模型能起效另一個模型卻不行。