评论区一条“多少钱”和一条“你们怎么收费”,在业务上可能指向完全不同的人——一个在比价,一个真有需求。意图识别要做的事情,就是在这类模糊表达里找到真正的意向客户。
意图识别是 AI 获客系统的核心模块,它的任务是把自然语言评论分类到预定义的意图槽位(购买意向、咨询意向、无效闲聊、同行探价等),并输出一个可执行的意向等级。本文记录一套从规则匹配到行业微调落地的完整方案。
整体方案从规则匹配演进到行业微调,大致经历了三个阶段:
01 规则匹配解决不了长尾表达
第一版意图识别用的是关键词规则:命中“多少钱”“价格”“怎么收费”就判为高意向。上线一周就发现两个问题。
召回率严重不足。用户表达同一意图的方式太多——“你们家做不做”“能帮我看看吗”"这个怎么弄"都不带价格词,规则完全命中不了。某次抽样统计中,300 条人工标注为高意向的评论,规则版只召回41%。
规则列表维护成本失控。每个行业要维护一套关键词表,装修行业和律师行业的表达完全不同,规则数量膨胀到 2000 多条后,互相冲突的情况开始出现。
规则匹配适合做第一层粗筛(过滤明显无效内容),但不适合做意图判定本身。
两版方案在关键指标上的对比如下:
| 维度 | 规则匹配版 | LoRA 微调版 |
|---|---|---|
| 高意向召回率 | 41%(300 条人工标注样本抽样) | 0.88 ~ 0.91(装修 0.90 / 法律 0.88 / 教培 0.91) |
| 高意向精确率 | 关键词命中即判高意向,误判率高,未单独统计 | 0.90 ~ 0.93(装修 0.92 / 法律 0.90 / 教培 0.93) |
| 宏平均 F1 | 未达可用水平,长尾表达大量漏召回 | 0.87 ~ 0.90(装修 0.89 / 法律 0.87 / 教培 0.90) |
| 规则/适配器维护成本 | 每行业维护一套关键词表,规则膨胀至 2000 多条后互相冲突 | 每行业一个几十 MB 的 LoRA 适配器,切换行业只需加载对应适配器 |
02 行业微调的核心是数据而非模型
第二版改为模型方案:基座模型选通用大模型,在其上做垂直行业微调。
一个关键认知:微调的效果瓶颈在数据,不在模型。测试中发现,把训练数据从 2000 条加到 8000 条,意图分类 F1 提升约 11 个百分点;而把基座模型从 7B 换到 13B,F1 只提升约 2 个百分点。数据质量和数量是第一优先级。
数据生产流程分四步:
- 从真实评论区拉取原始评论,按行业抽样
- 人工标注,每个样本给意图标签(购买/咨询/闲聊/探价/其他)
- 标注一致性校验,两人标注不一致的样本进入仲裁
- 构造反例——把容易误判的“高意向伪装”(如“这个多少钱?开个玩笑哈”)单独成集
上述四步流程可以概括为下面这张图:
反例集是微调质量的胜负手。没有反例的模型会把所有带“多少钱”的评论都判成高意向,误判率会高到业务无法接受。
03 微调方案选型与参数记录
对比了全参微调和 LoRA 两种路线。全参微调效果略好,但每个行业都要存一份完整权重,存储和推理成本高;LoRA 只训练低秩适配矩阵,单行业适配器只有几十 MB,切换行业只需加载不同的适配器。
最终方案采用 LoRA,关键参数:
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="SEQ_CLS"
)
训练阶段固定 base model,只更新适配器权重。学习率用 1e-4,batch size 32,3 个 epoch 后验证集指标开始收敛,第 5 个 epoch 出现轻微过拟合(训练 loss 继续下降、验证 loss 回升),最终取第 4 个 epoch 的 checkpoint。
04 线上推理与效果评估
线上推理服务把基座模型与行业 LoRA 适配器合并加载,对单条评论做意图分类并映射为意向等级,核心代码如下:
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
BASE_MODEL = "your-base-model-path"
ADAPTER_DIR = "./lora-adapter-zhuangxiu" # 每个行业一个适配器目录
# 意图标签与意向等级的映射关系
INTENT_LABELS = ["购买意向", "咨询意向", "无效闲聊", "同行探价", "其他"]
INTENT_LEVEL = {
"购买意向": "A(高意向)",
"咨询意向": "B(中意向)",
"同行探价": "C(观察)",
"无效闲聊": "D(无意向)",
"其他": "D(无意向)",
}
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
base_model = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL, num_labels=len(INTENT_LABELS)
)
# 加载 LoRA 适配器并与基座模型合并,合并后推理不再有额外延迟
model = PeftModel.from_pretrained(base_model, ADAPTER_DIR).merge_and_unload()
model.eval()
@torch.no_grad()
def classify_comment(text: str, max_length: int = 128):
inputs = tokenizer(
text, truncation=True, padding=True,
max_length=max_length, return_tensors="pt"
)
logits = model(**inputs).logits
probs = torch.softmax(logits, dim=-1)[0]
idx = int(torch.argmax(probs))
intent = INTENT_LABELS[idx]
return {
"text": text,
"intent": intent,
"confidence": round(float(probs[idx]), 4),
"level": INTENT_LEVEL[intent],
}
if __name__ == "__main__":
print(classify_comment("你们家装修大概多少钱一平?"))
关键步骤说明:
- 加载基座模型:用与训练阶段一致的基座模型和标签数量初始化分类头,保证权重结构对齐。
- 加载并合并适配器:
PeftModel.from_pretrained读取行业适配器,merge_and_unload()把低秩矩阵合并进基座权重,推理时无需额外计算分支。 - 切换行业:只需替换
ADAPTER_DIR指向对应行业的适配器目录,无需重新加载基座模型。 - 推理与后处理:
model.eval()关闭 dropout,torch.no_grad()省去梯度计算;softmax 取最大概率类别作为意图,再通过映射表输出意向等级与置信度。
线上推理时,LoRA 适配器与基座模型合并加载,推理延迟与未微调模型基本持平,单条评论分类耗时稳定在 300ms 以内。
评估用五折交叉验证,指标取宏平均 F1。三个行业(装修、法律、教培)的测试结果:
| 行业 | 意图类别数 | 宏平均F1 | 高意向精确率 | 高意向召回率 |
|---|---|---|---|---|
| 装修 | 5 | 0.89 | 0.92 | 0.90 |
| 法律 | 6 | 0.87 | 0.90 | 0.88 |
| 教培 | 5 | 0.90 | 0.93 | 0.91 |
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2601_96961573/article/details/168959923



