在本教程我們來(lái)看一下Mikolov et al中提到的word2vec模型。該模型是用于學(xué)習(xí)文字的向量表示,稱之為“word embedding”。
本教程意在展現(xiàn)出在TensorfLow中構(gòu)建word2vec模型有趣、本質(zhì)的部分。
我們會(huì)在教程的推進(jìn)中循序漸進(jìn)地解釋代碼,但是如果你更希望直入主題,可以在 tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py查看到一個(gè)最簡(jiǎn)單的實(shí)現(xiàn)。這個(gè)基本的例子提供的代碼可以完成下載一些數(shù)據(jù),簡(jiǎn)單訓(xùn)練后展示結(jié)果。一旦你覺(jué)得已經(jīng)完全掌握了這個(gè)簡(jiǎn)單版本,你可以查看 tensorflow/models/embedding/word2vec.py,這里提供了一些更復(fù)雜的實(shí)現(xiàn),同時(shí)也展示了TensorFlow的一些更進(jìn)階的特性,比如如何更高效地使用線程將數(shù)據(jù)送入文本模型,再比如如何在訓(xùn)練中設(shè)置檢查點(diǎn)等等。
但是首先,讓我們來(lái)看一下為何需要學(xué)習(xí)word embeddings。如果你對(duì)word embeddings相關(guān)內(nèi)容已經(jīng)是個(gè)專家了,那么請(qǐng)安心跳過(guò)本節(jié)內(nèi)容,直接深入細(xì)節(jié)干一些臟活吧。
通常圖像或音頻系統(tǒng)處理的是由圖片中所有單個(gè)原始像素點(diǎn)強(qiáng)度值或者音頻中功率譜密度的強(qiáng)度值,把它們編碼成豐富、高緯度的向量數(shù)據(jù)集。對(duì)于物體或語(yǔ)音識(shí)別這一類的任務(wù),我們所需的全部信息已經(jīng)都存儲(chǔ)在原始數(shù)據(jù)中(顯然人類本身就是依賴原始數(shù)據(jù)進(jìn)行日常的物體或語(yǔ)音識(shí)別的)。然后,自然語(yǔ)言處理系統(tǒng)通常將詞匯作為離散的單一符號(hào),例如 "cat" 一詞或可表示為 Id537 ,而 "dog" 一詞或可表示為 Id143。這些符號(hào)編碼毫無(wú)規(guī)律,無(wú)法提供不同詞匯之間可能存在的關(guān)聯(lián)信息。換句話說(shuō),在處理關(guān)于 "dogs" 一詞的信息時(shí),模型將無(wú)法利用已知的關(guān)于 "cats" 的信息(例如,它們都是動(dòng)物,有四條腿,可作為寵物等等)??梢?jiàn),將詞匯表達(dá)為上述的獨(dú)立離散符號(hào)將進(jìn)一步導(dǎo)致數(shù)據(jù)稀疏,使我們?cè)谟?xùn)練統(tǒng)計(jì)模型時(shí)不得不尋求更多的數(shù)據(jù)。而詞匯的向量表示將克服上述的難題。

