LDA主题分析代码详解
本文档详细解析白鹤梁LDA主题分析全流程代码实现,从MySQL数据获取到最终的可视化展示。
1. 整体流程概述
该代码实现了一个完整的LDA(Latent Dirichlet Allocation,潜在狄利克雷分配)主题模型分析流程,主要包含以下几个步骤:
- 数据获取:从MySQL数据库读取评论文本数据
- 数据清洗:去除重复数据、过滤短文本、清理特殊字符
- 中文分词:使用jieba库进行分词处理,并应用停用词表
- 最佳主题数确定:通过计算一致性得分(Coherence Score)并寻找次高峰来确定最佳主题数k
- LDA模型训练:使用tomotopy库训练LDA模型
- 可视化展示:使用pyLDAvis库生成交互式可视化结果
2. 代码结构与核心模块
2.1 导入依赖库
import os
import re
import pymysql
import pandas as pd
import jieba
import jieba.analyse
import tomotopy as tp
import pyLDAvis
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
from tqdm import tqdm
该代码使用了多个专业库来实现不同功能:
pymysql和pandas用于数据库操作和数据处理jieba用于中文分词tomotopy作为高效的LDA实现库pyLDAvis用于交互式可视化matplotlib用于绘制一致性得分曲线tqdm用于显示进度条
2.2 数据库配置
DB_CONFIG = {
"host": "localhost", "user": "root", "password": "******",
"database": "bishe", "port": 3306, "charset": "utf8mb4"
}
配置数据库连接信息,包括主机地址、用户名、密码、数据库名、端口和字符集。
3. 核心函数详解
3.1 数据获取模块
def fetch_comments():
"""从MySQL数据库获取评论文本数据"""
conn = pymysql.connect(**DB_CONFIG)
df = pd.read_sql("SELECT content FROM baiheliang WHERE content IS NOT NULL", conn)
conn.close()
return df
功能:建立数据库连接,执行SQL查询获取不为空的评论内容,并以DataFrame形式返回。
关键点:使用pandas的read_sql函数直接将SQL查询结果转换为DataFrame,操作简便;查询结束后及时关闭数据库连接,避免资源泄露。
3.2 数据清洗模块
def clean(text):
"""清理文本,保留中文及常用标点符号"""
return re.sub(r'[^\u4e00-\u9fa5,。!?、]+', '', str(text))
def text_etl(df):
"""对评论文本进行ETL处理"""
df = df.drop_duplicates(subset=['content'])
df = df[df['content'].str.len() >= 5]
df['content'] = df['content'].apply(clean)
return df
功能:
clean函数:使用正则表达式过滤文本,只保留中文字符和常见中文标点符号text_etl函数:执行完整的数据清洗流程,包括去重、过滤短文本和应用clean函数
关键点:
- 使用
drop_duplicates去除重复评论 - 过滤掉长度小于5的评论,避免过短文本影响分析质量
- 使用正则表达式精确控制保留的字符类型
3.3 分词模块
STOP_FILE = 'baiheliang_stopwords.txt' # 同目录放置
def cut(txt, topk=15):
"""对文本进行分词,并应用TF-IDF提取关键词"""
if os.path.exists(STOP_FILE):
jieba.analyse.set_stop_words(STOP_FILE)
return jieba.analyse.extract_tags(txt, topK=topk, withWeight=False)
功能:对文本进行分词处理,并应用TF-IDF算法提取topK个关键词。
关键点:
- 支持加载同目录下的停用词表
baiheliang_stopwords.txt - 使用
jieba.analyse.extract_tags进行关键词提取,而非简单分词 topk=15参数指定每个文本提取15个关键词
3.4 寻找最佳主题数k
def find_best_k(docs, kmin=2, kmax=10, min_df=2):
"""通过计算一致性得分并寻找次高峰确定最佳主题数k"""
scores = []
for k in range(kmin, kmax + 1):
mdl = tp.LDAModel(k=k, min_df=min_df, seed=42)
for w in docs:
if w:
mdl.add_doc(w)
mdl.train(200)
scores.append(tp.coherence.Coherence(mdl).get_score())
print(f'k={k} coherence={scores[-1]:.4f}')
first_max_idx = int(np.argmax(scores))
second_max_idx = first_max_idx + 1 + int(np.argmax(scores[first_max_idx + 1:]))
best_k = kmin + second_max_idx
print(f'\n🎯 跳过首峰 k={kmin+first_max_idx},选次高峰 k={best_k}\n')
plt.plot(range(kmin, kmax + 1), scores, marker='o', color='crimson')
plt.scatter([best_k], [scores[second_max_idx]], color='limegreen', zorder=5, label=f'best k={best_k}')
plt.xticks(range(kmin, kmax + 1)); plt.xlabel('Num Topics'); plt.ylabel('Coherence')
plt.legend(); plt.title('Coherence (skip first peak)')
os.makedirs('historyPNG', exist_ok=True)
plt.savefig(f'historyPNG/coherence_2ndPeak_{datetime.now():%Y%m%d_%H%M%S}.png', dpi=300, bbox_inches='tight')
plt.show()
return best_k
功能:通过计算不同主题数下的一致性得分,找到次高峰对应的主题数作为最佳k值。
技术要点:
- 一致性得分计算:使用
tp.coherence.Coherence(mdl).get_score()计算每个k值对应的一致性得分 - 次高峰选择策略:这是代码中的一个创新点,不是选择一致性得分最高的k值,而是选择次高峰对应的k值,避免过拟合
- 可视化:绘制一致性得分曲线,并将结果保存为图片文件
- 参数设置:
kmin=2和kmax=10:设置主题数的搜索范围min_df=2:忽略出现次数少于2次的词seed=42:设置随机种子,保证结果可重现
3.5 LDA模型训练
def train_lda(docs, k, min_df=2):
"""训练LDA主题模型"""
mdl = tp.LDAModel(k=k, min_df=min_df, seed=42)
for w in docs:
if w:
mdl.add_doc(w)
for i in range(0, 100, 10):
mdl.train(10)
print(f'Iteration {i:>3} ll={mdl.ll_per_word:.4f}')
print('\n—— 主题关键词 ——')
for i in range(k):
print(f'主题{i}: {mdl.get_topic_words(i, top_n=10)}')
return mdl
功能:使用tomotopy库训练LDA模型,并输出每个主题的top10关键词。
技术要点:
- 模型初始化:使用之前确定的最佳k值初始化LDA模型
- 批量训练与日志输出:每训练10次迭代输出一次似然度(log-likelihood)值
- 结果展示:输出每个主题的前10个关键词,方便直观了解主题内容
3.6 可视化模块
def visualize(mdl):
"""使用pyLDAvis对LDA模型结果进行可视化"""
topic_term = np.stack([mdl.get_topic_word_dist(k) for k in range(mdl.k)])
doc_topic = np.stack([d.get_topic_dist() for d in mdl.docs])
doc_topic /= doc_topic.sum(axis=1, keepdims=True)
doc_len = np.array([len(d.words) for d in mdl.docs])
vocab = list(mdl.used_vocabs)
term_freq = mdl.used_vocab_freq
data = pyLDAvis.prepare(
topic_term, doc_topic, doc_len, vocab, term_freq,
start_index=0, sort_topics=False
)
html = f'lda_baiheliang_full_stop_{datetime.now():%Y%m%d_%H%M%S}.html'
pyLDAvis.save_html(data, html)
print(f'✅ 可视化已保存 → {html}')
功能:将训练好的LDA模型结果转换为pyLDAvis可处理的格式,并生成交互式HTML可视化页面。
技术要点:
- 数据提取与转换:从模型中提取主题-词分布、文档-主题分布、文档长度、词汇表和词频等信息
- 概率归一化:对文档-主题分布进行归一化处理,确保每行和为1
- 结果保存:将可视化结果保存为HTML文件,文件名包含时间戳,避免覆盖
3.7 主流程控制
def main():
"""主流程控制函数"""
df = fetch_comments()
print(f'📥 原始评论 {len(df)} 条')
df = text_etl(df)
print(f'🧹 清洗后 {len(df)} 条')
corpus = df['content'].tolist()
docs = [cut(c) for c in tqdm(corpus, desc='分词')]
docs = [d for d in docs if d] # 过滤空列表
best_k = find_best_k(docs, kmin=2, kmax=10, min_df=2)
model = train_lda(docs, k=best_k, min_df=2)
visualize(model)
if __name__ == '__main__':
main()
功能:按顺序调用各个模块,完成从数据获取到可视化的完整LDA分析流程。
技术要点:
- 流程控制:清晰地组织了整个分析流程的执行顺序
- 进度反馈:在各个关键步骤输出进度信息,方便用户了解执行状态
- 空值处理:过滤分词后产生的空列表,避免影响后续分析
4. 技术实现要点分析
4.1 次高峰选择策略
代码中最具特色的部分是使用"次高峰"而非"最高峰"来确定最佳主题数k,这种策略有以下优势:
- 避免过拟合:当主题数过多时,模型可能会过度拟合训练数据
- 提高泛化能力:选择次高峰对应的k值,通常能获得更好的泛化性能
- 平衡主题粒度:既能保持主题的区分度,又不会使主题过于碎片化
4.2 tomotopy库的使用
代码使用tomotopy库而非传统的gensim库实现LDA,这有几个优点:
- 性能优势:tomotopy是基于C++实现的,训练速度更快
- 内存效率:对大规模语料的内存占用更小
- 功能丰富:内置了一致性得分计算等实用功能
4.3 TF-IDF关键词提取替代传统分词
代码没有使用简单的分词,而是使用TF-IDF算法提取关键词,这一选择的理由是:
- 聚焦核心信息:过滤掉低信息价值的词语
- 降低维度:减少特征空间维度,提高后续LDA模型的训练效率
- 提高主题质量:使用关键词而非所有词,有助于生成更清晰的主题
5. 代码优化建议
5.1 异常处理增强
当前代码在数据库连接、文件操作等方面缺乏完善的异常处理机制。建议增加try-except块:
def fetch_comments():
"""从MySQL数据库获取评论文本数据"""
try:
conn = pymysql.connect(**DB_CONFIG)
df = pd.read_sql("SELECT content FROM baiheliang WHERE content IS NOT NULL", conn)
return df
except Exception as e:
print(f"数据库查询错误: {e}")
return pd.DataFrame(columns=['content'])
finally:
if 'conn' in locals() and conn.open:
conn.close()
5.2 参数配置外部化
将硬编码的参数(如kmin、kmax、topk等)提取到配置文件或命令行参数中,提高代码的灵活性:
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='LDA主题分析')
parser.add_argument('--kmin', type=int, default=2, help='最小主题数')
parser.add_argument('--kmax', type=int, default=10, help='最大主题数')
parser.add_argument('--topk', type=int, default=15, help='每个文档提取的关键词数')
return parser.parse_args()
# 在main函数中使用
args = parse_args()
docs = [cut(c, topk=args.topk) for c in tqdm(corpus, desc='分词')]
best_k = find_best_k(docs, kmin=args.kmin, kmax=args.kmax, min_df=2)
5.3 结果持久化存储
除了生成可视化HTML外,建议将模型和关键结果保存为文件,方便后续使用:
def save_model(mdl, filepath):
"""保存LDA模型"""
mdl.save(filepath)
print(f"模型已保存至: {filepath}")
# 在main函数中添加
model_path = f'lda_model_k{best_k}_{datetime.now():%Y%m%d_%H%M%S}.bin'
save_model(model, model_path)
5.4 增加主题命名功能
自动为每个主题生成有意义的名称,提高可读性:
def generate_topic_names(mdl, top_n=3):
"""为每个主题生成基于topN关键词的名称"""
topic_names = []
for i in range(mdl.k):
top_words = [word for word, _ in mdl.get_topic_words(i, top_n=top_n)]
topic_name = "_“.join(top_words)
topic_names.append(topic_name)
return topic_names
# 在train_lda函数中添加
print('\n—— 主题关键词与名称 ——')
topic_names = generate_topic_names(mdl)
for i in range(k):
top_words = mdl.get_topic_words(i, top_n=10)
print(f'主题{i} ({topic_names[i]}): {top_words}')
6. 输入输出示例
输入输出示例
输入:
# 运行脚本
python LAD-baiheliang-k.py
输出:
📥 原始评论 1000 条
🧹 清洗后 850 条
分词: 100%|██████████| 850/850 [00:05<00:00, 165.23it/s]
k=2 coherence=0.4821
k=3 coherence=0.5123
k=4 coherence=0.5437
k=5 coherence=0.5219
k=6 coherence=0.5368
k=7 coherence=0.5012
k=8 coherence=0.4897
k=9 coherence=0.4732
k=10 coherence=0.4618
🎯 跳过首峰 k=4,选次高峰 k=6
Iteration 0 ll=-8.6241
Iteration 10 ll=-8.2153
Iteration 20 ll=-8.0927
Iteration 30 ll=-8.0314
Iteration 40 ll=-7.9926
Iteration 50 ll=-7.9658
Iteration 60 ll=-7.9463
Iteration 70 ll=-7.9318
Iteration 80 ll=-7.9205
Iteration 90 ll=-7.9116
—— 主题关键词 ——
主题0: [('景点', 0.0832), ('游览', 0.0514), ('推荐', 0.0498), ('值得', 0.0389), ('风景', 0.0375), ('非常', 0.0321), ('不错', 0.0297), ('地方', 0.0256), ('朋友', 0.0214), ('适合', 0.0198)]
主题1: [('历史', 0.0724), ('文化', 0.0689), ('价值', 0.0532), ('保护', 0.0467), ('研究', 0.0398), ('古代', 0.0354), ('重要', 0.0312), ('意义', 0.0289), ('珍贵', 0.0245), ('石刻', 0.0213)]
主题2: [('门票', 0.0921), ('价格', 0.0654), ('便宜', 0.0437), ('合理', 0.0398), ('性价比', 0.0362), ('优惠', 0.0314), ('学生', 0.0287), ('折扣', 0.0251), ('团购', 0.0215), ('套餐', 0.0198)]
主题3: [('交通', 0.0789), ('方便', 0.0623), ('路线', 0.0456), ('公交', 0.0412), ('地铁', 0.0354), ('停车场', 0.0321), ('自驾', 0.0287), ('打车', 0.0254), ('距离', 0.0213), ('导航', 0.0198)]
主题4: [('服务', 0.0854), ('态度', 0.0632), ('工作人员', 0.0514), ('热情', 0.0478), ('周到', 0.0398), ('专业', 0.0354), ('耐心', 0.0312), ('讲解', 0.0287), ('细致', 0.0245), ('帮助', 0.0213)]
主题5: [('设施', 0.0765), ('完善', 0.0532), ('卫生', 0.0489), ('厕所', 0.0421), ('休息', 0.0378), ('餐饮', 0.0345), ('购物', 0.0312), ('标识', 0.0287), ('无障碍', 0.0254), ('安全', 0.0213)]
✅ 可视化已保存 → lda_baiheliang_full_stop_20231115_143025.html
7. 总结
本代码实现了一个完整的LDA主题分析流程,从数据获取、清洗、分词到模型训练和可视化,每个环节都有详细的实现。特别是在确定最佳主题数时采用的"次高峰"策略,是一种值得借鉴的实践经验。通过对代码进行适当的优化和扩展,可以进一步提高其灵活性、健壮性和实用性。
9775



被折叠的 条评论
为什么被折叠?



