语义索引(可通俗理解为向量索引)技术是搜索引擎、推荐系统、广告系统在召回阶段的核心技术之一。语义索引模型的目标是:给定输入文本,模型可以从海量候选召回库中快速、准确地召回一批语义相关文本。语义索引模型的效果直接决定了语义相关的物料能否被成功召回进入系统参与上层排序,从基础层面影响整个系统的效果。

In-batch negatives

我们采用百度paddleNLP里提到的In-batch Negatives方案。

In-batch Negatives 策略的训练数据为语义相似的 Pair 对,策略核心是在 1 个 Batch 内同时基于 N 个负例进行梯度更新,将Batch 内除自身之外其它所有 Source Text 的相似文本 Target Text 作为负例,例如: 上例中“我手机丢了,我想换个手机” 有 1 个正例(”我想买个新手机,求推荐“),3 个负例(1.求秋色之空全集漫画,2.手机学日语的软件,3.侠盗飞车罪恶都市怎么改车)。

具体来说,In-batch negatives策略的实施步骤如下:

  1. 选择正样本:首先从当前批次中选择出一个正样本,这个样本是模型需要正确识别的目标样本。
  2. 选择负样本:然后从同一批次中随机选择或根据特定规则选择一些负样本。这些负样本可以是与正样本相似但被错误标记的样本,也可以是完全不相关的样本。
  3. 模型训练:将正样本和负样本一起输入模型进行训练。模型需要学会区分正样本和负样本,从而提高推荐或检索的准确性。

In-batch negatives策略的优势在于:

  • 提高模型的区分能力:通过在每个批次中引入负样本,模型被迫学习如何区分正样本和负样本,这有助于提高模型的泛化能力和区分度。
  • 利用现有数据:不需要额外的负样本库,可以直接利用当前批次中的数据作为负样本,这在数据有限的情况下尤其有用。
  • 减少计算资源消耗:与从全局样本集中采样负样本相比,In-batch negatives可以减少计算资源的消耗,因为它避免了在整个数据集上进行负采样的需要。

然而,In-batch negatives策略也存在一些潜在的问题,例如:

  • 批次大小的限制:如果批次大小较小,可能无法提供足够多样化的负样本,这可能影响模型的学习效果。
  • 偏差问题:由于负样本是在同一个批次中选择的,可能会出现某些样本被频繁选为负样本的情况,这可能导致模型学习到的表示存在偏差。

一般通过 Recall@1,Recall@5 ,Recall@10 ,Recall@20 和 Recall@50 指标来评估语义索引模型的召回效果。按照paddleNLP给出的基线:

策略 模型 Recall@1 Recall@5 Recall@10 Recall@20 Recall@50
In-batch Negatives ernie 1.0 51.301 65.309 69.878 73.996 78.881
In-batch Negatives rocketqa-zh-base-query-encoder 59.622 75.089 79.668 83.404 87.773

rocketqa作为打底transformer模型效果更好。

总结,为什么为采用In-batch negatives,一方面能充分利用现有数据,不用单独准备负样例,减少投入,另外一方面模型的区分能力也比较好。

模型数据方案

流传一句话,用1亿条数据,训练10个epoch,不如用10亿数据训练一个epoch,也就是见多识广,大力出奇迹。

我们要训练一个给搜索用的向量召回模型,核心就是让准备足够多的正样例数据。正样例数据,一方面网上有较多的开源数据,可以直接利用。另外一方面,之间了解SimBERT 时,他们的数据很多也源自于搜索数据,所以可以通过搜索引擎将query和召回结果的doc作为相似句对。

作为试验,我们构造了8000万的一个小训练集,用rocketqa-zh-mini-query-encoder作为打底模型,训练256维的embedding模型。

