如何通过数据库与AI实现以图搜图?OceanBase向量功能详解

news2024/11/25 23:22:16

OceanBase支持向量数据库的基础能力

当前,数据库存储系统与人工智能技术的结合,可以体现在两个主要的应用方向上。

一、近似搜索。它利用大语言模型(LLM,简称大模型)的嵌入(embedding)技术,将非结构化数据转换为向量数据并存储于数据库系统中。通过数据库系统提供的向量运算和近似度查询功能,实现搜索推荐和非结构化数据查询的应用场景。

二、检索增强生成。大模型具备自然语言对话、文本总结、智能体Agent、辅助编码等通用能力,但限于其预训练时使用有限知识,难以有效应对互联网平台源源不断涌现的海量知识。因此,常见的做法是使用数据库存储问答等语料并为大语言模型提供语料检索,即RAG。

1715577106

在OceanBase社区版的4.3版本中,率先支持了向量数据库的基本能力

  • 支持向量数据类型(VECTOR关键字)定义以及存储;
  • 支持向量数据列创建向量近似邻近搜索(ANN)索引,目前支持IVFFLAT以及HNSW两种算法;
  • 支持分区并行构建向量近似邻近搜索索引;
  • 支持分区并行执行向量近似邻近搜索。

这些能力得以让OceanBase成为上述两种AI应用架构的存储基座,下面按照近似搜索应用架构,以一个简单的图搜图应用来展示OceanBase的向量存储能力。

OceanBase向量存储能力演示

1. 部署OceanBase向量数据库Docker镜像

通过以下命令安装OceanBase向量数据库:

docker run -p 2881:2881 --name obvec -d oceanbase/oceanbase-ce:vector

等待docker容器输出“boot success!”之后,我们可以用SQL接口试玩一下OceanBase的向量处理能力:

obclient [test]> create table t1 (c1 vector(3), c2 int, c3 float, primary key (c2));
Query OK, 0 rows affected (0.128 sec)

obclient [test]> insert into t1 values ('[1.1, 2.2, 3.3]', 1, 1.1), ('[  9.1, 3.14, 2.14]', 2, 2.43), ('[7576.42, 467.23, 2913.762]', 3, 54.6), ('[3,1,2]', 4, 4.67), ('[42.4,53.1,5.23]', 5, 423.2), ('[  3.1, 1.5, 2.12]', 6, 32.1), ('[4,6,12]', 7, 23), ('[2.3,66.77,34.35]', 8, 67), ('[0.43,8.342,0.43]', 9, 67), ('[9.99,23.2,5.88]', 10, 67),('[23.5,76.5,6.34]',11,11);
Query OK, 11 rows affected (0.011 sec)
Records: 11  Duplicates: 0  Warnings: 0

obclient [test]> CREATE INDEX vidx1_c1_t1  on t1 (c1 l2) using hnsw;
Query OK, 0 rows affected (0.315 sec)

obclient [test]> select * from t1;
+--------------------------------------+----+-------+
| c1                                   | c2 | c3    |
+--------------------------------------+----+-------+
| [1.100000,2.200000,3.300000]         |  1 |   1.1 |
| [9.100000,3.140000,2.140000]         |  2 |  2.43 |
| [7576.419922,467.230011,2913.761963] |  3 |  54.6 |
| [3.000000,1.000000,2.000000]         |  4 |  4.67 |
| [42.400002,53.099998,5.230000]       |  5 | 423.2 |
| [3.100000,1.500000,2.120000]         |  6 |  32.1 |
| [4.000000,6.000000,12.000000]        |  7 |    23 |
| [2.300000,66.769997,34.349998]       |  8 |    67 |
| [0.430000,8.342000,0.430000]         |  9 |    67 |
| [9.990000,23.200001,5.880000]        | 10 |    67 |
| [23.500000,76.500000,6.340000]       | 11 |    11 |
+--------------------------------------+----+-------+
11 rows in set (0.004 sec)

