本项目是利用新浪新闻数据集做的文本聚类任务,目前实现了 LDA 及 KMEANS 两种算法。
.
├── __init__.py
├── config.py # 配置文件
├── data # 数据文件
│ ├── __init__.py
│ ├── kmeans.png # 算法训练阶段产生的图片
│ ├── lda.model # 算法模型
│ └── mallet-2.0.8 # java 实现的机器学习算法包,内含优化的 LDA 算法,可用 gensim 调用
├── kmeans.py # KMEANS 算法实现
├── lda.py # LDA 算法实现
├── load_datas.py # 数据集处理
├── log # 日志
│ └── server.log
├── readme.md
├── train.py # 算法训练
├── predict.py # LDA 算法推理
└── util.py # 公共库
目前项目暂未实现 shell 或者 python脚本配置项启动
项目的入口位于 train.py,可以通过运行该脚本分别利用 LDA 和 KMEANS 算法进行文本聚类。
python train.py
该项目采用的开源数据集是 THUCNews 新闻文本分类数据集,其中只使用了训练集 cnews.train.txt 部分,原始的数据集含有标签信息,共有 10 个类别,分别是:“体育”、“娱乐”、“家居”、“房产”、“教育”、“时尚”、“时政”、“游戏”、“科技”、“财经”。在所使用的训练集中,每个类别拥有的文档数均为 5000,共 5 万篇文章。
数据集的处理部分,代码详见 load_datas.py
该数据集本身就是开源经过清洗处理过的干净数据集,所以本身并不需要过多的清洗工作,数据集示例如下:
马晓旭意外受伤让国奥警惕 无奈大雨格外青睐殷家军记者傅亚雨沈阳报道 来到沈阳,国奥队依然没有摆脱雨水的困扰。7月31日下午6点,国奥队的日常训练再度受到大雨的干扰,无奈之下队员们只慢跑了25分钟就草草收场。31日上午10点,...... 有些不解。
下面我们简单的清洗一下,去掉了所有的符号或者其他非常规字符,只保留中文,字母和数字三种字符,处理后的文本示例如下:
马晓旭意外受伤让国奥警惕无奈大雨格外青睐殷家军记者傅亚雨沈阳报道来到沈阳国奥队依然没有摆脱雨水的困扰7月31日下午6点国奥队的日常训练再度受到大雨的干扰无奈之下队员们只慢跑了25分钟就草草收场31日上午10 ...... 有些不解
对于中文文本的处理,通常需要分词及停用词处理,分词采用的是 jiaba 分词工具,分词后示例如下:
'马晓旭', '意外', '受伤', '让', '国奥', '警惕', '无奈', '大雨', '格外', '青睐', '殷家', '军', '记者', '傅亚雨', '沈阳', '报道', '来到', '沈阳', '国奥队', '依然', '没有', '摆脱', '雨水', '的', '困扰', '7', '月', '31', '日', '下午', '6', '点', '国奥队', '的', '日常', '训练', '再度', '受到', '大雨', '的', '干扰', '无奈', '之下', '队员', '们', '只', '慢跑', '了', '25', '分钟', '就', '草草收场', '31', '日', '上午', '10', ......
在一开始第一遍做的时候,关于停用词部分采用的是百度停用词表,但是经过去停用词后依然存在大量的数字和单个字的词语,导致后续的 LDA 的效果很差,不忍直视的差,因此,第二遍处理的时候把分词结果中的一些数字和所有单字均作为停用词,这样一来,效果改善显著。
经过去停用词后的示例如下:
'马晓旭', '意外', '受伤', '国奥', '警惕', '无奈', '大雨', '格外', '青睐', '殷家', '傅亚雨', '沈阳', '报道', '来到', '沈阳', '国奥队', '依然', '摆脱', '雨水', '困扰', '下午', '国奥队', '日常', '训练', '再度', '大雨', '干扰', '无奈', '之下', '队员', '慢跑', '分钟', '草草收场', '上午', '国奥队', ...... '非战斗', '减员', '女足', '队员', '马晓旭', '热身赛', '受伤', '导致', '无缘', '奥运', '前科', '沈阳', '国奥队', '格外', '警惕', '训练', '嘱咐', '队员', '动作', '再出', '事情', '工作人员', '长春', '沈阳', '雨水', '一路', '伴随', '国奥队', '长春', '几次', '训练', '大雨', '搅和', '没想到', '沈阳', '碰到', '事情', '国奥', '球员', '雨水', '青睐', '不解'
后面做一个 bigram 或 trigram 处理的操作,可以将一些高频的连续短语组合成一个连词
'马晓旭', '意外', '受伤', '国奥', '警惕', '无奈', '大雨', '格外', '青睐', '殷家', '傅亚雨', '沈阳', '报道', '来到', '沈阳', '国奥队', '依然', '摆脱', '雨水', '困扰', '下午', '国奥队', '日常', '训练', '再度', '大雨', '干扰', '无奈_之下', '队员', '慢跑', '分钟', '草草收场', '上午', '国奥队', '奥体中心', '外场', '训练', '阴沉沉', '气象预报', '显示', '当天', '下午', '沈阳', '大雨', '幸好', '队伍', '上午', '训练', '干扰', '下午', ...... , '队员', '马晓旭', '热身赛', '受伤', '导致', '无缘', '奥运', '前科', '沈阳', '国奥队', '格外', '警惕', '训练', '嘱咐', '队员', '动作', '再出', '事情', '工作人员', '长春_沈阳', '雨水', '一路', '伴随', '国奥队', '长春', '几次', '训练', '大雨', '搅和', '没想到', '沈阳', '碰到', '事情', '国奥', '球员', '雨水', '青睐', '不解'
LDA 主题模型是文本集合的生成概率模型。假设每个文本由话题的一个多项分布表示,每个话题由单词的一个多项分布表示,特别假设文本的话题分布的先验分布是狄利克雷分布,话题的单词分布的先验分布也是狄利克雷分布。先验分布的导入使 LDA 能够更好的应对话题模型学习中的过拟合现象。LDA 模型是含有隐变量的概率图模型。LDA 模型的学习与推理无法直接求解,通常使用吉布斯抽样和变分 EM 算法。
LDA由Blei, David M.、Ng, Andrew Y.、Jordan于2003年提出,是一种主题模型,它可以将文档集 中每篇文档的主题以概率分布的形式给出,从而通过分析一些文档抽取出它们的主题(分布)出来后,便可以根据主题(分布)进行主题聚类或文本分类。同时,它是一种典型的词袋模型,即一篇文档是由一组词构成,词与词之间没有先后顺序的关系。
gensim 软件包中包含 LDA 算法的实现,但是效果不是很理想,Mallet 是一个基于 java 的自然语言处理算法包,其中包含优化改进版的 LDA 算法实现,幸运的是,利用 gensim 可以轻松的调用 mallet,实现跨语言调用。
Mallet 主题模型包括一个极其快速和高度可伸缩的 Gibbs 抽样实现,文档主题超参数优化的有效方法。
由于gensim 原生态的 LDA 版本无论是运行性能还是最终结果与 mallet 版本相比差距太大,因此项目中我们只选择 mallet 版本。但是为了保证该文档的完整性,我们下面依然会给出实验过程中的原生态 LDA 效果。
LDA 算法的输入主要包括分词词典以及向量化表示的文本。
1、创建词典 id2word,例如 id2word[100] ---> "草草收场"
2、对文本进行词频统计,即文本向量化表示,下文表示二元组的含义是 第几个词在该条新闻中出现的次数
[(0, 1), (1, 1), (2, 2), (3, 4), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 2), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 1), (17, 1), (18, 1), (19, 1), (20, 1), (21, 1), (22, 2), (23, 1), (24, 1), (25, 4), (26, 1), (27, 1), (28, 1), (29, 1), (30, 2), (31, 1), (32, 1), (33, 1), (34, 1), (35, 1), (36, 3), (37, 1), (38, 1), (39, 1), (40, 1), 。。。。。。。。。]
例如,将上文翻译为可读模式(仅仅是为了方便人读)
[('一周', 1), ('一路', 1), ('上午', 2), ('下午', 4), ('不再', 1), ('不解', 1), ('丝毫', 1), ('两天', 1), ('中止', 1), ('事情', 2), ('介绍', 1), ('休息', 1), ('伤病', 1), ('伴随', 1), ('位置', 1), ('例子', 1), ('例行_训练', 1), ('依然', 1), ('保护', 1), ('保证', 1), ('停下来', 1), ('傅亚雨', 1), ('全队', 2), ('再出', 1), ('再度', 1), ('冯萧霆', 4), ('减员', 1), ('几个', 1), ('几次', 1), ('出现意外', 1), ('分钟', 2), ('前科', 1), ('动作', 1), ('即将', 1), ('参加', 1), ('发烧_症状', 1), ...... ]
LDA 算法的主题数是个超参,需要借助对数据的先验知识以及超参搜索进行确定,对于 LDA 算法的评估指标主要有困惑度和一致性得分,然而困惑度通常参考价值不大,难以准确的进行评估,所以实际使用中主要参照的是 coherence value,详情可参照这里。
# 使用 gensim 包下的 LDA
lda_model = gensim.models.ldamodel.LdaModel(corpus=corpus,
id2word=id2word,
num_topics=10,
random_state=100,
update_every=1,
chunksize=100,
passes=10,
alpha='auto',
per_word_topics=True)
[(0,
'0.103*"基金" + 0.023*"市场" + 0.020*"公司" + 0.020*"投资" + 0.013*"银行" + '
'0.010*"投资者" + 0.009*"债券" + 0.009*"亿元" + 0.008*"收益" + 0.007*"2008"'),
(1,
'0.017*"反弹" + 0.014*"发现" + 0.012*"时间" + 0.006*"发生" + 0.006*"两只" + 0.005*"加大" '
'+ 0.005*"一只" + 0.004*"两个" + 0.004*"恢复" + 0.004*"告诉"'),
(2,
'0.015*"呈现" + 0.009*"暂停" + 0.008*"新浪_科技" + 0.007*"高位" + 0.007*"提取" + '
'0.007*"开户" + 0.006*"缩小" + 0.005*"颜色" + 0.005*"予以" + 0.004*"风险_准备金"'),
(3,
'0.022*"发行" + 0.022*"股票" + 0.016*"中国" + 0.014*"机构" + 0.012*"成立" + 0.012*"业务" '
'+ 0.012*"美国" + 0.011*"企业" + 0.011*"股市" + 0.010*"政策"'),
(4,
'0.031*"创新" + 0.021*"拍卖" + 0.012*"全年" + 0.009*"大涨" + 0.006*"巴菲特" + '
'0.006*"收于" + 0.006*"比重" + 0.005*"价值" + 0.005*"中海" + 0.005*"拍卖会"'),
(5,
'0.015*"下跌" + 0.010*"指数" + 0.009*"净值" + 0.009*"仓位" + 0.008*"涨幅" + '
'0.008*"QDII" + 0.008*"平均" + 0.008*"12" + 0.008*"跌幅" + 0.008*"股票_型基金"'),
(6,
'0.015*"变更" + 0.011*"翡翠" + 0.009*"以内" + 0.007*"放缓" + 0.007*"节后" + '
'0.007*"这部分" + 0.006*"超出" + 0.006*"解禁" + 0.005*"权证" + 0.004*"03"'),
(7,
'0.014*"配置" + 0.012*"精选" + 0.008*"选择" + 0.007*"发布" + 0.006*"收藏" + 0.005*"功能" '
'+ 0.005*"表现" + 0.005*"风格" + 0.005*"英国" + 0.005*"作品"'),
(8,
'0.017*"波动" + 0.011*"回暖" + 0.009*"放大" + 0.006*"邮票" + 0.006*"金牛" + 0.005*"创出" '
'+ 0.005*"面值" + 0.005*"设计" + 0.005*"太空" + 0.004*"高点"'),
(9,
'0.042*"报告" + 0.016*"设计" + 0.009*"采用" + 0.008*"资料" + 0.007*"萎缩" + 0.007*"悲观" '
'+ 0.006*"引导" + 0.006*"测试" + 0.006*"机身" + 0.005*"质量"')]
上面给出一些聚类结果信息,10 个簇,每个簇下前 10 个关键词及每个词的权重。
Perplexity: -11.488651193563687
Coherence Score: 0.41585289864166086
判断一个LDA模型是否合理的标准一般有两个:
①困惑度(Perplexity Score):在文本分析中,训练出来的模型识别某些文档包含哪些主题具有不确定性。因此数值越低,不确定性就越小,则最后的聚类结果就越好。
②一致性(Coherence Score):一种主要的最优主题数目选择的模型,衡量主题质量最有效的方法,提供四个一致性度量:u_mass、c_v、c_uci 以及c_npmi。
一致性得分才 0.41,很低。另外有一个软件包 pyLDAvis 可用于 LDA 结果的可视化,如下图所示:

上面的图中,左侧每个圆圈表示一个主题,圈越大表示主题占比越大,理想状态下,对于该份数据集最好的图像应该是所有圆圈大小差不多,而且圈之间没有太多的重叠。
当我们不对数据进行任何改动,仅仅将算法切换到 mallet 包下,看下结果:
[(0,
[('游戏', 0.02948806382787908),
('玩家', 0.016620182710182587),
('活动', 0.009368021178526073),
('系统', 0.004566093764359003),
('功能', 0.004484160947054792),
('支持', 0.004013600847672499),
('模式', 0.00383866321072567),
('体验', 0.0033636743104080145),
('系列', 0.0033559239087711293),
('采用', 0.0032241670809440874)]),
(1,
[('市场', 0.013003899365832674),
('项目', 0.012691672564284029),
('房地产', 0.011957592661976326),
('北京', 0.0102563035122046),
('企业', 0.007477484978421659),
('城市', 0.007022327685497412),
('中国', 0.006902987663572152),
('价格', 0.006900212314225053),
('开发商', 0.0068780095194482605),
('土地', 0.005909412597310686)]),
(2,
[('设计', 0.013374211000901713),
('搭配', 0.010714758040276525),
('时尚', 0.01000541027953111),
('风格', 0.006482717162608957),
('组图', 0.006374511571986775),
('品牌', 0.005079651337541328),
('空间', 0.005064021641118124),
('选择', 0.0045482416591523895),
('黑色', 0.004244063721070033),
('色彩', 0.004193567778779681)]),
(3,
[('发现', 0.010489961943350676),
('研究', 0.007555596426743505),
('美国', 0.006713092620614126),
('时间', 0.006089807004392724),
('科学家', 0.005484170310240183),
('地球', 0.004538559643603241),
('发生', 0.0038316738702135426),
('消息', 0.003789873791629395),
('人类', 0.003670975790323375),
('世界', 0.0035483622264765413)]),
(4,
[('比赛', 0.017430487449306864),
('球队', 0.009575105362207438),
('火箭', 0.009304741961990087),
('时间', 0.008927293450314098),
('球员', 0.008097118774352586),
('表现', 0.0064516129032258064),
('热火', 0.005637341956688843),
('篮板', 0.005478304662443343),
('防守', 0.00535637607018846),
('12', 0.005023458000901211)]),
(5,
[('中国', 0.012213800245407315),
('发展', 0.011093803030171176),
('工作', 0.007728589952223895),
('国家', 0.005970707765139978),
('合作', 0.005271906083838797),
('经济', 0.004225879158653218),
('国际', 0.00408316001079096),
('台湾', 0.0039891742304914235),
('部门', 0.003734194289493608),
('相关', 0.0035949560964572583)]),
(6,
[('学生', 0.024595562866363236),
('美国', 0.018474116720452217),
('中国', 0.016831798760725165),
('留学', 0.014698092297398081),
('大学', 0.012958193641920667),
('申请', 0.01201200939562699),
('学校', 0.010808804566776561),
('专业', 0.008572289870426776),
('教育', 0.00831875431271825),
('学习', 0.007523297391110398)]),
(7,
[('基金', 0.03416697695741854),
('银行', 0.015088816164625042),
('投资', 0.014667374607919626),
('公司', 0.013569209691343152),
('市场', 0.013051093368941872),
('亿元', 0.007622201057833413),
('投资者', 0.0054772296939563925),
('风险', 0.004936455223345143),
('发行', 0.004902468001030191),
('产品', 0.004812590679797315)]),
(8,
[('生活', 0.006468513289767319),
('中国', 0.005110341462634277),
('东西', 0.004601626926676318),
('事情', 0.00408411386887003),
('喜欢', 0.003992129322273858),
('希望', 0.0038969453131873846),
('朋友', 0.003785763991649403),
('一点', 0.0034578190863934857),
('孩子', 0.003381031986626247),
('告诉', 0.003282648515049472)]),
(9,
[('电影', 0.023434155295774674),
('导演', 0.01065013954857647),
('影片', 0.009593784243758317),
('观众', 0.007024089326482096),
('拍摄', 0.0057483815539967845),
('香港', 0.005732988398371019),
('票房', 0.00556654990316743),
('角色', 0.0051086035233009084),
('演员', 0.0049344684502844365),
('上映', 0.004146531296690568)])]
Coherence Score: 0.6824643745400414
一致性得分从 0.41 提升到了 0.68。而且根据每个簇中的关键词分布,我们发现其实聚类结果已经非常理想了。
**Mallet版本的 LDA 在训练过程中有一个 worker 参数,默认是 4,意味着采样过程中是多线程处理的,在服务器及 Google Colab 中运行均无问题,但在我本地会报 java 数组越界异常,该问题在 mallet 官方确实有一些反映,但未找到合理解决方案,因此该参数可暴露出来自由调整,调整为 1 意味着单线程处理,不再出现该问题,但是速度会慢一些。 **
然而,LDA 中也有一个类似于 KMeans 中聚类数 K 的超参,到底设定多少个主题比较合适?在这里,我们通过循环遍历区间范围,并根据一致性得分画出折线图:

实际上,我们并不总是寻找具有最高一致性得分的点,而是倾向于寻找具有较高分数平滑曲线段的左侧拐点,在上图明显我们应该选择 10 个主题数。
在选定最佳主题数后,我们可以重新进行训练,并对聚类后的结果进行汇总,例如下表给出每个主题的编号,主题关键词,主题代表性文章以及该主题在该文档下的占比:
| Topic_Num | Topic_Perc_Contrib | Keywords | Text | |
|---|---|---|---|---|
| 0 | 0.0 | 0.9050 | 中国, 发展, 企业, 国际, 国家, 合作, 经济, 工作, 北京, 城市 | [外交部, 公布, 中国, 联合国, 作用, 国际, 立场, 中新网_日电, 联合国大会, ... |
| 1 | 1.0 | 0.9098 | 市场, 房地产, 项目, 北京, 价格, 开发商, 土地, 销售, 上海, 房价 | [京新盘, 供应, 将量, 跌价, 半数以上, 中高档, 项目, 北京晨报, 报道, 多家,... |
| 2 | 2.0 | 0.8951 | 电影, 导演, 影片, 观众, 票房, 拍摄, 角色, 香港, 演员, 故事 | [赵氏_孤儿, 主题曲, 陈凯歌, 葛优, 幽默, 鞠躬, 陈凯歌, 担心, 葛优, 笑场,... |
| 3 | 3.0 | 0.9198 | 比赛, 球队, 火箭, 时间, 球员, 表现, 热火, 篮板, 防守, 12 | [爵士, 五虎将, 双破, 网而出, 邓肯, 爆发, 马刺, 轻取, 连胜, 新浪_体育讯,... |
| 4 | 4.0 | 0.9236 | 基金, 投资, 公司, 市场, 银行, 亿元, 投资者, 发行, 产品, 风险 | [降低, 营业税, 预期, 鼓舞, 基金, 反手, 银行, 股张, 伟霖, 降低, 营业税,... |
| 5 | 5.0 | 0.8807 | 游戏, 玩家, 活动, 功能, 系统, 支持, 模式, 系列, 时间, 采用 | [天劫_OL, 资料片, 上市, 更新, 公告, 2009, 推出, 天劫_OL, 最新_资... |
| 6 | 6.0 | 0.9152 | 发现, 研究, 美国, 时间, 科学家, 地球, 消息, 发生, 人类, 世界 | [科学家, 声称, 揭开, 通古斯_爆炸, 之谜, 新浪_科技, 北京, 时间, 消息, 美... |
| 7 | 7.0 | 0.9153 | 设计, 搭配, 时尚, 风格, 组图, 空间, 选择, 感觉, 品牌, 黑色 | [组图, 09_春夏, LV_手袋, 十大, 潮流, 新浪, 女性, 讯明, 年春夏, 注定... |
| 8 | 8.0 | 0.8830 | 银行, 相关, 台湾, 工作, 情况, 部门, 陈水扁, 调查, 报道, 公司 | [存折, 骗过, 银行, 系统, 存款, 法院, 银行, 全责, 本报讯, 刘艺明, 通讯员... |
| 9 | 9.0 | 0.9037 | 学生, 美国, 留学, 大学, 中国, 申请, 学校, 专业, 教育, 学习 | [重磅, 推荐, 美国大学_报考, 八大, 攻略, 美国大学_报考, 材料, 绝不, 一件,... |
下表则给出每个主题的新闻数以及占比情况等:
| Dominant_Topic | Topic_Keywords | Num_Documents | Perc_Documents | |
|---|---|---|---|---|
| 3.0 | 0.0 | 中国, 发展, 企业, 国际, 国家, 合作, 经济, 工作, 北京, 城市 | 4938 | 0.0988 |
| 9.0 | 1.0 | 市场, 房地产, 项目, 北京, 价格, 开发商, 土地, 销售, 上海, 房价 | 4022 | 0.0804 |
| 4.0 | 2.0 | 电影, 导演, 影片, 观众, 票房, 拍摄, 角色, 香港, 演员, 故事 | 4456 | 0.0891 |
| 0.0 | 3.0 | 比赛, 球队, 火箭, 时间, 球员, 表现, 热火, 篮板, 防守, 12 | 3412 | 0.0682 |
| 8.0 | 4.0 | 基金, 投资, 公司, 市场, 银行, 亿元, 投资者, 发行, 产品, 风险 | 3977 | 0.0795 |
$ claude mcp add text_cluster \
-- python -m otcore.mcp_server <graph>