茄子的个人空间

使用BERT得到短语向量表示并进行层次聚类实现相似性去重

字数统计: 1.6k阅读时长: 6 min
2025/03/06
loading

在自然语言处理(NLP)中,短语的相似性分析是一个常见且重要的任务。传统的文本相似性计算方法往往依赖于手工设计的特征,但随着深度学习和预训练语言模型的出现,BERT(Bidirectional Encoder Representations from Transformers)及其衍生模型为我们提供了更强大的表达能力。在本文中,我们将介绍如何使用BERT模型对短语进行向量化,并结合层次聚类算法对相似短语进行去重。

任务背景

在很多应用场景下,我们往往需要从大量的短语数据中去除重复或者相似的内容,以便于进一步分析。例如,在传统中医(TCM)数据分析中,可能有大量的类似任务相关查询或医疗术语需要进行聚类处理。这里,我们通过以下步骤来实现相似性去重:

  1. 使用BERT模型获取短语的向量表示:BERT及其变体能够通过上下文信息准确地捕捉短语的语义。
  2. 计算短语之间的相似度:通过计算短语向量之间的余弦相似度,得到短语之间的相似性。
  3. 进行层次聚类:层次聚类可以根据相似度将相似短语分到同一个类别,进而实现去重。

关键步骤

加载数据

在处理短语之前,我们首先需要加载包含短语的原始数据。假设数据已经保存在CSV文件中,每一行代表一个短语。我们通过Pandas库读取文件并提取短语列。

1
2
3
4
5
6
7
8

def load_data(self, file_path):
df = pd.read_csv(file_path)
if "text" not in df.columns:
raise ValueError("CSV 文件必须包含 'text' 列")
return df["text"].dropna().tolist()


使用BERT模型获取短语向量

通过加载一个预训练的BERT模型(例如 thenlper/gte-base-zh),我们可以将每个短语转换为一个固定维度的向量。这些向量能够捕捉短语的语义信息,后续的相似性计算将依赖这些向量。

1
2
3
4
def encode_phrases(self, phrases):
embeddings = self.model.encode(phrases, convert_to_numpy=True)
return embeddings

计算短语之间的相似度并进行聚类

短语的相似度可以通过计算它们的余弦相似度来衡量。为了对短语进行聚类,我们将计算短语向量之间的距离(使用余弦距离)。

1
2
3
4
5
6
def cluster_phrases(self, embeddings):
Z = linkage(1 - np.inner(embeddings, embeddings), method='average')
labels = fcluster(Z, t=self.eps, criterion='distance')
return labels


将相似的短语分到同一类别

下面,我们将相似的短语分到同一类别。

1
2
3
4
5
6
7

def group_clusters(self, phrases, labels):
cluster_dict = defaultdict(list)
for phrase, label in zip(phrases, labels):
cluster_dict[label].append(phrase)
return cluster_dict

保存聚类结果

最后,将聚类结果保存为CSV文件,每一列对应一个类别,方便后续的分析。

1
2
3
4
5
6
def save_clusters(self, cluster_dict, output_path):
filtered_clusters = {k: v for k, v in cluster_dict.items() if k != -1}
max_len = max(len(v) for v in filtered_clusters.values())
cluster_columns = [v + [""] * (max_len - len(v)) for v in filtered_clusters.values()]
df = pd.DataFrame(cluster_columns).T
df.to_csv(output_path, index=False, encoding="utf-8-sig")


完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
from sklearn.cluster import DBSCAN
from collections import defaultdict
from scipy.cluster.hierarchy import linkage, fcluster

class PhraseClustering:
def __init__(self, model_name="thenlper/gte-base-zh", eps=0.3, min_samples=2):
"""
初始化 BERT 模型和 DBSCAN 参数
:param model_name: 预训练 BERT 模型名称(默认使用轻量级 SBERT)
:param eps: DBSCAN 聚类的半径参数
:param min_samples: DBSCAN 的最小样本数
"""
self.model = SentenceTransformer(model_name)
self.eps = eps
self.min_samples = min_samples