向量空間模型 (VSMs)將詞匯表達(dá)(嵌套)于一個(gè)連續(xù)的向量空間中,語(yǔ)義近似的詞匯被映射為相鄰的數(shù)據(jù)點(diǎn)。向量空間模型在自然語(yǔ)言處理領(lǐng)域中有著漫長(zhǎng)且豐富的歷史,不過(guò)幾乎所有利用這一模型的方法都依賴于 分布式假設(shè),其核心思想為出現(xiàn)于上下文情景中的詞匯都有相類似的語(yǔ)義。采用這一假設(shè)的研究方法大致分為以下兩類:基于計(jì)數(shù)的方法 (e.g. 潛在語(yǔ)義分析), 和 預(yù)測(cè)方法 (e.g. 神經(jīng)概率化語(yǔ)言模型).
其中它們的區(qū)別在如下論文中又詳細(xì)闡述 Baroni et al.,不過(guò)簡(jiǎn)而言之:基于計(jì)數(shù)的方法計(jì)算某詞匯與其鄰近詞匯在一個(gè)大型語(yǔ)料庫(kù)中共同出現(xiàn)的頻率及其他統(tǒng)計(jì)量,然后將這些統(tǒng)計(jì)量映射到一個(gè)小型且稠密的向量中。預(yù)測(cè)方法則試圖直接從某詞匯的鄰近詞匯對(duì)其進(jìn)行預(yù)測(cè),在此過(guò)程中利用已經(jīng)學(xué)習(xí)到的小型且稠密的嵌套向量。
Word2vec是一種可以進(jìn)行高效率詞嵌套學(xué)習(xí)的預(yù)測(cè)模型。其兩種變體分別為:連續(xù)詞袋模型(CBOW)及Skip-Gram模型。從算法角度看,這兩種方法非常相似,其區(qū)別為CBOW根據(jù)源詞上下文詞匯('the cat sits on the')來(lái)預(yù)測(cè)目標(biāo)詞匯(例如,‘mat’),而Skip-Gram模型做法相反,它通過(guò)目標(biāo)詞匯來(lái)預(yù)測(cè)源詞匯。Skip-Gram模型采取CBOW的逆過(guò)程的動(dòng)機(jī)在于:CBOW算法對(duì)于很多分布式信息進(jìn)行了平滑處理(例如將一整段上下文信息視為一個(gè)單一觀察量)。很多情況下,對(duì)于小型的數(shù)據(jù)集,這一處理是有幫助的。相形之下,Skip-Gram模型將每個(gè)“上下文-目標(biāo)詞匯”的組合視為一個(gè)新觀察量,這種做法在大型數(shù)據(jù)集中會(huì)更為有效。本教程余下部分將著重講解Skip-Gram模型。
神經(jīng)概率化語(yǔ)言模型通常使用極大似然法 (ML) 進(jìn)行訓(xùn)練,其中通過(guò) softmax function 來(lái)最大化當(dāng)提供前一個(gè)單詞 h (代表 "history"),后一個(gè)單詞的概率 http://wiki.jikexueyuan.com/project/tensorflow-zh/images/vr1.png" alt="" /> (代表 "target"),
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/vr2.png" alt="" />
當(dāng) score(w_t,h) 計(jì)算了文字 w_t 和 上下文 h 的相容性(通常使用向量積)。我們使用對(duì)數(shù)似然函數(shù)來(lái)訓(xùn)練訓(xùn)練集的最大值,比如通過(guò):
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/word2vec2.png" alt="" />
這里提出了一個(gè)解決語(yǔ)言概率模型的合適的通用方法。然而這個(gè)方法實(shí)際執(zhí)行起來(lái)開(kāi)銷非常大,因?yàn)槲覀冃枰ビ?jì)算并正則化當(dāng)前上下文環(huán)境 h 中所有其他 V 單詞 w' 的概率得分,在每一步訓(xùn)練迭代中。

從另一個(gè)角度來(lái)說(shuō),當(dāng)使用word2vec模型時(shí),我們并不需要對(duì)概率模型中的所有特征進(jìn)行學(xué)習(xí)。而CBOW模型和Skip-Gram模型為了避免這種情況發(fā)生,使用一個(gè)二分類器(邏輯回歸)在同一個(gè)上下文環(huán)境里從 k 虛構(gòu)的 (噪聲) 單詞 http://wiki.jikexueyuan.com/project/tensorflow-zh/images/rw5.png" alt="" /> 區(qū)分出真正的目標(biāo)單詞 http://wiki.jikexueyuan.com/project/tensorflow-zh/images/rw4.png" alt="" />。我們下面詳細(xì)闡述一下CBOW模型,對(duì)于Skip-Gram模型只要簡(jiǎn)單地做相反的操作即可。

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

這也解釋了為什么這些向量在傳統(tǒng)的NLP問(wèn)題中可作為特性使用,比如用在對(duì)一個(gè)演講章節(jié)打個(gè)標(biāo)簽,或者對(duì)一個(gè)專有名詞的識(shí)別 (看看如下這個(gè)例子 Collobert et al.或者 Turian et al.)。
不過(guò)現(xiàn)在讓我們用它們來(lái)畫(huà)漂亮的圖表吧!
這里談得都是嵌套,那么先來(lái)定義一個(gè)嵌套參數(shù)矩陣。我們用唯一的隨機(jī)值來(lái)初始化這個(gè)大矩陣。
embeddings = tf.Variable(
tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
對(duì)噪聲-比對(duì)的損失計(jì)算就使用一個(gè)邏輯回歸模型。對(duì)此,我們需要對(duì)語(yǔ)料庫(kù)中的每個(gè)單詞定義一個(gè)權(quán)重值和偏差值。(也可稱之為輸出權(quán)重 與之對(duì)應(yīng)的 輸入嵌套值)。定義如下。
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]))
我們有了這些參數(shù)之后,就可以定義Skip-Gram模型了。簡(jiǎn)單起見(jiàn),假設(shè)我們已經(jīng)把語(yǔ)料庫(kù)中的文字整型化了,這樣每個(gè)整型代表一個(gè)單詞(細(xì)節(jié)請(qǐng)查看 tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py)。Skip-Gram模型有兩個(gè)輸入。一個(gè)是一組用整型表示的上下文單詞,另一個(gè)是目標(biāo)單詞。給這些輸入建立占位符節(jié)點(diǎn),之后就可以填入數(shù)據(jù)了。
# 建立輸入占位符
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
然后我們需要對(duì)批數(shù)據(jù)中的單詞建立嵌套向量,TensorFlow提供了方便的工具函數(shù)。
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
好了,現(xiàn)在我們有了每個(gè)單詞的嵌套向量,接下來(lái)就是使用噪聲-比對(duì)的訓(xùn)練方式來(lái)預(yù)測(cè)目標(biāo)單詞。
# 計(jì)算 NCE 損失函數(shù), 每次使用負(fù)標(biāo)簽的樣本.
loss = tf.reduce_mean(
tf.nn.nce_loss(nce_weights, nce_biases, embed, train_labels,
num_sampled, vocabulary_size))
我們對(duì)損失函數(shù)建立了圖形節(jié)點(diǎn),然后我們需要計(jì)算相應(yīng)梯度和更新參數(shù)的節(jié)點(diǎn),比如說(shuō)在這里我們會(huì)使用隨機(jī)梯度下降法,TensorFlow也已經(jīng)封裝好了該過(guò)程。
# 使用 SGD 控制器.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0).minimize(loss)
訓(xùn)練的過(guò)程很簡(jiǎn)單,只要在循環(huán)中使用feed_dict不斷給占位符填充數(shù)據(jù),同時(shí)調(diào)用
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)
完整地例子可參考 tensorflow/g3doc/tutorials/word2vec/word2vec_basic.py.
使用t-SNE來(lái)看一下嵌套學(xué)習(xí)完成的結(jié)果。