root_path=inbatch
python -u -m paddle.distributed.launch --gpus "0" \
train_batch_neg.py \
--device gpu \
--save_dir ./checkpoints/${root_path} \
--batch_size 64 \
--learning_rate 5E-5 \
--epochs 3 \
--output_emb_size 256 \
--model_name_or_path rocketqa-zh-mini-query-encoder \
--save_steps 5000 \
--max_seq_length 128 \
--margin 0.2 \
--train_set_file recall/train.csv \
--recall_result_dir "recall_result_dir" \
--recall_result_file "recall_result.txt" \
--hnsw_m 100 \
--hnsw_ef 100 \
--recall_num 50 \
--similar_text_pair_file "recall/dev.csv" \
--corpus_file "recall/corpus.csv"

训练完成导出onnx模型:

def convert_model(model_path):

    try:
import onnx
import onnxruntime as ort
import paddle2onnx
from onnxconverter_common import float16
except ImportError:
print(
"The inference precision is change to 'fp32', please install the dependencies that required for 'fp16' inference, pip install onnxruntime-gpu onnx onnxconverter-common"
)
onnx_dir = os.path.join(model_path, "onnx") if not os.path.exists(onnx_dir):
os.mkdir(onnx_dir)
float_onnx_file = os.path.join(onnx_dir, "model.onnx")
if not os.path.exists(float_onnx_file):
onnx_model = paddle2onnx.command.c_paddle_to_onnx(
model_file=os.path.join(model_path, "inference.pdmodel"),
params_file=os.path.join(model_path, "inference.pdiparams"),
opset_version=13,
enable_onnx_checker=True,
)
with open(float_onnx_file, "wb") as f:
f.write(onnx_model)
fp16_model_file = os.path.join(onnx_dir, "fp16_model.onnx")
if not os.path.exists(fp16_model_file):
onnx_model = onnx.load_model(float_onnx_file)
trans_model = float16.convert_float_to_float16(onnx_model, keep_io_types=True)
onnx.save_model(trans_model, fp16_model_file)

加载测试:

class MiniRocketQAEmbedding():

    def __init__(self, model_file: str = model_file, use_gpu: bool = True):

        providers = ['CUDAExecutionProvider'] if use_gpu else ['CPUExecutionProvider']
sess_options = ort.SessionOptions()
self.predictor = ort.InferenceSession(
model_file, sess_options=sess_options, providers=providers)
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path) def embeding(self, embeding_text):
features = self.tokenizer(embeding_text, max_seq_len=128,
pad_to_max_seq_len=True, truncation_strategy="longest_first") vecs = self.predictor.run(None, features.data)
return vecs[0] def similarity(self, pairs):
query = pairs[0][0]
texts = [item[1] for item in pairs]
emdbeding_text = [query]
emdbeding_text.extend(texts)
features = self.tokenizer(emdbeding_text, max_seq_len=128,
pad_to_max_seq_len=True, truncation_strategy="longest_first") vecs = self.predictor.run(None, features.data) # print(vecs) query_embeding = vecs[0][0]
vecs_text1 = query_embeding / (query_embeding**2).sum() ** 0.5 result = []
for i in range(1, len(vecs[0])):
vecs_text2 = vecs[0][i]
vecs_text2 = vecs_text2 / (vecs_text2**2).sum() ** 0.5
similarity = (vecs_text1 * vecs_text2).sum()
result.append({"similarity": float(similarity)}) return result if __name__ == "__main__":
bert = MiniRocketQAEmbedding(use_gpu=False)
import time
start = time.time()
bert.embeding(["双鱼座性格特点","双鱼座性格特点"])
print((time.time() - start) * 1000)

通过MTEB框架来测试自建搜索测试集效果:

if __name__ == '__main__':

    model = MyModel()
task_names = ["SSRetrieval"] for task in task_names:
model.query_instruction_for_retrieval = None
evaluation = MTEB(tasks=[task], task_langs=['zh', 'zh-CN'])
evaluation.run(model, output_folder=f"zh_results/256_model", batch_size=64)

测试结果:

