Python中的LDA - 如何網格搜索最佳主題模型?

翻譯自該鏈接
LDA in Python – How to grid search best topic models?
Python中的LDA - 如何網格搜索最佳主題模型?
Python的Scikit Learn使用Latent Dirichlet分配(LDA),LSI和非負矩陣分解等算法爲主題建模提供了方便的界面。 在本教程中,您將學習如何構建最佳的LDA主題模型,並探索如何將輸出顯示爲有意義的結果。
1.Introduction
在上一個教程中,您瞭解瞭如何使用gensim使用LDA構建主題模型。但是,在本教程中,我將使用python是最受歡迎的機器學習庫 - scikit learn。

使用scikit learn,你有一個完全不同的界面,使用網格搜索和矢量化,你有很多選擇可以探索,以找到最佳模型並呈現結果。

在本教程中,您將學習:

如何清理和處理文本數據?
如何準備文本文檔用scikit構建主題模型學習?
如何使用LDA構建基本主題模型並理解params?
如何提取主題的關鍵字?
如何對最佳模型進行網格搜索和調優?
如何在每個文檔中獲得主要主題?
查看主題關鍵字分佈並將其可視化
如何預測新文本的主題?
根據主題分佈對文檔進行分組
如何根據討論的主題獲得最相似的文檔?
未來有很多令人興奮的事情。來吧!
2、Load the packages
本教程中使用的核心包是scikit-learn(sklearn)。

正則表達式re,gensim和spacy用於處理文本。 用於可視化的pyLDAvis和matplotlib以及以表格格式操作和查看數據的numpy和pandas。

讓我們導入它們。

# Run in terminal or command prompt
# python3 -m spacy download en

import numpy as np
import pandas as pd
import re, nltk, spacy, gensim

# Sklearn
from sklearn.decomposition import LatentDirichletAllocation, TruncatedSVD
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import GridSearchCV
from pprint import pprint

# Plotting tools
import pyLDAvis
import pyLDAvis.sklearn
import matplotlib.pyplot as plt
%matplotlib inline

3、Import Newsgroups Text Data
我將使用20-Newsgroups數據集。 此版本的數據集包含來自20個不同主題的大約11k個新聞組帖子。 這可以作爲newsgroups.json使用。

由於它是json格式,具有一致的結構,我使用pandas.read_json(),結果數據集有3列,如圖所示。

# Import Dataset
df = pd.read_json('https://raw.githubusercontent.com/selva86/datasets/master/newsgroups.json')
print(df.target_names.unique())
['rec.autos' 'comp.sys.mac.hardware' 'rec.motorcycles' 'misc.forsale'
 'comp.os.ms-windows.misc' 'alt.atheism' 'comp.graphics'
 'rec.sport.baseball' 'rec.sport.hockey' 'sci.electronics' 'sci.space'
 'talk.politics.misc' 'sci.med' 'talk.politics.mideast'
 'soc.religion.christian' 'comp.windows.x' 'comp.sys.ibm.pc.hardware'
 'talk.politics.guns' 'talk.religion.misc' 'sci.crypt']
df.head(15)

在這裏插入圖片描述
4、Remove emails and newline characters

您可以在文本中看到許多電子郵件,換行符和額外空格,這非常令人分心。 讓我們使用正則表達式擺脫它們。

# Convert to list
data = df.content.values.tolist()

# Remove Emails
data = [re.sub('\S*@\S*\s?', '', sent) for sent in data]

# Remove new line characters
data = [re.sub('\s+', ' ', sent) for sent in data]

# Remove distracting single quotes
data = [re.sub("\'", "", sent) for sent in data]

pprint(data[:1])
['From: (wheres my thing) Subject: WHAT car is this!? Nntp-Posting-Host: '
 'rac3.wam.umd.edu Organization: University of Maryland, College Park Lines: '
 '15 I was wondering if anyone out there could enlighten me on this car I saw '
 'the other day. It was a 2-door sports car, looked to be from the late 60s/ '
 'early 70s. It was called a Bricklin. The doors were really small. In '
 'addition, the front bumper was separate from the rest of the body. This is '
 'all I know. If anyone can tellme a model name, engine specs, years of '
 'production, where this car is made, history, or whatever info you have on '
 'this funky looking car, please e-mail. Thanks, - IL ---- brought to you by '
 'your neighborhood Lerxst ---- ']

