文本聚类算法在大数据分析中的实战应用

文本聚类的实战力量:从海量数据中挖掘隐藏的智慧

无需预设标签,让数据自动“抱团”,揭示你不知道的深层规律

摘要

在大数据时代,海量文本信息汹涌而来。产品评论、新闻资讯、用户反馈、社交媒体内容…它们包含着巨大的价值,却也杂乱无章。文本聚类算法作为无监督学习的核心利器,不需要预定义标签,就能自动将相似的文本归为一组。本文将深入探讨K-Means、DBSCAN、层次聚类及主题模型(LDA)等经典算法在大规模文本处理中的实战应用场景、实现细节与优化技巧。你将学习如何利用Python生态的强大工具(scikit-learn, Gensim, spaCy)从预处理、特征工程、算法选型到结果分析与可视化,构建端到端的文本聚类流水线。通过三个实战案例(用户评论分组、新闻主题发现、社交媒体话题追踪),你将掌握在不同业务场景下,用聚类算法挖掘文本数据深层洞见的核心能力。

目标读者与前置知识

目标读者: 具备一定Python基础和数据处理经验(如熟悉Pandas, NumPy),了解基本机器学习概念,但对无监督学习特别是文本聚类实战应用经验较少的数据分析师、数据科学家或后端/全栈开发者。前置知识:
Python编程基础熟悉Pandas数据处理基本操作了解机器学习基本概念(如向量、距离度量)(加分项) 接触过基础的自然语言处理(NLP)概念(如分词、停用词)

文章目录

引言:为什么文本聚类是大数据分析的刚需?文本聚类的核心原理简述准备工作:环境、库与数据获取文本数据清洗与预处理实战特征工程:将文本转换为机器理解的语言算法竞技场:主流文本聚类算法详解与选择案例实战一:用户评论情感与主题分组(K-Means)案例实战二:新闻主题层次化发现(Hierarchical + LDA)案例实战三:社交媒体动态话题追踪(DBSCAN)聚类结果评估:不只是数量,更要质量结果分析与可视化:让洞见跃然纸上性能优化与挑战:应对海量文本常见问题排查 (FAQ/Troubleshooting)总结与未来展望参考资料与附录


第一部分:引言与基础

1. 引言:为什么文本聚类是大数据分析的刚需?

想象一下:

产品经理面对百万条杂乱无章的APP用户评论。内容运营被淹没在每日上万条的新闻稿件中。品牌公关需要实时监测网络舆情热点。学术研究者需要梳理海量文献的主题脉络。

在这些场景下,手动阅读、分类和总结是不可能完成的任务。我们需要自动化工具来理解、组织并提炼海量文本的核心信息。这就是文本聚类技术的用武之地:

无监督学习: 无需预先定义好的类别标签(例如:“好评”、“差评”、“功能需求”),算法自动发现数据的自然分组。这在现实世界中往往更可行,因为人工标注海量文本成本高昂。降维与洞察: 将无数个零散的文档组织成少数的、有意义的群组(簇
cluster
),显著降低认知负担,快速发现数据的主要模式(如主要用户抱怨点、热点话题、新兴趋势)。数据探索的第一步: 对于任何新获取的海量文本数据集,聚类往往是理解其潜在结构和关键内容的第一个有力步骤。它能为进一步的深入分析(如情感分析、主题建模)指明方向。驱动业务决策: 发现用户群体的细分需求、识别产品缺陷、监控品牌声誉、优化内容推荐系统等。

一句话总结:文本聚类让无序的文本海洋涌现出有意义的岛屿。

2. 文本聚类的核心原理简述

无论算法多么复杂,文本聚类解决的核心问题都类似:“哪些文本彼此相似,应该聚在一起?” 其核心流程抽象如下:

文本表示 (Representation): 将人类可读的文本转换为机器可计算的数学形式(通常是向量
Vector
)。这是最关键的步骤之一。相似度/距离计算 (Similarity/Distance Measurement): 定义衡量两个文本向量之间“相似”或“不同”的数学标准(如
欧氏距离

余弦相似度

杰卡德距离
)。聚类算法应用 (Clustering Algorithm): 利用选定的算法(如K-Means),基于计算出的相似度/距离,将文本向量划分到不同的组(簇)中。核心是寻找组内相似度最大、组间相似度最小。结果评估与解释 (Evaluation & Interpretation): 评估聚类结果的好坏,并对每个簇进行语义解释(例如,找出该簇的代表性词语或主题)。

关键概念:

簇 (Cluster): 一组被算法判定为相似的文本集合。簇中心 (Centroid): (在K-Means中)代表一个簇平均位置的点向量。理论上可以看作是簇的“中心点”或“代表点”。簇内/簇间距离: 评估聚类紧密性和分离性的核心指标。

