创建众筹网站,大学网站建设考核办法,网站开发 flex,济南专门做公司网站的公司Transformers实战#xff08;二#xff09;快速入门文本相似度、检索式对话机器人 
1、文本相似度 
1.1 文本相似度简介 文本匹配是一个较为宽泛的概念#xff0c;基本上只要涉及到两段文本之间关系的#xff0c;都可以被看作是一种文本匹配的任务#xff0c;  只是在具体…Transformers实战二快速入门文本相似度、检索式对话机器人 
1、文本相似度 
1.1 文本相似度简介 文本匹配是一个较为宽泛的概念基本上只要涉及到两段文本之间关系的都可以被看作是一种文本匹配的任务  只是在具体的场景下不同的任务对匹配二字的定义可能是存在差异的具体的任务场景包括文本相似度计算、问答匹配、对话匹配、文本推理等等另外如之前介绍的多项选择本质上也是文本匹配  本次重点关注文本相似度任务即判断两段文本是不是表达了同样的语义  文本相似度本质上是一个分类任务。  
Sentence ASentence BLabel找一部小时候的动画片求一部小时候的动画片。谢了1别急呀我的朋友。你一定要看我一下0明天多少度啊明天气温多少度啊1可怕的事情终于发生你到底想说什么?0 
1.2 最直接的解决方案—交互策略 
交互策略就是输入句子对对是否相似进行学习。 数据预处理方式如下 交互策略的实现比较简单类似于情感分析。 
1.2.1 数据集预处理 
数据集https://github.com/CLUEbenchmark/SimCLUE/tree/main 
预训练模型依然是哈工大开源的chinese-macbert-base 
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from datasets import load_datasetdataset  load_dataset(json, data_files./train_pair_1w.json, splittrain)
dataset[0:2]{sentence1: [找一部小时候的动画片,我不可能是一个有鉴赏能力的行家小姐我把我的时间都花在书写上象这样豪华的舞会我还是头一次见到。],sentence2: [求一部小时候的动画片。谢了, 蜡烛没熄就好了夜黑得瘆人情绪压抑。],label: [1, 0]}# 划分数据集
datasets  dataset.train_test_split(test_size0.2)# tokenizer  AutoTokenizer.from_pretrained(hfl/chinese-macbert-base)# 离线加载
model_path  /root/autodl-fs/models/chinese-macbert-base
tokenizer  AutoTokenizer.from_pretrained(model_path)def process_function(examples):tokenized_examples  tokenizer(examples[sentence1], examples[sentence2], max_length128, truncationTrue)tokenized_examples[labels]  [float(label) for label in examples[label]]return tokenized_examplestokenized_datasets  datasets.map(process_function, batchedTrue, remove_columnsdatasets[train].column_names)
tokenized_datasetsDatasetDict({train: Dataset({features: [input_ids, token_type_ids, attention_mask, labels],num_rows: 8000})test: Dataset({features: [input_ids, token_type_ids, attention_mask, labels],num_rows: 2000})
})print(tokenized_datasets[train][0]){
input_ids: [101, 1062, 4265, 1920, 782, 8024, 1963, 3362, 2769, 1762, 6878, 1168, 2600, 1385, 808, 1184, 6878, 1168, 4640, 2370, 7363, 678, 8024, 6929, 6421, 2582, 720, 1215, 8043, 102, 800, 2697, 6230, 2533, 800, 2190, 6821, 5439, 1928, 2094, 3683, 2190, 800, 1520, 1520, 6820, 779, 8024, 4507, 754, 800, 2190, 6821, 702, 782, 772, 4495, 4638, 3946, 2658, 679, 4881, 2544, 5010, 6629, 3341, 511, 102], 
token_type_ids: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
attention_mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
labels: 0.0
}1.2.2 加载模型、创建评估函数 
import evaluate# 离线加载模型
model  AutoModelForSequenceClassification.from_pretrained(model_path, num_labels1)# 这里采用离线加载
accuracy_path  /root/autodl-tmp/transformers-code/metrics/accuracy
f1_path  /root/autodl-tmp/transformers-code/metrics/f1acc_metric  evaluate.load(accuracy_path)
f1_metirc  evaluate.load(f1_path)def eval_metric(eval_predict):predictions, labels  eval_predictpredictions  [int(p  0.5) for p in predictions]labels  [int(l) for l in labels]acc  acc_metric.compute(predictionspredictions, referenceslabels)f1  f1_metirc.compute(predictionspredictions, referenceslabels)acc.update(f1)return acc1.2.3 创建TrainingArguments及Trainer 
train_args  TrainingArguments(output_dir./cross_model,      # 输出文件夹per_device_train_batch_size16,  # 训练时的batch_sizeper_device_eval_batch_size16,  # 验证时的batch_sizelogging_steps10,                # log 打印的频率evaluation_strategyepoch,     # 评估策略save_strategyepoch,           # 保存策略save_total_limit3,              # 最大保存数learning_rate2e-5,              # 学习率weight_decay0.01,               # weight_decaymetric_for_best_modelf1,      # 设定评估指标load_best_model_at_endTrue)     # 训练完成后加载最优模型from transformers import DataCollatorWithPadding
trainer  Trainer(modelmodel, argstrain_args, train_datasettokenized_datasets[train], eval_datasettokenized_datasets[test], data_collatorDataCollatorWithPadding(tokenizertokenizer),compute_metricseval_metric)trainer.train()1.2.4 模型预测 
from transformers import pipelinemodel.config.id2label  {0: 不相似, 1: 相似}
pipe  pipeline(text-classification, modelmodel, tokenizertokenizer, device0)result  pipe({text: 我喜欢北京, text_pair: 天气怎样}, function_to_applynone)
result[label]  相似 if result[score]  0.5 else 不相似
result{label: 不相似, score: 0.054742373526096344}1.3 基于向量匹配的解决方案 
如果从多个文本中找到最相似的文本应该如何做呢 
基于交互策略我们可以借鉴之前多项选择用相同的处理方式(如下图)。 但是这样效率极低因为每次都需要与全量数据进行模型推理数据量较大时很难满足时延要求。 
基于向量匹配的方案可以解决。 
我们可以将候选文本经过训练好的模型进行向量化然后存到向量数据库中(如faiss)。然后将问题也同样向量化去向量库中进行向量匹配。(这也是检索式机器人的思路我们将在检索机器人中将本章节训练好的向量模型作为预训练模型对文本进行向量化并将向量集合存到faiss中进行向量匹配这里仅仅训练出向量模型。) 那么这个向量模型该如何进行训练呢 
向量匹配训练分别对句子进行编码目标是让两个相似句子的相似度分数尽可能接近1。 数据预处理与多项选择类似 注意此时没有预定义模型需要我们自己实现模型。 
模型中的损失我们可以用pytorch提供的余弦损失函数 torch.nn.CosineEmbeddingLoss 余弦损失函数常常用于评估两个向量的相似性两个向量的余弦值越高则相似性越高。  x包括x1和x2即需要计算相似度的prediction和GT  y相当于人为给定的flag决定按哪种方式计算得到loss的结果。  注意此时label应该为正负1  如果需要约束使x1和x2尽可能的相似那么就使用y1prediction和GT完全一致时loss为0  
input1  torch.randn(100, 128)
input2  torch.randn(100, 128)
cos  nn.CosineEmbeddingLoss(reductionmean)# # 需要初始化一个N维的1或-1
loss_flag  torch.ones([100]) 
output  cos(input1, input2, loss_flag)print(output)	# tensor(1.0003)1.3.1 数据预处理 
数据集https://github.com/CLUEbenchmark/SimCLUE/tree/main 
预训练模型依然是哈工大开源的chinese-macbert-base 
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from datasets import load_dataset
import torch# 离线加载数据
dataset  load_dataset(json, data_files./train_pair_1w.json, splittrain)# 数据集划分
datasets  dataset.train_test_split(test_size0.2)# 和多项选择相似的处理方式
model_path  /root/autodl-fs/models/chinese-macbert-base
tokenizer  AutoTokenizer.from_pretrained(model_path)def process_function(examples):sentences  []labels  []for sen1, sen2, label in zip(examples[sentence1], examples[sentence2], examples[label]):sentences.append(sen1)sentences.append(sen2)# 这里label处理为1和-1labels.append(1 if int(label)  1 else -1)# input_ids, attention_mask, token_type_idstokenized_examples  tokenizer(sentences, max_length128, truncationTrue, paddingmax_length)tokenized_examples  {k: [v[i: i  2] for i in range(0, len(v), 2)] for k, v in tokenized_examples.items()}tokenized_examples[labels]  labelsreturn tokenized_examplestokenized_datasets  datasets.map(process_function, batchedTrue, remove_columnsdatasets[train].column_names)
tokenized_datasetsDatasetDict({train: Dataset({features: [input_ids, token_type_ids, attention_mask, labels],num_rows: 8000})test: Dataset({features: [input_ids, token_type_ids, attention_mask, labels],num_rows: 2000})
})1.3.2 自定义训练模型 
from transformers import BertForSequenceClassification, BertPreTrainedModel, BertModel
from typing import Optional
from transformers.configuration_utils import PretrainedConfig
from torch.nn import CosineSimilarity, CosineEmbeddingLossclass DualModel(BertPreTrainedModel):def __init__(self, config: PretrainedConfig, *inputs, **kwargs):super().__init__(config, *inputs, **kwargs)self.bert  BertModel(config)self.post_init()def forward(self,input_ids: Optional[torch.Tensor]  None,attention_mask: Optional[torch.Tensor]  None,token_type_ids: Optional[torch.Tensor]  None,position_ids: Optional[torch.Tensor]  None,head_mask: Optional[torch.Tensor]  None,inputs_embeds: Optional[torch.Tensor]  None,labels: Optional[torch.Tensor]  None,output_attentions: Optional[bool]  None,output_hidden_states: Optional[bool]  None,return_dict: Optional[bool]  None,):return_dict  return_dict if return_dict is not None else self.config.use_return_dict# Step1 分别获取sentenceA 和 sentenceB的输入senA_input_ids, senB_input_ids  input_ids[:, 0], input_ids[:, 1]senA_attention_mask, senB_attention_mask  attention_mask[:, 0], attention_mask[:, 1]senA_token_type_ids, senB_token_type_ids  token_type_ids[:, 0], token_type_ids[:, 1]# Step2 分别获取sentenceA 和 sentenceB的向量表示senA_outputs  self.bert(senA_input_ids,attention_masksenA_attention_mask,token_type_idssenA_token_type_ids,position_idsposition_ids,head_maskhead_mask,inputs_embedsinputs_embeds,output_attentionsoutput_attentions,output_hidden_statesoutput_hidden_states,return_dictreturn_dict,)senA_pooled_output  senA_outputs[1]    # [batch, hidden]senB_outputs  self.bert(senB_input_ids,attention_masksenB_attention_mask,token_type_idssenB_token_type_ids,position_idsposition_ids,head_maskhead_mask,inputs_embedsinputs_embeds,output_attentionsoutput_attentions,output_hidden_statesoutput_hidden_states,return_dictreturn_dict,)senB_pooled_output  senB_outputs[1]    # [batch, hidden]# step3 计算相似度cos  CosineSimilarity()(senA_pooled_output, senB_pooled_output)    # [batch, ]# step4 计算lossloss  Noneif labels is not None:loss_fct  CosineEmbeddingLoss(0.3)loss  loss_fct(senA_pooled_output, senB_pooled_output, labels)output  (cos,)return ((loss,)  output) if loss is not None else outputmodel  DualModel.from_pretrained(model_path)1.3.3 创建评估函数 
import evaluate# 这里采用离线加载
accuracy_path  /root/autodl-tmp/transformers-code/metrics/accuracy
f1_path  /root/autodl-tmp/transformers-code/metrics/f1acc_metric  evaluate.load(accuracy_path)
f1_metirc  evaluate.load(f1_path)def eval_metric(eval_predict):predictions, labels  eval_predictpredictions  [int(p  0.7) for p in predictions]labels  [int(l  0) for l in labels]acc  acc_metric.compute(predictionspredictions, referenceslabels)f1  f1_metirc.compute(predictionspredictions, referenceslabels)acc.update(f1)return acc1.3.4 创建TrainingArguments及Trainer 
train_args  TrainingArguments(output_dir./dual_model,      # 输出文件夹per_device_train_batch_size32,  # 训练时的batch_sizeper_device_eval_batch_size32,  # 验证时的batch_sizelogging_steps10,                # log 打印的频率evaluation_strategyepoch,     # 评估策略save_strategyepoch,           # 保存策略save_total_limit3,              # 最大保存数learning_rate2e-5,              # 学习率weight_decay0.01,               # weight_decaymetric_for_best_modelf1,      # 设定评估指标load_best_model_at_endTrue)     # 训练完成后加载最优模型trainer  Trainer(modelmodel, argstrain_args, train_datasettokenized_datasets[train], eval_datasettokenized_datasets[test], compute_metricseval_metric)trainer.train()1.3.5 自定义pipeline实现模型评估 
class SentenceSimilarityPipeline:def __init__(self, model, tokenizer) - None:self.model  model.bertself.tokenizer  tokenizerself.device  model.devicedef preprocess(self, senA, senB):return self.tokenizer([senA, senB], max_length128, truncationTrue, return_tensorspt, paddingTrue)def predict(self, inputs):inputs  {k: v.to(self.device) for k, v in inputs.items()}return self.model(**inputs)[1]  # [2, 768]def postprocess(self, logits):cos  CosineSimilarity()(logits[None, 0, :], logits[None,1, :]).squeeze().cpu().item()return cosdef __call__(self, senA, senB, return_vectorFalse):inputs  self.preprocess(senA, senB)logits  self.predict(inputs)result  self.postprocess(logits)if return_vector:return result, logitselse:return resultpipe  SentenceSimilarityPipeline(model, tokenizer)pipe(我喜欢北京, 明天不行, return_vectorTrue)(0.4414671063423157,tensor([[ 0.8044, -0.7820,  0.9974,  ..., -0.6317, -0.9653, -0.4989],[ 0.3756,  0.0484,  0.9767,  ..., -0.9928, -0.9980, -0.5648]],devicecuda:0, grad_fnTanhBackward0))注文本向量化更加便捷有效的工具 
sentence-transformers 
https://www.sbert.net/ 
text2vec 
https://github.com/shibing624/text2vec 
uniem 
https://github.com/wangyuxinwhy/uniem 
2、检索式对话机器人 
2.1 检索式对话机器人简介 对话机器人在本质上是一个用来模拟人类对话或聊天的计算机程序接收人类的自然语言作为输入并给出合适的回复  按照任务类型划分对话机器人简单的可以划分为闲聊机器人、问答机器人、任务型对话机器人  按照答案产生的逻辑划分对话机器人可以划分为检索式对话机器人和生成式对话机器人  
如何实现基于检索的问答机器人? 
QQ匹配策略 
可以利用QQ匹配策略即取最优结果的Q对应的Answer作为最终结果。 但是使用向量匹配的模型效果并不好很难直接取到最优结果  因此引入基于交互策略模型。向量匹配模块又称为召回模块交互策略的模块又称为排序模块  2.2 向量匹配和交互策略结合实现检索对话机器人 
法律知道数据集
https://github.com/SophonPlus/ChineseNlpCorpus预训练模型
1.2章节训练的交互模型
1.3章节训练的匹配模型2.2.1 加载自己训练的向量匹配模型 
import pandas as pddata  pd.read_csv(./law_faq.csv)
data.head()# dual_model.py文件中是自定义的DualModel
from dual_model import DualModel
from transformers import AutoTokenizer# 加载自己训练好的模型
dual_model  DualModel.from_pretrained(../12-sentence_similarity/dual_model/checkpoint-500/)
dual_model  dual_model.cuda()
dual_model.eval()
print(匹配模型加载成功)# 加载tokenzier
model_path  /root/autodl-fs/models/chinese-macbert-base
tokenzier  AutoTokenizer.from_pretrained(model_path)2.2.2 将知识库中的问题编码为向量 
import torch
from tqdm import tqdmquestions  data[title].to_list()
vectors  []
with torch.inference_mode():for i in tqdm(range(0, len(questions), 32)):batch_sens  questions[i: i  32]inputs  tokenzier(batch_sens, return_tensorspt, paddingTrue, max_length128, truncationTrue)inputs  {k: v.to(dual_model.device) for k, v in inputs.items()}# 这里拿出[CLS]的向量表示vector  dual_model.bert(**inputs)[1]vectors.append(vector)
vectors  torch.concat(vectors, dim0).cpu().numpy()
vectors.shape(18213, 768)2.2.3 将知识库中的问题向量存入向量库中 
# pip install faiss-cpu
import faissindex  faiss.IndexFlatIP(768)
faiss.normalize_L2(vectors)
index.add(vectors)
index2.2.4 将用户问题编码为向量 
quesiton  寻衅滋事
with torch.inference_mode():inputs  tokenzier(quesiton, return_tensorspt, paddingTrue, max_length128, truncationTrue)inputs  {k: v.to(dual_model.device) for k, v in inputs.items()}vector  dual_model.bert(**inputs)[1]q_vector  vector.cpu().numpy()
q_vector.shape(1, 768)2.2.5 向量匹配 
faiss.normalize_L2(q_vector)
# 使用faiss进行搜索
scores, indexes  index.search(q_vector, 10)# 将匹配到的相似问题及答案召回
topk_result  data.values[indexes[0].tolist()]# 匹配到的相似问题
topk_result[:, 0]array([涉嫌寻衅滋事, 两个轻微伤够寻衅滋事, 敲诈勒索罪, 聚群斗殴, 飞达暴力催收, 打架斗殴,涉嫌犯罪, 殴打他人治安处罚, 遵守法律的措施, 十级伤残工伤], dtypeobject)2.2.6 加载自己训练的交互模型 
from transformers import BertForSequenceClassificationcorss_model  BertForSequenceClassification.from_pretrained(../12-sentence_similarity/cross_model/checkpoint-500/)
corss_model  corss_model.cuda()
corss_model.eval()
print(模型加载成功)2.2.7 最终的预测结果 
# 候选问题集合
canidate  topk_result[:, 0].tolist()
ques  [quesiton] * len(canidate)
inputs  tokenzier(ques, canidate, return_tensorspt, paddingTrue, max_length128, truncationTrue)
inputs  {k: v.to(corss_model.device) for k, v in inputs.items()}
with torch.inference_mode():logits  corss_model(**inputs).logits.squeeze()result  torch.argmax(logits, dim-1)
resulttensor(0, devicecuda:0)# 候选答案集合
canidate_answer  topk_result[:, 1].tolist()match_quesiton  canidate[result.item()]
final_answer  canidate_answer[result.item()]
match_quesiton, final_answer(涉嫌寻衅滋事,说明具有寻衅滋事行为应受到相应的处罚行为人情形严重或行为恶劣的涉嫌了寻衅滋事罪。寻衅滋事是指行为人结伙斗殴的、追逐、拦截他人的、强拿硬要或者任意损毁、占用公私财物的、其他寻衅滋事的行为。寻衅滋事罪是指在公共场所无事生非、起哄闹事造成公共场所秩序严重混乱的追逐、拦截、辱骂、恐吓他人强拿硬要或者任意损毁、占用公私财物破坏社会秩序情节严重的行为。对于寻衅滋事行为的处罚1、《中华人*共和国治安管理处罚法》第二十六条规定有下列行为之一的处五日以上十日以下拘留可以并处五百元以下罚款;情节较重的处十日以上十五日以下拘留可以并处一千元以下罚款:(一)结伙斗殴的;(二)追逐、拦截他人的;(三)强拿硬要或者任意损毁、占用公私财物的;(四)其他寻衅滋事行为;...)