{
"dataset_revision": null,
"dev": {
"evaluation_time": 251.86,
"map_at_1": 0.13427,
"map_at_10": 0.62859,
"map_at_100": 0.72526,
"map_at_1000": 0.72564,
"map_at_3": 0.31398,
"map_at_5": 0.45025,
"mrr_at_1": 0.71863,
"mrr_at_10": 0.81982,
"mrr_at_100": 0.82077,
"mrr_at_1000": 0.82078,
"mrr_at_3": 0.80707,
"mrr_at_5": 0.81587,
"ndcg_at_1": 0.71803,
"ndcg_at_10": 0.77357,
"ndcg_at_100": 0.83634,
"ndcg_at_1000": 0.83907,
"ndcg_at_3": 0.72048,
"ndcg_at_5": 0.73003,
"precision_at_1": 0.71803,
"precision_at_10": 0.53373,
"precision_at_100": 0.07386,
"precision_at_1000": 0.00747,
"precision_at_3": 0.68889,
"precision_at_5": 0.65699,
"recall_at_1": 0.13427,
"recall_at_10": 0.78675,
"recall_at_100": 0.98082,
"recall_at_1000": 0.99181,
"recall_at_3": 0.35371,
"recall_at_5": 0.53211
},
"mteb_dataset_name": "SSRetrieval",
"mteb_version": "1.1.1"
}

同样的数据集,用peg模型测试:

{
"dataset_revision": null,
"dev": {
"evaluation_time": 1036.11,
"map_at_1": 0.09911,
"map_at_10": 0.42835,
"map_at_100": 0.49497,
"map_at_1000": 0.49681,
"map_at_3": 0.2277,
"map_at_5": 0.31901,
"mrr_at_1": 0.56794,
"mrr_at_10": 0.67111,
"mrr_at_100": 0.6737,
"mrr_at_1000": 0.67386,
"mrr_at_3": 0.65495,
"mrr_at_5": 0.66559,
"ndcg_at_1": 0.56794,
"ndcg_at_10": 0.56275,
"ndcg_at_100": 0.62991,
"ndcg_at_1000": 0.64939,
"ndcg_at_3": 0.55564,
"ndcg_at_5": 0.54815,
"precision_at_1": 0.56794,
"precision_at_10": 0.38468,
"precision_at_100": 0.05755,
"precision_at_1000": 0.00641,
"precision_at_3": 0.53329,
"precision_at_5": 0.49464,
"recall_at_1": 0.09911,
"recall_at_10": 0.55328,
"recall_at_100": 0.7634,
"recall_at_1000": 0.84758,
"recall_at_3": 0.25931,
"recall_at_5": 0.38263
},
"mteb_dataset_name": "SSRetrieval",
"mteb_version": "1.1.1"
}
模型 Recall@1 Recall@10 Recall@100 Recall@1000
peg模型 9.911 55.328 76.34 84.758
微调256模型 13.427 78.675 98.082 99.181

可以看到,微调的模型,用更小的参数,见多识广后,整体效果明显优于未经历大规模数据训练的更大尺寸的模型。

参考