3. 准备工作:环境、库与数据获取

环境要求:

Python (推荐 3.8+)包管理工具 (
pip

conda
)

核心库安装 (pip):


pip install pandas numpy scikit-learn matplotlib seaborn nltk gensim wordcloud spacy

下载必要的NLTK资源 (在Python中运行):


import nltk
nltk.download('punkt')  # 分词模型
nltk.download('stopwords') # 英文停用词表
# 若处理中文,通常需要结巴分词(Jieba):pip install jieba

获取示例数据:
实战中我们需要真实的数据。这里推荐几个来源:

Kaggle: 大量公开数据集 (如电商评论、新闻数据集、Reddit帖子)。UCI Machine Learning Repository: 经典数据集来源。Scikit-learn内置数据集: 如新闻组数据集 (
fetch_20newsgroups
).自爬取数据 (遵守robots.txt): 如社交媒体API (Twitter, 微博),新闻网站。
本次文章示例将主要使用Kaggle上的“Amazon Fine Food Reviews”(小型)和模拟生成的新闻标题数据(中型)。

创建虚拟数据集示例 (Python Pandas):


import pandas as pd

# 模拟用户评论数据
reviews_data = {
    'review_id': [1, 2, 3, 4, 5, 6, 7, 8],
    'text': [
        "This product is amazing! It works perfectly and arrived fast.",
        "Terrible quality. Broke after two days. Waste of money.",
        "The customer service was very helpful in resolving my issue.",
        "I love the design, but the battery life is too short.",
        "Fast shipping, good price, exactly as described.",
        "Not what I expected. Pictures are misleading.",
        "Easy to set up and use. Highly recommend.",
        "Software bug causes constant crashing. Very frustrated."
    ],
    'review_time': ['2023-10-01', '2023-10-02', '2023-10-02', '2023-10-03', '2023-10-04', '2023-10-05', '2023-10-05', '2023-10-06']
}
reviews_df = pd.DataFrame(reviews_data)

第二部分:核心内容

4. 文本数据清洗与预处理实战 (The Dirty Work!)

原始文本数据通常充满“噪声”,清洗和预处理至关重要:


import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer  # 或 WordNetLemmatizer (推荐)
# nltk.download('wordnet') # 若使用WordNetLemmatizer
# nltk.download('omw-1.4') # 多语言支持

# 定义预处理函数
def preprocess_text(text, language='english'):
    """
    对单条文本进行清洗和预处理。
    参数:
        text: 原始文本字符串。
        language: 文本语言 ('english', 'chinese'等)。
    返回:
        预处理后的文本字符串(单词列表)或分词后的列表(取决于后续特征工程需求)。
    """
    # 1. 小写化 (Lowercasing)
    text = text.lower()

    # 2. 去除特殊字符、数字、标点 (但注意: 根据需求保留某些标点,如情感分析中的'!')
    text = re.sub(r'[^a-zA-Zs]', '', text)  # 移除非字母字符 (英文简单版)
    # 中文可能需要更复杂的处理: 比如移除特定标点符号但保留句法结构,或用结巴分词处理

    # 3. 分词 (Tokenization)
    if language == 'english':
        tokens = nltk.word_tokenize(text)
    elif language == 'chinese':
        # 使用结巴分词
        import jieba
        tokens = list(jieba.cut(text))
    else:
        tokens = text.split()  # fallback

    # 4. 移除停用词 (Stopword Removal)
    stop_words = set(stopwords.words(language))
    tokens = [word for word in tokens if word not in stop_words]

    # 5. 词干化(Stemming) 或 词形还原(Lemmatization) - 词形还原通常更准确但慢
    # 选择一种:
    # a) 词干化 (激进)
    # stemmer = PorterStemmer()
    # tokens = [stemmer.stem(word) for word in tokens]
    # b) 词形还原 (推荐,更准确)
    lemmatizer = nltk.stem.WordNetLemmatizer()
    tokens = [lemmatizer.lemmatize(word) for word in tokens] # 通常需要POS标签提升精度,简化版此处省略

    # 6. (可选) 移除超短词
    tokens = [word for word in tokens if len(word) > 2]

    # 根据需求: 返回拼接字符串或词列表
    # return " ".join(tokens)  # 后续如用CountVectorizer可能直接要字符串
    return tokens  # 后续如用gensim需要词列表

# 应用预处理到DataFrame列
reviews_df['clean_text'] = reviews_df['text'].apply(preprocess_text)
print(reviews_df[['text', 'clean_text']].head())

预处理效果示例:

原始文本 清洗后 (词列表示例 – 英文)
“This product is amazing! It works perfectly…”
['product', 'amazing', 'work', 'perfectly', 'arrived', 'fast']
“Terrible quality. Broke after two days…”
['terrible', 'quality', 'broke', 'two', 'day', 'waste', 'money']
“I love the design, but the battery…”
['love', 'design', 'battery', 'life', 'short']
5. 特征工程:将文本转换为机器理解的语言

清洗后的文本还是单词序列,机器学习算法需要数值向量。这是文本聚类的灵魂一步

主流特征表示方法:

词袋模型 (Bag of Words, BoW) – 基础且常用

忽略单词顺序和语法,只关心单词是否出现及出现频率。CountVectorizer: 统计每个词在文档中的出现次数。TF-IDF Vectorizer: 衡量一个词对文档的重要程度(
TF(词频)
*
IDF(逆文档频率)
)。TF-IDF通常效果优于简单词频。


from sklearn.feature_extraction.text import TfidfVectorizer

# 注意: 如果`clean_text`是词列表,我们需要先拼接成字符串(或者用TfidfVectorizer的tokenizer参数)
reviews_df['clean_text_str'] = reviews_df['clean_text'].apply(lambda x: ' '.join(x))

# 创建TF-IDF向量器 (关键参数: max_features, max_df, min_df)
tfidf_vectorizer = TfidfVectorizer(max_features=1000)  # 限制最大特征(词汇)数量
tfidf_matrix = tfidf_vectorizer.fit_transform(reviews_df['clean_text_str'])

# tfidf_matrix 是稀疏矩阵 (Sparse Matrix), 形状为 (文档数, 词汇量)
print(f"文档数量: {tfidf_matrix.shape[0]}, 词汇表大小: {tfidf_matrix.shape[1]}")

N元语法 (N-grams)

捕捉相邻词的组合模式(如“电池寿命”,“退货困难”)。通过向
TfidfVectorizer
传递
ngram_range=(1, 2)
实现(使用1元语法和2元语法)。


tfidf_ngram_vectorizer = TfidfVectorizer(max_features=1000, ngram_range=(1, 2))
tfidf_ngram_matrix = tfidf_ngram_vectorizer.fit_transform(reviews_df['clean_text_str'])

词嵌入 (Word Embeddings)

更先进的表示方法,如Word2Vec, GloVe, FastText, BERT Embeddings。单词被映射到稠密向量(
dense vector
),词义相近的单词向量距离近。文档向量化(Doc2Vec): 直接为整个文档生成向量。词向量平均/加权平均 (Word Embeddings Aggregation): 简单有效的方法:取文档中所有词(或去除停用词后)的向量平均值(或TF-IDF加权平均)作为文档向量。


# 使用预训练词向量 (例如gensim的glove-twitter-25)
from gensim.models import KeyedVectors
# 下载预训练模型 (首次运行需要下载)
# !wget https://nlp.stanford.edu/data/glove.twitter.27B.zip
# !unzip glove.twitter.27B.zip
# 加载GloVe向量 (示例使用25维, 文件小)
glove_path = "glove.twitter.27B.25d.txt"  # 替换为实际路径
w2v_model = KeyedVectors.load_word2vec_format(glove_path, binary=False, no_header=True) # 非二进制格式,无头部行

# 定义函数将文档转换为词向量平均
def document_to_vector(tokens, model, dimensionality=25):
    valid_vectors = []
    for token in tokens:
        if token in model:  # 检查词是否在词表
            valid_vectors.append(model[token])
    if len(valid_vectors) > 0:
        return np.mean(valid_vectors, axis=0)
    else:
        return np.zeros(dimensionality)  # 返回零向量

# 应用函数
doc_vectors = reviews_df['clean_text'].apply(lambda tokens: document_to_vector(tokens, w2v_model))
doc_vector_matrix = np.vstack(doc_vectors.values)  # 转为(n_docs, dim)的numpy数组

主题模型向量 (如LDA)

使用LDA等主题模型为文档生成“主题概率分布”向量(
Topic Distribution Vector
)。这本身也是一种降维和特征提取方法,可以作为下游聚类的输入特征。(后续在案例中结合)

选择哪种特征?

中小型数据集+基础需求: TF-IDF (加N-gram) 通常是起点且效果不错。捕捉词义关联: 词嵌入平均或句嵌入。追求最高精度: Transformer(BERT, etc.)的句向量,但计算成本高。发现可解释主题后聚类: LDA主题分布向量。实践建议: 可以先尝试TF-IDF或词向量平均。关键是对不同方法的效果做比较(通过聚类评估指标和业务解释)。

6. 算法竞技场:主流文本聚类算法详解与选择

选好特征后,就要挑选“裁判”算法来决定哪些文本属于同一组。

K-Means:简单高效的明星

