94万条热线问题的分析之路——KMeans聚类、动态相似度与大模型分类

94万条热线问题的分析之路——KMeans聚类、动态相似度与大模型分类

某政务热线有94万条历史问题,怎么搞清楚这些问题到底在问什么?哪些是高频的?哪些知识库还没覆盖?这篇记录我用三种方法分析这批数据的过程:KMeans聚类做粗分、向量相似度做精分、大模型做语义分类。


一、问题:94万条问题,人工看不完

94万条问题,格式是这样的:

退休了医保怎么办
社保卡丢了怎么补办
灵活就业人员怎么参保
...

没有分类标签,没有结构化信息,就是一堆文本。要回答三个问题:

  1. 这些问题可以分成几大类? 参保?缴费?社保卡?
  2. 哪些问题是重复的? 94万里有多少是同一件事换了个问法
  3. 高频问题我们的知识库覆盖了多少?

二、方法一:KMeans + TF-IDF粗分

原理

TF-IDF把每条问题转成一个稀疏向量(关键词权重),KMeans把向量聚成N个簇,同一个簇里的问题被认为是一类。

import jieba
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import csv

texts = []
with open('problem.csv', 'r', encoding="utf-8") as file:
    csv_reader = csv.reader(file)
    for row in csv_reader:
        texts.append(row[0])

def chinese_tokenizer(text):
    return jieba.cut(text)

vectorizer = TfidfVectorizer(tokenizer=chinese_tokenizer)
X = vectorizer.fit_transform(texts)

kmeans = KMeans(n_clusters=5000, random_state=42)
kmeans.fit(X)
labels = kmeans.labels_

with open("result.txt", "w", encoding="utf-8") as file:
    for i, text in enumerate(texts):
        file.write(f"{text}\t{labels[i]}\n")

jieba分词 → TF-IDF提取特征 → KMeans聚5000个簇。

效果

5000个簇意味着平均每个簇约188条问题(94万/5000)。同一个簇里的问题确实有关联性,比如簇#1234里可能全是"退休"相关的问题。

问题:TF-IDF是基于关键词的,不理解语义。"退休怎么办"和"到了法定年龄怎么办理退休手续"分词结果不同,可能被分到不同的簇。粗分可用,精细不够。


三、方法二:向量相似度动态聚类

上一篇文章里把问题都向量化存到了Milvus。现在用向量相似度做更精准的聚类。

思路

从第一条问题开始,在Milvus里搜所有和它余弦相似度>0.95的问题,归为一类。然后找下一条还没被归类的问题,重复这个过程。

search_params = {"metric_type": "COSINE", "params": {"radius": 0.95}}
getedlist = []

for i in range(1, 93936):
    qs = client.get(collection_name="p_hotline", ids=[i],
                    output_fields=["uid", "Question", "embeddings_Q"])
    v = qs[0]["embeddings_Q"]
    uid = qs[0]["uid"]

    if uid in getedlist:
        continue

    res = client.search(
        collection_name="p_hotline",
        data=[v],
        limit=10000,
        output_fields=["uid", "Question"],
        search_params=search_params
    )

    for j in range(0, res[0].__len__()):
        getedlist.append(res[0][j]["entity"]["uid"])
        file.write(str(uid) + "\t" + str(res[0][j]["entity"]["uid"]) + "\t"
                   + str(res[0][j]["distance"]) + "\t"
                   + res[0][j]["entity"]["Question"] + "\n")

关键设计

  1. 相似度阈值0.95起步——非常严格,只有几乎一样的问题才会归到一起
  2. 已归类的问题跳过——getedlist记录所有已归类的问题ID,避免重复处理
  3. 逐条遍历93936条——对每条未归类的问题,搜索所有和它相似的问题

效果

比KMeans精准得多。"退休怎么办"和"到了退休年龄怎么办理"相似度>0.95,会被归到一类。因为用的是语义向量,不是关键词。

问题:慢。93936条,每条都要搜一次Milvus,跑了几个小时。


四、方法三:大模型语义分类

KMeans粗分、向量聚类精分,都还需要人去看每个簇/类的代表问题来确定分类名称。能不能让大模型直接分类?

DeepSeek分类

from openai import OpenAI

ctext = ("分类为参保、缴费、社保卡、养老保险、失业保险、医疗保险、"
         "工伤保险、生育保险、人事、就业培训、就业、社保档案、其他档案、其他"
         "之间那一类问题,只需返回的那分类的那几个字")

def f_classifiy(txt):
    client = OpenAI(api_key="sk-xxx", base_url="https://api.deepseek.com")
    response = client.chat.completions.create(
        model="deepseek-chat",
        messages=[{"role": "user", "content": txt}],
        stream=False
    )
    return response.choices[0].message.content

for str1 in texts:
    str2 = f_classifiy(str1 + "  " + ctext)
    file.write(str1 + "\t" + str2 + "\n")

把问题文本 + 分类要求一起发给DeepSeek,让它返回类别名称。14个预设类别覆盖了社保的主要领域。

效果

分类准确率很高。DeepSeek对中文社保领域的语义理解比TF-IDF强太多了。“退休后医保还能报销吗"被正确分类为"医疗保险”,不是"养老保险"。

问题:成本。每条问题调用一次API,94万条费用不低。实际上先用向量聚类把重复问题去掉,剩下几万条不重复的再用大模型分类,成本可控。


五、知识库覆盖率分析

分类完了,还要回答一个问题:高频问题我们的知识库覆盖了没有?

# 从p2集合取出所有问题向量,在SI_knowledge知识库中检索
for j in range(0, qs[0].__len__()):
    res = client.search(
        collection_name="SI_knowledge",
        data=[qs[0][j]["entity"]["v"]],
        limit=10,
        output_fields=["uid", "Question"],
        search_params={"metric_type": "COSINE", "params": {"radius": 0.0}}
    )
    if res[0].__len__() > 0:
        file.write(qs[0][j]["entity"]["Question"] + "\t"
                   + str(res[0][0]["distance"]) + "\t"
                   + res[0][0]["entity"]["Question"] + "\n")

把热线问题拿到知识库里搜,看匹配的最高相似度是多少。相似度>0.8说明知识库有覆盖,<0.5说明是知识盲区。

这个分析直接告诉我们:该往知识库补什么内容。


六、三种方法的对比

方法原理优点缺点
KMeans+TF-IDF关键词特征聚类快、不需要额外服务不理解语义、簇需要人工解读
向量相似度聚类语义向量+余弦相似度精准、理解语义慢、需要先建向量库
大模型分类LLM语义理解最精准、直接出类别名有API成本、有速率限制

实际工作流

  1. 先用向量相似度去重(94万→几万条不重复)
  2. 再用大模型分类(几万条→14个类别)
  3. 最后用覆盖率分析找出知识盲区

三种方法不是替代关系,是流水线关系。每一步为下一步准备更干净的数据。


相关阅读:

  • 《向量数据库实战——用Milvus+Ollama搭建社保知识检索系统》
  • 《约94万条热线问题怎么去重?动态相似度阈值+Milvus》
  • 《知识库建好了但够不够用?向量检索量化覆盖率》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值