NLP意图识别
意图识别是自然语言处理(NLP)的核心任务,核心是从用户的文本/语音输入中,精准提取其背后的真实需求(即“意图”),比如用户说“明天上海冷吗”,意图是“查询上海明日天气”。它是所有智能交互系统的“大脑中枢”,没有意图识别,机器就无法理解用户要“做什么”
常见方法
意图识别的方法随NLP技术演进,分为传统机器学习方法(依赖人工设计)和深度学习方法(自动学习特征)两大类,各有适用场景,需根据数据量、意图复杂度选择。
第一类:传统机器学习方法
传统方法的核心是“人工设计特征 + 分类器分类”,可解释性强、易落地,但对复杂语义(如歧义、多意图)处理能力弱。
1. 基于规则的方法(最基础,无数据依赖)
-
核心原理:通过“关键词匹配”“正则表达式”“决策树”等手工制定规则,判断用户意图。
- 关键词匹配:比如“天气”“温度”“冷/热”对应“查询天气”意图;“订单”“物流”“到哪了”对应“查询订单”意图;
- 正则表达式:比如用
r"(\d+)点.*(高铁|火车|机票)"
匹配“明天8点的高铁”这类含时间、交通工具的指令; - 决策树:先判断“是否含‘天气’关键词”,再判断“是否含城市名”,最后确定“查询XX城市天气”意图。
-
优缺点:
- 优点:无需数据训练,上线快、易维护(改规则只需改关键词/正则)、无数据依赖(适合冷启动场景);
- 缺点:覆盖范围有限(无法处理未定义的关键词,比如用户说“明天穿什么”,不含“天气”但意图是查天气)、无法处理歧义(比如“苹果”可能是“买水果”或“买手机”)。
2. 基于特征工程 + 分类器的方法(主流传统方案)
-
核心原理:分两步:① 手工提取文本的“可量化特征”;② 用分类器(如SVM、LR)学习“特征→意图”的映射关系。
- 步骤1:特征工程(关键环节,决定效果):
- 文本特征:TF-IDF(体现关键词重要性,如“物流”在“查快递”意图中权重高)、N-gram(捕捉短语,如“明天8点”比单个“明天”“8点”更有意义);
- 语言特征:词性(如“动词+名词”组合“订+机票”对应“预订”意图)、句法结构(如“主谓宾”“我+查+天气”);
- 统计特征:句子长度、关键词出现次数(如含2个“天气”相关词,更可能是“查天气”意图)。
- 步骤2:分类器选择:
- 朴素贝叶斯(Naive Bayes):适合小数据集,计算快,但假设特征独立(实际文本特征有依赖,精度一般);
- 逻辑回归(LR):可解释性强(能看到每个特征的权重,如“物流”权重0.8,“订单”权重0.7),适合需要“解释意图判断依据”的场景(如金融客服);
- 支持向量机(SVM):在中低维度特征上精度高,适合中小数据集(如1万条以内标注数据),但大数据量下速度慢。
- 步骤1:特征工程(关键环节,决定效果):
-
优缺点:
- 优点:可解释性强(知道“为什么判断为这个意图”)、对数据量要求低(几千条标注数据即可)、训练快;
- 缺点:特征工程依赖人工经验(新手可能提不出有效特征)、无法处理深层语义(如“穿什么”隐含“查天气”,无直接关键词,特征无法捕捉)。
第二类:深度学习方法
深度学习方法的核心是“自动学习语义特征”,无需人工设计特征,能处理歧义、多意图、深层语义,是当前工业界主流方案。
1. CNN(卷积神经网络):擅长捕捉局部关键特征
-
核心原理:用“卷积核”(如3×3窗口)扫描文本的词向量序列,提取局部语义特征(如“订机票”“查天气”这类短语),再通过池化层(如Max-Pooling)保留最关键的特征,最后用全连接层分类。
- 举例:处理“帮我订明天去北京的机票”,卷积核会重点捕捉“订+机票”“去+北京”这两个局部短语,判断为“预订机票”意图。
-
优缺点:
- 优点:计算快(卷积并行处理)、擅长捕捉短文本中的关键词组合(如“查物流”“退订单”);
- 缺点:无法处理长文本的“远距离依赖”(如“我下周要去上海,能帮我订个住的地方吗”,“订住的地方”和“上海”距离远,CNN难以关联)。
2. RNN/LSTM/GRU:擅长处理序列依赖
-
核心原理:将文本按“词的顺序”输入循环神经网络(RNN),通过“隐藏层状态”传递上下文信息,解决“远距离依赖”问题;LSTM/GRU是RNN的改进,通过“门控机制”(输入门、遗忘门、输出门)避免长序列“梯度消失”,更好保留长文本中的上下文关联。
- 举例:处理“我下周要去上海,能帮我订个住的地方吗”,LSTM会记住“上海”这个地点信息,关联到后面的“订住的地方”,判断为“预订上海酒店”意图,而非笼统的“预订住宿”。
-
优缺点:
- 优点:能处理长文本的序列依赖(如多轮对话、长指令)、比CNN更懂“上下文逻辑”;
- 缺点:计算慢(需按词顺序串行处理,无法并行)、对“长距离依赖”的捕捉仍有限(如文本超过50字,效果会下降)。
3. Transformer/BERT:当前最优,擅长全局语义理解
-
核心原理:基于“自注意力机制”,能同时关注文本中所有词的关联(全局上下文),解决“一词多义”“深层语义”问题;BERT是基于Transformer的预训练模型,通过“大规模文本预训练”(如维基百科、网页数据)学习通用语义,再用少量标注数据“微调”,即可实现高精度意图识别。
- 举例1(一词多义):用户说“我想买个苹果”,BERT会结合上下文判断——若上下文有“吃”“甜”,意图是“购买水果”;若有“系统”“拍照”,意图是“购买手机”;
- 举例2(深层语义):用户说“明天出门要不要带伞”,BERT能捕捉到“带伞”隐含“查是否下雨”,意图是“查询明日天气(是否降雨)”。
-
优缺点:
- 优点:精度最高(在公开意图数据集上准确率达95%以上)、能处理歧义、深层语义、长文本,无需人工设计特征;
- 缺点:模型体积大(如BERT-Base有1.1亿参数)、训练/推理速度慢(需GPU支持)、可解释性弱(不知道“为什么判断为这个意图”)。
4. 端到端意图识别(简化流程,工业界首选)
-
核心原理:将“文本输入→意图分类”的全流程整合为一个模型,无需拆分“特征提取→分类”步骤,最典型的是“BERT+分类头”(在BERT的输出层加一个全连接层,直接输出意图概率)。
- 流程:文本→BERT预训练模型(生成语义向量)→分类头(输出“查天气”“订机票”等意图的概率)→选择概率最高的意图。
-
优缺点:
- 优点:流程简单(无需人工干预)、精度高(继承BERT的语义理解能力)、易落地(有成熟开源库,如Hugging Face Transformers);
- 缺点:依赖GPU资源、小数据集下微调效果可能不如传统方法(需用“Few-shot学习”优化,如用少量数据做提示微调)。
# 关键词匹配 + 正则表达式 + 优先级排序
# 冷启动 无数据标注
import re
from collections import defaultdict#文本输入 → 预处理(统一格式) → 关键词匹配(基础分) → 正则匹配(精准分) → 分数最高的意图 → 输出结果
class RuleBasedIntentClassifier:def __init__(self):# 1. 定义意图-关键词映射(权重越高,匹配优先级越高)self.intent_keywords = {"查询订单": {"关键词": ["订单", "买的", "购买", "下单"], "权重": 3},"退换货": {"关键词": ["退", "换", "退货", "换货", "不合适"], "权重": 3},"查询物流": {"关键词": ["物流", "快递", "运输", "到哪了", "派送"], "权重": 2},"投诉": {"关键词": ["投诉", "差评", "态度差", "垃圾", "投诉你们"], "权重": 4} # 投诉优先级最高}# 2. 定义正则表达式规则(处理含数字/时间的指令)self.regex_rules = {"查询订单": [r"订单号.*(\d+)", r"(\d+)号订单"], # 匹配“订单号12345”“12345号订单”"查询物流": [r"快递.*(\d+)", r"物流.*(\d+)"] # 匹配“快递123456”}def predict(self, text):# 步骤1:预处理文本(小写化+去标点)text = text.lower().replace("?", "").replace("!", "").replace("。", "")# 步骤2:关键词匹配计分intent_scores = defaultdict(int)for intent, info in self.intent_keywords.items():for keyword in info["关键词"]:if keyword in text:intent_scores[intent] += info["权重"] # 命中关键词加分# 步骤3:正则表达式匹配(额外加分)for intent, patterns in self.regex_rules.items():for pattern in patterns:if re.search(pattern, text):intent_scores[intent] += 5 # 正则匹配更精准,加分更高# 步骤4:确定最终意图(分数最高的为结果,无匹配则为“其他”)if intent_scores:max_score_intent = max(intent_scores.items(), key=lambda x: x[1])[0]return max_score_intentelse:return "其他"# 测试
if __name__ == "__main__":classifier = RuleBasedIntentClassifier()test_texts = ["我的订单什么时候发货?","衣服太大了想退货","快递123456到哪了","投诉你们客服态度太差","你好,在吗?"]for text in test_texts:intent = classifier.predict(text)print(f"文本:{text} → 意图:{intent}")
# 基于 Hugging Face Transformers
# 有数据标注
import pandas as pd
import torch
from sklearn.model_selection import train_test_split
from transformers import (BertTokenizer, BertForSequenceClassification,TrainingArguments, Trainer, DataCollatorWithPadding
)
from datasets import Dataset# 1. 加载数据
df = pd.read_csv("intent_data.csv")
# 标签映射(文本→数字,如“查询订单”→0)
intent_list = list(df["intent"].unique())
id2label = {i: label for i, label in enumerate(intent_list)}
label2id = {label: i for i, label in enumerate(intent_list)}
df["label"] = df["intent"].map(label2id)# 2. 划分训练集和验证集
train_df, val_df = train_test_split(df, test_size=0.2, random_state=42)
train_dataset = Dataset.from_pandas(train_df)
val_dataset = Dataset.from_pandas(val_df)# 3. 加载BERT分词器(中文用"bert-base-chinese")
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")# 4. 数据预处理(分词+转成模型输入格式)
def preprocess_function(examples):return tokenizer(examples["text"], truncation=True, max_length=128)tokenized_train = train_dataset.map(preprocess_function, batched=True)
tokenized_val = val_dataset.map(preprocess_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer) # 动态padding#preprocess_function的输出是什么?
# 分词器处理后会返回 3 个关键字段:
# input_ids:文本对应的 Token ID 序列(如 “订单 333”→[101, 3847, 123, 123, 123, 102],其中 101 是[CLS],102 是[SEP]);
# attention_mask:标记哪些 Token 是真实文本(1),哪些是 Padding(0),模型会忽略 Padding 部分;
#token_type_ids:BERT 用于区分句子对(如问答中的问题和答案),单句文本时全为 0。#为什么max_length=128?
#BERT 基础模型支持最长 512 个 Token,但大多数用户输入(如 “订单 333 怎么还没到”)远短于 512,设为 128 可平衡精度和计算效率(更短的长度训练更快)。#动态 Padding 的作用:
#不同文本长度不同(如 “你好” vs “我的订单 1234567890 什么时候发货”),data_collator会在每个 batch 内用最少的 Padding 补全,避免固定长度导致的计算浪费。
# 5. 加载BERT分类模型
model = BertForSequenceClassification.from_pretrained("bert-base-chinese",num_labels=len(intent_list),id2label=id2label,label2id=label2id
)# 6. 定义训练参数
training_args = TrainingArguments(output_dir="./intent_model", # 模型保存路径learning_rate=2e-5, # 学习率(BERT常用2e-5~5e-5)per_device_train_batch_size=16, # .batch大小(GPU显存小则设8)per_device_eval_batch_size=16,num_train_epochs=3, # 训练轮次(小数据集3~5轮足够)evaluation_strategy="epoch", # 每轮评估一次save_strategy="epoch", # 每轮保存一次模型load_best_model_at_end=True # 最后加载最优模型
)
# 7. 定义Trainer并启动训练
trainer = Trainer(model=model, # 待训练的模型args=training_args, # 训练参数train_dataset=tokenized_train, # 训练集eval_dataset=tokenized_val, # 验证集tokenizer=tokenizer, # 分词器(用于处理padding等)data_collator=data_collator # 动态padding工具
)
trainer.train() # 启动训练# 8. 推理函数(预测新文本的意图)
def predict_intent(text):inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=128)with torch.no_grad():outputs = model(**inputs)predictions = torch.argmax(outputs.logits, dim=1)return id2label[predictions[0].item()]#outputs.logits是什么?
#模型输出的logits是一个张量(如[[-2.1, 3.5, -0.8]]),每个元素对应一个意图的 “原始得分”(未经过 softmax 归一化),得分越高,模型认为该意图的概率越大。#torch.argmax(outputs.logits, dim=1):
#在维度 1 上取最大值的索引(如上述例子中最大值 3.5 的索引是 1),即模型预测的数字标签。#with torch.no_grad():
#关闭 PyTorch 的梯度计算(推理时不需要更新参数),可减少内存占用,加快预测速度。