在Jupyter Notebook中使用word2vec和k-means进行词聚类

2021-11-25 15:32| 发布者: Fuller| 查看: 8838| 评论: 1

摘要: 1,本Notebook背景介绍通常我们对文档进行聚类,目的是:面对一堆文档,想自动划分成N类。其实也可以对一堆词聚类,目的是:面对一堆词,想自动划分成N类。可见,这两件事是类似的,其中,N这个数字可以自己定,当然 ...

1,本Notebook背景介绍

通常我们对文档进行聚类,目的是:面对一堆文档,想自动划分成N类。

其实也可以对一堆词聚类,目的是:面对一堆词,想自动划分成N类。

可见,这两件事是类似的,其中,N这个数字可以自己定,当然,多少最适合要观察聚类结果进行调整。而这两件事能够成行都依赖于被聚类对象的表示方法。通常,我们把被聚类对象表示成m维空间中的点,在空间中,距离相近的显然可以聚类在一起。

自从有了word2vec算法,就能将词转换成一个m维向量,从而可以利用聚类算法进行聚类了。本notebook罗列了利用Word2vec和k-means算法所需的python代码,大家可以下载这个notebook上增加自己的实验代码,比如,调整一下word2vec的window参数,看看是否对结果有好的影响。

此前,《Jupyter Notebook使用Gensim库做中文Word2Vec模型计算》和《机器学习库sklearn的K-Means聚类算法的使用方法》已经分别讲解了word2vec和k-means,但是,不断有同学询问,是否能有一个完整的notebook,进行词聚类,所以,就产生了本notebook。

下面的代码所处理的数据是由GooSeeker文本分词和情感分析软件生成的,这次实验的数据是用GooSeeker网络爬虫爬取了知乎上的一个话题,这个话题讨论了90年代下岗。GooSeeker文本分析软件能将爬到的文本进行分词,而且提供了一个选词界面,允许用户手工选词,这样可以让分析的范围更加精准。

那么,在运行本notebook之前,需要这些步骤准备数据(本notebook已经包含了实验数据,下面这5步不需执行):

1. 用GooSeeker网络爬虫爬取需要的话题

2. 将采集到的数据导入GooSeeker分词软件,创建分词和文本分析任务

3. 手工选词

4. 导出分词效果表和选词结果表

5. 放在本notebook的data/raw文件夹,就可以执行本notebook的python程序了。

其中:

分词效果表展示了每个文本文档被分词以后的样子,就是每个词用空格间隔开了。本notebook将使用分词效果表中的数据训练word2vec模型,得到所有词的向量。

选词结果表存了要被聚类的词,本notebook将根据这个表,从所有词向量中找到这些被选词,用他们训练k-means模型,并得到聚类结果

本notebook的python代码主要执行了下面的步骤:

1. 从分词效果表加载语料库

2. 训练word2vec模型

3. 查看和输出模型

4. K-means聚类

5. 展示聚类结果

2,词聚类的步骤总结

上面讲解了原理,那么,在一个实际场景中进行词聚类,我们总结成以下步骤:

1. 使用GooSeeker文本分词和情感分析软件进行分词,分词得到的“分词效果表”

2. 在GooSeeker文本分词和情感分析软件中选词,得到“选词结果表”。如果不想精选词,直接对所有词聚类也行。

3. 从分词工具导出分词效果表文件“分词效果_20xxxxxxxxxxxxxxxxx.xlsx”和选词结果表文件“选词结果_20xxxxxxxxxxxxxxxxx.xlsx”,放入本notebook的data/raw/目录下

4. 运行本Jupyter Notebook,将会使用Pandas把分词结果数据读出来,转成csv文件

5. 利用gensim库中的读取csv文件的函数生成sentences数据结构

6. 将sentences数据结构交给word2vec模型

7. 查询出所选词的向量,对数据进行观察

8. 将所选词的向量构成一个矩阵,使用K-Means进行聚类

3,第三方库

本notebook使用了gensim库和sklearn库,gensim库用于Word2vec训练,sklearn库做k-means算法计算。

如果未安装,请先使用下面的命令安装gensim库和sklearnm库,再运行实验本notebook:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gensim

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple sklearn


