隱狄利克雷分布(Latent Dirichlet Allocation 或 LDA)的 Python 實現(xiàn)
作者:Enes Gokce
原文:Topic Modeling with NLP on Amazon Reviews Application of Latent Dirichlet Allocation (LDA) with Python
譯者:Ivy Lee
譯文于 2020-07-06 始發(fā)自【biendata】公眾號:《以隱含狄利克雷分布,實現(xiàn)對亞馬遜評論的主題建模》
主題建模(Topic modeling)是另一種流行的文本分析技術(shù),最終目標(biāo)是在評論中找到中心思想,并發(fā)現(xiàn)隱藏的主題。語料庫中的文檔都包含一個或多個主題。
主題建模有很多技術(shù),在這里,我將介紹 隱狄利克雷分布(Latent Dirichlet Allocation 或 LDA) 的結(jié)果,這是一種無監(jiān)督分類方法。當(dāng)文本量很大而又不知道從哪里開始時,可以應(yīng)用主題建模,然后查看出現(xiàn)的組別。LDA 是專門為文本數(shù)據(jù)設(shè)計的。
要使用主題建模技術(shù),你需要提供:
- 文檔-術(shù)語矩陣(document-term matrix)
- 你想要算法提取的主題數(shù)
在主題建模的過程中,我會創(chuàng)建不同的模型進行比較,最后選擇最有意義的主題模型。
如何使用隱狄利克雷分布(LDA)
本文不會介紹 LDA 的數(shù)學(xué)基礎(chǔ)知識,主要是討論的是如何解釋 LDA 主題模型的結(jié)果。
LDA 主題建模過程會創(chuàng)建很多不同的主題組,作為研究人員,我們需要決定輸出中的組數(shù),但是我們并不知道最好的組數(shù)是多少。因此,我們需要嘗試不同的組數(shù),檢驗并比較主題模型,確定哪個主題模型更有意義、最有意義,在模型中具有最明顯的區(qū)別,然后在所有主題組中選擇最有意義的組(模型)。
必須指出的是,LDA 的性質(zhì)是主觀的。在選擇最有意義的主題組時,不同的人可能會得出不同的結(jié)論。我們尋找的是最合理的主題組,不同背景、不同領(lǐng)域?qū)I(yè)知識的人可能會做出不同的選擇。
LDA 是一種無監(jiān)督聚類方法。提到無監(jiān)督聚類方法,就不得不提一下 K-Means 聚類——最著名的無監(jiān)督聚類方法之一。K-Means 聚類在很多情況下都非常實用且有用,已應(yīng)用于文本挖掘多年。與每個單詞只能屬于一個集群(硬聚類)的 K-Means 聚類相反,LDA 允許“模糊”成員資格(軟聚類)。軟聚類允許集群重疊,而在硬聚類中,集群是互斥的。也就是說,在 LDA 中,一個單詞可以屬于多個組,而在 K-Means 聚類中則不可能。在 LDA 中,這種折衷使查找單詞之間的相似性更加容易。然而,這種軟聚類會導(dǎo)致很難劃分組別,因為同一個單詞可以出現(xiàn)在不同的組中。后續(xù)分析中我們會體會到這種影響。
應(yīng)用了主題建模技術(shù)之后,研究人員的工作就是解釋結(jié)果,查看每個主題中單詞的混合是否有意義。如果沒有意義,則可以嘗試修改主題的數(shù)量、文檔-術(shù)語矩陣中的術(shù)語、模型參數(shù),甚至嘗試使用其他模型。
數(shù)據(jù)準(zhǔn)備
本文使用的數(shù)據(jù)簡介: 數(shù)據(jù)下載自 Kaggle,由 Stanford Network Analysis Project 上傳。原始數(shù)據(jù)來自于 J. McAuley 和 J. Leskovec 所做的研究“從業(yè)余愛好者到鑒賞家:通過網(wǎng)絡(luò)評論對用戶專業(yè)知識的發(fā)展進行建模”(2013)。該數(shù)據(jù)集由亞馬遜網(wǎng)站的美食評論構(gòu)成,包括 1999 年至 2012 年的全部 568,454 條評論。每條評論包括產(chǎn)品和用戶信息,評分以及純文本評價。
在本次研究中,我將重點關(guān)注對亞馬遜的“好評”。我對“好評”的定義是:具有 4 星或 5 星(最高 5 星)的評論。換句話說,如果某條評論為 4 星或 5 星,就屬于本次研究中的“好評”;1 2 3 星則被標(biāo)記為“差評”。
數(shù)據(jù)準(zhǔn)備至關(guān)重要,如果準(zhǔn)備數(shù)據(jù)出現(xiàn)失誤,就無法實施主題建模。不過在本次研究中,我們不會深入探討如何準(zhǔn)備數(shù)據(jù),因為這不是本次研究的重點。但是,你需要做好心理準(zhǔn)備,這一步如果出現(xiàn)問題,會花費一些時間。如果你在處理自己的數(shù)據(jù)集時,需要對應(yīng)調(diào)整本文提供的代碼,希望一切順利。
先檢查數(shù)據(jù)的列行數(shù):
df.shape
(392384, 19) —— 數(shù)據(jù)集有 392,384 條評論。
對于很多家用計算機(以及 Google Colab)來說,這樣的數(shù)據(jù)量都是難以處理的。因此,我會只使用其中 10,000 條評論。非常遺憾,如果我們無法使用超級計算機,就無法使用所有評論數(shù)據(jù)。
# 從數(shù)據(jù)集中獲取前 10000 條好評
df_good_reviews= df.loc[df.Good_reviews ==1][0:10000]
下一步是計數(shù)向量化器(Count Vectorizer):
# 創(chuàng)建文檔-術(shù)語矩陣
from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
data_cv = cv.fit_transform(df_good_reviews.Text)
data_cv.shape
Pickle 存儲數(shù)據(jù),為后續(xù)創(chuàng)建文檔-術(shù)語矩陣做準(zhǔn)備。
# Pickle it
import pickle
pickle.dump(cv, open("cv_stop.pkl", "wb"))
data_stop.to_pickle("dtm_stop.pkl") #saving the pickled data
data = pd.read_pickle('dtm_stop.pkl')
data
Pickle 數(shù)據(jù)后,我們得到一個寬格式的詞典:
現(xiàn)在到了創(chuàng)建術(shù)語-文檔矩陣的時候了,該術(shù)語-文檔矩陣數(shù)據(jù)將用于生成主題模型,所以對此次研究至關(guān)重要。
# 所需的輸入之一就是術(shù)語-文檔矩陣
tdm = data.transpose()
tdm.head()
# 通過從 df -> 稀疏矩陣 -> Gensim 語料庫,將術(shù)語-文檔矩陣轉(zhuǎn)換為新的 Gensim 格式
sparse_counts = scipy.sparse.csr_matrix(tdm)
corpus = matutils.Sparse2Corpus(sparse_counts)
# Gensim 還需要一個字典,表示所有術(shù)語及其在術(shù)語-文檔矩陣中的對應(yīng)位置
cv = pickle.load(open("cv_stop.pkl", "rb"))
id2word = dict((v, k) for k, v in cv.vocabulary_.items())
現(xiàn)在開始創(chuàng)建主題模型!
使用 LDA 構(gòu)建主題模型
構(gòu)建主題模型的方法有很多。根據(jù)特定條件篩選文本,可以獲得不同的主題組。在本次研究中,我們會使用以下內(nèi)容創(chuàng)建主題模型:
- 所有文本
- 僅包含文本中的所有名詞
- 文本中的所有名詞和形容詞
主題建模 - 嘗試 1(所有文本)
首先,先嘗試使用所有評論數(shù)據(jù),在此過程中不進行文本篩選。
lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=2, passes=10)
lda.print_topics()
發(fā)現(xiàn)了兩組主題:
[(0, ‘0.020”coffee” + 0.011”like” + 0.010”tea” + 0.010”flavor” + 0.009”good” + 0.009”taste” + 0.007”one” + 0.007”great” + 0.006”use” + 0.006”cup”’),
(1, ‘0.010”great” + 0.009”like” + 0.009”good” + 0.007”love” + 0.007”food” + 0.007”one” + 0.007”product” + 0.005”taste” + 0.005”get” + 0.005”amazon”’)]
如何處理這一結(jié)果? 此時,我們可以檢查這兩個主題組,確定它們是否是具有差異性的組。我們不是預(yù)期一個組中的所有單詞都必須相關(guān),而是查看主題組的整體趨勢。具體到以上兩組,很難看到明顯的差異。這是我的個人看法,如果你認(rèn)為它們確實是兩個不同的組,并且能夠證明其合理性,也可以放心使用這一結(jié)果。
注意,之前提到過 LDA 是一種軟聚類模型。在這里,我們看到“l(fā)ike”一詞包含在兩組中,這是正常現(xiàn)象,因為在軟聚類模型中,一個單詞可以同時出現(xiàn)在不同的組。
不需要強迫自己努力使上述模型有意義,我們可以繼續(xù),創(chuàng)建更多的主題模型。
與創(chuàng)建 2 組主題模型的操作類似,創(chuàng)建 3 組和 4 組。
# LDA for num_topics = 3
lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=3, passes=10)
lda.print_topics()
# LDA for num_topics = 4
lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=4, passes=10)
lda.print_topics()
所有結(jié)果可在表 1(下表)中看到。此時,我們應(yīng)該檢查對比,嘗試找到最有意義的組。通過查看表 1,可以說“兩組主題的模型”是最有意義的,第一組與飲料有關(guān),第二組與反應(yīng)有關(guān)。當(dāng)然,你完全可以得出與我不同的結(jié)論!現(xiàn)在,這些結(jié)果先保留在這里,不會再進一步調(diào)整,因為我們會再創(chuàng)建 6 個主題模型。得到所有結(jié)果后,我可以更仔細(xì)地檢查輸出。在生成進一步的主題模型之后,我們會重新考慮表 1。
主題建模 - 嘗試 2(僅名詞): 在此步驟中,通過 LDA 方法僅使用名詞創(chuàng)建主題。同樣,我們的目的是在評論中找到隱藏的模式。現(xiàn)在,只需要使用不同的條件進行篩選。
與上一步類似,我們將運行帶有 2、3、4 個主題組的 LDA。
import nltk
from nltk.corpus import stopwords
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
import string
from nltk import word_tokenize, pos_tag
創(chuàng)建一個從文本字符串中提取名詞的函數(shù):
from nltk import word_tokenize, pos_tag
def nouns(text):
'''給定一個文本字符串,對其進行分詞,只提取出其中的名詞'''
is_noun = lambda pos: pos[:2] == 'NN'
tokenized = word_tokenize(text)
all_nouns = [word for (word, pos) in pos_tag(tokenized) if is_noun(pos)]
return ' '.join(all_nouns)
# 如果此代碼不能運行,可能是由于頁面格式導(dǎo)致的縮進錯誤。
將定義好的 nouns 函數(shù)應(yīng)用于評論文本數(shù)據(jù):
data_nouns = pd.DataFrame(df_good_reviews.Text.apply(nouns))
data_nouns
僅使用名詞創(chuàng)建一個新的文檔-術(shù)語矩陣:
from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import CountVectorizer
# 重新添加其他停用詞,因為我們正在重新創(chuàng)建文檔-術(shù)語矩陣
add_stop_words = ['like', 'im', 'know', 'just', 'dont', 'thats', 'right', 'people','youre', 'got', 'gonna', 'time', 'think', 'yeah', 'said']
stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words)
# 重新創(chuàng)建僅包含名詞的文檔-術(shù)語矩陣
cvn = CountVectorizer(stop_words=stop_words)
data_cvn = cvn.fit_transform(data_nouns.Text)
data_dtmn = pd.DataFrame(data_cvn.toarray(), columns=cvn.get_feature_names())
data_dtmn.index = data_nouns.index
data_dtmn
創(chuàng)建 Gensim 語料庫:
corpusn=matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmn.transpose()))
# 創(chuàng)建詞匯字典
id2wordn = dict((v, k) for k, v in cvn.vocabulary_.items())
? 從 2 組主題開始
ldan = models.LdaModel(corpus=corpusn, num_topics=2, id2word=id2wordn, passes=10)
ldan.print_topics()
? 3 組主題的 LDA
ldan = models.LdaModel(corpus=corpusn, num_topics=3, id2word=id2wordn, passes=10)
ldan.print_topics()
? 4 組主題的 LDA
ldan = models.LdaModel(corpus=corpusn, num_topics=4, id2word=id2wordn, passes=10)
ldan.print_topics()
表 2 是僅使用名詞嘗試的 LDA 主題模型輸出。現(xiàn)在,我們需要再次檢查主題,嘗試找到具有不同主題組的模型。同樣,仍然不需要花費很多時間,因為我們還會生成更多的主題模型。
對我來說,其中具有三個小組的主題模型很有意義。它包含以下組:
- 寵物食品
- 餅干和零食
- 飲品
當(dāng)然,得出不同的結(jié)論也是完全正常的。
主題建模-嘗試 3(名詞和形容詞): 在此步驟中,僅使用名詞和形容詞通過 LDA 方法創(chuàng)建主題模型。
準(zhǔn)備數(shù)據(jù):
from nltk.corpus import stopwords
import nltk
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
import string
from nltk import word_tokenize, pos_tag
自定義獲取名詞或形容詞的函數(shù):
def nouns_adj(text):
'''給定一個文本字符串,對其進行分詞,獲取其中的名詞和形容詞'''
is_noun_adj = lambda pos: pos[:2] == 'NN' or pos[:2] == 'JJ'
tokenized = word_tokenize(text)
nouns_adj = [word for (word, pos) in pos_tag(tokenized) if is_noun_adj(pos)]
return ' '.join(nouns_adj)
# 如果此代碼不能運行,可能是由于頁面格式導(dǎo)致的縮進錯誤。
對評論數(shù)據(jù)使用 nouns_adj 進行篩選:
data_nouns_adj = pd.DataFrame(df_good_reviews.Text.apply(nouns_adj))
data_nouns_adj
如你所見,這里僅包含名詞和形容詞。現(xiàn)在,我們要求 LDA 使用篩選后的這一個數(shù)據(jù)集版本創(chuàng)建主題模型。
為 LDA 準(zhǔn)備數(shù)據(jù):
# 僅使用名詞和形容詞創(chuàng)建一個新的文檔-術(shù)語矩陣,同時使用 max_df 刪除常見單詞
cvna = CountVectorizer(max_df=.8) # max_df 用于刪除出現(xiàn)頻率過高的數(shù)據(jù)值,也稱為“特定語料庫的停用詞(corpus-specific stop words)”
# 例如,max_df=.8 表示會忽略文檔中出現(xiàn)頻率大于 80% 的詞。
data_cvna = cvna.fit_transform(data_nouns_adj.Text)
data_dtmna = pd.DataFrame(data_cvna.toarray(), columns=cvna.get_feature_names())
data_dtmna.index = data_nouns_adj.index
data_dtmna
創(chuàng)建 Gensim 語料庫:
corpusna=matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmna.transpose()))
# 創(chuàng)建詞匯字典
id2wordna = dict((v, k) for k, v in cvna.vocabulary_.items())
現(xiàn)在可以運行 LDA 了。
? 從 2 組主題開始
ldana = models.LdaModel(corpus=corpusna, num_topics=2, id2word=id2wordna, passes=10)
ldana.print_topics()
? 嘗試 3 組主題
ldana = models.LdaModel(corpus=corpusna, num_topics=3, id2word=id2wordna, passes=10)
ldana.print_topics()
? 嘗試 4 組主題
ldana = models.LdaModel(corpus=corpusna, num_topics=4, id2word=id2wordna, passes=10)
ldana.print_topics()
表 3 是僅使用名詞和形容詞的 LDA 主題模型輸出。我們再次檢查主題,確定是否具有有意義的主題組。
評估結(jié)果
現(xiàn)在到了最后階段,表 1、表 2 和表 3 的結(jié)果必須一起評估。我們一共創(chuàng)建了 9 個主題模型,問一下自己:“哪一組更有意義?”現(xiàn)在,需要集中精力仔細(xì)檢查各個主題。哪一組有意義?如果都沒有意義,我們需要返回到數(shù)據(jù)清理步驟,更改參數(shù)或使用其他篩選條件和模型,這是一個遞歸過程。
在 9 個主題模型中,對我來說最有意義的是:僅名詞具有 3 個主題組的模型(表 4)。我在這里看到三個不同的類別:(1)寵物食品、(2)餅干和零食、(3)飲品。同樣,找到其他更有意義的主題模型是完全可以的。
記住一點,該數(shù)據(jù)集僅包含食品評論。因此,看到這些組都與食品有關(guān)是很正常的。
找出最有意義的主題模型后,通過更多迭代,獲得微調(diào)模型。3 組主題(僅名詞)對我來說最有意義,所以我將微調(diào)這一主題模型,把迭代數(shù)從 10 調(diào)整到 80。
# Fine-tuned LDA with topics = 3
ldan = models.LdaModel(corpus=corpusn, num_topics=3, id2word=id2wordn, passes=80)
ldan.print_topics()
在上表中,我們看到與表 4 類似的組,只是順序不同。通過以上使用 LDA 方法進行主題分析的所有步驟,可以得出結(jié)論,亞馬遜評論的好評可分為三個主要主題:(1)飲品、(2)寵物食品、(3)餅干和零食。
最后需要注意的是,要了解哪個主題組更有意義,我們可能需要對應(yīng)領(lǐng)域的專業(yè)知識。作為一名研究人員,有責(zé)任證明我們對主題組的選擇是正確的。只要理由充足,我們可以將任何主題組確定為最終主題模型。
感謝閱讀!分析愉快!
特別感謝我的朋友 Bibor Szabo 在撰寫本文時提供的寶貴建議。
提示: 此次研究所使用的 Python 代碼,可以在我的 GitHub 文件夾 中查找。另外,這次的主題模型研究是另一個更大項目的一部分。如果你對前面的步驟感興趣,可以查看我之前的文章:數(shù)據(jù)清洗 和 情緒分析。