Et voila! 與預(yù)期的一樣,相似的單詞被聚類在一起。對(duì)word2vec模型更復(fù)雜的實(shí)現(xiàn)需要用到TensorFlow一些更高級(jí)的特性,具體是實(shí)現(xiàn)可以參考 tensorflow/models/embedding/word2vec.py。
詞嵌套在NLP的預(yù)測(cè)問(wèn)題中是非常有用且使用廣泛地。如果要檢測(cè)一個(gè)模型是否是可以成熟地區(qū)分詞性或者區(qū)分專有名詞的模型,最簡(jiǎn)單的辦法就是直接檢驗(yàn)它的預(yù)測(cè)詞性、語(yǔ)義關(guān)系的能力,比如讓它解決形如king is to queen as father is to ?這樣的問(wèn)題。這種方法叫做類比推理 ,可參考Mikolov and colleagues,數(shù)據(jù)集下載地址為:
https://word2vec.googlecode.com/svn/trunk/questions-words.txt。
To see how we do this evaluation如何執(zhí)行這樣的評(píng)估,可以看build_eval_graph()和
eval()這兩個(gè)函數(shù)在下面源碼中的使用
tensorflow/models/embedding/word2vec.py.
超參數(shù)的選擇對(duì)該問(wèn)題解決的準(zhǔn)確性有巨大的影響。想要模型具有很好的表現(xiàn),需要有一個(gè)巨大的訓(xùn)練數(shù)據(jù)集,同時(shí)仔細(xì)調(diào)整參數(shù)的選擇并且使用例如二次抽樣的一些技巧。不過(guò)這些問(wèn)題已經(jīng)超出了本教程的范圍。
以上簡(jiǎn)單的例子展示了TensorFlow的靈活性。比如說(shuō),我們可以很輕松得用現(xiàn)成的tf.nn.sampled_softmax_loss()來(lái)代替tf.nn.nce_loss()構(gòu)成目標(biāo)函數(shù)。如果你對(duì)損失函數(shù)想做新的嘗試,你可以用TensorFlow手動(dòng)編寫(xiě)新的目標(biāo)函數(shù)的表達(dá)式,然后用控制器執(zhí)行計(jì)算。這種靈活性的價(jià)值體現(xiàn)在,當(dāng)我們探索一個(gè)機(jī)器學(xué)習(xí)模型時(shí),我們可以很快地遍歷這些嘗試,從中選出最優(yōu)。
一旦你有了一個(gè)滿意的模型結(jié)構(gòu),或許它就可以使實(shí)現(xiàn)運(yùn)行地更高效(在短時(shí)間內(nèi)覆蓋更多的數(shù)據(jù))。比如說(shuō),在本教程中使用的簡(jiǎn)單代碼,實(shí)際運(yùn)行速度都不錯(cuò),因?yàn)槲覀兪褂肞ython來(lái)讀取和填裝數(shù)據(jù),而這些在TensorFlow后臺(tái)只需執(zhí)行非常少的工作。如果你發(fā)現(xiàn)你的模型在輸入數(shù)據(jù)時(shí)存在嚴(yán)重的瓶頸,你可以根據(jù)自己的實(shí)際問(wèn)題自行實(shí)現(xiàn)一個(gè)數(shù)據(jù)閱讀器,參考 新的數(shù)據(jù)格式。對(duì)于Skip-Gram 模型,我們已經(jīng)完成了如下這個(gè)例子 tensorflow/models/embedding/word2vec.py。
如果I/O問(wèn)題對(duì)你的模型已經(jīng)不再是個(gè)問(wèn)題,并且想進(jìn)一步地優(yōu)化性能,或許你可以自行編寫(xiě)TensorFlow操作單元,詳見(jiàn) 添加一個(gè)新的操作。相應(yīng)的,我們也提供了Skip-Gram模型的例子 tensorflow/models/embedding/word2vec_optimized.py。請(qǐng)自行調(diào)節(jié)以上幾個(gè)過(guò)程的標(biāo)準(zhǔn),使模型在每個(gè)運(yùn)行階段有更好地性能。
在本教程中我們介紹了word2vec模型,它在解決詞嵌套問(wèn)題中具有良好的性能。我們解釋了使用詞嵌套模型的實(shí)用性,并且討論了如何使用TensorFlow實(shí)現(xiàn)該模型的高效訓(xùn)練??偟膩?lái)說(shuō),我們希望這個(gè)例子能夠讓向你展示TensorFlow可以提供實(shí)驗(yàn)初期的靈活性,以及在后期優(yōu)化模型時(shí)對(duì)模型內(nèi)部的可操控性。
原文地址:Vector Representation of Words 翻譯:btpeter 校對(duì):waiwaizheng