Python使用K-means实现文本聚类

Python使用K-means实现文本聚类

前言

最近遇到了这样一个需求,将N个文本内容聚类成若干个主题词团,减少人工分析文本和分类文本的工作量。

实现思路是使用 K-means算法通过高频词对文本内容进行聚类,K-means算法实现原理简单易于理解,缺点是词与词之间的顺序性和相互关系不能在分类中得到体现。实现步骤如下:

  • 使用 jieba对文本内容进行分词处理;
  • 去掉停用词;
  • 使用 TF-IDF算法将上一步过滤后的分词列表转换成矩阵形式;
  • 使用 K-means聚类算法对矩阵计算相似性;
  • 获取每个聚类的主题词/词团;

准备样本

周杰伦的30首歌曲的歌词金句作为我们聚类样本的内容,保存到 sourceData/周杰伦.txt 文本中。

分词

使用 pythonpip安装结巴分词组件

python 复制代码
pip install jieba

定义一个函数方法,读取 周杰伦.txt文件,并对文件内容的每一行文本做分词处理。

python 复制代码
import jieba

def get_jiebaword():
    try:
        with open('sourceData/周杰伦.txt', "r", encoding='utf-8') as fr:
            lines = fr.readlines()
    except FileNotFoundError:
        print("找不到此文件")
    jiebaword = []
    for line in lines:
        line = line.strip('\n')
        # 清除多余的空格
        line = "".join(line.split())
        # 默认精确模式
        seg_list = jieba.cut(line, cut_all=False)
        word = "/".join(seg_list)
        jiebaword.append(word)
    return jiebaword

分词后的文本列表,打印出来如图所示:

停用词

停用词是一些没有具体含义但在文本中经常会出现的词语,例如"的"、"是"、"许多"、"不仅"等。

中文停用词我们可以去网上下载,地址如下:

gitcode.com/open-source...

下载后的停用词在一个 hit_stopwords.txt 文件中,如图所示:

停用词不只是只有文字,也包括一些标点符号。

定义一个函数方法读取停用词。

python 复制代码
def get_stopword():
    stopword = []
    try:
        with open('sourceData/hit_stopwords.txt', "r", encoding='utf-8') as fr:
            lines = fr.readlines()
    except FileNotFoundError:
        print("找不到此文件")
    for line in lines:
        line = line.strip('\n')
        stopword.append(line)
    return stopword

定义一个函数方法从样本分词列表中过滤掉停用词,过滤后的结果保存到 CleanWords.txt 文件中。

python 复制代码
def clean_stopword(jiebaword, stopword):
    fw = open('resultData/周杰伦/CleanWords.txt', 'a+', encoding='utf-8')
    for words in jiebaword:
        words = words.split('/')
        for word in words:
            if word not in stopword:
                fw.write(word + '\t')
        fw.write('\n')
    fw.close()

CleanWords.txt 文件如图所示。

如果发现CleanWords.txt 文件中还有一些词语会影响聚类的效果,可以使用如下语句添加停用词。

python 复制代码
for i in range(30):
    stopword.append(str(i+1))
    
stopword.append('一路')
stopword.append('向北')

TF-IDF算法

为了让计算机能够理解词语的相似度,我们可以将文本格式的数据转换成矩阵类型的数据, TF-IDF矩阵在这方面是应用最为广泛的。

TF-IDF(Term Frequency-Inverse Document Frequency,词频-逆文档频率)是一种在信息检索和文本挖掘领域广泛使用的统计方法,用于评估一个词在文档或语料库中的重要程度。

  • TF 词频,表示某个词在文档中出现的频率。词频反映了词语在文档中的重要性,出现次数越多,TF值越高。计算公式:
  • IDF 逆文档频率,表示某个词在整个文档集合中的稀有程度。逆文档频率反映了词语在整个文档集合中的普遍性,出现次数越多的词,IDF值越低;反之,则越高。计算公式:

包含词t的文档书 +1 是为了防止除以 0 导致溢出。

总结一下 TF-IDFTF 表示相同的词在两篇文章中出现的频次越高,两篇文章越相似; IDF 表示某个词在所有文本中出现次数较少,只在某两篇文章中出现几次,则该两篇文章具有较高相似度。

