资讯动态

OpenClaw跨模态检索器训练全解:双塔结构、对比学习与工程实践

发布时间:2026/10/18 7:07:24 来源:尧图企业网站定制
前两天有位同行问我OpenClaw 做图搜文检索器是怎么训练的我发现不少人对跨模态检索的理解还停留在让模型看懂图片、再写一句描述——这其实是个根本性的误会。图搜文要的不是生成文字而是从几十万条候选文本里把和图片语义最匹配的那一条捞到最前面。它本质上是学一个图文共享的向量空间让图片向量和对应文本向量靠得足够近让不相关的内容拉得足够远。这篇文章我就用 OpenClaw 项目为例把跨模态检索器的训练思路、数据准备、损失函数、分阶段管线、评测方法和踩过的坑完整梳理一遍。内容偏实操适合正在做多模态检索、想做图文互相搜索、或者只是对对比学习感兴趣的读者。我会尽量把每个为什么这么设计都讲透而不是丢一堆公式完事。1. 检索器不是看图说话模型OpenClaw 真正学的是向量空间1.1 先想明白图搜文到底在搜什么我们先做一个简单的对照。假设产品给用户的入口是上传一张图片返回一段匹配的文字描述后端实际发生的事可以拆成两条路如果是生成式方案模型需要把图片像素解码成句子一次只输出一个结果没法快速处理几十万候选。如果是检索式方案系统提前把全部文本编码成向量建好索引用户上传图片后只需把图片也编码成一个向量然后去向量索引里做最近邻搜索。OpenClaw 走的是第二条路。这条路的好处是文本库再大也能在几十毫秒内返回结果而且召回和精排可以分阶段做。第一次召回粗一点没关系后面可以加重排模型。但代价是——整套系统的上限完全取决于那个图文共享向量空间的质量。这就是检索器训练的终极目标。1.2 双塔结构图像塔与文本塔OpenClaw 的检索器核心是两个编码器经典的双塔结构图像塔把图片处理成固定维度的向量。可以用纯 ViT也可以用 CNN 骨架后接一个池化层再经过一个 projection head 映射到共享空间。文本塔把句子编码成同样维度的向量。常见的是 BERT 类模型的 [CLS] 输出再接 projection head。两个塔的最后一层输出维度保持一致比如 512 或 1024 维。训练的时候我们只约束两边输出的向量在余弦距离或点积距离下满足相似度关系。两个塔内部权重各自独立不需要跨模态交互这也是它能高效做大规模召回的根本原因。1.3 训练的本质让匹配对近、不匹配对远检索器训练可以抽象成一句话让匹配图文对在向量空间里的距离很近让不匹配图文对的距离很远。听起来简单但难点在于不匹配对的选择和多远算远的定义。我见过不少第一次接触这个方向的同学上来就想着用分类思路做把文本类别当标签让图片去分类。这在纯文本检索里有一定道理但在图文检索里行不通——文本是开放域、长尾的没法穷举成固定类别。分类把问题简化成了给图片打离散标签而检索需要的是连续语义空间里的相对距离。所以 OpenClaw 从一开始就放弃分类目标改用度量学习/对比学习的方式来训练。2. 训练数据是胜负手图文对、负样本和难例挖掘2.1 数据形态一张图配若干条文本OpenClaw 最早期的实验数据长这样每张图片附带多条人工或半自动生成的描述。少的只有一条多的有十几条。训练时我们随机抽一条作为正样本同 batch 里的其他文本都视为负样本。这里有一个很容易踩的坑如果某张图有 10 条描述batch 里恰好出现两张相似图片它们的描述互相之间其实也算正相关但 InfoNCE 阶段会把对方当作负样本去推开。这会产生噪声梯度严重时会让模型在相似样本之间摇摆。解决办法主要有两个一是构造 batch 时做同图去重确保一个 batch 内不出现同图的不同描述二是给 loss 加一个同图掩码把同一图片的文本从负样本集合里拿掉。2.2 负样本的三种来源随机负例、batch 内负例、硬负例负样本决定模型能学到什么样的边界。只给简单负样本模型学到的是图片和完全无关文本的区别给硬负样本模型才能学到图片和易混淆文本的细粒度差异。按成本从低到高排序随机负样本从整个文本库里随机抽样实现最简单但训练后期基本没有梯度。batch 内负样本利用当前 batch 里的其他文本充当负样本这也是对比学习最经典的做法。好处是负样本来自当前真实分布成本几乎为零。硬负样本从检索结果里把相似但不匹配的文本捞出来专门构造困难样本。效果最好但需要额外维护一个难例队列或定期用当前模型做一轮检索挖掘。OpenClaw 最终采用的是batch 内负样本 周期性硬负样本挖掘的组合。batch 内负样本保证训练高效每训练若干个 epoch 后跑一次全量检索把 top-k 里错误结果抽出来当作额外的负样本喂回去。这个迭代对指标提升非常明显尤其是在相似场景、相似物体、相似属性上。2.3 数据清洗比模型结构更影响最终效果我有一次在实验报告里写过一句很直白的话换模型提 1 个点换数据清洗提 5 个点。这不是夸张。OpenClaw 的训练文本里经常出现三类脏数据纯标签式文本比如猫、树、天空这种描述没有完整语义。重复模板文本库存在大量这是一张关于某某的图片这种句式模型容易偷懒学句式。图文对应弱图片是雨天街道文本却是晴天郊外散步人工标注出错。清洗流程我们一般分四步长度过滤、重复文本去重、图文相关性打分过滤、人工抽检。其中相关性打分可以用一个现成的强模型做弱监督过滤比如拿通用图文匹配模型给数据打分分数过低直接丢弃。这步能显著提高整体样本质量也能让模型更专注在语义关系上。2.4 图文对不够怎么办合成数据与标题扩展真实图文对永远是稀缺的。OpenClaw 在冷启动阶段用过两种很实用的方法。第一种是搜关键字组合出合成文本。雨天 红色汽车 街道这种短语可以自由拼接再配上一套 prompts 模板比如在{场景}里有{物体}天气是{天气}。合成文本的好处是能快速扩大覆盖范围坏处是句式单一所以只用来做预训练不能用来做最终微调。第二种是弱标注扩展。从搜索引擎或公开数据集的相邻数据里把同源文本拿来当弱标签。这会让数据噪声上升但配合批次内大负样本数量模型其实能扛住挺大比例的低质正样本。3. 三种损失函数怎么选InfoNCE、Triplet 和辅助分类 Loss3.1 InfoNCEOpenClaw 默认的选择InfoNCE 是当前对比学习的事实标准。它的形式是L -log [ exp(sim(q,p) / τ) / Σ exp(sim(q, pi) / τ) ]其中 q 是查询向量图片p 是匹配的文本向量pi 遍历 batch 内所有正负样本τ 是温度系数。从直觉上说InfoNCE 做的事情是把正确答案在 softmax 里的占比最大化。它天然支持一个 query 对多个负样本而且梯度包含整个 batch 内的对比信息所以训练非常稳定。在 OpenClaw 里sim 用的是余弦相似度τ 初始设 0.07batch size 越大τ 可以适当调小。τ 越小模型越倾向于拉大所有负样本的间距容易过拟合τ 越大梯度越平缓训练会变慢。我们在少量数据集上调参时发现0.05 到 0.1 之间结果差别不大但超出 0.2 后效果明显退化。3.2 Triplet Loss 的适用场景和坑Triplet Loss 是一个 query、一个正例、一个负例目标是让正例距离小于负例距离加上 marginL max(0, sim(q, p-) - sim(q, p) margin)它和 InfoNCE 的区别在于Triplet 只要求正负的距离差大于 margin要求更低所以在一个 batch 里能利用的信息量更少。我们做实验的结果是只用 Triplet 训练 OpenClaw收敛速度明显慢于 InfoNCE最终 recall10 也低 3 到 5 个点。Triplet 真正好用的场景是在精调阶段当基础对齐已经做完了你只需要小步调整某一类难例Triplet 会更可控不会像 InfoNCE 那样牵一发动全身。另外如果内存里实在放不下大 batchTriplet 是最省显存的选择。3.3 辅助 Loss图文匹配分类只用对比 Loss 会出现一个问题向量空间只在相对距离上有意义没有直接学是否匹配的二分类边界。有的实现会给双塔加一个额外头把特征拼接后过一个 MLP 做图文匹配二分类ITM参与联合训练。这个辅助 Loss 表面上让模型多了一个目标实际作用是在特征里保留可判别的匹配信息对后续精排阶段有好处。OpenClaw 在微调阶段会打开这个分支预训练阶段反而关掉——预训练数据太脏硬让模型做严格二分类会引入大量噪声梯度。3.4 超参组合怎么定我做了一个小表记录 OpenClaw 在 50 万图文对上的对比实验损失组合温度/超参Recall5备注仅 InfoNCEτ0.070.682基线InfoNCE 同图掩码τ0.070.694消除同图互怼噪声Triplet onlymargin0.20.621收敛慢InfoNCE ITMτ0.07, λ0.20.701精调后效果更好InfoNCE 硬负例τ0.05, 负例2560.718挖掘难例后结论是预训练用纯 InfoNCE 就够了微调阶段再开 ITM 和硬负例别一上来就把组合拉满。4. OpenClaw 三阶段训练管线预训练、对齐微调、检索精排4.1 阶段一大规模弱相关图文对预训练第一阶段的目标是让双塔形成大致能对齐的语义空间不需要数据集太干净。我们用了几百万到上千万规模的弱相关图文对来源包括网页图文混合数据、公开数据集等。这一阶段 train batch size 尽量拉大OpenClaw 在 8 卡 A100 上跑过 batch size 4096。大 batch 对对比学习至关重要。batch 内负样本数量由 batch size 决定batch 越大同一个 query 能见过的负样本越多模型学到的决策边界越准确。这可以说是整个对比学习方法的命门。预训练阶段的损失只有 InfoNCE学习率用 Transformer 常用的线性预热 余弦衰减。图像塔直接用预训练好的 ViT 权重初始化文本塔用预训练好的 Bert 权重初始化这样能明显缩短收敛时间。4.2 阶段二小规模精标数据微调第二阶段的任务是纠偏。预训练数据里的图文关系是弱相关的很多描述并不精确。OpenClaw 会人工标注一批高质量的图文对数量控制在 10 万左右覆盖高频搜索场景和易混淆场景。微调时我们通常冻结图像塔和文本塔的前几层只更新后几层和 projection head。原因很简单底层特征轮廓、颜色、基础语义是通用的不需要为了检索任务重新学一遍高层语义对齐才需要按任务调整。微调阶段可以引入从硬负例挖掘得到的负样本并开启 ITM 辅助头。学习率要比预训练小一个数量级否则会出现严重的灾难性遗忘。4.3 阶段三重排序与蒸馏双塔检索器的上限在于它没有做图文 token 级交互。你说它快它就快在双塔各自独立编码但快的代价是有些需要联合推理的语义关系学不好。比如左边的猫压住了右边的书这种空间关系双塔只看全局向量很容易把图和描述匹配错。OpenClaw 的处理方式是给系统加一个重排阶段第一路双塔召回 top 100。第二路用一个更重的跨模态编码模型图文一起进 Transformer对 top 100 逐对打分。第三路把分数融合输出最终排序。这里有个非常值得讲的小技巧重排模型可以用蒸馏方式来训练。先用跨模态大模型在 batch 内给图文对打软标签然后用这个软标签去训练一个轻量级重排模型。蒸馏能在保持精度的前提下大幅降低线上成本。4.4 训练时怎么构建 batch 内负样本代码级示例最后给一段简化版的 PyTorch 风格代码展示 OpenClaw 在单卡单 batch 里构造 InfoNCE 损失的方式import torch import torch.nn.functional as F def contrastive_loss(image_embeds, text_embeds, temperature0.07): # image_embeds / text_embeds: (batch_size, embed_dim)已经 L2 归一化过了 # 相似度矩阵第 i 行第 j 列表示 第 i 张图片和第 j 条文本的余弦相似度 logits image_embeds text_embeds.t() / temperature # (batch_size, batch_size) labels torch.arange(logits.shape[0]).to(logits.device) # 图片到文本方向 loss_i2t F.cross_entropy(logits, labels) # 文本到图片方向 loss_t2i F.cross_entropy(logits.t(), labels) return (loss_i2t loss_t2i) / 2这段代码是对称 InfoNCE也就是既做图搜文也做文搜图。很多跨模态检索任务把这两个方向当成两个独立任务对待但实际用对称损失训练出来的向量空间更均匀不会偏向某一个模态。在分布式环境里这段代码需要改一下每个 GPU 先用 all_gather 把所有塔的输出汇总到一起再计算全局的相似度矩阵。否则每个 GPU 各自算 local batch 的 InfoNCE负样本数量就会被切成若干份效果明显变差。这是我在训练 OpenClaw 过程中踩过的大坑之一后面会单独说。5. 评测指标与 bad case为什么 Recallk 会骗人5.1 标准指标Recallk、MRR 和 NDCGOpenClaw 的离线评测主要看三个指标Recallk对每个 query 取前 k 个结果看正确结果在不在里面。最常用的有 Recall1、Recall5、Recall10。MRR正确结果的倒数排名的均值。它比 Recall 更敏感比如同一个 query 的正确结果从第 3 名移到第 1 名MRR 会立刻提升。NDCG考虑结果列表里可能有多个相关项按位置衰减折算收益。三个指标都在看但上线前真正拍板的是 Recall5 加上人工 bad case 抽检。原因是 Recall 只关心有没有在 top k不关心错误结果有多离谱而人工 review 能捕捉到很多指标看不出来的问题。5.2 评估集划分最容易犯的坑泄漏做跨模态检索评估最大的坑是数据集泄漏。我有一次发现 OpenClaw 的 Recall10 从 0.73 涨到 0.81特别兴奋排查之后才发现问题出在评估集里混入了训练集的近似文本——文本库里有重复修改过的句子模型其实是在背文本不是理解语义。正确的划分原则是按图片做 group 划分同一组语义接近的图片和文本要么全在训练集要么全在评估集不能被拆开。另外文本库里的重复片段要去重或者至少把包含相同高频短语的文本归到 same group避免泄漏。5.3 Bad case 三类每个都能追溯到训练设计OpenClaw 的 bad case 大致分三型第一型是属性丢失图上明明是红色汽车在雪地里模型找回的是汽车在雪地里。原因是训练数据里带颜色属性描述的文本占比不够高模型没把红色这种细粒度属性学到极致。第二型是概念混淆图片上是柯基检索结果出现柴犬的描述。这通常发生在底层的视觉相似度过强、高层语义区隔不足时。对策是加强硬负例挖掘专门构造相似犬种 不同品种名的难例。第三型是逻辑关系错误图片是一个女孩站在桥上看着夕阳检索结果变成一个女孩在桥上看风景漏掉了夕阳。这类问题双塔很难解决我们是靠重排阶段的跨模态模型来兜底。建议每个做跨模态检索的人都在训练过程中保留一个固定的 bad case 集每次实验完看一批。你会发现很多指标涨了但体验变差的情况靠 bad case 才能定位到真实原因。5.4 召回池大小会影响指标还有一点要提醒评估时的召回池大小candidate pool size必须提前定好并且线上保持一致。OpenClaw 早期在 1 万候选池上测评指标很高一上线上变成 50 万候选池Recall5 掉得非常快。小候选池里前 5 名的竞争压力小看不出长尾区分能力。正确的做法是评估和线上的召回池规模保持一致或者在发布前用同等量级的池子做一次全量评测。6. 训练中的那些坑精度、批量大小和分布式负样本6.1 混合精度训练里的梯度消失OpenClaw 初期用 fp16 混合精度训练loss 常常在下降一段时间后突然变成 NaN或者长时间不动。排查下来是两个原因InfoNCE 里的 exp 在低精度下很容易溢出特别是相似度接近正负极限时。温度系数偏小会让 logits 数值变大fp16 的表示范围更吃力。解决办法有两个一是用 bf16 代替 fp16数值稳定性会好很多二是把损失函数里的 softmax 改成稳定版本比如先减掉 logits 的最大值再做 exp。代码层面改动不大但对训练稳定性影响非常大。6.2 大 batch 与显存的矛盾对比学习最理想的情况是 batch 越大越好但显存是物理约束。OpenClaw 的图像塔和文本塔各自都很大batch size 拉高后显存直接爆掉。我们做过两个取舍用 gradient checkpointing 换取更大 batch训练速度慢 15%但 batch 可以提高一倍。把投影头的输出维度从 1024 降到 512这样相似度矩阵的显存占用直接降为原来的四分之一。大 batch 带来的收益不是线性的从 256 提到 512 有明显提升但从 1024 提到 2048 收益就开始放缓。在显存和效率之间找平衡建议先用 512 起步再逐步往上试。6.3 分布式训练时负样本数量被切碎的坑这是 OpenClaw 早期最不值得踩但最容易踩的坑。多卡训练时如果每个 GPU 只拿自己的 batch 算 InfoNCE负样本数量就等于本地 batch size而不是全局 batch size。8 卡每卡 128全局是 1024但每卡实际只用了 127 个负样本白白浪费了一半以上数据。标准解法是跨卡 all-gather每个 step 先把所有卡上的图像向量和文本向量收集起来组成全局矩阵再算 loss。我当时用 PyTorch 分布式训练时写了一个小封装把所有卡上的特征做 all_gather 后再进入对比损失函数效果立竿见影负样本数量直接从 127 变成 1023。6.4 Loss 不降怎么办从数据、温度、初始化三处下手如果你第一次训练跨模态检索器loss 不动是很常见的。我自己的排查顺序是先看 loss 是否在 sanity check 阶段就符合预期。一个快速 sanity check 是只取 16 个样本在小模型上过拟合如果 loss 能降到接近 0说明模型和数据管线没问题。检查温度系数。τ 太大梯度平缓先调小到 0.05 附近试试。检查投影头输出是否做了 L2 归一化。很多人在这里漏掉归一化导致余弦相似度和 dot 相似度混用训练目标不一致。最后再看数据质量。如果 batch 里全是低质量图文对模型很容易学到一个高相似度区域而没有任何区分度这时候要动数据不要动模型。写在最后一些个人体会做 OpenClaw 这套检索器这一年多我最大的一个感受是跨模态检索的训练门槛不在模型结构而在数据组织和对距离关系的理解。很多人一开始执着于换更强的编码器但同样一批数据只是把负样本从随机改成硬负例把温度系数调对效果提升就远超换一个更大的 backbone。另一个体会是图搜文永远不要只盯着离线指标。指标只能告诉你结果有没有排到前面不能告诉你用户为什么觉得这个结果很怪。我们后期很大一部分精力都花在看 bad case、拆解错误模式、针对性补数据上。这个过程很磨人但效果稳定且持久。如果现在让我给刚接触这个方向的人一个建议我会说别急着追论文里的新 loss先用最简单的双塔加 InfoNCE把你手头数据的负样本结构搞清楚。这一步做扎实了后面所有优化都会顺利很多。OpenClaw 的下一轮迭代我们计划把长尾概念和组合语义再深挖一层到时候再专门写一篇数据侧的经验分享。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