4, 准备运算环境

4.1,开启日志输出

把实验过程中的日志信息直接在Jupyter Notebook中输出,对于初学者来说,有助于掌握word2vec的算法和gensim的计算原理。熟练以后,可以把这一段删除。

import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)


4.2,引入需要用到的库

引入gensim库下的models,datapath,utils。其中datapath是便于操作磁盘文件的程序库,而uitils中一个使用csv文件的函数可以方便我们生成word2vec需要的sentences数据结构。

引入sklearn库, 导入sklearn下的K-means模块。sklearn,全称scikit-learn,是python中的机器学习库,建立在numpy、scipy、matplotlib等数据科学包的基础之上,涵盖了机器学习中的样例数据、数据预处理、模型验证、特征选择、分类、回归、聚类、降维等几乎所有环节,功能十分强大,目前sklearn版本是0.23。

from gensim.test.utils import datapath

from gensim import utils

import gensim.models

from sklearn.cluster import KMeans


5,生成word2vec需要的数据结构

按照gensim的官方文件,sentences是一个iteratable的数据结构即可,而且要求可以rewind的,不能是iterator生成器生成的只能迭代一次的结构。我们使用utils里面的一个函数,可以把csv文件直接读进来生成sentences数据结构表示的语料库。所以,首先要将GooSeeker分词软件生成的excel文件中的分词结果数据转换成csv文件。

【注意】生成了中间文件不是唯一选择,只是一种编程便利方式,可能运行时会稍微慢一点,因为要从pandas转成csv,还要写硬盘。

5.1,准备好pandas

导入必要的Python程序包,并且设定文件目录变量。原始数据放在raw文件夹,而处理好的数据放在processed文件夹。这里的原始数据就是从GooSeeker分词软件导出的分词效果表;而处理好的数据就是生成的csv文件。

import pandas as pd

import os

import time

%xmode Verbose

import warnings

warnings.filterwarnings("ignore", category=DeprecationWarning)

# 存原始数据的目录

raw_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\raw')

# 存处理后的数据的目录

processed_data_dir = os.path.join(os.getcwd(), '..\\..\\data\\processed')

filename_temp = pd.Series(['词频','分词效果','选词矩阵','选词匹配','选词结果','共词矩阵'])

file_word_freq = ''

file_seg_effect = ''

file_word_choice_matrix = ''

file_word_choice_match = ''

file_word_choice_result = ''

file_co_word_matrix = ''


5.2,检测data\raw目录下是否有分词效果表

在我们发布的一系列Jupyter Notebook中,凡是处理GooSeeker分词软件导出的结果文件的,都给各种导出文件起了固定的名字。为了方便大家使用,只要把导出文件放在data/raw文件夹,notebook就会找到导出文件,赋值给对应的文件名变量。下面罗列了可能用到的文件名变量:

file_word_freq:词频表

file_seg_effect: 分词效果表

file_word_choice_matrix: 选词矩阵表

file_word_choice_match: 选词匹配表

file_word_choice_result: 选词结果表

file_co_word_matrix: 共词矩阵表

【注意】本notebook只使用分词效果表和选词结果表,下面的代码将检查data/raw中有没有分词效果表和选词结果表,如果没有会报错,后面的程序就没法执行了。

# 0:'词频', 1:'分词效果', 2:'选词矩阵', 3:'选词匹配', 4:'选词结果', 5:'共词矩阵'

print(raw_data_dir + '\r\n')

for item_filename in os.listdir(raw_data_dir):

    if filename_temp[1] in item_filename:

        file_seg_effect = item_filename

        continue

    if filename_temp[4] in item_filename:

        file_word_choice_result = item_filename

        continue

if file_seg_effect:

    print("分词效果excel表:", "data\\raw\\", file_seg_effect)

else:

    print("分词效果excel表:不存在")

if file_word_choice_result:

    print("选词结果excel表:", "data\\raw\\", file_word_choice_result)

else:

    print("选词结果excel表:不存在")


输出显示:

C:\Users\work\workspace_219\notebook\Jupyter Notebook对选词结果进行聚类试验\notebook\eda\..\..\data\raw

