1. 程式人生 > >Gensim官方教程翻譯(五)——英文維基百科的實驗

Gensim官方教程翻譯(五)——英文維基百科的實驗

僅供個人學習之用,如有錯誤,敬請指正。原文地址

為了測試gensim的效能,我們在維基百科英文版上運行了一些實驗。
這個頁面描述了獲取與處理維基百科的過程,以便任何人都能再現這個結果。本教程要求已經正確安裝了gensim

譯者注:維基百科的內容在不斷更新,因此本文的結果僅供參考,可能與實際情況有出入。

準備語料庫

  1. 首先,從 http://download.wikimedia.org/enwiki/下載維基百科文件的存檔(你可能需要一個像這樣的檔案enwiki-latest-pages-articles.xml.bz2)。這個檔案大約8GB包含了所有的英文維基百科的文章(譯者注:實際上應該更大)。
  2. 將文章轉換為普通文字(處理wiki標記)並且將結果以稀疏TF-IDF向量的形式儲存。在Python中,非常輕鬆就能完成這個任務,我們甚至不需要將整個壓縮檔案解壓到硬碟上。Gensim中有一個指令碼可以完成這個任務,僅需:

已經廢棄 | Depressed

$ python -m gensim.scripts.make_wiki

當前可用 | Recommend(2017年7月19日)

>>> import genism.corpora.WikiCorpus  
>>> wiki = WikiCorpus('enwiki-20100622-pages-articles.xml.bz2'
) # 建立word到word_id的對映,需要很長時間,請耐心 >>> MmCorpus.serialize('wiki_en_tfidf.mm', wiki) # 將語料庫存到硬碟中,需要很長時間,請耐心 >>> wiki.dictionary.save_as_text('wiki_en_wordids.txt')

注:預處理步驟對8.2G壓縮的維基存檔實施了2個操作(提取字典、創建於儲存稀疏向量),在我的筆記版上消耗了大約9小時,因此你可能想要來1、2杯咖啡。
你需要大概35GB空閒硬碟空間來儲存輸出的稀疏向量。我建議直接壓縮這些檔案,例如bzip2(壓縮至約13GB)。Gensim可以直接使用壓縮檔案,可以幫助你節約硬碟空間。

潛在語義分析

首先讓我們載入在上述步驟建立的語料庫迭代器與字典:

>>> import logging, gensim, bz2
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

>>> # 載入id->word對映(字典),上述步驟的結果之一
>>> id2word = gensim.corpora.Dictionary.load_from_text('wiki_en_wordids.txt')
>>> # 載入語料庫迭代器
>>> mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm')
>>> # mm = gensim.corpora.MmCorpus(bz2.BZ2File('wiki_en_tfidf.mm.bz2')) # 如果你壓縮了tf-idf輸出,請使用這個

>>> print(mm)
MmCorpus(3931787 documents, 100000 features, 756379027 non-zero entries)

我們看到我們的語料庫用稀疏TF-IDF矩陣表示時,包含了3.9M的文件、100K屬性(不同的記號——這裡就是單詞)、0.76G非零輸入(譯者注:代表總詞數)。英文維基百科語料庫包括大約22.4億記號。
現在,我們已經準備好計算英文維基百科的LSA結果了:

>>> # 提取400LSI主題,使用預設的單程演算法
>>> lsi = gensim.models.lsimodel.LsiModel(corpus=mm, id2word=id2word, num_topics=400)

