先记录一下:搜索系统在很长一段时间里都默认“相关性 = 文本相似度”,但随着商品、短视频、图文页这类多模态内容占据主要流量,纯文本相关性判断越来越力不从心。一个用户搜索“黑色运动鞋 男士”,返回的页面上图片是一双棕色皮鞋,哪怕标题写满“运动鞋”,体验也是失败的。这篇教程围绕“如何用视觉语言模型(Vision-Language Model,VLM)做 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 Model,VLM)能同时接受图像和文本输入,并输出跨模态理解结果。它有两个基础能力:
- 把图像和文本投影到同一个语义空间,计算图文相似度;
- 以“看图回答”的方式,描述图像内容或回答关于图像的问题。
把这两种能力用到相关性度量上,正好可以弥补文本模型的缺陷。对应到工程链路上,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 K
Web-Scale 搜索的候选集合通常是亿级甚至千亿级。你不可能对每一个候选都调用一次生成式 VLM,因为哪怕单条只花 50 毫秒,一亿条也要算很久。所以整个系统必须先有“漏斗式”的级联结构:先用廉价算法把相关候选选出来,再用昂贵模型做精细判断。
在这个漏斗里,双塔 VLM 负责把“图文相关但文本不相关”的候选找回来,缓解文本召回的信息丢失问题。它的计算复杂度低,可以覆盖全量候选。
3.2 速度:在线与离线的不同诉求
在线搜索对延迟极其敏感。用户发起一次查询,系统往往只有几十毫秒到几百毫秒的预算。因此,所有大模型推理都必须放到离线或准实时链路中,在线只做“查表”和“融合分数”。
双塔 VLM 的在线部分只有文本编码和向量检索,延迟很低。生成式 VLM 通常不进在线链路,而是在离线阶段对指定候选集批量打分,或者通过缓存机制复用结果。
3.3 标注与评估:相关性本身也需要标签
相关性度量模型的好与坏,最终还是需要人工标注来评估。这里有一个容易被忽略的问题:多模态相关性的标注成本远高于纯文本相关性。标注员需要同时看 Query、标题、图片,还要判断“图片能否满足文本查询意图”。标注标准需要提前定义清楚,否则人工标注的一致性会很低,模型训练和评估都会受影响。
4. 环境准备与模型选型
4.1 运行环境
本文示例以 Python 环境为主,建议使用 Python 3.9 或更高版本。模型推理需要 GPU,显存需求取决于模型大小:
- 纯双塔 CLIP 类模型,如
clip-vit-base-patch32,8GB 显存即可运行; - 生成式 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 相关性打分 Pipeline
5.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", encoding="utf-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_tensors="pt", padding=True, truncation=True, ) 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(images=image, return_tensors="pt") 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_dtype=torch.bfloat16, device_map="auto", ) self.processor = AutoProcessor.from_pretrained(model_name) self.model.eval() def score(self, query: str, title: str, image_path: str): prompt = PROMPT_TEMPLATE.format(query=query, title=title) image = Image.open(image_path).convert("RGB") inputs = self.processor( images=image, text=prompt, return_tensors="pt", ).to(self.model.device) with torch.no_grad(): outputs = self.model.generate( **inputs, max_new_tokens=128, temperature=0.0, do_sample=False, ) text = self.processor.decode( outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True, ) 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}这里的关键在于温度参数temperature=0.0,意思是让模型输出尽量固定,减少随机性。相关性判别的结果应该稳定,不能同一个样本两次打分差异很大。解析 JSON 时不能只做一次json.loads,因为大模型很容易输出多余的引导语,我们直接抽取第一个{到最后一个}之间的字符串再做解析,容错率更高。
5.4 级联式打分流程
完整的流程应该是多阶段级联,而不是把全部候选都丢给生成式 VLM。下面示例展示了从候选集合到最终精排结果的完整流程:
# 文件路径:pipeline/cascade.py class CascadeRelevancePipeline: def __init__(self, dual_tower, generative_vlm, top_dual=50, top_vlm=10): 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(key=lambda x: x[1], reverse=True) 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(images=images, return_tensors="pt") 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 核心评估指标
评估相关性模型,最常用的指标是 NDCG(Normalized Discounted Cumulative Gain,归一化折损累积增益)和 Recall@K。
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, reverse=True)) if ideal == 0: return 0.0 return actual / ideal用法:对一次查询的多个候选,按模型预测分数从高到低排序,取排序后的人工标注等级作为pred_relevance;ideal_relevance是所有人共标签排序后的最优排列。
7.2 构造评测集
评测集不应该只用容易样本。建议从四个渠道收集:
- 线上搜索日志中的真实 Query 和曝光结果;
- 人工标注的随机采样样本;
- 长尾 Query 样本;
- 多媒体特征明显的样本,例如图片信息占比高的商品页。
尤其是最后一条,能直接反映 VLM 相比纯文本模型的价值。如果一个评测集里面全部样本光看标题就能判断相关性,那么 VLM 的优势完全无法体现。
7.3 与人工标注的一致性
衡量模型好不好,还可以看模型分数与人工标注的一致性,常用的指标是 QWK(Quadratic Weighted Kappa,二次加权 Kappa)。它考虑了相邻等级之间误判的严重程度:把“完全相关”判成“部分相关”,比把“完全相关”判成“不相关”更接近正确。
QWK 的计算逻辑比较复杂,不在本文展开。工程上,你至少需要统计模型的准确率和误差矩阵,重点关注模型是否倾向于把所有候选都打成中等分数。相关性模型如果区分度不够,排序效果会很差。
8. 高频问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型加载时显存溢出 | 模型参数量超出 GPU 显存 | 使用torch_dtype半精度推理、device_map="auto"自动分配、或更换更小模型 |
| 图像加载很慢 | 大量图片文件并发读取 | 增加多进程图像解码、提前做图像预处理和缓存 |
| 生成式 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 里包含图片的多模态搜索场景纳入相关性判断、以及面向不同语言和不同业务域的模型适配。多模态相关性度量的工程化还有大量细节值得打磨,从一次小规模验证开始,是比较稳妥的路径。