深入淺出Tensorflow(五):循環神經網路簡介

作者:鄭澤宇

AI前線出品| ID:ai-front

摘要: 「本文主要介紹循環神經網路。該網路結構源於霍普菲爾德網路,在語義信息深度表達表達、語音識別、語言建模、機器翻譯和時序分析等方面實現了突破。本文將以幾個典型的循環神經網路結構為例進行講解,並給出實驗代碼加以說明,並附以一個Tensorflow樣例來使用循環神經網路實現語言模型」。

循環神經網路(recurrent neural network,RNN)源自於1982年由Saratha Sathasivam提出的霍普菲爾德網路。霍普菲爾德網路因為實現困難,在其提出的時候並且沒有被合適地應用。該網路結構也於1986年後被全連接神經網路以及一些傳統的機器學習演算法所取代。然而,傳統的機器學習演算法非常依賴於人工提取的特徵,使得基於傳統機器學習的圖像識別、語音識別以及自然語言處理等問題存在特徵提取的瓶頸。而基於全連接神經網路的方法也存在參數太多、無法利用數據中時間序列信息等問題。隨著更加有效的循環神經網路結構被不斷提出,循環神經網路挖掘數據中的時序信息以及語義信息的深度表達能力被充分利用,並在語音識別、語言模型、機器翻譯以及時序分析等方面實現了突破。

循環神經網路的主要用途是處理和預測序列數據。在之前介紹的全連接神經網路或卷積神經網路模型中,網路結構都是從輸入層到隱含層再到輸出層,層與層之間是全連接或部分連接的,但每層之間的節點是無連接的。考慮這樣一個問題,如果要預測句子的下一個單詞是什麼,一般需要用到當前單詞以及前面的單詞,因為句子中前後單詞並不是獨立的。比如,當前單詞是「很」,前一個單詞是「天空」,那麼下一個單詞很大概率是「藍」。循環神經網路的來源就是為了刻畫一個序列當前的輸出與之前信息的關係。從網路結構上,循環神經網路會記憶之前的信息,並利用之前的信息影響後面結點的輸出。也就是說,循環神經網路的隱藏層之間的結點是有連接的,隱藏層的輸入不僅包括輸入層的輸出,還包括上一時刻隱藏層的輸出。

圖1展示了一個典型的循環神經網路。對於循環神經網路,一個非常重要的概念就是時刻。循環神經網路會對於每一個時刻的輸入結合當前模型的狀態給出一個輸出。從圖1中可以看到,循環神經網路的主體結構A的輸入除了來自輸入層Xt,還有一個循環的邊來提供當前時刻的狀態。在每一個時刻,循環神經網路的模塊A會讀取t時刻的輸入Xt,並輸出一個值ht。同時A的狀態會從當前步傳遞到下一步。因此,循環神經網路理論上可以被看作是同一神經網路結構被無限複製的結果。但出於優化的考慮,目前循環神經網路無法做到真正的無限循環,所以,現實中一般會將循環體展開,於是可以得到圖2所展示的結構。

圖1 循環神經網路經典結構示意圖

在圖2中可以更加清楚的看到循環神經網路在每一個時刻會有一個輸入Xt,然後根據循環神經網路當前的狀態At提供一個輸出Ht。從而神經網路當前狀態At是根據上一時刻的狀態At-1和當前輸入Xt共同決定的。從循環神經網路的結構特徵可以很容易地得出它最擅長解決的問題是與時間序列相關的。循環神經網路也是處理這類問題時最自然的神經網路結構。對於一個序列數據,可以將這個序列上不同時刻的數據依次傳入循環神經網路的輸入層,而輸出可以是對序列中下一個時刻的預測。循環神經網路要求每一個時刻都有一個輸入,但是不一定每個時刻都需要有輸出。在過去幾年中,循環神經網路已經被廣泛地應用在語音識別、語言模型、機器翻譯以及時序分析等問題上,並取得了巨大的成功。

圖2 循環神經網路按時間展開後的結構

