想象一下你手头有几千篇客户反馈,或者几万条社交媒体评论。人工阅读分类?那简直是噩梦。文本聚类技术就是来解决这个痛点的——它能自动把意思相近的文本归到同一组。比如电商平台可以用它自动整理用户评价,把"物流快"、"送货及时"归到物流好评类;新闻网站可以用它自动归类相似报道。
传统方法主要基于词频(TF-IDF)或主题模型(LDA),但这些方法有个致命伤:它们处理不了同义词和语义关联。比如"手机"和"智能手机"明明意思相近,传统方法却可能把它们分到不同组。这就是为什么我们需要语义向量——它能捕捉词语背后的真实含义。
BERT就像个超级翻译官,能把文字转换成富含语义的数字向量。举个例子:
这两个"苹果"虽然字面相同,但BERT生成的向量会差异明显,因为它能理解上下文。实际操作时,我们常用[CLS]位置的向量代表整句语义(虽然更精确的做法是取所有token向量的平均值):
python复制from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertModel.from_pretrained('bert-base-chinese')
inputs = tokenizer("今天天气真好", return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
sentence_vector = outputs.last_hidden_state[:, 0, :] # 取[CLS]向量
处理中文文本时要注意:
实测发现,对电商评论这类短文本,直接使用[CLS]向量效果就不错;但对论文摘要等长文本,使用各token向量的加权平均会更准确。
这是新手最常问的问题。假设我们有3500条文本,可以这样做:
python复制from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
max_clusters = 150
best_k = 0
best_score = -1
for k in range(2, max_clusters+1):
kmeans = KMeans(n_clusters=k)
labels = kmeans.fit_predict(vectors)
score = silhouette_score(vectors, labels)
if score > best_score:
best_score = score
best_k = k
不过实际项目中,我更喜欢用肘部法则结合业务需求。比如客户明确需要分成5-10类时,即使轮廓系数不是最高,也会优先考虑业务合理性。
离群点就像班级里的"另类学生",直接删除可能丢失重要信息。我的经验是:
python复制distances = np.linalg.norm(vectors - kmeans.cluster_centers_[labels], axis=1)
threshold = 2.5 * np.median(distances)
outliers = distances > threshold
曾有个金融风控项目,正是这些"离群点"发现了新型欺诈模式。所以我的建议是:不要轻易丢弃离群点,可以先标记出来人工复核。
拿我做过的一个电商评论项目为例:
python复制def preprocess(text):
text = re.sub(r'<[^>]+>', '', text) # 去HTML标签
text = text.translate(str.maketrans(',。!?【】()%', ',.!?[]()%'))
return text[:510] # 保留BERT最大长度
batch_size = 100
vectors = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
inputs = tokenizer(batch, padding=True, truncation=True,
return_tensors="pt", max_length=512).to('cuda')
with torch.no_grad():
outputs = model(**inputs)
batch_vectors = outputs.last_hidden_state[:, 0, :].cpu()
vectors.extend(batch_vectors)
不要完全依赖算法指标!我通常会:
曾遇到过一个有趣案例:最初聚类把"电池续航长"和"待机时间长"分到两类,后来改用BERT所有层的加权向量后,这些问题都解决了。这说明有时候模型需要"微调"才能更好理解业务语义。
处理大规模文本时,我总结出这些技巧:
batch_size=32的较小批次python复制# 内存友好的处理方式
import h5py
with h5py.File('vectors.h5', 'w') as f:
dset = f.create_dataset("vectors", (len(texts), 768), dtype='float32')
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
vectors = get_bert_vectors(batch)
dset[i:i+batch_size] = vectors
这可能是因为:
我的解决方案是:
n_init=10)python复制from sklearn.decomposition import PCA
pca = PCA(n_components=128)
reduced_vectors = pca.fit_transform(vectors)
kmeans = KMeans(n_clusters=best_k, n_init=10)
kmeans.fit(reduced_vectors)
单纯依赖BERT有时会忽略专业术语。在医疗文本项目中,我结合了:
python复制import numpy as np
# 假设已有bert_vectors和expert_features
combined_features = np.concatenate(
[bert_vectors, expert_features],
axis=1
)
这种混合特征使聚类准确率提升了15%,特别是在区分"糖尿病1型"和"糖尿病2型"这类专业术语时效果显著。
对于持续增长的数据(如每日新增的客服记录),我设计了一套动态流程:
这比完全重新聚类节省了70%的计算资源,同时保证了时效性。关键是要设置合理的类别相似度阈值,我通常保持在0.85-0.9之间。
在金融风控场景中,我们将这套方法用于分析客户投诉:
结果发现了一个有趣现象:关于"转账延迟"的投诉集中在周五下午,经排查是银行系统批量处理导致的。这个问题之前被淹没在海量投诉中,通过文本聚类才浮出水面。
另一个电商案例中,我们聚类用户评论后发现:
这帮助客户重新设计了包装方案,使差评率下降了40%。