原理: 事先指定簇数K。随机初始化K个中心点。迭代:1)将每个点分配到最近的中心点形成簇;2)根据簇内点重新计算中心点;直到中心点变化很小。优点: 简单、高效、易于实现和并行化、是其他算法的基础、在球形簇上效果佳。缺点: 必须指定K;对异常值敏感;对非球形簇效果差;结果受初始点影响;倾向于生成大小相近的簇;对特征缩放敏感。适用场景: 特征空间稠密(如TF-IDF, 嵌入向量)、数据量大、对速度要求高、特征维度可降维(如PCA)到球形空间、能预估大致簇数。Python实现 (scikit-learn):


from sklearn.cluster import KMeans

# 使用TF-IDF特征
num_clusters = 4  # 预估簇数 (选择策略见后续)
kmeans = KMeans(n_clusters=num_clusters, init='k-means++', n_init=10, max_iter=300, random_state=42)
kmeans.fit(tfidf_matrix)  # 传入稀疏/稠密矩阵
cluster_labels = kmeans.labels_  # 每个文档的簇标签

# 添加到原始DataFrame
reviews_df['cluster_kmeans'] = cluster_labels

层次聚类 (Hierarchical Clustering):寻找层级关系

原理: 不需要事先指定K。构建树状结构(树图
Dendrogram
):
凝聚 (Agglomerative): (更常用) 自底向上:开始时每个点是一个簇,逐步合并最近的两个簇,直到所有点都在一个簇中。分裂 (Divisive): 自顶向下:开始时所有点在一个簇,逐步分裂出相距最远的子簇。
关键点: 需要定义簇间距离度量
最小距离
(单链),
最大距离
(全链),
平均距离
,
Ward距离
(最小化簇内方差平方和,效果常较好)。优点: 可得到数据的层次结构(树图直观);不需要预先指定簇数K;对噪声和异常值相对K-Means稍好些(取决于连接方式)。缺点: 计算复杂度高(
O(n³)
),不适合大样本;对合并/分裂点的选择敏感;结果解释相对K-Means复杂;树图对大量点不友好。适用场景: 中小数据集;特别关注簇的层次关系;数据可能存在嵌套结构;不确定具体簇数时可探索树图。Python实现 (scikit-learn):


from sklearn.cluster import AgglomerativeClustering
from sklearn.metrics.pairwise import cosine_similarity
import scipy.cluster.hierarchy as sch
import matplotlib.pyplot as plt

# 1. 计算距离矩阵 (例如余弦距离: 1 - 余弦相似度, 因为scikit层次聚类需要距离)
distance_matrix = 1 - cosine_similarity(tfidf_matrix[0:50]) # 取前50个样本计算距离矩阵避免太大
# 真实场景可能需用PCA降维或采样后再用层次聚类以降低计算量

# 2. 进行层次聚类
# 通过树图确定大致簇数
dendrogram = sch.dendrogram(sch.linkage(distance_matrix, method='ward'))
plt.title('Dendrogram')
plt.xlabel('Documents (Sample)')
plt.ylabel('Euclidean Distance (Ward)')
plt.show()

# 观察树图决定高度(距离阈值)或簇数
chosen_num_clusters = 4  # 假设根据树图选择4
agg_clustering = AgglomerativeClustering(n_clusters=chosen_num_clusters, affinity='euclidean', linkage='ward')
# 注意: 如果特征空间很大且稀疏,'euclidean'可能不如'cosine'合适,但'ward'方法通常要求euclidean或manhattan。
# 一种变通:使用L2标准化后的TF-IDF矩阵配合'ward'和'euclidean'
cluster_labels = agg_clustering.fit_predict(normalized_tfidf_matrix)  # 假设 normalized_tfidf_matrix 是L2标准化后的

# 添加到DF (如果需要全量数据,可能需要策略如:在样本上聚类后分配全量点到最近簇中心)

DBSCAN (Density-Based Spatial Clustering of Applications with Noise): 发现任意形状的簇

原理: 基于密度。不需要指定K。定义核心点(邻域半径
eps
内至少有
min_samples
个点)、边界点(在核心点eps内但不是核心点)、噪声点(其它点)。核心点与其密度可达点组成一个簇。优点: 能发现任意形状的簇;能识别噪声点;对异常值鲁棒;不需要预先指定簇数;聚类效果对初始点不敏感。缺点: 参数(
eps
,
min_samples
)选择非常敏感且可能困难;对高维数据“维度灾难”敏感(距离可能失效);不适合密度差异大的数据集;大样本时内存消耗较高(需要距离矩阵或索引);边界点归属可能不稳定。适用场景: 数据中存在不规则的密集区域和稀疏区域(如地理空间、社交媒体热点);噪声或异常值较多;无法预估簇数但对识别噪声有需求;特征空间维度可控(最好配合降维)。Python实现 (scikit-learn):


from sklearn.cluster import DBSCAN
# 重要:文本向量通常高维稀疏,直接DBSCAN效果差!需要先做降维(如PCA/TSNE)或使用词嵌入平均得到稠密向量。
# 假设我们有一个稠密向量矩阵 doc_vector_matrix (如词嵌入平均)

# 尝试参数 (参数调优是关键!)
dbscan = DBSCAN(eps=0.5, min_samples=5, metric='euclidean')
cluster_labels = dbscan.fit_predict(doc_vector_matrix)

# DBSCAN标签: 簇标签为 -1 表示噪声点(Noise)
reviews_df['cluster_dbscan'] = cluster_labels
print(f"发现的簇数: {len(set(cluster_labels)) - (1 if -1 in cluster_labels else 0)}")
print(f"噪声点数量: {(cluster_labels == -1).sum()}")

主题模型+聚类:LDA作为特征提取器

思路: 潜在狄利克雷分布(Latent Dirichlet Allocation, LDA)本身是一种主题模型,它假设文档是多个主题的混合,而每个主题是词的混合。它为每个文档生成一个“主题概率分布”向量。优势: 得到的特征是文档的概率分布向量,维度等于主题数(通常为几十到几百),大大降低了原始特征的维度(原始词汇表可能有上万维)。这种向量捕捉了文档的语义主题组成,适合作为后续K-Means或层次聚类的输入。主题模型+聚类的结果通常具有更好的语义可解释性。流程:
预处理文本。创建词典和词袋(BOW)表示。训练LDA模型,得到文档-主题分布矩阵
doc_topic_matrix
。将此矩阵输入聚类算法(如K-Means)。
Python实现 (Gensim):


from gensim import corpora, models

# 假设 `processed_docs` 是预处理后的词列表构成的列表
# reviews_df['clean_text'] 已经是我们预处理后的词列表
processed_docs = reviews_df['clean_text'].tolist()

# 1. 创建词典 (ID->Word映射)
dictionary = corpora.Dictionary(processed_docs)
# (可选) 过滤掉太频或太低频的词
dictionary.filter_extremes(no_below=5, no_above=0.5)  # 至少在5个文档出现,最多50%文档出现

# 2. 创建词袋(BoW)语料库:每个文档-> (ID, 词频) 列表
corpus_bow = [dictionary.doc2bow(doc) for doc in processed_docs]

# 3. 训练LDA模型
num_topics = 8  # 设定主题数
lda_model = models.LdaModel(corpus=corpus_bow,
                            id2word=dictionary,
                            num_topics=num_topics,
                            iterations=50,
                            passes=10,
                            alpha='auto',
                            eta='auto',   # 超参数自动优化
                            random_state=42)

# 4. 获取所有文档的主题分布向量 (列表)
doc_topic_matrix = []
for doc_bow in corpus_bow:
    doc_topics = lda_model.get_document_topics(doc_bow, minimum_probability=0.0) # 获取文档属于每个主题的概率
    topic_dist = [prob for _, prob in doc_topics]  # 提取概率值
    doc_topic_matrix.append(topic_dist)

doc_topic_matrix = np.array(doc_topic_matrix)  # 转成Numpy数组, shape=(n_docs, n_topics)

# 5. 将主题分布作为特征进行聚类 (如K-Means)
kmeans_lda = KMeans(n_clusters=4, random_state=42)
cluster_labels_lda = kmeans_lda.fit_predict(doc_topic_matrix)
reviews_df['cluster_lda_kmeans'] = cluster_labels_lda

# 可选: 直接观察LDA主题并匹配到聚类
lda_model.print_topics(num_topics=num_topics, num_words=10)

LDA训练参数说明:


num_topics
: 主题数K(类比聚类数,但含义不同)。
passes
,
iterations
: 控制训练次数/强度,值越大越可能收敛但训练越慢。
alpha
: Dirichlet先验参数,控制文档主题分布的稀疏性(低->稀疏, 每个文档只有少数主题;高->均匀)。
eta
: Dirichlet先验参数,控制主题词分布的稀疏性(低->稀疏, 每个主题只有少量核心词;高->均匀)。
'auto'
是让模型学习超参数。

算法选择指南:

场景特征 推荐算法 理由
数据量大,特征稠密,需要快速度 K-Means 计算效率高,复杂度
O(nK)
需要可解释的层次关系 层次聚类 (Agglomerative) 树图展示层次结构
簇形状不规则,数据含噪声/异常值 DBSCAN 可发现任意形状簇,区分噪声
高度追求聚类的语义可解释性 LDA (特征提取) + K-Means / 层次聚类 LDA的主题词列表为每个簇提供直观描述
特征维度非常高且稀疏,无法很好降维 DBSCAN (小心参数)/LDA+K-Means DBSCAN对高维敏感,LDA降维后再聚类更有效
不确定簇数想探索 层次聚类 /
Elbow
/
Silhouette
选K
树图直观;指标辅助选择最佳K
有大致预估的簇数K K-Means 易于实现,直接使用预估K

如何决定K值(对于K-Means、LDA、层次聚类的目标簇数)?
没有银弹,常结合使用:

领域知识/业务预估: 用户可能希望分成几类?大致主题方向?方法一:肘部法则 (Elbow Method) – K-Means
计算不同K值下的
簇内平方和 (WCSS)

畸变值(distortion)
。绘制
WCSS ~ K
曲线。曲线拐点(类似肘部)对应较优K值。


distortions = []
K_range = range(2, 15) # 考察K从2到14
for k in K_range:
    km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42)
    km.fit(tfidf_matrix) # 使用特征矩阵
    distortions.append(km.inertia_) # inertia_就是WCSS

plt.figure(figsize=(10, 5))
plt.plot(K_range, distortions, 'bx-')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('Within-Cluster Sum of Squares (WCSS) / Inertia')
plt.title('The Elbow Method')
plt.grid()
plt.show() # 找"肘部"点

方法二:轮廓系数 (Silhouette Coefficient) – K-Means, 层次
衡量一个点在自己簇内的紧密度 vs 与其他簇的分隔度。计算所有点的轮廓系数(
[-1, 1]
)并求平均(
平均轮廓系数
)。值越接近1越好。


from sklearn.metrics import silhouette_score

silhouette_scores = []
for k in K_range:
    km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42)
    labels = km.fit_predict(tfidf_matrix)
    score = silhouette_score(tfidf_matrix, labels) # 计算平均轮廓系数
    silhouette_scores.append(score)

plt.figure(figsize=(10, 5))
plt.plot(K_range, silhouette_scores, 'rx-')
plt.xlabel('Number of Clusters (K)')
plt.ylabel('Average Silhouette Coefficient')
plt.title('Silhouette Score')
plt.grid()
plt.show() # 选择分数最高的K,或最高点附近最稳定陡峭下降的K

层次聚类的树图: 通过观察树图中纵轴(距离)上的“较长连接线”来切割,以此确定自然簇数。

7. 案例实战一:用户评论情感与主题分组 (K-Means + TF-IDF)

场景与目标: 电商网站用户评论(数千条)。目标是自动将评论分组,以便分析:
* 用户主要在讨论哪些方面(性能?价格?客服?物流?)
* 评论的隐含情感(虽然聚类是无监督,但常结合词云和关键词发现情感模式)。
工具栈: Python, Pandas, Scikit-learn (TFidfVectorizer, KMeans), WordCloud.

步骤:

加载数据: 使用实际数据集(如Amazon Fine Food Reviews)。确保包含评论文本
Text
,可能还有评分
Score
、产品ID等。清洗与预处理: 应用前述预处理函数。TF-IDF向量化:


tfidf = TfidfVectorizer(max_df=0.95, min_df=10, stop_words='english', max_features=2000)
tfidf_matrix = tfidf.fit_transform(reviews_df['clean_text_str'])


max_df
: 忽略在超过95%文档中出现的高频词(可能是停用词)。
min_df
: 忽略在少于10个文档中出现的低频词。
max_features
: 限制词汇表大小到2000个最相关词。
K-Means聚类:


# 结合领域知识(评分分布,初步看主题)和肘部法/轮廓系数确定K。假设K=5
kmeans = KMeans(n_clusters=5, random_state=42, n_init=10)
kmeans.fit(tfidf_matrix)
reviews_df['cluster'] = kmeans.labels_

分析每个簇:
统计信息: 每个簇的平均评分、评论数量、常见产品ID等。


cluster_summary = reviews_df.groupby('cluster').agg({
    'Score': ['mean', 'count'],  # 平均评分,评论数量
    'ProductId': lambda x: x.value_counts().index[0]  # 该簇最常见的商品ID
}).reset_index()

关键词提取 (词云 + TF-IDF权重):
方法一: 找出该簇所有文档中TF-IDF得分最高的词(根据原
tfidf_matrix
)。


def get_top_keywords(cluster_id, n_terms=20):
    # 获取属于该cluster的所有文档在TF-IDF矩阵中的行索引
    cluster_indices = reviews_df[reviews_df['cluster'] == cluster_id].index
    # 计算该簇内所有文档的TF-IDF特征(列)的平均值 (轴=0)
    avg_tfidf = np.asarray(tfidf_matrix[cluster_indices].mean(axis=0)).flatten()
    # 获取特征名(词汇表)
    feature_names = np.array(tfidf.get_feature_names_out())
    # 根据平均TF-IDF得分排序 (降序) 并取前n_terms个词
    top_keywords_idx = avg_tfidf.argsort()[-n_terms:][::-1]
    top_keywords = feature_names[top_keywords_idx]
    top_scores = avg_tfidf[top_keywords_idx]
    return list(zip(top_keywords, top_scores))

# 为每个簇提取关键词
cluster_keywords = {}
for cid in range(5): # 假设我们有5个簇
    cluster_keywords[cid] = get_top_keywords(cid)

# 打印簇0的前10关键词
print("Cluster 0 Top Keywords:", [word for word, score in cluster_keywords[0][:10]])

方法二:使用词云 (WordCloud):


from wordcloud import WordCloud
import matplotlib.pyplot as plt

def plot_wordcloud_for_cluster(cluster_id):
    text = " ".join(reviews_df[reviews_df['cluster'] == cluster_id]['clean_text_str'])
    wordcloud = WordCloud(width=800, height=400, background_color='white').generate(text)
    plt.figure(figsize=(10, 5))
    plt.imshow(wordcloud, interpolation='bilinear')
    plt.title(f'Cluster {cluster_id} Word Cloud')
    plt.axis('off')
    plt.show()

plot_wordcloud_for_cluster(0)

解读实例:
簇0: 平均评分~4.8,Top词
['great', 'love', 'perfect', 'delicious', 'excellent', 'taste', 'product', 'good']
。词云显示“love”, “great”, “delicious”巨大。解读:高度满意评论,聚焦于口味和产品品质。簇1: 平均评分~3.0,Top词
['price', 'expensive', 'cheaper', 'worth', 'store', 'pay', 'value', 'money']
解读:价格关注点,认为性价比不高或抱怨贵。簇2: 平均评分~1.5,Top词
['bad', 'horrible', 'taste', 'spoiled', 'wrong', 'rotten', 'quality', 'return']
。词云巨大“bad”,“horrible”。解读:极其负面评论,涉及产品质量、腐败、发货错误、退货要求。簇3: 平均评分~4.0,Top词
['dog', 'pet', 'food', 'love', 'healthy', 'vet', 'eat', 'recommend']
解读:宠物食品相关讨论,评价尚可,关注健康建议。簇4: 平均评分~3.5,Top词
['ship', 'time', 'arrive', 'long', 'delay', 'shipping', 'expected', 'week']
解读:物流问题(延迟、时间长),影响整体评价。

价值: 快速识别核心用户情绪点(负面集中在物流和产品质量)、重要话题(价格敏感度、宠物食品特定需求)。优先处理负分簇(簇2:质量问题;簇4:物流问题)和价格反馈(簇1)。

8. 案例实战二:新闻主题层次化发现 (Hierarchical + LDA)

场景与目标: 新闻聚合网站每日收录数千篇新闻。目标是将新闻分层级地组织成主题(如“科技 > 人工智能”、“体育 > 篮球”),便于导航和个性化推荐。
工具栈: Python, Pandas, Gensim (LdaModel), Scikit-learn (Pca, AgglomerativeClustering), Scipy (dendrogram)

步骤:

加载与预处理: 加载新闻数据集(标题+摘要或正文)。应用预处理(包括实体、特定停用词处理)。

LDA建模:


# 创建BoW语料库 (Dictionary, Corpus)
dictionary = corpora.Dictionary(processed_news)
dictionary.filter_extremes(no_below=10, no_above=0.5)
corpus = [dictionary.doc2bow(text) for text in processed_news]

# 训练LDA模型 (假设100个主题)
lda_model = models.LdaModel(corpus=corpus,
                          id2word=dictionary,
                          num_topics=100,
                          passes=4,
                          alpha='auto',
                          random_state=100)

获取文档主题分布: 如前面
document_to_vector
章节代码(
doc_topic_matrix = ...
)。

PCA降维与层次聚类:

PCA降维: LDA特征维度是100,需要先降维到较低维度(如10-50维)才能高效进行层次聚类和画图。


from sklearn.decomposition import PCA

pca = PCA(n_components=20, random_state=42)  # 降维到20维 (可视需求调整)
doc_topics_pca = pca.fit_transform(doc_topic_matrix) # 输入: (n_docs, 100)
# 计算余弦距离 (替代原始PCA空间欧氏距离)
distance_matrix = 1 - cosine_similarity(doc_topics_pca[0:500]) # 取500条看树图
# 使用Ward连接法 (倾向于产生大小相近的簇)
dendrogram = sch.dendrogram(sch.linkage(distance_matrix, method='ward'))
plt.title('News Topics Dendrogram')
plt.xlabel('Document Sample')
plt.ylabel('Ward Distance')
plt.show()

