2、向量化技术基础:Embedding模型原理、文本向量化、图像向量化、多模态向量化
好,咱们进入正题。向量检索的核心,说白了就是把非结构化的数据——文字、图片、音频——统统变成一串数字。这串数字就是向量。你想想看,计算机没法直接理解“猫”这个词,但它能理解 [0.23, -0.45, 0.78, ...] 这样的数组。把数据变成向量的过程,就叫向量化,也叫 Embedding。
我个人习惯把 Embedding 模型比作一个“编码器”。你扔进去一段文本,它吐出来一个固定长度的向量。这个向量要能保留原始数据的语义信息。什么意思?就是“苹果”和“香蕉”的向量距离,要比“苹果”和“汽车”近。这就是向量检索能工作的前提。
2.1 Embedding 模型原理:从词到向量的魔法
早期的做法很简单,叫 One-Hot 编码。比如词汇表有 1 万个词,“猫”这个词就对应一个 1 万维的向量,只有第 2333 位是 1,其他全是 0。这有什么问题?维度爆炸,而且词与词之间没有语义关系。“猫”和“狗”的向量距离,跟“猫”和“桌子”一样远。这显然不合理。
后来有了 Word2Vec、GloVe 这些静态向量模型。它们通过上下文来学习词的向量表示。我记得第一次用 Word2Vec 跑出结果时,发现“国王 - 男人 + 女人 ≈ 女王”,那种感觉真的很奇妙。但静态向量有个硬伤:一个词只有一个向量。比如“苹果”这个词,在“吃苹果”和“苹果手机”里,语义完全不同,但向量是一样的。
所以现在的主流方案是动态上下文向量,也就是 BERT 及其变体。BERT 用 Transformer 的注意力机制,让每个词的向量都依赖它所在的上下文。你输入“我今天想吃苹果”,它输出的“苹果”向量,和输入“我买了一部苹果手机”输出的“苹果”向量,是完全不同的。这才是真正的语义理解。
核心要点:Embedding 模型的目标是让语义相似的实体在向量空间中距离更近。这个距离通常用余弦相似度或欧氏距离来衡量。模型训练得越好,这个空间的结构就越合理。
2.2 文本向量化:最基础也最常用
文本向量化是咱们做向量检索的起点。目前最常用的方案是 Sentence-BERT(SBERT)。为什么不用原生 BERT?因为原生 BERT 做句子对相似度计算时,需要把两个句子拼在一起输入,这在检索场景下效率太低——你不可能把 100 万条候选句子都和查询拼一遍。
SBERT 用孪生网络结构,让两个句子各自通过同一个 BERT 模型,然后取 [CLS] 标记的输出,或者对所有 的输出做平均池化,得到一个固定长度的句子向量。这样,所有句子都可以预先向量化,查询时只需要算一次查询向量,然后跟索引里的向量做相似度比较就行。
我在项目中遇到过一个问题:直接用 BERT 的 [CLS] 输出做句子向量,效果其实不太好。后来发现,对最后一层所有 的输出做平均池化,或者用 CLS 加一个额外的全连接层,效果会稳定很多。嗯,这里要注意,不同的池化策略对最终检索精度影响很大。
实践建议:如果你用 HuggingFace 的 sentence-transformers 库,可以直接调用 model.encode() 方法。它默认用了平均池化,效果不错。但如果你追求极致精度,可以试试 model.encode(..., output_value='_embeddings') 然后自己实现加权池化。
from sentence_transformers import SentenceTransformer
# 加载预训练模型
model = SentenceTransformer('all-MiniLM-L6-v2')
# 文本向量化
sentences = ["我今天想吃苹果", "我买了一部苹果手机"]
embeddings = model.encode(sentences)
print(embeddings.shape) # (2, 384)
print(embeddings[0][:5]) # 输出前5个维度
2.3 图像向量化:让机器看懂图片
图像向量化的思路和文本类似,但用的模型不同。图像领域的主力是 CNN(卷积神经网络)和 Vision Transformer(ViT)。
CNN 通过卷积核在图像上滑动,逐层提取特征。底层检测边缘、纹理,高层检测物体部件、整体形状。最后通过全局平均池化或全连接层,输出一个固定长度的向量。ResNet、EfficientNet 都是经典的选择。
ViT 则是把图像切成一个个 patch(比如 16x16 像素),然后把每个 patch 当作一个“词”,用 Transformer 来处理。它在大型数据集上表现很好,但需要更多数据才能训好。
我建议你直接用 timm 库或者 PyTorch 的 torchvision.models,去掉最后的分类层,取倒数第二层的输出作为图像向量。举个例子:
import torch
import torchvision.models as models
from PIL import Image
from torchvision import transforms
# 加载预训练模型,去掉分类层
model = models.resnet50(pretrained=True)
model = torch.nn.Sequential(*list(model.children())[:-1]) # 去掉最后的全连接层
# 图像预处理
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
img = Image.open('cat.jpg')
img_tensor = transform(img).unsqueeze(0)
# 提取向量
with torch.no_grad():
embedding = model(img_tensor).squeeze().numpy()
print(embedding.shape) # (2048,)
避坑指南:我曾经在图像向量化上踩过一个坑——直接用分类模型的倒数第二层输出,但没做 L2 归一化。结果余弦相似度计算出来的值分布很不均匀,有的接近 1,有的接近 0。后来发现,对向量做 L2 归一化后,余弦相似度就等于点积,计算更稳定,检索效果也更好。所以,记得加一步归一化。
2.4 多模态向量化:打通文本与图像的桥梁
多模态向量化,就是把不同模态的数据映射到同一个向量空间。这样,你可以用文本搜图片,或者用图片搜文本。最经典的模型是 CLIP(Contrastive Language-Image Pre-training)。
CLIP 的训练思路很巧妙:它用 4 亿个图文对,让文本编码器和图像编码器各自输出向量,然后通过对比学习,让匹配的图文对向量距离更近,不匹配的拉远。训练完成后,文本和图像就被映射到了同一个向量空间。
你想想看,这意味着什么?你可以输入“一只在沙滩上奔跑的金毛犬”,然后从 100 万张图片里找出最匹配的那几张。这在电商搜索、内容审核、智能相册等场景下非常有用。
多模态向量化的难点在于对齐。不同模态的数据分布差异很大,模型需要学到跨模态的共性特征。我记得在做一个图文检索项目时,发现 CLIP 对“颜色”和“形状”的匹配很好,但对“材质”和“纹理”的匹配就弱一些。后来我们用了更细粒度的训练数据,才把这块补上。
关键洞察:多模态向量化的质量,很大程度上取决于训练数据的质量和多样性。如果你要做一个垂直领域的多模态检索(比如医疗影像+诊断报告),直接用 CLIP 可能效果不好。我建议你在领域数据上做微调,或者用 CoCa、BLIP 等更先进的模型。
2.5 本章知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图。它展示了从原始数据到向量空间的完整流程,以及不同模态的处理路径。
2.6 模型选型对比
最后,我整理了一张表格,把常用的向量化模型放在一起对比。你在实际项目中可以根据需求来选。
| 模型 | 模态 | 输出维度 | 推理速度 | 适用场景 |
|---|---|---|---|---|
| all-MiniLM-L6-v2 | 文本 | 384 | 快 | 通用文本检索、语义相似度 |
| all-mpnet-base-v2 | 文本 | 768 | 中 | 高精度文本检索、聚类 |
| ResNet-50 | 图像 | 2048 | 快 | 通用图像检索、特征提取 |
| ViT-B/16 | 图像 | 768 | 中 | 高精度图像检索、视觉理解 |
| CLIP-ViT-B/32 | 图文 | 512 | 中 | 图文互搜、零样本分类 |
选模型时,我一般会先看延迟要求。如果线上服务要求毫秒级响应,我会选维度低、推理快的模型,比如 all-MiniLM-L6-v2。如果精度优先,那就上 all-mpnet-base-v2 或者 CLIP。说白了,这就是一个 trade-off,跟咱们课程标题说的一样——精准度与延迟的平衡。