以 gensim 訓練中文詞向量
来源:互联网 发布:安装数据库失败日志 编辑:程序博客网 时间:2024/06/06 02:34
转自: http://zake7749.github.io/2016/08/28/word2vec-with-gensim/
最近正在嘗試幾種文本分類的算法,卻一直苦於沒有結構化的中文語料,原本是打算先爬下大把大把的部落格文章,再依 tag 將它們分門別類,可惜試了一陣子後,我見識到了理想和現實間的鴻溝。
所以就找上了基於非監督學習的 word2vec,為了銜接後續的資料處理,這邊採用的是基於 python 的主題模型函式庫 gensim。這篇教學並不會談太多 word2vec 的數學原理,而是考慮如何輕鬆又直覺地訓練中文詞向量,文章裡所有的程式碼都會傳上 github,現在,就讓我們進入正題吧。
取得語料
要訓練詞向量,第一步當然是取得資料集。由於 word2vec 是基於非監督式學習,訓練集一定一定要越大越好,語料涵蓋的越全面,訓練出來的結果也會越漂亮。我所採用的是維基百科於2016/08/20的備份,文章篇數共有 2822639 篇。因為維基百科會定期更新備份資料,如果 8 月 20 號的備份不幸地被刪除了,也可以前往維基百科:資料庫下載挑選更近期的資料,不過請特別注意一點,我們要挑選的是以 pages-articles.xml.bz2
結尾的備份,而不是以 pages-articles-multistream.xml.bz2
結尾的備份唷,否則會在清理上出現一些異常,無法正常解析文章。
在等待下載的這段時間,我們可以先把這次的主角gensim
配置好:
1
pip3 install --upgrade gensim
維基百科下載好後,先別急著解壓縮,因為這是一份 xml 文件,裏頭佈滿了各式各樣的標籤,我們得先想辦法送走這群不速之客,不過也別太擔心,gensim 早已看穿了一切,藉由調用 wikiCorpus,我們能很輕鬆的只取出文章的標題和內容。
初始化WikiCorpus
後,能藉由get_texts()
可迭代每一篇文章,它所回傳的是一個tokens list,我以空白符將這些 tokens 串接起來,統一輸出到同一份文字檔裡。這邊要注意一件事,get_texts()
受wikicorpus.py
中的變數ARTICLE_MIN_WORDS
限制,只會回傳內容長度大於 50 的文章。
12345678910111213141516171819202122232425
# -*- coding: utf-8 -*-import loggingimport sysfrom gensim.corpora import WikiCorpusdef main(): if len(sys.argv) != 2: print("Usage: python3 " + sys.argv[0] + " wiki_data_path") exit() logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) wiki_corpus = WikiCorpus(sys.argv[1], dictionary={}) texts_num = 0 with open("wiki_texts.txt",'w',encoding='utf-8') as output: for text in wiki_corpus.get_texts(): output.write(b' '.join(text).decode('utf-8') + '\n') texts_num += 1 if texts_num % 10000 == 0: logging.info("已處理 %d 篇文章" % texts_num)if __name__ == "__main__": main()
在shell 裡輸入:
1
python3 wiki_to_txt.py zhwiki-20160820-pages-articles.xml.bz2
如果你的資料不是 8 月 20 號的備份,記得把zhwiki-20160820-pages-articles.xml.bz2
換成你的備份的檔名唷。這約需花費 20 分鐘來處理,就讓我們先看一下接下來還要做些什麼吧~
開始斷詞
我們有清完標籤的語料了,第二件事就是要把語料中每個句子,進一步拆解成一個一個詞,這個步驟稱為「斷詞」。中文斷詞的工具比比皆是,這裏我採用的是 jieba,儘管它在繁體中文的斷詞上還是有些不如CKIP
,但他實在太簡單、太方便、太好調用了,足以彌補這一點小缺憾:
12
快速安裝結巴pip3 install jieba
12345678
# 斷詞示例import jiebaseg_list = jieba.cut("我来到北京清华大学", cut_all=False)print("Default Mode: " + "/ ".join(seg_list)) # 精确模式#輸出 Default Mode: 我/ 来到/ 北京/ 清华大学
現在,我們上一階段的檔案也差不多出爐了,以vi打開看起來會是這個樣子:
1
歐幾里得 西元前三世紀的希臘數學家 現在被認為是幾何之父 此畫為拉斐爾的作品 雅典學院 数学 是利用符号语言研究數量 结构 变化以及空间等概念的一門学科 从某种角度看屬於形式科學的一種 數學透過抽象化和邏輯推理的使用 由計數 計算 數學家們拓展這些概念......
Opps!出了一點狀況,我們發現簡體跟繁體混在一起了,比如「数学」與「數學」會被 word2vec 當成兩個不同的詞,所以我們在斷詞前,還需加上一道繁簡轉換的手續。然而我們的語料集相當龐大,一般的繁簡轉換會有些力不從心,建議採用OpenCC,轉換的方式很簡單:
1
opencc -i wiki_texts.txt -o wiki_zh_tw.txt -c s2tw.json
如果是要將繁體轉為簡體,只要將config的參數從s2tw.json
改成t2s.json
即可。現在再檢查一次wiki_zh_tw.txt
,的確只剩下繁體字了,終於能進入斷詞,輸入python3 segment.py
。
1234567891011121314151617181920212223242526272829303132333435
# -*- coding: utf-8 -*-import jiebaimport loggingdef main(): logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) # jieba custom setting. jieba.set_dictionary('jieba_dict/dict.txt.big') # load stopwords set stopwordset = set() with open('jieba_dict/stopwords.txt','r',encoding='utf-8') as sw: for line in sw: stopwordset.add(line.strip('\n')) output = open('wiki_seg.txt','w') texts_num = 0 with open('wiki_zh_tw.txt','r') as content : for line in content: words = jieba.cut(line, cut_all=False) for word in words: if word not in stopwordset: output.write(word +' ') texts_num += 1 if texts_num % 10000 == 0: logging.info("已完成前 %d 行的斷詞" % texts_num) output.close() if __name__ == '__main__':main()
Stopwords and Window
好啦,這個東西大概要跑個 80 分鐘,先讓我講些幹話,先讓我們看看上頭做了什麼。除了之前演示的斷詞外,這邊還多做了兩件事,一是調整jieba的辭典,讓他對繁體斷詞比較友善,二是引入了停用詞,停用詞就是像英文中的 the,a,this,中文的你我他,與其他詞相比顯得不怎麼重要,對文章主題也無關緊要的,就可以將它視為停用詞。而要排除停用詞的理由,其實與word2vec的實作概念大大相關,由於在開頭講明了不深究概念,就讓我舉個例子替代長篇大論。
首先,在word2vec有一個概念叫 windows,我習慣叫他窗口,因為它給我的感覺跟TCP 那個會滑來滑去的東東很像。
Word2Vec
很顯然,一個詞的意涵跟他的左右鄰居很有關係,比如「雨越下越大,茶越充越淡」,什麼會「下」?「雨」會下,什麼會「淡」?茶會「淡」,這樣的類比舉不勝舉,那麼,若把思維逆轉過來呢?
顯然,我們或多或少能從左右鄰居是誰,猜出中間的是什麼,這很像我們國高中時天天在練的英文克漏字。那麼問題來了,左右鄰居有誰?能更精確地說,你要往左往右看幾個?假設我們以「孔乙己 一到 店 所有 喝酒 的 人 便都 看著 他 笑」為例,如果往左往右各看一個:
12345
[孔乙己 一到] 店 所有 喝酒 的 人 便 都 看著 他 笑[孔乙己 一到 店] 所有 喝酒 的 人 便 都 看著 他 笑孔乙己 [一到 店 所有] 喝酒 的 人 便 都 看著 他 笑孔乙己 一到 [店 所有 喝酒] 的 人 便 都 看著 他 笑......
這樣就構成了一個 size=1 的 windows,這個 1 是極端的例子,為了讓我們看看有停用詞跟沒停用詞差在哪,這句話去除了停用詞應該會變成:
1
孔乙己 一到 店 所有 喝酒 人 看著 笑
我們看看「人」的窗口變化,原本是「的 人 便」,後來是「喝酒 人 看著」,相比原本的情形,去除停用詞後,我們對「人」這個詞有更多認識,比如人會喝酒,人會看東西,當然啦,這是我以口語的表達,機器並不會這麼想,機器知道的是人跟喝酒會有某種關聯,跟看會有某種關聯,但儘管如此,也遠比本來的「的 人 便」好太多太多了。
就在剛剛,我的斷詞已經跑完了,現在,讓我們進入收尾的階段吧
1
2016-08-26 22:27:59,480 : INFO : 已處理 260000 個 token
訓練詞向量
這是最簡單的部分,同時也是最困難的部分,簡單的是程式碼,困難的是詞向量效能上的微調與後訓練。對了,如果你已經對詞向量和語言模型有些研究,在輸入python3 train.py
之前,建議先看一下之後的內文,相信我,你會需要的。
12345678910111213141516171819
# -*- coding: utf-8 -*-from gensim.models import word2vecimport loggingdef main(): logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) sentences = word2vec.Text8Corpus("wiki_seg.txt") model = word2vec.Word2Vec(sentences, size=250) # Save our model. model.save("med250.model.bin") # To load a model. # model = word2vec.Word2Vec.load("your_model.bin")if __name__ == "__main__": main()
扣掉logging
與註釋就剩下三行,真是精簡的漂亮。上頭通篇的學問在model = word2vec.Word2Vec(sentences, size=250)
,我們先讓它現出原型:
1
class gensim.models.word2vec.Word2Vec(sentences=None, 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, cbow_mean=1, hashfxn=<built-in function hash>, iter=5, null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000)
這抵得上 train.py 的所有程式碼了。不過也別太擔心,裏頭多是無關緊要的參數,從初學的角度來看,我們會去動到的大概是:
- sentences:當然了,這是要訓練的句子集,沒有他就不用跑了
- size:這表示的是訓練出的詞向量會有幾維
- alpha:機器學習中的學習率,這東西會逐漸收斂到
min_alpha
- sg:這個不是三言兩語能說完的,sg=1表示採用skip-gram,sg=0 表示採用cbow
- window:還記得孔乙己的例子嗎?能往左往右看幾個字的意思
- workers:執行緒數目,除非電腦不錯,不然建議別超過 4
- min_count:若這個詞出現的次數小於
min_count
,那他就不會被視為訓練對象
等摸清 Word2Vec 背後的原理後,也可以試著調調hs
、negative
,看看對效能會有什麼影響。
詞向量實驗
訓練完成後,讓我們來測試一下模型的效能,運行python3 demo.py
。由於 gensim 將整個模型讀了進來,所以記憶體會消耗相當多,如果出現了MemoryError
,可能得調整一下min_count
或對常用詞作一層快取,這點要注意一下。
先來試試相似詞排序吧!
123456789101112131415161718192021222324252627282930313233343536373839404142
飲料相似詞前 100 排序飲品,0.8439314365386963果汁,0.7858869433403015罐裝,0.7305712699890137冰淇淋,0.702262818813324酸奶,0.7007108926773071口香糖,0.6987590193748474酒類,0.6967358589172363可口可樂,0.6885123252868652酒精類,0.6843742728233337含酒精,0.6825539469718933啤酒,0.6816493272781372薯片,0.6779764294624329紅茶,0.6656282544136047奶茶,0.656740128993988提神,0.6566425561904907牛奶,0.6556192636489868檸檬茶,0.6494661569595337籃球相似詞前 100 排序美式足球,0.6463411450386047橄欖球,0.6382837891578674男子籃球,0.6187020540237427冰球,0.6056296825408936棒球,0.5859025716781616籃球運動,0.5831792950630188籃球員,0.5782726407051086籃球隊,0.576259195804596排球,0.5743488073348999黑子,0.5609416961669922籃球比賽,0.5498511791229248打球,0.5496408939361572中國籃球,0.5471529960632324男籃,0.5460700392723083ncaa,0.543986439704895投投,0.5439497232437134曲棍球,0.5435376167297363nba,0.5415610671043396
前100太多了,所以只把前幾個結果貼上來,我們也能調用model.similarity(word2,word1)
來直接取得兩個詞的相似度:
123456789101112131415
冰沙 刨冰計算 Cosine 相似度0.631961417455電腦 飛鏢計算 Cosine 相似度0.154503715708電腦 程式計算 Cosine 相似度0.5021829415衛生紙 漫畫計算 Cosine 相似度0.167776641495
能稍微區隔出詞與詞之間的主題,整體來說算是可以接受的了。
更上一層樓
如何優化詞向量的表現?這其實有蠻多方法的,大方向是從應用的角度出發,我們能針對應用特化的語料進行再訓練,除此之外,斷詞器的選擇也很重要,它很大程度的決定什麼詞該在什麼地方出現,如果發現 jieba
有些力不能及的,不妨試著採用別的斷詞器,或是試著在 jieba
自訂辭典,調一下每個詞的權重。
應用考慮好了,接著看看模型,我們可以調整 model()
的參數,比方窗口大小、維度、學習率,進一步還能比較 skip-gram 與 cbow 的效能差異,什麼,你說不知道 skip-gram 跟 cbow 是什麼?且看下回分解。
參考資料
Training Word2Vec Model on English Wikipedia by Gensim
- 以 gensim 訓練中文詞向量
- 【python gensim使用】word2vec词向量处理中文语料
- 【python gensim使用】word2vec词向量处理中文语料
- windows环境下使用wiki中文百科及gensim工具库训练词向量
- 文本分析--Gensim向量空间
- gensim中文处理
- 【gensim中文教程】开始使用gensim
- gensim文档-语料库与向量空间
- 基于gensim进行句向量的训练
- gensim 文档-语料库与向量空间
- NLP02-Gensim语料与向量空间
- gensim Word2Vec 处理中文 KeyError
- gensim 中文语料训练 word2vec
- GENSIM
- GENSIM
- gensim
- 【python gensim使用】word2vec词向量处理英文语料
- 基于python的gensim word2vec训练词向量
- Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$Ac
- php 本地环境 局域网 访问设置 json 模拟请求 字符串不同类型分割
- 本地代码上传到github
- 代理传值
- android调用外部导航(百度,高德)
- 以 gensim 訓練中文詞向量
- 矩阵的最短路径和
- Android中framework res项目中各个文件夹的含义和用途详解
- Prepar3d.cfg
- 前端开发者,必须知道的项目资源
- 3.1SpringBoot-JPA演示
- iOS sectionFootersPinToVisibleBounds 导致iOS9.0系统之前的用户闪退
- window的showAsDropDown失效的问题
- Java位运算在程序设计中的使用:位掩码(BitMask)