def load_data(self, file_path):
"""
读取 CSV 文件中的短语
:param file_path: CSV 文件路径
:return: 短语列表
"""
df = pd.read_csv(file_path)
if "细粒度_任务相关查询" not in df.columns:
raise ValueError("CSV 文件必须包含 'text' 列")
return df["细粒度_任务相关查询"].dropna().tolist()

def encode_phrases(self, phrases):
"""
使用 BERT 编码短语
:param phrases: 短语列表
:return: 短语的向量表示(NumPy 数组)
"""
embeddings = self.model.encode(phrases, convert_to_numpy=True)
return embeddings

def cluster_phrases(self, embeddings):
"""
使用层次聚类进行聚类
:param embeddings: 短语的向量表示
:return: 聚类标签
"""
# 使用cosine距离计算层次聚类
Z = linkage(1 - np.inner(embeddings, embeddings), method='average') # 1 - cosine similarity = cosine distance

# 根据距离阈值来形成聚类
labels = fcluster(Z, t=self.eps, criterion='distance') # t表示聚类的距离阈值

return labels



def group_clusters(self, phrases, labels):
"""
根据 DBSCAN 结果将短语分组
:param phrases: 原始短语列表
:param labels: 聚类标签
:return: 以字典形式存储的聚类结果 {类别ID: 短语列表}
"""
cluster_dict = defaultdict(list)
for phrase, label in zip(phrases, labels):
cluster_dict[label].append(phrase)
return cluster_dict

def save_clusters(self, cluster_dict, output_path):
"""
将聚类结果保存到 CSV 文件,每一列对应一个类别
:param cluster_dict: 聚类后的短语字典
:param output_path: 输出 CSV 文件路径
"""
# 过滤掉噪声点(DBSCAN 中噪声点的 label 为 -1)
filtered_clusters = {k: v for k, v in cluster_dict.items() if k != -1}

# 按列对齐
max_len = max(len(v) for v in filtered_clusters.values())
cluster_columns = [v + [""] * (max_len - len(v)) for v in filtered_clusters.values()]

# 转置存入 DataFrame
df = pd.DataFrame(cluster_columns).T
df.to_csv(output_path, index=False, encoding="utf-8-sig")

def run(self, input_csv, output_csv):
"""
完整执行流程:加载数据 -> 计算向量 -> 聚类 -> 保存结果
:param input_csv: 输入 CSV 文件路径
:param output_csv: 输出 CSV 文件路径
"""
print("1. 加载数据...")
phrases = self.load_data(input_csv)

print("2. 计算短语向量...")
embeddings = self.encode_phrases(phrases)

print("3. 进行 层次 聚类...")
labels = self.cluster_phrases(embeddings)

print("4. 整理聚类结果...")
cluster_dict = self.group_clusters(phrases, labels)

print(f"5. 发现 {len(cluster_dict)} 个类别,保存到 {output_csv} ...")
self.save_clusters(cluster_dict, output_csv)
print("✅ 处理完成!")

if __name__ == "__main__":
# 配置参数
input_file = "taskPhraseClustering/second_fine_grained_tasks_new_sorted.csv" # 输入文件
output_file = "taskPhraseClustering/output_clusters.csv" # 输出文件
clustering = PhraseClustering(eps=3, min_samples=2) # 可调节 eps 影响聚类效果 eps 值越大 类别数越少
clustering.run(input_file, output_file)

总结

通过本教程,我们展示了如何使用BERT模型对短语进行语义向量化,并结合层次聚类算法实现短语相似性去重。此方法可以有效地将相似的短语分组,帮助我们在大规模文本数据中提取有用的信息。在实际应用中,我们还可以根据需要调整聚类的参数,以适应不同的数据集和需求。

值得一提的是,在上述的代码中, eps 参数是一个关键参数,它的值越大则类别数越少,反之,类别数越多。

enjoy it!

CATALOG
  1. 1. 任务背景
  2. 2. 关键步骤
    1. 2.1. 加载数据
    2. 2.2. 使用BERT模型获取短语向量
    3. 2.3. 计算短语之间的相似度并进行聚类
    4. 2.4. 将相似的短语分到同一类别
    5. 2.5. 保存聚类结果
  3. 3. 完整代码
  4. 4. 总结