scikit-learn 已经实现了 TF-IDF 算法,我们首先要安装scikit-learn 组件。

python 复制代码
pip install scikit-learn

使用python 实现,定义一个函数方法生成 TF-IDF 矩阵。

python 复制代码
from sklearn.feature_extraction.text import TfidfVectorizer

def get_tfidf():
    try:
        with open('resultData/周杰伦/CleanWords.txt', "r", encoding='utf-8') as fr:
            lines = fr.readlines()
    except FileNotFoundError:
        print("找不到此文件")
    transformer = TfidfVectorizer()
    tfidf = transformer.fit_transform(lines)
    # 转为数组形式
    tfidf_arr = tfidf.toarray()
    return tfidf_arr

打印输出的矩阵,如下图所示:

这个矩阵的形状是 30 * 217 ,它表示 217 个分词在 30 个文本中的 TF-IDF 值,值为0表示在此文章中没有出现过。由于打印的不是完整的矩阵,所以上图中的矩阵没有将非0的值显示出来。

K-means聚类

K-Means 聚类是一种常用的无监督学习算法,用于将数据集分成K个簇(cluster),使得簇内的数据点彼此之间尽可能相似,而簇间的数据点尽可能不同。 K-Means 算法的目标是最小化簇内数据点到簇中心的距离之和。

我们需要使用 nltk 组件调用 K-Means 算法。

python 复制代码
pip install nltk

定义一个函数方法,获取K-Means 聚类。

python 复制代码
from nltk.cluster import KMeansClusterer, cosine_distance
import pandas as pd

def get_cluster(tfidf_arr, k):
    kmeans = KMeansClusterer(num_means=k, distance=cosine_distance, avoid_empty_clusters=True)  # 分成k类,使用余弦相似分析
    kmeans.cluster(tfidf_arr)
    # 获取分类
    kinds = pd.Series([kmeans.classify(i) for i in tfidf_arr])
    
    fw = open('resultData/周杰伦/ClusterText.txt', 'a+', encoding='utf-8')
    for i, v in kinds.items():
        fw.write(str(i) + '\t' + str(v) + '\n')
    fw.close()

聚类结果保存在 ClusterText.txt 文件中,结果如图所示:

图中有两列数字,第一列数字是从0到29按顺序排列的数字,表示30个文本的序号。第二列数字表示5个聚类的序号 0~4

获取主题词

前面几步已经得到了对周杰伦歌词的聚类索引,但是我们并不清楚这些聚类索引代表什么含义,所以我们可以将这5个聚类里词频最高的几个词给提取出来。

定义一个函数方法,获取分类文档。

python 复制代码
def cluster_text(text_cnt):
    index_cluser = []
    try:
        with open('resultData/周杰伦/ClusterText.txt', "r", encoding='utf-8') as fr:
            lines = fr.readlines()
    except FileNotFoundError:
        print("找不到此文件")
    for line in lines:
        line = line.strip('\n')
        line = line.split('\t')
        index_cluser.append(line)
    # index_cluser[i][j]表示第i行第j列
    try:
        with open('resultData/周杰伦/CleanWords.txt', "r", encoding='utf-8') as fr:
            lines = fr.readlines()
    except FileNotFoundError:
        print("找不到此文件")
    for index, line in enumerate(lines):
        for i in range(text_cnt):
            if str(index) == index_cluser[i][0]:
                fw = open('resultData/周杰伦/cluster' + index_cluser[i][1] + '.txt', 'a+', encoding='utf-8')
                fw.write(line)
    fw.close()

将30个歌词文本的聚类结果分别放入5个文件中。

其中一个cluster文件结果如下:

得到以上分类文档以后,再分别统计各个聚类中频次最高的几个词,定义一个函数方法,代码如下:

python 复制代码
from collections import Counter

