目录

自然语言处理文本聚类

自然语言处理:文本聚类

介绍

大家好,博主又来和大家分享 自然语言处理 领域的知识了。今天给大家分享的内容是 自然语言处理中文本聚类

文本聚类 在自然语言处理领域占据着重要地位,它能将大量无序的文本按照内容的相似性自动划分成不同的类别,极大地提高了文本处理和信息提取的效率。就好比在一个大型图书馆中,文本聚类能够像智能管理员一样,把各种书籍按照主题分类摆放,方便读者快速找到所需资料。

而实现文本聚类的方法有很多,其中 k均值聚类算法基于高斯混合模型的最大期望值算法 ,以及无监督朴素贝叶斯模型是比较常用的。好了,话不多说,我们直接进入正题。

文本聚类

k均值聚类算法

k均值聚类算法是一种简单且经典的聚类算法 。它的核心思想就像是在一群人中寻找几个代表,让其他人都围绕着这些代表 “站队”

具体来说,我们首先要确定聚类的数量 k ,这个 k 就像是我们设定的几个 “代表小组” 的数量。然后随机选择 k 个文本作为初始的聚类中心,就好像是先选了几个有代表性的人。接下来,计算每个文本与这 k 个聚类中心的距离,把文本分配到距离最近的聚类中心所代表的类别中。之后,不断地重新计算每个类别中所有文本的中心位置,也就是更新聚类中心,就像代表们根据自己所在小组的人员情况调整位置一样。重复这个过程,直到聚类中心不再变化或者变化很小,此时就完成了文本聚类。

这种算法的优点是简单易懂、计算效率高,对于大规模文本数据的处理有较好的可扩展性。但它也有一些缺点,比如需要预先指定聚类的数量 k ,这个 k 值如果选择不当,可能会导致聚类结果不理想;而且它对初始聚类中心的选择比较敏感,不同的初始中心可能会得到不同的聚类结果。

接下来,我们将着手实现k均值算法,以此来开展文本聚类的工作。此次所运用的数据集涵盖了大约1万本图书的相关信息,并且这些图书被划分为 3 种不同的主题类别。在这个数据集中,我们所关注的文本内容并非图书的标题,而是图书的摘要部分。我们用代码来演示:

预处理完整代码

nlp_book_data_collection.py

# 导入用于处理JSON数据的库
import json
# 导入defaultdict类,用于创建具有默认值的字典
from collections import defaultdict
# 导入spaCy库,用于自然语言处理
import spacy
# 从spaCy的中文停用词模块中导入停用词集合
from spacy.lang.zh.stop_words import STOP_WORDS
# 导入tqdm库,用于显示进度条
from tqdm import tqdm

# 加载中文语言模型
nlp_model = spacy.load('zh_core_web_sm')


# 定义一个类,用于收集和处理书籍数据
class BookDataCollection:
    # 类的初始化方法
    def __init__(self):
        # 训练集和测试集的文件名
        self.training_file, self.testing_file = 'train_original.jsonl', 'test_original.jsonl'
        self.init_data()

    # 定义一个内部函数,用于读取JSONL文件
    def read_json_file(self, file_path):
        # 以只读模式打开文件,并指定编码为UTF-8
        with open(file_path, 'r', encoding='utf-8') as file:
            # 将文件的每一行读取为一个列表
            json_lines = list(file)
        # 初始化一个空列表,用于存储解析后的JSON数据
        data_list = []
        # 遍历每一行JSON数据
        for json_str in json_lines:
            # 将JSON字符串解析为Python对象,并添加到数据列表中
            data_list.append(json.loads(json_str))
        # 返回解析后的数据列表
        return data_list

    def init_data(self):
        # 调用read_json_file函数读取训练集和测试集数据
        self.training_data, self.testing_data = self.read_json_file(self.training_file), self.read_json_file(
            self.testing_file)
        # 打印训练集和测试集的大小
        print('训练集大小 =', len(self.training_data), ', 测试集大小 =', len(self.testing_data))

        # 初始化两个空字典,用于存储标签到ID和ID到标签的映射
        self.label_to_id, self.id_to_label = {}, {}
        # 遍历训练集和测试集数据
        for data_group in [self.training_data, self.testing_data]:
            # 遍历每个数据项
            for data_item in data_group:
                # 获取数据项的类别标签
                category = data_item['class']
                # 如果类别标签不在标签到ID的映射中
                if category not in self.label_to_id:
                    # 生成一个新的ID
                    index = len(self.label_to_id)
                    # 将类别标签和对应的ID添加到标签到ID的映射中
                    self.label_to_id[category] = index
                    # 将ID和对应的类别标签添加到ID到标签的映射中
                    self.id_to_label[index] = category
                # 获取类别标签对应的ID
                label_index = self.label_to_id[category]
                # 在数据项中添加标签ID字段
                data_item['label'] = label_index

    # 定义一个方法,用于对文本进行分词处理
    def tokenize_text(self, attribute='book'):
        # 遍历训练集和测试集数据
        for data_group in [self.training_data, self.testing_data]:
            # 使用tqdm显示进度条,遍历每个数据项
            for data_item in tqdm(data_group):
                # 获取数据项指定属性的文本内容,并转换为小写
                text_content = data_item[attribute].lower()
                # 使用spaCy模型对文本进行处理,过滤掉停用词,并提取词元
                tokens = [token.text for token in nlp_model(text_content) if token.text not in STOP_WORDS]
                # 在数据项中添加分词结果字段
                data_item['tokens'] = tokens

    # 定义一个方法,用于创建词汇表
    def create_vocabulary(self, min_frequency=3, min_length=2, max_vocab_size=None):
        # 初始化一个defaultdict,用于统计词元的频率
        word_frequency = defaultdict(int)
        # 遍历训练集数据
        for data_item in self.training_data:
            # 获取数据项的分词结果
            tokens = data_item['tokens']
            # 遍历每个词元
            for token in tokens:
                # 统计词元的频率
                word_frequency[token] += 1

        # 打印唯一词元数量、总词元数量、最大词频和最小词频
        print(f'唯一词元数量 = {len(word_frequency)}, 总词元数量 = {sum(word_frequency.values())}, '
              f'最大词频 = {max(word_frequency.values())}, 最小词频 = {min(word_frequency.values())}')

        # 初始化两个空字典,用于存储词元到ID和ID到词元的映射
        self.token_to_id = {}
        self.id_to_token = {}
        # 初始化总词频为0
        total_frequency = 0
        # 按词频从高到低对词元进行排序,并遍历
        for token, freq in sorted(word_frequency.items(), key=lambda x: -x[1]):
            # 如果指定了最大词汇表大小,且当前词汇表大小已达到最大值
            if max_vocab_size and len(self.token_to_id) >= max_vocab_size:
                # 跳出循环
                break
            # 如果词元的频率大于最小频率
            if freq > min_frequency:
                # 如果未指定最小词长,或者词元长度大于等于最小词长
                if (min_length is None) or (min_length and len(token) >= min_length):
                    # 将词元添加到词元到ID的映射中,并分配一个新的ID
                    self.token_to_id[token] = len(self.token_to_id)
                    # 将ID添加到ID到词元的映射中,并关联对应的词元
                    self.id_to_token[len(self.id_to_token)] = token
                    # 累加词元的频率到总词频中
                    total_frequency += freq
            else:
                # 跳出循环
                break
        # 打印最小词频、最小词长、最大词汇表大小、剩余词元数量和词表内词元占比
        print(f'最小词频 = {min_frequency}, 最小词长 = {min_length}, 最大词表大小 = {max_vocab_size}, '
              f'剩余词元数量 = {len(self.token_to_id)}, '
              f'词表内词元占比 = {total_frequency / sum(word_frequency.values())}')

    # 定义一个方法,用于将词元转换为对应的ID
    def convert_tokens_to_indices(self):
        # 遍历训练集和测试集数据
        for data_group in [self.training_data, self.testing_data]:
            # 遍历每个数据项
            for data_item in data_group:
                # 初始化一个空列表,用于存储词元对应的ID
                data_item['token_indices'] = []
                # 遍历数据项的分词结果
                for token in data_item['tokens']:
                    # 如果词元在词元到ID的映射中
                    if token in self.token_to_id:
                        # 将词元对应的ID添加到列表中
                        data_item['token_indices'].append(self.token_to_id[token])

