Embedding 深入:对比学习、双塔架构与向量检索工程

Embedding 是语义检索与 RAG 的地基,本文系统讲解对比学习原理(正负样本/InfoNCE 损失/温度系数)、双塔模型与 Sentence-Transformer 架构、Embedding 训练数据构造(难负样本/硬负样本挖掘/蒸馏)、相似度计算与归一化(余弦/点积/内积+缩放)、向量索引与量化、维度与多模态对齐、以及生产调优(批次内负样本/温度/距离度量)的完整方法论。

Embedding 质量的差距,最终会放大成检索与 RAG 效果的差距。向量化不是「调个 API」那么简单——对比学习怎么训、负样本怎么挖、距离怎么算,每一个决定都影响召回。本文把 Embedding 从原理讲到工程。

Embedding 为什么有效

Embedding 把离散文本(或图像、多模态内容)映射成连续向量,让「语义相近 → 向量相近」。从词向量(Word2Vec/Glove)到句子向量(Sentence-Transformer)再到多模态向量(CLIP),共同内核是在向量空间中用距离度量语义相似度。

一个常被误解的点:Embedding 本身无所谓好坏,好坏在于「相似度是否与语义一致」。同一个向量空间里,余弦相似度高的一对样本语义应该相近;如果距离关系和人类判断对不上,这个 Embedding 就没用。训练的目标就是让「向量空间的拓扑 ≈ 语义空间的拓扑」。

对比学习:Embedding 训练的核心范式

现代 Embedding 训练几乎都用对比学习(Contrastive Learning):让模型学会「正样本对靠近、负样本对远离」。核心损失是 InfoNCE(温度缩放交叉熵):

# InfoNCE 损失示意(批次内所有样本互为负样本)
# L = -log[ exp(sim(q, pos)/τ) / Σ_j exp(sim(q, neg_j)/τ) ]
# sim: 余弦相似度 | τ: 温度系数 | pos: 正样本 | neg_j: 批次内其他样本

import torch, torch.nn.functional as F

def contrastive_loss(q, pos, temperature=0.05):
    logits = q @ pos.T / temperature          # 批次内相似度矩阵
    labels = torch.arange(len(q), device=q.device)  # 对角线为正样本
    return F.cross_entropy(logits, labels)

三个核心超参:

  • 温度系数 τ:τ 越小,损失对「难分样本」越敏感,区分度越高,但训练越不稳定。常用 0.02~0.1。
  • 正样本来源:同义词、相似问题、相似文档、数据增强扰动。正样本质量决定 Embedding 上限。
  • 负样本来源:批次内其他样本、随机采样、难负样本挖掘。负样本是训练稳定的关键。

双塔模型与 Sentence-Transformer

双塔(Two-Tower)架构是 Embedding 的主流形态:Query 塔和 Document 塔(或共享权重的单塔)各自把输入编码成向量,训练时用对比损失拉近正对、推远负对。推理时两条塔独立编码,向量预先索引,查询只需计算一次向量再做 ANN 检索——这是向量检索低延迟的架构基础。

Sentence-Transformer 是双塔思想的开源实现,用预训练 Transformer(BERT 系)加池化层输出句子向量。工程要点:

# Sentence-Transformer 加载与编码
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-m3")   # 支持 dense + sparse + multi-vector
emb = model.encode("如何优化 RAG 召回率", normalize_embeddings=True)

微调 Embedding 模型的常用套路是在已有 Sentence-Transformer 上做领域适配:构造领域内的相似/不相似对,用对比损失继续训练几轮,比从头训省太多。数据量小(几千对)也能有明显提升。

训练数据构造:正样本、负样本与难负样本

Embedding 模型吃数据的方式很挑剔,负样本设计是质量的分水岭:

  • 随机负样本:随便抽的不相关样本。太简单,模型学不到判别力。
  • 批次内负样本(In-batch Negatives):同 batch 其他样本当负样本,几乎零成本,是默认配置。batch 越大负样本越多样。
  • 难负样本(Hard Negatives):检索结果里相似但不相关的样本。这是最重要的负样本——它逼模型区分「看似相关实则无关」的边界,直接决定检索精度的上限。
# 难负样本挖掘的简化流程
# 1) 用当前模型对语料建索引
# 2) 对每个正样本检索 top-K 相似但不匹配的文档
# 3) 把这些"假阳性"作为难负样本加入下一轮训练
# 4) 可迭代多轮(hard negative mining),每轮精度提升

