向量数据库基础和实践 (Faiss)
向量数据库:AI时代的数据基础设施
向量数据库是一种专门用于存储、索引和查询向量数据的数据库系统。在人工智能和机器学习飞速发展的今天,向量数据库正成为处理非结构化数据的核心基础设施。
是什么
向量数据库是一种新型数据库,它存储的数据不是传统的结构化表格,而是高维向量。这些向量通常是通过深度学习模型将非结构化数据(如文本、图像、音频、视频等)转换而来的数值表示。
与传统关系型数据库不同,向量数据库的核心功能不是基于精确匹配的查询,而是基于相似度匹配。它能够快速地从海量向量中找出与查询向量最相似的向量,这使得它在处理复杂的非结构化数据时具有独特的优势。
原理
向量数据库的核心原理包括以下几个方面:
1. 向量表示
任何类型的数据都可以通过深度学习模型转换为向量。例如:
- 文本可以通过Word2Vec、BERT等模型转换为向量
- 图像可以通过ResNet、VGG等模型转换为向量
- 音频可以通过MFCC、WaveNet等模型转换为向量
这些向量捕获了数据的语义信息,使得相似的数据具有相似的向量表示。
2. 相似度计算
向量数据库使用各种距离度量来计算向量之间的相似度,常用的包括:
- 欧几里得距离(Euclidean Distance):衡量向量空间中两点间的直线距离
- 余弦相似度(Cosine Similarity):衡量两个向量之间的夹角余弦值,不受向量长度影响
- 曼哈顿距离(Manhattan Distance):衡量向量空间中两点间的折线距离
- 汉明距离(Hamming Distance):衡量两个等长向量对应位置不同元素的数量
其中,余弦相似度在处理高维数据时表现尤为出色,因为它不受向量长度的影响。
相似度计算示例
为了更直观地理解这些相似度度量方法,我们以两个简单向量为例进行计算:
- 向量 A = [1, 2, 3]
- 向量 B = [4, 5, 6]
1. 欧氏距离
欧氏距离是向量空间中两点间的直线距离,计算公式为:
d(A,B)=∑i=1n(Ai−Bi)2 d(A,B) = \sqrt{\sum_{i=1}^{n} (A_i - B_i)^2} d(A,B)=i=1∑n(Ai−Bi)2
对于向量 A 和 B:
d(A,B)=(1−4)2+(2−5)2+(3−6)2=9+9+9=27≈5.196 d(A,B) = \sqrt{(1-4)^2 + (2-5)^2 + (3-6)^2} = \sqrt{9 + 9 + 9} = \sqrt{27} \approx 5.196 d(A,B)=(1−4)2+(2−5)2+(3−6)2=9+9+9=27≈5.196
2. 点积
点积(内积)是两个向量对应元素相乘之和,计算公式为:
[A⋅B=∑i=1nAi×Bi][ A \cdot B = \sum_{i=1}^{n} A_i \times B_i ][A⋅B=i=1∑nAi×Bi]
对于向量 A 和 B:
A⋅B=1×4+2×5+3×6=4+10+18=32 A \cdot B = 1\times4 + 2\times5 + 3\times6 = 4 + 10 + 18 = 32 A⋅B=1×4+2×5+3×6=4+10+18=32
点积值越大,通常表示向量在相同方向上的分量越多,但它受向量长度影响较大。
3. 余弦相似度
余弦相似度衡量两个向量之间的夹角余弦值,计算公式为:
cos(θ)=A⋅B∥A∥×∥B∥ \cos(\theta) = \frac{A \cdot B}{\|A\| \times \|B\|} cos(θ)=∥A∥×∥B∥A⋅B
其中,(|A|) 和 (|B|) 分别是向量 A 和 B 的模长(欧氏距离)。
对于向量 A 和 B:
- ∥A∥=12+22+32=14≈3.7417\|A\| = \sqrt{1^2 + 2^2 + 3^2} = \sqrt{14} \approx 3.7417∥A∥=12+22+32=14≈3.7417
- ∥B∥=42+52+62=77≈8.775\|B\| = \sqrt{4^2 + 5^2 + 6^2} = \sqrt{77} \approx 8.775∥B∥=42+52+62=77≈8.775
- cos(θ)=323.7417×8.775≈3232.83≈0.9747\cos(\theta) = \frac{32}{3.7417 \times 8.775} \approx \frac{32}{32.83} \approx 0.9747cos(θ)=3.7417×8.77532≈32.8332≈0.9747
余弦相似度接近1,表示两个向量方向非常相似;接近0,表示方向正交;接近-1,表示方向相反。
向量数据库的核心流程
- 输入:一个查询向量
- 输出:最相似的几个向量
最近邻搜索问题
最近邻搜索(Nearest Neighbor,NN)是向量数据库的核心问题,涉及多种算法:
- 暴力搜索(Brute Force):最精准但耗时长,适用于小规模数据
- 聚类算法:聚类算法生成的簇中心可以作为近似最近邻搜索的参考点,以快速定位相似的对象
- 近似最近邻(Approximate Nearest Neighbor,ANN):通过牺牲部分精度来换取显著的速度提升
- 局部敏感哈希(Locality Sensitive Hashing,LSH):使用哈希函数使相似向量更容易碰撞到同一桶中
- 分层导航小世界 (Hierarchical Navigable Small World Graphs) HNSW:构建一个多层图连接点到邻居,实现快速导航到近似邻居
这些算法在搜索质量和搜索时间之间做出了不同的权衡。
3. 索引结构
为了实现高效的相似度查询,向量数据库采用了各种专门的索引结构,主要包括:
- FLAT索引:简单暴力的全量扫描,适用于小规模数据
- IVF索引(倒排文件):将向量空间划分为多个聚类,查询时先找到最近的聚类,再在该聚类内搜索
- HNSW索引(分层导航小世界):通过构建多层图结构加速查询
- PQ索引(乘积量化):通过量化向量降低存储空间和计算复杂度
应用场景
以图搜图 视频推荐等等
向量数据库在以下场景中发挥着重要作用:
1. 推荐系统
向量数据库能够高效地计算用户兴趣向量与物品向量之间的相似度,从而实现精准推荐。例如:
- 电商平台的商品推荐
- 视频平台的内容推荐
- 音乐平台的歌曲推荐
2. 自然语言处理
向量数据库在NLP领域有着广泛应用:
- 语义搜索:根据查询的语义而非关键词匹配文档
- 问答系统:快速找到与问题相关的上下文信息
- 文本聚类:将相似主题的文本聚在一起
3. 计算机视觉
- 图像检索:根据图像内容而非标签查找相似图像
- 人脸识别:快速识别和比对人脸特征
- 物体检测:识别图像中的物体并分类
4. 多模态数据处理
向量数据库能够处理不同类型的数据(文本、图像、音频等),实现跨模态的相似度查询。例如,根据图像搜索相关的文本描述,或根据文本描述搜索相关的图像。
5. 大模型增强(RAG)
检索增强生成(RAG)是向量数据库的重要应用场景,它通过检索相关文档来增强大语言模型的生成能力,解决了大模型的知识时效性和幻觉问题。
对比
与传统关系型数据库对比
特性 | 向量数据库 | 关系型数据库 |
---|---|---|
数据类型 | 高维向量 | 结构化数据 |
查询方式 | 相似度匹配 | 精确匹配 |
索引结构 | 专门的向量索引(IVF、HNSW等) | B+树、哈希索引等 |
主要应用 | 非结构化数据处理、AI应用 | 事务处理、报表分析 |
主流向量数据库产品对比
产品 | 特点 | 优势 | 劣势 |
---|---|---|---|
Faiss | 开源、高效、支持多种索引 | 性能出色、内存占用低 | 不支持分布式部署 |
Milvus | 开源、分布式、支持多种索引 | 可扩展性强、支持云原生 | 资源消耗较大 |
Pinecone | 闭源、托管服务 | 开箱即用、无需维护 | 成本较高 |
Qdrant | 开源、轻量级、支持过滤条件 | 安装简单、查询速度快 | 生态相对薄弱 |
Chroma | 开源、轻量级、专注于开发体验 | 简单易用、内存占用低、集成度高 | 不支持分布式部署、高级功能较少 |
实践
使用 faiss 存储向量并且进行向量检索
FAISS(Facebook AI Similarity Search)是由Facebook人工智能研究院开发的高效 相似度搜索和聚类 库,专为处理大规模高维向量数据设计。
核心特点
- 高效检索 :提供多种索引类型(如 Flat 、 IVF 、 HNSW 等),支持精确搜索和近似搜索(ANN),平衡搜索速度与精度。
- 大规模处理 :可轻松处理数十亿级别的高维向量(如10亿+ 128维向量)。
- 硬件加速 :支持CPU和GPU加速(通过CUDA),显著提升检索性能。
- 多语言支持 :提供Python和C++接口,易于集成到不同应用中。
'''
相似性搜索'''
import os
import timeimport faiss
import numpy as npnp.random.seed(42) # 保证多次运行结果一致# 1. 索引创建
def test01():dim = 10data = np.random.rand(10000, dim)idx_name = f"dim_{dim}_datalen_{len(data)}.faiss"# dim_1000_datalen_100000 500ms生成向量时间降到了 150 ms 数据占用 380MBt1 = time.time()if not os.path.exists(idx_name):# index = faiss.IndexFlatL2(dim) # 使用欧式距离计算相似度index = faiss.IndexFlatIP(dim) # 使用点积计算相似度# index = faiss.index_factory(dim, "Flat", faiss.METRIC_L2)# index = faiss.index_factory(dim, "Flat", faiss.METRIC_INNER_PRODUCT)# 添加向量index.add(data)faiss.write_index(index, idx_name)else:index = faiss.read_index(idx_name)t2 = time.time()print('index time:', t2 - t1)# 搜索向量topK= 1query_vectors = np.random.rand(1, dim)distances, idx = index.search(query_vectors, k=topK)print(distances) # D 就是 点积 或者是 欧氏距离,I 就是搜索结果对应的IDprint("=======================")print(idx) # D 就是 点积 或者是 欧氏距离,I 就是搜索结果对应的IDresV = data[idx][0,0]print(resV)# 计算两个向量的点积print("=======================")dis = np.sum(query_vectors * resV, axis=1) # 都是 dim*1 的向量print(dis)print("=======================")# 查询最近似向量的IDI = index.assign(query_vectors, k=topK)print(I)# 重建指定位置向量,并不是所有索引都支持该函数# print(index.reconstruct(0))# # 删除指定 ID 数据# index.remove_ids(np.array([1, 2, 3]))# print(index.ntotal)## # 删除所有向量数据# index.reset()# print(index.ntotal)if __name__ == '__main__':test01()
如何加速向量检索
'''IndexFlat 索引是一种基于线性搜索的索引,它通过逐个计算与每个向量的相似度来进行搜索。在数据量较大的时候,搜索效率会较低。
此时,我们可以使用 IndexIVFFlat 索引来提升搜索效率。
它的原理如下:对于所有的向量进行聚类,相当于把所有的数据进行分类。当进行查询时,在最相似的N 个簇中进行线性搜索。
这就减少了需要进行相似度计算的数据量,从而提升搜索效率。time: 0.0645294189453125
[[28.383003 29.018719]] [[ 54513 682317]]
time: 0.013531208038330078
[[28.383003 29.018719]] [[ 54513 682317]]搜索簇 数量减少 精度下降,但是 速度提升
这种方法是一种在查询的精度和效率之间平衡的方法
time: 0.06836390495300293
[[28.383003 29.018719]] [[ 54513 682317]]
time: 0.0030002593994140625
[[28.383003 30.071785]] [[ 54513 597587]]'''
import faiss
import numpy as np
import timenp.random.seed(42)
data = np.random.rand(1000000, 256)
ids = np.arange(0, 1000000)
query_vector = np.random.rand(1, 256)def test01():index = faiss.IndexFlatL2(256)index = faiss.IndexIDMap(index)# 添加向量index.add_with_ids(data, ids)# 搜索向量s = time.time()D, I = index.search(query_vector, k=2)print('time:', time.time() - s)print(D, I)def test02():# 第一个参数:量化参数# 第二个参数:向量维度# 第三个参数:质心数量quantizer = faiss.IndexFlatL2(256)index = faiss.IndexIVFFlat(quantizer, 256, 100) # 100 个质心# 增加搜索质心数量可以提高精确度,但是需要更多的时间# 默认为1 index.nprobe = 1 表示在搜索过程中只考虑一个聚类中心。# 这意味着搜索会在与查询向量最相似的一个聚类中进行线性搜索。这种方法可以显著提高搜索速度,但可能会牺牲一些精度。# 10可以 5 不行index.nprobe = 10# 聚类计算质心index.train(data)# 添加向量index.add_with_ids(data, ids)# 搜索向量s = time.time()D, I = index.search(query_vector, k=2) # ANN近似最近邻print('time:', time.time() - s)print(D, I)if __name__ == '__main__':test01() # 暴力搜索test02() # 聚类之后再搜索