以機器翻譯為例來介紹循環神經網路是如何解決實際問題的。循環神經網路中每一個時刻的輸入為需要翻譯的句子中的單詞。如圖3所示,需要翻譯的句子為ABCD,那麼循環神經網路第一段每一個時刻的輸入就分別是A、B、C和D,然後用「」作為待翻譯句子的結束符。在第一段中,循環神經網路沒有輸出。從結束符「」開始,循環神經網路進入翻譯階段。該階段中每一個時刻的輸入是上一個時刻的輸出,而最終得到的輸出就是句子ABCD翻譯的結果。從圖8-3中可以看到句子ABCD對應的翻譯結果就是XYZ,而Q是代表翻譯結束的結束符。

如之前所介紹,循環神經網路可以被看做是同一神經網路結構在時間序列上被複制多次的結果,這個被複制多次的結構被稱之為循環體。如何設計循環體的網路結構是循環神經網路解決實際問題的關鍵。和卷積神經網路過濾器中參數是共享的類似,在循環神經網路中,循環體網路結構中的參數在不同時刻也是共享的。

圖4展示了一個使用最簡單的循環體結構的循環神經網路,在這個循環體中只使用了一個類似全連接層的神經網路結構。下面將通過圖4中所展示的神經網路來介紹循環神經網路前向傳播的完整流程。循環神經網路中的狀態是通過一個向量來表示的,這個向量的維度也稱為循環神經網路隱藏層的大小,假設其為h。從圖4中可以看出,循環體中的神經網路的輸入有兩部分,一部分為上一時刻的狀態,另一部分為當前時刻的輸入樣本。對於時間序列數據來說(比如不同時刻商品的銷量),每一時刻的輸入樣例可以是當前時刻的數值(比如銷量值);對於語言模型來說,輸入樣例可以是當前單詞對應的單詞向量(word embedding)。

圖4 使用單層全連接神經網路作為循環體的循環神經網路結構圖(圖中中間標有tanh的小方框表示一個使用了tanh作為激活函數的全連接神經網路)

長短時記憶網路(LTSM)結構

循環神經網路工作的關鍵點就是使用歷史的信息來幫組當前的決策。例如使用之前出現的單詞來加強對當前文字的理解。循環神經網路可以更好地利用傳統神經網路結構所不能建模的信息,但同時,這也帶來了更大的技術挑戰——長期依賴(long-term dependencies)問題。

在有些問題中,模型僅僅需要短期內的信息來執行當前的任務。比如預測短語「大海的顏色是藍色」中的最後一個單詞「藍色」時,模型並不需要記憶這個短語之前更長的上下文信息——因為這一句話已經包含了足夠的信息來預測最後一個詞。在這樣的場景中,相關的信息和待預測的詞的位置之間的間隔很小,循環神經網路可以比較容易地利用先前信息。

但同樣也會有一些上下文場景更加複雜的情況。比如當模型試著去預測段落「某地開設了大量工廠,空氣污染十分嚴重... 這裡的天空都是灰色的」的最後一個單詞時,僅僅根據短期依賴就無法很好的解決這種問題。因為只根據最後一小段,最後一個詞可以是「藍色的」或者「灰色的」。但如果模型需要預測清楚具體是什麼顏色,就需要考慮先前提到但離當前位置較遠的上下文信息。因此,當前預測位置和相關信息之間的文本間隔就有可能變得很大。當這個間隔不斷增大時,類似圖4中給出的簡單循環神經網路有可能會喪失學習到距離如此遠的信息的能力。或者在複雜語言場景中,有用信息的間隔有大有小、長短不一,循環神經網路的性能也會受到限制。

長短時記憶網路(long short term memory, LSTM)的設計就是為了解決這個問題,而循環神經網路被成功應用的關鍵就是LSTM。在很多的任務上,採用LSTM結構的循環神經網路比標準的循環神經網路表現更好。在下文中將重點介紹LSTM結構。LSTM結構是由Sepp Hochreiter和Jürgen Schmidhuber於1997年提出的,它是一種特殊的循環體結構。如圖5所示,與單一tanh循環體結構不同,LSTM是一種擁有三個「門」結構的特殊網路結構。

圖5 LSTM單元結構示意圖