def get_title(cluster, top_n=5):
    fw = open('resultData/周杰伦/title.txt', 'a+', encoding='utf-8')

    for i in range(cluster):
        try:
            with open('resultData/周杰伦/cluster' + str(i) + '.txt', "r", encoding='utf-8') as fr:
                lines = fr.readlines()
        except FileNotFoundError:
            print("找不到此文件")
        all_words = []
        for line in lines:
            line = line.strip('\n')
            line = line.split('\t')
            for word in line:
                all_words.append(word)
        c = Counter()
        for x in all_words:
            if len(x) > 1 and x != '\r\n':
                c[x] += 1

        print('主题' + str(i) + '----------------------------------------------------\n词频统计结果:\n')
        fw.write('主题' + str(i) + '----------------------------------------------------\n词频统计结果:\n')
        # 输出词频最高的那个词,也可以输出多个高频词
        for (k, v) in c.most_common(top_n):
            print(k, ':', v, '\n')
            fw.write(k + ':' + str(v) + '\n')
        fw.write('\n')

    fw.close()

执行结果保存在 title.txt 文件中,我这里参数 top_n 是传的3,表示获取3个主题词,效果如图所示:

因为样本做的比较少,所以词频统计的数量不多,所以代表性也不是很强。另一个原因是 K-means 算法是一种无监督算法,一开始要定义好聚类的数量,算法根据聚类的数量随机选取聚类中心点,中心点选的不准会极大影响聚类结果的准确度。所以可以定义不同的聚类数量多计算几次,直到满意为止。

主流程方法

主流程 main 方法基本是调用上文中列表的所有函数方法,按步骤开始执行。

此外,还定义了一个 delete_files_in_directory 函数用来在生成聚类结果之前先删除上一次生成的结果,否则生成的结果txt文件会叠加上一次的结果。

python 复制代码
import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.cluster import KMeansClusterer, cosine_distance
import pandas as pd
from collections import Counter
import os

def delete_files_in_directory(directory):
    if not os.path.exists(directory):
        os.mkdir(directory)
        return
    # 遍历目录中的所有文件
    for filename in os.listdir(directory):
        file_path = os.path.join(directory, filename)
        # 检查路径是否为文件(而非子目录等)
        if os.path.isfile(file_path):
            # 删除文件
            os.remove(file_path)

if __name__ == '__main__':
    # 定义聚类的个数
    cluster = 5
    # 定义主题词个数
    top_n = 3

    # 删除上一次的结果数据
    delete_files_in_directory('resultData/周杰伦')

    # 结巴分词
    jiebaword = get_jiebaword()

    # 获取停用词
    stopword = get_stopword()

    # ---停用词补充,视具体情况而定---
    for i in range(30):
        stopword.append(str(i+1))

    stopword.append('一路')
    stopword.append('向北')

    # ----------------------
    # 去除停用词
    clean_stopword(jiebaword, stopword)

    # 获取tfidf矩阵
    tfidf_arr = get_tfidf()
    text_cnt = tfidf_arr.shape[0]

    # ---输出测试---
    # print(tfidf_arr)
    # print(tfidf_arr.shape)
    # -------------

    # K-means聚类
    get_cluster(tfidf_arr, cluster)

    # 获取分类文件
    cluster_text(text_cnt)

    # 统计出主题词
    get_title(cluster, top_n)
相关推荐
ALISHENGYA6 分钟前
精讲Python之turtle库(二):设置画笔颜色、回旋伞、变色回旋伞、黄色三角形、五角星,附源代码
python·turtle
drebander24 分钟前
PyTorch 模型 浅读
pytorch·python·大模型
securitor26 分钟前
【java】IP来源提取国家地址
java·前端·python
计算机学姐26 分钟前
基于微信小程序的民宿预订管理系统
java·vue.js·spring boot·后端·mysql·微信小程序·小程序
Code侠客行1 小时前
Scala语言的编程范式
开发语言·后端·golang
一只码代码的章鱼1 小时前
粒子群算法 笔记 数学建模
笔记·算法·数学建模·逻辑回归
小小小小关同学1 小时前
【JVM】垃圾收集器详解
java·jvm·算法
圆圆滚滚小企鹅。1 小时前
刷题笔记 贪心算法-1 贪心算法理论基础
笔记·算法·leetcode·贪心算法
Kacey Huang2 小时前
YOLOv1、YOLOv2、YOLOv3目标检测算法原理与实战第十三天|YOLOv3实战、安装Typora
人工智能·算法·yolo·目标检测·计算机视觉
加德霍克2 小时前
【机器学习】使用scikit-learn中的KNN包实现对鸢尾花数据集或者自定义数据集的的预测
人工智能·python·学习·机器学习·作业