>>> # 列印對前10個主題貢獻最多的單詞(無論積極與消極)
>>> lsi.print_topics(10)
topic #0(332.762): 0.425*"utc" + 0.299*"talk" + 0.293*"page" + 0.226*"article" + 0.224*"delete" + 0.216*"discussion" + 0.205*"deletion" + 0.198*"should" + 0.146*"debate" + 0.132*"be"
topic #1(201.852): 0.282*"link" + 0.209*"he" + 0.145*"com" + 0.139*"his" + -0.137*"page" + -0.118*"delete" + 0.114*"blacklist" + -0.108*"deletion" + -0.105*"discussion" + 0.100*"diff"
topic #2(191.991): -0.565*"link" + -0.241*"com" + -0.238*"blacklist" + -0.202*"diff" + -0.193*"additions" + -0.182*"users" + -0.158*"coibot" + -0.136*"user" + 0.133*"he" + -0.130*"resolves"
topic #3(141.284): -0.476*"image" + -0.255*"copyright" + -0.245*"fair" + -0.225*"use" + -0.173*"album" + -0.163*"cover" + -0.155*"resolution" + -0.141*"licensing" + 0.137*"he" + -0.121*"copies"
topic #4(130.909): 0.264*"population" + 0.246*"age" + 0.243*"median" + 0.213*"income" + 0.195*"census" + -0.189*"he" + 0.184*"households" + 0.175*"were" + 0.167*"females" + 0.166*"males"
topic #5(120.397): 0.304*"diff" + 0.278*"utc" + 0.213*"you" + -0.171*"additions" + 0.165*"talk" + -0.159*"image" + 0.159*"undo" + 0.155*"www" + -0.152*"page" + 0.148*"contribs"
topic #6(115.414): -0.362*"diff" + -0.203*"www" + 0.197*"you" + -0.180*"undo" + -0.180*"kategori" + 0.164*"users" + 0.157*"additions" + -0.150*"contribs" + -0.139*"he" + -0.136*"image"
topic #7(111.440): 0.429*"kategori" + 0.276*"categoria" + 0.251*"category" + 0.207*"kategorija" + 0.198*"kategorie" + -0.188*"diff" + 0.163*"категория" + 0.153*"categoría" + 0.139*"kategoria" + 0.133*"categorie"
topic #8(109.907): 0.385*"album" + 0.224*"song" + 0.209*"chart" + 0.204*"band" + 0.169*"released" + 0.151*"music" + 0.142*"diff" + 0.141*"vocals" + 0.138*"she" + 0.132*"guitar"
topic #9(102.599): -0.237*"league" + -0.214*"he" + -0.180*"season" + -0.174*"football" + -0.166*"team" + 0.159*"station" + -0.137*"played" + -0.131*"cup" + 0.131*"she" + -0.128*"utc"

建立這個LSI模型在我的筆記本上消耗了4小時9分鐘[1]。大約16,000個文件每分鐘,包括所有的I/O。

注:如果你需要更快得到結果,參見《分散式計算》教程。Gensim中的BLAS庫顯然利用了多核,以便相同的資料可以在一臺多核機器上免費地更快處理,而且不用任何分散式安裝。

我們看到總處理時間主要是用於預處理階段總原始維基百科XML中提取TF-IDF語料庫,消耗了9小時。
gensim中使用的演算法對於每個輸入只需瀏覽一遍,因此適合用於文件以不重複流的形式輸入及儲存/迭代多次語料庫消費太大的情形。

隱含狄利克雷分配

正如上面的潛在語義分析,首先載入迭代器與字典。

>>> import logging, gensim, bz2
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

>>> # load id->word mapping (the dictionary), one of the results of step 2 above
>>> id2word = gensim.corpora.Dictionary.load_from_text('wiki_en_wordids.txt')
>>> # load corpus iterator
>>> mm = gensim.corpora.MmCorpus('wiki_en_tfidf.mm')
>>> # mm = gensim.corpora.MmCorpus(bz2.BZ2File('wiki_en_tfidf.mm.bz2')) # use this if you compressed the TFIDF output

>>> print(mm)
MmCorpus(3931787 documents, 100000 features, 756379027 non-zero entries)