LSTM靠一些「門」的結構讓信息有選擇性地影響每個時刻循環神經網路中的狀態。所謂「門」的結構就是一個使用sigmoid神經網路和一個按位做乘法的操作,這兩個操作合在一起就是一個「門」的結構。之所以該結構叫做「門」是因為使用sigmoid作為激活函數的全連接神經網路層會輸出一個0到1之間的數值,描述當前輸入有多少信息量可以通過這個結構。於是這個結構的功能就類似於一扇門,當門打開時(sigmoid神經網路層輸出為1時),全部信息都可以通過;當門關上時(sigmoid神經網路層輸出為0時),任何信息都無法通過。本節下面的篇幅將介紹每一個「門」是如何工作的。

為了使循環神經網更有效的保存長期記憶,圖5中「遺忘門」和「輸入門」至關重要,它們是LSTM結構的核心。「遺忘門」的作用是讓循環神經網路「忘記」之前沒有用的信息。比如一段文章中先介紹了某地原來是綠水藍天,但後來被污染了。於是在看到被污染了之後,循環神經網路應該「忘記」之前綠水藍天的狀態。這個工作是通過「遺忘門」來完成的。「遺忘門」會根據當前的輸入xt、上一時刻狀態ct-1和上一時刻輸出ht-1共同決定哪一部分記憶需要被遺忘。在循環神經網路「忘記」了部分之前的狀態後,它還需要從當前的輸入補充最新的記憶。這個過程就是「輸入門」完成的。如圖5所示,「輸入門」會根據xt、ct-1和ht-1決定哪些部分將進入當前時刻的狀態ct。比如當看到文章中提到環境被污染之後,模型需要將這個信息寫入新的狀態。通過「遺忘門」和「輸入門」,LSTM結構可以更加有效的決定哪些信息應該被遺忘,哪些信息應該得到保留。

LSTM結構在計算得到新的狀態ct後需要產生當前時刻的輸出,這個過程是通過「輸出門」完成的。「輸出們」會根據最新的狀態ct、上一時刻的輸出ht-1和當前的輸入xt來決定該時刻的輸出ht。比如當前的狀態為被污染,那麼「天空的顏色」後面的單詞很可能就是「灰色的」。

相比圖4中展示的循環神經網路,使用LSTM結構的循環神經網路的前向傳播是一個相對比較複雜的過程。具體LSTM每個「門」中的公式可以參考論文Long short-term memory。在TensorFlow中,LSTM結構可以被很簡單地實現。以下代碼展示了在TensorFlow中實現使用LSTM結構的循環神經網路的前向傳播過程。

#定義一個LSTM結構。在TensorFlow中通過一句簡單的命令就可以實現一個完整LSTM結構。# LSTM中使用的變數也會在該函數中自動被聲明。lstm = rnn_cell.BasicLSTMCell(lstm_hidden_size)# 將LSTM中的狀態初始化為全0數組。和其他神經網路類似,在優化循環神經網路時,每次也# 會使用一個batch的訓練樣本。以下代碼中,batch_size給出了一個batch的大小。# BasicLSTMCell類提供了zero_state函數來生成全領的初始狀態。state = lstm.zero_state(batch_size, tf.float32)# 定義損失函數。loss = 0.0# 在8.1節中介紹過,雖然理論上循環神經網路可以處理任意長度的序列,但是在訓練時為了# 避免梯度消散的問題,會規定一個最大的序列長度。在以下代碼中,用num_steps# 來表示這個長度。for i in range(num_steps):# 在第一個時刻聲明LSTM結構中使用的變數,在之後的時刻都需要復用之前定義好的變數。 if i > 0: tf.get_variable_scope().reuse_variables() # 每一步處理時間序列中的一個時刻。將當前輸入(current_input)和前一時刻狀態 # (state)傳入定義的LSTM結構可以得到當前LSTM結構的輸出lstm_output和更新後 # 的狀態state。 lstm_output, state = lstm(current_input, state) # 將當前時刻LSTM結構的輸出傳入一個全連接層得到最後的輸出。 final_output = fully_connected(lstm_output) # 計算當前時刻輸出的損失。 loss += calc_loss(final_output, expected_output)

通過上面這段代碼看到,通過TensorFlow可以非常方便地實現使用LSTM結構的循環神經網路,而且並不需要用戶對LSTM內部結構有深入的了解。

自然語言建模