分词效果excel表: data\raw\ 分词效果_202111171002345970.xlsx

选词结果excel表: data\raw\ 选词结果_202109291141532740.xlsx


5.3,读取分词效果表和选词结果表

df_seg_effect = pd.read_excel(os.path.join(raw_data_dir, file_seg_effect))

df_word_choice_result = pd.read_excel(os.path.join(raw_data_dir, file_word_choice_result))

# 分别获取分词效果表的分词数据和选词结果表的标签词

df_seg_effect_index = df_seg_effect["分词数据"]

df_word_choice_tag = df_word_choice_result['标签词']


5.4,查看excel表前10行数据

df_seg_effect_index.head(10)


输出结果显示:

0    我 听 我 爸 说 当时 我们 这 边 的 下岗 的 人 好多 都 找 不 到 工作 吃不上...

1                                         一切都在 在 变化 之中

2    上 了 些 年纪 所以 不幸 是 亲历者 看 了 各 答 后 做 一旁 补 大 下岗 之前 ...

3    原因 很 简单 经济 要好 国家 要 强大 大家 就 要 好好 工作 不管 你 怎么 说 事...

4    看 完 这个 答案 我 才 明白 原来 当年 国企 里面 养 了 一 厂 的 王进喜 啊 各...

5    下岗 潮 的 一大 后果 就是 生产力 被 重创 朱 执政 有 一 年 纺织品 价格 翻 了...

6    当时 我 外公 在 一家 军工厂 当 一把手 那段 时间 订单 急剧 萎缩 厂里 连续 几 ...

7                                  对 国营企业 施行 的 抓大放小 政策

8                         那个 人 当年 的 预言 和 担心 通通 变为 了 现实

9    那年 下岗 听 闻 很多 事 有 一家 人 父母 都 下岗 了 当时 他们 一家 人 很久没...

Name: 分词数据, dtype: object


df_word_choice_tag.head(10)

输出结果显示:

0    下岗

1    没有

2    企业

3    时候

4    工人

5    国家

6    工作

7    问题

8    知道

9    可以

Name: 标签词, dtype: object


5.5,分别获取分词效果表的分词数据和选词结果表的标签词

我们使用“分词数据”这一列,作为模型实验的语料库。可以看到“分词数据”这列的数据是分词后的效果,每个分开的词之间用空格间隔。

5.6,把“分词数据”列保存到csv文件

corpus_path = os.path.join('../../data/processed', 'corpus.csv')

df_seg_effect_index.to_csv(corpus_path,index=False, header=False ,encoding="utf-8")


6,构建语料库

6.1,定义SegEffectCorpus语料库类

定义一个python类,为了今后方便使用。这个类就是把前面生成的csv文件的每一行读进来,一行是一个文档,所有行构建成word2vec需要的语料库数据结构。

class SegEffectCorpus:

    """An iterator that yields sentences (lists of str)."""

    def __iter__(self):

        for line in open(corpus_path, encoding='UTF-8'):

            # 每行一篇文档,每个文档由空格分隔的多个词组成

            # there's one document per line, tokens separated by whitespace

            yield utils.simple_preprocess(line)


6.2,生成语料库

一行一个文档,这里面有所有行,命名为sentences。

sentences = SegEffectCorpus()


7,基于句子训练word2vec模型

训练时,句子(sentences)参数必须指定

除此之外,还有其它几个可选参数:

corpus_file (str, optional) – LineSentence格式的语料库文件路径。

window (int, optional) – 一个句子中当前单词和被预测单词的最大距离。

vector_size (int, optional) – word向量的维度。

min_count (int, optional) – 忽略词频小于此值的单词。

workers (int, optional) – 训练模型时使用的线程数。

更加详细的可以前往官网查看:models.word2vec – Word2vec embeddings — gensim

model = gensim.models.Word2Vec(sentences=sentences, window=3, vector_size=500)


7.1,从所有词的向量中找出所选词的向量

import numpy as np

tag_vec = []

for index, tag in df_word_choice_tag.items():

    try:

        vec_cameroon = model.wv[tag]

        tag_vec.append(vec_cameroon)

    except KeyError as e:

        print(e)