我們將會執行線上LDA(Hoffman等[3]),這個演算法可以不斷利用新到的文件更新LDA模型。線上LDA可以與批量LDA進行對比,後者是一次處理整個語料庫,若要更新只能再次處理整個語料庫。不同點是,給定一個相對穩定的文件流(沒有太多主題偏移),線上更新只需遍歷較小的文件塊(子語料庫)本身是相當不錯的,因此模型收斂更快( The difference is that given a reasonably stationary document stream (not much topic drift), the online updates over the smaller chunks (subcorpora) are pretty good in themselves, so that the model estimation converges faster)。結果是,我們可能只需要處理一遍全部語料庫:如果語料庫有3M文章,我們在讀入10K文章後更新一次,這意味著在一次遍歷中我們需要更新300次,很可能得到一個比較準確的主題估計:

>>> # 提取100維LDA主題,使用一次遍歷,每1萬文件更新一次
>>> lda = gensim.models.ldamodel.LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)
using serial LDA version on this node
running online LDA training, 100 topics, 1 passes over the supplied corpus of 3931787 documents, updating model once every 10000 documents
...

不像LSA,來自LDA的主題更容易解釋:

>>> # print the most contributing words for 20 randomly selected topics
>>> lda.print_topics(20)
topic #0: 0.009*river + 0.008*lake + 0.006*island + 0.005*mountain + 0.004*area + 0.004*park + 0.004*antarctic + 0.004*south + 0.004*mountains + 0.004*dam
topic #1: 0.026*relay + 0.026*athletics + 0.025*metres + 0.023*freestyle + 0.022*hurdles + 0.020*ret + 0.017*divisão + 0.017*athletes + 0.016*bundesliga + 0.014*medals
topic #2: 0.002*were + 0.002*he + 0.002*court + 0.002*his + 0.002*had + 0.002*law + 0.002*government + 0.002*police + 0.002*patrolling + 0.002*their
topic #3: 0.040*courcelles + 0.035*centimeters + 0.023*mattythewhite + 0.021*wine + 0.019*stamps + 0.018*oko + 0.017*perennial + 0.014*stubs + 0.012*ovate + 0.011*greyish
topic #4: 0.039*al + 0.029*sysop + 0.019*iran + 0.015*pakistan + 0.014*ali + 0.013*arab + 0.010*islamic + 0.010*arabic + 0.010*saudi + 0.010*muhammad
topic #5: 0.020*copyrighted + 0.020*northamerica + 0.014*uncopyrighted + 0.007*rihanna + 0.005*cloudz + 0.005*knowles + 0.004*gaga + 0.004*zombie + 0.004*wigan + 0.003*maccabi
topic #6: 0.061*israel + 0.056*israeli + 0.030*sockpuppet + 0.025*jerusalem + 0.025*tel + 0.023*aviv + 0.022*palestinian + 0.019*ifk + 0.016*palestine + 0.014*hebrew
topic #7: 0.015*melbourne + 0.014*rovers + 0.013*vfl + 0.012*australian + 0.012*wanderers + 0.011*afl + 0.008*dinamo + 0.008*queensland + 0.008*tracklist + 0.008*brisbane
topic #8: 0.011*film + 0.007*her + 0.007*she + 0.004*he + 0.004*series + 0.004*his + 0.004*episode + 0.003*films + 0.003*television + 0.003*best
topic #9: 0.019*wrestling + 0.013*château + 0.013*ligue + 0.012*discus + 0.012*estonian + 0.009*uci + 0.008*hockeyarchives + 0.008*wwe + 0.008*estonia + 0.007*reign
topic #10: 0.078*edits + 0.059*notability + 0.035*archived + 0.025*clearer + 0.022*speedy + 0.021*deleted + 0.016*hook + 0.015*checkuser + 0.014*ron + 0.011*nominator
topic #11: 0.013*admins + 0.009*acid + 0.009*molniya + 0.009*chemical + 0.007*ch + 0.007*chemistry + 0.007*compound + 0.007*anemone + 0.006*mg + 0.006*reaction
topic #12: 0.018*india + 0.013*indian + 0.010*tamil + 0.009*singh + 0.008*film + 0.008*temple + 0.006*kumar + 0.006*hindi + 0.006*delhi + 0.005*bengal
topic #13: 0.047*bwebs + 0.024*malta + 0.020*hobart + 0.019*basa + 0.019*columella + 0.019*huon + 0.018*tasmania + 0.016*popups + 0.014*tasmanian + 0.014*modèle
topic #14: 0.014*jewish + 0.011*rabbi + 0.008*bgwhite + 0.008*lebanese + 0.007*lebanon + 0.006*homs + 0.005*beirut + 0.004*jews + 0.004*hebrew + 0.004*caligari
topic #15: 0.025*german + 0.020*der + 0.017*von + 0.015*und + 0.014*berlin + 0.012*germany + 0.012*die + 0.010*des + 0.008*kategorie + 0.007*cross
topic #16: 0.003*can + 0.003*system + 0.003*power + 0.003*are + 0.003*energy + 0.002*data + 0.002*be + 0.002*used + 0.002*or + 0.002*using
topic #17: 0.049*indonesia + 0.042*indonesian + 0.031*malaysia + 0.024*singapore + 0.022*greek + 0.021*jakarta + 0.016*greece + 0.015*dord + 0.014*athens + 0.011*malaysian
topic #18: 0.031*stakes + 0.029*webs + 0.018*futsal + 0.014*whitish + 0.013*hyun + 0.012*thoroughbred + 0.012*dnf + 0.012*jockey + 0.011*medalists + 0.011*racehorse
topic #19: 0.119*oblast + 0.034*uploaded + 0.034*uploads + 0.033*nordland + 0.025*selsoviet + 0.023*raion + 0.022*krai + 0.018*okrug + 0.015*hålogaland + 0.015*russiae + 0.020*manga + 0.017*dragon + 0.012*theme + 0.011*dvd + 0.011*super + 0.011*hunter + 0.009*ash + 0.009*dream + 0.009*angel

