由于工作需要,使用了聚类方法在文本语料中挖掘类别信息,下面是一个demo,供大家参考。实验数据由于公司原因不便公开。

实验步骤:

1、 排序去重,经过排序去重后数据从10万条变为3万条。
2、 结巴分词。
3、 特征提取,使用平滑后的tf-idf作为特征,为每个用户问题构建特征向量,采用了scikit-learn 中的类 TfidfVectorizer。
4、 采用了两种聚类方法K-means 。
K-means:算法的优点是收敛速度快,缺点是聚类形状在空间中是凸的。

实验代码:

github代码:https://github.com/xlniu/Text-Clustering

from sklearn.feature_extraction.text import TfidfVectorizer
import jieba
from sklearn.cluster import KMeans

# 输入输出文件
fin = open('user_question.txt', 'r') # 需要聚类的语料,每行一个句子
fout = open('result.txt', 'w') # 聚类后的结果,格式:label + '\t' + sentence

# 结巴分词
user_question = []
lines = fin.readlines()
for line in lines:
	line = line.strip()
	words = jieba.cut(line)
	user_question.append(' '.join(words))
print "fenci over"

# 使用tf-idf提取特征
vectorizer = TfidfVectorizer(max_df=0.9,min_df=3)
vector = vectorizer.fit_transform(user_question)

# 使用kmeans算法聚类,init:初始值选择的方式,n_init:用不同的初始化质心运行算法的次数
db = KMeans(n_clusters=10, init='k-means++', n_init=10)
db.fit(vector)
labels = db.labels_

# 将结果写入文件
for i,line in zip(labels, lines):
	fout.write(str(i) + '\t' + line)
for i,line in zip(labels, lines):
	fout.write(str(i) + '\t' + line)

# 统计输出每一类的数量
from collections import Counter
Counter(labels)

聚类后的观察结果(类别:主题 数量):

0:积分 804
1:金币 3862
2:如何…… 915
3:祝福、祝愿 552
4:话费 1466
5:较为杂乱,看不出是什么主题 13729
6:流量 3906
7:怎么…… 3009
8:心愿+希望…574
9:套餐 2618

结论和改进方法:

        观察了下聚类结果,大概是可接受和可用的,基于聚类语料的性质,名词型的类别较为可信和可用,比如0,1,3,4,6,8,9这七类是比较靠谱的,而2,5,7这三类则较为杂乱,观察之后看不出是什么主题,尤其是第5类,主题不明,而且数据量占到了总数据量的1/3。
        造成以上结果的原因是:使用tf-idf作为特征,只考虑了词频,没有考虑语义信息,这一点从聚类结果可以观察出,可以尝试使用word2vec方法来提取特征。

探索一下其他方法:

(1) Tfidf后使用nmf进行主题提取,再用kmeans聚类。
聚类后的观察结果:从结果看还不如之前的方法。
0:乱七八糟
1:取消
2:怎么
3:可以
4:金币
5:套餐
6:什么
7:乱七八糟
8:话费
9:为什么

(2) CountVectorizer后使用lda进行主题提取,再用kmeans聚类。
聚类后的观察结果:从结果看还不如nmf方法,每个主题都不明确,乱七八糟。
(3) 用bert提取每句话的cls特征,然后用kmeans聚类。
聚类后的观察结果:从结果看还不如nmf方法,每个主题都不明确,乱七八糟。
(4) 限定词性,比如只保留句子中的名词,因为我们关心的是名词性的类别,再使用tfidf提取特征,最后使用kmeans聚类。这种方法还没有尝试。

以上这些方法可能在我的语料中不适用,但不代表在其他语料不适用,大家可以多多尝试。