tag_vec = np.array(tag_vec)

print(tag_vec)

输出结果显示:

[[ 0.28006706  0.35687545  0.34631252 ... -0.4566334  -0.42030814

  -0.21405639]

 [ 0.27025273  0.32620966  0.3126129  ... -0.42959076 -0.39224032

  -0.1812565 ]

 [ 0.21612883  0.23300147  0.14998631 ... -0.32195064 -0.30174166

  -0.06214559]

 ...

 [ 0.12504356  0.11681405  0.08627358 ... -0.18008293 -0.18297078

  -0.02731059]

 [ 0.10030089  0.0950103   0.06754703 ... -0.144793   -0.1554194

  -0.01663852]

 [ 0.09820881  0.09028573  0.06390575 ... -0.13971356 -0.14736614

  -0.0131916 ]]


8,K-means聚类

使用sklearn的KMeans模块进行聚类分析,可以设置要聚几类。

此处k设置为10

n_clusters=10

# 建立模型。n_clusters参数用来设置分类个数,即K值,这里表示将样本分为10类。

cluster = KMeans(n_clusters=n_clusters,random_state=0).fit(tag_vec)


8.1,使用labels_查看聚好的类别

y_pred = cluster.labels_

print(y_pred)

输出结果显示:

[5 5 3 5 5 4 1 5 5 4 3 4 5 3 3 5 1 1 5 1 3 5 3 4 5 1 1 4 1 3 1 1 4 4 1 1 5

 4 3 1 4 1 1 4 1 3 3 1 1 1 1 4 4 4 4 3 1 1 1 4 4 1 1 4 1 3 3 4 1 3 4 3 4 4

 4 3 1 4 4 3 4 4 4 3 3 1 4 3 3 4 8 4 4 1 4 4 1 3 3 4 4 4 3 3 8 4 3 3 4 4 4

 3 4 3 3 4 4 9 4 9 4 9 3 3 9 4 8 4 8 8 8 9 3 3 9 9 9 9 3 3 8 3 4 8 8 9 8 9

 8 8 8 9 8 8 9 8 9 8 8 9 9 8 8 9 9 9 9 9 3 9 8 8 9 8 8 9 8 8 3 3 8 8 0 9 9

 0 8 8 9 9 8 3 0 0 0 8 0 8 8 9 9 9 8 8 6 9 8 9 8 6 0 9 9 0 0 9 8 6 8 0 0 9

 9 9 9 9 6 8 9 8 8 6 0 9 0 8 0 0 0 8 0 9 8 8 6 9 6 0 0 0 6 0 6 9 6 0 6 0 0

 0 6 0 0 6 6 0 9 6 6 9 0 6 6 6 6 6 9 6 6 6 2 0 6 6 0 6 0 6 6 6 6 0 0 6 0 0

 0 0 6 6 6 6 0 0 0 6 0 6 6 6 6 0 6 6 6 0 0 6 6 0 6 6 6 6 6 6 6 6 0 6 6 6 0

 0 0 2 0 6 0 6 6 6 0 0 0 6 6 6 0 2 0 2 6 6 0 6 6 2 7 0 0 7 2 2 6 7 6 6 2 6

 6 2 2 6 2 6 2 6 2 6 2 2 7 7 2 7 2 0 7 2 2 7 2 2 2 2 2 2 7 7 2 2 2 7 6 2 7

 7 2 2 6 2 2 7 2 2 2 7 2 2 2 2 2 7 2 7 2 2 7 2 2 7 7 2 7 7 2 7 2 2 2 7 2 7

 7 2 7 7 7 7 7 2 2 7 7 7 7 7 2 7 7 2 7 7 6 7 2 2 2 7 7 2 2 2 2 2 2 2 2 7 7

 7 7 2 7 7 7 7 7 7 7]


8.2,每个类包含的词的数量

quantity = pd.Series(y_pred).value_counts()

print(f"cluster聚类数量:\n{quantity}")

输出结果显示:

cluster聚类数量:

6    80

2    69

0    61

7    54

9    49

4    48

8    47

3    41

1    30

5    12

dtype: int64


