詞袋模型(Bag of Words Model)
詞袋模型的概念
先來看張圖,從視覺上感受一下詞袋模型的樣子。
詞袋模型看起來像一個口袋把所有詞都裝進去,但卻不完全如此。在自然語言處理和信息檢索中作為一種簡單假設,詞袋模型把文本(段落或者文檔)被看作是無序的詞匯集合,忽略語法甚至是單詞的順序,把每一個單詞都進行統計,同時計算每個單詞出現的次數,常被用在文本分類中,如貝葉斯算法、LDA 和 LSA等。
動手實戰詞袋模型
(1)詞袋模型
本例中,我們自己動手寫代碼看看詞袋模型是如何操作的。
首先,引入 jieba 分詞器、語料和停用詞(標點符號集合,自己可以手動添加或者用一個文本字典代替)。
import jieba #定義停用詞、標點符號 punctuation = [",","。", ":", ";", "?"] #定義語料 content = ["機器學習帶動人工智能飛速的發展。", "深度學習帶動人工智能飛速的發展。", "機器學習和深度學習帶動人工智能飛速的發展。" ]
接下來,我們先對語料進行分詞操作,這里用到 lcut() 方法:
#分詞 segs_1 = [jieba.lcut(con) for con in content] print(segs_1)
得到分詞后的結果如下:
[['機器', '學習', '帶動', '人工智能', '飛速', '的', '發展', '。'], ['深度', '學習', '帶動', '人工智能', '飛速', '的', '發展', '。'], ['機器', '學習', '和', '深度', '學習', '帶動', '人工智能', '飛速', '的', '發展', '。']]
因為中文語料帶有停用詞和標點符號,所以需要去停用詞和標點符號,這里語料很小,我們直接去標點符號:
tokenized = [] for sentence in segs_1: words = [] for word in sentence: if word not in punctuation: words.append(word) tokenized.append(words) print(tokenized)
去標點符號后,我們得到結果如下:
[['機器', '學習', '帶動', '人工智能', '飛速', '的', '發展'], ['深度', '學習', '帶動', '人工智能', '飛速', '的', '發展'], ['機器', '學習', '和', '深度', '學習', '帶動', '人工智能', '飛速', '的', '發展']]
下面操作就是把所有的分詞結果放到一個袋子(List)里面,也就是取並集,再去重,獲取對應的特征詞。
#求並集 bag_of_words = [ x for item in segs_1 for x in item if x not in punctuation] #去重 bag_of_words = list(set(bag_of_words)) print(bag_of_words)
得到的特征詞結果如下:
['飛速', '的', '深度', '人工智能', '發展', '和', '機器', '學習', '帶動']
我們以上面特征詞的順序,完成詞袋化:
bag_of_word2vec = [] for sentence in tokenized: tokens = [1 if token in sentence else 0 for token in bag_of_words ] bag_of_word2vec.append(tokens)
最后得到詞袋向量:
[[1, 1, 0, 1, 1, 0, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1]]
上面的例子在編碼時,對於 for 循環多次直接用到列表推導式。在 Python 中,列表推導式的效率比 for 快很多,尤其在數據量大的時候效果更明顯,建議多使用列表推導式。
(2)Gensim 構建詞袋模型
下面我們介紹 Gensim 庫的使用,繼續沿用上面的例子:
from gensim import corpora import gensim #tokenized是去標點之后的 dictionary = corpora.Dictionary(tokenized) #保存詞典 dictionary.save('deerwester.dict') print(dictionary)
這時我們得到的結果不全,但通過提示信息可知道共9個獨立的詞:
Dictionary(9 unique tokens: ['人工智能', '發展', '學習', '帶動', '機器']...)
那我們如何查看所有詞呢?通過下面方法,可以查看到所有詞和對應的下標:
#查看詞典和下標 id 的映射 print(dictionary.token2id)
最后結果如下:
{'人工智能': 0, '發展': 1, '學習': 2, '帶動': 3, '機器': 4, '的': 5, '飛速': 6, '深度': 7, '和': 8}
根據得到的結果,我們同樣可以得到詞袋模型的特征向量。這里順帶提一下函數 doc2bow(),作用只是計算每個不同單詞的出現次數,將單詞轉換為其整數單詞 id 並將結果作為稀疏向量返回。
corpus = [dictionary.doc2bow(sentence) for sentence in segs_1] print(corpus )
得到的稀疏向量結果如下:
[[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1)], [(0, 1), (1, 1), (2, 1), (3, 1), (5, 1), (6, 1), (7, 1)], [(0, 1), (1, 1), (2, 2), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1)]]
詞向量 (Word Embedding)
深度學習帶給自然語言處理最令人興奮的突破是詞向量(Word Embedding)技術。詞向量技術是將詞語轉化成為稠密向量。在自然語言處理應用中,詞向量作為機器學習、深度學習模型的特征進行輸入。因此,最終模型的效果很大程度上取決於詞向量的效果。
詞向量的概念
在 Word2Vec 出現之前,自然語言處理經常把字詞進行獨熱編碼,也就是 One-Hot Encoder。
大數據 [0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0]
雲計算[0,0,0,0,1,0,0,0,0,……,0,0,0,0,0,0,0]
機器學習[0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0]
人工智能[0,0,0,0,0,0,0,0,0,……,1,0,0,0,0,0,0]
比如上面的例子中,大數據 、雲計算、機器學習和人工智能各對應一個向量,向量中只有一個值為1,其余都為0。所以使用 One-Hot Encoder有以下問題:
- 第一,詞語編碼是隨機的,向量之間相互獨立,看不出詞語之間可能存在的關聯關系。
- 第二,向量維度的大小取決於語料庫中詞語的多少,如果語料包含的所有詞語對應的向量合為一個矩陣的話,那這個矩陣過於稀疏,並且會造成維度災難。
而解決這個問題的手段,就是使用向量表示(Vector Representations)。比如 Word2Vec 可以將 One-Hot Encoder 轉化為低維度的連續值,也就是稠密向量,並且其中意思相近的詞也將被映射到向量空間中相近的位置。經過降維,在二維空間中,相似的單詞在空間中的距離也很接近。
這里簡單給詞向量一個定義,詞向量就是要用某個固定維度的向量去表示單詞。也就是說要把單詞變成固定維度的向量,作為機器學習(Machine Learning)或深度學習模型的特征向量輸入。
動手實戰詞向量
(1)Word2Vec
Word2Vec 是 Google 團隊2013年推出的,自提出后被廣泛應用在自然語言處理任務中,並且受到它的啟發,后續出現了更多形式的詞向量模型。Word2Vec 主要包含兩種模型:Skip-Gram 和 CBOW,值得一提的是,Word2Vec 詞向量可以較好地表達不同詞之間的相似和類比關系。
下面我們通過代碼實戰來體驗一下 Word2Vec。通過 pip install gensim
安裝好庫后,即可導入使用。
先導入 Gensim 中的 Word2Vec 和 jieba 分詞器,再引入從百度百科抓取的黃河和長江的語料:
from gensim.models import Word2Vec import jieba #定義停用詞、標點符號 punctuation = [",","。", ":", ";", ".", "'", '"', "’", "?", "/", "-", "+", "&", "(", ")"] sentences = [ "長江是中國第一大河,干流全長6397公里(以沱沱河為源),一般稱6300公里。流域總面積一百八十余萬平方公里,年平均入海水量約九千六百余億立方米。以干流長度和入海水量論,長江均居世界第三位。", "黃河,中國古代也稱河,發源於中華人民共和國青海省巴顏喀拉山脈,流經青海、四川、甘肅、寧夏、內蒙古、陝西、山西、河南、山東9個省區,最后於山東省東營墾利縣注入渤海。干流河道全長5464千米,僅次於長江,為中國第二長河。黃河還是世界第五長河。", "黃河,是中華民族的母親河。作為中華文明的發祥地,維系炎黃子孫的血脈.是中華民族民族精神與民族情感的象征。", "黃河被稱為中華文明的母親河。公元前2000多年華夏族在黃河領域的中原地區形成、繁衍。", "在蘭州的“黃河第一橋”內蒙古托克托縣河口鎮以上的黃河河段為黃河上游。", "黃河上游根據河道特性的不同,又可分為河源段、峽谷段和沖積平原三部分。 ", "黃河,是中華民族的母親河。" ]
上面定義好語料,接下來進行分詞,去標點符號操作 :
sentences = [jieba.lcut(sen) for sen in sentences] tokenized = [] for sentence in sentences: words = [] for word in sentence: if word not in punctuation: words.append(word) tokenized.append(words) print(tokenized)
這樣我們獲取的語料在分詞之后,去掉了標點符號,如果做得更嚴謹,大家可以去停用詞,然后進行模型訓練:
model = Word2Vec(tokenized, sg=1, size=100, window=5, min_count=2, negative=1, sample=0.001, hs=1, workers=4)
參數解釋如下:
- sg=1 是
skip-gram
算法,對低頻詞敏感;默認 sg=0 為 CBOW 算法。 - size 是輸出詞向量的維數,值太小會導致詞映射因為沖突而影響結果,值太大則會耗內存並使算法計算變慢,一般值取為100到200之間。
- window 是句子中當前詞與目標詞之間的最大距離,3表示在目標詞前看3-b 個詞,后面看 b 個詞(b 在0-3之間隨機)。
min_count
是對詞進行過濾,頻率小於min-count
的單詞則會被忽視,默認值為5。- negative 和 sample 可根據訓練結果進行微調,sample 表示更高頻率的詞被隨機下采樣到所設置的閾值,默認值為 1e-3。
- hs=1 表示層級 softmax 將會被使用,默認 hs=0 且 negative 不為0,則負采樣將會被選擇使用。
- 詳細參數說明可查看 Word2Vec 源代碼。
訓練后的模型可以保存與加載,如下代碼所示:
model.save('model') #保存模型 model = Word2Vec.load('model') #加載模型
模型訓練好之后,接下來就可以使用模型,可以用來計算句子或者詞的相似性、最大匹配程度等。
例如,我們判斷一下黃河和黃河自己的相似度:
print(model.similarity('黃河', '黃河'))
結果輸出為:
1.0000000000000002
例如,當輸入黃河和長江來計算相似度的時候,結果就比較小,因為我們的語料實在太小了。
print(model.similarity('黃河', '長江'))
結果輸出為:
-0.036808977457324699
下面我們預測最接近的詞,預測與黃河和母親河最接近,而與長江不接近的詞:
print(model.most_similar(positive=['黃河', '母親河'], negative=['長江']))
得到結果如下,可以根據相似度大小找到與黃河和母親河最接近的詞(實際處理建議增大數據量和去停用詞)。
[('是', 0.14632007479667664), ('以', 0.14630728960037231), ('長河', 0.13878652453422546), ('河道', 0.13716217875480652), ('在', 0.11577725410461426), ('全長', 0.10969121754169464), ('內蒙古', 0.07590540498495102), ('入海', 0.06970417499542236), ('民族', 0.06064444035291672), ('中華文明', 0.057667165994644165)]
上面通過小數據量的語料實戰,加強了對 Word2Vec 的理解,總之 Word2Vec 是一種將詞變成詞向量的工具。通俗點說,只有這樣文本預料才轉化為計算機能夠計算的矩陣向量。
(2)Doc2Vec
Doc2Vec 是 Mikolov 在 Word2Vec 基礎上提出的另一個用於計算長文本向量的工具。在 Gensim 庫中,Doc2Vec 與 Word2Vec 都極為相似。但兩者在對輸入數據的預處理上稍有不同,Doc2vec 接收一個由 LabeledSentence 對象組成的迭代器作為其構造函數的輸入參數。其中,LabeledSentence 是 Gensim 內建的一個類,它接收兩個 List 作為其初始化的參數:word list 和 label list。
Doc2Vec 也包括兩種實現方式:DBOW(Distributed Bag of Words)和 DM (Distributed Memory)。DBOW 和 DM 的實現,二者在 gensim 庫中的實現用的是同一個方法,該方法中參數 dm = 0 或者 dm=1 決定調用 DBOW 還是 DM。Doc2Vec 將文檔語料通過一個固定長度的向量表達。
下面是 Gensim 中 Doc2Vec 模型的實戰,我們把上述語料每一句話當做一個文本,添加上對應的標簽。接下來,定義數據預處理類,作用是給每個文章添加對應的標簽:
#定義數據預處理類,作用是給每個文章添加對應的標簽 from gensim.models.doc2vec import Doc2Vec,LabeledSentence doc_labels = ["長江","黃河","黃河","黃河","黃河","黃河","黃河"] class LabeledLineSentence(object): def __init__(self, doc_list, labels_list): self.labels_list = labels_list self.doc_list = doc_list def __iter__(self): for idx, doc in enumerate(self.doc_list): yield LabeledSentence(words=doc,tags=[self.labels_list[idx]]) model = Doc2Vec(documents,dm=1, size=100, window=8, min_count=5, workers=4) model.save('model') model = Doc2Vec.load('model')
上面定義好了數據預處理函數,我們將 Word2Vec 中分詞去標點后的數據,進行轉換:
iter_data = LabeledLineSentence(tokenized, doc_labels)
得到一個數據集,我開始定義模型參數,這里 dm=1,采用了 Gensim 中的 DM 實現。
model = Doc2Vec(dm=1, size=100, window=8, min_count=5, workers=4)
model.build_vocab(iter_data)
接下來訓練模型, 設置迭代次數1000次,start_alpha
為開始學習率,end_alpha
與 start_alpha
線性遞減。
model.train(iter_data,total_examples=model.corpus_count,epochs=1000,start_alpha=0.01,end_alpha =0.001)
最后我們對模型進行一些預測:
#根據標簽找最相似的,這里只有黃河和長江,所以結果為長江,並計算出了相似度 print(model.docvecs.most_similar('黃河'))
得到的結果:
[('長江', 0.25543850660324097)]
然后對黃河和長江標簽做相似性計算:
print(model.docvecs.similarity('黃河','長江'))
得到的結果:
0.25543848271351405
上面只是在小數據量進行的小練習,而最終影響模型准確率的因素有:文檔的數量越多,文檔的相似性越好,也就是基於大數據量的模型訓練。在工業界,Word2Vec 和 Doc2Vec 常見的應用有:做相似詞計算;相關詞挖掘,在推薦系統中用在品牌、用戶、商品挖掘中;上下文預測句子;機器翻譯;作為特征輸入其他模型等。總結,本文只是簡單的介紹了詞袋和詞向量模型的典型應用,對於兩者的理論和其他詞向量模型,比如 TextRank 、FastText 和 GloVe 等。
import jieba #定義停用詞、標點符號 punctuation = [",","。", ":", ";", "?"] #定義語料 content = ["機器學習帶動人工智能飛速的發展。", "深度學習帶動人工智能飛速的發展。", "機器學習和深度學習帶動人工智能飛速的發展。" ] #分詞 segs_1 = [jieba.lcut(con) for con in content] print(segs_1) print("---------------------------------") #去除停用詞 tokenized = [] for sentence in segs_1: words = [] for word in sentence: if word not in punctuation: words.append(word) tokenized.append(words) print(tokenized) print("---------------------------------") #求並集 bag_of_words = [ x for item in segs_1 for x in item if x not in punctuation] #去重 bag_of_words = list(set(bag_of_words)) print(bag_of_words) print("---------------------------------") #詞袋向量 bag_of_word2vec = [] for sentence in tokenized: tokens = [1 if token in sentence else 0 for token in bag_of_words] bag_of_word2vec.append(tokens) print(bag_of_word2vec)
from gensim.models import Word2Vec import jieba #定義停用詞、標點符號 punctuation = [",","。", ":", ";", ".", "'", '"', "’", "?", "/", "-", "+", "&", "(", ")"] sentences = [ "長江是中國第一大河,干流全長6397公里(以沱沱河為源),一般稱6300公里。流域總面積一百八十余萬平方公里,年平均入海水量約九千六百余億立方米。以干流長度和入海水量論,長江均居世界第三位。", "黃河,中國古代也稱河,發源於中華人民共和國青海省巴顏喀拉山脈,流經青海、四川、甘肅、寧夏、內蒙古、陝西、山西、河南、山東9個省區,最后於山東省東營墾利縣注入渤海。干流河道全長5464千米,僅次於長江,為中國第二長河。黃河還是世界第五長河。", "黃河,是中華民族的母親河。作為中華文明的發祥地,維系炎黃子孫的血脈.是中華民族民族精神與民族情感的象征。", "黃河被稱為中華文明的母親河。公元前2000多年華夏族在黃河領域的中原地區形成、繁衍。", "在蘭州的“黃河第一橋”內蒙古托克托縣河口鎮以上的黃河河段為黃河上游。", "黃河上游根據河道特性的不同,又可分為河源段、峽谷段和沖積平原三部分。 ", "黃河,是中華民族的母親河。" ] sentences = [jieba.lcut(sen) for sen in sentences] tokenized = [] for sentence in sentences: words = [] for word in sentence: if word not in punctuation: words.append(word) tokenized.append(words) print(tokenized) model = Word2Vec(tokenized, sg=1, size=100, window=5, min_count=2, negative=1, sample=0.001, hs=1, workers=4) model.save('model') # 保存模型 model = Word2Vec.load('model') # 加載模型 print(model.similarity('黃河', '黃河')) print(model.similarity('黃河', '長江')) # 預測與黃河和母親河最接近,而與長江不接近的詞 print(model.most_similar(positive=['黃河', '母親河'], negative=['長江']))
參考文獻: