用scikit-learn學習LDA主題模型

劉建平Pinard發表於2017-05-26

    在LDA模型原理篇我們總結了LDA主題模型的原理,這裡我們就從應用的角度來使用scikit-learn來學習LDA主題模型。除了scikit-learn,  還有spark MLlib和gensim庫也有LDA主題模型的類庫,使用的原理基本類似,本文關注於scikit-learn中LDA主題模型的使用。

1. scikit-learn LDA主題模型概述

    在scikit-learn中,LDA主題模型的類在sklearn.decomposition.LatentDirichletAllocation包中,其演算法實現主要基於原理篇裡講的變分推斷EM演算法,而沒有使用基於Gibbs取樣的MCMC演算法實現。

    而具體到變分推斷EM演算法,scikit-learn除了我們原理篇裡講到的標準的變分推斷EM演算法外,還實現了另一種線上變分推斷EM演算法,它在原理篇裡的變分推斷EM演算法的基礎上,為了避免文件內容太多太大而超過記憶體大小,而提供了分步訓練(partial_fit函式),即一次訓練一小批樣本文件,逐步更新模型,最終得到所有文件LDA模型的方法。這個改進演算法我們沒有講,具體論文在這:“Online Learning for Latent Dirichlet Allocation” 。

    下面我們來看看sklearn.decomposition.LatentDirichletAllocation類庫的主要引數。

2. scikit-learn LDA主題模型主要引數和方法

    我們來看看LatentDirichletAllocation類的主要輸入引數:

    1) n_topics: 即我們的隱含主題數$K$,需要調參。$K$的大小取決於我們對主題劃分的需求,比如我們只需要類似區分是動物,植物,還是非生物這樣的粗粒度需求,那麼$K$值可以取的很小,個位數即可。如果我們的目標是類似區分不同的動物以及不同的植物,不同的非生物這樣的細粒度需求,則$K$值需要取的很大,比如上千上萬。此時要求我們的訓練文件數量要非常的多。

    2) doc_topic_prior:即我們的文件主題先驗Dirichlet分佈$\theta_d$的引數$\alpha$。一般如果我們沒有主題分佈的先驗知識,可以使用預設值$1/K$。

    3) topic_word_prior:即我們的主題詞先驗Dirichlet分佈$\beta_k$的引數$\eta$。一般如果我們沒有主題分佈的先驗知識,可以使用預設值$1/K$。

    4) learning_method: 即LDA的求解演算法。有 ‘batch’ 和 ‘online’兩種選擇。 ‘batch’即我們在原理篇講的變分推斷EM演算法,而"online"即線上變分推斷EM演算法,在"batch"的基礎上引入了分步訓練,將訓練樣本分批,逐步一批批的用樣本更新主題詞分佈的演算法。預設是"online"。選擇了‘online’則我們可以在訓練時使用partial_fit函式分佈訓練。不過在scikit-learn 0.20版本中預設演算法會改回到"batch"。建議樣本量不大隻是用來學習的話用"batch"比較好,這樣可以少很多引數要調。而樣本太多太大的話,"online"則是首先了。

    5)learning_decay:僅僅在演算法使用"online"時有意義,取值最好在(0.5, 1.0],以保證"online"演算法漸進的收斂。主要控制"online"演算法的學習率,預設是0.7。一般不用修改這個引數。

    6)learning_offset:僅僅在演算法使用"online"時有意義,取值要大於1。用來減小前面訓練樣本批次對最終模型的影響。

    7) max_iter :EM演算法的最大迭代次數。

    8)total_samples:僅僅在演算法使用"online"時有意義, 即分步訓練時每一批文件樣本的數量。在使用partial_fit函式時需要。

    9)batch_size: 僅僅在演算法使用"online"時有意義, 即每次EM演算法迭代時使用的文件樣本的數量。

    10)mean_change_tol :即E步更新變分引數的閾值,所有變分引數更新小於閾值則E步結束,轉入M步。一般不用修改預設值。

    11) max_doc_update_iter: 即E步更新變分引數的最大迭代次數,如果E步迭代次數達到閾值,則轉入M步。

    從上面可以看出,如果learning_method使用"batch"演算法,則需要注意的引數較少,則如果使用"online",則需要注意"learning_decay", "learning_offset",“total_samples”和“batch_size”等引數。無論是"batch"還是"online", n_topics($K$), doc_topic_prior($\alpha$), topic_word_prior($\eta$)都要注意。如果沒有先驗知識,則主要關注與主題數$K$。可以說,主題數$K$是LDA主題模型最重要的超引數。

3. scikit-learn LDA中文主題模型例項

    下面我們給一個LDA中文主題模型的簡單例項,從分詞一直到LDA主題模型。

    完整程式碼參見我的github: https://github.com/ljpzzz/machinelearning/blob/master/natural-language-processing/lda.ipynb

    我們的有下面三段文件語料,分別放在了nlp_test0.txt, nlp_test2.txt和 nlp_test4.txt:

    沙瑞金讚歎易學習的胸懷,是金山的百姓有福,可是這件事對李達康的觸動很大。易學習又回憶起他們三人分開的前一晚,大家一起喝酒話別,易學習被降職到道口縣當縣長,王大路下海經商,李達康連連賠禮道歉,覺得對不起大家,他最對不起的是王大路,就和易學習一起給王大路湊了5萬塊錢,王大路自己東挪西撮了5萬塊,開始下海經商。沒想到後來王大路竟然做得風生水起。沙瑞金覺得他們三人,在困難時期還能以沫相助,很不容易。

    沙瑞金向毛婭打聽他們家在京州的別墅,毛婭笑著說,王大路事業有成之後,要給歐陽菁和她公司的股權,她們沒有要,王大路就在京州帝豪園買了三套別墅,可是李達康和易學習都不要,這些房子都在王大路的名下,歐陽菁好像去住過,毛婭不想去,她覺得房子太大很浪費,自己家住得就很踏實。

    347年(永和三年)三月,桓溫兵至彭模(今四川彭山東南),留下參軍周楚、孫盛看守輜重,自己親率步兵直攻成都。同月,成漢將領李福襲擊彭模,結果被孫盛等人擊退;而桓溫三戰三勝,一直逼近成都。

    首先我們進行分詞,並把分詞結果分別存在nlp_test1.txt, nlp_test3.txt和 nlp_test5.txt:

# -*- coding: utf-8 -*-

import jieba
jieba.suggest_freq('沙瑞金', True)
jieba.suggest_freq('易學習', True)
jieba.suggest_freq('王大路', True)
jieba.suggest_freq('京州', True)
#第一個文件分詞#
with open('./nlp_test0.txt') as f:
    document = f.read()
    
    document_decode = document.decode('GBK')
    document_cut = jieba.cut(document_decode)
    #print  ' '.join(jieba_cut)
    result = ' '.join(document_cut)
    result = result.encode('utf-8')
    with open('./nlp_test1.txt', 'w') as f2:
        f2.write(result)
f.close()
f2.close()  

#第二個文件分詞#
with open('./nlp_test2.txt') as f:
    document2 = f.read()
    
    document2_decode = document2.decode('GBK')
    document2_cut = jieba.cut(document2_decode)
    #print  ' '.join(jieba_cut)
    result = ' '.join(document2_cut)
    result = result.encode('utf-8')
    with open('./nlp_test3.txt', 'w') as f2:
        f2.write(result)
f.close()
f2.close() 

#第三個文件分詞#
jieba.suggest_freq('桓溫', True)
with open('./nlp_test4.txt') as f:
    document3 = f.read()
    
    document3_decode = document3.decode('GBK')
    document3_cut = jieba.cut(document3_decode)
    #print  ' '.join(jieba_cut)
    result = ' '.join(document3_cut)
    result = result.encode('utf-8')
    with open('./nlp_test5.txt', 'w') as f3:
        f3.write(result)
f.close()
f3.close()  

    現在我們讀入分好詞的資料到記憶體備用,並列印分詞結果觀察:

with open('./nlp_test1.txt') as f3:
    res1 = f3.read()
print res1
with open('./nlp_test3.txt') as f4:
    res2 = f4.read()
print res2
with open('./nlp_test5.txt') as f5:
    res3 = f5.read()
print res3

    列印出的分詞結果如下:

    沙瑞金 讚歎 易學習 的 胸懷 , 是 金山 的 百姓 有福 , 可是 這件 事對 李達康 的 觸動 很大 。 易學習 又 回憶起 他們 三人 分開 的 前一晚 , 大家 一起 喝酒 話別 , 易學習 被 降職 到 道口 縣當 縣長 , 王大路 下海經商 , 李達康 連連 賠禮道歉 , 覺得 對不起 大家 , 他 最 對不起 的 是 王大路 , 就 和 易學習 一起 給 王大路 湊 了 5 萬塊 錢 , 王大路 自己 東挪西撮 了 5 萬塊 , 開始 下海經商 。 沒想到 後來 王大路 竟然 做 得 風生水 起 。 沙瑞金 覺得 他們 三人 , 在 困難 時期 還 能 以沫 相助 , 很 不 容易 。
    沙瑞金 向 毛婭 打聽 他們 家 在 京州 的 別墅 , 毛婭 笑 著 說 , 王大路 事業有成 之後 , 要 給 歐陽 菁 和 她 公司 的 股權 , 她們 沒有 要 , 王大路 就 在 京州 帝豪園 買 了 三套 別墅 , 可是 李達康 和 易學習 都 不要 , 這些 房子 都 在 王大路 的 名下 , 歐陽 菁 好像 去 住 過 , 毛婭 不想 去 , 她 覺得 房子 太大 很 浪費 , 自己 家住 得 就 很 踏實 。
    347 年 ( 永和 三年 ) 三月 , 桓溫 兵至 彭模 ( 今 四川 彭山 東南 ) , 留下 參軍 周楚 、 孫盛 看守 輜重 , 自己 親率 步兵 直攻 成都 。 同月 , 成漢 將領 李福 襲擊 彭模 , 結果 被 孫盛 等 人 擊退 ; 而 桓溫 三 戰三勝 , 一直 逼近 成都 。

    我們接著匯入停用詞表,這裡的程式碼和中文文字挖掘預處理流程總結中一樣,如果大家沒有1208個的中文停用詞表,可以到之前的這篇文章的連結裡去下載。

#從檔案匯入停用詞表
stpwrdpath = "stop_words.txt"
stpwrd_dic = open(stpwrdpath, 'rb')
stpwrd_content = stpwrd_dic.read()
#將停用詞表轉換為list  
stpwrdlst = stpwrd_content.splitlines()
stpwrd_dic.close()

    接著我們要把詞轉化為詞頻向量,注意由於LDA是基於詞頻統計的,因此一般不用TF-IDF來做文件特徵。程式碼如下:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
corpus = [res1,res2,res3]
cntVector = CountVectorizer(stop_words=stpwrdlst)
cntTf = cntVector.fit_transform(corpus)
print cntTf

    輸出即為所有文件中各個詞的詞頻向量。有了這個詞頻向量,我們就可以來做LDA主題模型了,由於我們只有三個文件,所以選擇主題數$K$=2。程式碼如下:

lda = LatentDirichletAllocation(n_topics=2,
                                learning_offset=50.,
                                random_state=0)
docres = lda.fit_transform(cntTf)

    通過fit_transform函式,我們就可以得到文件的主題模型分佈在docres中。而主題詞 分佈則在lda.components_中。我們將其列印出來:

print docres
print lda.components_

    文件主題的分佈如下:

[[ 0.00950072  0.99049928]
 [ 0.0168786   0.9831214 ]
 [ 0.98429257  0.01570743]]

    可見第一個和第二個文件較大概率屬於主題2,則第三個文件屬於主題1.

    主題和詞的分佈如下:

[[ 1.32738199  1.24830645  0.90453117  0.7416939   0.78379936  0.89659305
   1.26874773  1.23261029  0.82094727  0.87788498  0.94980757  1.21509469
   0.64793292  0.89061203  1.00779152  0.70321998  1.04526968  1.30907884
   0.81932312  0.67798129  0.93434765  1.2937011   1.170592    0.70423093
   0.93400364  0.75617108  0.69258778  0.76780266  1.17923311  0.88663943
   1.2244191   0.88397724  0.74734167  1.20690264  0.73649036  1.1374004
   0.69576496  0.8041923   0.83229086  0.8625258   0.88495323  0.8207144
   1.66806345  0.85542475  0.71686887  0.84556777  1.25124491  0.76510471
   0.84978448  1.21600212  1.66496509  0.84963486  1.24645499  1.72519498
   1.23308705  0.97983681  0.77222879  0.8339811   0.85949947  0.73931864
   1.33412296  0.91591144  1.6722457   0.98800604  1.26042063  1.09455497
   1.24696097  0.81048961  0.79308036  0.95030603  0.83259407  1.19681066
   1.18562629  0.80911991  1.19239034  0.81864393  1.24837997  0.72322227
   1.23471832  0.89962384  0.7307045   1.39429334  1.22255041  0.98600185
   0.77407283  0.74372971  0.71807656  0.75693778  0.83817087  1.33723701
   0.79249005  0.82589143  0.72502086  1.14726838  0.83487136  0.79650741
   0.72292882  0.81856129]
 [ 0.72740212  0.73371879  1.64230568  1.5961744   1.70396534  1.04072318
   0.71245387  0.77316486  1.59584637  1.15108883  1.15939659  0.76124093
   1.34750239  1.21659215  1.10029347  1.20616038  1.56146506  0.80602695
   2.05479544  1.18041584  1.14597993  0.76459826  0.8218473   1.2367587
   1.44906497  1.19538763  1.35241035  1.21501862  0.7460776   1.61967022
   0.77892814  1.14830281  1.14293716  0.74425664  1.18887759  0.79427197
   1.15820484  1.26045121  1.69001421  1.17798335  1.12624327  1.12397988
   0.83866079  1.2040445   1.24788376  1.63296361  0.80850841  1.19119425
   1.1318814   0.80423837  0.74137153  1.21226307  0.67200183  0.78283995
   0.75366187  1.5062978   1.27081319  1.2373463   2.99243195  1.21178667
   0.66714016  2.17440219  0.73626368  1.60196863  0.71547934  1.94575151
   0.73691176  2.02892667  1.3528508   1.0655887   1.1460755   4.17528123
   0.74939365  1.23685079  0.76431961  1.17922085  0.70112531  1.14761871
   0.80877956  1.12307426  1.21107782  1.64947394  0.74983027  2.03800612
   1.21378076  1.21213961  1.23397206  1.16994431  1.07224768  0.75292945
   1.10391419  1.26932908  1.26207274  0.70943937  1.1236972   1.24175001
   1.27929042  1.19130408]]

    在實際的應用中,我們需要對$K,\alpha,\eta$進行調參。如果是"online"演算法,則可能需要對"online"演算法的一些引數做調整。這裡只是給出了LDA主題模型從原始文件到實際LDA處理的過程。希望可以幫到大家。

 

 (歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com) 

相關文章