DeepSeek本地RAG知识库(17):向量数据库原理讲解(小白也能懂)
大家好!我是程序员寒山。
近日很多小伙伴问,为什么要用向量数据库?它到底是怎么存储数据的?
今天就给大家揭秘一下向量数据库,看看它有什么神奇的地方?
一、什么是向量数据?——菜谱变"坐标"
说向量数据库前,首先要明白什么是向量数据,因为向量数据库的核心是存储向量数据!
举个例子:想象我们在学做一道菜:番茄炒蛋,我们把准备阶段分三步:每一步都转化为空间三维坐标:
"番茄切块" → [0.7, -0.2, 1.3]
"打散鸡蛋" → [0.5, 1.1, -0.8]
"热锅放油" → [-1.2, 0.4, 0.6]
[0.7, -0.2, 1.3]这些就是向量数据!
每个步骤都被嵌入模型(Embedding)翻译成机器能懂的"位置坐标"(向量化)。就像把"番茄炒蛋步骤"变成地图上的定位点,方便快速查找。
实例应用:当你说"炒蛋前需要准备什么?",嵌入模型会把它转换成类似[0.3, 1.2, -0.5]的坐标,然后在"菜谱地图"上找最近的坐标点。
这就是向量数据库的一个简单的使用,[0.7, -0.2, 1.3]里面的数量就是维度,768维就是有768个数。
二、 关系型数据库怎么存储的
我们先来看看在我们常用的mysql数据库中,是怎么存数据的。
比如:想象你有一个纸质菜谱本,每页记录:
- 菜名:宫保鸡丁
- 类型:川菜
- 配料:鸡胸肉300g、花生米50g...
- 步骤:1.鸡肉切丁 2.调制酱汁...
我们可以创建一个名为 recipes 的表来存储菜谱信息,表中包含以下字段:
- id:自增的主键,用于唯一标识每条记录。
- dish_name:菜名,使用 VARCHAR 类型存储。
- dish_type:菜系类型,同样使用 VARCHAR 类型。
- ingredients:配料信息,由于内容可能较长,使用 TEXT 类型。
- steps:烹饪步骤,也使用 TEXT 类型。
通过下面的数据库建表SQL语句,就可以创建这个表了。
CREATE TABLE IF NOT EXISTS recipes (
id INT AUTO_INCREMENT PRIMARY KEY,
dish_name VARCHAR(255) NOT NULL,
dish_type VARCHAR(255) NOT NULL,
ingredients TEXT NOT NULL,
steps TEXT NOT NULL
);
当我们想查询的时候,我们用SQL查询时就像查字典:
SELECT * FROM recipes WHERE dish_type = '川菜' AND ingredients LIKE '%花生%'
type川菜为精确匹配,配料花生为模糊匹配。
可以精确返回配料中还要花生的所有川菜条目。
这就是典型的关系型数据库(如MySQL)的存储和查询方式。
但当我们想找"制作简单、口感酸甜的鸡肉料理"时,传统数据库就束手无策了——这正是向量数据库的战场。
三、关系型数据库是否可以存向量数据
那么难道关系型数据库就不能存向量数据么?答案是否定的,可以把向量数据以数组等形式存在数据库里面,如下面:
方法:利用数据库支持的数组或JSON类型(如PostgreSQL)。
-- PostgreSQL示例
CREATE TABLE vectors (
id SERIAL PRIMARY KEY,
embedding FLOAT[]
);
- 优点: 灵活支持任意维度的向量。 存储结构简单,高维向量查询性能仍需优化。。
- 缺点: 无法直接索引或计算:需在应用层解析数据后再处理。 计算相似度时性能低(需全表扫描并计算)。
当然现在也推出了一些插件扩展支持查询(使用pgvector):
-- 查找与目标向量最相似的10条数据(按欧氏距离排序)
SELECT * FROM items
ORDER BY embedding <-> '[0.1, 0.2, ..., 0.5]'::VECTOR
LIMIT 10;
当然这种关系数据库的向量搜索性能远不如专用向量数据库(如Milvus)。超过百万级数据时,性能问题会是关键。
通过合理选择存储方式和扩展工具,关系数据库可以有效地支持部分向量数据的存储和查询需求。
四、向量数据的由来
4.1 什么是向量数据?
向量数据是一种将对象(如文本、图像、菜谱等)转换为数值形式的数据。每个对象被表示为一个向量(即一组有序的数值),其中每个数值代表一个特征(Feature)。
向量化后可以通过GPU进行高效地处理和分析数据。
4.2 菜谱的向量化
那么我的菜谱数据到底是怎么向量化的呢?我们来看看这个实例
假设我们有3道菜谱:麻婆豆腐、番茄炒蛋、清蒸鲈鱼。我们可以为每道菜定义以下特征(维度):
特征1:菜的基本信息,辣不辣、咸淡如何、要等多久、是荤是素,我们平时到饭店点菜是不是也是这样,先问服务员的也是这4个问题呢。
菜谱 | 辣度 | 咸度 | 烹饪时间 | 是否含肉类 |
麻婆豆腐 | 8 | 7 | 30 | 0 |
番茄炒蛋 | 0 | 5 | 10 | 0 |
清蒸鲈鱼 | 1 | 6 | 25 | 1 |
现在我们对这4个特征进行向量化,得到4个维度,每道菜谱对应一个4维向量:
- 麻婆豆腐:[8, 7, 30, 0]
- 番茄炒蛋:[0, 5, 10, 0]
- 清蒸鲈鱼:[1, 6, 25, 1]
特征2:菜系种类
比如我们的三道茶的种类如下:
菜谱名称 | 菜系种类 |
麻婆豆腐 | 川菜 |
清蒸鲈鱼 | 粤菜 |
寿司 | 日料 |
因为这个特征非常明确简单,我们可以使用我们前面想嵌入模型的时候讲到的独热编码算法实现:
- 菜系类别:["川菜", "粤菜", "日料", "西餐"]
- 向量化结果:
- 麻婆豆腐(川菜)→ [1, 0, 0, 0]
- 清蒸鲈鱼(粤菜)→ [0, 1, 0, 0]
- 寿司(日料)→ [0, 0, 1, 0]
这样对菜品种类,我们就又生成了4个维度的向量数据。
特征3:食材列表
还是我们的菜品,实例数据如下:
菜谱名称 | 食材列表 |
麻婆豆腐 | 豆腐、牛肉末、辣椒、豆瓣酱 |
番茄炒蛋 | 番茄、鸡蛋、盐、糖 |
通过我们的嵌入模型,就可以实现向量化。
- 食材集合:["豆腐", "牛肉末", "辣椒", "豆瓣酱", "番茄", "鸡蛋", "盐", "糖"]
- 统计食材在全部菜谱中的频率,赋予权重(如“牛肉末”可能比“盐”更具区分度):
- 麻婆豆腐 → [0.8, 0.9, 0.7, 0.6, 0, 0, 0.1, 0.1]
- 番茄炒蛋 → [0, 0, 0, 0, 0.9, 0.8, 0.2, 0.3]
我们有8种素材,所以我们这里就得到一个8维的食材向量数据,随着食材的增多,维度也会增加的。
特征4:烹饪过程
过程数据一般是分步操作数据或者是一段描述文字,如果是我们的文档分段,就和我们的每一段内容类似。
菜谱名称 | 烹饪过程(简化版) |
麻婆豆腐 | 1. 炒香豆瓣酱;2. 加入牛肉末翻炒;3. 加豆腐炖煮10分钟 |
番茄炒蛋 | 1. 炒鸡蛋至凝固;2. 加入番茄翻炒;3. 加盐和糖调味 |
这内容的向量化不能像前面的那些直接向量化,需要特征提取。
可以进行关键词提取:
- 分词后统计高频动作词(如“炒”“炖煮”“翻炒”“调味”“过油”):
- 麻婆豆腐 → {"炒": 0.6, "炖煮": 0.8, "翻炒": 0.5, "调味": 0.3,"过油": 0.0}
- 番茄炒蛋 → {"炒": 0.9, "翻炒": 0.7, "调味": 0.6, "调味": 0.3,"过油": 0.0}
可以进行词嵌入(Word2Vec)操作,
- 将每个步骤转换为语义向量(如每个词映射为100维向量):
- 麻婆豆腐 → 平均词向量:[0.2, -0.3, 0.7, ..., 0.1](100维)
- 番茄炒蛋 → 平均词向量:[0.4, 0.1, -0.2, ..., 0.5]
总特征:
我们把上面的所有特征合起来,就是是菜谱的完整向量,
假设特征维度如下:
- 基础特征:辣度、咸度、时间、是否含肉 → 4维
- 菜系种类:4种菜系 → 4维
- 食 材:8种食材 → 8维
- 烹饪过程:5个高频动词 → 5维
比如:麻婆豆腐的完整向量:
[8, 7, 30, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0.6, 0.8, 0.5, 0.3, 0.0]
(总维度:4+4+8+5 = 21维)
好了,这就是我们的向量数据的由来。
从这里可以看出我们前面说的,虽然向量数据库的主要是存储我们的非结构化数据,但是如果是做了部分或者大致的结构化,那么向量检索也就更加准确,非结构化并不是完全的随意数据,是有限度的非结构化,也可以说是有限度的结构化。
五、向量数据库的使用
我们以如何构建智能菜谱库为例,使用Milvus数据库,来给大家演示一下是如何使用的。
5.1 首先介绍几个向量数据库的概念:
- 集合(Collection):相当于数据库的表,每个集合里面有很多条数据,包含多个属性、字段,存储与向量相关的文本描述。
- 分区(Partition):是集合内数据的逻辑划分,用于提高数据管理和查询效率。每个分区包含一部分向量数据,可以基于特定的条件进行划分。
- 实体(Entity): 集合中的一条数据记录,包含多个属性。
- 属性(Attribute): 实体中的具体字段,如ID、描述、向量等。
- 索引(Index):加速搜索的目录系统
- 向量数据:高维浮点数数组,用于表示实体的特征,适用于相似性搜索。
- 标量数据: 如整数、字符串等,用于存储实体的属性信息,用于过滤和描述实体。
5.2 数据建模
假设我们的菜谱结构如下:
from pymilvus import connections, FieldSchema, CollectionSchema, DataType
recipe_schema = CollectionSchema(
fields=[
FieldSchema("id", DataType.INT64, is_primary=True),
FieldSchema("name", DataType.VARCHAR, max_length=100),
FieldSchema("type", DataType.VARCHAR, max_length=20),
FieldSchema("ingredients", DataType.VARCHAR, max_length=500),
FieldSchema("steps", DataType.VARCHAR, max_length=2000),
FieldSchema("vector", DataType.FLOAT_VECTOR, dim=768)
],
description="智能菜谱库"
)
5.3 完整代码演示
# 连接Milvus
connections.connect(alias="default", host='localhost', port='19530')
# 创建集合
from pymilvus import utility, Collection
collection = Collection("recipes", recipe_schema)
# 插入数据
recipes = [
{
"id": 1,
"name": "宫保鸡丁",
"type": "川菜",
"ingredients": "鸡胸肉、花生、干辣椒...",
"steps": "1.鸡肉切丁...",
"vector": [0.23, 0.76, ..., 0.89] # 768维向量
},
# 更多食谱...
]
# 创建索引
index_params = {
"metric_type": "L2", # 使用欧氏距离
"index_type": "IVF_FLAT", # 选择索引类型
"params": {"nlist": 128} # 将数据分为128个聚类
}
collection.create_index("vector", index_params)
# 设置搜索参数
search_params = {
"metric_type": "L2", # 使用欧氏距离
"params": {"nprobe": 10} # 搜索10个最近的聚类
}
# 搜索示例
results = collection.search(
vectors=[[0.25, 0.72, ..., 0.85]], # 用户输入向量
anns_field="vector",
param=search_params,
limit=3, # 返回前3个结果
output_fields=["name", "type"] # 返回的字段
)
# 预期输出
for hit in results[0]:
print(f"菜名:{hit.entity.get('name')}, 相似度:{hit.distance:.4f}")
六、向量数据库优缺点对比
优点:
- 语义理解:
能明白"煎蛋"和"炒蛋"是近亲,就像知道"西红柿"和"番茄"是同一种食材。
- 模糊搜索:
即使记不清菜名也能找到,你可以说"那个红红黄黄的菜..." → 准确找到番茄炒蛋
- 多模态融合:
当使用多模态的嵌入模型后,是可以支持图文音视频统一处理的。
缺点:
- 硬件要求高:
普通电磁炉(CPU)做不出专业灶台(GPU)的效果
- 冷启动难:
需要先准备和积累足够数据
- 黑箱问题:
有时会莫名混搭,想要番茄炒蛋,却给出西红柿炖牛腩的步骤也是有可能的。
五、常见问题QA
Q1:为什么不用MySQL直接存向量?
- 传统数据库的索引不适合高维数据
- 相似度搜索效率低
Q2:如何处理不断更新的菜谱?
- 动态数据:先插入数据,后建索引
- 定期重建索引
Q3:向量维度是不是越高越好?
维度数 | 精度 | 性能 | 适用场景 |
128 | 一般 | 快 | 移动端应用 |
768 | 高 | 中等 | 通用场景 |
1024 | 很高 | 较慢 | 专业检索 |
Q4:如何保证搜索准确率?
使用混合查询:
# 结合向量搜索和标量过滤
search_params = {
"expr": "type == '川菜'",
"anns_field": "vector",
# ...其他参数
}
现在你是不是已经了解了什么是向量数据库和它的工作原理和过程呢!
快去动手试试吧!