解析Tensorflow官方PTB模型的demo

【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小時黔漂。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诫尽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炬守,更是在濱河造成了極大的恐慌牧嫉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酣藻,居然都是意外死亡曹洽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挠蛉,你說我怎么就攤上這事⊥当溃” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵撞羽,是天一觀的道長阐斜。 經(jīng)常有香客問我,道長诀紊,這世上最難降的妖魔是什么智听? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮渡紫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘考赛。我一直安慰自己惕澎,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布颜骤。 她就那樣靜靜地躺著唧喉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忍抽。 梳的紋絲不亂的頭發(fā)上八孝,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音鸠项,去河邊找鬼干跛。 笑死,一個胖子當著我的面吹牛祟绊,可吹牛的內(nèi)容都是我干的楼入。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼牧抽,長吁一口氣:“原來是場噩夢啊……” “哼嘉熊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扬舒,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阐肤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孕惜,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡愧薛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诊赊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厚满。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖碧磅,靈堂內(nèi)的尸體忽然破棺而出碘箍,到底是詐尸還是另有隱情,我是刑警寧澤鲸郊,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布丰榴,位于F島的核電站,受9級特大地震影響秆撮,放射性物質(zhì)發(fā)生泄漏四濒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一职辨、第九天 我趴在偏房一處隱蔽的房頂上張望盗蟆。 院中可真熱鬧,春花似錦舒裤、人聲如沸喳资。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仆邓。三九已至,卻和暖如春伴鳖,著一層夾襖步出監(jiān)牢的瞬間节值,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工榜聂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搞疗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓须肆,卻偏偏與公主長得像贴汪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子休吠,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 近日扳埂,谷歌官方在 Github開放了一份神經(jīng)機器翻譯教程,該教程從基本概念實現(xiàn)開始瘤礁,首先搭建了一個簡單的NMT模型...
    MiracleJQ閱讀 6,373評論 1 11
  • 激活函數(shù)(Activation Function) 為了讓神經(jīng)網(wǎng)絡能夠?qū)W習復雜的決策邊界(decision bou...
    御風之星閱讀 5,132評論 0 8
  • 今天早上起來突然有一種罪惡感阳懂, 為什么? 因為昨天公眾號里面沒有更新文章,也就是沒有輸出價值岩调。 其實說也奇怪巷燥,以前...
    唐瑤愛看書閱讀 541評論 0 0
  • 時光荏苒,一晃二十余載号枕,潛心作畫的時候缰揪,耳邊仿佛還能聽見他的聲音。 “一個好的畫師葱淳,要懂得舍棄過去钝腺,才會有新的色彩...
    一木木一閱讀 383評論 2 2
  • 我從來不是溫順乖巧的孩子,這大概與童年成長的環(huán)境有著密切的關聯(lián)赞厕。我在廣闊的農(nóng)村度過了大部分的童年時光艳狐。與同齡姑娘在...
    白陸河閱讀 1,796評論 22 29