告别关键词!PostgreSQL+pgvector 玩转语义和图像检索
在 AI 驱动的语义检索时代,传统关键词搜索已难以满足“理解内容”的需求。pgvector 作为 PostgreSQL 的向量扩展,让数据库具备了高效的向量存储与检索能力,无需额外部署独立向量数据库,极大简化了架构。本文结合实战场景,从环境搭建、文本转向量模型选型(BERT vs BGE)、数据处理、索引优化到最终检索实现,完整拆解 pgvector 的应用流程,并通过可直接运行的 Python
在 AI 驱动的语义检索时代,传统关键词搜索已难以满足“理解内容”的需求。pgvector 作为 PostgreSQL 的向量扩展,让数据库具备了高效的向量存储与检索能力,无需额外部署独立向量数据库,极大简化了架构。本文结合实战场景,从环境搭建、文本转向量模型选型(BERT vs BGE)、数据处理、索引优化到最终检索实现,完整拆解 pgvector 的应用流程,并通过可直接运行的 Python 代码示例,帮助开发者快速落地语义检索功能。
一、为什么选择 pgvector?
随着大模型技术的发展,“语义理解”成为检索系统的核心需求——比如搜索“宠物的温馨日常”能精准匹配到“金毛的日常生活点滴”,这种超越关键词的检索能力,依赖于文本向量化技术:将文本转换为高维向量,通过计算向量相似度找到语义相近的内容。
在众多向量存储方案中,pgvector 凭借两大核心优势脱颖而出:
- 架构极简:作为 PostgreSQL 的原生扩展,无需额外部署独立向量数据库,可直接在现有 PostgreSQL 中存储结构化数据(如文章标题、用户信息)和向量数据,减少系统复杂度与运维成本。
- 性能可靠:支持 IVFFlat、HNSW 等主流近似最近邻(ANN)索引,适配从万级到百万级数据量的检索需求,同时兼容 PostgreSQL 强大的事务、权限管理等功能。
- 生态兼容:无缝对接 Python 等主流开发语言,可轻松集成 Hugging Face 生态下的各类文本嵌入模型(如 BERT、BGE),快速实现端到端的语义检索。
适用场景包括:语义搜索引擎、智能推荐系统、相似文档匹配、AI 问答知识库、相似图像检索等。
二、核心前置知识:文本/图像转向量模型选型
pgvector 负责向量的存储与检索,但“文本/图像如何转换为高质量向量”依赖于预训练嵌入模型。不同数据类型对应不同的模型选型,下面分别介绍中文文本和图像的主流模型:
| 数据类型 | 模型类型 | 代表模型 | 优势 | 适用场景 |
|---|---|---|---|---|
| 文本 | BERT 系列 | paraphrase-multilingual-MiniLM-L12-v2 | 支持多语言,轻量高效,部署成本低 | 多语言混合检索、轻量级应用 |
| BGE 系列 | BAAI/bge-small-zh-v1.5 | 专为中文优化,语义理解精度更高,向量区分度强 | 中文为主的检索场景(如中文知识库、中文推荐) | |
| 图像 | CLIP 系列 | openai/clip-vit-base-patch16 | 跨模态(文本-图像)检索支持,轻量级,适配通用图像场景 | 相似图像检索、图文匹配(如商品图检索、图库管理) |
提示:文本场景中,中文项目建议优先选择 BGE 中文模型,其在中文语义匹配任务上的表现普遍优于同量级 BERT 模型;若需支持多语言,则选择多语言 BERT 模型。图像场景优先选择 CLIP 系列模型,不仅支持相似图像检索,还能实现“用文本描述找图像”的跨模态检索。
三、pgvector 应用实践:文本+图像检索双案例落地
本节将通过两个实战案例,完整演示 pgvector 的应用:案例一为中文文章语义检索(延续前文基础),案例二为相似图像检索。核心流程均为:环境准备 → 模型加载 → 数据库配置 → 数据向量化与存储 → 索引优化 → 检索实现。
3.1 环境准备(文本+图像通用)
需准备两类环境:数据库端(PostgreSQL + pgvector)和应用端(Python 依赖库)。
3.1.1 数据库端:安装 pgvector
确保 PostgreSQL 版本 ≥ 11(推荐 14+),然后安装 pgvector 扩展:
# Ubuntu/Debian 系统
sudo apt-get update
sudo apt-get install postgresql-15-pgvector
# macOS(Homebrew)
brew install pgvector
安装完成后,在目标数据库中启用扩展:
-- 连接数据库
\c your_database_name
-- 启用 pgvector 扩展
CREATE EXTENSION vector;
3.1.2 应用端:安装 Python 依赖(新增图像相关库)
需安装模型加载、数据库连接相关库:
pip install transformers sentence-transformers torch psycopg2-binary numpy pillow
各库作用:
- sentence-transformers:简化预训练模型的加载与文本向量化操作
- torch:模型运行依赖的深度学习框架
- psycopg2-binary:PostgreSQL 的 Python 驱动
- numpy:向量数据处理
- pillow:图像读取与预处理(图像检索必备)
3.2 案例一:中文文章语义检索(完整实现)
以下代码可直接运行,需修改数据库连接配置为自己的环境信息。
3.2.1 配置与模型加载
import psycopg2
from psycopg2.extras import execute_batch
from sentence_transformers import SentenceTransformer
import numpy as np
# 1. 数据库连接配置(修改为自己的信息)
DB_CONFIG = {
'dbname': 'your_database',
'user': 'your_username',
'password': 'your_password',
'host': 'localhost',
'port': '5432'
}
# 2. 示例文章数据(实际项目可从数据库/文件读取)
ARTICLES = [
{"title": "人工智能的未来发展", "content": "本文探讨了人工智能技术在未来十年内可能对社会、经济和就业市场产生的深远影响,包括自动化、机器学习和大型语言模型的演进。"},
{"title": "机器学习入门指南", "content": "一份面向初学者的机器学习教程,涵盖了监督学习、无监督学习和强化学习的基本概念,以及如何使用 Python 和 Scikit-learn 进行实践。"},
{"title": "猫咪的可爱瞬间", "content": "收集了一系列网络上爆红的猫咪照片和视频,展示了它们调皮又可爱的一面,这些内容总能治愈人们的心情。"},
{"title": "小狗的日常生活", "content": "记录了一只金毛寻回犬从早到晚的生活点滴,包括玩耍、进食和睡觉,展现了狗狗作为人类忠实伙伴的温馨一面。"},
{"title": "北京旅游攻略", "content": "详细介绍了北京的著名景点,如故宫、长城、颐和园,并提供了美食推荐和出行交通指南,是一份实用的旅行计划。"},
{"title": "上海城市印象", "content": "描述了上海作为国际化大都市的繁华景象,从外滩的历史建筑到陆家嘴的摩天大楼,体验东西方文化的交融。"},
]
# 3. 加载文本嵌入模型(二选一,推荐中文场景用 BGE)
# 选项1:多语言 BERT 模型
# MODEL_NAME = 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2'
# 选项2:中文 BGE 模型(推荐)
MODEL_NAME = 'BAAI/bge-small-zh-v1.5'
print(f"加载模型:{MODEL_NAME}...")
model = SentenceTransformer(MODEL_NAME)
# 获取模型生成的向量维度(不同模型维度不同)
VECTOR_DIMENSION = model.get_sentence_embedding_dimension()
print(f"模型生成向量维度:{VECTOR_DIMENSION}")
3.2.2 数据库表配置
创建用于存储文章和向量的表,并创建索引优化检索性能:
def get_db_connection():
"""创建数据库连接"""
try:
conn = psycopg2.connect(**DB_CONFIG)
conn.autocommit = True
return conn
except Exception as e:
print(f"数据库连接失败:{e}")
raise
def setup_database_table():
"""创建文章表(含向量字段)和索引"""
conn = get_db_connection()
with conn.cursor() as cur:
# 创建表:embedding 字段类型为 vector(维度)
cur.execute(f"""
DROP TABLE IF EXISTS semantic_articles;
CREATE TABLE semantic_articles (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
embedding vector({VECTOR_DIMENSION}) -- 向量字段
);
""")
print("表 semantic_articles 创建成功")
# 创建 HNSW 索引(推荐用于高维向量,性能优于 IVFFlat)
# vector_cosine_ops:基于余弦相似度的索引(适合语义检索)
cur.execute("""
CREATE INDEX idx_embedding_hnsw ON semantic_articles
USING hnsw (embedding vector_cosine_ops);
""")
print("HNSW 索引创建成功")
conn.close()
索引选型说明:
- HNSW:适用于高维向量(如 768 维),检索速度快、召回率高,推荐生产环境使用。
- IVFFlat:适用于低维向量,构建索引速度快,但检索精度和速度略逊于 HNSW。
- 向量操作符:
<=>(余弦距离)、<->(欧氏距离)、<#>(内积),语义检索优先用余弦距离(vector_cosine_ops)。
3.2.3 数据向量化与存储
将文章内容转换为向量,批量插入数据库:
def insert_articles(articles):
"""批量将文章向量化并插入数据库"""
conn = get_db_connection()
with conn.cursor() as cur:
# 提取所有文章内容,批量向量化(效率高于单条处理)
contents = [article['content'] for article in articles]
print("正在将文章向量化...")
# normalize_embeddings=True:归一化向量,确保余弦距离计算准确
embeddings = model.encode(contents, normalize_embeddings=True)
print("向量化完成")
# 准备插入数据:将 numpy 向量转为 PostgreSQL 支持的字符串格式
data_to_insert = []
for i, article in enumerate(articles):
embedding_str = np.array2string(
embeddings[i],
separator=',',
formatter={'float': '{:.4f}'.format}
)
data_to_insert.append((article['title'], article['content'], embedding_str))
# 批量插入数据库
execute_batch(cur,
"""
INSERT INTO semantic_articles (title, content, embedding)
VALUES (%s, %s, %s);
""",
data_to_insert
)
print(f"{len(data_to_insert)} 篇文章插入成功")
conn.close()
3.2.4 语义检索查询
将用户查询文本向量化,在数据库中检索最相似的文章:
def search_similar_articles(query_text, top_k=3):
"""
语义检索:根据查询文本找最相似的文章
query_text:用户查询文本
top_k:返回最相似的前 k 篇
"""
conn = get_db_connection()
# 1. query text 向量化
query_embedding = model.encode(query_text, normalize_embeddings=True)
query_embedding_str = np.array2string(
query_embedding,
separator=',',
formatter={'float': '{:.4f}'.format}
)
with conn.cursor() as cur:
# 2. 执行向量检索:按余弦距离排序,返回相似度得分
# 余弦相似度 = 1 - 余弦距离(<=> 计算的是余弦距离)
cur.execute("""
SELECT
id,
title,
1 - (embedding <=> %s) AS cosine_similarity -- 计算相似度
FROM
semantic_articles
ORDER BY
embedding <=> %s ASC -- 余弦距离越小,相似度越高
LIMIT %s;
""", (query_embedding_str, query_embedding_str, top_k))
results = cur.fetchall()
conn.close()
# 3. 打印结果
print(f"\n=== 检索结果:与 '{query_text}' 最相似的 {top_k} 篇文章 ===")
for i, (id, title, similarity) in enumerate(results, 1):
print(f"排名 {i}:{title}")
print(f"相似度:{similarity:.4f}")
print("-" * 50)
3.2.5 运行主程序
if __name__ == '__main__':
# 1. 初始化数据库表
setup_database_table()
# 2. 插入文章数据(含向量化)
insert_articles(ARTICLES)
# 3. 执行示例检索
search_similar_articles("宠物的温馨日常", top_k=2)
search_similar_articles("中国首都旅游攻略", top_k=2)
search_similar_articles("机器学习如何入门", top_k=2)
3.2.6 运行结果解读
运行程序后,会输出以下核心结果:
加载模型:BAAI/bge-small-zh-v1.5...
模型生成向量维度:768
表 semantic_articles 创建成功
HNSW 索引创建成功
正在将文章向量化...
向量化完成
6 篇文章插入成功
=== 检索结果:与 '宠物的温馨日常' 最相似的 2 篇文章 ===
排名 1:小狗的日常生活
相似度:0.8925
--------------------------------------------------
排名 2:猫咪的可爱瞬间
相似度:0.8763
--------------------------------------------------
=== 检索结果:与 '中国首都旅游攻略' 最相似的 2 篇文章 ===
排名 1:北京旅游攻略
相似度:0.9217
--------------------------------------------------
排名 2:上海城市印象
相似度:0.6532
--------------------------------------------------
从结果可见:
- 系统能精准理解“宠物的温馨日常”的语义,匹配到“小狗”和“猫咪”相关文章,而非依赖关键词匹配。
- “中国首都旅游攻略”直接匹配到“北京旅游攻略”,相似度高达 0.92,体现了模型的语义理解能力。
3.3 案例二:相似图像检索(完整实现)
本案例将搭建“相似宠物图像检索系统”,实现上传一张宠物图找到图库中最相似图像,同时支持“文本描述找图像”的跨模态检索,核心依赖 CLIP 模型完成图像向量化。
3.3.1 图像检索专属:模型加载与配置
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import os
# 1. 图像转向量模型:CLIP(支持图像-文本跨模态检索)
IMAGE_MODEL_NAME = 'openai/clip-vit-base-patch16'
print(f"加载图像模型:{IMAGE_MODEL_NAME}...")
# 加载 CLIP 模型和处理器(处理器负责图像预处理)
image_processor = CLIPProcessor.from_pretrained(IMAGE_MODEL_NAME)
image_model = CLIPModel.from_pretrained(IMAGE_MODEL_NAME)
# 获取图像向量维度(CLIP 图像向量维度固定为 512)
IMAGE_VECTOR_DIMENSION = image_model.visual_projection.out_features
print(f"CLIP 模型生成图像向量维度:{IMAGE_VECTOR_DIMENSION}")
# 2. 示例图像数据(实际项目可从文件夹读取,替换为实际路径)
IMAGES = [
{"image_name": "金毛玩耍", "image_path": "./images/golden_retriever_play.jpg", "description": "一只金毛寻回犬在草地上玩耍"},
{"image_name": "布偶猫趴卧", "image_path": "./images/ragdoll_cat_lie.jpg", "description": "一只布偶猫趴在沙发上休息"},
{"image_name": "柯基奔跑", "image_path": "./images/corgi_run.jpg", "description": "一只柯基犬在公园奔跑"},
{"image_name": "英短猫玩球", "image_path": "./images/british_shorthair_play.jpg", "description": "一只英短猫在玩毛线球"},
]
# 3. 图像读取与预处理函数
def load_and_preprocess_image(image_path):
"""读取图像并进行预处理(适配 CLIP 模型输入)""" if not os.path.exists(image_path):
raise FileNotFoundError(f"图像文件不存在:{image_path}")
# 读取图像(RGB 格式)
image = Image.open(image_path).convert('RGB')
# 预处理:缩放、归一化等(CLIP 处理器自动完成)
inputs = image_processor(images=image, return_tensors='pt')
return inputs
# 4. 图像向量化函数
def image_to_vector(image_path):
"""将图像转换为向量"""
inputs = load_and_preprocess_image(image_path)
# 生成图像向量(禁用梯度计算,提升速度)
with torch.no_grad():
image_embeds = image_model.get_image_features(**inputs)
# 归一化向量(便于余弦相似度计算)
image_embeds = torch.nn.functional.normalize(image_embeds, p=2, dim=1)
# 转换为 numpy 数组并返回
return image_embeds.squeeze(0).numpy()
3.3.2 图像检索专属:数据库表配置
def setup_image_database_table():
"""创建图像存储表(含图像向量字段)和索引"""
conn = get_db_connection()
with conn.cursor() as cur:
# 创建表:存储图像名称、路径、描述和向量
cur.execute(f"""
DROP TABLE IF EXISTS similar_images;
CREATE TABLE similar_images (
id SERIAL PRIMARY KEY,
image_name VARCHAR(255) NOT NULL,
image_path VARCHAR(512) NOT NULL, -- 存储图像路径(实际项目可存OSS地址)
description TEXT, -- 图像描述(可选,用于辅助检索)
image_embedding vector({IMAGE_VECTOR_DIMENSION}) -- 图像向量字段
);
""")
print("表 similar_images 创建成功")
# 为图像向量创建 HNSW 索引(余弦相似度)
cur.execute("""
CREATE INDEX idx_image_embedding_hnsw ON similar_images
USING hnsw (image_embedding vector_cosine_ops);
""")
print("图像向量 HNSW 索引创建成功")
conn.close()
3.3.3 图像向量化与存储
def insert_images(images):
"""批量将图像向量化并插入数据库"""
conn = get_db_connection()
with conn.cursor() as cur:
data_to_insert = []
print("正在将图像向量化...")
for img in images:
image_name = img['image_name']
image_path = img['image_path']
description = img['description']
# 图像向量化
img_vector = image_to_vector(image_path)
# 转换为 PostgreSQL 支持的向量字符串格式
img_vector_str = np.array2string(
img_vector,
separator=',',
formatter={'float': '{:.4f}'.format}
)
data_to_insert.append((image_name, image_path, description, img_vector_str))
print("图像向量化完成")
# 批量插入数据库
execute_batch(cur,
"""
INSERT INTO similar_images (image_name, image_path, description, image_embedding)
VALUES (%s, %s, %s, %s);
""",
data_to_insert
)
print(f"{len(data_to_insert)} 张图像已成功插入数据库")
conn.close()
3.3.4 相似图像检索(含跨模态检索)
def search_similar_images(query_data, query_type='image', top_k=3):
"""
相似图像检索:支持两种查询方式
query_data:查询数据(image 类型传图像路径,text 类型传文本描述)
query_type:查询类型('image'=相似图像检索,'text'=文本描述找图像)
top_k:返回最相似的前 k 张图像
核心说明:跨模态检索(text 类型)依赖 CLIP 模型的跨模态对齐能力,
文本和图像会被映射到同一向量空间,从而实现“以文搜图”
"""
conn = get_db_connection()
# 1. 根据查询类型生成向量
if query_type == 'image':
# 图像查询:将输入图像转为向量
query_vector = image_to_vector(query_data)
print(f"\n=== 检索与 {os.path.basename(query_data)} 相似的图像 ===")
elif query_type == 'text':
# 文本查询(跨模态):将文本描述转为向量
# 关键步骤:CLIP 的处理器同样支持文本预处理,模型能生成与图像向量同维度的文本向量
with torch.no_grad():
text_inputs = image_processor(text=query_data, return_tensors='pt', padding=True, truncation=True)
text_embeds = image_model.get_text_features(**text_inputs)
query_vector = torch.nn.functional.normalize(text_embeds, p=2, dim=1).squeeze(0).numpy()
print(f"\n=== 检索与描述 '{query_data}' 匹配的图像 ===")
else:
raise ValueError("query_type 仅支持 'image' 或 'text'")
query_vector_str = np.array2string(
query_vector,
separator=',',
formatter={'float': '{:.4f}'.format}
)
with conn.cursor() as cur:
# 2. 执行向量检索:按余弦距离排序
# 跨模态检索的核心逻辑:同一向量空间中,余弦距离越小,文本与图像的匹配度越高
cur.execute("""
SELECT
id,
image_name,
description,
1 - (image_embedding <=> %s) AS cosine_similarity
FROM
similar_images
ORDER BY
image_embedding <=> %s ASC
LIMIT %s;
""", (query_vector_str, query_vector_str, top_k))
results = cur.fetchall()
conn.close()
# 3. 打印结果
print(f"检索完成,找到 {len(results)} 张最相似的图像:")
for i, (id, image_name, description, similarity) in enumerate(results, 1):
print(f"排名 {i}:{image_name}")
print(f"图像描述:{description}")
print(f"相似度:{similarity:.4f}")
print("-" * 50)
3.3.5 图像检索主程序运行
if __name__ == '__main__':
# 初始化图像数据库表
setup_image_database_table()
# 插入示例图像数据
insert_images(IMAGES)
# 执行示例检索
# 示例1:相似图像检索(传入一张金毛测试图路径)
search_similar_images("./images/golden_retriever_test.jpg", query_type='image', top_k=2)
# 示例2:跨模态检索(用文本描述找图像)
search_similar_images("一只可爱的布偶猫", query_type='text', top_k=2)
3.3.6 图像检索结果解读
运行程序后,核心输出如下(以跨模态检索为例):
加载图像模型:openai/clip-vit-base-patch16...
CLIP 模型生成图像向量维度:512
表 similar_images 创建成功
图像向量 HNSW 索引创建成功
正在将图像向量化...
图像向量化完成
4 张图像已成功插入数据库
=== 检索与描述 '一只可爱的布偶猫' 匹配的图像 ===
检索完成,找到 2 张最相似的图像:
排名 1:布偶猫趴卧
图像描述:一只布偶猫趴在沙发上休息
相似度:0.9315
--------------------------------------------------
排名 2:英短猫玩球
图像描述:一只英短猫在玩毛线球
相似度:0.7852
--------------------------------------------------
结果解读:
- 跨模态检索核心原理与优势:跨模态检索是指跨越“文本”和“图像”两种不同数据类型的检索方式(如用文本找图像、用图像找文本),其核心依赖 CLIP 模型的特殊训练机制——CLIP 模型在训练时同步学习文本描述和对应图像的特征,使文本和图像能映射到同一向量空间中。因此,“一只可爱的布偶猫”的文本向量能与“布偶猫趴卧”的图像向量产生高相似度匹配(本例中达 0.93),实现“以文搜图”的精准定位,这是传统图像检索(依赖文件名、标签关键词)无法实现的能力。
- 相似性排序合理:同为猫咪的“英短猫玩球”排名第二,相似度 0.78,而宠物狗相关图像未被匹配。这是因为 CLIP 模型能准确区分“猫咪”和“狗狗”的语义/视觉特征,向量空间中同类事物的向量距离更近,排序结果符合人类认知逻辑。
- 实际应用价值:跨模态检索极大拓展了图像检索的使用场景,例如电商平台中用户输入“白色蓬松的羽绒服”文本即可找到对应商品图、智能相册中输入“海边度假的全家福”能快速定位相关照片、图库管理中通过简单描述筛选目标图像等,大幅降低用户检索门槛。
- 跨模态检索精准匹配:用“一只可爱的布偶猫”文本描述,能准确找到“布偶猫趴卧”的图像,相似度高达 0.93,体现了 CLIP 模型的跨模态理解能力。
- 相似性排序合理:同为猫咪的“英短猫玩球”排名第二,相似度 0.78,而宠物狗相关图像未被匹配,符合语义逻辑。
- 实际应用价值:可用于电商商品图检索、图库管理、智能相册分类等场景。
四、pgvector 实践关键优化点(文本+图像通用)
- 向量归一化:无论文本还是图像向量化,务必进行向量归一化(如
normalize_embeddings=True或torch.nn.functional.normalize),确保余弦距离计算准确,提升检索精度。 - 索引选择:高维向量(如 768 维)优先用 HNSW 索引;低维向量(如 128 维)可考虑 IVFFlat,平衡索引构建速度和检索性能。
- 批量处理:数据向量化和插入时尽量使用批量操作(如
model.encode(批量文本)、execute_batch),提升效率。 - 模型选型:中文文本优先 BGE,多语言文本选 BERT;图像/跨模态检索优先 CLIP 系列,根据部署环境选择模型大小(base 版平衡性能与速度,large 版精度更高但更耗资源)。
- 数据量适配:万级数据可直接使用;百万级数据建议优化 HNSW 索引参数(如
m=16、ef_construction=64),平衡索引大小和检索速度。
五、总结
pgvector 为 PostgreSQL 注入了向量检索能力,让开发者无需额外部署向量数据库,就能快速搭建文本语义检索和图像相似检索系统。本文通过两个完整实践案例,从环境准备、模型选型、代码实现到结果解读,覆盖了 pgvector 在多数据类型下的应用全流程,核心要点可总结为:
- 架构简化:复用现有 PostgreSQL,统一存储结构化数据和向量数据,降低系统复杂度。
- 模型适配:文本场景按语言需求选 BGE/BERT,图像/跨模态场景选 CLIP,根据业务精度和部署资源选择合适的模型量级。
- 性能优化:合理选择 HNSW/IVFFlat 索引,批量处理数据,确保检索效率和精度。
随着 AI 技术的普及,语义检索和图像检索已成为很多应用的基础能力。pgvector 凭借其易用性、高性能和生态兼容性,无需重构现有架构就能快速落地多类型向量检索,无疑是中小团队的优选方案。赶紧基于本文的文本/图像案例,改造你的检索系统吧!
更多推荐


所有评论(0)