127在线民宿 UGC 数据挖掘实战--基于词向量的主题聚类挖掘

基于词向量的主题聚类挖掘

数据准备

参考 《旅游民宿基本要求与评价》 标准中的评级指标辅助定义用户评价主题,本次实验将使用基于 Word2Vec 和 KMeans 主题词聚类的方式研究顾客评论中的主题分布情况。


image.png

使用 Pandas 加载在线数据表格,并查看数据维度和第一行数据。

import pandas as pd
data = pd.read_csv('https://labfile.oss.aliyuncs.com/courses/2628/1-1.csv')
print(data.shape)
data.head(1)

数据属性如下表所示


image.png

训练词向量

# 加载词性标注模块
import jieba.posseg
# 加载词向量训练模块
from gensim import models, similarities
# 添加 notebook 可视化模块
from tqdm.notebook import tqdm

jieba 词性标注预热,使用 jieba 中的词性标注模块对输入的句子进行处理,产生每个词和对应的词性。

list(jieba.posseg.cut('垃圾饭店'))

数据预处理
批量对用户评论分词和词性标注,对每一句用户评论进行词性标注并选取其中长度大于 1 的名词,此步骤比较耗时,可以通过进度条观察处理情况。

# 收集用户评论中的长度大于 1 的名词
n_word = list()
# 将用户评论转换为 list 的形式
sentence_list = data['content'].tolist()
# 实时显示处理状态
for sentence in tqdm(sentence_list):
    tmp = list()
    for word, flag in jieba.posseg.cut(sentence):
        if 'n' in flag and len(word) > 1:
            tmp.append(word)
    n_word.append(tmp)

词向量训练
Word2Vec 是 Google 的一个开源工具,可以把对文本内容的处理简化为向量空间中的向量运算,通过计算出词与词之间的余弦值,计算出两两向量在空间上的相似度,并以此来表示文本语义上的相似度。我们利用 gensim 中的 Word2Vec 建立词向量模型,对上一步提取的词进行词向量训练,训练词语在空间上的表达。

# size 为词向量维度数,min_count 为统计词频出现的最小词频数,其余参数使用默认
%time model = models.word2vec.Word2Vec(n_word, size=300, min_count=1)

词向量使用
我们看出与“环境”相近的词语。词向量受到训练语料的限制,语料越大、模型效果越好,大家自行可以使用更大的语料进行训练和使用。

# 输入测试用例
keys = '环境'
topn = 5
# most_similar 就是通过找到词语向量并计算向量余弦相似度,找到最近的 topn 的词语
model.wv.most_similar(positive=[keys], topn=topn)

尝试使用字典中的一个名词来提取对应的词向量,并打印词向量维度信息。

words = '早餐'
model.wv[words].shape

批量进行提取词向量,直接传入字典的 list 即可,我们发现字典中总共有 3119 个词语。

# 提取全部的词
words_list = list(model.wv.vocab.keys())
word_vector = model.wv[words_list]
# 打印主题词典的个数
len(word_vector)

KMeans 主题聚类

聚类是一个将数据集中在某些方面相似的数据成员进行分类组织的过程,聚类就是一种发现这种内在结构的技术。本次实验我们使用 KMeans 算法对训练出来的词向量进行聚合,聚合出来的词语簇为一个主题,它是一种迭代求解的聚类分析算法,首先将数据分为 K 组,随机选取 K 个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心,聚类中心以及分配给它们的对象就代表一个聚类。
聚类个数选取
KMeans 算法需要一个初始的聚类个数作为启动聚类的参数,本实验将研究聚类点个数对内部聚类结构的影响规律来寻找最佳聚类个数。

# 加载 KMeans 聚类算法模块
from sklearn.cluster import KMeans

# 加载画图模块
import matplotlib.pyplot as plt
%matplotlib inline

判断合适的 KMeans 聚类点时,最常用的是手肘法。随着聚类个数 k 的增大会大幅增加每个簇的聚合程度,故 SSE(误差平方和) 的下降幅度会很大,而当 k 到达真实聚类数时,再增加 k 所得到的聚合程度回报会迅速变小,所以 SSE 开始的下降幅度会很大,然后随着 k 值的继续增大而趋于平缓,变化关系是类似手肘的形状,而这个肘部对应的 k 值就是数据的真实聚类数。我们通过设置最大的聚类点 cluster_max,利用模型迭代产生的误差平方和,来判断合适的聚类点。通过控制变量的方式选取最佳聚类中心,通过结果趋势图看出,最佳的聚类数在 4 或者 5 附近,后续的主题个数选取可以选择其中的数值进行试验即可。需要耗费一些时间,请耐心等待。

# SSE 是所有样本的聚类误差,代表了聚类效果的好坏,sse_error_list 存放每次结果的误差平方和
sse_error_list = list()
# 设置最大的聚类中心点个数

cluster_max = 20
# 实时显示聚类训练的情况

for clusters in tqdm(range(1, cluster_max + 1)):
    # 构造聚类器,只改变聚类个数,其余均使用默认配置
    clf = KMeans(n_clusters=clusters)
    # 开始训练模型
    clf.fit(word_vector)
    sse_error_list.append(clf.inertia_)

# 横坐标表示聚类个数
plt.xlabel('topic_cluster')

# 纵坐标表示误差
plt.ylabel('error')

# 对聚类点进行显示设置
plt.plot(sse_error_list, '*-')

# 对聚类结果进行可视化
plt.show()

自动化聚类个数选取
通过肘部图观察最佳的聚类个数需要比较专业的经验和人工参与,不适合自动化的聚类。我们简化此自动化选择聚类个数的过程,首先求得 SSE 的均值,利用小于均值的策略也可以选择聚类个数。