簡單地說,語言模型的目的是為了計算一個句子的出現概率。在這裡把句子看成是單詞的序列,於是語言模型需要計算的就是p(w1,w2,w3,…,wn)。利用語言模型,可以確定哪個單詞序列的可能性更大,或者給定若干個單詞,可以預測下一個最可能出現的詞語。舉個音字轉換的例子,假設輸入的拼音串為「xianzaiquna」,它的輸出可以是「西安在去哪」,也可以是「現在去哪」。根據語言常識,我們知道轉換成第二個的概率更高。語言模型就可以告訴我們後者的概率大於前者,因此在大多數情況下轉換成後者比較合理。

語言模型效果好壞的常用評價指標是複雜度(perplexity)。簡單來說,perplexity值刻畫的就是通過某一個語言模型估計的一句話出現的概率。比如當已經知道(w1,w2,w3···wm)這句話出現在語料庫之中,那麼通過語言模型計算得到的這句話的概率越高越好,也就是perplexity值越小越好。計算perplexity值的公式如下:

複雜度perplexity表示的概念其實是平均分支係數(average branch factor),即模型預測下一個詞時的平均可選擇數量。例如,考慮一個由0~9這10個數字隨機組成的長度為m的序列。由於這10個數字出現的概率是隨機的,所以每個數字出現的概率是1/10。因此,在任意時刻,模型都有10個等概率的候選答案可以選擇,於是perplexity就是10(有10個合理的答案)。perplexity的計算過程如下:

因此,如果一個語言模型的perplexity是89,就表示,平均情況下,模型預測下一個詞時,有89個詞等可能地可以作為下一個詞的合理選擇。

PTB (Penn Treebank Dataset)文本數據集是語言模型學習中目前最被廣泛使用數據集。本小節將在PTB數據集上使用循環神經網路實現語言模型。在給出語言模型代碼之前將先簡單介紹PTB數據集的格式以及TensorFlow對於PTB數據集的支持。首先,需要下載來源於Tomas Mikolov網站上的PTB數據。數據的下載地址為:

fit.vutbr.cz/~imikolov/

將下載下來的文件解壓之後可以得到如下文件夾列表

1-train/ 2-nbest-rescore/ 3-combination/ 4-data-generation/ 5-one-iter/ 6-recovery-during-training/ 7-dynamic-evaluation/ 8-direct/ 9-char-based-lm/ data/ models/ rnnlm-0.2b/

在本文中只需要關心data文件夾下的數據,對於其他文件不再一一介紹,感興趣的讀者可以自行參考README文件。在data文件夾下總共有7個文件,但本文中將只會用到以下三個文件:

ptb.test.txt #測試集數據文件ptb.train.txt #訓練集數據文件ptb.valid.txt #驗證集數據文件

這三個數據文件中的數據已經經過了預處理,包含了10000 個不同的詞語和語句結束標記符(在文本中就是換行符)以及標記稀有詞語的特殊符號。下面展示了訓練數據中的一行:

mr. <unk> is chairman of <unk> n.v. the dutch publishing group

為了讓使用PTB數據集更加方便,TensorFlow提供了兩個函數來幫助實現數據的預處理。首先,TensorFlow提供了ptb_raw_data函數來讀取PTB的原始數據,並將原始數據中的單詞轉化為單詞ID。以下代碼展示了如何使用這個函數。

from tensorflow.models.rnn.ptb import reader# 存放原始數據的路徑。DATA_PATH = "/path/to/ptb/data"train_data, valid_data, test_data, _ = reader.ptb_raw_data(DATA_PATH) # 讀取數據原始數據。print len(train_data)print train_data[:100]

運行以上程序可以得到輸出:

929589 [9970, 9971, 9972, 9974, 9975, 9976, 9980, 9981, 9982, 9983, 9984, 9986, 9987, 9988, 9989, 9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999, 2, 9256, 1, 3, 72, 393, 33, 2133, 0, 146, 19, 6, 9207, 276, 407, 3, 2, 23, 1, 13, 141, 4, 1, 5465, 0, 3081, 1596, 96, 2, 7682, 1, 3, 72, 393, 8, 337, 141, 4, 2477, 657, 2170, 955, 24, 521, 6, 9207, 276, 4, 39, 303, 438, 3684, 2, 6, 942, 4, 3150, 496, 263, 5, 138, 6092, 4241, 6036, 30, 988, 6, 241, 760, 4, 1015, 2786, 211, 6, 96, 4]