nlp_text_clustering.py

# 从book_data_container模块导入BookDataContainer类
from nlp_book_data_collection import BookDataCollection


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()

预处理运行结果

训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [02:29<00:00, 57.74it/s]
100%|██████████| 2156/2156 [00:37<00:00, 56.76it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699

进程已结束退出代码为 0

然后,我们引入实现TF-IDF算法的相关函数,并把经过处理的数据集输入至该函数内,从而获取文档的特征信息。我们用代码来演示:

特征信息完整代码

nlp_tfidf_transformer.py

# 导入numpy库,用于进行数值计算
import numpy as np


# 定义一个类,用于进行TF-IDF转换
class TFIDFTransformer:
    # 类的初始化方法
    def __init__(self, vocabulary_size, normalization='l2', smooth_idf_flag=True, sublinear_tf_flag=True):
        # 存储词汇表大小
        self.vocabulary_size = vocabulary_size
        # 存储归一化方法
        self.normalization = normalization
        # 存储是否进行平滑IDF计算的标志
        self.smooth_idf_flag = smooth_idf_flag
        # 存储是否进行子线性TF计算的标志
        self.sublinear_tf_flag = sublinear_tf_flag

    # 定义一个方法,用于拟合数据,计算IDF值
    def fit_data(self, input_data):
        # 初始化一个全零数组,用于存储每个词元的文档频率
        document_frequency = np.zeros(self.vocabulary_size, dtype=np.float64)
        # 遍历输入数据
        for data_item in input_data:
            # 遍历数据项中唯一的词元ID
            for token_index in set(data_item):
                # 统计词元的文档频率
                document_frequency[token_index] += 1
        # 根据平滑IDF标志,对文档频率进行平滑处理
        document_frequency += int(self.smooth_idf_flag)
        # 计算样本数量,并根据平滑IDF标志进行调整
        num_samples = len(input_data) + int(self.smooth_idf_flag)
        # 计算每个词元的IDF值
        self.idf = np.log(num_samples / document_frequency) + 1

    # 定义一个方法,用于对输入数据进行转换
    def transform_data(self, input_data):
        # 确保已经计算了IDF值
        assert hasattr(self, 'idf')
        # 初始化一个全零矩阵,用于存储词频矩阵
        term_frequency = np.zeros((len(input_data), self.vocabulary_size), dtype=np.float64)
        # 遍历输入数据
        for i, data_item in enumerate(input_data):
            # 遍历数据项中的词元ID
            for token in data_item:
                # 统计词元的词频
                term_frequency[i, token] += 1
        # 如果启用了子线性TF计算
        if self.sublinear_tf_flag:
            # 对词频矩阵进行子线性变换
            term_frequency = np.log(term_frequency + 1)
        # 计算TF-IDF矩阵
        transformed_data = term_frequency * self.idf
        # 如果启用了归一化
        if self.normalization:
            # 计算每行的L2范数
            row_norm = (transformed_data ** 2).sum(axis=1)
            # 将范数为0的行的范数设为1,避免除零错误
            row_norm[row_norm == 0] = 1
            # 对TF-IDF矩阵进行归一化处理
            transformed_data /= np.sqrt(row_norm)[:, None]
        # 返回转换后的矩阵
        return transformed_data

    # 定义一个方法,用于拟合数据并进行转换
    def fit_and_transform(self, input_data):
        # 调用fit_data方法拟合数据
        self.fit_data(input_data)
        # 调用transform_data方法进行转换
        return self.transform_data(input_data)

nlp_text_clustering.py

# 从nlp_book_data_collection模块导入BookDataCollection类
from nlp_book_data_collection import BookDataCollection
# 从nlp_tfidf_transformer模块导入TFIDFTransformer类
from nlp_tfidf_transformer import TFIDFTransformer


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()

        # 获取训练集和测试集数据
        training_data, testing_data = book_dataset.training_data, book_dataset.testing_data

        # 获取词汇表大小
        vocabulary_size = len(book_dataset.token_to_id)
        # 初始化训练输入列表
        training_input = []
        # 遍历训练数据
        for data_item in training_data:
            # 将词元索引添加到训练输入列表
            training_input.append(data_item['token_indices'])
        # 创建TF-IDF转换器对象
        tfidf_transformer = TFIDFTransformer(vocabulary_size, normalization='l2', smooth_idf_flag=True,
                                             sublinear_tf_flag=True)
        # 拟合训练数据
        tfidf_transformer.fit_data(training_input)
        # 转换训练数据
        training_features = tfidf_transformer.transform_data(training_input)
        # 打印训练特征的形状
        print(training_features.shape)


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()

特征信息运行结果

训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [02:29<00:00, 57.74it/s]
100%|██████████| 2156/2156 [00:37<00:00, 56.76it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699
(8626, 9504)

进程已结束退出代码为 0

当获取到数据后,便可运用 k均值聚类算法 对文本展开聚类操作。在此过程中,一个关键的前提是要预先设定簇数 https://latex.csdn.net/eq?K 。为了便于与实际的标签数据进行对照分析,此次设定 https://latex.csdn.net/eq?K 值为 3。下面,我们用代码来演示:

聚类操作完整代码

nlp_kmeans_clusterer.py

# 导入NumPy库,用于数值计算
import numpy as np


# 定义一个类,用于实现K-Means聚类算法
class KMeansClusterer:
    # 类的初始化方法
    def __init__(self, num_clusters, data_dimension, stop_threshold=1e-4, max_steps=100):
        # 存储聚类的数量
        self.num_clusters = num_clusters
        # 存储数据的维度
        self.data_dimension = data_dimension
        # 存储停止迭代的阈值
        self.stop_threshold = stop_threshold
        # 存储最大迭代步数
        self.max_steps = max_steps

    # 定义一个方法,用于更新聚类中心
    def update_cluster_centers(self, input_data):
        # 初始化一个全零矩阵,用于存储聚类中心
        cluster_centers = np.zeros([self.num_clusters, self.data_dimension])
        # 遍历每个聚类
        for k in range(self.num_clusters):
            # 获取属于当前聚类的数据点
            cluster_data = input_data[self.cluster_assignments == k]
            # 如果当前聚类中有数据点
            if len(cluster_data) > 0:
                # 计算当前聚类的中心
                cluster_centers[k] = cluster_data.mean(axis=0)
        # 返回更新后的聚类中心
        return cluster_centers

    # 定义一个方法,用于拟合模型
    def fit_model(self, input_data):
        # 打印初始化信息
        print('-----------初始化-----------')
        # 获取输入数据的样本数量
        num_samples = len(input_data)
        # 获取输入数据的维度
        data_dim = len(input_data[0])
        # 随机初始化每个数据点的聚类分配
        self.cluster_assignments = np.random.randint(0, self.num_clusters, num_samples)
        # 调用update_cluster_centers方法更新聚类中心
        self.cluster_centers = self.update_cluster_centers(input_data)

        # 打印初始化完成信息
        print('-----------初始化完成-----------')
        # 初始化当前迭代步数为0
        current_step = 0
        # 当当前迭代步数小于最大迭代步数时,继续迭代
        while current_step < self.max_steps:
            # 迭代步数加1
            current_step += 1
            # 初始化每个数据点的聚类分配为0
            self.cluster_assignments = np.zeros(num_samples, int)
            # 遍历每个数据点
            for i, data_point in enumerate(input_data):
                # 计算每个数据点和每个簇中心的L2距离
                distances = np.linalg.norm(data_point[None, :] - self.cluster_centers, ord=2, axis=-1)
                # 找到每个数据点所属新的聚类
                self.cluster_assignments[i] = distances.argmin(-1)

            # 调用update_cluster_centers方法更新聚类中心
            new_cluster_centers = self.update_cluster_centers(input_data)

            # 计算聚类中心的平均移动距离
            center_movement = np.linalg.norm(new_cluster_centers - self.cluster_centers, ord=2, axis=-1).mean()
            # 打印当前迭代步数和聚类中心的平均移动距离
            print(f"第{current_step}步,中心点平均移动距离:{center_movement}")
            # 如果聚类中心的平均移动距离小于停止迭代的阈值
            if center_movement < self.stop_threshold:
                # 打印停止迭代信息
                print("中心点不再移动,退出程序")
                # 跳出循环
                break

            # 更新聚类中心
            self.cluster_centers = new_cluster_centers

nlp_text_clustering.py

# 从nlp_book_data_collection模块导入BookDataCollection类
from nlp_book_data_collection import BookDataCollection
# 从nlp_tfidf_transformer模块导入TFIDFTransformer类
from nlp_tfidf_transformer import TFIDFTransformer
# 从nlp_kmeans_clusterer模块导入KMeansClusterer类
from nlp_kmeans_clusterer import KMeansClusterer

# 聚类的数量
num_clusters = 3


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()

        # 获取训练集和测试集数据
        training_data, testing_data = book_dataset.training_data, book_dataset.testing_data

        # 获取词汇表大小
        vocabulary_size = len(book_dataset.token_to_id)
        # 初始化训练输入列表
        training_input = []
        # 遍历训练数据
        for data_item in training_data:
            # 将词元索引添加到训练输入列表
            training_input.append(data_item['token_indices'])
        # 创建TF-IDF转换器对象
        tfidf_transformer = TFIDFTransformer(vocabulary_size, normalization='l2', smooth_idf_flag=True,
                                             sublinear_tf_flag=True)
        # 拟合训练数据
        tfidf_transformer.fit_data(training_input)
        # 转换训练数据
        training_features = tfidf_transformer.transform_data(training_input)
        # 打印训练特征的形状
        print(training_features.shape)

        # 创建K-Means聚类器对象
        kmeans_clusterer = KMeansClusterer(num_clusters, training_features.shape[1])
        # 拟合训练特征
        kmeans_clusterer.fit_model(training_features)


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()
训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [03:05<00:00, 46.54it/s]
100%|██████████| 2156/2156 [00:36<00:00, 58.80it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699
(8626, 9504)
-----------初始化-----------
-----------初始化完成-----------
第1步中心点平均移动距离0.06890890571733081
第2步中心点平均移动距离0.05478241286369078
第3步中心点平均移动距离0.018892396863314006
第4步中心点平均移动距离0.013429342825043227
第5步中心点平均移动距离0.013566480916429499
第6步中心点平均移动距离0.013989222795632706
第7步中心点平均移动距离0.011440477943083664
第8步中心点平均移动距离0.007103963059848575
第9步中心点平均移动距离0.006074946365616077
第10步中心点平均移动距离0.00477369638045405
第11步中心点平均移动距离0.0037873833801874888
第12步中心点平均移动距离0.003169778238863545
第13步中心点平均移动距离0.0026544650799152428
第14步中心点平均移动距离0.0029003953549483793
第15步中心点平均移动距离0.002845907460494008
第16步中心点平均移动距离0.0029565530700650586
第17步中心点平均移动距离0.0046409302413431995
第18步中心点平均移动距离0.007601779316323283
第19步中心点平均移动距离0.008556943854755123
第20步中心点平均移动距离0.007326270837156583
第21步中心点平均移动距离0.004774654456463306
第22步中心点平均移动距离0.004131223162570739
第23步中心点平均移动距离0.0034665383237183632
第24步中心点平均移动距离0.0028326750163131242
第25步中心点平均移动距离0.0024085910806691804
第26步中心点平均移动距离0.0025467882048793647
第27步中心点平均移动距离0.0024867293424594233
第28步中心点平均移动距离0.00232851482144344
第29步中心点平均移动距离0.002858996918517763
第30步中心点平均移动距离0.0027176858630063097
第31步中心点平均移动距离0.0024908107307621374
第32步中心点平均移动距离0.002725824149863784
第33步中心点平均移动距离0.0029918663142641765
第34步中心点平均移动距离0.0021179114918197877
第35步中心点平均移动距离0.0019590759238497786
第36步中心点平均移动距离0.0021350571707413643
第37步中心点平均移动距离0.0029617190015900938
第38步中心点平均移动距离0.0033936270370122233
第39步中心点平均移动距离0.0029231439612436316
第40步中心点平均移动距离0.0032854686271343914
第41步中心点平均移动距离0.0031848632066834794
第42步中心点平均移动距离0.003748152169792271
第43步中心点平均移动距离0.0034990023467288468
第44步中心点平均移动距离0.003588666039850141
第45步中心点平均移动距离0.003444720739168902
第46步中心点平均移动距离0.004132389860665119
第47步中心点平均移动距离0.0042080010326193
第48步中心点平均移动距离0.004440204058509182
第49步中心点平均移动距离0.002555459672126323
第50步中心点平均移动距离0.0025740606612964256
第51步中心点平均移动距离0.0017377711466915063
第52步中心点平均移动距离0.001173105458515561
第53步中心点平均移动距离0.0014038477302960907
第54步中心点平均移动距离0.002010183332161079
第55步中心点平均移动距离0.0025465099651279664
第56步中心点平均移动距离0.003412787991913555
第57步中心点平均移动距离0.00491581405490893
第58步中心点平均移动距离0.006009725805675484
第59步中心点平均移动距离0.0030275349972018945
第60步中心点平均移动距离0.0023261247949924505
第61步中心点平均移动距离0.0019728143627690867
第62步中心点平均移动距离0.0021625513846364737
第63步中心点平均移动距离0.001665616845718412
第64步中心点平均移动距离0.000931590997003939
第65步中心点平均移动距离0.001013041855168734
第66步中心点平均移动距离0.0008405371362220621
第67步中心点平均移动距离0.001367543177768866
第68步中心点平均移动距离0.0014425643303775304
第69步中心点平均移动距离0.001116963591121085
第70步中心点平均移动距离0.001491048450742236
第71步中心点平均移动距离0.002040608920857972
第72步中心点平均移动距离0.0036833183433489704
第73步中心点平均移动距离0.010667221023078093
第74步中心点平均移动距离0.022518116498817724
第75步中心点平均移动距离0.019497945687248934
第76步中心点平均移动距离0.012781228163674337
第77步中心点平均移动距离0.006360174137399897
第78步中心点平均移动距离0.004201484107008095
第79步中心点平均移动距离0.0035826113950724998
第80步中心点平均移动距离0.0037105073591507513
第81步中心点平均移动距离0.00219649791629415
第82步中心点平均移动距离0.0015802391154533663
第83步中心点平均移动距离0.001539134331937778
第84步中心点平均移动距离0.0
中心点不再移动退出程序

进程已结束退出代码为 0

为了能更直观地呈现聚类效果,我们定义了 display_clusters() 函数,它的作用是展示每个真实分类下各个簇的占比情况。接下来,我们会利用这个函数展示k均值算法的聚类结果,借此观察 3 个标签中不同簇的占比情况。 我们用代码来演示:

聚类结果完整代码

nlp_text_clustering.py

# 从nlp_book_data_collection模块导入BookDataCollection类
from nlp_book_data_collection import BookDataCollection
# 从nlp_tfidf_transformer模块导入TFIDFTransformer类
from nlp_tfidf_transformer import TFIDFTransformer
# 从nlp_kmeans_clusterer模块导入KMeansClusterer类
from nlp_kmeans_clusterer import KMeansClusterer

# 聚类的数量
num_clusters = 3


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def display_clusters(self, cluster_assignments, num_clusters, book_dataset, labels_list):
        # 初始化标签聚类字典
        label_clusters = {label_id: {} for label_id in book_dataset.id_to_label}
        # 初始化每个标签下每个聚类的计数为0
        for k, v in label_clusters.items():
            label_clusters[k] = {i: 0 for i in range(num_clusters)}
        # 遍历标签和聚类分配
        for label_id, cluster_id in zip(labels_list, cluster_assignments):
            # 增加对应标签和聚类的计数
            label_clusters[label_id][cluster_id] += 1

        # 按标签ID排序遍历
        for label_id in sorted(book_dataset.id_to_label.keys()):
            # 初始化聚类信息字符串
            cluster_str = book_dataset.id_to_label[label_id] + ':\t{ '
            # 遍历每个聚类
            for cluster_id in range(num_clusters):
                # 获取当前聚类的计数
                count = label_clusters[label_id][cluster_id]
                # 计算总数
                total = sum(label_clusters[label_id].values())
                # 拼接聚类信息
                cluster_str += f'{str(cluster_id)}: {count}({count / total:.2f}), '
            # 结束聚类信息字符串
            cluster_str += '}'
            # 打印聚类信息
            print(cluster_str)

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()

        # 获取训练集和测试集数据
        training_data, testing_data = book_dataset.training_data, book_dataset.testing_data

        # 获取词汇表大小
        vocabulary_size = len(book_dataset.token_to_id)
        # 初始化训练输入列表
        training_input = []
        # 遍历训练数据
        for data_item in training_data:
            # 将词元索引添加到训练输入列表
            training_input.append(data_item['token_indices'])
        # 创建TF-IDF转换器对象
        tfidf_transformer = TFIDFTransformer(vocabulary_size, normalization='l2', smooth_idf_flag=True,
                                             sublinear_tf_flag=True)
        # 拟合训练数据
        tfidf_transformer.fit_data(training_input)
        # 转换训练数据
        training_features = tfidf_transformer.transform_data(training_input)
        # 打印训练特征的形状
        print(training_features.shape)

        # 创建K-Means聚类器对象
        kmeans_clusterer = KMeansClusterer(num_clusters, training_features.shape[1])
        # 拟合训练特征
        kmeans_clusterer.fit_model(training_features)

        # 初始化标签列表
        labels_list = []
        # 遍历训练数据
        for data_item in training_data:
            # 将标签添加到标签列表
            labels_list.append(data_item['label'])
        # 打印标签列表的长度
        print(len(labels_list))

        # 获取K-Means聚类结果
        cluster_assignments = kmeans_clusterer.cluster_assignments
        # 显示K-Means聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()

聚类结果运行结果

训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [03:00<00:00, 47.89it/s]
100%|██████████| 2156/2156 [00:36<00:00, 58.84it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699
(8626, 9504)
-----------初始化-----------
-----------初始化完成-----------
第1步中心点平均移动距离0.07187923654672244
第2步中心点平均移动距离0.044157507688436216
第3步中心点平均移动距离0.022896531888343605
第4步中心点平均移动距离0.0159488039593934
第5步中心点平均移动距离0.012182250613021412
第6步中心点平均移动距离0.012091131336056611
第7步中心点平均移动距离0.011953069179042916
第8步中心点平均移动距离0.007551366337940332
第9步中心点平均移动距离0.00607274740895354
第10步中心点平均移动距离0.0051005041228621905
第11步中心点平均移动距离0.0037475278494419353
第12步中心点平均移动距离0.002873947676155657
第13步中心点平均移动距离0.0032856197227294393
第14步中心点平均移动距离0.0037597515304094073
第15步中心点平均移动距离0.005154214384698833
第16步中心点平均移动距离0.01429702264635499
第17步中心点平均移动距离0.011662801294254196
第18步中心点平均移动距离0.01061592695683227
第19步中心点平均移动距离0.01964312337838108
第20步中心点平均移动距离0.012665767903761787
第21步中心点平均移动距离0.005392861470868378
第22步中心点平均移动距离0.002772730322582759
第23步中心点平均移动距离0.0018288923370850228
第24步中心点平均移动距离0.0014922387193858154
第25步中心点平均移动距离0.0010964808376660166
第26步中心点平均移动距离0.0011809417903345324
第27步中心点平均移动距离0.0008189027391701968
第28步中心点平均移动距离0.0008794506802535549
第29步中心点平均移动距离0.0003873751480754425
第30步中心点平均移动距离0.00023008235702405927
第31步中心点平均移动距离0.0
中心点不再移动退出程序
8626
计算机类:	{ 0: 397(0.10), 1: 845(0.22), 2: 2600(0.68), }
艺术传媒类:	{ 0: 177(0.08), 1: 2115(0.92), 2: 7(0.00), }
经管类:	{ 0: 2408(0.97), 1: 36(0.01), 2: 41(0.02), }

进程已结束退出代码为 0

基于高斯混合模型的最大期望值算法

基于高斯混合模型的最大期望值算法则是另一种强大的文本聚类方法。高斯混合模型认为文本数据是由多个高斯分布混合而成的,就好像是把不同颜色的颜料混合在一起。

最大期望值算法通过不断地迭代,分为期望步骤和最大化步骤。在期望步骤中,根据当前的模型参数,计算每个文本属于不同高斯分布的概率,就像是猜测每个文本更可能属于哪一种 “颜料” ;在最大化步骤中,利用期望步骤得到的概率,重新计算模型的参数,比如高斯分布的均值、方差和权重,就像调整颜料的混合比例。通过这样反复迭代,让模型更好地拟合文本数据,从而实现文本聚类。

这种算法的优势在于能够灵活地拟合复杂的数据分布,对于多模态的数据聚类效果很好,而且它有完善的理论基础。不过,它也存在一些问题,比如计算复杂度比较高,对超参数的选择很敏感,像高斯分布的数量、协方差类型等,而且容易陷入局部最优解,就像在一个复杂的迷宫中,容易被困在某个局部区域,找不到全局最优的聚类结果。

下面,我们来演示怎样运用高斯混合模型进行聚类操作。需要注意的是,高斯混合模型会计算每个数据点属于各个簇的概率分布,在实际应用中,我们通常把概率最高的簇作为最终的聚类输出结果。通过这种方式,我们就能依据数据点的概率分布情况,将它们划分到最合适的簇中,实现文本数据的有效聚类。我们用代码来演示:

高斯混合聚类完整代码

nlp_gaussian_mixture_model.py

# 导入NumPy库,用于数值计算
import numpy as np
# 从scipy库中导入多元正态分布函数
from scipy.stats import multivariate_normal as gaussian


# 定义高斯混合模型类
class GaussianMixtureModel:
    # 初始化方法,设置聚类数量、数据维度和最大迭代次数
    def __init__(self, num_clusters, data_dimension, max_iterations=100):
        # 存储聚类数量
        self.num_clusters = num_clusters
        # 存储数据维度
        self.data_dimension = data_dimension
        # 存储最大迭代次数
        self.max_iterations = max_iterations

        # 初始化混合系数,每个聚类初始权重相同
        self.mixing_coefficients = np.ones(num_clusters) / num_clusters
        # 随机初始化每个聚类的均值
        self.means = np.random.rand(num_clusters, data_dimension) * 2 - 1
        # 初始化协方差矩阵为全零矩阵
        self.covariances = np.zeros((num_clusters, data_dimension, data_dimension))
        # 遍历每个聚类
        for i in range(num_clusters):
            # 将每个聚类的协方差矩阵设为单位矩阵
            self.covariances[i] = np.eye(data_dimension)

    # 期望步骤,计算每个数据点属于每个聚类的概率
    def expectation_step(self, input_data):
        # 遍历每个聚类
        for i in range(self.num_clusters):
            # 计算每个数据点属于当前聚类的概率
            self.responsibilities[:, i] = self.mixing_coefficients[i] * gaussian.pdf(input_data, mean=self.means[i],
                                                                                     cov=self.covariances[i])
        # 对概率进行归一化处理
        self.responsibilities /= self.responsibilities.sum(axis=1, keepdims=True)

    # 最大化步骤,更新模型参数
    def maximization_step(self, input_data):
        # 计算每个聚类的责任权重总和
        responsibility_sum = self.responsibilities.sum(axis=0)
        # 更新混合系数
        self.mixing_coefficients = responsibility_sum / self.num_samples
        # 更新每个聚类的均值
        self.means = np.matmul(self.responsibilities.T, input_data) / responsibility_sum[:, None]
        # 遍历每个聚类
        for i in range(self.num_clusters):
            # 计算数据点与当前聚类均值的差值
            delta = np.expand_dims(input_data, axis=1) - self.means[i]
            # 计算协方差矩阵
            covariance = np.matmul(delta.transpose(0, 2, 1), delta)
            # 更新当前聚类的协方差矩阵
            self.covariances[i] = np.matmul(covariance.transpose(1, 2, 0), self.responsibilities[:, i]) / \
                                  responsibility_sum[i]

    # 计算对数似然值
    def log_likelihood_value(self, input_data):
        # 初始化对数似然值为0
        log_likelihood = 0
        # 遍历每个数据点
        for data_point in input_data:
            # 初始化概率为0
            probability = 0
            # 遍历每个聚类
            for i in range(self.num_clusters):
                # 累加数据点属于当前聚类的概率
                probability += self.mixing_coefficients[i] * gaussian.pdf(data_point, mean=self.means[i],
                                                                          cov=self.covariances[i])
            # 累加对数概率
            log_likelihood += np.log(probability)
        # 返回平均对数似然值
        return log_likelihood / self.num_samples

    # 拟合模型
    def fit_model(self, input_data):
        # 存储输入数据的样本数量
        self.num_samples = len(input_data)
        # 初始化责任矩阵为全零矩阵
        self.responsibilities = np.zeros((self.num_samples, self.num_clusters))
        # 计算初始对数似然值
        log_likelihood = self.log_likelihood_value(input_data)
        # 打印开始迭代信息
        print('开始迭代')
        # 开始迭代
        for i in range(self.max_iterations):
            # 执行期望步骤
            self.expectation_step(input_data)
            # 执行最大化步骤
            self.maximization_step(input_data)
            # 计算新的对数似然值
            new_log_likelihood = self.log_likelihood_value(input_data)
            # 打印当前迭代步数和对数似然值
            print(f'第{i}步, log-likelihood = {new_log_likelihood:.4f}')
            # 如果对数似然值变化小于阈值,停止迭代
            if new_log_likelihood - log_likelihood < 1e-4:
                print('log-likelihood不再变化,退出程序')
                break
            else:
                # 更新对数似然值
                log_likelihood = new_log_likelihood

    # 转换数据,返回每个数据点所属的聚类
    def transform_data(self, input_data):
        # 确保责任矩阵已计算且长度与输入数据一致
        assert hasattr(self, 'responsibilities') and len(self.responsibilities) == len(input_data)
        # 返回每个数据点所属的聚类索引
        return np.argmax(self.responsibilities, axis=1)

    # 拟合并转换数据
    def fit_and_transform(self, input_data):
        # 拟合模型
        self.fit_model(input_data)
        # 转换数据
        return self.transform_data(input_data)

和k均值聚类方法相似,在运用基于最大期望值法的高斯混合模型时,我们来观察一下在 Books 数据集中, 3 个真实类别里不同簇各自所占的比例情况:

nlp_text_clustering.py

# 从nlp_book_data_collection模块导入BookDataCollection类
from nlp_book_data_collection import BookDataCollection
from nlp_gaussian_mixture_model import GaussianMixtureModel
# 从nlp_tfidf_transformer模块导入TFIDFTransformer类
from nlp_tfidf_transformer import TFIDFTransformer
# 从nlp_kmeans_clusterer模块导入KMeansClusterer类
from nlp_kmeans_clusterer import KMeansClusterer
# 从sklearn库中导入主成分分析(PCA)类
from sklearn.decomposition import PCA

# 聚类的数量
num_clusters = 3


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def display_clusters(self, cluster_assignments, num_clusters, book_dataset, labels_list):
        # 初始化标签聚类字典
        label_clusters = {label_id: {} for label_id in book_dataset.id_to_label}
        # 初始化每个标签下每个聚类的计数为0
        for k, v in label_clusters.items():
            label_clusters[k] = {i: 0 for i in range(num_clusters)}
        # 遍历标签和聚类分配
        for label_id, cluster_id in zip(labels_list, cluster_assignments):
            # 增加对应标签和聚类的计数
            label_clusters[label_id][cluster_id] += 1

        # 按标签ID排序遍历
        for label_id in sorted(book_dataset.id_to_label.keys()):
            # 初始化聚类信息字符串
            cluster_str = book_dataset.id_to_label[label_id] + ':\t{ '
            # 遍历每个聚类
            for cluster_id in range(num_clusters):
                # 获取当前聚类的计数
                count = label_clusters[label_id][cluster_id]
                # 计算总数
                total = sum(label_clusters[label_id].values())
                # 拼接聚类信息
                cluster_str += f'{str(cluster_id)}: {count}({count / total:.2f}), '
            # 结束聚类信息字符串
            cluster_str += '}'
            # 打印聚类信息
            print(cluster_str)

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()

        # 获取训练集和测试集数据
        training_data, testing_data = book_dataset.training_data, book_dataset.testing_data

        # 获取词汇表大小
        vocabulary_size = len(book_dataset.token_to_id)
        # 初始化训练输入列表
        training_input = []
        # 遍历训练数据
        for data_item in training_data:
            # 将词元索引添加到训练输入列表
            training_input.append(data_item['token_indices'])
        # 创建TF-IDF转换器对象
        tfidf_transformer = TFIDFTransformer(vocabulary_size, normalization='l2', smooth_idf_flag=True,
                                             sublinear_tf_flag=True)
        # 拟合训练数据
        tfidf_transformer.fit_data(training_input)
        # 转换训练数据
        training_features = tfidf_transformer.transform_data(training_input)
        # 打印训练特征的形状
        print(training_features.shape)

        # 创建K-Means聚类器对象
        kmeans_clusterer = KMeansClusterer(num_clusters, training_features.shape[1])
        # 拟合训练特征
        kmeans_clusterer.fit_model(training_features)

        # 初始化标签列表
        labels_list = []
        # 遍历训练数据
        for data_item in training_data:
            # 将标签添加到标签列表
            labels_list.append(data_item['label'])
        # 打印标签列表的长度
        print(len(labels_list))

        # 获取K-Means聚类结果
        cluster_assignments = kmeans_clusterer.cluster_assignments
        # 显示K-Means聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)

        # 创建PCA降维器对象
        pca_reducer = PCA(n_components=50)
        # 对训练特征进行降维
        training_pca = pca_reducer.fit_transform(training_features)

        # 创建高斯混合模型对象
        gmm_model = GaussianMixtureModel(num_clusters, data_dimension=training_pca.shape[1])
        # 拟合并转换训练数据
        cluster_assignments = gmm_model.fit_and_transform(training_pca)
        # 打印高斯混合模型聚类结果
        print(cluster_assignments)
        # 显示高斯混合模型聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()

高斯混合聚类运行结果

训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [02:36<00:00, 54.95it/s]
100%|██████████| 2156/2156 [00:36<00:00, 59.32it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699
(8626, 9504)
-----------初始化-----------
-----------初始化完成-----------
第1步中心点平均移动距离0.07821463876668384
第2步中心点平均移动距离0.05703827383757104
第3步中心点平均移动距离0.0227878128221168
第4步中心点平均移动距离0.012985270767233279
第5步中心点平均移动距离0.010560323354748833
第6步中心点平均移动距离0.011526155533891311
第7步中心点平均移动距离0.014447526451567915
第8步中心点平均移动距离0.01480361614434264
第9步中心点平均移动距离0.007805741764265261
第10步中心点平均移动距离0.004696899248898042
第11步中心点平均移动距离0.003858954772666219
第12步中心点平均移动距离0.0037634051652670757
第13步中心点平均移动距离0.0036342433215190346
第14步中心点平均移动距离0.004037746948698562
第15步中心点平均移动距离0.0037074902463420808
第16步中心点平均移动距离0.0025126860295755764
第17步中心点平均移动距离0.0017742952721109479
第18步中心点平均移动距离0.001311558516669006
第19步中心点平均移动距离0.0008183675564787064
第20步中心点平均移动距离0.0011545852276947674
第21步中心点平均移动距离0.0006864544106957334
第22步中心点平均移动距离0.0009260959472041178
第23步中心点平均移动距离0.0004974572613901904
第24步中心点平均移动距离0.00047763554726450624
第25步中心点平均移动距离0.000561811847711657
第26步中心点平均移动距离0.00030713124482862106
第27步中心点平均移动距离0.0
中心点不再移动退出程序
8626
计算机类:	{ 0: 1233(0.32), 1: 2572(0.67), 2: 37(0.01), }
艺术传媒类:	{ 0: 74(0.03), 1: 282(0.12), 2: 1943(0.85), }
经管类:	{ 0: 26(0.01), 1: 2452(0.99), 2: 7(0.00), }
开始迭代
第0步, log-likelihood = 77.2117
第1步, log-likelihood = 87.3795
第2步, log-likelihood = 90.4587
第3步, log-likelihood = 91.7301
第4步, log-likelihood = 92.7042
第5步, log-likelihood = 93.5868
第6步, log-likelihood = 94.6852
第7步, log-likelihood = 95.8890
第8步, log-likelihood = 96.4726
第9步, log-likelihood = 96.8001
第10步, log-likelihood = 96.9560
第11步, log-likelihood = 97.0857
第12步, log-likelihood = 97.5848
第13步, log-likelihood = 97.5959
第14步, log-likelihood = 97.6033
第15步, log-likelihood = 97.6140
第16步, log-likelihood = 97.6341
第17步, log-likelihood = 97.6695
第18步, log-likelihood = 97.6801
第19步, log-likelihood = 97.6888
第20步, log-likelihood = 97.7062
第21步, log-likelihood = 97.7420
第22步, log-likelihood = 97.7711
第23步, log-likelihood = 97.8122
第24步, log-likelihood = 97.8207
第25步, log-likelihood = 97.8232
第26步, log-likelihood = 97.8244
第27步, log-likelihood = 97.8255
第28步, log-likelihood = 97.8284
第29步, log-likelihood = 97.8330
第30步, log-likelihood = 97.8486
第31步, log-likelihood = 97.9543
第32步, log-likelihood = 98.0578
第33步, log-likelihood = 98.0620
第34步, log-likelihood = 98.0625
第35步, log-likelihood = 98.0626
第36步, log-likelihood = 98.0628
第37步, log-likelihood = 98.0628
log-likelihood不再变化退出程序
[0 2 0 ... 2 0 2]
计算机类:	{ 0: 2742(0.71), 1: 299(0.08), 2: 801(0.21), }
艺术传媒类:	{ 0: 9(0.00), 1: 421(0.18), 2: 1869(0.81), }
经管类:	{ 0: 829(0.33), 1: 1625(0.65), 2: 31(0.01), }

进程已结束退出代码为 0

无监督朴素贝叶斯模型

无监督朴素贝叶斯模型也是文本聚类的有力工具。它基于贝叶斯定理和特征条件独立假设,认为文本中的各个词之间是相互独立的,就好像每个人都是独立行动的个体。在文本聚类时,它通过统计文本中词的出现频率,结合贝叶斯定理,计算文本属于不同主题(类别)的概率,然后把概率相近的文本归为一类。

这种算法的优点是简单高效,对数据量的要求相对较低,可扩展性也不错。然而,它的特征条件独立假设在实际情况中往往不太符合,因为文本中的词之间其实是有语义关联的;而且它对多义词的处理能力有限,可能会把不同含义的同一个词都当作相同的来处理,影响聚类的准确性;此外,模型的性能很大程度上依赖于文本的表示方式,如果文本表示不准确,聚类效果也会受到影响。

接下来,为大家展示基于朴素贝叶斯模型的聚类算法具体是如何实现的:

朴素贝叶斯模型聚类完整代码

nlp_unsupervised_naive_bayes_classifier.py

# 导入NumPy库,用于数值计算
import numpy as np
# 从scipy库中导入对数求和指数函数
from scipy.special import logsumexp


# 定义无监督朴素贝叶斯分类器类
class UnsupervisedNaiveBayesClassifier:
    # 初始化方法,设置聚类数量、数据维度和最大迭代次数
    def __init__(self, num_clusters, data_dimension, max_iterations=100):
        # 存储聚类数量
        self.num_clusters = num_clusters
        # 存储数据维度
        self.data_dimension = data_dimension
        # 存储最大迭代次数
        self.max_iterations = max_iterations

        # 初始化先验概率,每个聚类初始概率相同
        self.prior_probabilities = np.ones(num_clusters) / num_clusters
        # 随机初始化条件概率
        self.conditional_probabilities = np.random.random((num_clusters, data_dimension))
        # 对条件概率进行归一化处理
        self.conditional_probabilities /= self.conditional_probabilities.sum(axis=1, keepdims=True)

    # 期望步骤,计算每个数据点属于每个聚类的概率
    def expectation_step(self, input_data):
        # 遍历每个数据点
        for i, data_point in enumerate(input_data):
            # 计算对数概率
            self.responsibilities[i, :] = np.log(self.prior_probabilities) + (
                    np.log(self.conditional_probabilities) * data_point).sum(axis=1)
            # 减去对数求和指数,避免数值溢出
            self.responsibilities[i, :] -= logsumexp(self.responsibilities[i, :])
            # 计算概率
            self.responsibilities[i, :] = np.exp(self.responsibilities[i, :])

    # 最大化步骤,更新模型参数
    def maximization_step(self, input_data):
        # 计算每个聚类的责任权重总和
        self.prior_probabilities = self.responsibilities.sum(axis=0) / self.num_samples
        # 对先验概率进行归一化处理
        self.prior_probabilities /= self.prior_probabilities.sum()
        # 遍历每个聚类
        for i in range(self.num_clusters):
            # 更新条件概率
            self.conditional_probabilities[i] = (self.responsibilities[:, i:i + 1] * input_data).sum(axis=0) / \
                                                (self.responsibilities[:, i] * input_data.sum(axis=1)).sum()
        # 避免条件概率为0
        self.conditional_probabilities += 1e-10
        # 对条件概率进行归一化处理
        self.conditional_probabilities /= self.conditional_probabilities.sum(axis=1, keepdims=True)

    # 计算对数似然值
    def log_likelihood_value(self, input_data):
        # 初始化对数似然值为0
        log_likelihood = 0
        # 遍历每个数据点
        for data_point in input_data:
            # 存储每个聚类的对数概率
            log_probabilities = []
            # 遍历每个聚类
            for i in range(self.num_clusters):
                # 计算对数概率
                log_probabilities.append(
                    np.log(self.prior_probabilities[i]) + (
                            np.log(self.conditional_probabilities[i]) * data_point).sum())
            # 累加对数求和指数
            log_likelihood += logsumexp(log_probabilities)
        # 返回平均对数似然值
        return log_likelihood / len(input_data)

    # 拟合模型
    def fit_model(self, input_data):
        # 存储输入数据的样本数量
        self.num_samples = len(input_data)
        # 初始化责任矩阵为全零矩阵
        self.responsibilities = np.zeros((self.num_samples, self.num_clusters))
        # 计算初始对数似然值
        log_likelihood = self.log_likelihood_value(input_data)
        # 打印初始对数似然值
        print(f'初始化log-likelihood = {log_likelihood:.4f}')
        # 打印开始迭代信息
        print('开始迭代')
        # 开始迭代
        for i in range(self.max_iterations):
            # 执行期望步骤
            self.expectation_step(input_data)
            # 执行最大化步骤
            self.maximization_step(input_data)
            # 计算新的对数似然值
            new_log_likelihood = self.log_likelihood_value(input_data)
            # 打印当前迭代步数和对数似然值
            print(f'第{i}步, log-likelihood = {new_log_likelihood:.4f}')
            # 如果对数似然值变化小于阈值,停止迭代
            if new_log_likelihood - log_likelihood < 1e-4:
                print('log-likelihood不再变化,退出程序')
                break
            else:
                # 更新对数似然值
                log_likelihood = new_log_likelihood

    # 转换数据,返回每个数据点所属的聚类
    def transform_data(self, input_data):
        # 确保责任矩阵已计算且长度与输入数据一致
        assert hasattr(self, 'responsibilities') and len(self.responsibilities) == len(input_data)
        # 返回每个数据点所属的聚类索引
        return np.argmax(self.responsibilities, axis=1)

    # 拟合并转换数据
    def fit_and_transform(self, input_data):
        # 拟合模型
        self.fit_model(input_data)
        # 转换数据
        return self.transform_data(input_data)

nlp_text_clustering.py

# 从nlp_book_data_collection模块导入BookDataCollection类
from nlp_book_data_collection import BookDataCollection
from nlp_gaussian_mixture_model import GaussianMixtureModel
# 从nlp_tfidf_transformer模块导入TFIDFTransformer类
from nlp_tfidf_transformer import TFIDFTransformer
# 从nlp_kmeans_clusterer模块导入KMeansClusterer类
from nlp_kmeans_clusterer import KMeansClusterer
# 从sklearn库中导入主成分分析(PCA)类
from sklearn.decomposition import PCA
# 从nlp_unsupervised_naive_bayes_classifier模块导入UnsupervisedNaiveBayesClassifier类
from nlp_unsupervised_naive_bayes_classifier import UnsupervisedNaiveBayesClassifier
# 导入NumPy库,用于数值计算
import numpy as np

# 聚类的数量
num_clusters = 3


# 定义一个用于自然语言处理文本聚类的类
class NLPTextClustering:
    def __init__(self):
        pass

    def display_clusters(self, cluster_assignments, num_clusters, book_dataset, labels_list):
        # 初始化标签聚类字典
        label_clusters = {label_id: {} for label_id in book_dataset.id_to_label}
        # 初始化每个标签下每个聚类的计数为0
        for k, v in label_clusters.items():
            label_clusters[k] = {i: 0 for i in range(num_clusters)}
        # 遍历标签和聚类分配
        for label_id, cluster_id in zip(labels_list, cluster_assignments):
            # 增加对应标签和聚类的计数
            label_clusters[label_id][cluster_id] += 1

        # 按标签ID排序遍历
        for label_id in sorted(book_dataset.id_to_label.keys()):
            # 初始化聚类信息字符串
            cluster_str = book_dataset.id_to_label[label_id] + ':\t{ '
            # 遍历每个聚类
            for cluster_id in range(num_clusters):
                # 获取当前聚类的计数
                count = label_clusters[label_id][cluster_id]
                # 计算总数
                total = sum(label_clusters[label_id].values())
                # 拼接聚类信息
                cluster_str += f'{str(cluster_id)}: {count}({count / total:.2f}), '
            # 结束聚类信息字符串
            cluster_str += '}'
            # 打印聚类信息
            print(cluster_str)

    def text_clustering(self):
        # 创建书籍数据集对象
        book_dataset = BookDataCollection()
        # 打印标签到ID的映射
        print(book_dataset.id_to_label)

        # 对文本进行分词处理
        book_dataset.tokenize_text(attribute='abstract')
        # 创建词汇表
        book_dataset.create_vocabulary(min_frequency=3)
        # 将词元转换为索引
        book_dataset.convert_tokens_to_indices()

        # 获取训练集和测试集数据
        training_data, testing_data = book_dataset.training_data, book_dataset.testing_data

        # 获取词汇表大小
        vocabulary_size = len(book_dataset.token_to_id)
        # 初始化训练输入列表
        training_input = []
        # 遍历训练数据
        for data_item in training_data:
            # 将词元索引添加到训练输入列表
            training_input.append(data_item['token_indices'])
        # 创建TF-IDF转换器对象
        tfidf_transformer = TFIDFTransformer(vocabulary_size, normalization='l2', smooth_idf_flag=True,
                                             sublinear_tf_flag=True)
        # 拟合训练数据
        tfidf_transformer.fit_data(training_input)
        # 转换训练数据
        training_features = tfidf_transformer.transform_data(training_input)
        # 打印训练特征的形状
        print(training_features.shape)

        # 创建K-Means聚类器对象
        kmeans_clusterer = KMeansClusterer(num_clusters, training_features.shape[1])
        # 拟合训练特征
        kmeans_clusterer.fit_model(training_features)

        # 初始化标签列表
        labels_list = []
        # 遍历训练数据
        for data_item in training_data:
            # 将标签添加到标签列表
            labels_list.append(data_item['label'])
        # 打印标签列表的长度
        print(len(labels_list))

        # 获取K-Means聚类结果
        cluster_assignments = kmeans_clusterer.cluster_assignments
        # 显示K-Means聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)

        # 创建PCA降维器对象
        pca_reducer = PCA(n_components=50)
        # 对训练特征进行降维
        training_pca = pca_reducer.fit_transform(training_features)

        # 创建高斯混合模型对象
        gmm_model = GaussianMixtureModel(num_clusters, data_dimension=training_pca.shape[1])
        # 拟合并转换训练数据
        cluster_assignments = gmm_model.fit_and_transform(training_pca)
        # 打印高斯混合模型聚类结果
        print(cluster_assignments)
        # 显示高斯混合模型聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)

        # 初始化训练计数矩阵
        training_count_matrix = np.zeros((len(training_input), vocabulary_size))
        # 遍历训练输入
        for i, data_item in enumerate(training_input):
            # 遍历词元索引
            for token_index in data_item:
                # 增加对应位置的计数
                training_count_matrix[i, token_index] += 1

        # 创建无监督朴素贝叶斯分类器对象
        naive_bayes_classifier = UnsupervisedNaiveBayesClassifier(num_clusters, data_dimension=vocabulary_size,
                                                                  max_iterations=100)
        # 拟合并转换训练数据
        cluster_assignments = naive_bayes_classifier.fit_and_transform(training_count_matrix)
        # 打印无监督朴素贝叶斯分类器聚类结果
        print(cluster_assignments)
        # 显示无监督朴素贝叶斯分类器聚类结果
        self.display_clusters(cluster_assignments, num_clusters, book_dataset, labels_list)


# 程序入口,当作为脚本直接运行时执行以下代码
if __name__ == "__main__":
    # 创建NLPTextClustering类的一个实例
    nlp_text_clustering = NLPTextClustering()
    # 调用实例的text_clustering方法进行文本聚类操作
    nlp_text_clustering.text_clustering()

朴素贝叶斯模型聚类运行结果

训练集大小 = 8626 , 测试集大小 = 2156
{0: '计算机类', 1: '艺术传媒类', 2: '经管类'}
100%|██████████| 8626/8626 [02:36<00:00, 54.95it/s]
100%|██████████| 2156/2156 [00:36<00:00, 59.32it/s]
唯一词元数量 = 34251, 总词元数量 = 806791, 最大词频 = 19194, 最小词频 = 1
最小词频 = 3, 最小词长 = 2, 最大词表大小 = None, 剩余词元数量 = 9504, 词表内词元占比 = 0.8910448926673699
(8626, 9504)
-----------初始化-----------
-----------初始化完成-----------
第1步中心点平均移动距离0.07821463876668384
第2步中心点平均移动距离0.05703827383757104
第3步中心点平均移动距离0.0227878128221168
第4步中心点平均移动距离0.012985270767233279
第5步中心点平均移动距离0.010560323354748833
第6步中心点平均移动距离0.011526155533891311
第7步中心点平均移动距离0.014447526451567915
第8步中心点平均移动距离0.01480361614434264
第9步中心点平均移动距离0.007805741764265261
第10步中心点平均移动距离0.004696899248898042
第11步中心点平均移动距离0.003858954772666219
第12步中心点平均移动距离0.0037634051652670757
第13步中心点平均移动距离0.0036342433215190346
第14步中心点平均移动距离0.004037746948698562
第15步中心点平均移动距离0.0037074902463420808
第16步中心点平均移动距离0.0025126860295755764
第17步中心点平均移动距离0.0017742952721109479
第18步中心点平均移动距离0.001311558516669006
第19步中心点平均移动距离0.0008183675564787064
第20步中心点平均移动距离0.0011545852276947674
第21步中心点平均移动距离0.0006864544106957334
第22步中心点平均移动距离0.0009260959472041178
第23步中心点平均移动距离0.0004974572613901904
第24步中心点平均移动距离0.00047763554726450624
第25步中心点平均移动距离0.000561811847711657
第26步中心点平均移动距离0.00030713124482862106
第27步中心点平均移动距离0.0
中心点不再移动退出程序
8626
计算机类:	{ 0: 1233(0.32), 1: 2572(0.67), 2: 37(0.01), }
艺术传媒类:	{ 0: 74(0.03), 1: 282(0.12), 2: 1943(0.85), }
经管类:	{ 0: 26(0.01), 1: 2452(0.99), 2: 7(0.00), }
开始迭代
第0步, log-likelihood = 77.2117
第1步, log-likelihood = 87.3795
第2步, log-likelihood = 90.4587
第3步, log-likelihood = 91.7301
第4步, log-likelihood = 92.7042
第5步, log-likelihood = 93.5868
第6步, log-likelihood = 94.6852
第7步, log-likelihood = 95.8890
第8步, log-likelihood = 96.4726
第9步, log-likelihood = 96.8001
第10步, log-likelihood = 96.9560
第11步, log-likelihood = 97.0857
第12步, log-likelihood = 97.5848
第13步, log-likelihood = 97.5959
第14步, log-likelihood = 97.6033
第15步, log-likelihood = 97.6140
第16步, log-likelihood = 97.6341
第17步, log-likelihood = 97.6695
第18步, log-likelihood = 97.6801
第19步, log-likelihood = 97.6888
第20步, log-likelihood = 97.7062
第21步, log-likelihood = 97.7420
第22步, log-likelihood = 97.7711
第23步, log-likelihood = 97.8122
第24步, log-likelihood = 97.8207
第25步, log-likelihood = 97.8232
第26步, log-likelihood = 97.8244
第27步, log-likelihood = 97.8255
第28步, log-likelihood = 97.8284
第29步, log-likelihood = 97.8330
第30步, log-likelihood = 97.8486
第31步, log-likelihood = 97.9543
第32步, log-likelihood = 98.0578
第33步, log-likelihood = 98.0620
第34步, log-likelihood = 98.0625
第35步, log-likelihood = 98.0626
第36步, log-likelihood = 98.0628
第37步, log-likelihood = 98.0628
log-likelihood不再变化退出程序
[0 2 0 ... 2 0 2]
计算机类:	{ 0: 2742(0.71), 1: 299(0.08), 2: 801(0.21), }
艺术传媒类:	{ 0: 9(0.00), 1: 421(0.18), 2: 1869(0.81), }
经管类:	{ 0: 829(0.33), 1: 1625(0.65), 2: 31(0.01), }
初始化log-likelihood = -779.6002
开始迭代
第0步, log-likelihood = -589.5137
第1步, log-likelihood = -583.1897
第2步, log-likelihood = -579.2382
第3步, log-likelihood = -577.0352
第4步, log-likelihood = -575.8822
第5步, log-likelihood = -575.3356
第6步, log-likelihood = -574.8611
第7步, log-likelihood = -574.5028
第8步, log-likelihood = -574.2625
第9步, log-likelihood = -574.1030
第10步, log-likelihood = -573.9690
第11步, log-likelihood = -573.7704
第12步, log-likelihood = -573.6187
第13步, log-likelihood = -573.4686
第14步, log-likelihood = -573.3280
第15步, log-likelihood = -573.2186
第16步, log-likelihood = -573.1160
第17步, log-likelihood = -572.9857
第18步, log-likelihood = -572.8356
第19步, log-likelihood = -572.7076
第20步, log-likelihood = -572.4683
第21步, log-likelihood = -572.3301
第22步, log-likelihood = -572.2375
第23步, log-likelihood = -572.0611
第24步, log-likelihood = -571.8587
第25步, log-likelihood = -571.7117
第26步, log-likelihood = -571.6412
第27步, log-likelihood = -571.6158
第28步, log-likelihood = -571.5863
第29步, log-likelihood = -571.5397
第30步, log-likelihood = -571.5281
第31步, log-likelihood = -571.5163
第32步, log-likelihood = -571.4902
第33步, log-likelihood = -571.4693
第34步, log-likelihood = -571.4604
第35步, log-likelihood = -571.4457
第36步, log-likelihood = -571.4339
第37步, log-likelihood = -571.4301
第38步, log-likelihood = -571.4288
第39步, log-likelihood = -571.4272
第40步, log-likelihood = -571.4257
第41步, log-likelihood = -571.4254
第42步, log-likelihood = -571.4176
第43步, log-likelihood = -571.4060
第44步, log-likelihood = -571.4028
第45步, log-likelihood = -571.4027
log-likelihood不再变化退出程序
[0 1 0 ... 1 0 1]
计算机类:	{ 0: 2266(0.59), 1: 1479(0.38), 2: 97(0.03), }
艺术传媒类:	{ 0: 64(0.03), 1: 703(0.31), 2: 1532(0.67), }
经管类:	{ 0: 2237(0.90), 1: 117(0.05), 2: 131(0.05), }

进程已结束退出代码为 0

结束

好了,以上就是本次分享的全部内容了。不知道大家是否对自然语言处理中的文本聚类有了更深入的了解呢?

本次分享主要围绕文本聚类展开,介绍了几种常见的文本聚类方法,包括 k 均值聚类算法、基于高斯混合模型的最大期望值算法和无监督朴素贝叶斯模型。

  • k均值聚类算法: 简单且经典,计算效率高,但需要预先指定聚类数量 k,对初始聚类中心的选择比较敏感。
  • 基于高斯混合模型的最大期望值算法: 能够灵活地拟合复杂的数据分布,对于多模态的数据聚类效果较好,但计算复杂度高,对超参数的选择敏感,容易陷入局部最优解。
  • 无监督朴素贝叶斯模型: 简单高效,对数据量的要求相对较低,可扩展性不错,但特征条件独立假设在实际情况中往往不太符合,对多义词的处理能力有限,模型性能依赖于文本的表示方式。

文本聚类在自然语言处理领域有着广泛的应用前景,如信息检索、文本分类、情感分析等。随着技术的不断发展,未来可能会出现更高效、更准确的文本聚类算法。同时,结合 深度学习 等技术,也有望进一步提升文本聚类的效果。

那么本次分享就到这里了。最后,博主还是那句话: 请大家多去大胆的尝试和使用,成功总是在不断的失败中试验出来的,敢于尝试就已经成功了一半 。如果大家对博主分享的内容感兴趣或有帮助,请点赞和关注。大家的点赞和关注是博主持续分享的动力🤭,博主也希望让更多的人学习到新的知识。