根据树图选择切割高度或目标簇数: 观察树图,在距离较大的地方切割能得到粗粒度的主题分类(如一级分类),在距离小的地方切割得到细粒度主题(子主题)。本例假设根据树图,选择在距离=15处切割得到大约8个一级主题(
cl_level1
),在距离=5处再次切割得到子主题(
cl_level2
)。


# 在全量样本上进行一级聚类 (Agglomerative聚类,使用PCA特征)
agg_cl_level1 = AgglomerativeClustering(n_clusters=8, affinity='euclidean', linkage='ward')
cluster_level1 = agg_cl_level1.fit_predict(doc_topics_pca)  # 全量数据已降维

# 在每个一级簇内部进行二级聚类(获取子主题)
doc_topic_matrix_denorm = doc_topic_matrix  # 原始100维主题分布 (更利于捕捉细节)
cluster_level2 = np.full(len(cluster_level1), -1)  # 初始化二级标签为-1
for cl_id in range(8):
    # 获取属于当前一级簇cl_id的所有文档索引
    indices = np.where(cluster_level1 == cl_id)[0]
    if len(indices) > 20:  # 足够大的簇才进行子聚类
        sub_features = doc_topic_matrix_denorm[indices, :] # 使用原始主题分布特征
        # 对这个子集做PCA(可选)后聚类(假设子簇数根据轮廓系数或领域经验选10或动态选)
        # ... (此处实现子聚类, 例如KMeans选K=5)
        sub_kmeans = KMeans(n_clusters=5, random_state=42, n_init=10).fit(sub_features)
        sub_labels = sub_kmeans.labels_
        # 将二级标签赋值到主标签数组(避免标签冲突)
        cluster_level2[indices] = sub_labels + (cl_id * 10) # 标签偏移避免冲突
    else:
        # 小簇不分子类,二级标签直接沿用一级
        cluster_level2[indices] = cl_id

news_df['topic_level1'] = cluster_level1
news_df['topic_level2'] = cluster_level2

层级主题解读: 为每一级主题(
topic_level1
)和子主题(
topic_level2
)找出最有代表性的LDA主题词
lda_model.print_topic(topic_id, topn=10)
)和代表性的新闻标题


def interpret_cluster(df, cluster_col, cluster_id, n_items=5):
    cluster_df = df[df[cluster_col] == cluster_id]
    # a. 打印该簇LDA关键词 (取簇内文档主题分布的众数或平均主题ID)
    most_common_topic = cluster_df['lda_dominant_topic'].mode().iloc[0] # 假设有存储每个文档的主主题ID
    print(f"Cluster {cluster_id} Dominant LDA Topic Words:")
    print(lda_model.print_topic(most_common_topic, topn=10))
    # b. 随机取几条代表性标题
    print("
Sample Headlines:")
    for title in cluster_df.sample(n=n_items)['title']: # 假设有title列
        print(f" - {title}")
interpret_cluster(news_df, 'topic_level1', 1) # 解读一级主题1
interpret_cluster(news_df, 'topic_level2', 15) # 解读二级主题15 (假设属于一级主题1的子主题)

结果示例:

一级主题1: “人工智能、机器学习、模型、训练、数据、神经网络、算法…”。代表性标题:“谷歌推出新一代AI芯片”、“深度学习在医疗影像诊断取得突破”、“全球人工智能论坛在京召开”。二级主题15 (1的子主题): “芯片、GPU、算力、英伟达、处理器、AI加速、功耗…”。代表性标题:“英伟达发布新一代AI超级芯片GH200”,“中国AI芯片初创公司获5亿融资”,“AI算力竞赛加剧,GPU短缺将持续”。

价值: 构建了新闻主题的层次化分类体系,实现自动化新闻归档。可应用于网站导航菜单、用户兴趣画像的构建(不同层级偏好)、热点子主题的实时监测。

9. 案例实战三:社交媒体动态话题追踪 (DBSCAN + Embedding)

场景与目标: 品牌监测需实时跟踪Twitter上与公司相关的话题标签 (
#YourBrand
) 下的帖子。目标:自动发现新兴的热点讨论群(“集群”
cluster
),及时发现可能的公关危机或推广机会。
挑战: 数据流式实时到达;话题形式多样且易变(短文本、表情符号、新词缩写);大量噪声(垃圾广告、无关内容)。
工具栈: Python, Pandas, Tweepy/Twarc (API), Sentence Transformers (MiniLM), Scikit-learn (DBSCAN), UMAP/HDBSCAN (可选更优组合).

步骤:

(近)实时数据获取: 使用Twitter API(如免费的标准API或付费的高级API)收集包含目标标签的推文。可使用Tweepy监听流或按时间间隔拉取。


import tweepy

# 认证 (示例, 使用自有API Key)
auth = tweepy.OAuth1UserHandler(consumer_key, consumer_secret, access_token, access_token_secret)
api
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容