**知识蒸馏(Distillation)**是数据不足时的高效补量:用强模型(如 GPT 打分、LLM 生成)为弱模型制造相似/不相似对,让弱模型学强模型的相似度分布。蒸馏数据往往比人工标注性价比高得多。

相似度计算与归一化

向量建好索引后,检索就是算距离。三种常用度量各有用场:

  • 余弦相似度(Cosine):对向量长度不敏感,适合文本语义(只看方向)。默认首选。
  • 点积(Dot Product):长度也参与计分,常用于评分模型、多模态匹配。
  • 欧氏距离(L2):几何距离,物理/坐标类场景更直观。
# 归一化是检索正确性的关键一步
# 归一化后: 余弦相似度 == 点积  |  欧氏距离与余弦单调对应
# 不归一化直接做点积检索,长向量会系统性偏高分,召回被污染
# 生产实践:编码后一律 normalize_embeddings=True

工程铁律:除非明确需要长度信息,一律归一化后统一用点积。这样既保留余弦语义,又能利用向量库对点积/内积的硬件优化(比余弦计算快)。

向量索引与量化:召回与精度的平衡

Embedding 建索引后要用 ANN(近似最近邻) 检索才能撑住百万级规模,常用 HNSW / IVF-PQ:

  • HNSW:图式索引,召回率高、查询快,但内存占用大。中小规模(<1000 万)首选。
  • IVF-PQ:先聚类再量化,省内存、可扩展,但参数多、召回略降。大规模海量数据场景适用。
  • 标量量化(SQ8):把 float 向量压成 int8,内存减 4 倍,召回损失很小,是「免费午餐」。
# Qdrant 建 HNSW + 量化索引的示例
from qdrant_client import QdrantClient, models

client.create_collection(
    "documents",
    vectors_config={"size": 1024, "distance": models.Distance.DOT},
    quantization_config=models.ScalarQuantization(
        scalar=models.ScalarQuantizationConfig(
            type=models.ScalarType.INT8, always_ram=True
        )
    ),
    hnsw_config=models.HnswConfigDiff(m=32, ef_construct=200),
)

召回与精度的平衡点要靠离线评测标定:建一个带 ground truth 的评测集(每个 query 标注正确答案),对比不同索引参数下的 Recall@K 与延迟,而不是拍脑袋选参数。

维度、长度与多模态对齐

工程上几个容易踩的坑:

  • 向量维度:1024 维以上内存爆炸(1000 万 × 1024 × 4B ≈ 40GB)。bge-m3 的 1024 维可用,但超大语料建议用 256~768 维的轻量模型,或做 PCA 降维。
  • 输入长度:长文档直接编码会丢信息。标准做法是切块(Chunking)后对块编码,检索到块再映射回原文,兼顾召回与上下文完整性。
  • 多模态对齐:CLIP 类模型把图文映射到同一空间,支持「文本搜图、图搜图、图文联合检索」。对齐训练靠大规模图文对(image-text pairs),负样本同样用 in-batch + 难负样本。
# 长文档切块的简化示意
# 128 token 一句的滑动窗口 + 重叠 → 块 Embedding → 检索块 → 用块定位原文段落
# 块太大语义混叠、块太小上下文断裂;512~1024 token 是常见折中

生产调优清单

把 Embedding 调好,按下面清单逐项核对:

  1. 归一化:编码后一律 normalize,检索统一用点积。
  2. 温度与批次:训练 τ 用 0.05 起步,batch 尽量大(≥64)以扩充 in-batch 负样本。
  3. 难负样本:每轮加 hard negatives,两三轮挖掘后召回与 NDCG 提升最明显。
  4. 检索参数:HNSW 的 ef_search 调高召回、m 调高图质量(均牺牲内存)。
  5. 评测闭环:建 ground truth 集,用 Recall@K / MRR / NDCG 回归,任何索引或模型改动都要过评测。
  6. 混合检索兜底:纯向量召回不到精确关键词,配 BM25 或稀疏向量混合,再 Reranker 精排——RAG 生产标配。

总结

Embedding 的工程本质是**「空间拓扑 = 语义拓扑」的闭环**:对比学习定范式,双塔架构定形态,难负样本与蒸馏定质量,归一化与索引定检索效率,评测定迭代方向。落地顺序建议:先按「归一化 + 点积 + HNSW」把基线跑起来,再挖难负样本迭代精度,最后按评测指标调索引参数。Embedding 是检索系统的地基,值得花时间打磨。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ai」更多文章

  1. 类别不平衡与异常检测:从重采样到半监督方法
  2. MLOps 治理与可复现:模型注册、漂移监控与合规
  3. 数据工程进阶:标注体系、合成数据与数据飞轮