8.3,获取聚类之后每个聚类中的数据

注意,word2vec每次训练得到的模型会有稍许变化,那么聚类出来的结果也会有些变化,这一次运算我看到6,2,0,7这些类含有的词最多,那么我们就看看是什么词。

n_category = 6

res = df_word_choice_tag[(y_pred == n_category)]

count = 0

print(f"类别为{n_category}的数据:")

for word in res:

    print(f"{word}\t",end="")

    count = count + 1

    if(count % 10 == 0):

        print(f"\n")

输出结果显示:

类别为6的数据:

盈利 煤矿 印象 同事 得到 创业 吃饭 上学 明白 事业

出生 所在 负责 没人 改变 家乡 年轻人 补偿 农民工 裁员

私有化 程度 背景 离开 工人下岗 退休金 淘汰 了解 县城 年纪

感受 一代人 朱镕基 老人 养老 纺织厂 转型 长大 技能 受到

到底 老百姓 年龄 学费 待遇 方面 角度 接受 支持 身体

系统 安排 承担 投资 在于 还好 享受 责任 思想 变化

老家 家属 事件 命运 状态 考虑 会计 生活费 抱怨 地位

事儿 估计 参加 样子 现实 意思 销售 小区 提到 女人


n_category = 2

res = df_word_choice_tag[(y_pred == n_category)]

count = 0

print(f"类别为{n_category}的数据:")

for word in res:

    print(f"{word}\t",end="")

    count = count + 1

    if(count % 10 == 0):

        print(f"\n")

输出结果显示:

类别为2的数据:

私人 赚钱 提供 超过 获得 文化 放弃 记忆 人才 都会

生产力 公有制 住房 集体 产生 小时 土地 粮食 坚持 评论

回去 钢铁 说法 老妈 处于 收购 年级 资本家 阶层 化肥

提出 说话 例子 反对 学生 照顾 姥爷 职业 需求 老婆

没法 下降 石油 优势 生存 实际 电视 集团 成都 代价

想起 缺乏 实行 结束 饭碗 权利 宣传 坐在 行为 知识

电话 专业 官员 我国 数据 运动 经验 集中 利用


n_category = 0

res = df_word_choice_tag[(y_pred == n_category)]

count = 0

print(f"类别为{n_category}的数据:")

count = 0

for word in res:

    print(f"{word}\t",end="")

    count = count + 1

    if(count % 10 == 0):

        print(f"\n")

输出结果显示:

类别为0的数据:

竞争 经营 干活 世界 建设 水平 幼儿园 组织 国有资产 以为

抛弃 人口 岗位 一代 国有 属于 剩下 大学生 衣服 资本

人生 包袱 基础 认识 相关 相当 政治 医疗 建立 干部

增加 提高 回到 资产 女儿 贷款 体系 形成 邻居 设备

遇到 计划 实现 群体 看见 危机 减少 办公室 喜欢 司机

不如 机器 达到 报纸 姐姐 增长 奶奶 阶级 权力 精神

男人


n_category = 7

res = df_word_choice_tag[(y_pred == n_category)]

count = 0

print(f"类别为{n_category}的数据:")

count = 0

for word in res:

    print(f"{word}\t",end="")

    count = count + 1

    if(count % 10 == 0):

        print(f"\n")

输出结果显示:

类别为7的数据:

代表 双职工 是从 冲击 世纪 竞争力 父辈 现象 概念 东北人

失败 本质 都有 面临 是否 想象 回忆 阵痛 保证 来看

话题 是不是 悲剧 外公 据说 听到 规模 意识 状况 来源

结婚 还算 奉献 讨论 代人 赶上 外婆 培养 帮助 大人

养活 感谢 大潮 产业工人 安置 担心 大锅饭 原本 身份 目的

意义 重工业 民企 民营企业


8.4,得到聚类中心

在《机器学习库sklearn的K-Means聚类算法的使用方法》一文我们使用了matplotlib画出来每个数据点,并观察他们的聚类情况,但是,本notebook中的数据点是多维的,投影到2维和3维空间中,可能数据点就会重叠在一起,看不到实际情况。所以,我打算先找到聚类中心,然后找到围绕中心的词,通过调整外围词的数量,观察一个类别代表什么含义。

