TensorFlow教程之完整教程 2.7 字詞的向量表示
本文檔為TensorFlow參考文檔,本轉載已得到TensorFlow中文社區授權。
Vector Representations of Words
在本教程我們來看一下Mikolov et al中提到的word2vec模型。該模型是用於學習文字的向量表示,稱之為“word embedding”。
亮點
本教程意在展現出在TensorfLow中構建word2vec模型有趣、本質的部分。
- 我們從我們為何需要使用向量表示文字開始。
- 我們通過直觀地例子觀察模型背後的本質,以及它是如何訓練的(通過一些數學方法評估)。
- 同時我們也展示了TensorFlow對該模型的簡單實現。
- 最後,我們著眼於讓給這個簡單版本的模型表現更好。
但是首先,讓我們來看一下為何需要學習word embeddings。如果你對word embeddings相關內容已經是個專家了,那麼請安心跳過本節內容,直接深入細節幹一些髒活吧。
動機: 為什麼需要學習 Word Embeddings?
通常圖像或音頻係統處理的是由圖片中所有單個原始像素點強度值或者音頻中功率譜密度的強度值,把它們編碼成豐富、高緯度的向量數據集。對於物體或語音識別這一類的任務,我們所需的全部信息已經都存儲在原始數據中(顯然人類本身就是依賴原始數據進行日常的物體或語音識別的)。然後,自然語言處理係統通常將詞匯作為離散的單一符號,例如 "cat" 一詞或可表示為 Id537
,而 "dog" 一詞或可表示為 Id143
。這些符號編碼毫無規律,無法提供不同詞匯之間可能存在的關聯信息。換句話說,在處理關於 "dogs" 一詞的信息時,模型將無法利用已知的關於 "cats" 的信息(例如,它們都是動物,有四條腿,可作為寵物等等)。可見,將詞匯表達為上述的獨立離散符號將進一步導致數據稀疏,使我們在訓練統計模型時不得不尋求更多的數據。而詞匯的向量表示將克服上述的難題。

向量空間模型 (VSMs)將詞匯表達(嵌套)於一個連續的向量空間中,語義近似的詞匯被映射為相鄰的數據點。向量空間模型在自然語言處理領域中有著漫長且豐富的曆史,不過幾乎所有利用這一模型的方法都依賴於 分布式假設,其核心思想為出現於上下文情景中的詞匯都有相類似的語義。采用這一假設的研究方法大致分為以下兩類:基於技術的方法 (e.g. 潛在語義分析), 和 預測方法 (e.g. 神經概率化語言模型).
其中它們的區別在如下論文中又詳細闡述 Baroni et al.,不過簡而言之:基於計數的方法計算某詞匯與其鄰近詞匯在一個大型語料庫中共同出現的頻率及其他統計量,然後將這些統計量映射到一個小型且稠密的向量中。預測方法則試圖直接從某詞匯的鄰近詞匯對其進行預測,在此過程中利用已經學習到的小型且稠密的嵌套向量。
Word2vec是一種可以進行高效率詞嵌套學習的預測模型。其兩種變體分別為:連續詞袋模型(CBOW)及Skip-Gram模型。從算法角度看,這兩種方法非常相似,其區別為CBOW根據源詞上下文詞匯('the cat sits on the')來預測目標詞匯(例如,‘mat’),而Skip-Gram模型做法相反,它通過目標詞匯來預測源詞匯。Skip-Gram模型采取CBOW的逆過程的動機在於:CBOW算法對於很多分布式信息進行了平滑處理(例如將一整段上下文信息視為一個單一觀察量)。很多情況下,對於小型的數據集,這一處理是有幫助的。相形之下,Skip-Gram模型將每個“上下文-目標詞匯”的組合視為一個新觀察量,這種做法在大型數據集中會更為有效。本教程餘下部分將著重講解Skip-Gram模型。
處理噪聲對比訓練
神經概率化語言模型通常使用極大似然法 (ML) 進行訓練,其中通過 softmax function 來最大化當提供前一個單詞 h (代表 "history"),後一個單詞的概率 (代表 "target"),
當 score(w_t,h) 計算了文字 w_t 和 上下文 h 的相容性(通常使用向量積)。我們使用對數似然函數來訓練訓練集的最大值,比如通過:
這裏提出了一個解決語言概率模型的合適的通用方法。然而這個方法實際執行起來開銷非常大,因為我們需要去計算並正則化當前上下文環境 h 中所有其他 V 單詞 w' 的概率得分,在每一步訓練迭代中。