obclient [test]> select c1,c2 from t1 order by c1 <-> '[3,1,2]' limit 2;
+------------------------------+----+
| c1                           | c2 |
+------------------------------+----+
| [3.000000,1.000000,2.000000] |  4 |
| [3.100000,1.500000,2.120000] |  6 |
+------------------------------+----+
2 rows in set (0.013 sec)
  • 首先创建一个包含向量列c1的向量数据表t1;
  • 插入向量数据,展示OceanBase向量数据常量值的定义方式;
  • 在该向量数据表上创建hnsw向量索引(也支持创建ivfflat索引);
  • 向量数据表全表扫描;
  • 一个典型的向量近似最邻近查询(select XXX from XX order by XXX limit XX);
    • <->:计算向量之间的欧式距离;
    • <@>:计算向量之间的内积;
    • <~>:计算向量之间的cosine距离。

2. 处理图片数据

可以选择任意的分类图片库作为数据集,本文演示资料是从如下链接下载:

极市开发者平台-计算机视觉算法开发落地平台-极市科技

这个图片库中的图片大小不一,对于传统的机器学习应用来说需要统一图片大小,不过我们使用embedding模型进行向量搜索的方式并不需要。唯一需要做的预处理操作是:图片库中的图片按照图片目录事先已做好归类,需要打散统一放到一个目录下:

import os
import shutil

def copy_imgs(src, dest):
    if not os.path.exists(dest):
        os.makedirs(dest)

    for root, dirs, files in os.walk(src):
        for file in files:
            if file.endswith('.jpg'):
                src_file_path = os.path.join(root, file)
                dest_file_path = os.path.join(dest, file)

                shutil.copy2(src_file_path, dest_file_path)

copy_imgs(src_dir, dest_dir)

3. 使用Python连接OceanBase

我们使用sqlalchemy库连接OceanBase,由于vector类型并不是mysql方言中支持的类型,需要定义一个Vector类,实现该类型从数据库类型转为python列表类型、列表类型转为OceanBase向量常量类型的方法:

# OceanBase Vector DataBase 
import datetime
from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, Type

from sqlalchemy import Column, String, Table, create_engine, insert, text
from sqlalchemy.types import UserDefinedType, Float, String
from sqlalchemy.dialects.mysql import JSON, LONGTEXT, VARCHAR, INTEGER

try:
    from sqlalchemy.orm import declarative_base
except ImportError:
    from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

def from_db(value):
    return [float(v) for v in value[1:-1].split(',')]

def to_db(value, dim=None):
    if value is None:
        return value

    return '[' + ','.join([str(float(v)) for v in value]) + ']'

class Vector(UserDefinedType):
    cache_ok = True
    _string = String()

    def __init__(self, dim):
        super(UserDefinedType, self).__init__()
        self.dim = dim

    def get_col_spec(self, **kw):
        return "VECTOR(%d)" % self.dim

    def bind_processor(self, dialect):
        def process(value):
            return to_db(value, self.dim)
        return process

    def literal_processor(self, dialect):
        string_literal_processor = self._string._cached_literal_processor(dialect)

        def process(value):
            return string_literal_processor(to_db(value, self.dim))
        return process

    def result_processor(self, dialect, coltype):
        def process(value):
            return from_db(value)
        return process

# 与 OceanBase Vector DataBase 建立连接
ob_host = "127.0.0.1"
ob_port = 2881
ob_database = "test"
ob_user = "root@test"
ob_password = ""
connection_str = f"mysql+pymysql://{ob_user}:{ob_password}@{ob_host}:{ob_port}/{ob_database}?charset=utf8mb4"
ob_vector_db = create_engine(connection_str)

OceanBase docker启动会自动创建一个test租户,并在本地2881端口开启MySQL服务,构造连接串后创建连接即可。

4. 定义向量处理接口

接着我们事先定义好OceanBase向量处理的Python接口:

# 创建 img2img 表
def ob_create_img2img(embedding_dim):
    img2img_table_query = f"""
        CREATE TABLE IF NOT EXISTS `img2img` (
            id INT NOT NULL, 
            embedding VECTOR({embedding_dim}), 
            path VARCHAR(1024) NOT NULL, 
            PRIMARY KEY (id)
        )
    """
    with ob_vector_db.connect() as conn:
        with conn.begin():
            conn.execute(text(img2img_table_query))
            print(f"create table ok: {img2img_table_query}")

glb_img_id = 0
# 向 img2img 表中插入向量
def ob_insert_img2img(embedding_dim, embedding_vec, path):
    global glb_img_id
    glb_img_id += 1
    img_id = glb_img_id
    img2img_table = Table(
        "img2img",
        Base.metadata,
        Column("id", INTEGER, primary_key=True),
        Column("embedding", Vector(embedding_dim)),
        Column("path", VARCHAR(1024), nullable=False),
        keep_existing=True,
    )
    data = [{
        "id": img_id,
        "embedding": embedding_vec.tolist(),
        "path": path,
    }]
    with ob_vector_db.connect() as conn:
        with conn.begin():
            conn.execute(insert(img2img_table).values(data))

# vector_distance_op:
# <->: 欧式距离; <~>: cosine距离; <@>: 点积 
# 使用 OceanBase Vector DataBase 进行 ANN 查找
def ob_ann_search(vector_distance_op, query_vector, topk):
    try:
        from sqlalchemy.engine import Row
    except ImportError:
        raise ImportError(
            "Could not import Row from sqlalchemy.engine. "
            "Please 'pip install sqlalchemy>=1.4'."
        )

    vector_str = to_db(query_vector)
    sql_query = f"""
        SELECT path, embedding {vector_distance_op} '{vector_str}' as distance
        FROM `img2img`
        ORDER BY embedding {vector_distance_op} '{vector_str}'
        LIMIT {topk}
    """
    sql_query_str_for_print = f"""
        SELECT path, embedding {vector_distance_op} '?' as distance
        FROM `img2img`
        ORDER BY embedding {vector_distance_op} '?'
        LIMIT {topk}
    """
    with ob_vector_db.connect() as conn:
        begin_ts = datetime.datetime.now()
        results: Sequence[Row] = conn.execute(text(sql_query)).fetchall()
        print(f"Search {sql_query_str_for_print} cost: {(datetime.datetime.now() - begin_ts).total_seconds()} s")
        return [res for res in results]
    return []
  • ob_create_img2img:创建向量数据表。需要传入向量维度,目前OceanBase限制一个向量数据表只支持插入固定维度的向量;在图搜图这个应用中,表的schema定义为:
    • id:每一张图片分配一个唯一的id号,用作向量数据的主键;
    • embedding:存放图片嵌入的向量数据,用于近似查询;
    • path:图片的路径。通过embedding字段找到近似的向量后,利用path字段来展示图片。
  • ob_insert_img2img:向向量数据表插入向量数据;
  • ob_ann_search:用于执行向量近似最邻近查询并计算查询耗时。

5. 图片导入OceanBase

我们使用CLIP模型来将图片转为向量。CLIP模型可以使用towhee库进行下载:

import os
import shutil
from towhee import ops,pipe,AutoPipes,AutoConfig,DataCollection

img_pipe = AutoPipes.pipeline('text_image_embedding')

然后,简单调用一下即可获取向量:

def img_embedding(path):
    return img_pipe(path).get()[0]

最后将整个pipeline组合起来,将图片库中的所有图片转为向量,再插入OceanBase中。特殊处理一下第一次插入,需要额外执行向量数据表的创建:

# 将图片转换为 embedding 向量后导入 OceanBase Vector DataBase
def import_all_imgs(img_dir):
    embedding_dim = -1
    first_embedding = True
    imgs = os.listdir(img_dir)
    for i in range(len(imgs)):
        path = os.path.join(img_dir, imgs[i])
        vec = img_embedding(path)
        if first_embedding:
            embedding_dim = len(vec.tolist())
            ob_create_img2img(embedding_dim)
            first_embedding = False
        if embedding_dim != len(vec.tolist()):
            print(f"dim mismatch!! ---- expect: {embedding_dim} while get {len(vec.tolist())}")
            break
        ob_insert_img2img(embedding_dim, vec, path)
        if i % 100 == 0:
            print(f"{i} vectors inserted...")
    print("import finish")