建立該LDA模型在我的筆記本上消耗了6小時20分鐘。如果你需要更快些,考慮在計算機叢集上使用分散式LDA
請注意LDA和LSA執行中的兩個不同點:我要求LSA提取400主題,LDA只要100主題(因此速度的差異實際上可能更大)。第二,gensim中的LSA實現是真正的線上:如果輸入流的性質(nature)及時改變,LSA將會重定向以反映這些變化,只用一個相對較小的更新。相反地,LDA不是真正線上(雖然文章[3]的標題如此),因為後來更新對模型的影響逐漸變小。如果輸入的文件流有主題偏移,LDA將會感到困惑,並且調整自己以適應新狀況的速度會越來越慢。
一句話,多次使用LDA的更新功能時請小心。當事先知道整個語料庫並且沒有表現出主題偏移時,批量LDA的用法是可以的,並沒有影響。
要執行批量LDA(非線上),訓練LdaModel

>>> # extract 100 LDA topics, using 20 full passes, no online updates
>>> lda = gensim.models.ldamodel.LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=0, passes=20)

像往常一樣,一個訓練好的模型可以用來將新的文件、沒見過的文件(普通詞袋計數向量)轉化為LDA主題分佈:

>>> doc_lda = lda[doc_bow]

[1] (1, 2) 我的筆記本 = MacBook Pro, Intel Core i7 2.3GHz, 16GB DDR3 RAM, OS X with libVec.
[2] 這裡我們最關心的是效能問題,但是檢索出來LSA概念也十分有趣。我不是維基百科專家,沒有深入剖析過它,但是Brian Mingus針對這個結果進行了這樣的分析:

第四、五個主題清楚地表明匯入的大量關於城市、國家等的人口、經濟等方面的統計資料庫的影響。

第六個主題表示體育的影響,第七個是音樂。因此,前10概念顯然被維基百科的機器人和擴充模板主導,這提示我們LSA是一個非常強力的資料分析工具,但是不是萬金油(silver bullet)。通常,也是渣進滓出(garbage in, garbage out)。順帶一提,歡迎完善維基百科標記解析程式碼。^_^

[3] (1, 2) Hoffman, Blei, Bach. 2010. Online learning for Latent Dirichlet Allocation [pdf] [code]