意图识别模型使用 基于BERT的对话意图和槽位联合识别 CPU运行BERT模型-亲测成功
我们在开发AI-Agent智能体时,通常会使用提示词工程设置场景的带入,在实际项目中会有很多场景,如果所有提示词都放一起就会超过Token限制,则不得不拆分很多场景的提示词。
很多场景下,用户就必须要选择一个场景进入聊天,这样很不智能,意图识别用来做前置处理,判断用户输入的意图,然后帮用户选择场景。
意图识别:理解用户需求的第一步
在问答对话中,准确理解用户的意图是构建有效回答的关键。意图识别,即判断用户想要什么,相当于为系统定向选择场景,帮助系统更精确的选择回复路径。
例如:当用户询问:“查询电影票” 时,系统必须确定用户是想查询电影票,而不是演唱会票,飞机票等。
意图识别的难点:
-
- 多意图问题;用户的表达可能含有多个含义
-
- 语义模糊:用户输入不规范,或语言表达不标准,如错别字等。
-
- 上下文理解:不同场景和时间节点下相同的表达可能具有不同的意图。
常用的意图识别方法:
-
- 规则模板匹配:通过人工设定模板,如"从[地点]到[地点]的航班",将用户输入与模板匹配,从而判断意图。虽然精确度高,但需大量人力维护,不易推广。
-
- 统计机器学习: 通过提取文本特征,如词性标注和词向量化表示,借助支持向量机等模型进行分类。适合简单的分类,但在复杂意图下效果有限。
-
- 深度学习: 借助神经网络和预训练模型,无需人工设计特征,自动完成意图分类。尽管效果好,但需要大量标注数据。
在RAG系统中,意图识别是基础的前置任务,它将用户输入映射到最可能的意图,为后续的回答生成奠定基础。
基于BERT的对话意图
运行环境
Python 3.8
下载代码
git clone https://github.com/Linear95/bert-intent-slot-detector.git
pycharm开发工具导入项目
数据准备
示例代码里自带了测试数据在:data/SMP2019下,我们这里直接使用,先了解是怎么样使用,然后在根据自己的需求去训练数据
- 训练数据:
以json格式给出,每条数据包括三个关键词:
text表示待检测的文本,
intent代表文本的类别标签,
slots是文本中包括的所有槽位以及对应的槽值,以字典形式给出。
在data/路径下,给出了SMP2019数据集作为参考。
数据样例如下:
{
"text": "开微信",
"domain": "app",
"intent": "LAUNCH",
"slots": {
"name": "微信"
}
}
利用data/SMP2019/split_data.py,我们可以再将SMP2019的所有数据拆分成一个训练集split_train.json和一个测试集split_test.json
运行split_data.py程序 报错:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 35: illegal multibyte sequence
增加编码格式:encoding=‘utf-8’
open('train.json', 'r', encoding='utf-8') as f
- 生产意图标签和槽位标签
运行extract_labels.py程序, 同样报错:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 35: illegal multibyte sequence
同样也是要增加编码格式:encoding=‘utf-8’
open('train.json', 'r', encoding='utf-8') as f
- 意图标签:
以txt格式给出,每行一个意图,未识别意图以[UNK]标签表示。以SMP2019/intent_labels.txt为例:
[UNK]
LAUNCH
QUERY
ROUTE
...
- 槽位标签:
与意图标签类似,以txt格式给出。包括三个特殊标签: [PAD]表示输入序列中的padding token, [UNK]表示未识别序列标签, [O]表示没有槽位的token标签。对于有含义的槽位标签,又分为以’B_'开头的槽位开始的标签, 以及以’I_'开头的其余槽位标记两种。
以SMP2019/slot_labels.txt为例:
[PAD]
[UNK]
[O]
I_ingredient
B_ingredient
...
根据示例数据,训练意图模型
可以直接修改train.py代码,然后运行
if __name__ == '__main__':
# 训练示例数据,生成意图模型
parser = argparse.ArgumentParser()
# environment parameters
parser.add_argument("--cuda_devices", type=str, default='0', help='set cuda device numbers')
parser.add_argument("--no_cuda", action='store_true', default=False, help='whether use cuda device for training')
# model parameters
parser.add_argument("--tokenizer_path", type=str, default='bert-base-chinese', help="pretrained tokenizer loading path")
parser.add_argument("--model_path", type=str, default='bert-base-chinese', help="pretrained model loading path")
# data parameters
parser.add_argument("--train_data_path", type=str, default='D:\PycharmProjects\\ai\\bert-intent-slot-detector\data\SMP2019\split_train.json', help="training data path")
parser.add_argument("--test_data_path", type=str, default='D:\PycharmProjects\\ai\\bert-intent-slot-detector\data\SMP2019\split_test.json', help="testing data path")
parser.add_argument("--slot_label_path", type=str, default='D:\PycharmProjects\\ai\\bert-intent-slot-detector\data\SMP2019\slot_labels.txt', help="slot label path")
parser.add_argument("--intent_label_path", type=str, default='D:\PycharmProjects\\ai\\bert-intent-slot-detector\data\SMP2019\intent_labels.txt', help="intent label path")
# training parameters
parser.add_argument("--save_dir", type=str, default='D:\PycharmProjects\\ai\\bert-intent-slot-detector\saved_model', help="directory to save the model")
parser.add_argument("--max_training_steps", type=int, default=0, help = 'max training step for optimizer, if larger than 0')
parser.add_argument("--gradient_accumulation_steps", type=int, default=1, help="number of updates steps to accumulate before performing a backward() pass.")
parser.add_argument("--saving_steps", type=int, default=300, help="parameter update step number to save model")
parser.add_argument("--logging_steps", type=int, default=10, help="parameter update step number to print logging info.")
parser.add_argument("--eval_steps", type=int, default=10, help="parameter update step number to print logging info.")
parser.add_argument("--saving_epochs", type=int, default=1, help="parameter update epoch number to save model")
parser.add_argument("--batch_size", type=int, default=128, help = 'training data batch size')
parser.add_argument("--train_epochs", type=int, default=10, help = 'training epoch number')
parser.add_argument("--learning_rate", type=float, default=5e-5, help = 'learning rate')
parser.add_argument("--adam_epsilon", type=float, default=1e-8, help="epsilon for Adam optimizer")
parser.add_argument("--warmup_steps", type=int, default=0, help="warmup step number")
parser.add_argument("--weight_decay", type=float, default=0.0, help="weight decay rate")
parser.add_argument("--max_grad_norm", type=float, default=1.0, help="maximum norm for gradients")
args = parser.parse_args()
train(args)
也可以使用命令行动态传参的方式运行
可以使用以下命令进行模型训练,这里我们选择在bert-base-chinese预训练模型基础上进行微调:
python train.py \
--cuda_devices 0 \
--tokenizer_path "bert-base-chinese" \
--model_path "bert-base-chinese" \
--train_data_path "xxx\bert-intent-slot-detector\data\SMP2019\split_train.json" \
--test_data_path "xxx\bert-intent-slot-detector\data\SMP2019\split_test.json" \
--intent_label_path "xxx\bert-intent-slot-detector\data\SMP2019\intent_labels.txt" \
--slot_label_path "xxx\bert-intent-slot-detector\data\SMP2019\slot_labels.txt" \
--save_dir "xxx\bert-intent-slot-detector\saved_model" \
--batch_size 32 \
--train_epochs 5
运行成功后会在saved_model生成微调后的模型
运行模型 测试意图识别
运行detector.py程序,准备识别用户输入的意图
if __name__ == '__main__':
model_path = 'saved_model/model/model_epoch2'
tokenizer_path = 'saved_model/tokenizer/'
intent_path = 'data/SMP2019/intent_labels.txt'
slot_path = 'data/SMP2019/slot_labels.txt'
model = JointIntentSlotDetector.from_pretrained(
model_path=model_path,
tokenizer_path=tokenizer_path,
intent_label_path=intent_path,
slot_label_path=slot_path
)
while True:
text = input("input: ")
print(model.detect(text))
下图能正确的识别输入的意图。
参考链接:https://github.com/Linear95/bert-intent-slot-detector