利⽤sklearn训练LDA主题模型及调参详解
writelines方法的参数可以是
加载语料库及预处理
本⽂选⽤的语料库为sklearn⾃带API的20newsgroups语料库,该语料库包含商业、科技、运动、航空航天等多领域新闻资料,很适合NLP的初学者进⾏使⽤。
sklearn_20newsgroups给出了⾮常详细的介绍。
预处理⽅⾯,直接调⽤了NLTK的接⼝进⾏⼩写化、分词、去除停⽤词、POS筛选及词⼲化。这⾥进⾏哪些操作完全根据实际需要和数据来定,⽐如我就经常放弃词⼲化或者放弃POS筛选(原因通常是结果不好==)…以下代码为加载20newsgroups数据及⽂本预处理部分代码。
# 1.加载数据
# 该语料库包含商业、科技、运动、航空航天等多领域新闻资料
from sklearn.datasets import fetch_20newsgroups
dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
data_samples = dataset.data[:2000]  # 截取需要的量,2000条
# print(data_samples)
# 2.⽂本预处理, 可选项
import nltk
import string
pus import stopwords
from nltk.stem.porter import PorterStemmer
# 每次访问数据需要添加数据⾄路径当中
def textPrecessing(text):
# ⼩写化
text = text.lower()
# 去除特殊标点
for c in string.punctuation:
text = place(c, ' ')
# 分词
wordLst = nltk.word_tokenize(text)
# 去除停⽤词
filtered = [w for w in wordLst if w not in stopwords.words('english')]
# 仅保留名词或特定POS
refiltered = nltk.pos_tag(filtered)
filtered = [w for w, pos in refiltered if pos.startswith('NN')]
# 词⼲化
ps = PorterStemmer()
filtered = [ps.stem(w) for w in filtered]
return " ".join(filtered)
# 该区域仅⾸次运⾏,进⾏⽂本预处理,第⼆次运⾏起注释掉
# docList = []
# for desc in data_samples:
#    docLst.append(textPrecessing(desc).encode('utf-8'))
# with open('D:/data/LDA/20newsgroups(2000).txt', 'a') as f:
#    for line in docLst:
#        f.writelines(str(line) +
#                      '\n')
# ==============================================================================
# 从第⼆次运⾏起,直接获取预处理过的docLst,前⾯load数据、预处理均注释掉
docList = []
with open('D:/data/LDA/20newsgroups(2000).txt', 'r') as f:
for line adlines():
if line != '':
docList.append(line.strip())
# ==============================================================================
CountVectorizer统计词频
LDA模型学习时的训练数据并不是⼀篇篇⽂本,⽽是Document-word matrix,它可以是array也可以是
稀疏矩阵,维数是n_samples*n_features,其中n_features为词(term)的个数。因此在训练LDA主题模型前,需要先利⽤CountVectorizer统计词频并保存,代码如下:
# 3.统计词频
from sklearn. import CountVectorizer
als import joblib  # 也可以选择pickle等保存模型,请随意
# 构建词汇统计向量并保存,仅运⾏⾸次 API: /stable/modules/generated/sklearn.CountVectorizer.html
tf_vectorizer = CountVectorizer(max_df=0.95, min_df=2,
max_features=1500,
stop_words='english')
tf = tf_vectorizer.fit_transform(docList)
joblib.dump(tf_vectorizer, 'D:/saved_model/vectorizer_sklearn/del')
# ==============================================================================
# #得到存储的tf_vectorizer,节省预处理时间
# tf_vectorizer = joblib.load('D:/saved_model/vectorizer_sklearn/del')
# tf = tf_vectorizer.fit_transform(docList)
# print(tf)
# ==============================================================================
CountVectorizer的API请⾃⾏参考sklearn,⽂中代码限定term出现次数必须⼤于2,最终保留前n_features=2500的term作为features。训练得到的tf_vectorizer 利⽤joblib保存到⽂件,第⼆次起可以直接从⽂件中load进来避免重复计算。该步骤得到的tf矩阵为⼀个“⽂章-词语”稀疏矩阵,可以通过_feature_names()得到每⼀维feature对应的term。
LDA主题模型训练
终于到了最关键的LDA主题模型训练阶段。虽说此阶段最关键,但如果数据质量⾼,如果前⾯的步骤没有偷⼯减料,这步其实⽔到渠成;反之,问题可能都会累计到此阶段集中的反映出来。要想训练优秀的主题模型,两个重要的前提就是数据质量和⽂本预处理。在此特别安利⼀下⽤起来舒服的预处理包:中⽂–>jieba,英⽂–>spaCy。上⽂采⽤nltk实属⽆奈,因为这台电脑⽆法成功安装spaCy唉。。
好了不跑题。LDA训练代码如下,其中参数请参考最后⾯的附录sklearn LDA API 中⽂解释。
# 4.LDA主题模型训练
# API: /stable/modules/generated/sklearn.decomposition.LatentDirichletAllocation.html
from sklearn.decomposition import LatentDirichletAllocation
# ===============================================================================
# 训练并存储为模型,初次执⾏取消注释
lda = LatentDirichletAllocation(n_components=20,  # ⽂章表⽰成20维的向量
max_iter=200,
learning_method='batch',
verbose=True)
lda.fit(tf)  # tf即为Document_word Sparse Matrix
joblib.dump(lda, 'D:/saved_model/LDA_sklearn/LDA_del')
# ===============================================================================
# 加载lda模型,初次请执⾏上⾯的训练过程,注释加载模型
# lda = joblib.load('D:/saved_model/LDA_sklearn/LDA_del')
print(lda.perplexity(tf))  # 收敛效果
(4)结果展⽰
LDA的训练时间根据max_iter设置的不同以及数据收敛情况的不同⽽差别很⼤。测试时max_iter设置为⼏⼗次通常很快就会结束,当然如果实际应⽤的话,建议⾄少上千次吧。
texts = [
"In this morning's TechBytes, we look back at the technology that changes the world in the past decade.\"\" As we counted down to 2000, fears Y2K would crash the world's computers had many questioning if we become too dependent o    "Movement is usually the sign of a person healthy, because only people who love sports will be healthy. I am a love sports, so I was born to now only had a disease. Of the many sports I like table tennis best.Table tennis is a sport, it does no    "While starting out on a business endeavour, following a set of rules is crucial for finding success.Without proper rules a business can go spiralling down and without taking too long at it. Following are golden rules that will ensure your success # ⽂本先预处理,再在词频模型中结构化,然后将结构化的⽂本list传⼊LDA主题模型,判断主题分布。
processed_texts = []
for text in texts:
temp = textPrecessing(text)
processed_texts.append(temp)
vectorizer_texts = ansform(processed_texts)
# print(vectorizer_texts)
ansform(vectorizer_texts))  # 获得分布矩阵
# 5.结果
def print_top_words(model, feature_names, n_top_words):
# 打印每个主题下权重较⾼的term
for topic_idx, topic in enumerate(modelponents_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
# 打印主题-词语分布矩阵
print(modelponents_)
n_top_words = 20
tf_feature_names = _feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
(Optional)调参过程
可以调整的参数
n_topics: 主题的个数
n_features: feature的个数,即常⽤词个数
doc_topic_prior:即我们的⽂档主题先验Dirichlet分布θd的参数α
topic_word_prior:即我们的主题词先验Dirichlet分布βk的参数η
learning_method: 即LDA的求解算法,有’batch’和’online’两种选择
其余sklearn提供的参数:根据LDA求解算法的不同,存在⼀些其它参数可以调节,参见最后的附录:sklearn LDA API 中⽂解释。
两种可⾏的调参⽅案
⼀、以n_topics为例,按照perplexity的⼤⼩选择最佳模型。当然,topic数⽬的不同势必会导致perplexity计算的不同,因此perplexity仅能作为参考,topic数⽬还需要根据实际需
求主观指定。n_topics调参代码如下:
# 同迭代次数下,维度的个数
from time import time
docList = []
with open('D:/data/LDA/20newsgroups(2000).txt', 'r') as f:
for line adlines():
if line != '':
docList.append(line.strip())
als import joblib
tf_vectorizer = joblib.load('D:/saved_model/vectorizer_sklearn/del')
tf = tf_vectorizer.fit_transform(docList)
from sklearn.decomposition import LatentDirichletAllocation
n_topics = range(20, 35, 5)
perplexityLst = [1.0]*len(n_topics)
#训练LDA并打印训练时间
lda_models = []
for idx, n_topic in enumerate(n_topics):
lda = LatentDirichletAllocation(n_components=n_topic,
max_iter=20,
learning_method='batch',
evaluate_every=200,
#                                    perp_tol=0.1, #default
#                                    doc_topic_prior=1/n_topic, #default
#                                    topic_word_prior=1/n_topic, #default
verbose=0)
t0 = time()
lda.fit(tf)
perplexityLst[idx] = lda.perplexity(tf)
lda_models.append(lda)
print("# of Topic: %d, " % n_topics[idx])
print("done in %0.3fs, N_iter %d, " % ((time() - t0), lda.n_iter_))
print("Perplexity Score %0.3f" % perplexityLst[idx])
#打印最佳模型
best_index = perplexityLst.index(min(perplexityLst))
best_n_topic = n_topics[best_index]
best_model = lda_models[best_index]
print("Best # of Topic: ", best_n_topic)
import matplotlib.pyplot as plt
import os
#绘制不同主题数perplexity的不同
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(n_topics, perplexityLst)
ax.set_xlabel("# of topics")
ax.set_ylabel("Approximate Perplexity")
plt.savefig(os.path.join('lda_result', 'perplexityTrend.png'))
plt.show()
⼆、如果想⼀次性调整所有参数也可以直接利⽤sklearn作cv,但是这样做的结果⼀定是,耗时⼗分长。以下代码仅供参考,可以根据⾃⾝的需求进⾏增减。
del_selection import GridSearchCV
parameters = {'learning_method':('batch', 'online'),
'n_topics':range(20, 75, 5),
'perp_tol': (0.001, 0.01, 0.1),
'doc_topic_prior':(0.001, 0.01, 0.05, 0.1, 0.2),
'topic_word_prior':(0.001, 0.01, 0.05, 0.1, 0.2)
'max_iter':1000}
lda = LatentDirichletAllocation()
model = GridSearch(lda, parameters)
model.fit(tf)
sorted(model.cv_results_.keys())
附录:sklearn LDA API 中⽂解释
Class sklearn.decomposition.LatentDirichletAllocation(n_topics=10, doc_topic_prior=None, topic_word_prior=None, learning_method=None, learning_decay=0.7, learning_offset=10.0, max_iter=10, batch_size=128, evaluate_every=-1, total_samples=1000000.0, perp_tol=0.1, mean_change_tol=0.001,
max_doc_update_iter=100, n_jobs=1, verbose=0, random_state=None)
参数:
1. n_topics: 即我们的隐含主题数K,需要调参。K的⼤⼩取决于我们对主题划分的需求,⽐如我们只需要类似区分是动物,植物,还是⾮⽣物这样的粗粒度需求,那么K值可以取
的很⼩,个位数即可。如果我们的⽬标是类似区分不同的动物以及不同的植物,不同的⾮⽣物这样的细粒度需求,则K值需要取的很⼤,⽐如上千上万。此时要求我们的训练⽂档数量要⾮常的多。
2. doc_topic_prior:即我们的⽂档主题先验Dirichlet分布θd的参数α。⼀般如果我们没有主题分布的先验知识,可以使⽤默认值1/K。
3. topic_word_prior:即我们的主题词先验Dirichlet分布βk的参数η。⼀般如果我们没有主题分布的先验知识,可以使⽤默认值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”时有意义,即分步训练时每⼀批⽂档样本的数量。在使⽤p
artial_fit函数时需要。
9)batch_size: 仅仅在算法使⽤”online”时有意义,即每次EM算法迭代时使⽤的⽂档样本的数量。
10)mean_change_tol :即E步更新变分参数的阈值,所有变分参数更新⼩于阈值则E步结束,转⼊M步。⼀般不⽤修改默认值。
11)max_doc_update_iter: 即E步更新变分参数的最⼤迭代次数,如果E步迭代次数达到阈值,则转⼊M步。
⽅法:
1)fit(X[, y]):利⽤训练数据训练模型,输⼊的X为⽂本词频统计矩阵。
2)fit_transform(X[, y]):利⽤训练数据训练模型,并返回训练数据的主题分布。
3)get_params([deep]):获取参数
4)partial_fit(X[, y]):利⽤⼩batch数据进⾏Online⽅式的模型训练。
5)perplexity(X[, doc_topic_distr, sub_sampling]):计算X数据的approximate perplexity。
6)score(X[, y]):计算approximate log-likelihood。
7)set_params(**params):设置参数。
8)transform(X):利⽤已有模型得到语料X中每篇⽂档的主题分布。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。