用下面的代码得到聚类中心的向量,然后找到中心向量相近的向量。

【重要提醒】数据探索思路没有问题,但是下面的代码是否合适,需要多方验证。因为,我没有找到sklearn中的函数,用于计算离中心词最近的N个词,那么,我采用了word2vec中的函数,这样就有下面的问题:

1. word2vec中求向量距离的算法跟sklearn的聚类算法中的距离算法是否一样?比如,都用欧式距离,或者都用其他距离,应该是正确的

2. word2vec的模型中含有预料数据中的所有词,那么,算出来的相近词很多不属于被观察的所选词

centroid=cluster.cluster_centers_

centroid=cluster.cluster_centers_

for cent in centroid:

    print(f"cluster聚类中心:\n{cent}")

输出结果显示:

cluster聚类中心:

[ 1.69013768e-01  1.60321653e-01  1.17232829e-01  2.46103436e-01

  4.65250462e-02  2.17778813e-02 -4.06405050e-03  1.00424893e-01

  6.63540512e-02  8.62851962e-02 -1.52554959e-01 -6.10452294e-02

.......

 -1.57521233e-01 -5.99336475e-02  9.51049849e-04  1.33074388e-01

  1.81386411e-01  1.42902642e-01 -6.27511069e-02  2.48194203e-01

  1.09543160e-01  7.37459958e-02 -3.62911299e-02 -9.11157504e-02

  2.33341247e-01 -5.53545803e-02  6.53567761e-02  2.11561427e-01

 -3.16228926e-01 -2.76067585e-01 -2.88534522e-01 -3.36503834e-02]


上面的结果并不好看,我们要看看中心的那个词是什么,才能观察到类别的含义

from sklearn.metrics import pairwise_distances_argmin

labels_random = pairwise_distances_argmin(centroid, tag_vec)

print(labels_random)

for index in labels_random:

    print(f"{df_word_choice_tag[index]}\t", end="")

输出结果显示:

[332  16 442  84 112  24 278 469 176 276]

办公室 工厂 缺乏 个人 办法 觉得 背景 感谢 压力 完成


注意对比一下这10个词与10类中的每个词就能看到,上面列出来的10个词,按照顺序属于类别0,1,2,....

有了这些中心位置,那么我们就找跟他们最近的topN个词,选择N的时候不要超出类别范围。通过调整N,比如,从大到小,揣摩这个类别到底代表什么含义。

前面我们已经看到了,类别0,2,6,7这4个类别比较大,那么我们观察一下他们围绕中心词的10个词是什么。

0号类别中心词是办公室,那么最近10个词是:

for item in model.wv.most_similar(positive=['办公室'], topn=10):

    print(item)

输出结果显示:

('车间', 0.9998899102210999)

('有时候', 0.9998782873153687)

('机器', 0.9998592138290405)

('老妈', 0.9998512864112854)

('奶奶', 0.9998374581336975)

('居然', 0.9998329281806946)

('如此', 0.9998326301574707)

('儿子', 0.9998306035995483)

('中的', 0.9998295307159424)

('属于', 0.9998283982276917)


2号类别中心词是缺乏,那么最近10个词是:

for item in model.wv.most_similar(positive=['缺乏'], topn=10):

    print(item)

输出结果显示:

('自然', 0.9998841285705566)

('某些', 0.9998740553855896)

('能力', 0.9998729825019836)

('日本', 0.9998723864555359)

('群体', 0.9998697638511658)

('通过', 0.9998685717582703)

('国内', 0.999857485294342)

('中的', 0.9998543858528137)

('完成', 0.9998524188995361)

('随着', 0.9998501539230347)


6号类别中心词是背景,那么最近10个词是:

for item in model.wv.most_similar(positive=['背景'], topn=10):

    print(item)

输出结果显示:

('反正', 0.9998777508735657)

('许多', 0.9998736381530762)

('愿意', 0.999870777130127)

('永远', 0.9998701214790344)

('时期', 0.9998684525489807)

('得到', 0.99986732006073)

('农民工', 0.9998658895492554)

