尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

多模态检索的统一之道:稀疏嵌入与稠密嵌入如何协同

多模态检索的统一之道:稀疏嵌入与稠密嵌入如何协同 多模态检索系统里新手和老手最大的分歧往往不在模型选型而在一个非常基础的问题你到底应该用稀疏嵌入还是稠密嵌入如果只做文本检索这问题已经被讨论过无数遍可一旦输入变成了图像、视频、音频、PDF 和表格的混合体很多人就会默认“多模态嵌入模型既然能生成统一向量那我只要把稠密向量做好就行”。我在实际项目里见过太多反例用纯稠密向量做多模态检索召回来的结果“感觉相关”但用户最想找的品牌型号、文档编号、合同条款却排在后面。UEmbedUnified Sparse and Dense Multimodal Embeddings这个方向之所以值得关注不是因为它多了一个输出头而是它把“精确匹配”和“语义匹配”重新放进了同一个 embedding 系统里让多模态检索从一个“只能用向量猜意图”的黑盒变成一个“既能理解语义又能指出证据”的协同系统。1. 先搞清楚多模态检索里“稀疏”和“稠密”到底在争什么很多人第一次听到“稀疏嵌入”和“稠密嵌入”时容易把它们理解成同一种东西的两种存储方式。实际上这两种表示背后是两套完全不同的检索假设稀疏嵌入强调的是“特定概念或词项是否出现”稠密嵌入强调的是“语义是否相近”。这两者在文本检索时代就已经是长期争论的焦点到了多模态场景又被模型架构和训练方式放大了。1.1 稠密嵌入把一切都变成向量但丢掉了很多精度稠密嵌入的原理是用一个固定长度的实数向量表示一段文本、一张图片、一段音频。常见的做法是训练一个编码器让相似的输入在向量空间里靠近不相似的输入互相远离。多模态模型之所以受欢迎核心能力就在于能把图像和文本映射到同一个向量空间比如给定一张“城市夜景”的图片它的图像向量会和一段描述“霓虹灯下的街道”的文本向量距离很近。这种设计擅长处理模糊查询和同义改写。用户说“有没有那种晚上城市灯光的图”系统能通过向量距离找到接近“城市夜景”的图像哪怕用户没有使用任何图片标签里的词。但稠密嵌入也有一个很实际的问题为了把语义压缩进一个向量模型必须做信息取舍。模型会把“iPhone 15 Pro Max 256GB 深蓝色”和“最新款苹果手机”在语义上拉近可如果用户的目标就是要找存档里那台设备的具体型号字符串稠密向量往往不能给出精确匹配。品牌名、编号、合同条款、产品批次、人名、病毒名称这些“低语义但高精确度”的信息恰恰是纯稠密向量最容易丢失的部分。这还不是最麻烦的。稠密向量一旦生成就很难被业务规则干预。如果运营想加一条规则——“型号字段必须精确命中”在稠密向量空间里几乎没法操作因为向量是模型学出来的黑盒表示你无法直接告诉它“这个维度代表型号”。1.2 稀疏嵌入传统词汇匹配的延续却很少被用在多模态上稀疏嵌入的历史比深度学习向量要长得多。经典的 BM25、词袋模型本质上都是稀疏表示向量的维度对应词典里的词项每个文档只激活极少数维度其他维度都是零。现代一点的稀疏嵌入类似 SPLADE会学习每个词项的权重但仍然保持高维稀疏的结构。在多模态场景里稀疏嵌入的处境有些尴尬。文本可以轻松地用 tokenizer 生成词项权重但图像怎么得到稀疏表示音频怎么得到稀疏表示一个更通用的思路是稀疏嵌入的维度不一定必须对应文本 token它可以对应“视觉概念”“OCR 识别出的文本”“音频事件标签”“检测出的物体类别”等。比如一张图片稀疏向量可能激活“夕阳”“海面”“沙滩”“人物剪影”这些概念一段音频可能激活“说话人”“翻页声”“环境噪音”等。这种做法的价值在于它让“精确证据”有了显式的位置。当用户查询里出现一个准确的地名或人名系统可以直接在稀疏维度上找到对应权重而不是在稠密空间里靠距离猜。同时稀疏表示天然有更好的可解释性你能看到一条结果是因为哪个概念被命中而来。但稀疏嵌入的短板也很明显它很难处理同义改写和跨模态语义。用户说“晚上城市的灯光”图片稀疏表示里可能只有“夜景”“建筑”“灯光”但这三个概念不一定同时激活稀疏匹配就可能漏掉相关结果。1.3 UEmbed 的核心假设两者本来就不该互相替代UEmbed 这个方向最核心的假设不是“稀疏比稠密好”或“稠密比稀疏强”而是“单一表示模式根本无法覆盖所有多模态检索意图”。检索意图从来不是同质的。有些查询是语义模糊的比如“找一个适合做PPT封面的科技感背景图”这种时候靠稠密向量有些查询是精确的比如“文件名里带 Q3_Report 的 PDF”这种时候靠稀疏匹配还有更多查询是两种混合比如“2023年第三季度科技行业报告”既包含语义概念“科技行业报告”也包含精确字段“2023年第三季度”。如果模型只输出一种表示就等于逼着用户做选择。UEmbed 的统一之处不是简单地把两个向量拼接起来而是在同一个多模态输入上同时学习两个输出头让它们共享底层语义编码能力但各自的输出形式保留各自的优势。这个设计假设看起来温和真正落地时改变了很多系统层面的东西检索服务不再只能调用一个向量索引而是可以同时用倒排索引和稠密向量索引排序阶段也不再用单一距离计算而是做稀疏分数和稠密分数的融合。这一点才是 UEmbed 名字里 “Unified” 的真正含义。2. 为什么多模态模型生成的嵌入会同时遇到稀疏和稠密的问题概念上说清楚稀疏和稠密的区别之后还需要回答一个问题为什么多模态场景特别需要“统一”而不是像文本检索那样用混合检索在工程层面拼一拼就行原因在于多模态输入的语义粒度差异太大了普通后融合方案很难从根上解决对齐问题。2.1 图像/音频/文本的语义粒度差异文本自带离散的语言单元词、短语、句子之间有清晰的层级。图像不是这样图像是连续像素模型需要自己决定“视野”里哪些区域构成一个对象、一个场景、一个属性。音频更特殊它既包括人说话的语义内容也包括语气、环境音、背景音乐这些非语义信息。把这些不同粒度的输入统一编码成同一个稠密向量本质上是在做非常激进的信息压缩。一个视频片段里可能同时有人物、场景、字幕、对话、背景音乐稠密向量能表达“这一段大概是在下雨的城市街头”但很难精确表达“字幕里出现了合同续约日期”或“对话中提到项目编号 PRJ-2025-001”。更麻烦的是不同模态的信息精确程度不一样。文本中的专有名词是天然的高精确度信号图像中的 OCR 文字也接近这种特性但音频里的专有名词如果没有经过 ASR 转写可能只是模糊的声音片段。如果模型只输出一个稠密向量它只能把这些信息“融”在一起最终结果就是这个向量既不擅长语义理解也不擅长精确匹配两头都不占。所以多模态系统天然需要两种不同性质的表示一种负责把语义浓缩另一种负责把精确证据保留下来。UEmbed 的核心不是发明了新概念而是把这个天然需求变成了模型输出的一部分。2.2 常见多模态嵌入模型只输出稠密向量的三个后果现在的多模态嵌入模型很多都只输出一个稠密向量。这在很多比赛、榜单上看着够用真正投到业务里会产生三个容易被低估的后果。第一个后果是无法做精确字段检索。图像里的 OCR 文字、文档里的页码、音频里的口令词一旦被压缩进稠密向量就失去了独立检索的可能。你想做“只匹配文档编号”的约束根本无从下手。第二个后果是可解释性差。系统返回“这张图片与查询相关”但它为什么相关哪个区域、哪个关键词促使模型给出这个结果稠密向量给不出答案。对需要回复用户“命中依据”的客服系统、知识库系统、合规审查系统这几乎是致命的。第三个后果是在线干预困难。业务里经常会有临时规则比如“某品牌名必须精确匹配”“某些敏感词不能出现在召回中”。在稠密向量上做这种干预要么重新训练模型要么在应用层做硬过滤两种方式都没法平滑地融合进排序过程。当只有稠密向量时这三个问题不是 bug而是架构天然缺失的能力。UEmbed 这类方案试图用额外输出头补上这一块。2.3 统一输出的关键不是“再加一个向量”而是共享语义空间有人会想那我让模型同时输出一个稠密向量和一个稀疏向量不就行了技术上确实可以但如果两个输出头只是各算各的最后结果大概率是“稀疏部分像一个文本词表分类器稠密部分像一个纯视觉编码器”彼此毫无协同。真正的关键在于两个输出头必须共享一个语义空间。什么意思就是模型对“文本里的猫”和“图像里的猫”要有同一个内部理解然后在这个基础上稀疏头决定把“猫”这个概念的权重升高稠密头决定把整个语义压缩进一个向量。共享空间会让两个头在训练时互相约束稠密头可以告诉稀疏头“这个词项对这个文档很重要”稀疏头也可以告诉稠密头“这个维度的证据缺失会导致精确匹配失败”。这里可以借用最近讨论得比较多的“多模态引导”概念。在一个统一的输出空间里给定一个查询模型实际上是在做两件事第一识别出哪些输入元素是“语义线索”比如概念、主题、场景第二识别出哪些输入元素是“精确线索”比如专有名词、编号、OCR 文本。这两种线索合在一起相当于一个向导既知道大方向又能报出具体的门牌号。UEmbed 的稀疏和稠密输出本质上就是这两种引导信号的具体化。3. 理解 UEmbed 的架构一个统一的输出空间而不是两个独立管道前面讲了很多理念这一节落地到架构层面。虽然 UEmbed 没有一个统一的官方开源实现可以照抄但结合现有技术和这类方案的常见设计我们可以拆出一个比较清晰的参考结构。3.1 输入编码器与共享投影层一个多模态嵌入模型输入可能是图像、文本、音频、视频帧或者它们的任意组合。第一步通常是进入各自模态的编码器比如视觉 transformer、文本编码器、音频编码器。这些编码器会输出各自模态的 token 序列或特征序列。接下来的关键设计是所有模态的编码结果都要进入一个共享投影层把不同模态的特征映射到同一个语义空间中。这个投影层的维度就是一个超参数设得太低语义容量不够细节容易丢设得太高后面的稀疏头会很难训练因为概念空间太大负样本太多。我见过一些项目在第一步就踩坑为了省显存直接把不同模态编码器的输出 concat 一下就送进任务头中间没有一个真实的共享投影层。这样做的后果是文本编码器和图像编码器可能各自已经把语义空间学习好了但两者之间不对齐后续无论是稀疏头还是稠密头都得从头学一个“翻译”效果往往很差。共享投影层虽然听起来只是一个线性层但它是统一语义空间的物理基础。3.2 稀疏输出头与稠密输出头如何协同共享投影层之后模型分出两个分支。稠密输出头相对好理解它把共享向量进一步压缩成一个固定维度向量比如 1024 维后续用余弦相似度或内积做语义检索。稀疏输出头则复杂一些。它的输出维度通常等于“概念词表大小”这个词表可能是文本词表、视觉概念标签、OCR 字符集、音频事件标签的并集。模型对每个概念输出一个权重然后通过类似 top-k 激活的方式只保留权重最大的若干维度其余都置为零。这样既保证了稀疏性也让每次检索只需要处理少数活跃维度计算成本和存储成本都可控。协同在哪里体现训练时稀疏头不能只靠文本监督还要结合多模态对齐信号。比如一张图片配了一段描述文字那图片的稀疏输出中“沙滩”“天空”“海浪”这些概念的权重就应该升高如果描述文字里有“2024 年夏季旅行”那 OCR 或文本匹配出来的“2024”也值得提升权重。稠密头则负责优化整段语义的紧凑表示。两者共享同样的底层特征但各自有自己的预测目标。推理时一个输入同时拿到两个输出稀疏向量用来做倒排召回或精确匹配稠密向量用来做语义近似召回。两者不是互相替代而是互补。3.3 一个最小推理示例下面给出一个简化到不能再简化的参考结构便于理解这种设计的数据流。它不是一个真实 API只是帮助你建立直觉的占位实现。dataclass class UEmbedOutput: sparse: Dict[int, float] # concept_id - weight dense: np.ndarray # 1024-dim dense vector class UEmbedEncoder(nn.Module): def __init__(self, backbone, shared_dim, dense_dim, vocab_size): super().__init__() self.backbone backbone # 多模态编码器输出 hidden states self.projection nn.Linear(hidden_size, shared_dim) self.dense_head nn.Linear(shared_dim, dense_dim) self.sparse_head nn.Linear(shared_dim, vocab_size) def forward(self, inputs): hidden self.backbone(inputs) shared self.projection(hidden) dense self.dense_head(shared) sparse_logits self.sparse_head(shared) sparse top_k_activation(sparse_logits, k32) return UEmbedOutput(sparsesparse, densedense)在检索服务里可以这样理解它的使用方式索引端对每个文档doc调用编码器得到UEmbedOutput将dense写入向量索引将sparse写入倒排索引。查询端对查询query做同样处理。召回阶段用稀疏索引做精准匹配召回得到sparse_hits用稠密向量索引做 ANN 召回得到dense_hits。融合排序合并两个命中集合按融合分数重排。一个最简单的融合公式参考score alpha * norm(sparse_score) beta * norm(dense_score)这里最关键的一点是sparse_score和dense_score必须分别做归一化。稀疏分数的分布和稠密距离的分布完全不同直接相加会让某一个信号主导另一个失效。alpha和beta不要拍脑袋定应该在一个小验证集上做网格搜索找到最适合当前数据分布的比例。4. 落地时要注意的五个坑从理解结构到真正上线中间隔着很多工程细节。这里挑五个最常见的坑展开每一个都值得在项目启动前想清楚。4.1 数据标注没有对齐监督信号稀疏嵌入就是空壳稀疏头并不是天生就能学会“哪些概念重要”。如果只是用图文对做对比学习模型很可能倾向于把高频概念都激活一遍最后稀疏向量变成稠密向量的粗糙版本完全没有“精确证据”的能力。要让稀疏头真正学会输出精确概念需要细粒度的监督信号。比如图片对应的描述文本中哪些词是关键词图像里是否出现 OCR 文字内容是什么音频里是否提到特定的实体、编号或短语文档中哪些字段具有唯一标识性质。如果项目里没有这些标注至少要做一个弱监督版本用现成的 OCR、ASR、实体抽取工具先给训练数据打一层“伪精确标签”。这比完全不标注要好得多。但也要意识到弱监督标签会带入噪声需要后续人工抽检。4.2 向量维度和词表设计稀疏嵌入不是直接把文本 token 搬过来多模态稀疏嵌入的词表设计是一个容易被低估的问题。如果只把文本词表拿过来用图像中的“视觉概念”和音频中的“事件标签”就没有对应的索引。模型训练得再好也无法在这个词表上表达图像内容。一种常见做法是构造“概念词表”把文本高频词、视觉概念标签、OCR 字符、音频事件标签合并并做频率统计。然后根据场景需要保留高频高价值的概念同时留一些“罕见词”的回退策略比如把不常见 token 映射到一个统一的[UNK]避免词表无限膨胀。词表太小的后果是专有名词无法被表达精确匹配能力直接归零。词表太大的后果是训练难度增加尤其对稀疏头来说负样本空间太大很容易把权重学到高频词上。建议先做一轮词频统计和业务关键词清单再决定词表规模。4.3 索引与存储稀疏和稠密要放在同一个检索服务里很多团队在技术验证时会分别用 Elasticsearch 存稀疏倒排索引用 Faiss 存稠密向量索引然后业务层各查一次合并结果。短期实验可以这样做但长期运行很痛苦。问题在于两个索引的文档 ID 必须保持一致数据的更新流程也要同步。新增一个文档如果稠密索引写入成功稀疏索引写入失败就会出现召回不一致。更麻烦的是如果某一个索引出了故障线上会直接降级成单路召回而业务层还不一定能感知到。更稳妥的做法是使用支持混合检索的索引服务或者自己维护一个统一的文档主数据流确保同一个doc_id对应的稠密向量和稀疏向量总是一起更新。如果确实用两个独立系统那就要在文档写入流程里加入事务性补偿机制以及定期一致性校验。4.4 融合策略分数归一化不是简单相加混合检索最容易被忽视的环节是分数融合。稀疏分数和稠密分数的数值分布通常差异巨大。稀疏分数可能来自倒排索引的 BM25 变体范围可能是 0 到几十稠密分数可能是余弦相似度范围在 -1 到 1 之间。直接把两个分数相加等于让稀疏分数主导排序。正确的做法是先分别对两路分数做归一化。可以用 min-max 归一化也可以用 softmax 或 rank 归一化把分数变成同一量纲。然后在验证集上学习alpha和beta。不要小看这一步很多项目在模型上花了很多力气最后排序效果不好其实就是因为融合权重不对。另外如果某个业务场景特别强调精确匹配可以考虑“稀疏硬约束 稠密精排”的组合方式。先要求某些关键字段必须命中再用稠密分数对已经命中的结果排序。这种策略比单纯的加权融合更可控。4.5 评估指标不能只盯 RecallK用离线指标评估统一嵌入效果时会遇到一个有趣的现象加了稀疏输出之后RecallK 可能提升不大甚至在某些标准数据集上略降。这时候不要急着否定方向而要追问我们真正关心的是什么正确评估维度应该包括精确匹配率查询中包含的专有名词是否被正确召回首条结果可解释性用户是否容易判断“为什么推荐这条结果”人工评估中的“明显不相关比例”业务侧干预能力的度量比如能否通过调整某些概念权重快速改变召回结果。这些维度在纯稠密模型上往往很难提升但 UEmbed 这类统一方案可以。所以评估指标必须和业务价值绑定不能只依赖单一指标。5. 从单模态到多模态统一嵌入一个可复用的调试与排查链路当你自己实现或调试一个类似 UEmbed 的系统时很容易陷入“效果不好先调 alpha/beta”的误区。实际上混合检索效果差大概率不是最后的融合参数出了错而是前面某一层已经坏了。下面这条排查链路可以帮你按顺序定位问题。5.1 先确认单模态嵌入是否正常不要一上来就测多模态混合检索。先分别测文本查询 vs 文本文档看稀疏头是否命中核心关键词稠密头是否能召回语义相近文档图像查询 vs 图像文档看稠密头是否能召回相似图像稀疏头是否激活了“场景”“物体”类概念音频查询 vs 音频文档如果有音频看稀疏头是否能召回包含特定词句的片段。如果单模态内部就不合理比如一个纯文本查询稀疏头命中的关键词和查询毫无关系那问题基本在编码器或训练数据集而不是融合策略。这时候去调 alpha/beta 是浪费时间。5.2 再检查跨模态对齐单模态正常后再测跨模态。准备一批“文本描述-图像”对检查一段描述“沙滩上的日落”能否在稠密空间找到对应的图像同一张图像稀疏头输出的概念标签是否包括“沙滩”“日落”“天空”如果查询文本包含精确字段比如“图中有 NO PARKING 标志”稀疏头是否在图像 OCR 概念上激活。这一阶段能帮你判断两个输出头是否真的共享了语义空间。如果文本可以匹配但图像稀疏头总是输出一些不相关的高频概念说明跨模态对齐训练不足需要补充更多细粒度对齐样本。5.3 记录日志与样本分析建议在检索服务里记录一份详细日志至少包含查询 ID稀疏命中列表及权重稠密 TopK 列表及相似度融合分数构成最终排序结果。遇到 badcase 时首先看“两路信号是否都在场”。比如一个查询包含了文档编号但稀疏命中结果里根本没有这个编号说明稀疏头没学到精确字段如果稀疏命中了编号但稠密 TopK 里全是不相关的文档说明两个头的排序语义没有对齐。通过日志可以快速区分这两种情况避免靠猜。5.4 用一个小步调优的顺序如果确实需要调优建议严格按照下面的顺序先修编码器和训练数据确保单模态表示合理再修跨模态对齐确保两个头共享语义空间再修索引同步确保稠密和稀疏索引数据一致然后调融合权重和归一化方式最后才考虑模型结构改动和资源占用优化。这个顺序的核心原则是不要让上层方案替下层问题背锅。很多人把大量时间花在调融合权重上最后发现根因是跨模态对齐根本没做好。先做小步验证能省很多事。6. UEmbed 这类方案真正改变了什么长期价值与适用边界最后我们把视角拉远一点UEmbed 这类统一稀疏和稠密多模态嵌入的方案真正改变的到底是什么它不只是让检索多了一个可用的分数来源而是改变了整个多模态检索系统的设计假设。6.1 它让多模态检索变成可解释、可干预、可组合纯稠密向量系统最让人头疼的一点是不可解释性和不可干预性。你无法回答“为什么这条结果排在前面”也无法在不重训模型的情况下加入一条业务规则。有了稀疏输出之后这两件事变得可能。稀疏向量的每个维度对应一个可理解的概念系统可以展示“这条结果命中了哪些概念和关键词”。业务侧如果想调整规则比如提高“品牌名”的权重或者要求某个敏感词必须被过滤可以直接在稀疏维度上操作。这意味着多模态检索系统从“靠感觉调向量”变成了“能部分用规则管理”。更重要的是稀疏和稠密的组合让检索系统可以根据场景重新组织纯语义场景可以只跑稠密精确匹配场景可以只跑稀疏混合场景可以两路融合甚至叠加硬约束。同一个模型不再需要为每个场景重新训练。6.2 适合什么场景不适合什么场景任何方案都有适用边界。UEmbed 这类统一嵌入方案在以下场景里价值很明显多模态知识库检索文档里包含大量专有名词、编号、表格、图表文字多模态 RAG需要为生成模型提供可引用的证据片段视频/音频内容检索用户会搜索句子片段、人名、数字需要合规审计和可解释性的行业应用。但在另外一些场景统一嵌入可能不是最优选择。比如纯语义探索式推荐场景用户没有明确的关键词需求稀疏信号反而可能引入噪声再比如对延迟极其敏感、只有几毫秒预算的极简检索架构同时维护两套索引和两次召回会带来额外成本又比如训练数据本身就很稀疏、标注质量差的场景硬做统一训练可能不如先用一个现成的纯稠密模型兜底。6.3 对普通开发者的建议不要一上来就重构如果你现在正在做一个多模态检索项目UEmbed 听起来很诱人但我不建议直接推翻现有系统重头训练一个统一模型。更理性的路径是先做“伪统一”实验。具体来说用现有模型生成稠密向量再用 OCR、ASR、关键词抽取等工具生成文档的稀疏表示然后在一个小样本集合上做混合检索。观察之前纯稠密召回解决不了的 badcase 是否有所改善。如果改善明显说明统一方向值得投入如果改善不明显问题可能不在表示类型而在标注质量或评估指标。即便决定要做统一训练也要控制增量。可以先固定共享编码器只训练稀疏输出头对比旧稠密系统是否有提升然后再逐步放开共享层联合微调。这样每一步都有可验证的收益也方便定位问题。说到底UEmbed 这类方案能带来价值的根本原因不是“稀疏”或“稠密”哪个更正确而是多模态世界的检索需求足够复杂单一表示永远不够。一个真正健壮的系统应该像一个有经验的向导那样既听得懂你很抽象的描述也能准确找到你要的那个具体门牌号。而统一嵌入正好是给系统同时装上这两种能力的第一步。
返回列表