5、Tokenize and Clean-up using gensim’s simple_preprocess()
現在句子看起來更好了,但是你想把每個句子標記成一個單詞列表,完全刪除標點符號和不必要的字符。

Gensim的simple_preprocess()非常適合這個。 另外我設置了deacc = True來刪除標點符號。

def sent_to_words(sentences):
    for sentence in sentences:
        yield(gensim.utils.simple_preprocess(str(sentence), deacc=True))  # deacc=True removes punctuations

data_words = list(sent_to_words(data))

print(data_words[:1])
[['from', 'wheres', 'my', 'thing', 'subject', 'what', 'car', 'is', 'this', 'nntp', 'posting', 'host', 'rac', 'wam', 'umd', 'edu', 'organization', 'university', 'of', 'maryland', 'college', 'park', 'lines', 'was', 'wondering', 'if', 'anyone', 'out', 'there', 'could', 'enlighten', 'me', 'on', 'this', 'car', 'saw', 'the', 'other', 'day', (..truncated..)]]

6、Lemmatization
詞形還原是一個將單詞轉換爲詞根的過程。

例如:‘學習’變成’學習’,‘會變成’相遇’,‘更好’和’最佳’變成’好’。

這樣做的好處是,我們可以減少字典中唯一單詞的總數。 因此,文檔 - 單詞矩陣中的列數(由下一步中的CountVectorizer創建)將以較小的列更密集。

您可以期待最終生成更好的主題。

