从算法到代码实战
📖 目录导读
- 什么是文件内容层次聚类树?
- 层次聚类树的原理与适用场景
- 脚本实现前必须做的数据预处理
- 核心算法:使用Python实现层次聚类树
- 代码实战:从文本文件到聚类树的完整脚本
- 如何可视化聚类树(树状图)?
- 常见问题与优化技巧
- Q&A:针对脚本实现的常见疑问解答
什么是文件内容层次聚类树?
层次聚类树是一种基于文档内容相似性,通过计算文本间的距离(如余弦相似度、杰卡德距离等),自动将多份文档组织成树状层级结构的无监督学习方法,它不同于平面聚类(如K-means),不需要预设聚类数量,而是生成从每个文档单独一个簇到所有文档合并为一个簇的完整层级结构。对于SEO从业者、内容分析师和科研人员而言,这种技术能快速发现主题相近的文档群组,识别核心内容脉络,优化网站内链结构或研究文献分类,将公司内部100份产品说明书自动归类为“安装指南”“故障排查”“规格参数”等子群,并展示它们之间的隶属关系。

层次聚类树的原理与适用场景
层次聚类分为自底向上(凝聚式)和自顶向下(分裂式)两种,实际脚本实现中,凝聚式层次聚类(AGNES)最常用,流程如下:
- 将每个文件视为一个独立的簇。
- 计算所有簇之间的相似度(或距离)。
- 找到最相似的两个簇,合并为一个新簇。
- 重复步骤2-3,直到所有簇合并为一个根簇。
- 记录每次合并的顺序和距离,形成树状结构(聚类树)。
适用场景:
- 新闻网站自动将同类事件文章归类到不同主题之下
- 学术论文根据摘要自动发现研究热点分支
- 企业知识库中自动将技术文档、FAQ、使用手册分层级组织SEO优化:将500篇博客文章按主题层次聚合,规划站内链接结构
脚本实现前必须做的数据预处理
脚本实现的核心前提是转化为数值向量,以下是完整预处理步骤:
1 文本清洗
import re
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
def clean_text(text):
# 去除HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 去除特殊符号,保留中文、英文、数字
text = re.sub(r'[^\w\s\u4e00-\u9fff]', '', text)
# 转小写(针对英文)
text = text.lower()
# 去除停用词(中英文均可)
stop_words = set(stopwords.words('english'))
words = text.split()
return ' '.join([w for w in words if w not in stop_words])
2 向量化(TF-IDF优于词袋模型)
文件聚类必须使用TF-IDF权重,因为它能降低常见无意义词汇(如“的”“的”“the”)的干扰,突出每份文件的特色词汇。
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(max_df=0.8, min_df=2, max_features=5000) X = vectorizer.fit_transform([clean_text(f.read()) for f in file_list])
3 距离矩阵计算
使用余弦距离(1 - 余弦相似度)比欧氏距离更适合高维文本向量:
from sklearn.metrics.pairwise import cosine_distances dist_matrix = cosine_distances(X)
脚本实现层次聚类树的核心代码
1 使用Scipy库快速实现
Python的scipy.cluster.hierarchy提供了成熟的凝聚层次聚类功能:
from scipy.cluster.hierarchy import linkage, dendrogram import numpy as np # linkage矩阵:使用'ward'方法(最小方差合并) Z = linkage(dist_matrix, method='ward') # 查看聚类树结构 print(Z[:5]) # 前5行:每行表示一次合并操作
Z矩阵的每一行包含:[簇A索引, 簇B索引, 合并距离, 新簇中包含的文件数],这是构建聚类树的核心数据结构。
2 完整脚本:文件内容层次聚类树生成器
import os
from glob import glob
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy.cluster.hierarchy import linkage, fcluster
import matplotlib.pyplot as plt
def build_cluster_tree(directory, output_plot='cluster_tree.png'):
# 读取所有txt文件
files = glob(os.path.join(directory, '*.txt'))
texts = []
filenames = []
for fpath in files:
with open(fpath, 'r', encoding='utf-8') as f:
texts.append(f.read())
filenames.append(os.path.basename(fpath))
# 向量化(已包含预处理)
vectorizer = TfidfVectorizer(max_df=0.8, min_df=2, stop_words='english')
X = vectorizer.fit_transform(texts)
# 计算距离矩阵
from sklearn.metrics.pairwise import cosine_distances
dist = cosine_distances(X)
# 层次聚类
Z = linkage(dist, method='average') # average链接效果更好
# 剪枝:设定阈值,将树划分为有限个簇
max_clusters = 5 # 最终想要的簇数量
clusters = fcluster(Z, t=max_clusters, criterion='maxclust')
# 输出结果
for i, (name, cluster_id) in enumerate(zip(filenames, clusters)):
print(f"文件: {name} -> 聚类 {cluster_id}")
# 绘制树状图
plt.figure(figsize=(12, 8))
dendrogram(Z, labels=filenames, leaf_font_size=8, color_threshold=0.7)
plt.title('文件内容层次聚类树')
plt.xlabel('文件')
plt.ylabel('距离')
plt.tight_layout()
plt.savefig(output_plot, dpi=150)
plt.show()
if __name__ == '__main__':
build_cluster_tree('./documents')
如何解读与可视化聚类树?
1 树状图关键元素
- 叶子节点:每个文件
- 内部节点:合并后形成的簇
- 垂直高度:合并时的距离(距离越小,文件越相似)
- 颜色阈值:可通过
color_threshold参数设置切割点,将树分为不同颜色区域
2 从聚类树提取层级关系
# 获取每个文件在树中的层级索引(0为根)
from scipy.cluster.hierarchy import to_tree
T = to_tree(Z)
if T:
# 遍历树结构可生成JSON格式的层级目录
def tree_to_dict(node):
if node.is_leaf():
return {'name': filenames[node.id], 'children': []}
else:
left = tree_to_dict(node.get_left())
right = tree_to_dict(node.get_right())
return {'name': f'Cluster_{node.id}', 'children': [left, right]}
hierarchy_json = tree_to_dict(T)
print(hierarchy_json) # 可输出为JSON文件供前端展示
Q&A:针对脚本实现的常见疑问解答
Q1:文件内容聚类必须全是同一语言吗?
A:不需要,TF-IDF支持多语言混合,但建议英文文档单独处理,因为停用词表不同,中文文档可使用jieba分词后,用TfidfVectorizer的analyzer='word'配合自定义分词器。
Q2:我只有100个文件,速度很快,但如果有10万个文件怎么办?
A:此时cosine_distances计算O(n²)的矩阵会内存溢出,解决方案:
- 使用MiniBatchKMeans先预聚类成1000个伪中心,再用层次聚类
- 使用
fastcluster库的linkage_vector方法加速 - 只对TF-IDF向量的non-zero部分存储稀疏矩阵
Q3:为什么我的聚类树看起来全是叶子节点直接合并?
A:这说明所有文件之间距离都很近(内容高度相似),建议降低max_df(删除常见通用词),或使用min_df=5过滤低频词,拉开文件间差异。
Q4:如何将聚类结果输出为树状文件夹结构?
A:可以在build_cluster_tree中,根据clusters数组创建文件夹并移动文件:
import shutil
output_dir = 'clustered_docs'
for i, cluster_id in enumerate(set(clusters)):
cluster_folder = os.path.join(output_dir, f'Cluster_{cluster_id}')
os.makedirs(cluster_folder, exist_ok=True)
for idx, label in enumerate(clusters):
if label == cluster_id:
shutil.copy(files[idx], cluster_folder)
Q5:脚本能不能支持PDF或Word文件?
A:可以,使用PyPDF2或python-docx抽取文本内容后,替换texts.append(f.read())部分为相应的文本提取逻辑。
优化技巧与SEO排名考量
为了让脚本生成的内容层次结构更符合搜索引擎偏好:
- 文件名包含关键词:每个文件命名时加入核心词(如
seo-tutorial-层次聚类.md),聚类树会自然将这些词体现出来。 - 使用密集的TF-IDF特征:将
max_features设为3000-10000之间,覆盖更多主题特征词。 - 调参:
method='ward'适合球形簇,average适合文本数据,single适合长链状结构,建议轮询几种方法比较结果。 - 可视化增强:在树状图的叶子上标注文件标题(而非文件名),提高可读性。
完全符合SEO排名的层次聚类树脚本,能帮助站长快速发现内容缺口(哪些簇中文件太少),优化站内链接(将同一簇内文章互相链接),并生成结构化的网站地图提案。
延伸阅读:可参考scikit-learn官方文档的“层次聚类教程”,以及第三方库yellowbrick提供的KElbowVisualizer辅助选择聚类数。
本文撰写过程中参考了Scipy官方文档、Scikit-learn用户指南及Stack Overflow相关问答,已进行去重及原创化改写。