概述
在NLP中,對於一個詞,我們用一個詞向量來表示,最常見的一個方式是one hot representation,這種詞向量的編碼方式就是用一個很長的向量來表示一個詞,向量的長度為詞典的大小N,向量的分量只有一個1,其他全為0,1的位置對應該詞在詞典中的索引。這種表示方法不需要繁瑣的計算,簡單易得,但是缺點也不少:
- 我們的詞匯表一般都非常大,比如達到百萬級別,這樣每個詞都用百萬維的向量來表示,簡直是內存的災難。
- 這樣表示數據,數據非常稀疏,稀疏數據的訓練效率比較低,我們通常需要更多地數據來訓練。
- 維度很高,很容易造成維數災難。
- 無法體現出詞之間的關系,比如“面條”和“方便面”顯然有非常緊密的關系,但轉化成向量[1,0,0]和[0,1,0]以后,就看不出兩者有什么關系了,因為這兩個向量相互正交。
- 這樣的向量其實除了一個位置是1,其余的位置全部都是0,表達的效率不高。
另一個方式是分布式表示(Distributed Representation),基本思想是將每個詞表達成一個固定長度的、稠密、連續的實數短向量,此時向量長度可以自由選擇,與詞典規模無關。與之相對的one-hot encoding向量空間只有一個維度是1,其余都是0。分布式表示最大的優點是具備非常powerful的特征表達能力,比如 n 維向量每維 k 個值,可以表征 \(k^n\) 個概念。向量空間模型(Vector Space Models)可以將字詞轉化為連續值的向量表示,並且其中意思相近的詞將被映射到向量空間中相近的位置。向量空間模型在NLP中主要依賴的假設為Distributional Hypothesis,即在相同語境中出現的詞其語義也相近。向量空間模型可以分為兩類,一類是計數模型,比如LSA;另一類是預測模型(比如Neural Probabilisitc Language Models)。計數模型統計在語料庫中,相鄰出現的詞的頻率,再把這些計數統計結果轉為小而稠密的矩陣;而預測模型則根據一個詞周圍相鄰的詞推測出這個詞,以及它的向量空間。
我們今天要要介紹的word2vec和Bengio在2003年發表的論文A neural probabilistic language model中提出的模型很相似,可以看做是在此基礎上的改進,下圖是Bengio在論文中提出的網絡結構:
這篇文章提出的神經網絡語言模型(NNLM,Neural Probabilistic Language Model)采用的是文本分布式表示,即每個詞表示為稠密的實數向量。NNLM模型的目標是構建語言模型:
詞的分布式表示即word embedding是訓練語言模型的一個附加產物,即圖中的Matrix C。
盡管Bengio 2003年便提出了NNLM,但是由於它的局限性,word embedding真正火起來是google Mikolov 2013年發表的兩篇word2vec的文章 Efficient Estimation of Word Representations in Vector Space 和 Distributed Representations of Words and Phrases and their Compositionality,更重要的是發布了簡單好用的word2vec工具包。值得說明的一點是,word2vec是一個獲得word embeding的工具,該工具得到的訓練結果——word embedding,可以很好地度量詞與詞之間的相似性,挖掘詞之間的聯系。里面的模型只是很淺的神經網絡,算不得深度學習算法,除了word2vec之外,我們還有其他的獲得word embeding的方法,如bert等。
下面我們會詳細的介紹word2vec中的兩個主要語言模型:CBOW和Skip-Gram,以及Hierarchical Softmax和Negative Sampling這兩個針對以上兩種模型的具體的優化方法。這兩個方法很好的解決了計算有效性,事實上這兩個方法都沒有嚴格的理論證明,有些trick之處,非常的實用主義。然后我們利用gensim的python版來介紹word2vec的使用。
word2vec原理
CBOW模型和Skip-gram模型是神經概率語言模型的兩種變形形式,下面我們將會一一介紹。
CBOW模型
CBOW(Continuous Bag-of-Words)的意思就是用上下文來預測當前詞,如下圖所示:
在上圖中,通過詞\(w_t\)的前后詞\(w_{t-2}\),\(w_{t-1}\),\(w_{t+1}\), \(w_{t+2}\)來預測當前詞\(w_t\)。此處的窗口的大小window為2。
其中,在CBOW模型中包含三層,即輸入層,映射層和輸出層。輸入層是當前詞周圍每個詞對應的詞向量,在映射層將這些詞的詞向量相加求平均,在輸出層求出為當前詞的概率。
注:
如何根據一個詞的one-hot編碼,得到它對應的詞向量,可以看下圖所示:
在word2vec實際的實現的網絡里,上式中左邊的式子是輸入詞的one-hot編碼和隱藏層參數矩陣的乘積,在做這個乘法時是不會進行矩陣的運算的,而是直接通過輸入值中1的位置索引來尋找隱藏層中的參數矩陣中對應的索引的行。word2vec訓練的目的就是得到這個隱藏層參數矩陣,這個矩陣也可以叫做embeding 矩陣,由於這一步很簡單,我們上面的講解里忽略掉這個隱藏層,輸入層已經是之后詞得到它所對應的詞向量了,當然這個輸入的詞向量也是要求解的參數,下面的Skip-gram模型里面也按照這個設定,不再詳述。
從數學上看,CBOW模型等價於一個詞袋模型的向量乘以一個embedding矩陣,從而得到一個連續的embedding向量。這也是CBOW模型名稱的由來。
Skip-gram模型
而Skip-gram(Continuous Skip-gram)模型則與CBOW模型正好相反,在Skip-gram模型中,是用當前詞來預測上下文,如下圖所示:
在上圖中,通過詞\(w_t\)來預測它的前后詞\(w_{t-2}\),\(w_{t-1}\),\(w_{t+1}\), \(w_{t+2}\)。此處的窗口的大小window為2。
這里輸入層是當前詞對應的詞向量,映射層什么也不做,在輸出層求出當前詞上下文窗口中詞的概率。
Skip-gram名稱來源於該模型在訓練時會對上下文環境里的word進行采樣,然后預測我們選到這個附近詞的概率。
gensim中word2vec的使用
首先我們先介紹一下gensim中的word2vec API,官方API介紹如下:
class gensim.models.word2vec.Word2Vec(sentences=None,
corpus_file=None, vector_size=100, alpha=0.025,
window=5, min_count=5, max_vocab_size=None, sample=0.001,
seed=1, workers=3, min_alpha=0.0001, sg=0, hs=0, negative=5,
ns_exponent=0.75, cbow_mean=1, hashfxn=<built-in function hash>,
epochs=5, null_word=0, trim_rule=None, sorted_vocab=1,
batch_words=10000, compute_loss=False, callbacks=(),
comment=None, max_final_vocab=None)
在gensim
中,word2vec
相關的API
都在包gensim.models.word2vec
中。和算法有關的參數都在類gensim.models.word2vec.Word2Vec
中。算法需要注意的參數有:
- sentences: 我們要分析的語料,可以是一個列表,或者從文件中遍歷讀出(通過
word2vec
提供的LineSentence
類來讀文件,word2vec.LineSentence(filename)
)。 - vector_size: 詞向量的維度,默認值是
100
。這個維度的取值一般與我們的語料的大小相關,如果是不大的語料,比如小於100M
的文本語料,則使用默認值一般就可以了。如果是超大的語料,建議增大維度。 - window:即詞向量上下文最大距離,
window
越大,則和某一詞較遠的詞也會產生上下文關系。默認值為5
。在實際使用中,可以根據實際的需求來動態調整這個window
的大小。如果是小語料則這個值可以設的更小。對於一般的語料這個值推薦在[5,10]
之間。 - sg: 即我們的
word2vec
兩個模型的選擇了。如果是0
, 則是CBOW
模型,是1
,則是Skip-Gram
模型,默認是0
,即CBOW
模型。 - hs: 即我們的
word2vec
兩個解法的選擇了,如果是1
, 則是Hierarchical Softmax
,是0
的話並且負采樣個數negative
大於0
, 則是Negative Sampling
。默認是0
即Negative Sampling
。 - negative:即使用
Negative Sampling
時負采樣的個數,默認是5
。推薦在[3,10]
之間。 - cbow_mean: 僅用於
CBOW
在做投影的時候,為0
,則算法中的\(x_w\)為上下文的詞向量之和,為1
則為上下文的詞向量的平均值。在我們的原理篇中,是按照詞向量的平均值來描述的。個人比較喜歡用平均值來表示\(x_w\),默認值也是1
,不推薦修改默認值。 - min_count:需要計算詞向量的最小詞頻。這個值可以去掉一些很生僻的低頻詞,默認是
5
。如果是小語料,可以調低這個值。 - iter: 隨機梯度下降法中迭代的最大次數,默認是
5
。對於大語料,可以增大這個值。 - alpha: 在隨機梯度下降法中迭代的初始步長。算法原理篇中標記為
η
,默認是0.025
。 - min_alpha: 由於算法支持在迭代的過程中逐漸減小步長,
min_alpha
給出了最小的迭代步長值。隨機梯度下降中每輪的迭代步長可以由iter,alpha, min_alpha一起得出。這部分由於不是word2vec算法的核心內容,因此在原理篇我們沒有提到。對於大語料,需要對alpha, min_alpha,iter一起調參,來選擇合適的三個值。
常用的導入中文訓練數據的方式,有以下幾種:
使用Python的內置列表,
# 導入gensim庫
from gensim.models import word2vec
# 第一種輸入方式:Python內置列表
sentences = [['第', '一', '個', '句子'], ['第', '二', '個', '句子']]
# 調用函數訓練模型
model = word2vec.Word2Vec(sentences)
sentences是一個列表的列表,它的每個元素是一個句子所構成的列表。
當數據集特別大的時候,使用上述第一種方法就需要消耗特別多的內存,gensim還支持使用迭代生成器的方式輸入,
class MySentences(object):
def __init__(self, dirname):
self.dirname = dirname
def __iter__(self):
for fname in os.listdir(self.dirname):
for line in open(os.path.join(self.dirname, fname)):
yield line.split()
sentences = MySentences('/some/directory') # 第二種輸入方式:占用內存少的迭代器
model = Word2Vec(sentences)
這個輸入方式會依次打開“/some/directory”文件夾中的每個數據文件,對於每個文件一行一行的讀取數據進模型,這樣就避免了一次性讀入而占用太多內存。注意這里的每個文件里的每一行都是經過分詞的一個句子,單詞之間用空格分隔。
對於一個語料文件,里面的每一行都是經過分詞的一個句子,單詞之間用空格分隔,那么我們還可以使用
sentences = word2vec.LineSentence('corpus.txt')的方式進行讀取,而sentences = word2vec.PathLineSentences(dir_name)則是針對dir_name文件夾下的所有的這樣的文件進行讀取。
from gensim.models import word2vec
#訓練
model = word2vec.Word2Vec(sentences, hs=1,min_count=1,window=3,size=100)
model.save('model') # 保存模型
model = word2vec.Word2Vec.load('model') # 加載模型
model.wv['man']# 得到詞的詞向量
#找出某一個詞向量最相近的詞集合
for val in model.wv.similar_by_word("酒店", topn=10):
print(val[0], val[1])
#查看兩個詞的相似性
#找出不同類的詞
參考
word2vec前世今生
Word2Vec Tutorial - The Skip-Gram Model
word2vec原理(二) 基於Hierarchical Softmax的模型
用深度學習(CNN RNN Attention)解決大規模文本分類問題 - 綜述和實踐
word2vector的原理,結構,訓練過程
Word2vec 入門(skip-gram部分)
源碼解析——word2vec源碼解析