最近从微博评论中获取了部分关于俄乌局势的评论,于是尝试根据评论内容进行简单的LDA主题聚类分析。主要涉及评论数据清洗、LDA数据分析、pyLDAvis可视化、困惑度计算。
一、数据处理
1:需要的特殊库:
import jieba
import jieba.posseg as psg
import pyLDAvis.sklearn
import matplotlib.pyplot as plt
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
2:数据展示
因为只想针对评论进行分析,仅从MongoDB中下载了用户名和对应评论,为保证运行性能,数据为35000条左右,大致内容如下:
为方便运行,文末的链接中提供了short版本,可以快速出结果,不然还是需要一段时间的。
3:设置用户词典和用户停用词表
相关路径如下:
output_path = r'F:\pycharm_project\LDA\lda\result'
file_path = r'F:\pycharm_project\LDA\lda\data'
os.chdir(file_path)
data=pd.read_csv("doc_ewujushi_short.csv")
os.chdir(output_path)
dic_file = r"F:\pycharm_project\LDA\lda\stop_dic\dict.txt"
stop_file = r"F:\pycharm_project\LDA\lda\stop_dic\stopwords.txt"
4:数据分词并简单清洗
其实还是建议直接调包,自创粑粑山让大伙见笑了。
def chinese_word_cut(mytext):
jieba.load_userdict(dic_file) # 加载用户词典
jieba.initialize() # 手动初始化(可选)
# 加载用户停用词表
try:
stopword_list = open(stop_file,encoding ='utf-8')
except:
stopword_list = []
print("error in stop_file")
stop_list = [] # 存储用户停用词
flag_list = ['n','nz','vn'] # 指定在jieba.posseg分词函数中只保存n:名词、nz:其他专名、vn:动名词
for line in stopword_list:
line = re.sub(u'\n|\\r', '', line)
stop_list.append(line)
word_list = []
seg_list = psg.cut(mytext) # jieba.posseg分词
# 原称之为粑粑山型的 词语过滤 还是建议直接掉包,见笑了
for seg_word in seg_list:
word = re.sub(u'[^\u4e00-\u9fa5]','',seg_word.word) # 只匹配所有中文
find = 0 # 标志位
for stop_word in stop_list:
if stop_word == word or len(word)<2: #长度小于2或者在用户停用词表中,将被过滤
find = 1
break
if find == 0 and seg_word.flag in flag_list: #标志位为0且是需要的词性则添加至word_list
word_list.append(word)
return (" ").join(word_list)
二、LDA分析
1:CountVectorizer函数计算词频
该函数通过fit_transform函数将文本中的词语转换为词频矩阵,它通过fit_transform函数计算各个词语出现的次数。矩阵元素a[i][j] 表示j词在第i个文本下的词频。即各个词语出现的次数,通过get_feature_names()可看到所有文本的关键字,通过toarray()可看到词频矩阵的结果。
data["content_cutted"] = data.content.apply(chinese_word_cut)
n_features = 1000 #这里作为测试,提取1000个特征词语
tf_vectorizer = CountVectorizer(strip_accents = 'unicode',
max_features=n_features,
stop_words='english',
max_df = 0.5,
min_df = 10)
tf = tf_vectorizer.fit_transform(data.content_cutted)
2:LatentDirichletAllocation函数进行LDA处理
设置需要分成的主题个数等参数,可以得到每篇文章属于每个主题的概率矩阵和分成每个主题中主题词的分布概率矩阵。需要注意的是,这里需要手动指定分类数量,文末会介绍困惑度寻求“最优分类数”的方式,针对本数据集,并没有看出比较优秀(个人观感)。
n_topics = 7 # 手动指定分类数
lda = LatentDirichletAllocation(n_components=n_topics, max_iter=50,
learning_method='batch',
learning_offset=50,
doc_topic_prior=0.1,
topic_word_prior=0.01,
random_state=0)
lda.fit(tf)
3:偷偷搂一眼主题与对应词语
def print_top_words(model, feature_names, n_top_words):
tword = []
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
topic_w = " ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]])
tword.append(topic_w)
print(topic_w)
return tword
n_top_words = 25
tf_feature_names = tf_vectorizer.get_feature_names()
topic_word = print_top_words(lda, tf_feature_names, n_top_words)
因为个人停用词典并没有设置过多词语,可以看出还是存在一些干扰。
4:输出每个评论对应的主题与高频词
import numpy as np
topics=lda.transform(tf)
topic = []
for t in topics:
topic.append(list(t).index(np.max(t)))
data['topic']=topic
data.to_excel("data_topic.xlsx",index=False)
导出的文件中会给出对应的标签,0-6,对应上面的主题词与所属类别。
三、可视化部分
pyLDAvis.enable_notebook() # 请在notebook中运行
pic = pyLDAvis.sklearn.prepare(lda, tf, tf_vectorizer)
pyLDAvis.save_html(pic, 'lda_pass'+str(n_topics)+'.html')
pyLDAvis.show(pic)
在notebook环境中会显示一个 类似于下图的可视化界面,上方代码中也指定了保存路径,可以根据文中开头部分的路径中查找。
四、根据困惑度选取最优聚类数
可以简单理解为指定不同聚类数,然后通过lda.perplexity()函数进行处理,最后通过lda.score()进行评分绘制,理论上选取最低点,或者第一个拐点。评分绘制可以使用最常用的matplotlib库。
需要注意,这里的n_max_topic不要一次性设置过大,个人觉得有两个主要原因,仅供参考。
(1):设置成10表示分别指定聚类数量从1-10进行聚类计算,在大文本中需要消耗过多时间
(2):在评分绘图过程中可能会因为数据过于密集,而导致难以观察最优聚类数。
plexs = []
scores = []
n_max_topics = 10
for i in range(1,n_max_topics):
print('正在进行第',i,'轮计算')
lda = LatentDirichletAllocation(n_components=i, max_iter=50,
learning_method='batch',
learning_offset=50,random_state=0)
lda.fit(tf)
plexs.append(lda.perplexity(tf))
scores.append(lda.score(tf))
结果绘制,需要注意的是,坐标需要根据n_max_topic进行相应的设置。
n_t=9 #区间最右侧的值。注意:不能大于n_max_topics
x=list(range(1,n_t))
plt.plot(x,plexs[1:n_t])
plt.xlabel("number of topics")
plt.ylabel("perplexity")
plt.show()
使用35000评论的数据集进行评估,理论选取3类,但是实际感觉有些过少,short版本中最优值为7,因为受到评论内容以及数据质量和数据清洗方法的多种因素共同影响,因此评分数据仅供参考。
五、代码链接
链接:https://pan.baidu.com/s/1p-awMzFCk4EKxUrSvxTR1A
提取码:2jam