前段时间看了LDA主题模型的理论知识,想着还是需要具体实践来感受下。然后想着搜一下看还有没有别的主题模型,看能不能都跑一下demo对比下效果,结果看到了一篇汇总类的知乎:
https://www.zhihu.com/question/34801598(回答者:苏格兰折耳喵)
其中除了lda模型外,还提到了针对短文本的Biterm主题模型,以及 corex_topic(先验知识主题模型);后面还有一些涉及到神经网络的,我个人看了下,感觉也就Topically-Driven-Language-Model稍微权威一些,但源码是tensorflow的,对我来说弄起来比较麻烦。
最终只选了LDA模型(gibbs采样和EM算法),BTM以及 corex_topic(不加入先验信息和加入先验信息)三个模型在同一个汽车评论数据集上进行了效果的对比(数据因为某些原因无法公开,见谅)。
本章主要目的在于对比各个模型的实验效果,所以以代码和结论为主,涉及到相关的理论部分大家可以自行搜索,有很多讲的很好的文章。
主题模型原则上大多都是"无监督学习",所以想要对比各个模型效果的话,肯定需要一套统一的评价指标。
在LDA中提出了perplexity(困惑度)的评价方法,这个方法存在两个问题:
后来Lau等人把这些方法进行了汇总,本文就用的其中效果好且相对稳定的"NPMI"值(归一化点互信息)来进行评估。详见:代码工具地址
LDA模型在python中比较常用的包有两个,一个就是原生态的lda,执行命令即可安装:
pip install lda
这个lda模型在训练的时候用的是gibbs采样。
另一个就是sklearn中的LatentDirichletAllocation,这个lda模型训练用的是EM算法。
直接上代码及最终效果评估结果:
import numpy as np
import lda
import os
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.externals import joblib
'''
须知,以下代码若无特殊说明,理解同下:
1.doc_list是进行分词后的语料,格式如下:
doct_list = ['今天 天气 不错 ', '我 想 玩 游戏']
2.stop_words_list是中文停用词表,格式如下:
stop_words_list = ['为了', '为什么么']
'''
#加载语料onehot化的工具
tf_vectorizer = CountVectorizer(max_df=0.95, min_df=2,
#max_features=1500,
stop_words=stop_words_list)
tf = tf_vectorizer.fit_transform(doc_list)
#加载lda模型
model = lda.LDA(n_topics=20, n_iter=500, random_state=1)
model.fit(tf) # model.fit_transform(X) is also available
joblib.dump(model, './model/lda_1.model')
#获取训练后的主题词矩阵
topic_word = model.topic_word_ # model.components_ also works
n_top_words = 8
vocab = tf_vectorizer.get_feature_names()
f_topic = open('result/lda_Gibbs.topic_words.res', 'w')
#打印每个主题topK相关性的主题词
for i, topic_dist in enumerate(topic_word):
topic_words = np.array(vocab)[np.argsort(topic_dist)][:-(n_top_words+1):-1]
print('Topic {}: {}'.format(i, ' '.join(topic_words)))
f_topic.write(' '.join(topic_words)+'\n')
f_topic.close()
没有 配置 倒车 座椅 影像 雷达 电动 后视镜 [0.24]
导航 没有 自动 启动 钥匙 功能 一键 不能 [0.22]
油耗 公里 满意 左右 高速 市区 磨合期 现在 [0.19]
内饰 做工 塑料 比较 有点 细节 粗糙 用料 [0.18]
外观 喜欢 外形 好看 大气 内饰 时尚 设计 [0.17]
满意 没有 地方 有点 一点 目前 发现 要说 [0.16]
噪音 有点 隔音 声音 发动机 减震 胎噪 比较 [0.15]
价格 没有 保养 4s店 优惠 便宜 配件 销售 [0.15]
大灯 天窗 没有 轮毂 全景 车灯 轮胎 led [0.14]
动力 起步 提速 发动机 空调 加速 超车 感觉 [0.13]
有点 起步 顿挫 变速箱 刹车 感觉 声音 发动机 [0.13]
空间 后排 座椅 舒服 后备箱 比较 感觉 乘坐 [0.12]
异响 问题 有点 知道 车门 小毛病 不好 解决 [0.12]
发动机 底盘 品牌 四驱 越野 suv 性能 安全 [0.12]
没有 后排 不能 座椅 后备箱 有点 设计 储物 [0.10]
外观 空间 内饰 动力 大气 漂亮 配置 油耗 [0.09]
操控 不错 非常 动力 底盘 空间 起来 驾驶 [0.09]
配置 性价比 空间 外观 满意 价格 不错 比较 [0.07]
满意 感觉 一点 觉得 这车 车子 一个 真的 [0.06]
空间 方便 满意 不错 适合 油耗 比较 很大 [0.03]
==========================================================================
Average Topic Coherence = 0.133
Median Topic Coherence = 0.131
每行是一个主题top 8的主题词,以及该主题的一致性得分。
最后两行是 得分平均值 和 中位数。
from sklearn.externals import joblib
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
tf_vectorizer = CountVectorizer(max_df=0.95, min_df=2,
#max_features=1500,
stop_words=stop_words_list)
tf = tf_vectorizer.fit_transform(doc_list)
lda = LatentDirichletAllocation(n_components=20,
max_iter=500,
learning_method='batch',
n_jobs = -1,
verbose=True)
lda.fit(tf) # tf即为Document_word Sparse Matrix
joblib.dump(lda, 'model/LDA_sklearn_main.model')
def print_top_words(model, feature_names, n_top_words):
# 打印每个主题下权重较高的term
for topic_idx, topic in enumerate(model.components_):
topic_words = " ".join([feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]])
print("Topic #%d:" % topic_idx)
print(topic_words)
print()
# 打印主题-词语分布矩阵
print(model.components_)
f_topic.close()
n_top_words = 8
tf_feature_names = tf_vectorizer.get_feature_names()
print_top_words(lda, tf_feature_names, n_top_words)
没有 倒车 影像 雷达 配置 居然 导航 加装 [0.25]
自动 没有 功能 钥匙 启动 后视镜 一键 不能 [0.25]
座椅 后排 没有 舒服 不能 调节 电动 中间 [0.20]
油耗 公里 左右 高速 市区 磨合期 偏高 满意 [0.19]
噪音 发动机 声音 隔音 有点 效果 轮胎 胎噪 [0.18]
内饰 塑料 中控 比较 做工 用料 设计 中控台 [0.17]
外观 大气 漂亮 内饰 好看 空间 满意 时尚 [0.14]
动力 起步 提速 发动机 加速 不错 感觉 超车 [0.13]
空间 后排 后备箱 很大 舒适 不错 乘坐 比较 [0.13]
满意 地方 一点 没有 车子 目前 感觉 要说 [0.12]
天窗 导航 全景 手机 配件 售后 蓝牙 维修 [0.12]
车身 减震 悬挂 底盘 稳定 舒适性 颠簸 感觉 [0.11]
做工 大灯 不够 细节 轮毂 缝隙 led 晚上 [0.11]
问题 知道 没有 小毛病 异响 提车 4s店 现在 [0.11]
配置 性价比 底盘 不错 安全 比较 级别 价位 [0.09]
异响 刹车 有点 顿挫 不好 感觉 变速箱 容易 [0.09]
喜欢 觉得 选择 这车 个人 好看 感觉 比较 [0.08]
价格 省油 音响 性价比 便宜 不错 优惠 毕竟 [0.07]
有点 新车 重要 感觉 稍微 味道 一点 一个 [0.07]
外形 回头率 适合 性能 比较 停车 越野 四驱 [0.06]
==========================================================================
Average Topic Coherence = 0.134
Median Topic Coherence = 0.120
可以看到,虽然LDA模型训练算法不同,但最终的得分其实差不太多。
这个模型就不放代码了,因为我用的开源代码核心逻辑是C++的,只能在数据处理阶段尽可能和LDA保持一致,列一下项目的代码地址和评估结果:
ps:这个模型也有一个python的开源代码,但我用了下速度太慢了,基本不可用,所以最终用的是原作者的开源代码。
自动 没有 导航 功能 不能 启停 手机 启动 [0.19]
没有 配置 倒车 座椅 影像 雷达 天窗 钥匙 [0.19]
油耗 公里 满意 高速 左右 市区 现在 目前 [0.17]
没有 4s店 价格 保养 销售 优惠 4s 配件 [0.15]
外观 内饰 大气 时尚 漂亮 满意 设计 喜欢 [0.14]
动力 发动机 油耗 超车 加速 不错 提速 高速 [0.13]
问题 异响 没有 出现 知道 发现 有点 现在 [0.12]
座椅 后排 空间 没有 有点 不能 舒服 调节 [0.12]
内饰 做工 塑料 有点 比较 细节 用料 感觉 [0.12]
噪音 声音 有点 发动机 隔音 感觉 异响 比较 [0.10]
有点 起步 感觉 动力 顿挫 发动机 变速箱 比较 [0.09]
没有 空间 有点 后备箱 地方 后排 满意 储物 [0.09]
空间 后排 满意 后备箱 很大 比较 不错 非常 [0.07]
配置 空间 性价比 外观 比较 不错 价格 油耗 [0.07]
空间 外观 不错 动力 非常 满意 内饰 比较 [0.06]
外观 喜欢 满意 好看 比较 外形 感觉 内饰 [0.06]
满意 没有 有点 感觉 地方 油耗 一点 车子 [0.06]
没有 有点 空调 新车 感觉 味道 满意 座椅 [0.05]
大灯 没有 有点 轮胎 悬挂 感觉 底盘 轮毂 [0.05]
外观 空间 满意 动力 重要 感觉 内饰 三遍 [0.04]
==========================================================================
Average Topic Coherence = 0.104
Median Topic Coherence = 0.097
看起来效果不太理想,个人猜测可能是因为我的评论数据大多数都不太短,而BTM应该适用于一句话(比如弹幕)的语料。
项目代码地址
tf_vectorizer = CountVectorizer(max_df=0.95, min_df=2,
#max_features=1500,
stop_words=stop_words_list)
tf = tf_vectorizer.fit_transform(doc_list)
# Word labels for each column can be provided to the model
vocab = tf_vectorizer.get_feature_names()
# Train the CorEx topic model
#if not os.path.exists('./model/corex_topic.model'):
topic_model = ct.Corex(n_hidden=20,
max_iter=500,
verbose=1) # Define the number of latent (hidden) topics to use.
topic_model.fit(tf, words=vocab)#, anchors = anchor_words)
joblib.dump(topic_model, './model/corex_topic_no_prior.model')
#test
topics = topic_model.get_topics(n_words=8)
for topic_n,topic in enumerate(topics):
words,mis,_ = zip(*topic)
topic_str = str(topic_n)+': '+' '.join(words)
print(topic_str)
油耗 高速 市区 左右 偏高 百公里 个油 平均 [0.30]
倒车 自动 影像 雷达 启动 一键 钥匙 启停 [0.28]
内饰 做工 粗糙 用料 精致 精细 豪华 简单 [0.24]
塑料 有待 装配 缝隙 工艺 提高 车漆 轮胎 [0.22]
座椅 天窗 全景 电动 调节 大灯 后视镜 真皮 [0.21]
价格 实惠 便宜 经济 配件 质量 代步 保有量 [0.19]
空间 乘坐 宽敞 储物 后备箱 舒适 很大 超大 [0.19]
配置 安全 丰富 齐全 车身 稳定 科技 四轮 [0.17]
公里 现在 目前 磨合期 销售 保养 多公里 200 [0.17]
省油 足够 家用 家庭 省心 耐用 好开 皮实 [0.17]
动力 超车 强劲 提速 充沛 加速 够用 不足 [0.16]
没有 导航 居然 加装 雾灯 车门 竟然 简配 [0.13]
新车 知道 希望 厂家 4s店 是不是 提车 可能 [0.13]
声音 发动机 起步 明显 减震 顿挫 油门 隔音 [0.12]
发现 问题 暂时 地方 缺点 接受 情况 没什么 [0.12]
影响 不能 视线 容易 有时候 位置 离合器 太高 [0.09]
外观 大气 漂亮 时尚 有点 外形 性价比 不错 [0.08]
喜欢 重要 suv 线条 好看 三遍 设计 车型 [0.07]
满意 感觉 觉得 一点 这车 要说 很多 一个 [0.07]
后排 中间 方便 身高 涡轮 舒服 东西 坐在 [0.07]
==========================================================================
Average Topic Coherence = 0.158
Median Topic Coherence = 0.161
anchor_words = [['噪音'],
['空间'],
['外观'],
['油耗'],
['自动'],
['内饰'],
['缝隙'],
['电动'],
['价格'],
['配置'],
['省油']]
topic_model.fit(tf, words=vocab, anchors = anchor_words)
油耗 公里 市区 左右 磨合期 高速 百公里 个油 [0.30]
倒车 自动 没有 导航 影像 雷达 钥匙 一键 [0.25]
空调 夏天 制冷 视线 冬天 暖风 压缩机 天气 [0.24]
座椅 后排 调节 电动 不能 后视镜 中间 前排 [0.24]
内饰 做工 塑料 粗糙 用料 设计 精致 细节 [0.23]
外观 大气 漂亮 时尚 外形 好看 喜欢 霸气 [0.19]
重要 三遍 轮毂 事情 17 运动 18 铝合金 [0.18]
空间 宽敞 储物 乘坐 后备箱 舒适 很大 足够 [0.18]
配置 全景 天窗 丰富 安全 性价比 级别 esp [0.17]
动力 操控 超车 强劲 不错 性能 提速 精准 [0.17]
助力 转向 方向 使用 方便 操作 前置 前驱 [0.15]
声音 发动机 噪音 顿挫 明显 异响 变速箱 起步 [0.15]
需要 慢慢 每次 适应 习惯 接受 经常 加油 [0.14]
满意 地方 要说 一点 目前 没什么 应该 一个 [0.14]
问题 新车 有待 希望 知道 提车 厂家 4s店 [0.12]
价格 保养 选择 便宜 suv 买车 品牌 合资 [0.11]
觉得 个人 东西 坐在 很多 里面 其实 最后 [0.10]
有点 暂时 发现 后轮 太小 不太 喇叭 鼓刹 [0.09]
后面 之前 特别 平时 不会 面包车 出去 比较 [0.07]
感觉 可能 现在 原因 车子 起来 驾驶 开车 [0.07]
==========================================================================
Average Topic Coherence = 0.165
Median Topic Coherence = 0.163
可以看出来corex_topic模型的效果比LDA和BTM又要好一些,而且加入先验知识后的评分又略微提升(先验知识是我自己根据各个主题的关键词所概括的,并不是最合理的)。
在这个语料上,我们可以看到,如果按topic coherence作为评估标准的话:
corex_topic>LDA>BTM
但实际运用中,肯定还要结合具体的业务和数据,因为无监督模型本身就有一些“玄学”,不经过具体实验也很难断定哪个模型是最合适的。
另外,相关文献我就不一一列举了,都可以在开头提到的知乎回答中找到对应的文献。