('重新', 0.9998648166656494)

('即使', 0.9998641014099121)

('不要', 0.9998586773872375)


7号类别中心词是感谢,那么最近10个词是:

for item in model.wv.most_similar(positive=['感谢'], topn=10):

    print(item)

输出结果显示:

('一切', 0.9998477101325989)

('曾经', 0.9998447895050049)

('另外', 0.9998349547386169)

('而已', 0.9998282194137573)

('愿意', 0.9998224377632141)

('永远', 0.9998178482055664)

('绝望', 0.9998123645782471)

('状态', 0.9998121857643127)

('得到', 0.9998061060905457)

('直接', 0.9998060464859009)


9,调整参数做对比实验

个人认为(没有看过算法原理,也没有实验验证过,仅仅猜测),word2vec在生成词向量的时候,受window参数影响比较大,尤其下一步还要做词聚类,更依赖于向量坐标值,所以,下面我们调整一下window参数,对比一下结果

9.1,调整word2vec的window参数,重新训练

model2 = gensim.models.Word2Vec(sentences=sentences, window=10, vector_size=500)


9.2,从所有词的向量中找出所选词的向量

tag_vec2 = []

for index, tag in df_word_choice_tag.items():

    try:

        vec_cameroon = model2.wv[tag]

        tag_vec2.append(vec_cameroon)

    except KeyError as e:

        print(e)

tag_vec2 = np.array(tag_vec2)

print(tag_vec2)

输出结果显示:

[[ 0.41712654  0.47305188  0.41938704 ... -0.65201235 -0.48494622

  -0.25077116]

 [ 0.37631005  0.39844137  0.33175978 ... -0.5925424  -0.43348897

  -0.18643509]

 [ 0.1355679   0.07398985 -0.2872968  ... -0.35405728 -0.14280449

   0.21224871]

 ...

 [ 0.11721177  0.07381704  0.00891381 ... -0.19018999 -0.14107154

   0.02088194]

 [ 0.1026341   0.07113202  0.01266137 ... -0.16621074 -0.12928386

   0.01577106]

 [ 0.10585404  0.06639028  0.00851469 ... -0.16627984 -0.13094328

   0.02440548]]


9.3,k-means聚类

类别数不变,便于对比window变化

n_clusters=10

# 建立模型。n_clusters参数用来设置分类个数,即K值,这里表示将样本分为10类。

cluster2 = KMeans(n_clusters=n_clusters,random_state=0).fit(tag_vec2)


9.4,查看聚好的类别

y_pred2 = cluster2.labels_

print(y_pred2)

输出结果显示:

[1 3 6 1 3 4 1 4 1 5 6 3 1 6 6 3 3 1 1 1 4 1 6 4 1 3 5 5 1 6 1 3 5 3 8 1 1

 5 3 1 3 1 8 4 1 4 4 3 8 3 1 4 5 3 5 4 1 3 5 3 5 5 1 5 3 3 5 1 3 5 3 4 5 3

 1 4 1 3 3 4 3 5 8 4 5 8 3 5 3 5 3 3 5 1 3 5 8 5 4 5 2 4 4 4 2 2 4 5 5 3 8

 5 5 5 4 8 5 4 3 8 7 5 5 3 4 8 2 5 8 2 8 4 5 2 2 4 8 4 4 2 2 4 8 8 5 5 8 4

 2 8 5 5 8 5 2 8 4 5 8 2 2 8 2 4 4 4 8 5 2 0 0 2 5 8 0 4 0 2 5 5 8 8 2 4 4

 2 8 0 4 0 0 5 8 2 2 2 0 5 2 4 5 2 0 0 7 2 0 2 8 0 8 4 5 2 2 2 0 8 0 5 7 2

 5 4 2 2 8 9 0 8 8 0 2 5 0 0 0 2 0 8 0 2 8 8 0 5 8 5 8 2 8 0 9 4 9 2 8 2 0

 2 9 0 2 0 0 2 0 7 0 5 2 0 0 0 0 7 2 7 7 9 0 0 0 8 2 7 2 0 0 0 9 0 2 0 0 0

 2 2 7 0 0 0 0 2 0 7 2 9 0 9 9 2 0 9 9 2 0 0 7 2 2 7 7 9 0 0 7 7 2 2 0 2 0

 0 0 7 0 7 2 0 7 0 2 5 0 0 7 0 4 2 9 7 9 0 2 9 0 7 7 2 2 9 9 2 9 9 7 9 9 0

 0 2 0 9 7 0 7 0 7 0 7 7 7 9 0 7 7 0 9 0 9 7 7 0 0 7 9 7 9 9 2 9 7 7 9 2 7

 9 2 7 7 0 7 7 0 0 9 7 0 9 7 0 0 9 7 9 7 7 9 7 7 7 7 0 9 9 7 9 0 7 0 9 7 9

 9 7 7 9 9 7 9 7 0 9 9 9 9 9 7 9 7 7 9 9 0 9 0 7 7 9 9 9 0 7 7 7 2 7 7 7 9

 9 7 2 7 9 7 7 7 7 7]


