【seq2seq】代碼案例解讀
RNN 模型作為一個可以學習時間序列的模型被認為是深度學習中比較重要的一類模型映琳。在Tensorflow的官方教程中,有兩個與之相關的模型被實現(xiàn)出來固惯。第一個模型是圍繞著Zaremba的論文Recurrent Neural Network Regularization蕴侣,以Tensorflow框架為載體進行的實驗再現(xiàn)工作。第二個模型則是較為實用的英語法語翻譯器。在這篇博客里考蕾,我會主要針對第一個模型的代碼進行解析。在之后的隨筆里我會進而解析英語法語翻譯器的機能会宪。論文以及Tensorflow官方教程介紹:Zaremba設計了一款帶有regularization機制的RNN模型肖卧。該模型是基于RNN模型的一個變種,叫做LSTM掸鹅。論文中塞帐,框架被運用在語言模型,語音識別河劝,機器翻譯以及圖片概括等應用的建設上來驗證架構(gòu)的優(yōu)越性壁榕。作為Tensorflow的官方demo,該模型僅僅被運用在了語言模型的建設上來試圖重現(xiàn)論文中的數(shù)據(jù)赎瞎。官方已經(jīng)對他們的模型制作了一部教程牌里,點擊這里查看官方教程(英語版)。代碼解析:代碼可以在github找到务甥,這里先放上代碼地址牡辽。點擊這里查看代碼。代碼框架很容易理解敞临,一開始态辛,PTB模型被設計入了一個類。該類的init函數(shù)為多層LSTM語言模型的架構(gòu)挺尿,代碼如下:
def __init__(self, is_training, config):
self.batch_size = batch_size = config.batch_size
self.num_steps = num_steps = config.num_steps
size = config.hidden_size
vocab_size = config.vocab_size
#這里是定義輸入tensor的placeholder奏黑,我們可見這里有兩個輸入,
# 一個是數(shù)據(jù)编矾,一個是目標
self._input_data = tf.placeholder(tf.int32, [batch_size, num_steps])
self._targets = tf.placeholder(tf.int32, [batch_size, num_steps])
# Slightly better results can be obtained with forget gate biases
# initialized to 1 but the hyperparameters of the model would need to be
# different than reported in the paper.
# 這里首先定義了一單個lstm的cell熟史,這個cell有五個parameter,依次是
# number of units in the lstm cell, forget gate bias, 一個已經(jīng)deprecated的
# parameter input_size, state_is_tuple=False, 以及activation=tanh.這里我們
# 僅僅用了兩個parameter,即size窄俏,也就是隱匿層的單元數(shù)量以及設forget gate
# 的bias為0. 上面那段英文注視其實是說如果把這個bias設為1效果更好蹂匹,雖然
# 會制造出不同于原論文的結(jié)果。
lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(size, forget_bias=0.0)
if is_training and config.keep_prob < 1: # 在訓練以及為輸出的保留幾率小于1時
# 這里這個dropoutwrapper其實是為每一個lstm cell的輸入以及輸出加入了dropout機制
lstm_cell = tf.nn.rnn_cell.DropoutWrapper(
lstm_cell, output_keep_prob=config.keep_prob)
# 這里的cell其實就是一個多層的結(jié)構(gòu)了凹蜈。它把每一曾的lstm cell連在了一起得到多層
# 的RNN
cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * config.num_layers)
# 根據(jù)論文地4頁章節(jié)4.1限寞,隱匿層的初始值是設為0
self._initial_state = cell.zero_state(batch_size, tf.float32)
with tf.device("/cpu:0"):
# 設定embedding變量以及轉(zhuǎn)化輸入單詞為embedding里的詞向量(embedding_lookup函數(shù))
embedding = tf.get_variable("embedding", [vocab_size, size])
inputs = tf.nn.embedding_lookup(embedding, self._input_data)
if is_training and config.keep_prob < 1:
# 對輸入進行dropout
inputs = tf.nn.dropout(inputs, config.keep_prob)
# Simplified version of tensorflow.models.rnn.rnn.py's rnn().
# This builds an unrolled LSTM for tutorial purposes only.
# In general, use the rnn() or state_saving_rnn() from rnn.py.
#
# The alternative version of the code below is:
#
# from tensorflow.models.rnn import rnn
# inputs = [tf.squeeze(input_, [1])
# for input_ in tf.split(1, num_steps, inputs)]
# outputs, state = rnn.rnn(cell, inputs, initial_state=self._initial_state)
outputs = []
state = self._initial_state
with tf.variable_scope("RNN"):
for time_step in range(num_steps):
if time_step > 0: tf.get_variable_scope().reuse_variables()
# 從state開始運行RNN架構(gòu)忍啸,輸出為cell的輸出以及新的state.
(cell_output, state) = cell(inputs[:, time_step, :], state)
outputs.append(cell_output)
# 輸出定義為cell的輸出乘以softmax weight w后加上softmax bias b. 這被叫做logit
output = tf.reshape(tf.concat(1, outputs), [-1, size])
softmax_w = tf.get_variable("softmax_w", [size, vocab_size])
softmax_b = tf.get_variable("softmax_b", [vocab_size])
logits = tf.matmul(output, softmax_w) + softmax_b
# loss函數(shù)是average negative log probability, 這里我們有現(xiàn)成的函數(shù)sequence_loss_by_example
# 來達到這個效果。
loss = tf.nn.seq2seq.sequence_loss_by_example(
[logits],
[tf.reshape(self._targets, [-1])],
[tf.ones([batch_size * num_steps])])
self._cost = cost = tf.reduce_sum(loss) / batch_size
self._final_state = state
if not is_training:
return
# learning rate
self._lr = tf.Variable(0.0, trainable=False)
tvars = tf.trainable_variables()
# 根據(jù)張量間的和的norm來clip多個張量
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
config.max_grad_norm)
# 用之前的變量learning rate來起始梯度下降優(yōu)化器履植。
optimizer = tf.train.GradientDescentOptimizer(self.lr)
# 一般的minimize為先取compute_gradient,再用apply_gradient
# 這里我們不需要compute gradient, 所以直接等于叫了minimize函數(shù)的后半段计雌。
self._train_op = optimizer.apply_gradients(zip(grads, tvars))##
上面代碼注釋已就框架進行了解釋。但我有意的留下了一個最為關鍵的部分沒有解釋玫霎,即variable_scope以及reuse_variable函數(shù)白粉。該類函數(shù)有什么特殊意義呢?我們這里先賣個關子鼠渺,下面的內(nèi)容會就這個問題深入探究。模型建立好后該類還有其他如assign_lr(self,session,lr_value)以及property函數(shù)如input_data(self). 這些函數(shù)淺顯易懂眷细,就不在這里解釋了拦盹。之后,官方代碼設計了小模型(原論文中沒有regularized的模型)外溪椎,還原了論文里的中等模型以及大模型普舆。這些模型是基于同樣的框架,不過不同在迭代數(shù)校读,神經(jīng)元數(shù)以及dropout概率等地方沼侣。另有由于小模型的keep_prob概率被設計為1,將不會運用dropout。另外歉秫,由于系統(tǒng)的運行是在terminal里輸入”python 文件名 --參數(shù) 參數(shù)值“格式蛾洛,名為get_config()的函數(shù)的意義在于把用戶輸入,如small雁芙,換算成運用SmallConfig()類轧膘。最后,我們來看一看main函數(shù)以及run_epoch函數(shù)兔甘。首先來看下run_epoch:
def run_epoch(session, m, data, eval_op, verbose=False):
""Runs the model on the given data."""
epoch_size = ((len(data) // m.batch_size) - 1) // m.num_steps
start_time = time.time()
costs = 0.0
iters = 0
state = m.initial_state.eval()
# ptb_iterator函數(shù)在接受了輸入谎碍,batch size以及運行的step數(shù)后輸出
# 步驟數(shù)以及每一步驟所對應的一對x和y的batch數(shù)據(jù),大小為 [batch_size, num_step]
for step, (x, y) in enumerate(reader.ptb_iterator(data, m.batch_size,
m.num_steps)):
# 在函數(shù)傳遞入的session里運行rnn圖的cost和 fina_state結(jié)果洞焙,另外也計算eval_op的結(jié)果
# 這里eval_op是作為該函數(shù)的輸入蟆淀。
cost, state, _ = session.run([m.cost, m.final_state, eval_op],
{m.input_data: x,
m.targets: y,
m.initial_state: state})
costs += cost
iters += m.num_steps
# 每一定量運行后輸出目前結(jié)果
if verbose and step % (epoch_size // 10) == 10:
print("%.3f perplexity: %.3f speed: %.0f wps" %
(step * 1.0 / epoch_size, np.exp(costs / iters),
iters * m.batch_size / (time.time() - start_time)))
return np.exp(costs / iters)
該函數(shù)很正常,邏輯也比較清晰澡匪,容易理解∪廴危現(xiàn)在,讓我們重點看看我們的main函數(shù):
def main(_):
# 需要首先確認輸入數(shù)據(jù)的path仙蛉,不然沒法訓練模型
if not FLAGS.data_path:
raise ValueError("Must set --data_path to PTB data directory")
# 讀取輸入數(shù)據(jù)并將他們拆分開
raw_data = reader.ptb_raw_data(FLAGS.data_path)
train_data, valid_data, test_data, _ = raw_data
# 讀取用戶輸入的config笋敞,這里用具決定了是小,中還是大模型
config = get_config()
eval_config = get_config()
eval_config.batch_size = 1
eval_config.num_steps = 1
# 建立了一個default圖并開始session
with tf.Graph().as_default(), tf.Session() as session:
#先進行initialization
initializer = tf.random_uniform_initializer(-config.init_scale,
config.init_scale)
#注意荠瘪,這里就是variable scope的運用了夯巷!
with tf.variable_scope("model", reuse=None, initializer=initializer):
m = PTBModel(is_training=True, config=config)
with tf.variable_scope("model", reuse=True, initializer=initializer):
mvalid = PTBModel(is_training=False, config=config)
mtest = PTBModel(is_training=False, config=eval_config)
tf.initialize_all_variables().run()
for i in range(config.max_max_epoch):
# 遞減learning rate
lr_decay = config.lr_decay ** max(i - config.max_epoch, 0.0)
m.assign_lr(session, config.learning_rate * lr_decay)
#打印出perplexity
print("Epoch: %d Learning rate: %.3f" % (i + 1, session.run(m.lr)))
train_perplexity = run_epoch(session, m, train_data, m.train_op,
verbose=True)
print("Epoch: %d Train Perplexity: %.3f" % (i + 1, train_perplexity))
valid_perplexity = run_epoch(session, mvalid, valid_data, tf.no_op())
print("Epoch: %d Valid Perplexity: %.3f" % (i + 1, valid_perplexity))
test_perplexity = run_epoch(session, mtest, test_data, tf.no_op())
print("Test Perplexity: %.3f" % test_perplexity)
還記得之前賣的關子么赛惩?這個重要的variable_scope函數(shù)的目的其實是允許我們在保留模型權重的情況下運行多個模型。首先趁餐,從RNN的根源上說喷兼,因為輸入輸出有著時間關系,我們的模型在訓練時每此迭代都要運用到之前迭代的結(jié)果后雷,所以如果我們直接使用(cell_output, state) = cell(inputs[:, time_step, :], state)我們可能會得到一堆新的RNN模型季惯,而不是我們所期待的前一時刻的RNN模型。再看main函數(shù)臀突,當我們訓練時勉抓,我們需要的是新的模型,所以我們在定義了一個scope名為model的模型時說明了我們不需要使用以存在的參數(shù)候学,因為我們本來的目的就是去訓練的藕筋。而在我們做validation和test的時候呢?訓練新的模型將會非常不妥梳码,所以我們需要運用之前訓練好的模型的參數(shù)來測試他們的效果隐圾,故定義reuse=True。這個概念有需要的朋友可以參考Tensorflow的官方文件對共享變量的描述掰茶。
好了暇藏,我們了解了這個模型代碼的架構(gòu)以及運行的機制,那么他在實際運行中效果如何呢濒蒋?讓我們來實際測試一番盐碱。由于時間問題,我只運行了小模型沪伙,也就是不用dropout的模型甸各。運行方式為在ptb_word_lm.py的文件夾下輸入python ptb_word_lm.py --data_path=/tmp/simple-examples/data/ --model small。這里需要注意的是你需要下載simple-examples.tar.gz包焰坪,下載地址點擊這里趣倾。運行結(jié)果如下:
這里簡便的放入了最后結(jié)果,我們可見某饰,在13個epoch時儒恋,我們的測試perplexity為117.605, 對應了論文里non-regularized LSTM的114.5,運行時間約5到6小時黔漂。