AI应用开发 - Vector Databases
Vector Databases
Purpose and Functionality
向量数据库是专为存储和检索高维向量数据而设计的数据库,通过相似度搜索而非精确匹配来找到最相关的结果。
核心原理
向量数据库的核心是向量嵌入(Vector Embeddings):将非结构化数据(文本、图像、音频等)转换为数值数组,在高维空间中表示其语义特征。
数据 → 嵌入模型 → 向量嵌入 → 向量数据库存储
↓
用户查询 → 嵌入模型 → 查询向量 → 相似度匹配 → 返回最近邻结果
相似度度量:
- 余弦相似度(Cosine Similarity):衡量向量方向夹角
- 欧氏距离(Euclidean Distance):衡量直线距离
- 点积(Dot Product):快速计算相似度
查询三阶段
- Indexation(索引):将向量嵌入转换为快速搜索的数据结构
- Inquiry(查询):比较查询向量与索引向量,找到最近邻
- Post-production(后处理):对最近邻进行后处理,生成最终输出
与传统数据库的区别
| 维度 | 传统数据库 | 向量数据库 |
|---|---|---|
| 数据类型 | 字符串、整数(精确值) | 高维向量(相似度) |
| 查询方式 | 精确匹配 | 相似度搜索 |
| 索引方式 | B-tree、Hash | HNSW、LSH、ANN |
| 适用场景 | 事务处理 | AI/ML 场景 |
何时使用 vs 何时不用
适合使用向量数据库的场景:
- 大规模知识库,需要数百万次查询
- 长期存在的共享数据
- 需要语义搜索、推荐系统、图像识别
可能不需要向量数据库的场景:
- 数据是短暂的(每次请求新鲜上下文)
- 用户只问少量问题
- 需要最快速度响应
- 场景:RAG 系统中用户上传临时文档,问几个问题后丢弃
基准测试参考(50,000 嵌入,1,536 维度):
| 方法 | 时间 |
|---|---|
| 朴素 KNN 搜索 | ~24ms/查询 |
| HNSW 索引构建 | ~277ms(一次性) |
| HNSW 查询 | ~0.47ms/查询 |
盈亏平衡点:约 11,510 次查询后,索引成本才能被查询时间节省抵消。
Top 5 向量数据库
| 数据库 | 特点 | 适用场景 |
|---|---|---|
| Pinecone | 完全托管、简单易用 | 云原生应用、快速上手 |
| Milvus | 开源、高性能、硬件加速 | 大规模生产环境 |
| Zilliz | 开源、分布式搜索 | 大数据、AI 应用 |
| Qdrant | 复杂过滤条件支持 | 需要精确筛选的搜索 |
| Deeplake | 云原生、实时摄取 | ML 工作流 |
12 大应用场景
- NLP:语义搜索、情感分析、文本分类
- 异常检测:网络流量分析、欺诈检测
- 推荐系统:相似度匹配、个性化推荐
- 图像识别:以图搜图、视觉相似
- 搜索引擎:语义搜索、相关内容返回
- RAG:为 LLM 提供外部知识库,减少幻觉
- 医疗:患者相似性分析、药物发现
- 自动驾驶:传感器数据处理、环境理解
- 金融:欺诈检测、风险评估
- 音乐/多媒体:相似内容推荐
- 聚类分类:基于相似度的数据分组
- 图分析:社区识别、连接预测
优势
- 数据类型:专为向量嵌入设计
- 可扩展性:可存储数十亿高维向量
- 高速搜索:先进索引算法(HNSW)
- 相似度搜索:找到最佳匹配而非精确匹配
- 灵活性:处理结构和非结构化数据
核心挑战
- 事务支持弱(数据管理)
- 高维向量索引计算成本高
- 搜索计算量大、成本高
- 调试困难(高维空间难以可视化)
- 非向量格式数据不适用
- 数据稀疏性导致效率问题
- 大数据集查询延迟更高
- 更新/删除需要维护索引一致性
选择标准
- 可扩展性和性能:数据量、维度、查询响应时间
- 数据模型和索引方法:是否支持灵活 schema、ANN 算法
- 易用性:文档、社区、配置难度
- 集成能力:API、SDK、与现有系统兼容性
- 社区和支持:活跃度、响应速度
- 成本:许可费用、运维成本
Popular Vector Databases
FAISS
FAISS(Facebook AI Similarity Search)是 Meta AI 开发的开源向量相似度搜索库,专注于高效相似度搜索和向量聚类。
核心算法
| 算法 | 说明 |
|---|---|
| K-means 聚类 | 将数据分成簇,查询时缩小搜索空间,只在相关簇中搜索 |
| 乘积量化(PQ) | 将向量压缩为短编码,大幅减少内存使用,加速搜索同时保持较高精度 |
| 优化乘积量化(OPQ) | PQ 增强版,旋转数据以更好地适应量化网格,提高压缩向量精度 |
距离度量
- 欧几里得距离:直线距离,关注向量几何相似性
- 余弦相似度:方向角度,适合文本分析(方向比长度更重要)
关键特性
| 特性 | 说明 |
|---|---|
| 可扩展性 | 支持百万到数十亿向量,使用倒排文件系统和 HNSW 图 |
| 速度 | GPU 加速可达 CPU 的 20 倍(Pascal 架构) |
| 准确性 | 可在速度和精度间权衡,支持 1-recall@1 等指标 |
| 多样性 | 支持图像、文本、音频等各类数据 |
应用场景
- 推荐系统:快速找到相似产品/电影/文章
- 图像视频搜索:通过视觉特征找到相似内容
- 异常检测:欺诈检测、网络安全、质量控制
- 信息检索:基于语义的文档搜索
代码示例
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# 构建索引
db = FAISS.from_documents(docs, embeddings)
# 查询
query = "What is machine learning?"
docs = db.similarity_search(query)
注意:Faiss 是一个库而非完整数据库,适用于需要将向量搜索集成到现有系统的场景。相比完整的向量数据库(Pinecone、Milvus 等),Faiss 更轻量但需要更多手动管理。
Pinecone
Pinecone 是一个云原生向量数据库,专为处理高维向量数据设计,使用近似最近邻(ANN)搜索实现高效相似度匹配。
工作原理
数据 → 向量化 → 创建索引 → ANN 搜索 → 相似度比较 → 后处理 → 返回结果
核心特性
| 特性 | 说明 |
|—|—|—|
| 快速且新鲜的向量搜索 | 亿级数据规模下极低延迟;索引实时更新 |
| 过滤向量搜索 | 结合元数据过滤器(类别、价格等)实现更快相关结果 |
| 实时更新 | 支持数据动态更新,无需全量重建索引 |
| 备份和集合 | 自动备份,支持选择性备份特定索引 |
| 友好 API | 语言无关,支持多种编程语言 |
| 成本效益 | 按使用量付费 |
应用场景
- 音频/文本搜索:高维数据快速相似度搜索
- NLP:文档分类、语义搜索、情感分析、问答系统
- 推荐系统:个性化相似商品推荐
- 图像/视频分析:监控、图像识别
- 时间序列相似性搜索:检测历史数据中的相似模式
与其他向量数据库对比
| 特性 | Pinecone | FAISS | Milvus |
|---|---|---|---|
| 部署方式 | 云原生(托管服务) | 库(需自行集成) | 开源(可自托管) |
| 实时更新 | 支持 | 需重建索引 | 支持 |
| 元数据过滤 | 支持 | 需额外实现 | 支持 |
| 成本 | 按使用量付费 | 免费(自托管) | 免费(自托管) |
Chroma
什么是 Chroma?
Chroma 是一个专为 AI 应用设计的开源向量数据库,以轻量级、易用性著称。它主要用于存储和检索高维向量嵌入,是 RAG(检索增强生成)系统的核心组件。
核心特点:
- 开源免费(Apache 2.0)
- 轻量级,API 简洁直观
- 支持多种嵌入模型
- 多语言客户端:Python、JavaScript/TypeScript、Rust
- 可本地部署或 Docker 部署
安装
pip install chromadb
# 或使用 Docker
docker pull chromadb/chroma
核心概念
| 概念 | 类比(MySQL) | 说明 |
|---|---|---|
| Client | 数据库连接 | 与 Chroma 数据库的连接 |
| Collection | 数据表 | 存储嵌入、文档和元数据的集合 |
| Document | 文本内容 | 要存储的原始文本 |
| Embedding | 向量 | 文档的数值表示 |
| Metadata | 附加信息 | 关于文档的额外数据 |
快速上手
创建客户端
import chromadb
# 方式一:内存模式(重启后数据丢失,适合测试)
client = chromadb.Client()
# 方式二:持久化模式(数据保存在本地磁盘)
client = chromadb.PersistentClient(path="./chroma_db")
# 方式三:HTTP 客户端(连接远程服务)
client = chromadb.HttpClient(host="localhost", port=8000)
创建集合
# 创建新集合
collection = client.create_collection(name="my_collection")
# 获取已存在的集合
collection = client.get_collection(name="my_collection")
# 获取或创建(如果不存在则创建)
collection = client.get_or_create_collection(name="my_collection")
# 删除集合
client.delete_collection(name="my_collection")
添加数据
collection.add(
documents=[
"This is a document about pineapple",
"This is a document about oranges"
],
ids=["id1", "id2"],
metadatas=[
{"source": "fruit article", "category": "tropical"},
{"source": "fruit article", "category": "citrus"}
]
)
# 添加后查看数量
print(f"文档数量: {collection.count()}")
查询数据
# 相似度搜索
results = collection.query(
query_texts=["This is a query document about hawaii"],
n_results=2 # 返回最相似的 2 个结果
)
print(results)
# 输出格式:
# {
# 'ids': [['id1', 'id2']],
# 'documents': [['This is a document about pineapple', 'This is a document about oranges']],
# 'metadatas': [[{'source': 'fruit article', 'category': 'tropical'}, ...]],
# 'distances': [[0.5, 0.8]], # 距离越小越相似
# 'embeddings': None
# }
获取和删除数据
# 根据 ID 获取
results = collection.get(ids=["id1", "id2"])
# 更新数据
collection.update(
ids=["id1"],
documents=["Updated document content"],
metadatas=[{"source": "updated"}]
)
# 删除数据
collection.delete(ids=["id1"])
自定义嵌入函数
Chroma 默认使用嵌入模型,你也可以指定自己的模型:
from chromadb.utils import embedding_functions
# 使用 OpenAI 的嵌入模型
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
api_key="your-api-key",
model_name="text-embedding-3-small"
)
# 创建集合时指定嵌入函数
collection = client.create_collection(
name="my_collection",
embedding_function=openai_ef
)
# 使用 Sentence Transformers(本地模型)
sentence_ef = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="all-MiniLM-L6-v2" # 或指定本地路径
)
collection = client.create_collection(
name="my_collection",
embedding_function=sentence_ef
)
元数据过滤
查询时可以结合元数据进行精确筛选:
# 创建带元数据的集合
collection = client.create_collection(name="filtered_collection")
collection.add(
documents=["Apple pie recipe", "Orange juice recipe", "Carrot cake recipe"],
ids=["id1", "id2", "id3"],
metadatas=[
{"type": "dessert", "calories": 300},
{"type": "drink", "calories": 100},
{"type": "dessert", "calories": 400}
]
)
# 只查询 type="dessert" 的文档
results = collection.query(
query_texts=["sweet treats"],
n_results=2,
where={"type": "dessert"} # 元数据过滤条件
)
# 复合条件
results = collection.query(
query_texts=["light food"],
n_results=2,
where={
"type": {"$eq": "dessert"}, # type == "dessert"
"calories": {"$lt": 350} # calories < 350
}
)
支持的操作符:
| 操作符 | 含义 | 示例 |
|---|---|---|
$eq |
等于 | {"type": {"$eq": "dessert"}} |
$ne |
不等于 | {"type": {"$ne": "drink"}} |
$lt |
小于 | {"calories": {"$lt": 300}} |
$lte |
小于等于 | {"calories": {"$lte": 300}} |
$gt |
大于 | {"calories": {"$gt": 100}} |
$gte |
大于等于 | {"calories": {"$gte": 100}} |
与 LangChain 集成
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# 创建向量存储
vectorstore = Chroma.from_documents(
documents=texts, # 文档列表
embedding=OpenAIEmbeddings(), # 嵌入函数
persist_directory="./chroma_langchain_db" # 持久化路径
)
# 相似度搜索
docs = vectorstore.similarity_search("your query", k=3)
# 带分数的搜索
docs_with_scores = vectorstore.similarity_search_with_score("your query", k=3)
Chroma 1.0+ 新特性
1.0.0 版本核心更新:
- Rust 前端成为默认,性能提升 20%+
- SQLite 迁移,单机部署更轻量
- 多语言 SDK 全面升级(Python、JS/TS、Rust)
- OpenAPI 规范更新,接口文档更清晰
1.0.8 版本核心更新:
- SPANN 索引默认启用,提升高维向量检索效率
- 集合复制(Collection Forking)功能
- 负载动态连接配置
- 更完善的 GC(垃圾回收)系统
部署方式对比
| 方式 | 适用场景 | 特点 |
|---|---|---|
内存模式 Client() |
本地开发测试 | 快速、免维护、重启丢失 |
持久化模式 PersistentClient() |
本地生产环境 | 数据持久化、轻量 |
| Docker 部署 | 团队共享/生产 | 隔离性好、易扩展 |
| 云服务 | 企业级应用 | 免运维、自动扩缩容 |
Chroma vs 其他向量数据库对比
| 特性 | Chroma | FAISS | Pinecone | Milvus |
|---|---|---|---|---|
| 类型 | 完整数据库 | 向量索引库 | 云服务 | 开源数据库 |
| 部署难度 | ⭐ 简单 | ⭐⭐ 中等 | ⭐ 简单(托管) | ⭐⭐⭐ 复杂 |
| 功能完整性 | CRUD+检索 | 仅索引/检索 | CRUD+检索+托管 | CRUD+检索+高级功能 |
| 适用规模 | 千-百万级 | 百万级 | 亿级+ | 亿级+ |
| 成本 | 免费 | 免费 | 按量付费 | 免费(自托管) |
| 适合场景 | AI 应用开发、原型验证 | 嵌入式场景 | 企业级生产 | 大规模生产 |
常见问题
Q:Chroma 和 FAISS 有什么区别?
Chroma 是完整的数据库产品,开箱即用带 CRUD 功能;FAISS 只是一个向量索引库,需要自己集成到现有系统中。
Q:Chroma 适合多大数据量?
Chroma 适合中小规模数据(数千到数百万向量)。对于十亿级数据,考虑使用 Milvus、Pinecone 等。
Q:如何选择嵌入模型?
| 场景 | 推荐模型 |
|---|---|
| 英文文本 | all-MiniLM-L6-v2(轻量)、text-embedding-3 |
| 中文文本 | m3e-base、bge-base-zh |
| 多语言 | multilingual-e5-base |
最小示例
import chromadb
client = chromadb.Client()
collection = client.create_collection(name="demo")
collection.add(documents=["苹果很好吃", "香蕉也不错"], ids=["1", "2"])
results = collection.query(query_texts=["水果"], n_results=1)
print(results['documents'])
# 输出:[['苹果很好吃']]
参考链接
- 官方文档:https://docs.trychroma.com
- GitHub:https://github.com/chroma-core/chroma
- 知乎教程:https://zhuanlan.zhihu.com/p/658217843
- CSDN 深度解析:https://blog.csdn.net/leohu0723/article/details/157844388
Indexing Embeddings
什么是向量索引?
向量索引是将高维向量数据组织成一种特殊数据结构的过程,目的是让搜索速度从"大海捞针"变成"按图索骥"。
打个比方:假设你要在一百万本书里找"和《活着》最相似的书":
- 没有索引:你得把每一本书都翻开来和《活着》对比一遍 — O(N) 复杂度,百万本书可能要等几分钟
- 有索引:图书管理员已经把你的一百万本书按"主题/风格/年代"分门别类放好了,你直接去"现实主义文学"那个书架找 — O(log N) 复杂度,可能只要几毫秒
向量索引就是这个"图书分类系统",只不过分类的依据是向量在高维空间中的几何位置。
为什么需要索引?
朴素搜索的困境
假设我们有 100 万个 1536 维的向量(OpenAI ada-002 的输出维度)。每次查询时:
| 方法 | 计算量 | 延迟(估算) |
|---|---|---|
| 暴力搜索(逐个比较) | 100万次距离计算 | ~100-500ms |
| 建立索引后搜索 | 100-1000次距离计算 | ~1-10ms |
关键数字:暴力搜索 100 万个向量大约需要 100ms,而建立索引后只需要 1ms 左右,快了 100 倍。
更重要的是:当数据量从 100 万增长到 10 亿时,暴力搜索延迟会线性增长到 10 秒(不可用),而好的索引算法只会增长到 ~10-50ms。
核心概念:ANN 搜索
向量索引基于 ANN(Approximate Nearest Neighbor,近似最近邻) 搜索,而非精确搜索。
ANN vs KNN
| 特性 | KNN(精确搜索) | ANN(近似搜索) |
|---|---|---|
| 结果质量 | 100% 精确 | 95-99% 精确 |
| 搜索速度 | 慢 | 快 |
| 内存占用 | 低 | 较高(需要额外索引结构) |
| 适用规模 | <10万 | 10万-10亿+ |
ANN 的核心思想:牺牲 1-5% 的精度,换取 10-100 倍的性能提升。在实际应用中,用户几乎感知不到差异。
主流索引算法详解
1. HNSW(分层可导航小世界图)
一句话理解:想象一个"高速公路网络"——全国高速连成一张网,城市内部又有地方公路。查询时先走高速快速缩小范围,再走地方公路找到精确目标。
Layer 2: ●━━━━━━━●━━━━━━━●━━━━━━━● ← 少量节点,广域快速跳转
│ │ │ │
Layer 1: ●●━━━━●●━━━━●●━━━━●●━━━━●● ← 中等密度
||| ||| ||| |||
Layer 0: ●●●●●●●●●●●●●●●●●●●●●●●●●●●●● ← 所有节点,最近邻连接
工作原理:
- 建索引时:自底向上构建多层图,上层节点少、下层节点多,像金字塔
- 查询时:从顶层最远节点开始,比较后跳到最近的邻居节点,类似贪心爬山
- 特点:查询快(O(log N))、精度高、内存占用中等
适用场景:大多数通用场景,Pinecone 默认使用
参数调优:
M:每个节点维护多少条边(默认 16,越大越精准但越慢)efConstruction:建索引时的搜索广度(默认 200)
2. IVF(倒排文件索引)
一句话理解:先把向量按 K-means 聚类分成 N 个簇,查询时只搜索最相关的几个簇,而非全部。
原始向量空间:
●●●●●●●● Cluster 0 (centroid: C0)
●●●●●●●● Cluster 1 (centroid: C1)
●●●●●●●● Cluster 2 (centroid: C2)
查询向量 Q:
距离 C0: 0.8 ← 跳过
距离 C1: 0.1 ← 最相关,只在这个簇里搜索
距离 C2: 0.6 ← 跳过
工作原理:
- 用 K-means 把所有向量分成 N 个簇,每个簇有个中心点
- 查询时先和所有中心点比较,选出最近的 K 个簇
- 只在这 K 个簇内做暴力搜索
优点:原理简单,易于理解
缺点:维度高时聚类效果下降,搜索精度不稳定
适用场景:数据分布均匀、维度不是特别高(<512维)
3. LSH(局部敏感哈希)
一句话理解:把相似的向量"哈希"到同一个桶里,查询时直接到对应桶里找。
向量 v1 = [0.1, 0.8, 0.2] → 哈希值 "0101" → 桶 A
向量 v2 = [0.2, 0.7, 0.1] → 哈希值 "0101" → 桶 A ← 和 v1 在同一桶,大概率相似
向量 v3 = [0.9, 0.1, 0.8] → 哈希值 "1010" → 桶 B
查询 Q = [0.15, 0.75, 0.2] → 哈希值 "0101" → 桶 A → 返回 v1, v2
核心特点:
- 概率保证:相似的向量以高概率落在同一桶
- 但不是精确保证:有可能两个很相似的向量恰好落在不同桶
适用场景:需要"哈希碰撞"来加速的场景,如去重、相似文档检测
4. PQ(乘积量化)
一句话理解:把大向量"压缩"成短编码,压缩 4-16 倍,大幅降低内存和计算量。
原始向量:[0.12, 0.85, 0.33, 0.91, 0.45, 0.67, 0.23, 0.78]
↓ 分成4段,每段2个数
子向量:[0.12, 0.85] [0.33, 0.91] [0.45, 0.67] [0.23, 0.78]
↓ 每段独立量化
量化编码: 3 7 2 5 ← 原来8个数变成4个数字
为什么叫"乘积"量化:
- 把向量分成 M 段(通常 M=8 或 16)
- 每段独立做 K-means 聚类(比如 K=256)
- 存储时只存每段所属的聚类 ID,而非原始向量
压缩效果:
| 向量维度 | 原始大小 | PQ 压缩后 | 压缩比 |
|---|---|---|---|
| 1536 维 | 6KB | 0.75KB | 8x |
| 3072 维 | 12KB | 1KB | 12x |
缺点:有精度损失,约 2-5%
适用场景:内存受限场景,如边缘设备、大规模数据集
索引算法对比
| 算法 | 查询速度 | 精度 | 内存占用 | 适用规模 | 核心原理 |
|---|---|---|---|---|---|
| HNSW | ★★★★★ | ★★★★★ | ★★★☆☆ | 10亿+ | 图导航 |
| IVF | ★★★☆☆ | ★★★★☆ | ★★☆☆☆ | 1亿 | 聚类 |
| LSH | ★★★☆☆ | ★★☆☆☆ | ★★★★☆ | 1000万 | 哈希碰撞 |
| PQ | ★★★★★ | ★★★☆☆ | ★★★★★ | 10亿+ | 向量压缩 |
实际选择建议:
- 通用场景 → HNSW(Pinecone、Milvus 默认)
- 内存受限 → PQ 或 HNSW+QP
- 需要精确结果 → HNSW + 增大搜索参数
- 快速原型 → IVF(实现简单)
实际使用中的索引参数
Pinecone 的索引配置
import pinecone
# 创建索引时指定索引类型和参数
pinecone.create_index(
name="my-index",
dimension=1536,
metric="cosine", # 或 "euclidean", "dotproduct"
spec={
"serverless": {
"cloud": "aws",
"region": "us-west-2"
}
}
)
Pinecone 内部自动选择最优索引算法(HNSW 变体),你只需关注:
- dimension:向量维度(必须和嵌入模型输出一致)
- metric:距离度量方式(影响相似度计算)
- pod 类型:影响性能和成本
调整搜索精度 vs 速度
# 查询时控制搜索范围
query_results = index.query(
vector=query_vector,
top_k=10,
include_metadata=True,
# 精度调优参数(不同数据库 API 不同)
ef=200 # HNSW 的 ef 参数,越大越精准但越慢
)
索引构建的最佳实践
1. 批量导入效率高
# 低效:逐条插入
for item in items:
index.upsert([(id, vector)])
# 高效:批量插入
batch_size = 1000
for i in range(0, len(items), batch_size):
batch = items[i:i+batch_size]
vectors = [(item.id, item.vector) for item in batch]
index.upsert(vectors)
2. 索引构建时机
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 预先构建 | 数据全部导入后统一建索引 | 一次性导入 |
| 增量更新 | 新数据来了实时更新索引 | 持续数据流入 |
| 混合策略 | 定期全量重建 + 实时增量更新 | 生产环境 |
3. 索引监控指标
- 查询延迟(P99):95% 的查询应该在 X ms 内完成
- 召回率:ANN 结果与暴力搜索结果的比值,越接近 1 越好
- 索引大小:索引占用的内存/磁盘空间
常见问题
Q:索引会影响查询结果的准确性吗?
A:ANN 搜索返回的是"近似最优"而非"精确最优"。但实际上:
- 召回率通常在 95-99%,即 100 个最近邻中漏掉 1-5 个
- 对于大多数应用(推荐、搜索),用户感知不到差异
- 如果必须 100% 精确,只能用暴力搜索,但延迟会高 50-100 倍
Q:HNSW 的 M 参数越大越好吗?
A:不是,需要权衡:
- M↑ → 精度↑,但内存↑、建索引时间↑
- 建议值:M=16-64,根据数据规模和延迟要求调整
- 小规模数据(<10万)可以不用 HNSW,直接暴力搜索也很快
Q:如何选择距离度量?
| 度量 | 适用场景 | 特点 |
|---|---|---|
| 余弦相似度 | 文本嵌入(方向比长度重要) | 值域 [-1, 1],0 表示正交 |
| 点积 | 归一化向量、推荐系统 | 值域 [-1, 1](归一化后) |
| 欧氏距离 | 图像嵌入、坐标数据 | 值域 [0, +∞),物理距离 |
经验法则:如果你用了 OpenAI 或 Cohere 的嵌入模型,选择 余弦相似度。
总结
向量索引是将"大海捞针"变成"按图索骥"的关键技术:
- HNSW 是当前最流行的算法,综合性能最好
- ANN 搜索牺牲 1-5% 精度换取 10-100 倍性能提升
- PQ 量化是内存受限场景的首选
- 实际使用中,优先用云服务的默认配置,有性能问题再调参
Similarity Search
什么是相似度搜索?
相似度搜索(Similarity Search)是在高维向量空间中找到与查询向量最相似的结果的过程。与传统数据库的精确匹配不同,它返回的是"最接近"的若干结果,按相似度排序。
一句话理解
把 1000 万张图片转化为一千万个点保存在空间中,查询时:
- 输入一张"猫"的图片 → 转换为向量 → 在空间中找距离最近的点 → 返回"相似猫图片"
搜索的三步骤
1. 向量化:文本/图像 → 嵌入模型 → 高维向量
2. 索引化:向量 → 索引结构(HNSW/IVF 等)
3. 搜索:查询向量 → 计算距离 → 返回最近邻
向量表示:如何把万物变成向量?
核心思想
将现实世界的对象(文本、图像、音频)转换为连续的数值向量,语义相似的内容在向量空间中距离更近。
常见嵌入模型
| 模型 | 用途 | 输出维度 | 特点 |
|---|---|---|---|
| Word2Vec | 词嵌入 | 100-300 维 | 捕捉词的语义关系,“king - man + woman ≈ queen” |
| GloVE | 词嵌入 | 50-300 维 | 融合全局统计信息 |
| Universal Sentence Encoder (USE) | 句子嵌入 | 512 维 | 整句语义,而非单词叠加 |
| CNN (如 VGG) | 图像嵌入 | 数百-数千维 | 提取视觉特征 |
嵌入空间可视化
生物相关
↑
🐱 猫 🐕 狗 🐴 马
↗ ↘
🍎 苹果 🍊 橙子
↖ 食物相关 ↓
🍞 面包
语义相似的词在向量空间中聚集在一起,形成"语义簇"。
距离度量:如何衡量"相似"?
距离度量是相似度搜索的核心,决定了如何计算向量之间的"远近"。
1. 欧几里得距离(Euclidean Distance)
几何直觉:直线距离,像用尺子测量两点间的长度。
B(4,4)
/|
/ |
/ |
A(1,1)
d = √[(4-1)² + (4-1)²] = √18 ≈ 4.24
公式:
d(A,B) = √[Σ(ai - bi)²]
适用场景:
- 密集连续数据(如图像特征)
- 物理距离有意义的场景
- 低维或中等维数据(<100维)
局限性:高维空间中"维度灾难"问题,距离趋于相近
2. 曼哈顿距离(Manhattan Distance / L1)
几何直觉:像在城市中开车,只能沿格子路走,不能直穿大楼。
B(4,4)
/|
/ |
/ |
A(1,1)
d = |4-1| + |4-1| = 3 + 3 = 6
公式:
d(A,B) = Σ|ai - bi|
适用场景:
- 网格状数据(如棋盘移动)
- 对异常值更鲁棒
- 特征尺度不同的场景
3. 余弦相似度(Cosine Similarity)
几何直觉:不看向量长度,只看方向。两个向量方向越接近,相似度越高。
A
↗
↗ 60°
↗______ B
cos(θ) = (A·B) / (|A|·|B|)
公式:
cos(A,B) = (A·B) / (|A|·|B|)
值域:[-1, 1]
1:完全相同方向0:正交(无关联)-1:完全相反
为什么文本搜索首选余弦相似度?
- 文本向量维度高且稀疏(很多 0)
- 关注"词出现与否"而非"词出现多少次"
- 归一化后长度不影响结果
4. 切比雪夫距离(Chebyshev Distance)
几何直觉:像象棋中的"王",可以横竖斜走,取最大移动次数。
公式:
d(A,B) = max(|ai - bi|)
适用场景:
- 棋盘类游戏
- 允许对角线移动的网格导航
距离度量对比与选择
| 度量 | 公式 | 值域 | 适用数据 | 特点 |
|---|---|---|---|---|
| 欧几里得 | √[Σ(ai-bi)²] | [0, +∞) | 密集连续、图像 | 直观,但高维效果差 |
| 曼哈顿 | Σ | ai-bi | [0, +∞) | |
| 余弦 | (A·B)/(|A||B|) | [-1, 1] | 文本、高维稀疏 | 只看方向不看长度 |
| 切比雪夫 | max|ai-bi| | [0, +∞) | 棋盘、网格导航 | 考虑最极端差异 |
选择决策树
数据特点?
├── 高维稀疏(文本)→ 余弦相似度 ✓
├── 密集连续(图像特征)→ 欧几里得距离 ✓
├── 网格/坐标系 → 曼哈顿距离 ✓
└── 游戏/网格导航 → 切比雪夫距离 ✓
搜索算法:如何高效找到最近邻?
k-NN(k-Nearest Neighbors)精确搜索
原理:计算查询向量与所有向量的距离,返回最近的 K 个。
查询向量 Q:
与所有点计算距离 → 排序 → 返回最近的 3 个
Q 到 A: 0.85
Q 到 B: 0.12 ← K=3 结果
Q 到 C: 0.23 ← K=3 结果
Q 到 D: 0.31 ← K=3 结果
Q 到 E: 0.67
优点:100% 精确
缺点:O(N) 复杂度,百万级数据太慢
适用规模:<10 万向量
ANN(近似最近邻)搜索
原理:牺牲少量精度换取 10-100 倍速度提升。
与 k-NN 的对比:
| 特性 | k-NN | ANN |
|---|---|---|
| 精度 | 100% | 95-99% |
| 速度 | 慢 | 快 |
| 内存 | 低 | 较高 |
| 数据规模 | <10万 | 10万-10亿+ |
常见 ANN 算法:HNSW、IVF、LSH(详见 Indexing 章节)
实际应用场景
1. 电商推荐
用户浏览:Nike Air Max 运动鞋
↓ 向量化
向量空间中找到相似向量
↓
返回:Nike Air Jordan、Adidas Ultra Boost、Puma RS-X
2. 图像搜索
上传图片 → CNN 提取特征向量 → 在向量空间中搜索 → 返回相似图片
3. 文档检索
搜索"机器学习教程"
↓ USE 编码
找到语义最相似的文档(即使没用"机器学习"这个词)
4. 欺诈检测
新交易 → 转换为特征向量 → 找最近的历史交易
↓
相似异常交易多 → 高风险
实战代码示例
使用 FAISS 进行相似度搜索
import numpy as np
from faiss import IndexFlatL2
# 创建 10000 个 128 维的随机向量(模拟嵌入)
dimension = 128
num_vectors = 10000
vectors = np.random.random((num_vectors, dimension)).astype('float32')
# 建立索引(使用欧几里得距离的暴力搜索)
index = IndexFlatL2(dimension)
index.add(vectors)
# 查询:找最相似的 5 个
query = np.random.random((1, dimension)).astype('float32')
distances, indices = index.search(query, k=5)
print(f"最相似的 5 个向量索引: {indices}")
print(f"对应的距离: {distances}")
使用余弦相似度
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
# 两个句子的嵌入向量(简化示例)
sentence1 = np.array([[0.1, 0.8, 0.3, 0.5]])
sentence2 = np.array([[0.2, 0.7, 0.2, 0.6]])
similarity = cosine_similarity(sentence1, sentence2)
print(f"余弦相似度: {similarity[0][0]:.4f}")
常见问题
Q:余弦相似度和点积有什么区别?
| 特性 | 余弦相似度 | 点积 |
|---|---|---|
| 归一化 | 是(除以向量长度) | 否 |
| 值域 | [-1, 1] | 无界 |
| 对长度敏感 | 不敏感 | 敏感 |
如果向量已经归一化,余弦相似度 ≈ 点积。
Q:高维空间中欧几里得距离为什么失效?
维度灾难:随着维度增加,所有点之间的距离趋于相等。
100 维空间中随机向量:
- 最小距离 ≈ 最大距离的 0.7 倍
- 有效区分度大大降低
解决方案:降维(PCA/UMAP)或使用对高维更鲁棒的度量(余弦相似度)。
Q:如何选择 K 值?
| K 值 | 特点 | 适用场景 |
|---|---|---|
| K=1 | 最近邻,易受噪声影响 | 快速匹配、异常检测 |
| K=5~10 | 平衡精确度和稳定性 | 通用推荐 |
| K=20+ | 稳定,但可能过于宽泛 | 聚类、分类 |
经验:从 K=10 开始,根据效果调整。
总结
- 向量表示是基础:Embedding 模型决定了你能否捕捉到"语义相似性"
- 距离度量是核心:余弦适合文本,欧几里得适合图像
- 搜索算法是效率:数据量大时必须用 ANN
- 选择原则:
- 文本/推荐 → 余弦相似度
- 图像/特征 → 欧几里得距离
- 大规模数据 → HNSW 索引 + ANN 搜索
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)