行业资讯
📅 2026/8/28 14:59:14
基于视觉语言模型的Web-Scale搜索相关性度量实践
先记录一下搜索系统在很长一段时间里都默认“相关性 文本相似度”但随着商品、短视频、图文页这类多模态内容占据主要流量纯文本相关性判断越来越力不从心。一个用户搜索“黑色运动鞋 男士”返回的页面上图片是一双棕色皮鞋哪怕标题写满“运动鞋”体验也是失败的。这篇教程围绕“如何用视觉语言模型Vision-Language ModelVLM做 Web-Scale 场景下的搜索相关性度量”展开会先讲清楚相关性的业务定义与两条建模路线再给出一套可落地的级联打分方案包括双塔 VLM、生成式 VLM、FAISS 向量检索、NDCG 评估等完整示例。适合有一定搜索或 NLP 基础的开发者阅读也适合完全没有大模型工程经验、但想快速评估“是否值得引入 VLM”的读者。1. 背景相关性度量为什么需要视觉语言模型1.1 相关性度量的业务含义搜索系统的核心不只是“能搜到”而是“能搜到用户想要的”。相关性度量Relevance Measurement负责回答一个很直接的问题给定一个用户查询 Query 和一条候选结果 Document两者是否相关相关到什么程度在电商场景里用户搜“男士黑色运动鞋”返回一件标题写满“运动鞋”但图片是拖鞋的商品这不算相关返回标题和图片都是黑色运动鞋、但实际只有童鞋尺码的商品也谈不上完全相关。在传统文本检索里这部分判断主要依赖标题、正文、类目等文本特征图片信息通常只能通过OCR或人工维护的图片标题间接纳入信息损耗非常明显。搜索领域为了把排序做得更精细通常会把相关性划分成多个等级。常见的做法是五级标注等级 4完全满足用户需求等级 3高度相关接近用户意图等级 2部分相关只满足一部分意图等级 1弱相关仅有关键词层面的关联等级 0不相关。相关性度量表面上是“打分问题”本质上是在模拟“人看到这个搜索结果后是否满意”的认知判断。这个判断既包含文本语义也包含视觉语义。用户搜“夏天穿的运动鞋”模型如果能看到图片中鞋面是透气网布就比单纯看标题“运动鞋”更容易给出准确分数。1.2 传统文本相关性方案的瓶颈传统方案链路大致是这样的文本召回BM25、倒排索引→ 向量召回文本双塔模型→ 精排特征点击率、文本相关性、类目匹配→ 重排。文本相关性打分可以用 BM25、TF-IDF也可以用 BERT 类模型做 Query-Document 语义匹配。这套体系在纯文本场景下已经很成熟但遇到多模态内容会出现几个非常明显的问题。第一页面信息被截断。商品页、内容页的有效信息很大一部分在图片、视频封面、表格和富媒体里。纯文本模型只能看到标题和正文摘要。如果标题是营销词或者省略语相关性判断就会失真。举个例子某商品标题只写“潮流新款”图片才是真正的运动鞋文本模型无法理解这个商品的真实形态。第二语义粒度不足。用户说“适合送长辈的礼物”靠文本很难知道商品图片呈现的颜色、风格、适用人群。文本模型无法回答“图片里是否真的是一双黑色运动鞋”这种问题。我们需要的不是“关键词是否重合”而是“页面内容是否真正满足查询意图”。第三长尾 Query 冷启动。新上线的内容没有点击数据文本模型缺少交互信号很容易把关键词重复但实际不匹配的页面召回并排到前面。标题堆砌关键词是搜索场景里非常常见的作弊手段这在纯文本链路中很难根治。这些瓶颈不是“换一个更好的文本模型”就能解决的因为信息源本身就缺失了图像通道。VLM 的引入本质上是把“相关性判断”从纯文本语义空间扩展到“文本 图像”的联合语义空间。1.3 VLM 能解决什么问题视觉语言模型Vision-Language ModelVLM能同时接受图像和文本输入并输出跨模态理解结果。它有两个基础能力把图像和文本投影到同一个语义空间计算图文相似度以“看图回答”的方式描述图像内容或回答关于图像的问题。把这两种能力用到相关性度量上正好可以弥补文本模型的缺陷。对应到工程链路上VLM 可以承担两类任务一是做图文语义匹配的初筛分数代替或增强传统的文本向量召回和粗排二是做精排阶段的细粒度相关性判别让模型“亲眼看一看”图片、布局、色彩再结合标题内容输出相关性等级。但要强调一点VLM 不是替代整条搜索链路。在 Web-Scale 场景下候选文档量级达到亿级以上直接让大模型逐条看图打分是不现实的。更合理的定位是VLM 作为相关性信号的生产者在特定环节给现有系统补充更强的多模态判断能力。这也是后面所有实践方案的设计前提。2. VLM 做相关性度量的两条技术路线2.1 双塔 VLM图文向量相似度双塔结构是目前工程落地最成熟的路线。代表结构是 CLIP 类模型文本编码器把 Query 编码成向量图像编码器把商品图或页面截图编码成向量两个向量在联合语义空间里计算余弦相似度。这种做法的优点非常明显文本和图像都可以离线预计算向量在线只需要编码 Query然后通过向量索引检索 Top K延迟可控适合 Web-Scale 的召回和粗排阶段。缺点是模型输出的是一个“整体相似度”对细粒度语义的区分能力有限。它可以告诉你“这张图和这句话整体上比较相关”但很难输出“图片里商品颜色不符合用户要求”这种结构化理由。因此双塔 VLM 更适合做初筛。2.2 生成式 VLM细粒度判别打分生成式 VLM如 LLaVA、Qwen-VL 这类多模态大模型通常由视觉编码器和语言模型组成输入图像和文本提示输出自由文本。用于相关性度量时我们可以把任务包装成一个问答或判别任务给模型看商品图和标题问“请判断这条商品是否满足用户查询的意图并给出相关性等级”。这种方式的优点是理解粒度更细。模型能描述图像内容发现“图中鞋子是褐色的不是用户要求的黑色”这类细节。它还能输出理由便于人工复盘和后续调试。缺点是工程成本明显上升推理成本高单条耗时大只能用于精排/重排阶段的小候选集输出是自由文本需要设计稳定的解析和重试逻辑。2.3 两条路线的取舍落地时不要二选一而是把两条路线放进同一个级联流程里召回/粗排阶段用双塔 VLM 的向量分数或者双塔分数与 BM25 分数加权融合把候选从亿级收敛到千级精排阶段用生成式 VLM 对前几十位候选做细粒度打分输出相关性等级和理由重排阶段再用业务规则多样性、新鲜度、商业策略做最终排序。双塔解决“规模和速度”问题生成式 VLM 解决“精度和解释性”问题。下面第 5 节会给出完整的实现思路。3. Web-Scale 场景的三个核心约束3.1 规模从亿级候选到 Top KWeb-Scale 搜索的候选集合通常是亿级甚至千亿级。你不可能对每一个候选都调用一次生成式 VLM因为哪怕单条只花 50 毫秒一亿条也要算很久。所以整个系统必须先有“漏斗式”的级联结构先用廉价算法把相关候选选出来再用昂贵模型做精细判断。在这个漏斗里双塔 VLM 负责把“图文相关但文本不相关”的候选找回来缓解文本召回的信息丢失问题。它的计算复杂度低可以覆盖全量候选。3.2 速度在线与离线的不同诉求在线搜索对延迟极其敏感。用户发起一次查询系统往往只有几十毫秒到几百毫秒的预算。因此所有大模型推理都必须放到离线或准实时链路中在线只做“查表”和“融合分数”。双塔 VLM 的在线部分只有文本编码和向量检索延迟很低。生成式 VLM 通常不进在线链路而是在离线阶段对指定候选集批量打分或者通过缓存机制复用结果。3.3 标注与评估相关性本身也需要标签相关性度量模型的好与坏最终还是需要人工标注来评估。这里有一个容易被忽略的问题多模态相关性的标注成本远高于纯文本相关性。标注员需要同时看 Query、标题、图片还要判断“图片能否满足文本查询意图”。标注标准需要提前定义清楚否则人工标注的一致性会很低模型训练和评估都会受影响。4. 环境准备与模型选型4.1 运行环境本文示例以 Python 环境为主建议使用 Python 3.9 或更高版本。模型推理需要 GPU显存需求取决于模型大小纯双塔 CLIP 类模型如clip-vit-base-patch328GB 显存即可运行生成式 VLM 进行推理至少需要 16GB 显存具体取决于模型参数量。版本需要根据你的项目实际情况调整。本文示例以常见环境为例重点演示配置思路不同版本的 API 可能略有差异。4.2 依赖库核心依赖如下transformers加载和处理模型torch深度学习框架Pillow图像读取faiss-cpu或faiss-gpu向量检索numpy、pandas数据处理。安装命令如下pip install transformers torch Pillow faiss-cpu numpy pandas如果你需要做 LoRA 微调还需要安装pip install peft4.3 模型选型建议模型选型主要看你的业务阶段如果目标是快速验证“VLM 是否能提升相关性”直接用公开的 CLIP 类模型做双塔打分即可如果目标是提升精排效果可以选用开放权重的大型多模态模型具体名称以你实际环境和资源为准如果目标是上线到高并发线上系统更推荐自建双塔模型或者对生成式 VLM 做蒸馏而不是直接在线调用大模型。4.4 示例项目结构建议按下面结构组织代码vlm_relevance/ ├── data/ │ ├── samples.jsonl │ └── images/ ├── models/ │ └── clip_model.py ├── pipeline/ │ ├── dual_tower.py │ ├── generative_vlm.py │ ├── cascade.py │ └── vector_index.py ├── eval/ │ └── metrics.py └── main.py这样的结构比较清晰数据、模型、流程、评估分开放方便后续替换模型或调整链路。5. 从零实现一个 VLM 相关性打分 Pipeline5.1 数据准备构建 query-doc 样本我们以一个简化的电商搜索场景为例。每条候选文档包含查询词、文档 ID、标题、图片路径和人工标注等级。示例数据格式如下{ query: 男士黑色运动鞋, doc_id: item_1024, title: 新款男士轻便跑鞋 黑色 透气, image_path: data/images/item_1024.jpg, relevance_label: 4 }加载数据import pandas as pd import json def load_samples(path: str): samples [] with open(path, r, encodingutf-8) as f: for line in f: samples.append(json.loads(line)) return samples samples load_samples(data/samples.jsonl)这里有两点需要注意。第一图片路径必须真实可读建议在数据预处理阶段统一校验第二相关性标签的标注口径要提前统一不同标注员之间的差异会影响模型效果评估。5.2 双塔 VLM 打分我们先用一个双塔模型计算 Query 与图片之间的相似度。这里以 CLIP 类模型为例# 文件路径pipeline/dual_tower.py from PIL import Image import torch from transformers import CLIPProcessor, CLIPModel class DualTowerScorer: def __init__(self, model_name: str): self.model CLIPModel.from_pretrained(model_name) self.processor CLIPProcessor.from_pretrained(model_name) self.model.eval() def encode_text(self, query: str): inputs self.processor( text[query], return_tensorspt, paddingTrue, truncationTrue, ) with torch.no_grad(): feature self.model.get_text_features(**inputs) return torch.nn.functional.normalize(feature, dim-1) def encode_image(self, image_path: str): image Image.open(image_path).convert(RGB) inputs self.processor(imagesimage, return_tensorspt) with torch.no_grad(): feature self.model.get_image_features(**inputs) return torch.nn.functional.normalize(feature, dim-1) def score(self, query: str, image_path: str) - float: q_vec self.encode_text(query) i_vec self.encode_image(image_path) return float((q_vec i_vec.T).item()) scorer DualTowerScorer(openai/clip-vit-base-patch32) score scorer.score( 男士黑色运动鞋, data/images/item_1024.jpg, ) print(双塔相似度:, score)代码逻辑并不复杂核心点有三个文本和图像特征都做了normalize这样点积等于余弦相似度get_text_features和get_image_features是 CLIP 模型专门用来取出特征向量的接口双塔分数是浮点数适合做排序不适合直接作为绝对相关度使用。需要提醒的是不同双塔模型的特征提取接口差异很大。有的模型需要走forward后拼接结果有的模型没有单独的特征接口。上面示例更强调流程思路请以实际模型 API 为准。5.3 生成式 VLM 判别打分在精排阶段我们希望模型能输出更细节的判断。这里用生成式 VLM 的通用调用方式做演示。实现上我们把相关性判断包装成提示词要求模型输出 JSON方便解析。# 文件路径pipeline/generative_vlm.py import json import torch from transformers import AutoProcessor, AutoModelForCausalLM PROMPT_TEMPLATE 判断商品信息与用户查询是否相关。 用户查询{query} 商品标题{title} 商品图片image 请只输出一个 JSON格式如下 {{relevance: 0, reason: 一句话理由}} relevance 取值范围说明 4完全满足3高度相关2部分相关1弱相关0不相关 class GenerativeVLMScorer: def __init__(self, model_name: str): self.model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.bfloat16, device_mapauto, ) self.processor AutoProcessor.from_pretrained(model_name) self.model.eval() def score(self, query: str, title: str, image_path: str): prompt PROMPT_TEMPLATE.format(queryquery, titletitle) image Image.open(image_path).convert(RGB) inputs self.processor( imagesimage, textprompt, return_tensorspt, ).to(self.model.device) with torch.no_grad(): outputs self.model.generate( **inputs, max_new_tokens128, temperature0.0, do_sampleFalse, ) text self.processor.decode( outputs[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue, ) return self._parse_output(text) def _parse_output(self, text: str): # 提取 JSON 子串防止模型输出额外文本 start text.find({) end text.rfind(}) 1 if start -1 or end 0: return {relevance: -1, reason: text} try: obj json.loads(text[start:end]) return obj except json.JSONDecodeError: return {relevance: -1, reason: text}这里的关键在于温度参数temperature0.0意思是让模型输出尽量固定减少随机性。相关性判别的结果应该稳定不能同一个样本两次打分差异很大。解析 JSON 时不能只做一次json.loads因为大模型很容易输出多余的引导语我们直接抽取第一个{到最后一个}之间的字符串再做解析容错率更高。5.4 级联式打分流程完整的流程应该是多阶段级联而不是把全部候选都丢给生成式 VLM。下面示例展示了从候选集合到最终精排结果的完整流程# 文件路径pipeline/cascade.py class CascadeRelevancePipeline: def __init__(self, dual_tower, generative_vlm, top_dual50, top_vlm10): self.dual_tower dual_tower self.generative_vlm generative_vlm self.top_dual top_dual self.top_vlm top_vlm def rank(self, query: str, candidates): # 阶段一双塔粗筛 scored_by_dual [] for item in candidates: s self.dual_tower.score(query, item[image_path]) scored_by_dual.append((item, s)) scored_by_dual.sort(keylambda x: x[1], reverseTrue) coarse_top scored_by_dual[: self.top_dual] # 阶段二生成式 VLM 精排 detailed_results [] for item, _ in coarse_top[: self.top_vlm]: result self.generative_vlm.score( query, item[title], item[image_path] ) detailed_results.append((item, result)) return detailed_results从流程上就能看出设计原则双塔负责快速过滤生成式 VLM 只处理最有可能相关的少量候选。这种结构可以控制成本同时保留多模态相关性判断能力。5.5 运行与验证主程序如下# 文件路径main.py from pipeline.dual_tower import DualTowerScorer from pipeline.generative_vlm import GenerativeVLMScorer from pipeline.cascade import CascadeRelevancePipeline if __name__ __main__: samples load_samples(data/samples.jsonl) dual DualTowerScorer(openai/clip-vit-base-patch32) gen GenerativeVLMScorer(your-vlm-model-name) pipeline CascadeRelevancePipeline(dual, gen) query 男士黑色运动鞋 result pipeline.rank(query, samples[:100]) for item, vlm_result in result[:5]: print(item[doc_id], vlm_result)如果你第一次跑通会发现几个问题图片加载可能很慢、生成式 VLM 单条耗时很长、部分样本的 JSON 解析失败。这些都是正常现象后续第 6 节会给出对应的工程优化手段。6. Web-Scale 部署与性能优化6.1 向量索引与 ANN 检索双塔输出的是向量在亿级候选上不可能用暴力计算余弦相似度。工程上普遍使用 FAISS 这类近似最近邻ANN索引。示例如下# 文件路径pipeline/vector_index.py import faiss import numpy as np def build_index(image_embeddings: np.ndarray, nlist: int 128): dim image_embeddings.shape[1] quantizer faiss.IndexFlatIP(dim) index faiss.IndexIVFFlat( quantizer, dim, nlist, faiss.METRIC_INNER_PRODUCT, ) index.train(image_embeddings) index.add(image_embeddings) return index def search(index, query_vector: np.ndarray, top_k: int 50): query_vector query_vector.astype(float32) scores, ids index.search(query_vector, top_k) return scores, ids需要理解两个参数nlist表示聚类中心数量影响召回率和建索引速度METRIC_INNER_PRODUCT适合余弦相似度前提是向量已经归一化。实际业务中你还需要根据数据更新频率决定是否每天重建索引。索引重建不是本文的重点但它是 Web-Scale 系统绕不开的运维工作。6.2 批处理与异步双塔模型的图像编码和生成式 VLM 推理都支持批处理。批量推理能显著提升 GPU 利用率降低单条平均耗时。双塔打分改成批量def encode_images_batch(self, image_paths): images [Image.open(p).convert(RGB) for p in image_paths] inputs self.processor(imagesimages, return_tensorspt) with torch.no_grad(): features self.model.get_image_features(**inputs) return torch.nn.functional.normalize(features, dim-1)生成式 VLM 也可以批量输入多张图和多个提示词。实际操作时需要注意max_new_tokens和批量大小的平衡避免单次推理显存溢出。6.3 缓存与蒸馏多模态相关性打分有一个特点同一条内容在短时间内变化不大。因此可以引入缓存机制。对已经打过分的内容按 Doc ID Query 的规范化哈希作为缓存 Key。如果内容没有变化就直接复用历史分数不需要重新推理。另一个高效方案是模型蒸馏用生成式 VLM 对一批样本打分作为弱标签训练一个轻量级双塔模型。这样线上只跑小模型也能逼近大模型的精度。蒸馏本质上是一种“离线重排”的工程化落地方式。6.4 离线全量 在线增量Web-Scale 系统在部署时建议把相关性信号拆成两条链路离线链路每日对新增内容预计算图像向量、生成式 VLM 初评结果写入索引和相关库在线链路用户 Query 到达后只做文本编码、向量检索和分数融合不调用大模型。这种“离线全量 在线增量”的架构可以很好地平衡效果和成本。大模型在在线请求路径上出现一次超时就可能影响整条搜索链路的可用性因此要尽量避免让大模型在线同步推理。7. 如何评估相关性度量模型本身7.1 核心评估指标评估相关性模型最常用的指标是 NDCGNormalized Discounted Cumulative Gain归一化折损累积增益和 RecallK。NDCG 适合评估排序质量。它有两个特点相关等级越高的文档排得越靠前得分越高等级本身也参与计算相关等级 4 的文档比相关等级 2 的文档贡献更大。简化实现如下# 文件路径eval/metrics.py import math def dcg(relevance_scores): score 0.0 for idx, rel in enumerate(relevance_scores): score (2 ** rel - 1) / math.log2(idx 2) return score def ndcg(pred_relevance, ideal_relevance): actual dcg(pred_relevance) ideal dcg(sorted(ideal_relevance, reverseTrue)) if ideal 0: return 0.0 return actual / ideal用法对一次查询的多个候选按模型预测分数从高到低排序取排序后的人工标注等级作为pred_relevanceideal_relevance是所有人共标签排序后的最优排列。7.2 构造评测集评测集不应该只用容易样本。建议从四个渠道收集线上搜索日志中的真实 Query 和曝光结果人工标注的随机采样样本长尾 Query 样本多媒体特征明显的样本例如图片信息占比高的商品页。尤其是最后一条能直接反映 VLM 相比纯文本模型的价值。如果一个评测集里面全部样本光看标题就能判断相关性那么 VLM 的优势完全无法体现。7.3 与人工标注的一致性衡量模型好不好还可以看模型分数与人工标注的一致性常用的指标是 QWKQuadratic Weighted Kappa二次加权 Kappa。它考虑了相邻等级之间误判的严重程度把“完全相关”判成“部分相关”比把“完全相关”判成“不相关”更接近正确。QWK 的计算逻辑比较复杂不在本文展开。工程上你至少需要统计模型的准确率和误差矩阵重点关注模型是否倾向于把所有候选都打成中等分数。相关性模型如果区分度不够排序效果会很差。8. 高频问题与排查思路问题现象常见原因解决思路模型加载时显存溢出模型参数量超出 GPU 显存使用torch_dtype半精度推理、device_mapauto自动分配、或更换更小模型图像加载很慢大量图片文件并发读取增加多进程图像解码、提前做图像预处理和缓存生成式 VLM 输出乱码或额外文本提示词不够明确模型自由发挥收紧提示词模板要求只输出 JSON解析时取首个{到最后一个}增加重试双塔相似度分布很集中模型训练数据与业务场景差异大对相似度做分位数归一化或使用领域数据微调双塔模型VLM 打分不稳定生成设置了随机采样将temperature设为 0关闭do_sample长尾 Query 效果差模型没见过相关概念用长尾 Query 构造微调数据集做 LoRA 领域适配在线延迟过高把大模型放进了在线链路改为离线预计算在线只读缓存和向量索引遇到问题时不要一上来就换更大模型。先把数据分批检查图片是否能打开、提示词是否稳定输出 JSON、双塔相似度分布是否正常。多数问题在数据层面就能找到原因。9. 最佳实践与工程建议9.1 采用级联架构不要把 VLM 当万能打分器Web-Scale 场景的黄金法则是廉价模型做全量筛选昂贵模型做局部精排。不要试图用生成式 VLM 对全部候选打分那样既不经济也无法满足在线延迟要求。双塔 VLM 和生成式 VLM 是互补关系不是替代关系。9.2 用 LoRA 做领域适配而不是全量微调直接使用公开模型在垂直领域可能效果不稳定。建议收集一批领域内样本构建“Query 图片 标注等级”的训练集使用 LoRA 方式对模型做轻量微调。LoRA 只训练少量参数训练成本低且可以保留原模型的通用能力。微调后依然要对评测集做回归测试防止只提升训练集效果而伤害线上搜索体验。9.3 提示词模板要工程化生成式 VLM 的提示词是核心配置必须像代码一样做版本管理。建议把提示词、温度、max_new_tokens等参数统一放在配置文件中方便线上调整同时记录每次提示词改动后的评测结果。不要直接在主程序里硬编码长文本提示词。9.4 建立多模态相关性评测回归机制引入 VLM 之后原来纯文本模型的评测集不应该被替换掉而是新增一个多模态评测集。每次模型更新、提示词调整、数据变化都要同时跑两个评测集确保没有引入回归问题。否则可能出现在新场景下效果好老场景反而变差的情况。9.5 数据合规与内容安全多模态数据合规问题比纯文本更复杂。图片可能包含人脸、品牌 Logo、文字信息使用时需要确认数据来源和授权范围。涉及用户隐私的内容要做脱敏处理涉及商品版权图的场景也要谨慎。训练数据的采集和标注必须在授权范围内进行。9.6 监控分数分布与漂移相关性模型上线后需要监控打分分布是否漂移。如果双塔相似度整体上升或者生成式 VLM 的相关性等级集中在某一个档位说明线上数据和训练数据之间出现了偏差。建议每天统计分数分布、精排候选的覆盖率和人工标注回流数据的准确率。10. 总结与下一步这篇教程从相关性度量的业务定义出发梳理了传统文本模型的三个瓶颈介绍了双塔 VLM 和生成式 VLM 在搜索相关性任务中的分工并给出了一套完整的级联打分 Pipeline。你可以直接复制示例代码在本地样本上验证 VLM 是否真的能提升你的搜索相关性效果。如果你准备在业务里落地建议先做三件事第一用 100 条人工标注样本分别用纯文本模型、双塔 VLM、生成式 VLM 打分对比 NDCG先确认 VLM 是否带来增量 第二先在小流量或离线评测环境中跑通级联链路不要直接替换线上排序 第三把 VLM 定位成“精排增强器”与现有文本相关性信号共存互补而不是完全取代。后续可以继续研究的方向包括用蒸馏把生成式 VLM 的能力压缩到轻量双塔模型、把 Query 里包含图片的多模态搜索场景纳入相关性判断、以及面向不同语言和不同业务域的模型适配。多模态相关性度量的工程化还有大量细节值得打磨从一次小规模验证开始是比较稳妥的路径。