def lemmatization(texts, allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV']):
    """https://spacy.io/api/annotation"""
    texts_out = []
    for sent in texts:
        doc = nlp(" ".join(sent)) 
        texts_out.append(" ".join([token.lemma_ if token.lemma_ not in ['-PRON-'] else '' for token in doc if token.pos_ in allowed_postags]))
    return texts_out

# Initialize spacy 'en' model, keeping only tagger component (for efficiency)
# Run in terminal: python3 -m spacy download en
nlp = spacy.load('en', disable=['parser', 'ner'])

# Do lemmatization keeping only Noun, Adj, Verb, Adverb
data_lemmatized = lemmatization(data_words, allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV'])

print(data_lemmatized[:2])
['where s  thing subject what car be nntp post host rac wam umd edu organization university maryland college park line be wonder anyone out there could enlighten car see other days be door sport car look be late early be call bricklin door be really small addition front bumper be separate rest body be know anyone can tellme model name engine spec year production where car be make history whatev info have funky look car mail thank bring  neighborhood lerxst' (..truncated..)]

7、Create the Document-Word matrix
LDA主題模型算法需要文檔字矩陣作爲主要輸入。

您可以使用CountVectorizer創建一個。 在下面的代碼中,我將CountVectorizer配置爲考慮至少發生10次(min_df)的單詞,刪除內置英語停用詞,將所有單詞轉換爲小寫,並且單詞可以包含至少長度爲3的數字和字母 爲了成爲一個詞。

因此,要創建doc-word矩陣,首先需要使用所需的配置初始化CountVectorizer類,然後應用fit_transform來實際創建矩陣。

由於大多數單元格包含零,因此結果將採用稀疏矩陣的形式以節省內存。

如果要以2D數組格式實現它,請調用稀疏矩陣的todense()方法,就像在下一步中完成的那樣。

vectorizer = CountVectorizer(analyzer='word',       
                             min_df=10,                        # minimum reqd occurences of a word 
                             stop_words='english',             # remove stop words
                             lowercase=True,                   # convert all words to lowercase
                             token_pattern='[a-zA-Z0-9]{3,}',  # num chars > 3
                             # max_features=50000,             # max number of uniq words
                            )

data_vectorized = vectorizer.fit_transform(data_lemmatized)

8、Check the Sparsicity
稀疏性只不過是文檔 - 字矩陣中非零數據點的百分比,即data_vectorized。

由於此矩陣中的大多數單元格將爲零,因此我有興趣知道單元格的百分比包含非零值。

# Materialize the sparse data
data_dense = data_vectorized.todense()

# Compute Sparsicity = Percentage of Non-Zero cells
print("Sparsicity: ", ((data_dense > 0).sum()/data_dense.size)*100, "%")
Sparsicity:  0.775887569365 %

9、Build LDA model with sklearn
一切都準備建立潛在Dirichlet分配(LDA)模型。 讓我們初始化一個並調用fit_transform()來構建LDA模型。

對於此示例,我已根據有關數據集的先驗知識將n_topics設置爲20。 稍後我們將使用網格搜索找到最佳數字。

# Build LDA Model
lda_model = LatentDirichletAllocation(n_topics=20,               # Number of topics
                                      max_iter=10,               # Max learning iterations
                                      learning_method='online',   
                                      random_state=100,          # Random state
                                      batch_size=128,            # n docs in each learning iter
                                      evaluate_every = -1,       # compute perplexity every n iters, default: Don't
                                      n_jobs = -1,               # Use all available CPUs
                                     )
lda_output = lda_model.fit_transform(data_vectorized)

print(lda_model)  # Model attributes
LatentDirichletAllocation(batch_size=128, doc_topic_prior=None,
             evaluate_every=-1, learning_decay=0.7,
             learning_method='online', learning_offset=10.0,
             max_doc_update_iter=100, max_iter=10, mean_change_tol=0.001,
             n_components=10, n_jobs=-1, n_topics=20, perp_tol=0.1,
             random_state=100, topic_word_prior=None,
             total_samples=1000000.0, verbose=0)

10、Diagnose model performance with perplexity and log-likelihood
具有較高對數似然性和較低困惑度的模型(exp(-1。*每個單詞的對數似然))被認爲是好的。 我們來檢查一下我們的模型。

# Log Likelyhood: Higher the better
print("Log Likelihood: ", lda_model.score(data_vectorized))

# Perplexity: Lower the better. Perplexity = exp(-1. * log-likelihood per word)
print("Perplexity: ", lda_model.perplexity(data_vectorized))

# See model parameters
pprint(lda_model.get_params())
Log Likelihood:  -9965645.21463
Perplexity:  2061.88393838
{'batch_size': 128,
 'doc_topic_prior': None,
 'evaluate_every': -1,
 'learning_decay': 0.7,
 'learning_method': 'online',
 'learning_offset': 10.0,
 'max_doc_update_iter': 100,
 'max_iter': 10,
 'mean_change_tol': 0.001,
 'n_components': 10,
 'n_jobs': -1,
 'n_topics': 20,
 'perp_tol': 0.1,
 'random_state': 100,
 'topic_word_prior': None,
 'total_samples': 1000000.0,
 'verbose': 0}

另一方面,困惑可能不是評估主題模型的最佳方法,因爲它不考慮單詞之間的上下文和語義關聯。 這可以使用主題一致性度量來捕獲,我在前面提到的gensim教程中描述了這個例子。
11、How to GridSearch the best LDA model?
LDA模型最重要的調整參數是n_components(主題數)。 另外,我也將搜索learning_decay(它控制學習率)。

除此之外,其他可能的搜索參數可能是learning_offset(低於早期迭代。應該> 1)和max_iter。 如果您有足夠的計算資源,這些可能值得嘗試。

請注意,網格搜索爲param_grid dict中的所有可能的param值組合構建了多個LDA模型。 因此,這個過程會消耗大量的時間和資源。

# Define Search Param
search_params = {'n_components': [10, 15, 20, 25, 30], 'learning_decay': [.5, .7, .9]}

# Init the Model
lda = LatentDirichletAllocation()

# Init Grid Search Class
model = GridSearchCV(lda, param_grid=search_params)

# Do the Grid Search
model.fit(data_vectorized)
GridSearchCV(cv=None, error_score='raise',
       estimator=LatentDirichletAllocation(batch_size=128, doc_topic_prior=None,
             evaluate_every=-1, learning_decay=0.7, learning_method=None,
             learning_offset=10.0, max_doc_update_iter=100, max_iter=10,
             mean_change_tol=0.001, n_components=10, n_jobs=1,
             n_topics=None, perp_tol=0.1, random_state=None,
             topic_word_prior=None, total_samples=1000000.0, verbose=0),
       fit_params=None, iid=True, n_jobs=1,
       param_grid={'n_topics': [10, 15, 20, 25, 30], 'learning_decay': [0.5, 0.7, 0.9]},
       pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
       scoring=None, verbose=0)

12、How to see the best topic model and its parameters?

# Best Model
best_lda_model = model.best_estimator_

# Model Parameters
print("Best Model's Params: ", model.best_params_)

# Log Likelihood Score
print("Best Log Likelihood Score: ", model.best_score_)

# Perplexity
print("Model Perplexity: ", best_lda_model.perplexity(data_vectorized))
Best Model's Params:  {'learning_decay': 0.9, 'n_topics': 10}
Best Log Likelyhood Score:  -3417650.82946
Model Perplexity:  2028.79038336

13、Compare LDA Model Performance Scores
根據num_topics繪製對數似然得分,清楚地顯示主題數= 10有更好的分數。 而learning_decay爲0.7優於0.5和0.9。

這讓我想到,即使我們知道數據集有20個不同的主題,一些主題可以共享共同的關鍵字。 例如,‘alt.atheism’和’soc.religion.christian’可以有很多常用詞。 與’rec.motorcycles’和’rec.autos’,'comp.sys.ibm.pc.hardware’和’comp.sys.mac.hardware’相同,你就明白了。

爲了進一步調整這一點,您可以對10到15之間的主題數進行更精細的網格搜索。但是我現在要跳過它。

因此,最重要的是,對於此數據集,不同主題(甚至10個主題)的較低最佳數量可能是合理的。 我還不知道。 但是LDA這樣說。 讓我們來看看。

# Get Log Likelyhoods from Grid Search Output
n_topics = [10, 15, 20, 25, 30]
log_likelyhoods_5 = [round(gscore.mean_validation_score) for gscore in model.grid_scores_ if gscore.parameters['learning_decay']==0.5]
log_likelyhoods_7 = [round(gscore.mean_validation_score) for gscore in model.grid_scores_ if gscore.parameters['learning_decay']==0.7]
log_likelyhoods_9 = [round(gscore.mean_validation_score) for gscore in model.grid_scores_ if gscore.parameters['learning_decay']==0.9]

# Show graph
plt.figure(figsize=(12, 8))
plt.plot(n_topics, log_likelyhoods_5, label='0.5')
plt.plot(n_topics, log_likelyhoods_7, label='0.7')
plt.plot(n_topics, log_likelyhoods_9, label='0.9')
plt.title("Choosing Optimal LDA Model")
plt.xlabel("Num Topics")
plt.ylabel("Log Likelyhood Scores")
plt.legend(title='Learning decay', loc='best')
plt.show()

在這裏插入圖片描述
14、How to see the dominant topic in each document?
要將文檔歸類爲屬於特定主題,邏輯方法是查看哪個主題對該文檔的貢獻最大並將其分配。

在下表中,我已經在文檔中列出了所有主要主題,並在其自己的專欄中分配了最主要的主題。

# Create Document - Topic Matrix
lda_output = best_lda_model.transform(data_vectorized)

# column names
topicnames = ["Topic" + str(i) for i in range(best_lda_model.n_topics)]

# index names
docnames = ["Doc" + str(i) for i in range(len(data))]

# Make the pandas dataframe
df_document_topic = pd.DataFrame(np.round(lda_output, 2), columns=topicnames, index=docnames)

# Get dominant topic for each document
dominant_topic = np.argmax(df_document_topic.values, axis=1)
df_document_topic['dominant_topic'] = dominant_topic

# Styling
def color_green(val):
    color = 'green' if val > .1 else 'black'
    return 'color: {col}'.format(col=color)

def make_bold(val):
    weight = 700 if val > .1 else 400
    return 'font-weight: {weight}'.format(weight=weight)

# Apply Style
df_document_topics = df_document_topic.head(15).style.applymap(color_green).applymap(make_bold)
df_document_topics

在這裏插入圖片描述
15、Review topics distribution across documents

df_topic_distribution = df_document_topic['dominant_topic'].value_counts().reset_index(name="Num Documents")
df_topic_distribution.columns = ['Topic Num', 'Num Documents']
df_topic_distribution

在這裏插入圖片描述
16、How to visualize the LDA model with pyLDAvis?
pyLDAvis提供了最佳的可視化,可以查看主題 - 關鍵字分佈。

一個好的主題模型將爲每個主題提供不重疊,相當大的blob。 這似乎就是這種情況。 所以,我們很好。

pyLDAvis.enable_notebook()
panel = pyLDAvis.sklearn.prepare(best_lda_model,data_vectorized,vectorizer,mds='tsne')
panel

在這裏插入圖片描述
17、How to see the Topic’s keywords?
每個主題中每個關鍵字的權重包含在lda_model.components_中作爲2d數組。 可以使用get_feature_names()從vectorizer對象獲取關鍵字本身的名稱。

讓我們使用此信息爲每個主題中的所有關鍵字構建權重矩陣。

# Topic-Keyword Matrix
df_topic_keywords = pd.DataFrame(best_lda_model.components_)

# Assign Column and Index
df_topic_keywords.columns = vectorizer.get_feature_names()
df_topic_keywords.index = topicnames

# View
df_topic_keywords.head()

在這裏插入圖片描述
18、Get the top 15 keywords each topic
從上面的輸出中,我想看到代表該主題的前15個關鍵字。

下面定義的show_topics()創建了它。

# Show top n keywords for each topic
def show_topics(vectorizer=vectorizer, lda_model=lda_model, n_words=20):
    keywords = np.array(vectorizer.get_feature_names())
    topic_keywords = []
    for topic_weights in lda_model.components_:
        top_keyword_locs = (-topic_weights).argsort()[:n_words]
        topic_keywords.append(keywords.take(top_keyword_locs))
    return topic_keywords

topic_keywords = show_topics(vectorizer=vectorizer, lda_model=best_lda_model, n_words=15)        

# Topic - Keywords Dataframe
df_topic_keywords = pd.DataFrame(topic_keywords)
df_topic_keywords.columns = ['Word '+str(i) for i in range(df_topic_keywords.shape[1])]
df_topic_keywords.index = ['Topic '+str(i) for i in range(df_topic_keywords.shape[0])]
df_topic_keywords

在這裏插入圖片描述
19、How to predict the topics for a new piece of text?
假設您已經構建了主題模型,則需要在預測主題之前通過相同的轉換例程來獲取文本。

對於我們的情況,轉換的順序是:

sent_to_words() - > lemmatization() - > vectorizer.transform() - > best_lda_model.transform()

您需要以相同的順序應用這些轉換。 因此,爲了簡化它,讓我們將這些步驟組合成一個predict_topic()函數。

# Define function to predict topic for a given text document.
nlp = spacy.load('en', disable=['parser', 'ner'])

def predict_topic(text, nlp=nlp):
    global sent_to_words
    global lemmatization

    # Step 1: Clean with simple_preprocess
    mytext_2 = list(sent_to_words(text))

    # Step 2: Lemmatize
    mytext_3 = lemmatization(mytext_2, allowed_postags=['NOUN', 'ADJ', 'VERB', 'ADV'])

    # Step 3: Vectorize transform
    mytext_4 = vectorizer.transform(mytext_3)

    # Step 4: LDA Transform
    topic_probability_scores = best_lda_model.transform(mytext_4)
    topic = df_topic_keywords.iloc[np.argmax(topic_probability_scores), :].values.tolist()
    return topic, topic_probability_scores

# Predict the topic
mytext = ["Some text about christianity and bible"]
topic, prob_scores = predict_topic(text = mytext)
print(topic)
['say', 'god', 'people', 'write', 'think', 'know', 'believe', 'christian', 'make', 'subject', 'line', 'good', 'just', 'organization', 'thing']

mytext已被分配到具有宗教和基督教相關關鍵詞的主題,這是非常有意義和有意義的。
20、How to cluster documents that share similar topics and plot?
您可以在document-topic probabilioty矩陣上使用k-means聚類,這只是lda_output對象。 由於最佳模型有15個簇,我在KMeans()中設置了n_clusters = 15。

或者,您可以避免使用k-means,而是將羣集指定爲具有最高概率分數的主題列編號。

我們現在有了集羣號。 但我們還需要X和Y列來繪製圖。

對於X和Y,您可以在lda_output對象上使用SVD,其中n_components爲2. SVD確保這兩列從前兩個組件中的lda_output中捕獲最大可能的信息量。

# Construct the k-means clusters
from sklearn.cluster import KMeans
clusters = KMeans(n_clusters=15, random_state=100).fit_predict(lda_output)

# Build the Singular Value Decomposition(SVD) model
svd_model = TruncatedSVD(n_components=2)  # 2 components
lda_output_svd = svd_model.fit_transform(lda_output)

# X and Y axes of the plot using SVD decomposition
x = lda_output_svd[:, 0]
y = lda_output_svd[:, 1]

# Weights for the 15 columns of lda_output, for each component
print("Component's weights: \n", np.round(svd_model.components_, 2))

# Percentage of total information in 'lda_output' explained by the two components
print("Perc of Variance Explained: \n", np.round(svd_model.explained_variance_ratio_, 2))
Component's weights: 
 [[ 0.08  0.23  0.24  0.14  0.2   0.85  0.09  0.19  0.07  0.2 ]
 [ 0.02 -0.1   0.9   0.16  0.16 -0.32 -0.01 -0.01  0.13  0.09]]
Perc of Variance Explained: 
 [ 0.09  0.21]

我們有每個文檔的X,Y和簇號。

讓我們沿着兩個SVD分解的組件繪製文檔。 點的顏色表示簇編號(在本例中)或主題編號。

# Plot
plt.figure(figsize=(12, 12))
plt.scatter(x, y, c=clusters)
plt.xlabel('Component 2')
plt.xlabel('Component 1')
plt.title("Segregation of Topic Clusters", )

在這裏插入圖片描述
21、How to get similar documents for any given piece of text?
一旦知道給定文檔的主題概率(使用predict_topic()),用所有其他文檔的概率分數計算歐氏距離。

最相似的文件是距離最小的文件。

from sklearn.metrics.pairwise import euclidean_distances

nlp = spacy.load('en', disable=['parser', 'ner'])

def similar_documents(text, doc_topic_probs, documents = data, nlp=nlp, top_n=5, verbose=False):
    topic, x  = predict_topic(text)
    dists = euclidean_distances(x.reshape(1, -1), doc_topic_probs)[0]
    doc_ids = np.argsort(dists)[:top_n]
    if verbose:        
        print("Topic KeyWords: ", topic)
        print("Topic Prob Scores of text: ", np.round(x, 1))
        print("Most Similar Doc's Probs:  ", np.round(doc_topic_probs[doc_ids], 1))
    return doc_ids, np.take(documents, doc_ids)
# Get similar documents
mytext = ["Some text about christianity and bible"]
doc_ids, docs = similar_documents(text=mytext, doc_topic_probs=lda_output, documents = data, top_n=1, verbose=True)
print('\n', docs[0][:500])
Topic KeyWords:  ['say', 'god', 'people', 'write', 'think', 'know', 'believe', 'christian', 'make', 'subject', 'line', 'good', 'just', 'organization', 'thing']
Topic Prob Scores of text:  [[ 0.   0.   0.8  0.   0.   0.   0.   0.   0.   0. ]]
Most Similar Doc's Probs:   [[ 0.   0.   0.8  0.   0.   0.   0.1  0.   0.   0. ]]

 From: Subject: about Eliz C Prophet Lines: 21 Rob Butera asks about a book called THE LOST YEARS OF JESUS, by Elizabeth Clare Prophet. I do not know the book. However, Miss Prophet is the leader of a group (The Church Universal and Triumphant) derived from the I AM group founded by a Mr. Ballard who began his mission in the 1930s (I am writing this from memory and may not have all the details straight -- for an old account, check your library for a book by Marcus Bach) after an eighteenth-centu

22、Conclusion
我們在這篇文章中介紹了一些尖端的主題建模方法。 如果你成功完成了這項工作,那就做得很好。 對於那些關注時間,內存消耗和構建主題模型的各種主題的人,請查看LDA的gensim教程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章