2、向量化基础:从原始数据到数学向量的第一公里
各位同学,今天我们聊一个非常基础但又极其关键的话题——向量化。说白了,就是把文本、图像这些人类能理解的东西,变成机器能计算的数字序列。我在做向量数据库选型时发现,很多人只关心检索性能,却忽略了向量质量才是决定系统上限的命门。
2.1 Embedding 技术原理:万物皆可向量
Embedding 这个词,直译是“嵌入”。什么意思呢?就是把一个高维的、离散的符号,映射到一个低维的、连续的向量空间里。你想想看,一个词、一张图、一段音频,本质上都是符号。计算机不认识“苹果”这个词,但它能计算 [0.12, -0.45, 0.78, ...] 这个向量。
核心思想就一句话:语义相近的对象,在向量空间里距离也近。
Embedding 的本质:它是一个从原始特征空间到分布式表示空间的映射函数 f: X → Rd,其中 d 是向量维度。这个映射要保留原始数据的语义结构。
我在项目中遇到过一种典型误区:有人直接把 one-hot 编码当向量用。比如“苹果”=[1,0,0],“香蕉”=[0,1,0],“手机”=[0,0,1]。这三个向量两两之间的夹角都是 90 度,语义距离完全一样。这显然不合理——苹果和香蕉都是水果,应该比手机更近才对。Embedding 要解决的就是这个问题。
2.2 文本向量化:从 Word2Vec 到 BERT
2.4.1 Word2Vec:静态向量的经典
Word2Vec 是 Google 在 2013 年提出的。它有两种训练方式:CBOW(连续词袋)和 Skip-gram。CBOW 用上下文预测当前词,Skip-gram 用当前词预测上下文。我个人习惯用 Skip-gram,因为它在处理低频词时效果更好。
训练完成后,每个词对应一个固定向量。比如“国王”减去“男人”加上“女人”,结果会非常接近“女王”。这就是经典的向量偏移现象。
# Word2Vec 训练示例(Gensim)
from gensim.models import Word2Vec
sentences = [["我", "喜欢", "吃", "苹果"],
["他", "喜欢", "吃", "香蕉"]]
model = Word2Vec(sentences, vector_size=128, window=5, min_count=1, sg=1)
vector = model.wv["苹果"] # 获取向量,shape: (128,)
避坑指南:Word2Vec 的向量是静态的,一个词只有一个向量。这意味着“苹果”(水果)和“苹果”(手机品牌)会被映射到同一个点。我曾经在一个电商搜索项目中踩过这个坑,后来不得不改用 BERT 做消歧。
2.4.2 BERT:动态向量的革命
BERT 的出现改变了游戏规则。它不再是给每个词一个固定向量,而是根据上下文动态生成。同样的“苹果”,在“吃苹果”和“买苹果”中,向量完全不同。
BERT 的核心是 Transformer 的 Encoder 部分,通过双向注意力机制捕捉上下文信息。输出时,我们可以取最后一层的 [CLS] 作为句子向量,也可以对所有 做平均池化。
# BERT 向量提取示例(HuggingFace)
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertModel.from_pretrained("bert-base-chinese")
text = "我喜欢吃苹果"
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs)
# 取 [CLS] 作为句子向量
sentence_vector = outputs.last_hidden_state[:, 0, :].detach().numpy()
# shape: (1, 768)
注意:BERT 的向量维度通常是 768(base 版本)或 1024(large 版本)。这比 Word2Vec 的 128-300 维大得多。维度越高,表达能力越强,但存储和检索成本也越高。在流批一体架构中,你需要权衡精度和延迟。
2.3 图像向量化:从 CNN 到 ResNet
图像向量化的思路和文本类似,但输入是像素矩阵。早期做法是把像素拉平成一维向量,但效果很差。为什么?因为图像有空间结构,拉平会丢失局部特征。
CNN(卷积神经网络)解决了这个问题。它通过卷积核在图像上滑动,提取局部特征,再通过池化层降维。最后接一个全连接层,输出固定长度的向量。
2.3.1 CNN 做图像向量的基本流程
- 输入图像,尺寸归一化(如 224x224x3)
- 经过多层卷积+池化,提取特征图
- 全局平均池化,把特征图压缩成向量
- 可选:再接一个全连接层,输出指定维度
我记得在做一个商品图片检索系统时,直接用 VGG16 的 fc7 层输出(4096 维)做向量。结果检索速度慢得离谱。后来改用 ResNet50 的全局平均池化层输出(2048 维),精度没降多少,速度翻了一倍。
2.3.2 ResNet:解决退化问题
ResNet 的核心创新是残差连接。简单说,就是让网络学习“残差”而不是“原始映射”。假设期望输出是 H(x),传统网络直接学 H(x),ResNet 学 F(x) = H(x) - x,然后输出 F(x) + x。
这样做的好处是:梯度可以直接通过恒等映射传播,不会消失。所以 ResNet 可以做到 152 层甚至更深,而 VGG 到 19 层就饱和了。
# 用 ResNet50 提取图像向量(PyTorch)
import torch
import torchvision.models as models
from torchvision import transforms
from PIL import Image
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("apple.jpg")
img_tensor = transform(img).unsqueeze(0)
with torch.no_grad():
vector = model(img_tensor).squeeze().numpy()
# shape: (2048,)
经验之谈:用预训练模型做图像向量时,建议去掉最后的全连接分类层,用全局平均池化层的输出。这样得到的向量更具泛化性,适合做相似度检索。我一般用 ResNet50 的 2048 维输出,效果和速度比较均衡。
2.4 向量维度与相似度度量
2.4.1 维度选择:不是越大越好
向量维度是个需要仔细权衡的参数。维度越高,表达能力越强,但也会带来三个问题:
- 存储成本:每个向量占用 d × 4 字节(float32),100 亿条 768 维向量需要约 2.8TB
- 计算成本:高维向量的距离计算更慢,O(d) 的复杂度在 d 很大时不可忽视
- 维度灾难:高维空间中,所有点之间的距离趋于相等,区分度下降
| 模型 | 典型维度 | 适用场景 | 存储成本(1亿条) |
|---|---|---|---|
| Word2Vec | 128-300 | 关键词匹配、主题分类 | ~120GB |
| BERT-base | 768 | 语义搜索、问答系统 | ~286GB |
| ResNet50 | 2048 | 图像检索、视觉搜索 | ~763GB |
| Ada-002 | 1536 | 通用文本嵌入 | ~572GB |
2.4.2 相似度度量:选对距离公式
有了向量,怎么比较它们是否相似?常用的度量有三种:
- 余弦相似度:cos(A, B) = A·B / (|A|×|B|),范围 [-1, 1]。只关心方向,不关心长度。文本向量常用。
- 欧氏距离:d(A, B) = sqrt(Σ(Ai-Bi)²),范围 [0, +∞)。关心位置。图像向量常用。
- 点积:A·B = Σ(Ai×Bi),范围 (-∞, +∞)。如果向量已归一化,等价于余弦相似度。
我的建议:在流批一体架构中,如果向量已经做了 L2 归一化,点积和余弦相似度等价。此时优先用点积,因为计算更快(少一次除法)。我在 Milvus 和 Faiss 中都是这么配置的。
为什么会这样?因为归一化后 |A|=|B|=1,余弦公式简化为 A·B。而点积只需要乘法和加法,不需要开方和除法,硬件更友好。
2.5 本章知识体系总览
下面这张图总结了向量化的核心流程和关键决策点。你可以把它当作一个快速参考。
从这张图可以看出,向量化的核心链路是:原始数据 → 模型提取 → 向量表示 → 维度选择 → 相似度度量。每一步都有多个选项,没有银弹。你需要根据业务场景、数据规模、延迟要求来做取舍。
最后提醒一句:向量化是整个向量检索系统的起点。如果这一步没做好,后面再牛的索引算法也救不了。我见过太多团队花大量时间调优 Milvus 参数,结果发现是向量质量太差——词都没分对,模型没选对,维度没调好。嗯,先把基础打牢。