自2013年以來,word2vec一直是一種有效的詞嵌入的方法,本文把word2vec用圖解的方式進行,全篇沒有數學公式,很是通俗易懂,推薦初學者閱讀。python
(原文做者:jalammar,翻譯:黃海廣)。git
備註:這個是另外一個版本的翻譯,網上也有其它版本的翻譯,都是獨立完成的。github
原文連接:算法
https://jalammar.github.io/illustrated-word2vec/api
這篇文章的代碼傳到了本站的github:網絡
https://github.com/fengdu78/machine_learning_beginner/tree/master/word2vec架構
我發現嵌入的概念是機器學習中最迷人的想法之一。若是您曾經使用Siri,Google智能助理,Alexa,谷歌翻譯,甚至智能手機鍵盤進行下一詞預測,那麼您頗有可能從這個已經成爲天然語言處理模型核心的想法中受益。在過去的幾十年中,使用嵌入技術進行神經模型已有至關大的發展(最近的發展包括BERT和GPT2 等尖端模型的語境化嵌入)。dom
自2013年以來,Word2vec一直是一種有效建立單詞嵌入的方法。除了詞嵌入字的方法以外,它的一些概念已經被證實能夠在非語言任務中有效地建立推薦引擎和理解順序數據。好比Airbnb,阿里巴巴,Spotify和Anghami這樣的公司都從NLP世界中創造出這一優秀的工具並將其用於生產中,從而爲新型推薦引擎提供支持。機器學習
咱們將討論嵌入的概念,以及使用word2vec生成嵌入的機制。ide
讓咱們從一個例子開始,瞭解使用向量來表示事物。
您是否知道五個數字(向量)的列表能夠表明您的個性?
使用0到100的範圍表示你的個性(其中0是最內向的,100是最外向的)。
五大人格特質測試,這些測試會問你一個問題列表,而後在不少方面給你打分,內向/外向就是其中之一。
圖:測試結果示例。它能夠真正告訴你不少關於你本身的事情,而且在學術、我的和職業成功方面都具備預測能力。
假設個人測試得分爲38/100。咱們能夠用這種方式繪製:
讓咱們將範圍切換到從-1到1:
瞭解一我的,一個維度的信息不夠,因此讓咱們添加另外一個維度 - 測試中另外一個特徵的得分。
你可能不知道每一個維度表明什麼,但仍然能夠從一我的的個性的向量表示中得到了不少有用的信息。
咱們如今能夠說這個向量部分表明了個人個性。當你想要將另外兩我的與我進行比較時,向量化表示的有用性就出現了。在下圖中,兩我的中哪個更像我?
處理向量時,計算類似度得分的經常使用方法是餘弦類似度:
一號人物與個人餘弦類似度得分高,因此咱們的性格比較類似。
然而,兩個方面還不足以捕獲有關不一樣人羣的足夠信息。幾十年的心理學研究已經研究了五個主要特徵(以及大量的子特徵)。因此咱們在比較中使用全部五個維度:
咱們無法在二維上繪製出來五個維度,這是機器學習中的常見挑戰,咱們常常須要在更高維度的空間中思考。但好處是餘弦類似度仍然有效。它適用於任意數量的維度:
嵌入的兩個中心思想:
咱們能夠將人(事物)表示爲數字的向量。
咱們導入在維基百科上訓練的GloVe向量:
import gensim import gensim.downloader as api model = api.load('glove-wiki-gigaword-50') model["king"] #查看「king」最類似的單詞 [('prince', 0.8236179351806641), ('queen', 0.7839042544364929), ('ii', 0.7746230363845825), ('emperor', 0.7736247181892395), ('son', 0.766719400882721), ('uncle', 0.7627150416374207), ('kingdom', 0.7542160749435425), ('throne', 0.7539913654327393), ('brother', 0.7492411136627197), ('ruler', 0.7434253096580505)]
這是一個包含50個數字的列表,咱們沒法說清楚裏面的值表明什麼。咱們把全部這些數字放在一行,以便咱們能夠比較其餘單詞向量。讓咱們根據它們的值對單元格進行顏色編碼(若是它們接近2則爲紅色,若是它們接近0則爲白色,若是它們接近-2則爲藍色)
import seaborn as sns import matplotlib.pyplot as plt import numpy as np plt.figure(figsize=(15, 1)) sns.heatmap([model["king"]], xticklabels=False, yticklabels=False, cbar=False, vmin=-2, vmax=2, linewidths=0.7) plt.show()
咱們將忽略數字並僅查看顏色以指示單元格的值,咱們將「King」與其餘詞語進行對比:
plt.figure(figsize=(15, 4)) sns.heatmap([ model["king"], model["man"], model["woman"], model["king"] - model["man"] + model["woman"], model["queen"], ], cbar=True, xticklabels=False, yticklabels=False, linewidths=1) plt.show()
看看「man」和「woman」是如何彼此更類似的,他們中的任何一個都是「king」?這告訴你一些事情。這些向量表示捕獲了這些單詞的信息/含義/關聯。
這是另外一個示例列表(經過垂直掃描列來查找具備類似顏色的列):
有幾點須要指出:
全部這些不一樣的單詞都有一個直的紅色列。它們在這個維度上是類似的(咱們不知道每一個維度代碼是什麼)
你能夠看到「woman」和「girl」在不少地方是如何類似的。與「man」和「boy」同樣
「boy」和「girl」也有彼此類似的地方,但與「woman」或「man」不一樣。這些是否能夠編寫一個模糊的青年概念?可能。
除了最後一個字以外的全部字都表明着人。我添加了一個對象「water」來顯示類別之間的差別。例如,您能夠看到藍色列一直向下並在嵌入「water」以前中止。
咱們能夠添加和減去單詞嵌入並得到有趣的結果,最有名的例子是公式:「king」 - 「man」 + 「woman」:
model.most_similar(positive=["king","woman"],negative=["man"]) [('queen', 0.8523603677749634), ('throne', 0.7664334177970886), ('prince', 0.759214460849762), ('daughter', 0.7473883032798767), ('elizabeth', 0.7460220456123352), ('princess', 0.7424569725990295), ('kingdom', 0.7337411642074585), ('monarch', 0.7214490175247192), ('eldest', 0.7184861898422241), ('widow', 0.7099430561065674)]
咱們能夠像之前同樣想象這個類比:
若是想要給出NLP應用程序的示例,最好的示例之一將是智能手機鍵盤的下一個字(詞)預測功能。這是數十億人天天使用數百次的功能。
下一個字(詞)預測是一項能夠經過語言模型解決的任務。語言模型能夠採用單詞列表(比方說兩個單詞),並嘗試預測它們以後的單詞。
在上面的屏幕截圖中,咱們能夠將模型視爲接受這兩個綠色單詞(thou shalt)並返回建議列表(「not」是具備最高几率的那個字)的模型:
咱們能夠把模型想象成這個黑盒子:
但實際上,該模型不會只輸出一個單詞。它實際上輸出了它所知道的全部單詞的機率分數(模型的「詞彙表」,其範圍能夠從幾千到一百多萬個字(詞))。而後應用程序必須找到分數最高的單詞,並將其呈現給用戶。
圖:神經語言模型的輸出是模型知道的全部單詞的機率分數。咱們在這裏將機率稱爲百分比,好比機率40%將在輸出向量中表示爲
0.4
通過訓練,早期的神經語言模型(Bengio 2003)將分三步計算預測:
在討論嵌入時,第一步對咱們來講最相關。訓練過程的結果之一是這個矩陣包含咱們詞彙表中每一個單詞的嵌入。在預測時間內,咱們只查找輸入字的嵌入,並使用它們來計算預測:
如今讓咱們轉到訓練過程,以瞭解嵌入矩陣是如何工做的。
與大多數其餘機器學習模型相比,語言模型具備巨大優點。即:咱們全部的書籍,文章,維基百科內容和其餘形式的大量文本數據能夠做爲訓練數據。與此相比,許多其餘機器學習模型須要手動設計特徵和專門收集的數據。
單詞經過咱們查看它們每每會出如今旁邊的其餘單詞來嵌入。其機制就是這樣
咱們得到了大量文本數據(例如,全部維基百科文章)。而後
咱們有一個窗口(好比說三個單詞),咱們會對全部文本進行滑動。
當這個窗口滑動文本時,咱們(虛擬地)生成一個用於訓練模型的數據集。爲了準確看看它是如何完成的,讓咱們看看滑動窗口如何處理這個短語:
當咱們開始時,窗口在句子的前三個單詞上:
咱們將前兩個單詞做爲特徵,將第三個單詞做爲標籤:
咱們如今已經在數據集中生成了第一個樣本,咱們稍後可使用它來訓練語言模型。
而後咱們將窗口滑動到下一個位置並建立第二個樣本:
如今生成第二個示例。
很快咱們就會有一個更大的數據集,在不一樣的單詞對以後,這些數據集會出現:
在實踐中,模型每每在咱們滑動窗口時進行訓練。但我發現邏輯上將「數據集生成」階段與訓練階段分開是更清楚的。除了基於神經網絡的語言建模方法以外,一種稱爲N-gram的技術一般用於訓練語言模型。
要了解這種從N-gram到神經模型的轉換如何反映現實世界的產品,建議看這篇2015年博客文章,介紹他們的神經語言模型並將其與以前的N-gram模型進行比較。
給了你句子前面的內容,進行填空:
我在這裏給你的背景是空格以前的五個字(以及以前提到的「bus」)。我相信大多數人都會猜到空格里的這個詞會是「bus」。可是,若是我再給你一條信息:空格以後的一句話,那會改變你的答案嗎?
這徹底改變了應該留在空格中的內容。「red」這個詞如今最可能填到空格中。咱們從中學到的是特定詞語以前和以後的詞語都具備信息價值。事實證實,考慮兩個方向(咱們猜想的單詞左側和右側的單詞)會讓詞嵌入作得更好。
讓咱們看看咱們如何調整咱們訓練模型的方式來解決這個問題。
咱們不只能夠查看在目標詞以前的兩個單詞,還能夠查看其後的兩個單詞。
若是咱們這樣作,咱們實際構建和訓練模型的數據集將以下所示:
這被稱爲連續詞袋結構,並在word2vec論文 one of the word2vec papers 中進行過描述。
另外一種結構與連續詞袋結構略有不一樣,但也能夠也顯示出良好結果。這個結構試圖使用當前詞來猜想相鄰詞,而不是根據其上下文(它以前和以後的詞)猜想一個詞。咱們能夠想到它在訓練文本上滑動的窗口以下所示:
綠色框中的字將是輸入字,每一個粉色框將是可能的輸出。粉色框具備不一樣的陰影,由於此滑動窗口實際上在咱們的訓練數據集中建立了四個單獨的樣本:
此方法稱爲skipgram架構。咱們能夠執行如下操做將滑動窗口可視化:
這會將這四個樣本添加到咱們的訓練數據集中:
而後咱們將窗口滑動到下一個位置:
這將產生咱們的下四個樣本:
接着滑動幾個位置以後,咱們有更多的樣本:
如今咱們已經從現有的運行文本中提取了咱們的skipgram訓練數據集,讓咱們看看咱們如何使用它來訓練預測相鄰單詞的基本神經語言模型。
咱們從數據集中的第一個樣本開始。咱們把特徵提供給未經訓練的模型,要求它預測一個合適的相鄰單詞。
該模型進行三個步驟並輸出預測向量(機率分配給其詞彙表中的每一個單詞)。因爲該模型未通過訓練,所以在此階段的預測確定是錯誤的。但那不要緊。咱們知道應該它將猜到哪一個詞:咱們目前用於訓練模型的行中的標籤/輸出單元格:
「目標向量」的詞(字)機率爲1,其餘詞(字)的機率都是0。咱們減去兩個向量,獲得一個偏差向量:
如今可使用此偏差向量來更新模型,以便下次當「not」做爲輸入時,模型更有可能猜想「thou」。
這就是訓練的第一步。咱們繼續使用數據集中的下一個樣本進行相同的處理,而後是下一個樣本,直到咱們覆蓋了數據集中的全部樣本。這就結束了一個epcho的訓練。咱們繼續訓練多個epcho,而後咱們就有了訓練好的模型,咱們能夠從中提取嵌入矩陣並將其用於任何其餘應用。
雖然這加深了咱們對該過程的理解,但仍然不是word2vec實際上的訓練過程。
回想一下這個神經語言模型如何計算其預測的三個步驟:
從計算的角度來看,第三步很是消耗資源:尤爲是咱們將在數據集中爲每一個訓練樣本作一次(極可能數千萬次)。咱們須要作一些事情來提升效率。
一種方法是將目標分紅兩個步驟:
生成高質量的單詞嵌入(不要擔憂下一個單詞預測)。
咱們將專一於第1步,由於咱們專一於嵌入。要使用高性能模型生成高質量嵌入,咱們能夠從預測相鄰單詞切換模型的任務:
並將其切換到一個取輸入和輸出字的模型,並輸出一個分數,代表它們是不是鄰居(0表示「不是鄰居」,1表示「鄰居」)。
這個簡單的改變,將咱們須要的模型從神經網絡改成邏輯迴歸模型:所以它變得更簡單,計算速度更快。
這個改變要求咱們切換數據集的結構 - 標籤如今是一個值爲0或1的新列。它們將所有爲1,由於咱們添加的全部單詞都是鄰居。
如今能夠以極快的速度計算 - 在幾分鐘內處理數百萬個示例。可是咱們須要關閉一個漏洞。若是咱們全部的例子都是正面的(目標:1),咱們打開本身的智能模型的可能性老是返回1 - 達到100%的準確性,但什麼都不學習並生成垃圾嵌入。
爲了解決這個問題,咱們須要在數據集中引入負樣本 - 不是鄰居的單詞樣本。咱們的模型須要爲這些樣本返回0。如今這是一個挑戰,模型必須努力解決,並且速度還要快。
圖:對於咱們數據集中的每一個樣本,咱們添加了負樣本。它們具備相同的輸入詞和0標籤。可是咱們填寫什麼做爲輸出詞?咱們從詞彙表中隨機抽取單詞
這個想法的靈感來自Noise-contrastive estimation。咱們將實際信號(相鄰單詞的正例)與噪聲(隨機選擇的不是鄰居的單詞)進行對比。這是計算量和統計效率的巨大折衷。
咱們如今已經介紹了word2vec中的兩個核心思想:
負採樣和skipgram。
如今咱們已經創建了skipgram和負採樣的兩個中心思想,咱們能夠繼續仔細研究實際的word2vec訓練過程。
在訓練過程開始以前,咱們預先處理咱們正在訓練模型的文本。在這一步中,咱們肯定詞彙量的大小(咱們稱之爲vocab_size,好比說,將其視爲10,000)以及哪些詞屬於它。在訓練階段的開始,咱們建立兩個矩陣 - Embedding矩陣和Context矩陣。這兩個矩陣在咱們的詞彙表中嵌入了每一個單詞(這vocab_size是他們的維度之一)。第二個維度是咱們但願每次嵌入的時間長度(embedding_size- 300是一個常見值,但咱們在本文前面的例子是50。)。
在訓練過程開始時,咱們用隨機值初始化這些矩陣。而後咱們開始訓練過程。在每一個訓練步驟中,咱們採起一個正樣本及其相關的負樣本。咱們來看看咱們的第一組:
如今咱們有四個單詞:輸入單詞not和輸出/上下文單詞:( thou實際鄰居),aaron,和taco(負樣本)。咱們繼續查找它們的嵌入 - 對於輸入詞,咱們查看Embedding矩陣。對於上下文單詞,咱們查看Context矩陣(即便兩個矩陣都在咱們的詞彙表中嵌入了每一個單詞)。
而後,咱們計算輸入嵌入與每一個上下文嵌入的點積。。在每種狀況下,會產生一個數字,該數字表示輸入和上下文嵌入的類似性。
如今咱們須要一種方法將這些分數轉化爲看起來像機率的東西 :使用sigmoid函數把機率轉換爲0和1。
如今咱們能夠將sigmoid操做的輸出視爲這些樣本的模型輸出。您能夠看到taco得分最高aaron,而且在sigmoid操做以前和以後仍然具備最低分。既然未經訓練的模型已作出預測,而且看到咱們有一個實際的目標標籤要比較,那麼讓咱們計算模型預測中的偏差。爲此,咱們只從目標標籤中減去sigmoid分數。
error=target−sigmoid
這是「機器學習」的「學習」部分。如今,咱們能夠利用這個錯誤分數調整not,thou,aaron和taco的嵌入,使下一次咱們作出這一計算,結果會更接近目標分數。
訓練步驟到此結束。咱們從這一步驟中獲得稍微好一點的嵌入(not,thou,aaron和taco)。咱們如今進行下一步(下一個正樣本及其相關的負樣本),並再次執行相同的過程。
當咱們循環遍歷整個數據集屢次時,嵌入繼續獲得改進。而後咱們能夠中止訓練過程,丟棄Context矩陣,並使用Embeddings矩陣做爲下一個任務的預訓練嵌入。
word2vec訓練過程當中的兩個關鍵超參數是窗口大小和負樣本的數量。
不一樣的窗口大小能夠更好地提供不一樣的任務。
一種啓發式方法是較小的窗口嵌入(2-15),其中兩個嵌入之間的高類似性得分代表這些單詞是可互換的(注意,若是咱們只查看周圍的單詞,反義詞一般能夠互換 - 例如,好的和壞的常常出如今相似的情境中)。
使用較大的窗口嵌入(15-50,甚至更多)會獲得類似性更能指示單詞相關性的嵌入。實際上,您一般須要對嵌入過程提供註釋指導,爲您的任務帶來有用的類似感。
Gensim默認窗口大小爲5(輸入字自己加上輸入字以前的兩個字和輸入字以後的兩個字)。
負樣本的數量是訓練過程的另外一個因素。原始論文裏負樣本數量爲5-20。它還指出,當你擁有足夠大的數據集時,2-5彷佛已經足夠了。Gensim默認爲5個負樣本。
我但願你如今對詞嵌入和word2vec算法有所瞭解。我也但願如今當你讀到一篇提到「skip gram with negative sampling」(SGNS)的論文時,你會對這些概念有了更好的認識。
本文做者:jalammar。
Distributed Representations of Words and Phrases and their Compositionality [pdf]
Efficient Estimation of Word Representations in Vector Space [pdf]
A Neural Probabilistic Language Model [pdf]
Speech and Language Processing by Dan Jurafsky and James H. Martin is a leading resource for NLP. Word2vec is tackled in Chapter 6.
Chris McCormick has written some great blog posts about Word2vec. He also just released The Inner Workings of word2vec, an E-book focused on the internals of word2vec.
Want to read the code? Here are two options:
Mikolov’s original implementation in C – better yet, this version with detailed comments from Chris McCormick.
Evaluating distributional models of compositional semantics
On word embeddings, part 2