dest_dir = "/your/image/dest/dir"
import_all_imgs(dest_dir)

导入完成后,开启一个MySQL连接,可以看到导入了5399条维度为512的向量:

obclient [test]> show create table img2img\G
*************************** 1. row ***************************
       Table: img2img
Create Table: CREATE TABLE `img2img` (
  `id` int(11) NOT NULL,
  `embedding` vector(512) DEFAULT NULL,
  `path` varchar(1024) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `vidx` (`embedding`) BLOCK_SIZE 16384 LOCAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
1 row in set (0.002 sec)

obclient [test]> select count(*) from img2img;
+----------+
| count(*) |
+----------+
|     5399 |
+----------+
1 row in set (0.021 sec)

6. 启动图搜图小应用

我们使用gradio库作为简易的WebUI,接受以下两个输入。

  • 图片上传组件:待查询的图片。
  • topK滑动组件:设定最邻近查询的topK值。

查询时,首先将传入的图片写到临时路径,再将临时图片嵌入为向量,最后调用之前定义的ob_ann_search函数获取最邻近图片路径列表。最后通过gallery组件展示图片:

# Gradio 界面
import gradio as gr
import os
import IPython.display as display
import imageio

# Gradio界面的主要逻辑函数
def show_search_results(image, topk):
    if not os.path.exists("uploads"):
        os.makedirs("uploads")
    # 保存上传的图片到临时目录并获取其路径
    if image is not None:
        temp_image_path = os.path.join("uploads", "uploaded_image.jpg")
        imageio.imsave(temp_image_path, image)
        
        # 调用图搜索函数
        query_vec = img_embedding(temp_image_path)
        res = ob_ann_search("<~>", query_vec, topk)
        result_paths = [r.path for r in res]
        return result_paths
    return []

# 创建Gradio UI
iface = gr.Interface(fn=show_search_results,
                     inputs=[gr.Image(label="上传图片"), gr.Slider(1, 10, step=1, label="Top K")],
                     outputs=gr.Gallery(label="搜索结果图片"),
                     examples=[])


# 在Jupyter Notebook内运行Gradio应用
iface.launch()

简单测试一下,可以发现原图被精确地查找了出来:

1715577126

而相关图片中都是“海豹”!

1715577132

7. 创建一个向量索引会如何?

开启一个MySQL连接,创建一个ivfflat索引:

obclient [test]> create index vidx on img2img (embedding l2) using ivfflat;
Query OK, 0 rows affected (11.129 sec)

创建索引前耗时39ms,而在利用向量索引进行查询优化后,仅7.6ms就响应了Top 9的结果:

1715577147

1715577153

拥抱AI,强化向量功能

OceanBase分布式数据库-海量数据 笔笔算数

  • 依靠OceanBase的分布式存储引擎,提供海量向量数据存储能力;
  • 扩展OceanBase分区并行执行能力,提供高效的向量近似检索能力。

这意味着在海量存储和高效检索这两个场景下,OceanBase用户将获得更低的存储成本、更快的查询速度和更精确的查询结果

此外,在AI应用中,相对于直接存储非结构化数据,存储非结构化数据Embedding后的向量数据具有两个优势:其一,数据更安全,非结构化数据对于数据库管理者不可见;其二,便于语义理解,向量近似搜索是一种从语义层面的检索方式,相似的文字、图片、视频信息具有距离接近的向量数值,这意味着用户在检索时更加灵活,即使使用相近的关键词也能得到精确的检索结果,节省检索成本并提高检索效率。

目前OceanBase可以初步支持近似搜索和搜索增强两个典型应用场景。

近似搜索场景包括但不限于:

  • 搜索推荐;
  • 数据分类、去重;
  • 用于生成式模型的向量输入,如风格迁移应用;
  • ……

检索增强生成场景包括但不限于:

  • 私有知识库问答;
  • Text2SQL;
  • ……

后续OceanBase将继续强化向量功能,包括但不限于简化ANN搜索的SQL语法、支持GPU加速、支持更多向量操作函数及向量检索算法、强化标量向量混合查询能力,以及提供更多的AI接口,比如支持matrix,能够直接使用SQL接口对图片进行一些变换操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Android面试八股文】请描述new一个对象的流程

文章目录 请描述new一个对象的流程JVM创建对象的过程检查加载分配内存内存空间初始化设置对象初始化请描述new一个对象的流程 JVM创建对象的过程 当JVM遇到一条new指令时,它需要完成以下几个步骤: 类加载与检查内存分配 并发安全性内存空间初始化设置对象信息对象初始化下图…

永磁同步直线电机(PMLSM)控制与仿真3-永磁同步直线电机数学三环控制整定

文章目录 1、电流环参数整定2、速度环参数整定3、位置环参数整定 写在前面&#xff1a;原本为一篇文章写完了永磁同步直线电机数学模型介绍&#xff0c;永磁同步直线电机数学模型搭建&#xff0c;以及永磁同步直线电机三环参数整定及三环仿真模型搭建&#xff0c;但因为篇幅较长…

贪心+dp,CF 1282B2 - K for the Price of One (Hard Version)

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1282B2 - K for the Price of One (Hard Version)https://codeforces.com/problemset/problem/1282/B2 二、解题报告 1、思路分析 考虑优先买价格低的&#xff0c;所以先升序排序 定义状态f[i]为购买前i个…

axios打通fastapi和vue,实现前后端分类项目开发

axios axios是一个前后端交互的工具&#xff0c;负责在前端代码&#xff0c;调用后端接口&#xff0c;将后端的数据请求到本地以后进行解析&#xff0c;然后传递给前端进行处理。 比如&#xff0c;我们用fastapi写了一个接口&#xff0c;这个接口返回了一条信息&#xff1a; …

LeetCode | 709.转换成小写字母

这道题可以用api也可以自己实现&#xff0c;都不难&#xff0c;大小字母之前相差了32&#xff0c;检查到大写字母时加上32即可 class Solution(object):def toLowerCase(self, s):""":type s: str:rtype: str"""return s.lower()class Solution…

韩顺平0基础学java——第22

p460-483 常用类 包装类Wrapper 针对8种几种数据类型相应的引用类型——包装类 包装类和基本数据类型的转换 jdk5之前的手动装箱和拆箱&#xff1a; jdk5之后的自动装箱和拆箱&#xff1a; 三元运算符是一个整体&#xff1a; 这个三元运算符里&#xff0c;精度最高的是doubl…

机器学习周记(第四十三周:MCformer)2024.6.10~2024.6.16

目录 摘要ABSTRACT1 论文信息1.1 论文标题1.2 论文摘要1.3 论文引言1.4 论文贡献 2 论文模型2.1 问题定义2.2 可逆实例归一化&#xff08;Reversible Instance Normalization&#xff09;2.3 混合通道块 &#xff08;Mixed-Channels Block&#xff09;2.4 编码器&#xff08;De…

MPLS VPN一

R1为客户&#xff0c;现在进行一些基本配置&#xff0c;来确保可以通路由 先启动OSPF跑通 在R3上 等一会 现在启动MPLS 对R3 对R4 然后在R2上 再把接口划到空间里面 原来的IP在公网里面&#xff0c;被清除了 然后再配置接口 查看 对R1&#xff08;相当于客户&#xff09; …

CrossOver和PD虚拟机谁更强大?CrossOver和PD虚拟机应该怎么选择

在当前的虚拟化技术和应用程序兼容性解决方案中&#xff0c;CrossOver和PD虚拟机&#xff08;Parallels Desktop&#xff09;都是备受用户喜爱的选择。对于需要在非原生系统上运行应用程序的用户而言&#xff0c;选择合适的工具尤为重要。那么&#xff0c;CrossOver和PD虚拟机谁…

虚表-在成员函数前面加上virtual后发生了什么事情???

从上图得出&#xff1a; 子类继承的父类的虚表指针吗&#xff1f; 答&#xff1a;是的。 子类和父类是同一个虚表还是不同的虚表&#xff1f; 答&#xff1a;不同的虚表&#xff0c;倘若是同一个当子类进行覆盖的时候&#xff0c;父类的虚表会丢失。 下面有一些方法来观察类…

【linux】认识“文件”的本质,理解“文件系统”的设计逻辑,体会linux优雅的设计理念

⭐⭐⭐个人主页⭐⭐⭐ ~~~~~~~~~~~~~~~~~~ C站最❤❤❤萌❤❤❤博主 ~~~~~~~~~~~~~~~~~~~ ​♥东洛的克莱斯韦克-CSDN博客♥ ~~~~~~~~~~~~~~~~~~~~ 嗷呜~ ✌✌✌✌ 萌妹统治世界~ &#x1f389;&#x1f389;&#x1f389;&#x1f389; ✈✈✈✈相关文章✈✈✈✈ &#x1f4a…

虚函数表共享和生成

每个类有不同的虚表&#xff0c;相同的类使用相同的虚表 模拟动态绑定的一个过程

【Apache Doris】周FAQ集锦:第 6 期

【Apache Doris】周FAQ集锦&#xff1a;第 6 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

C++11左值、右值

知识回顾&#xff0c;详解引用 简单概括&#xff0c;引用就是给已存在对象取别名&#xff0c;引用变量与其引用实体共用同一块内存空间 左右值区分 注意&#xff1a;不一定左边的都是左值&#xff0c;右边的都是右值 左边的也可能是右值&#xff0c;等号右边的也可能是左值 …

大数据工程师如何做到数据可视化?

好的数据可视化作品都是通过不断的数据对比分析实战出来的。 今天给大家带来一篇大数据工程师干货&#xff0c;从多角度解析做数据可视化的重要性&#xff0c;并解读一些适用的应用场景。大数据工程师们刷到这篇文章时一定要进来看看&#xff0c;满满的干货。 目录 1. 什么是数…

中望CAD 2025 (ZW3D2025) 简体中文修改版

名称&#xff1a;中望CAD 2025 (ZW3D2025) 简体中文修改版 描述&#xff1a;一款三维CAD设计工具&#xff0c;运行破解补丁ZW3D2025-2024-Patch执行修补。 链接&#xff1a;夸克网盘分享 &#x1f4c1; 大小&#xff1a;3.2GB &#x1f3f7; 标签&#xff1a;#PC软件 #CAD #设…

为什么笔记本电脑触控板不工作?这里有你想要的答案和解决办法

序言 你的笔记本电脑触控板停止工作了吗?值得庆幸的是,这个令人沮丧的问题通常很容易解决。以下是笔记本电脑触控板问题的最常见原因和修复方法。 触控板被功能键禁用 大多数(如果不是全部的话)Windows笔记本电脑都将其中一个功能键用于禁用和启用笔记本电脑触控板。按键…

【Arthas案例】某应用依赖两个GAV不同但包含两个相同全限定类名StaticLoggerBinder,引起log4j.Level类找不到异常

3分钟内解决问题 两个不同的GAV依赖冲突&#xff0c;包含相同全限定类名&#xff0c;引起ClassNotFoundException Maven依赖的三坐标体系GAV(G-groupId&#xff0c;A-artifactId&#xff0c;V-version) 【案例1】某应用依赖两个GAV不同的jar&#xff0c;但包含两个相同全限定类…

Jmeter多个请求按照比例并发压测的几种方式

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、需求 在压测的过程中&#xff0c;为了能够压测整个链路&#xff0c;通常需要多个接口进行并…

OpenAI Assistants API:如何使用代码或无需代码创建您自己的AI助手

Its now easier than ever to create your own AI Assistant that can handle a lot of computing tasks for you. See how you can get started with the OpenAI AI Assistant API. 现在比以往任何时候都更容易创建您自己的AI助手&#xff0c;它可以为您处理许多计算任务。了…