9.5,查看每个类包含的词的数量

quantity2 = pd.Series(y_pred2).value_counts()

print(f"cluster聚类数量:\n{quantity2}")

输出结果显示:

cluster聚类数量:

0    90

7    76

2    70

9    60

5    53

8    41

4    40

3    31

1    24

6     6

dtype: int64


9.6,观察聚类中含有的词

我们这次还是看最大的类,就是0,7,2,9

n_category = 0

res2 = df_word_choice_tag[(y_pred2 == n_category)]

count = 0

print(f"类别为{n_category}的数据:")

for word in res2:

    print(f"{word}\t",end="")

    count = count + 1

    if(count % 10 == 0):

        print(f"\n")

输出结果显示:

类别为0的数据:

老板 条件 肯定 压力 分配 丈夫 保障 水平 失去 愿意

机会 煤矿 相信 找到 维持 得到 岗位 全家 一代 属于

剩下 创业 人生 认识 相当 负责 没人 准备 家乡 年轻人

补偿 农民工 裁员 私人 干部 工人下岗 了解 县城 年纪 回到

一代人 女儿 贷款 老人 养老 纺织厂 邻居 遇到 技能 老百姓

看见 待遇 系统 安排 还好 办公室 喜欢 司机 不如 思想

老家 姐姐 家属 命运 考虑 生活费 估计 参加 都会 现实

意思 销售 小时 男人 粮食 回去 钢铁 说话 反对 学生

姥爷 老婆 没法 电视 成都 想起 饭碗 女人 坐在 专业

限于篇幅,其它不写了。

9.7,找中心词

centroid2=cluster2.cluster_centers_

labels_random2 = pairwise_distances_argmin(centroid2, tag_vec2)

print(labels_random2)

for index in labels_random2:

    print(f"{df_word_choice_tag[index]}\t", end="")


输出结果显示:

[345  44 342  11  75 172  10 346 125 365]

家属 爸爸 达到 开始 利益 上海 经济 事件 算是 是从


中心词肯定都变的面目全非了,如果再看围绕中心词的相近词,也会感觉有很大变化。

10,结论

可以看到,选择不同的window参数得到的结果变化还是很大的,因为在生成词向量的时候,一个词能够看到的上下文长度变了,被当成上下文的词就变了,那么生成的词向量就变了。所有这些参数,包括要求k-means聚类的个数,需要经验和实验,前提是一个类代表的意义能够敏锐把握好,然后,调整这些参数,让每个类代表独特的意义,意义不重叠为好。

另外,我并没有找到更合适的观察所分类别的可视化工具,我采用了一个方法,先找到类别中心,然后看最近的N个词。在上面章节,我也强调了这个方法可能存在的问题。

其实,LDA方法更加成熟,还有配套的可视化展示类别的工具,参看《微博内容分词并手工选词后用JupyterNotebook做LDA主题分析》,如果是为了做话题分析,个人认为使用LDA更具操作性。

11,下载本notebook

下载源代码请进:使用word2vec和k-means进行词聚类

1

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

发表评论

最新评论

评论 15964002091 2021-11-25 18:16
非常感谢!论文刚好用到

查看全部评论(1)

GMT+8, 2024-10-4 20:49