從另一個角度來說,當使用word2vec模型時,我們並不需要對概率模型中的所有特征進行學習。而CBOW模型和Skip-Gram模型為了避免這種情況發生,使用一個二分類器(邏輯回歸)在同一個上下文環境裏從 k 虛構的 (噪聲) 單詞 區分出真正的目標單詞
。我們下麵詳細闡述一下CBOW模型,對於Skip-Gram模型隻要簡單地做相反的操作即可。

從數學角度來說,我們的目標是對每個樣本最大化:
其中 代表的是數據集在當前上下文 h ,根據所學習的嵌套向量
,目標單詞 w 使用二分類邏輯回歸計算得出的概率。在實踐中,我們通過在噪聲分布中繪製比對文字來獲得近似的期望值(通過計算蒙特卡洛平均值)。
當真實地目標單詞被分配到較高的概率,同時噪聲單詞的概率很低時,目標函數也就達到最大值了。從技術層麵來說,這種方法叫做 負抽樣,而且使用這個損失函數在數學層麵上也有很好的解釋:這個更新過程也近似於softmax函數的更新。這在計算上將會有很大的優勢,因為當計算這個損失函數時,隻是有我們挑選出來的 k 個 噪聲單詞,而沒有使用整個語料庫 V。這使得訓練變得非常快。我們實際上使用了與noise-contrastive estimation (NCE)介紹的非常相似的方法,這在TensorFlow中已經封裝了一個很便捷的函數tf.nn.nce_loss()
。
讓我們在實踐中來直觀地體會它是如何運作的!
Skip-gram 模型
下麵來看一下這個數據集
the quick brown fox jumped over the lazy dog
我們首先對一些單詞以及它們的上下文環境建立一個數據集。我們可以以任何合理的方式定義‘上下文’,而通常上這個方式是根據文字的句法語境的(使用語法原理的方式處理當前目標單詞可以看一下這篇文獻 Levy et al.,比如說把目標單詞左邊的內容當做一個‘上下文’,或者以目標單詞右邊的內容,等等。現在我們把目標單詞的左右單詞視作一個上下文, 使用大小為1的窗口,這樣就得到這樣一個由(上下文, 目標單詞)
組成的數據集:
([the, brown], quick), ([quick, fox], brown), ([brown, jumped], fox), ...
前文提到Skip-Gram模型是把目標單詞和上下文顛倒過來,所以在這個問題中,舉個例子,就是用'quick'來預測 'the' 和 'brown' ,用 'brown' 預測 'quick' 和 'brown' 。因此這個數據集就變成由(輸入, 輸出)
組成的:
(quick, the), (quick, brown), (brown, quick), (brown, fox), ...
目標函數通常是對整個數據集建立的,但是本問題中要對每一個樣本(或者是一個batch_size
很小的樣本集,通常設置為16 <= batch_size <= 512
)在同一時間執行特別的操作,稱之為隨機梯度下降 (SGD)。我們來看一下訓練過程中每一步的執行。
假設用 t 表示上麵這個例子中quick
來預測 the
的訓練的單個循環。用 num_noise
定義從噪聲分布中挑選出來的噪聲(相反的)單詞的個數,通常使用一元分布,P(w)。為了簡單起見,我們就定num_noise=1
,用 sheep
選作噪聲詞。接下來就可以計算每一對觀察值和噪聲值的損失函數了,每一個執行步驟就可表示為:
整個計算過程的目標是通過更新嵌套參數 來逼近目標函數(這個這個例子中就是使目標函數最大化)。為此我們要計算損失函數中嵌套參數
的梯度,比如,
(幸好TensorFlow封裝了工具函數可以簡單調用!)。對於整個數據集,當梯度下降的過程中不斷地更新參數,對應產生的效果就是不斷地移動每個單詞的嵌套向量,直到可以把真實單詞和噪聲單詞很好得區分開。
我們可以把學習向量映射到2維中以便我們觀察,其中用到的技術可以參考 t-SNE 降緯技術。當我們用可視化的方式來觀察這些向量,就可以很明顯的獲取單詞之間語義信息的關係,這實際上是非常有用的。當我們第一次發現這樣的誘導向量空間中,展示了一些特定的語義關係,這是非常有趣的,比如文字中 male-female,gender 甚至還有 country-capital 的關係, 如下方的圖所示 (也可以參考 Mikolov et al., 2013論文中的例子)。

這也解釋了為什麼這些向量在傳統的NLP問題中可作為特性使用,比如用在對一個演講章節打個標簽,或者對一個專有名詞的識別 (看看如下這個例子 Collobert et al.或者 Turian et al.)。
不過現在讓我們用它們來畫漂亮的圖表吧!
建立圖形
這裏談得都是嵌套,那麼先來定義一個嵌套參數矩陣。我們用唯一的隨機值來初始化這個大矩陣。
embeddings = tf.Variable(
tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
對噪聲-比對的損失計算就使用一個邏輯回歸模型。對此,我們需要對語料庫中的每個單詞定義一個權重值和偏差值。(也可稱之為輸出權重
與之對應的 輸入嵌套值
)。定義如下。
nce_weights = tf.Variable(
tf.truncated_normal([vocabulary_size, embedding_size],
stddev=1.0 / math.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
我們有了這些參數之後,就可以定義Skip-Gram模型了。簡單起見,假設我們已經把語料庫中的文字整型化了,這樣每個整型代表一個單詞。Skip-Gram模型有兩個輸入。一個是一組用整型表示的上下文單詞,另一個是目標單詞。給這些輸入建立占位符節點,之後就可以填入數據了。
# 建立輸入占位符
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
然後我們需要對批數據中的單詞建立嵌套向量,TensorFlow提供了方便的工具函數。
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
好了,現在我們有了每個單詞的嵌套向量,接下來就是使用噪聲-比對的訓練方式來預測目標單詞。
# 計算 NCE 損失函數, 每次使用負標簽的樣本.
loss = tf.reduce_mean(
tf.nn.nce_loss(nce_weights, nce_biases, embed, train_labels,
num_sampled, vocabulary_size))
我們對損失函數建立了圖形節點,然後我們需要計算相應梯度和更新參數的節點,比如說在這裏我們會使用隨機梯度下降法,TensorFlow也已經封裝好了該過程。
# 使用 SGD 控製器.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0).minimize(loss)
訓練模型
訓練的過程很簡單,隻要在循環中使用feed_dict
不斷給占位符填充數據,同時調用 session.run
即可。
for inputs, labels in generate_batch(...):
feed_dict = {training_inputs: inputs, training_labels: labels}
_, cur_loss = session.run([optimizer, loss], feed_dict=feed_dict)
嵌套學習結果可視化
使用t-SNE來看一下嵌套學習完成的結果。

Et voila! 與預期的一樣,相似的單詞被聚類在一起。
嵌套學習的評估: 類比推理
詞嵌套在NLP的預測問題中是非常有用且使用廣泛地。如果要檢測一個模型是否是可以成熟地區分詞性或者區分專有名詞的模型,最簡單的辦法就是直接檢驗它的預測詞性、語義關係的能力,比如讓它解決形如king is to queen as father is to ?
這樣的問題。
超參數的選擇對該問題解決的準確性有巨大的影響。想要模型具有很好的表現,需要有一個巨大的訓練數據集,同時仔細調整參數的選擇並且使用例如二次抽樣的一些技巧。不過這些問題已經超出了本教程的範圍。
優化實現
以上簡單的例子展示了TensorFlow的靈活性。比如說,我們可以很輕鬆得用現成的tf.nn.sampled_softmax_loss()
來代替tf.nn.nce_loss()
構成目標函數。如果你對損失函數想做新的嚐試,你可以用TensorFlow手動編寫新的目標函數的表達式,然後用控製器執行計算。這種靈活性的價值體現在,當我們探索一個機器學習模型時,我們可以很快地遍曆這些嚐試,從中選出最優。
一旦你有了一個滿意的模型結構,或許它就可以使實現運行地更高效(在短時間內覆蓋更多的數據)。比如說,在本教程中使用的簡單代碼,實際運行速度都不錯,因為我們使用Python來讀取和填裝數據,而這些在TensorFlow後台隻需執行非常少的工作。如果你發現你的模型在輸入數據時存在嚴重的瓶頸,你可以根據自己的實際問題自行實現一個數據閱讀器,參考 新的數據格式。
如果I/O問題對你的模型已經不再是個問題,並且想進一步地優化性能,或許你可以自行編寫TensorFlow操作單元,詳見 添加一個新的操作。請自行調節以上幾個過程的標準,使模型在每個運行階段有更好地性能。
總結
在本教程中我們介紹了word2vec模型,它在解決詞嵌套問題中具有良好的性能。我們解釋了使用詞嵌套模型的實用性,並且討論了如何使用TensorFlow實現該模型的高效訓練。總的來說,我們希望這個例子能夠讓向你展示TensorFlow可以提供實驗初期的靈活性,以及在後期優化模型時對模型內部的可操控性。
最後更新:2017-08-22 16:06:25