第4章:基础概念解析 - 嵌入、维度、相似度度量¶
4.1 向量嵌入(Embedding)¶
什么是向量嵌入¶
向量嵌入(Embedding) 是将离散、高维的原始数据(如文字、图片、音频)转换为连续、低维的稠密向量的过程。
核心目标:让"语义相似"的数据在向量空间中"距离相近"。
# 向量化示例
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
# 文本嵌入
texts = ["我想买一部iPhone手机", "iPhone拍照效果很好", "今天天气真不错"]
embeddings = model.encode(texts)
print(f"向量形状: {embeddings.shape}") # (3, 384) - 3个文本,每个384维
print(f"第一个文本的向量: {embeddings[0][:5]}...") # [0.12, -0.34, 0.56, ...]
嵌入的直观理解¶
graph TD
subgraph 嵌入过程
A["原始数据"] -->|Embedding Model| B["高维向量"]
end
subgraph 不同数据的嵌入
B -->|文本| T["文字向量"]
B -->|图片| I["图像向量"]
B -->|音频| S["音频向量"]
B -->|视频| V["视频向量"]
end
subgraph 向量空间
T -->|"位置相近"| T1["'狗' → [0.2, 0.8]"]
T -->|"位置相近"| T2["'猫' → [0.2, 0.7]"]
T1 -->|"位置远离"| T3["'汽车' → [0.9, 0.1]"]
end
style A fill:#e1f5fe
style B fill:#fff3e0
style T fill:#c8e6c9
style I fill:#c8e6c9
style S fill:#c8e6c9
style V fill:#c8e6c9
嵌入的几个关键特性¶
1. 语义保持¶
语义相近的内容在向量空间中距离较近:
# 语义相似 → 向量距离近
result = cosine_similarity(
embed("国王"), # [0.8, 0.2, 0.1, ...]
embed("王后") # [0.7, 0.3, 0.2, ...]
)
print(f"国王 vs 王后: {result:.3f}") # ~0.95 (很接近)
result = cosine_similarity(
embed("国王"),
embed("汽车")
)
print(f"国王 vs 汽车: {result:.3f}") # ~0.1 (距离远)
2. 可计算性¶
向量之间可以进行数学运算:
# 著名的 king - man + woman ≈ queen
# 这个例子完美展示了向量嵌入的语义空间
king = embed("king")
man = embed("man")
woman = embed("woman")
# 向量运算
result = king - man + woman
# 验证结果与 queen 的相似度
queen = embed("queen")
similarity = cosine_similarity(result, queen)
print(f"king - man + woman 接近 queen: {similarity:.3f}") # ~0.8
3. 维度约简¶
将高维稀疏数据转换为低维稠密向量:
| 原始数据 | One-Hot编码维度 | 嵌入后维度 | 压缩比 |
|---|---|---|---|
| 英文单词 | ~50,000 | 128-512 | 100-400x |
| 图片像素 | 224×224×3 | 512-2048 | 60-300x |
| 文档词汇 | 10,000+ | 768-1536 | 10-15x |
主流嵌入模型¶
| 模型 | 开发者 | 向量维度 | 特点 |
|---|---|---|---|
| Word2Vec | 100-300 | 词嵌入经典 | |
| BERT | 768-1024 | 上下文相关 | |
| Sentence-BERT | UKPLab | 384-1024 | 句子级别 |
| OpenAI Embeddings | OpenAI | 1536 | API调用 |
| CLIP | OpenAI | 512-1024 | 多模态 |
| text-embedding-3 | OpenAI | 3072 | 最新版本 |
# 不同嵌入模型对比
from sentence_transformers import SentenceTransformer
# 轻量级模型
light_model = SentenceTransformer('all-MiniLM-L6-v2')
light_dim = light_model.get_sentence_embedding_dimension() # 384
# 高质量模型
quality_model = SentenceTransformer('all-mpnet-base-v2')
quality_dim = quality_model.get_sentence_embedding_dimension() # 768
print(f"轻量模型维度: {light_dim}, 质量模型维度: {quality_dim}")
4.2 维度(Dimension)¶
什么是维度¶
维度(Dimension) 是指向量中包含的数值元素的个数。
# 一维向量
v1 = [0.5] # 1维
# 二维向量(平面上的点)
v2 = [0.3, 0.7] # 2维
# 三维向量(空间中的点)
v3 = [0.2, 0.5, 0.8] # 3维
# 高维向量(AI中的常见形式)
v384 = [0.1] * 384 # 384维
v1536 = [0.1] * 1536 # 1536维
维度与信息量¶
graph LR
subgraph 维度与表达能力
A["低维度 (2-50)"] -->|"信息有限
表达能力弱"| B["只能捕捉简单特征"]
C["中维度 (100-500)"] -->|"平衡选择
性价比高"| D["语义信息丰富"]
E["高维度 (1000+)"] -->|"信息丰富
但存储成本高"| F["细微语义差异"]
end
style A fill:#ffccbc
style C fill:#c8e6c9
style E fill:#e1f5fe
维度的实际影响¶
| 维度范围 | 典型应用 | 存储空间(1M向量) | 搜索速度 |
|---|---|---|---|
| 128 | 简单语义 | ~512MB | 极快 |
| 384 | 通用文本 | ~1.5GB | 快 |
| 768 | 高质量文本 | ~3GB | 中等 |
| 1536 | 精细语义 | ~6GB | 较慢 |
| 3072 | 最高质量 | ~12GB | 慢 |
# 计算向量存储空间
def calculate_storage(num_vectors, dimension, bytes_per_float=4):
"""计算存储空间"""
total_bytes = num_vectors * dimension * bytes_per_float
return total_bytes / (1024**3) # 转换为GB
print(f"1M向量 x 384维 = {calculate_storage(1_000_000, 384):.2f} GB")
print(f"1M向量 x 1536维 = {calculate_storage(1_000_000, 1536):.2f} GB")
维度灾难¶
当维度非常高时,向量之间的距离区分度会降低,这就是"维度灾难"。
# 维度灾难示意
import numpy as np
def avg_distance_in_unit_hyperball(dim, n_samples=10000):
"""计算单位超球体中随机点的平均距离"""
points = np.random.uniform(-1, 1, (n_samples, dim))
# 随机计算一些距离
distances = []
for i in range(min(1000, n_samples)):
for j in range(i+1, min(1000, n_samples)):
distances.append(np.linalg.norm(points[i] - points[j]))
return np.mean(distances)
dims = [2, 10, 50, 100, 500, 1000, 5000]
for d in dims:
avg_dist = avg_distance_in_unit_hyperball(d)
print(f"维度 {d:4d}: 平均距离 {avg_dist:.3f}")
# 维度越高,所有点之间的距离差异越小
# 这使得区分"相似"和"不相似"变得困难
graph TD
subgraph 维度灾难示意
A["低维度空间"] -->|"点分布紧密
距离差异明显"| B["容易区分相似/不相似"]
C["高维度空间"] -->|"点分布稀疏
距离趋于一致"| D["难以区分相似/不相似"]
end
style A fill:#c8e6c9
style B fill:#c8e6c9
style C fill:#ffccbc
style D fill:#ffccbc
4.3 相似度度量(Similarity Metrics)¶
相似度度量对比¶
graph TD
subgraph 欧氏距离 L2
L2_1["√[(a₁-b₁)² + (a₂-b₂)² + ...]"]
L2_2["值越小越相似"]
L2_3["范围: [0, +∞)"]
end
subgraph 余弦相似度 Cosine
C_1["(A · B) / (|A| × |B|)"]
C_2["值越大越相似"]
C_3["范围: [-1, 1]"]
end
subgraph 点积 Dot Product
D_1["A₁B₁ + A₂B₂ + ..."]
D_2["值越大越相似"]
D_3["范围: (-∞, +∞)"]
end
subgraph 曼哈顿距离 L1
M_1["|a₁-b₁| + |a₂-b₂| + ..."]
M_2["值越小越相似"]
M_3["范围: [0, +∞)"]
end
1. 欧氏距离(L2 Distance)¶
定义:向量各维度差值平方和的平方根。
import numpy as np
def euclidean_distance(a, b):
"""欧氏距离"""
return np.linalg.norm(a - b)
# 示例
a = np.array([1, 2, 3])
b = np.array([4, 6, 3])
dist = euclidean_distance(a, b)
print(f"欧氏距离: {dist:.2f}") # 4.47
# 几何解释:在二维空间中,这是两点之间的直线距离
2. 余弦相似度(Cosine Similarity)¶
定义:两个向量夹角的余弦值。
def cosine_similarity(a, b):
"""余弦相似度"""
dot = np.dot(a, b)
norm_a = np.linalg.norm(a)
norm_b = np.linalg.norm(b)
return dot / (norm_a * norm_b)
# 示例
a = np.array([1, 1, 0])
b = np.array([1, 1, 1])
c = np.array([-1, -1, 0])
print(f"a与b: {cosine_similarity(a, b):.3f}") # 0.816 (夹角45°)
print(f"a与c: {cosine_similarity(a, c):.3f}") # -1.000 (夹角180°)
3. 点积相似度(Dot Product)¶
定义:对应元素相乘后求和。
def dot_product(a, b):
"""点积"""
return np.dot(a, b)
# 示例
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
dot = dot_product(a, b)
print(f"点积: {dot}") # 32 (1*4 + 2*5 + 3*6)
4. 归一化后的关系¶
当向量经过L2归一化后,点积等价于余弦相似度:
def normalized_dot_product(a, b):
"""归一化后的点积 = 余弦相似度"""
a_norm = a / np.linalg.norm(a)
b_norm = b / np.linalg.norm(b)
return np.dot(a_norm, b_norm)
a = np.array([3, 4]) # 模长=5
b = np.array([4, 3]) # 模长=5
print(f"余弦相似度: {cosine_similarity(a, b):.3f}") # 0.896
print(f"归一化点积: {normalized_dot_product(a, b):.3f}") # 0.896
如何选择相似度度量¶
| 场景 | 推荐度量 | 原因 |
|---|---|---|
| 文本语义搜索 | 余弦相似度 | 文本向量方向比长度更重要 |
| 人脸识别 | 余弦相似度 | 忽略光照对向量长度的影响 |
| 推荐系统 | 点积 | 需要考虑用户偏好强度 |
| 图像特征匹配 | 欧氏距离 | 特征强度本身有意义 |
| 音乐推荐 | 余弦相似度 | 风格方向比热度更重要 |
# 实际选择示例
# 场景1: 语义文本搜索
query = "推荐好用的笔记本电脑"
doc_embeddings = model.encode(corpus)
query_embedding = model.encode(query)
# 使用余弦相似度
similarities = cosine_similarity([query_embedding], doc_embeddings)[0]
top_k = np.argsort(similarities)[-5:][::-1]
# 场景2: 推荐系统
user_preference = user_embedding # 用户偏好向量
item_features = item_embeddings # 物品特征向量
# 使用点积(考虑强度)
scores = np.dot(user_preference, item_features.T)
top_k = np.argsort(scores)[-5:][::-1]
4.4 批处理与实时处理¶
批量向量化¶
当需要处理大量数据时,使用批处理可以显著提升效率:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
# 单条处理(慢)
texts = ["文本1", "文本2", "文本3"]
for text in texts:
embedding = model.encode(text) # 每条单独编码
# 批量处理(快)
batch_embeddings = model.encode(texts, batch_size=32) # 一次性编码
print(f"批量形状: {batch_embeddings.shape}") # (3, 384)
批处理性能对比¶
| 方式 | 100条文本 | 10,000条文本 | 1,000,000条文本 |
|---|---|---|---|
| 单条处理 | 2秒 | 200秒 | 20,000秒 |
| 批量处理(32) | 0.5秒 | 8秒 | 800秒 |
| 加速比 | 4x | 25x | 25x |
4.5 概念速查表¶
graph TD
subgraph 核心概念
E[Embedding
向量嵌入]
D[Dimension
维度]
S[Similarity
相似度度量]
I[Index
索引结构]
N[NNS
最近邻搜索]
ANN[ANN
近似最近邻]
end
subgraph 关系
E -->|生成| V[Vector
向量]
V -->|具有| D
D -->|影响| S
V -->|建立| I
I -->|加速| N
N -->|近似版| ANN
end
style E fill:#e1f5fe
style D fill:#fff3e0
style S fill:#c8e6c9
style I fill:#e1f5fe
style N fill:#fff3e0
style ANN fill:#c8e6c9
style V fill:#ffccbc
关键术语定义¶
| 术语 | 英文 | 定义 | 示例 |
|---|---|---|---|
| 向量嵌入 | Embedding | 将数据转换为高维向量的技术 | embed("苹果") → [0.8, 0.1, ...] |
| 维度 | Dimension | 向量中元素的个数 | 384维、1536维 |
| 相似度度量 | Similarity Metric | 衡量向量间相似程度的方法 | 余弦相似度、欧氏距离 |
| 索引 | Index | 加速检索的数据结构 | HNSW、IVF |
| 最近邻 | Nearest Neighbor | 与查询向量最相似的向量 | K=1时返回1个最近邻 |
| 近似最近邻 | ANN | 用精度换速度的近似搜索 | 99%精度+10x速度 |
本章小结¶
本章要点:
- 向量嵌入(Embedding):
- 将文本、图片等数据转换为数值向量
- 语义相似的内容在向量空间中距离相近
-
使用预训练模型可以快速获得高质量嵌入
-
维度(Dimension):
- 维度越高,表达能力越强,但存储成本越高
- 需要在效果和性能之间找到平衡
-
常见维度:384(轻量)、768(标准)、1536(高质量)
-
相似度度量:
- 余弦相似度:最常用,适合文本语义搜索
- 欧氏距离:适合考虑向量大小的场景
-
点积:适合推荐系统等需要考虑强度的场景
-
性能优化:
- 批量处理可以显著提升向量化效率
- 选择合适的向量维度平衡效果和性能
下一章预告:第5章:典型应用场景 - 了解向量数据库在实际应用中的使用方法 →