sse_error_mean = sum(sse_error_list) / len(sse_error_list)
sse_error_mean

我们可以通过使用简单的聚类个数判断策略来找到最佳聚类点。比如我们使用判断当某个聚类个数下的 SSE 的数值小于均值,即选择此聚类个数的下一个聚类个数作为最佳聚类点。

# 定义处理函数
def get_best_clusters(sse_error_list):
    # 通过均值来判断肘部的点,index 从 0 开始
    for index, value in enumerate(sse_error_list):
        if sse_error_mean > value:
            # 返回不超过均值点的下一个聚类索引即可
            return index + 2

求出最佳的聚类个数。

best_topic_cluster = get_best_clusters(sse_error_list)
best_topic_cluster

训练主题聚类模型
我们使用上述自动化方式求得的聚类个数作为初始化的参数,开始对词向量进行主题聚类。

# 使用最佳的聚类个数初始化模型,其余使用默认设置
clf = KMeans(n_clusters=best_topic_cluster)

开始进行训练,并产生对每个词的主题预测的聚类类别。

topic_cluster_labels = clf.fit_predict(word_vector)

输出聚类中心的坐标,因为有 5 个聚类中心,所以显示 5 个聚类中心的向量。

cluster_centers_list = clf.cluster_centers_
len(cluster_centers_list)

通过查询每个词的向量和聚类类别,通过与聚类中心进行欧式距离计算,批量计算每个词向量到最近聚类中心点的欧式距离。

import numpy as np
# 存储计算的欧式距离
cal_EuclideanDistance = list()

for index, words in tqdm(list(enumerate(words_list))):
    # 取出词的词向量
    words_vec = model.wv[words]
    # 取出词的聚类中心点的向量
    center_vec = cluster_centers_list[topic_cluster_labels[index]]
    # 计算词向量到聚类中心点的欧式距离
    dist = np.sqrt(np.sum(np.square(words_vec - center_vec)))
    cal_EuclideanDistance.append(dist)

将上述计算的主题词、聚类标签、与中心词的欧式距离设置好列名写入 DataFrame,并打印前 5 行。

# 设置 DataFrame 的列名
topic_cluster_data = pd.DataFrame({
    'words': words_list,
    'topic_cluster': topic_cluster_labels,
    'euclidean_distance': cal_EuclideanDistance
})

# 显示前几行数据
topic_cluster_data.head()

主题聚合

主题的具体意义可以按照中心词进行抽象,我们下面打印聚类主题的抽象标签。

topic_cluster_data['topic_cluster'].unique()

对不同编号下的主题进行聚合,我们可以发现不同主题下的评价数量的差异,以此挖掘用户比较关注的主题。

topic_cluster_data['topic_cluster'].groupby(
    topic_cluster_data['topic_cluster']).count()

分别统计不同 topic lcluster 下的前 topn 个聚类中心点最近的词语。

# 按照词与中心点的欧式距离进行降序排列
def get_top_cluster_words(topic_cluster_labels, topn=20):
    # 定义主题和主题词的存储结构
    cluster_result = dict()

    # 分别处理每个主题下的主题词并按照主题词与主题中心的欧式距离尽心倒序处理
    for topic_cluster_label in topic_cluster_labels:
        topic_select = topic_cluster_data[topic_cluster_data['topic_cluster'] ==
                                          topic_cluster_label].sort_values(
            by=["euclidean_distance"],
            ascending=False)
        # 输出每个主题下的 topn 的主题词
        cluster_result[topic_cluster_label] = topic_select['words'].tolist()[
            :topn]
    return cluster_result

产生主题序号,抽象表示用户评价主题。

topic_cluster_labels_list = list(range(best_topic_cluster))
topic_cluster_labels_list

计算在每一个聚类中心附近的聚类主题词,并按照欧式距离进行倒排,中心点既是排在第一个词语,通过同一主题下的主题词来总结此主题的具体意思。但是由于本实验使用的语料过小,此效果随着词向量的规模而变化,训练语料越大,主题聚类效果越好,后续还可以直接使用第三方训练好的词向量模型进行中心词向量化。

# 设定每一个主题显示 topn 的词语
topn = 30
topic_result = get_top_cluster_words(topic_cluster_labels_list, topn=topn)
# 打印每个主题下的词语
for topic_num, topic_words in topic_result.items():
    print('主题 # {}- 主题词: {}'.format(topic_num, topic_words))

我们得出主题聚类的目就是扩充主题词典,并以此挖掘用户兴趣。扩充后的主题词典如下所示:

topic_words_list = {
    '环境': [
        '环境', '周边', '风景', '空气', '江景', '小区', '景点', '夜景', '街', '周围', '景区', '声音',
        '景色'
    ],
    '价格': ['价格', '房价', '性价比', '价位', '单价', '价钱'],
    '特色': ['特色', '装潢', '布置', '建筑', '结构', '格调', '装修', '设计', '风格', '隔音'],
    '设施': [
        '设施', '设备', '条件', '硬件', '房间', '热水', 'WiFi', 'wife', '电梯', '阳台', '卫生间',
        '洗手间', '空调', '垃圾桶', '通风'
    ],
    '餐饮': ['餐饮', '早餐', '咖啡', '味道', '饭', '菜', '水果'],
    '交通': ['交通', '车程', '地段', '路程', '停车', '客运站', '马路'],
    '服务': ['服务', '态度', '前台', '服务员', '老板', '掌柜', '店家', '工作人员'],
    '体验': ['体验', '整体', '感觉'],
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容