一、项目摘要
BERT(Bidirectional Encoder Representations from Transformers)是一种由Google开发的预训练语言模型,它基于Transformer架构。Transformer是一种用于处理序列数据的神经网络架构,它在NLP任务中取得了巨大成功。BERT的特点在于它是一个双向的(Bidirectional)语言模型,能够同时考虑一个词语左右两侧的上下文信息。这使得BERT在理解和生成自然语言文本方面表现出色。在BERT发布之前,许多传统的语言模型在处理上下文信息时只考虑了单个方向(通常是从左到右或从右到左),而BERT的双向结构使得它可以更好地理解句子中词语的语境。BERT的预训练模型可以通过微调来适应各种NLP任务,例如文本分类、命名实体识别、语义理解等。由于BERT的强大表现和通用性,它成为了自然语言处理领域的重要里程碑之一,对于许多NLP任务都取得了领先的效果。
本文旨在使用bert模型对3500篇相关又无关的文章进行聚类,将文章主旨意思接近或者相近的句子分为一类。在这里主是通过bert生成句子向量,将文章语义向量化,然后使用K-means聚类分析方法对句子向量进行聚类,并且设置距离阀值,最后返回语义相近的类簇。
二、任务流程
1、先在transformers库中导入Bert模型和Bert分词器,因为本文是中文文本,所以采用的是bert-base-chinese预训练语言模型。
ps:BertTokenizer分类器是以字为粒度,并且在中文bert词汇表中也是以中文字为粒度。
2、然后我们将3500篇文章读进pandas,依次传入bert模型,并且使用 [cls] 块来代表句子的向量,然后记录下每个句子的向量。
ps:[cls] 块不能精确的代表整个句子的向量,它只是在bert模型中代表句子开始的标志模块,如果对结果的精确度要求非常高,不建议使用 [cls] 块的向量,可以使用tokens的平均向量值。
3、在得到句子的表征后,我就可以使用k-means对句子表征进行聚类分析,同时我们需要做的是,将离群点进行去除,并且由模型选择最适合的类簇数量。
ps:最佳类簇由于文本语义多样性,我们这里取的是文本数量的乘以0.1,并在其中寻找最佳得分类簇数量。
三、实验代码
1、从transformers中导入Bert模型和bert分词器。如果是第一次加载bert模型的话,在这里可以为bert模型的下载安装指定路径,也可以使用自己下载过的模型。
import pandas as pd
from transformers import BertModel, BertTokenizer
import torch
from sklearn.cluster import KMeans
import numpy as np
2、加载预训练的BERT模型和tokenizer。中午对应的bert模型通常使用bert-base-chinese。处理英文文本时可以替换为别的语种模型。
# 加载预训练的BERT模型和tokenizer
model_name = "bert-base-chinese" # 可以替换为其他预训练模型
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertModel.from_pretrained(model_name)
df = pd.read_excel('绩效目标.xlsx', usecols=['KPI_TARGET'])
print(df)
3、将调用bert生成语句向量并且使用K-means聚类封装成了一个函数,为了是方便调用,它的输入是一个包含文本语句的df,它的返回值是一个包含聚类结果的df。
def simlar_sentences(df_KPI_TARGET):
sentences_list = df_KPI_TARGET['KPI_TARGET'].values.tolist() # df转为列表
print(len(sentences_list))
new_list = []
for sentence in sentences_list:
# 去除换行符、缩进
sentence = str(sentence)
sentence = sentence.replace('\n', '')
sentence = sentence.replace('\t', '')
sentence = sentence.replace(' ', '')
new_list.append(sentence)
print(new_list)
print(len(new_list))
list_sen_embedding = []
i=0
for one in new_list:
# 使用tokenizer对句子进行编码
inputs = tokenizer(one, return_tensors="pt", padding=True, truncation=True)
#print(inputs)
# 获取句子向量
with torch.no_grad():
outputs = model(**inputs)
# 获取句子向量(CLS token对应的向量)
sentence_vector = outputs.last_hidden_state[:, 0, :]
i=i+1
print(i)
list_sen_embedding.append(sentence_vector)
# 转换为numpy array
sentence_vectors = np.concatenate(list_sen_embedding, axis=0)
# 使用K均值聚类进行聚类
print('start')
'''
# 寻找最佳的类簇数量
max_clusters = 150
best_score = -1
best_k = 0
for k in range(2, max_clusters + 1):
kmeans = KMeans(n_clusters=k)
kmeans.fit(sentence_vectors)
labels = kmeans.labels_
score = silhouette_score(sentence_vectors, labels)
print(f"For n_clusters = {k}, silhouette score is {score}")
if score > best_score:
best_score = score
best_k = k
print(f"Best number of clusters: {best_k}")
# 使用最佳类簇数量进行聚类
best_kmeans = KMeans(n_clusters=best_k)
best_kmeans.fit(sentence_vectors)
labels = best_kmeans.labels_
###去除离群点代码
# 计算每个样本点到其所属簇中心的距离
distances = np.linalg.norm(sentence_vectors - best_kmeans.cluster_centers_[labels], axis=1)
# 设置离群点阈值,可以根据需要调整
outlier_threshold = 2.5 * np.median(distances) # 用中位数作为阈值
# 记录离群点的索引位置
outliers_mask = distances > outlier_threshold
outlier_indices = np.where(outliers_mask)[0]
# 创建布尔数组,标记离群点位置为 True,非离群点位置为 False
data_points_marked = np.full(len(sentence_vectors), False)
data_points_marked[outlier_indices] = True
# 将非离群点的数据标记为其对应的聚类标签
data_points_marked[~outliers_mask] = labels[~outliers_mask]
# 输出标记了离群点位置和非离群点数据聚类标签的数组
print("Index | Is Outlier | Cluster Label")
cluster_label_list = []
for i, (is_outlier, cluster_label) in enumerate(zip(data_points_marked, labels)):
cluster_label_list.append(cluster_label)
print(f"{i} | {is_outlier} | {cluster_label}")
'''
kmeans = KMeans(n_clusters=350)
kmeans.fit(sentence_vectors)
# 获取聚类结果
cluster_labels = kmeans.labels_
# 打印聚类结果
print(kmeans.labels_)
# 将聚类标签添加到原始数据中
df_KPI_TARGET['Cluster'] = cluster_labels
# 将带有聚类标签的 DataFrame 写入 CSV 文件
#df_KPI_TARGET.to_csv('clustered_data.csv', index=False)
# 将聚类标签添加到原始数据中
df_KPI_TARGET['Cluster'] = cluster_labels
# 计算每个类簇的样本数量
cluster_sizes = df_KPI_TARGET['Cluster'].value_counts()
# 获取数据量低于2条的类簇
small_clusters = cluster_sizes[cluster_sizes < 2].index
# 去除数据量低于2条的类簇
df_KPI_TARGET = df_KPI_TARGET[~df_KPI_TARGET['Cluster'].isin(small_clusters)]
# 将带有聚类标签的 DataFrame 写入 CSV 文件
#df_KPI_TARGET.to_csv('clustered_data(1).csv', index=False)
df_result = df_KPI_TARGET #
return df_result
四、实验总结
bert预训练语言模型在此次任务中表现非常优秀,利用bert预训练语言模型生成的语句向量在一点程度上正确的代表了文本的语义信息,这才使得在k-means聚类分析的时候,得到了很好的聚类效果,下图是两个不同类簇的聚类结果。
![]() | ![]() |
BERT预训练模型与KMeans结合有以下几点优点:1、降维和聚类:利用BERT提取的语义信息,可以将文本数据映射到低维空间,然后再使用KMeans等聚类算法对文本进行聚类分析。2、语义相似度比较:BERT可以捕获文本数据的语义信息,结合KMeans可以用于测量文本数据之间的相似度,从而进行更精确的聚类分析。3、高质量的特征表示:BERT预训练模型提供了丰富的语义特征表示,结合KMeans可以更好地挖掘文本数据的特征,提高聚类的准确性和效率。
综上所述,BERT预训练模型和KMeans结合可以有效提高文本数据的聚类分析能力,提供更具实际意义和语义相关性的聚类结果。