從輸出中可以看出訓練數據中總共包含了929589 個單詞,而這些單詞被組成了一個非常長的序列。這個序列通過特殊的標識符給出了每句話結束的位置。在這個數據集中,句子結束的標識符ID為2。

雖然循環神經網路可以接受任意長度的序列,但是在訓練時需要將序列按照某個固定的長度來截斷。為了實現截斷並將數據組織成batch,TensorFlow提供了ptb_iterator函數。以下代碼展示了如何使用ptb_iterator函數。

from tensorflow.models.rnn.ptb import reader# 類似地讀取數據原始數據。DATA_PATH = "/path/to/ptb/data"train_data, valid_data, test_data, _ = reader.ptb_raw_data(DATA_PATH) # 將訓練數據組織成batch大小為4、截斷長度為5的數據組。result = reader.ptb_iterator(train_data, 4, 5)# 讀取第一個batch中的數據,其中包括每個時刻的輸入和對應的正確輸出。x, y = result.next()print "X:", xprint "y:", y

運行以上程序可以得到輸出:

X: [[9970 9971 9972 9974 9975][ 332 7147 328 1452 8595][1969 0 98 89 2254][ 3 3 2 14 24]]y: [[9971 9972 9974 9975 9976][7147 328 1452 8595 59][ 0 98 89 2254 0][ 3 2 14 24 198]]

圖6展示了ptb_iterator函數實現的功能。ptb_iterator函數會將一個長序列劃分為batch_size段,其中batch_size為一個batch的大小。每次調用ptb_iterator時,該函數會從每一段中讀取長度為num_step的子序列,其中num_step為截斷的長度。從上面代碼的輸出可以看到,在第一個batch的第一行中,前面5個單詞的ID和整個訓練數據中前5個單詞的ID是對應的。ptb_iterator在生成batch時可以會自動生成每個batch對應的正確答案,這個對於每一個單詞,它對應的正確答案就是該單詞的後面一個單詞。

圖6 將一個長序列分成batch並截斷的操作示意圖

在介紹了語言模型的理論和使用到的數據集之後,下面給出了一個完成的TensorFlow樣常式序來通過循環神經網路實現語言模型。

