本质就是文本匹配:粗排+精排;
在自然语言处理 (NLP) 领域,检索类聊天机器人是一种常见的应用,它们通常用于快速、准确地从已知的信息集中检索答案。这种类型的聊天机器人通过匹配用户的查询与预先准备好的答案集合来工作,而不是生成全新的响应。
有两种方式得到检索结果:
- 问题与问题之间,比较多。qq问题;
- 问题与答案之间进行检索;
1 把所有问题放入模型BERT中,得到问题候选集合;
2 对用户问题进行编码,得到问题向量;
3 匹配;可能通过fassi得到的结果不是很好,可以把前十拿出来,然后再精排序;
fassi模型加载的太多数据会导致机器崩调。我猜测是在做与其他向量匹配时候内存溢出导致。
1 faiss
FAISS (Facebook AI Similarity Search) 是一个开源的库,由 Facebook AI Research (FAIR) 开发并维护。它主要用于高效地搜索大规模的向量集合,并且特别适用于高维数据的情况。FAISS 提供了多种索引结构和相似度度量方法,可以极大地提高相似性搜索的速度。
主要特点
- 高性能:FAISS 能够在大规模的向量集合上执行快速的近似最近邻搜索。
- 灵活:支持多种距离度量方法,例如 L2 距离、内积等。
- 可扩展:能够处理数十亿级别的向量集合。
- 易于集成:提供 C++ 和 Python 接口,方便集成到现有的项目中。
使用场景
- 推荐系统:根据用户的行为或偏好找到最相关的项目。
- 计算机视觉:基于图像特征进行图像检索。
- 自然语言处理:基于语义嵌入进行文档检索或问答系统的答案检索。
- 机器学习:在大规模数据集上的聚类分析或异常检测。
技术细节
FAISS 支持多种索引结构和技术来加速搜索过程,包括但不限于:
- Flat Index:直接计算所有向量之间的距离,适用于较小的数据集。
- Inverted File:通过将向量划分到不同的“列表”中来减少搜索范围。
- Product Quantization (PQ):将向量分解成多个子向量,并对每个子向量进行量化,以此来减少存储需求和搜索时间。
- Hierarchical Navigable Small World Graphs (HNSW):构建一个层次图结构,允许在图中进行高效的近似最近邻搜索。
- IVF (Inverted Multi-Index):结合了 Inverted File 和 Product Quantization 的技术,以获得更好的性能和精度。
2 代码部分
torch.inference_mode()
是 PyTorch 中的一个上下文管理器(context manager),它用于指示 PyTorch 处于推理(inference)模式下运行。在推理模式下,PyTorch 不会追踪计算图,这有助于减少内存消耗并提高性能,因为不需要保留梯度信息来进行反向传播。# dual_model.eval() 是一个方法调用,用于将模型设置为评估(evaluation)模式。这个方法对于确保模型在推理阶段能够正确地运行是非常重要的,
# 尤其是在涉及像批量归一化(Batch Normalization)、Dropout 等在训练和评估时行为不同的层时。
import pandas as pd
import json
import os
train_df = pd.read_csv("./law_faq.csv")
train_df = train_df.sample(10000)
# dual_model 模型定义的文件
from dual_model import DualModel
dual_model = DualModel.from_pretrained("../text_similar/code/dual_model/checkpoint-1238/")
#dual_model = dual_model.cuda()
dual_model.eval()
# dual_model.eval() 是一个方法调用,用于将模型设置为评估(evaluation)模式。这个方法对于确保模型在推理阶段能够正确地运行是非常重要的,
# 尤其是在涉及像批量归一化(Batch Normalization)、Dropout 等在训练和评估时行为不同的层时。
print("匹配模型加载成功!")
# 将问题编码为向量,问题候选集合
import torch
from tqdm import tqdm
from transformers import AutoTokenizer
# 加载预训练模型
tokenzier = AutoTokenizer.from_pretrained("../chinese_macbert_large/")
question = train_df['title'].to_list()
vectors = []
with torch.inference_mode():
for i in tqdm(range(0,len(question),32)):
batch_sens = question[i:i+32]
inputs = tokenzier(batch_sens, return_tensors='pt',padding='max_length',max_length=128,truncation=True)
vector = dual_model.bert(**inputs)[1]
vectors.append(vector)
vectors = torch.concat(vectors,dim=0).cpu().numpy()
vectors.shape
# 向量太大 机器会崩
import faiss
index = faiss.IndexFlatIP(768) # 向量的维度 = 768
faiss.normalize_L2(vectors) # 对向量进行normalize
index.add(vectors) # 添加向量
index
quesiton = "寻衅滋事"
with torch.inference_mode():
inputs = tokenzier(quesiton, return_tensors="pt", padding=True, max_length=128, truncation=True)
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
faiss.normalize_L2(q_vector)
scores, indexes = index.search(q_vector, 10)
topk_result = data.values[indexes[0].tolist()]
topk_result[:, 0]
# 加载交互模型
from transformers import BertForSequenceClassification
# 需要完成前置模型训练
corss_model = BertForSequenceClassification.from_pretrained("../12-sentence_similarity/cross_model/checkpoint-500/")
#corss_model = corss_model.cuda()
corss_model.eval()
print("模型加载成功!")
# 排序得分
canidate = topk_result[:, 0].tolist()
ques = [quesiton] * len(canidate)
inputs = tokenzier(ques, canidate, return_tensors="pt", padding=True, max_length=128, truncation=True)
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)
result
dual_model.py
#搭建模型
from transformers import BertForSequenceClassification, BertPreTrainedModel, BertModel
from typing import Optional
from transformers.configuration_utils import PretrainedConfig
from torch.nn import CosineSimilarity, CosineEmbeddingLoss
import torch
class 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_mask=senA_attention_mask,
token_type_ids=senA_token_type_ids,
position_ids=position_ids,
head_mask=head_mask,
inputs_embeds=inputs_embeds,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
)
senA_pooled_output = senA_outputs[1] # [batch, hidden]
senB_outputs = self.bert(
senB_input_ids,
attention_mask=senB_attention_mask,
token_type_ids=senB_token_type_ids,
position_ids=position_ids,
head_mask=head_mask,
inputs_embeds=inputs_embeds,
output_attentions=output_attentions,
output_hidden_states=output_hidden_states,
return_dict=return_dict,
)
senB_pooled_output = senB_outputs[1] # [batch, hidden]
# step3 计算相似度
cos = CosineSimilarity()(senA_pooled_output, senB_pooled_output) # [batch, ]
# step4 计算loss
loss = None
if 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 output