In-batch negatives Embedding模型介绍与实践的更多相关文章

  1. 模型介绍之FastText

    模型介绍一: 1. FastText原理及实践 前言----来源&特点 fastText是Facebook于2016年开源的一个词向量计算和文本分类工具,在学术上并没有太大创新.但是它的优点也 ...

  2. (zhuan) 深度学习全网最全学习资料汇总之模型介绍篇

    This blog from : http://weibo.com/ttarticle/p/show?id=2309351000224077630868614681&u=5070353058& ...

  3. OSI七层网络模型与TCP/IP四层模型介绍

    目录 OSI七层网络模型与TCP/IP四层模型介绍 1.OSI七层网络模型介绍 2.TCP/IP四层网络模型介绍 3.各层对应的协议 4.OSI七层和TCP/IP四层的区别 5.交换机工作在OSI的哪 ...

  4. IO模型介绍

    先理解几个问题: (1)为什么读取文件的时候,需要用户进程通过系统调用内核完成(系统不能自己调用内核)什么是用户态和内核态?为什么要区分内核态和用户态呢? 在 CPU 的所有指令中,有些指令是非常危险 ...

  5. 关于Axure RP软件的介绍——软件工程实践第二次个人作业

    关于Axure RP软件的介绍——软件工程实践第二次个人作业 Axure RP是一个非常专业的快速原型设计的一个工具,客户提出需求,然后根据需求定义和规格.设计功能和界面的专家能够快速创建应用软件或W ...

  6. RabbitMQ系列(三)RabbitMQ交换器Exchange介绍与实践

    RabbitMQ交换器Exchange介绍与实践 RabbitMQ系列文章 RabbitMQ在Ubuntu上的环境搭建 深入了解RabbitMQ工作原理及简单使用 RabbitMQ交换器Exchang ...

  7. python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)

    昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...

  8. {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块

    python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...

  9. 深入理解 Java 内存模型(一)- 内存模型介绍

    深入理解 Java 内存模型(一)- 内存模型介绍 深入理解 Java 内存模型(二)- happens-before 规则 深入理解 Java 内存模型(三)- volatile 语义 深入理解 J ...

  10. IO模型《一》IO模型介绍

    IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞 ...

随机推荐

  1. Win10已死!微软发布Windows 11大更新:引入ChatGPT、升级巨大

    今天凌晨微软在开发者大会上公布了Windows 11的新版本更新"Moment 3",整体升级幅度非常的大. 新系统的多任务有了改进,现在按下Alt+Tab时,可以显示更多的Edg ...

  2. 多进程|基于非阻塞调用的轮询检测方案|进程等待|重新理解挂起|Linux OS

    说在前面 今天给大家带来操作系统中进程等待的概念,我们学习的操作系统是Linux操作系统. 我们今天主要的目标就是认识wait和waitpid这两个系统调用. 前言 那么这里博主先安利一下一些干货满满 ...

  3. php获取服务器操作系统等信息

    php获取服务器操作系统等信息 获取请求页面时通信协议的名称和版本: $_SERVER['SERVER_PROTOCOL'] 例如,"HTTP/1.0". PHP程式版本:< ...

  4. Flutter聊天室|dart+flutter仿微信App界面|flutter聊天实例

    一.项目概述 flutter-chatroom是采用基于flutter+dart+chewie+image_picker+photo_view等技术跨端开发仿微信app界面聊天室项目.实现了消息发送/ ...

  5. 19c RAC 告警日志报错 ORA 7445 [pevm_icd_call_common()+225]

    问题现象: 在一套2节点的19c RAC 环境下,节点2 alert告警 ORA 7445,且频度固定为每分钟报一次:期间有重启实例,但故障依旧: ========================== ...

  6. ASP.NET Core分布式项目实战(Consent视图制作)--学习笔记

    任务19:Consent视图制作 按照上一节 Consent 的思路 在 mvcCookieAuthSample 项目的 Controllers 文件夹下新建一个 ConsentController ...

  7. NC16681 [NOIP2003]加分二叉树

    题目链接 题目 题目描述 ​ 设一个n个节点的二叉树tree的中序遍历为(l,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tr ...

  8. vs 工程中替换 Qt 静态库

    上篇介绍了如何编译 Qt 静态库 编译 windows 上的 qt 静态库 这篇介绍如何替换已有的 Qt 静态库,比如 Qt5.15.0 有很多 bug,我们不得不提升 Qt 版本来避免 bug 导致 ...

  9. 记一次 QT VS Tools 无法配置 version 的问题

    问题概述: 在 QT VS Tools 拓展工具中添加多个 qt 版本的静态库时,发现输入完 Name 和 Path 之后点击 OK,新输入的 version 路径并没有保存成功 测试的 QT VS ...

  10. 【Android逆向】定位native函数在哪个so中方法

    1. 在逆向过程中经常需要定位方法在哪个so中,而app加载的so很多,比如 那么如何快速定位方法在哪里呢 2. 比如如下案例,首先看日志 03-28 11:01:56.457 14566 14566 ...