# -*- coding: utf-8 -*-import numpy as npimport tensorflow as tffrom tensorflow.models.rnn.ptb import readerDATA_PATH = "/path/to/ptb/data" # 數據存放的路徑。HIDDEN_SIZE = 200 # 隱藏層規模。NUM_LAYERS = 2 # 深層循環神經網路中LSTM結構的層數。VOCAB_SIZE = 10000 # 詞典規模,加上語句結束標識符和稀有 # 單詞標識符總共一萬個單詞。LEARNING_RATE = 1.0 # 學習速率。TRAIN_BATCH_SIZE = 20 # 訓練數據batch的大小。TRAIN_NUM_STEP = 35 # 訓練數據截斷長度。# 在測試時不需要使用截斷,所以可以將測試數據看成一個超長的序列。EVAL_BATCH_SIZE = 1 # 測試數據batch的大小。EVAL_NUM_STEP = 1 # 測試數據截斷長度。NUM_EPOCH = 2 # 使用訓練數據的輪數。KEEP_PROB = 0.5 # 節點不被dropout的概率。MAX_GRAD_NORM = 5 # 用於控制梯度膨脹的參數。# 通過一個PTBModel類來描述模型,這樣方便維護循環神經網路中的狀態。class PTBModel(object): def __init__(self, is_training, batch_size, num_steps): # 記錄使用的batch大小和截斷長度。 self.batch_size = batch_size self.num_steps = num_steps # 定義輸入層。可以看到輸入層的維度為batch_size × num_steps,這和 # ptb_iterator函數輸出的訓練數據batch是一致的。 self.input_data = tf.placeholder(tf.int32, [batch_size, num_steps]) # 定義預期輸出。它的維度和ptb_iterator函數輸出的正確答案維度也是一樣的。 self.targets = tf.placeholder(tf.int32, [batch_size, num_steps]) # 定義使用LSTM結構為循環體結構且使用dropout的深層循環神經網路。 lstm_cell = tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE) if is_training : lstm_cell = tf.nn.rnn_cell.DropoutWrapper( lstm_cell, output_keep_prob=KEEP_PROB) cell = tf.nn.rnn_cell.MultiRNNCell([lstm_cell] * NUM_LAYERS) # 初始化最初的狀態,也就是全零的向量。 self.initial_state = cell.zero_state(batch_size, tf.float32) # 將單詞ID轉換成為單詞向量。因為總共有VOCAB_SIZE個單詞,每個單詞向量的維度 # 為HIDDEN_SIZE,所以embedding參數的維度為VOCAB_SIZE × HIDDEN_SIZE。 embedding = tf.get_variable("embedding", [VOCAB_SIZE, HIDDEN_SIZE]) # 將原本batch_size × num_steps個單詞ID轉化為單詞向量,轉化後的輸入層維度 # 為batch_size × num_steps × HIDDEN_SIZE。 inputs = tf.nn.embedding_lookup(embedding, self.input_data) # 只在訓練時使用dropout。 if is_training: inputs = tf.nn.dropout(inputs, KEEP_PROB) # 定義輸出列表。在這裡先將不同時刻LSTM結構的輸出收集起來,再通過一個全連接 # 層得到最終的輸出。 outputs = [] # state 存儲不同batch中LSTM的狀態,將其初始化為0。 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() # 從輸入數據中獲取當前時刻獲的輸入並傳入LSTM結構。 cell_output, state = cell(inputs[:, time_step, :], state) # 將當前輸出加入輸出隊列。 outputs.append(cell_output) # 把輸出隊列展開成[batch, hidden_size*num_steps]的形狀,然後再 # reshape成[batch*numsteps, hidden_size]的形狀。 output = tf.reshape(tf.concat(1, outputs), [-1, HIDDEN_SIZE]) # 將從LSTM中得到的輸出再經過一個全鏈接層得到最後的預測結果,最終的預測結果在 # 每一個時刻上都是一個長度為VOCAB_SIZE的數組,經過softmax層之後表示下一個 # 位置是不同單詞的概率。 weight = tf.get_variable("weight", [HIDDEN_SIZE, VOCAB_SIZE]) bias = tf.get_variable("bias", [VOCAB_SIZE]) logits = tf.matmul(output, weight) + bias # 定義交叉熵損失函數。TensorFlow提供了sequence_loss_by_example函數來計 # 算一個序列的交叉熵的和。 loss = tf.nn.seq2seq.sequence_loss_by_example( [logits], # 預測的結果。 [tf.reshape(self.targets, [-1])], # 期待的正確答案,這裡將 # [batch_size, num_steps] # 二維數組壓縮成一維數組。 # 損失的權重。在這裡所有的權重都為1,也就是說不同batch和不同時刻 # 的重要程度是一樣的。 [tf.ones([batch_size * num_steps], dtype=tf.float32)]) # 計算得到每個batch的平均損失。 self.cost = tf.reduce_sum(loss) / batch_size self.final_state = state # 只在訓練模型時定義反向傳播操作。 if not is_training: return trainable_variables = tf.trainable_variables() # 通過clip_by_global_norm函數控制梯度的大小,避免梯度膨脹的問題。 grads, _ = tf.clip_by_global_norm( tf.gradients(self.cost, trainable_variables), MAX_GRAD_NORM) # 定義優化方法。 optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE) # 定義訓練步驟。 self.train_op = optimizer.apply_gradients( zip(grads, trainable_variables))# 使用給定的模型model在數據data上運行train_op並返回在全部數據上的perplexity值。def run_epoch(session, model, data, train_op, output_log): # 計算perplexity的輔助變數。 total_costs = 0.0 iters = 0 state = session.run(model.initial_state) # 使用當前數據訓練或者測試模型。for step, (x, y) in enumerate( reader.ptb_iterator(data, model.batch_size, model.num_steps)): # 在當前batch上運行train_op並計算損失值。交叉熵損失函數計算的就是下一個單 # 詞為給定單詞的概率。 cost, state, _ = session.run( [model.cost, model.final_state, train_op], {model.input_data: x, model.targets: y, model.initial_state: state}) # 將不同時刻、不同batch的概率加起來就可以得到第二個perplexity公式等號右 # 邊的部分,再將這個和做指數運算就可以得到perplexity值。 total_costs += cost iters += model.num_steps # 只有在訓練時輸出日誌。 if output_log and step % 100 == 0: print("After %d steps, perplexity is %.3f" % ( step, np.exp(total_costs / iters))) # 返回給定模型在給定數據上的perplexity值。 return np.exp(total_costs / iters)def main(_): # 獲取原始數據。 train_data, valid_data, test_data, _ = reader.ptb_raw_data(DATA_PATH) # 定義初始化函數。 initializer = tf.random_uniform_initializer(-0.05, 0.05) # 定義訓練用的循環神經網路模型。with tf.variable_scope("language_model", reuse=None, initializer=initializer): train_model = PTBModel(True, TRAIN_BATCH_SIZE, TRAIN_NUM_STEP) # 定義評測用的循環神經網路模型。with tf.variable_scope("language_model", reuse=True, initializer=initializer): eval_model = PTBModel(False, EVAL_BATCH_SIZE, EVAL_NUM_STEP) with tf.Session() as session: tf.initialize_all_variables().run() # 使用訓練數據訓練模型。 for i in range(NUM_EPOCH): print("In iteration: %d" % (i + 1)) # 在所有訓練數據上訓練循環神經網路模型。 run_epoch(session, train_model, train_data, train_model.train_op, True) # 使用驗證數據評測模型效果。 valid_perplexity = run_epoch( session, eval_model, valid_data, tf.no_op(), False) print("Epoch: %d Validation Perplexity: %.3f" % ( i + 1, valid_perplexity)) # 最後使用測試數據測試模型效果。 test_perplexity = run_epoch( session, eval_model, test_data, tf.no_op(), False) print("Test Perplexity: %.3f" % test_perplexity) if __name__ == "__main__": tf.app.run()

