在 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 实践关键优化点(文本+图像通用)

  1. 向量归一化:无论文本还是图像向量化,务必进行向量归一化(如 normalize_embeddings=Truetorch.nn.functional.normalize),确保余弦距离计算准确,提升检索精度。
  2. 索引选择:高维向量(如 768 维)优先用 HNSW 索引;低维向量(如 128 维)可考虑 IVFFlat,平衡索引构建速度和检索性能。
  3. 批量处理:数据向量化和插入时尽量使用批量操作(如 model.encode(批量文本)execute_batch),提升效率。
  4. 模型选型:中文文本优先 BGE,多语言文本选 BERT;图像/跨模态检索优先 CLIP 系列,根据部署环境选择模型大小(base 版平衡性能与速度,large 版精度更高但更耗资源)。
  5. 数据量适配:万级数据可直接使用;百万级数据建议优化 HNSW 索引参数(如 m=16ef_construction=64),平衡索引大小和检索速度。

五、总结

pgvector 为 PostgreSQL 注入了向量检索能力,让开发者无需额外部署向量数据库,就能快速搭建文本语义检索和图像相似检索系统。本文通过两个完整实践案例,从环境准备、模型选型、代码实现到结果解读,覆盖了 pgvector 在多数据类型下的应用全流程,核心要点可总结为:

  1. 架构简化:复用现有 PostgreSQL,统一存储结构化数据和向量数据,降低系统复杂度。
  2. 模型适配:文本场景按语言需求选 BGE/BERT,图像/跨模态场景选 CLIP,根据业务精度和部署资源选择合适的模型量级。
  3. 性能优化:合理选择 HNSW/IVFFlat 索引,批量处理数据,确保检索效率和精度。

随着 AI 技术的普及,语义检索和图像检索已成为很多应用的基础能力。pgvector 凭借其易用性、高性能和生态兼容性,无需重构现有架构就能快速落地多类型向量检索,无疑是中小团队的优选方案。赶紧基于本文的文本/图像案例,改造你的检索系统吧!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