運行以上程序可以得到類似如下的輸出:

In iteration: 1After 0 steps, perplexity is 10003.783After 100 steps, perplexity is 1404.742After 200 steps, perplexity is 1061.458After 300 steps, perplexity is 891.044After 400 steps, perplexity is 782.037…After 1100 steps, perplexity is 228.711After 1200 steps, perplexity is 226.093After 1300 steps, perplexity is 223.214Epoch: 2 Validation Perplexity: 183.443Test Perplexity: 179.420

從輸出可以看出,在迭代開始時perplexity值為10003.783,這基本相當於從一萬個單詞中隨機選擇下一個單詞。而在訓練結束後,在訓練數據上的perplexity值降低到了179.420。這表明通過訓練過程,將選擇下一個單詞的範圍從一萬個減小到了大約180個。通過調整LSTM隱藏層的節點個數和大小以及訓練迭代的輪數還可以將perplexity值降到更低。

本文內容來自作者圖書作品《TensorFlow:實戰Google深度學習框架》。

作者介紹

鄭澤宇,才雲首席大數據科學家,前谷歌高級工程師。從 2013 年加入谷歌至今,鄭澤宇作為主要技術人員參與並領導了多個大數據項目,擁有豐富機器學習、數據挖掘工業界及科研項目經驗。2014 年,他提出產品聚類項目用於銜接谷歌購物和谷歌知識圖譜(Knowledge Graph)數據,使得知識卡片形式的廣告逐步取代傳統的產品列表廣告,開啟了谷歌購物廣告在搜索頁面投遞的新紀元。他於2013 年 5 月獲得美國 Carnegie Mellon University(CMU)大學計算機碩士學位, 期間在頂級國際學術會議上發表數篇學術論文,並獲得西貝爾獎學金。


-全文完-

關注人工智慧的落地實踐,與企業一起探尋 AI 的邊界,AICon 全球人工智慧技術大會火熱售票中,8 折倒計時一周搶票,詳情點擊:

aicon.geekbang.org/?

《深入淺出TensorFlow》迷你書現已發布,關注公眾號「AI前線」,ID:ai-front,回復關鍵字:TF,獲取下載鏈接!


推薦閱讀:

利用TensorFlow搞定知乎驗證碼之《讓你找中文倒轉漢字》
cs20si:tensorflow for research 學習筆記2
深入淺出Tensorflow(一):深度學習及TensorFlow簡介

TAG:TensorFlow | 神经网络 | RNN |