基于milvus的多模态检索

基于milvus的多模态检索

  • 1.Milvus简介(2019)
    • [1.1 什么是向量检索](#1.1 什么是向量检索)
  • [2 Milvus安装](#2 Milvus安装)
    • [2.1 服务器配置](#2.1 服务器配置)
    • [2.2 前提条件](#2.2 前提条件)
    • [2.3 启动attu](#2.3 启动attu)
    • [2.4 下载示例代码进行测试](#2.4 下载示例代码进行测试)
  • [3 多模态搜索场景](#3 多模态搜索场景)
    • [3.1 文搜图](#3.1 文搜图)
    • [3.2 图搜图(相似图片搜索)](#3.2 图搜图(相似图片搜索))
    • [3.3 使用 Milvus Python SDK 实现多模态搜索](#3.3 使用 Milvus Python SDK 实现多模态搜索)

本文包含milvus安装使用、attu 可视化,完整指南启动 Milvus 进行了向量相似度搜索,利用CLIP模型进行多模态检索,附完整代码。

1.Milvus简介(2019)

1.1 什么是向量检索

向量是具有一定大小和方向的量,可以简单理解为一串数字的集合,就像一行多列的矩阵,比如:[2,0,1,9,0,6,3,0]。每一行代表一个数据项,每一列代表一个该数据项的各个属性。特征向量是包含事物重要特征的向量。大家比较熟知的一个特征向量是RGB (红-绿-蓝)色彩。每种颜色都可以通过对红®、绿(G)、蓝(B)三种颜色的比例来得到。这样一个特征向量可以描述为:颜色 = [红,绿,蓝]。向量检索是指从向量库中检索出距离目标向量最近的 K 个向量。一般我们用两个向量间的欧式距离,余弦距离等来衡量两个向量间的距离,一次来评估两个向量的相似度。

2 Milvus安装

2.1 服务器配置

复制代码
(milvus) [root@ecs-86676-0005 suanfa_jingxiang]# docker --version
Docker version 19.03.9, build 9d988398e7
(milvus) [root@ecs-86676-0005 suanfa_jingxiang]# docker-compose --version
Docker Compose version v2.21.0
(milvus) [root@ecs-86676-0005 suanfa_jingxiang]# uname -a
Linux ecs-86676-0005 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

使用Docker Compose安装 Milvus standalone(即单机版),进行一个快速milvus的体验。

2.2 前提条件

  • 系统可以使用centos或者ubuntu
  • 系统已经安装docker和docker-compose
  • milvus版本这里选择2.3.1。由于milvus依赖etcd和minio,因此需要先启动这2个组件。同样也使用docker进行启动。

etcd:用来存储milvus的元数据。

minio:用来存储milvus的向量数据和索引数据。

下载milvus-standalone-docker-compose.yml 文件,保存为docker-compose.yml:

复制代码
wget https://github.com/milvus-io/milvus/releases/download/v2.3.1/milvus-standalone-docker-compose.yml -O docker-compose.yml

这里经过了一定修改,让其更加方便使用。

这个yml文件里面定义了etcd、minio、milvus的启动参数。

修改后的docker-compose.yml文件内容如下:

复制代码
version: '3.5'

services:
  etcd:
    container_name: milvus-etcd
    image: quay.io/coreos/etcd:v3.5.5
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
    ports:
      - "2379:2379"
    command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 5s
      timeout: 3s
      retries: 10

  minio:
    container_name: milvus-minio
    image: minio/minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: minioadmin
      MINIO_SECRET_KEY: minioadmin
    ports:
      - "9001:9001"
      - "9000:9000"
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
    command: minio server /minio_data --console-address ":9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 5s
      timeout: 3s
      retries: 10

  standalone:
    container_name: milvus-standalone
    image: milvusdb/milvus:v2.3.1
    command: ["milvus", "run", "standalone"]
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
    volumes:
      - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 30s
      start_period: 90s
      timeout: 20s
      retries: 3
    ports:
      - "19530:19530"
      - "9091:9091"
    depends_on:
      - "etcd"
      - "minio"

networks:
  default:
    name: milvus

然后后台启动这些容器:

复制代码
docker-compose up -d
-d 代表后台启动
使用ps命令查看容器:

如果看到healthy状态,说明容器内的服务可以正常使用了。

2.3 启动attu

attu为milvus的一款图形化管理工具,非常方便对milvus的一些管理。

启动attu:

复制代码
docker pull zilliz/attu:v2.3.0l
docker run -d --name=attu -p 2099:3000 -e MILVUS_URL=your ip zilliz/attu:v2.3.0

2.4 下载示例代码进行测试

下载 hello_milvus.py 直接或使用以下命令

复制代码
wget https://raw.githubusercontent.com/milvus-io/pymilvus/v2.2.8/examples/hello_milvus.py
or
wget https://raw.githubusercontent.com/milvus-io/pymilvus/v2.2.x/examples/hello_milvus.py

3 多模态搜索场景

3.1 文搜图

文搜图是指用自然语言描述来检索相关图片。这种搜索模式结合了文本理解和图像特征匹配。

  • 实现步骤:
  1. 使用 Chinese CLIP 模型将查询文本转换为向量。
  2. 在 Milvus 中搜索与查询向量最相似的图片向量。

3.2 图搜图(相似图片搜索)

图搜图允许用户上传一张图片,然后找到数据库中与之相似的图片。

  • 实现步骤:
  1. 使用 Chinese CLIP 模型提取查询图片的特征向量。
  2. 在 Milvus 中执行向量相似度搜索,找出最相似的图片。

3.3 使用 Milvus Python SDK 实现多模态搜索

下面我们将使用 Milvus Python SDK 来演示如何实现上述搜索场景的核心功能。Milvus 提供了多种 API 和 SDK,包括 RESTful API、Python SDK (PyMilvus)、Go SDK、Java SDK、Node.js SDK,以及由 Microsoft 贡献的 C# SDK。在本例中,我们将使用 PyMilvus 。

  1. 连接Milvus并创建集合
    这里,我们使用了 IVF_FLAT 索引。Milvus 支持多种索引类型,包括 FLAT、IVF_FLAT、IVF_SQ8、IVF_PQ、HNSW、ANNOY 等,您可以根据具体需求选择合适的索引类型。
    首先,安装必要的库:

    pip install pymilvus clip torch pillow

完整代码如下:

复制代码
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility
import os
import torch
from PIL import Image
import clip


class MilvusImageSearch:
    def __init__(self, host="0.0.0.0", port="19530", top_k=2, save_folder="output", image_folder_path="data"):
        self.top_k = top_k
        self.save_folder = save_folder
        self.image_folder_path = image_folder_path

        # 连接到 Milvus 服务器
        connections.connect("default", host=host, port=port)
        self.collection_name = "multimodal_image_collection"

        if utility.has_collection(self.collection_name):
            utility.drop_collection(self.collection_name)

        # 定义 Collection Schema
        fields = [
            FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=False),
            FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=512),
            FieldSchema(name="image_path", dtype=DataType.VARCHAR, max_length=512)
        ]
        schema = CollectionSchema(fields, "多模态图片集合")
        self.collection = Collection(self.collection_name, schema)

        # 创建索引以加快搜索速度
        index_params = {"metric_type": "L2", "index_type": "IVF_FLAT", "params": {"nlist": 1024}}
        self.collection.create_index("embedding", index_params)

        # 加载 CLIP 模型
        self.device = "cuda" if torch.cuda.is_available() else "cpu"
        self.model, self.preprocess = clip.load("ViT-B/32", device=self.device)

    def image_to_vector(self, image_path):
        image = Image.open(image_path)
        image = self.preprocess(image).unsqueeze(0).to(self.device)
        with torch.no_grad():
            image_features = self.model.encode_image(image)
        return image_features.cpu().numpy()[0]

    def batch_insert_images(self, batch_size=10):
        image_files = [os.path.join(self.image_folder_path, img) for img in os.listdir(self.image_folder_path)
                       if img.lower().endswith(('.png', '.jpg', '.jpeg'))]

        ids, embeddings, image_paths = [], [], []
        for i, image_file in enumerate(image_files):
            try:
                vector = self.image_to_vector(image_file)
                ids.append(i + 1)
                embeddings.append(vector)
                image_paths.append(image_file)
            except Exception as e:
                print(f"处理图片 {image_file} 出错: {e}")
                continue

            if len(embeddings) == batch_size:
                try:
                    self.collection.insert([ids, embeddings, image_paths])
                    print(f"成功插入 {len(ids)} 条记录")
                    ids, embeddings, image_paths = [], [], []
                except Exception as e:
                    print(f"插入错误: {e}")

        if embeddings:
            try:
                self.collection.insert([ids, embeddings, image_paths])
                print(f"成功插入剩余的 {len(ids)} 条记录")
            except Exception as e:
                print(f"插入错误: {e}")

        self.collection.flush()
        self.collection.load()

    def search_images_by_text(self, query_text):
        try:
            text_inputs = clip.tokenize([query_text]).to(self.device)
            with torch.no_grad():
                text_features = self.model.encode_text(text_inputs)
            query_vector = text_features.cpu().numpy()[0]
            search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
            results = self.collection.search([query_vector], "embedding", search_params, limit=self.top_k,
                                             output_fields=["id", "image_path"])
            return results
        except Exception as e:
            print(f"搜索错误: {e}")
            return []

    def search_similar_images(self, query_image_path):
        try:
            query_vector = self.image_to_vector(query_image_path)
            search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
            results = self.collection.search([query_vector], "embedding", search_params, limit=self.top_k,
                                             output_fields=["id", "image_path"])
            return results
        except Exception as e:
            print(f"搜索错误: {e}")
            return []

    def display_results(self, results):
        os.makedirs(self.save_folder, exist_ok=True)  # 创建保存图片的目录
        for i, result in enumerate(results[0]):
            print(f"Top {i + 1} 匹配图片ID: {result.id}, 距离: {result.distance}")
            image_path = result.entity.get("image_path")
            print(f"图片路径: {image_path}")
            try:
                image = Image.open(image_path)
                image.show()
                # 保存图片到指定目录
                save_path = os.path.join(self.save_folder, f"result_{i + 1}.jpg")
                image.save(save_path)
                print(f"图片已保存到: {save_path}")
            except Exception as e:
                print(f"显示图片出错: {e}")


# 使用示例
search_system = MilvusImageSearch(top_k=3, save_folder="output", image_folder_path="data")

# 插入图片
search_system.batch_insert_images()

# 根据文本搜索图片
query = "dog"
results = search_system.search_images_by_text(query)
print(f"查询文本: '{query}'")
search_system.display_results(results)

# 根据图片搜索相似图片
query_image = "./data/4.jpeg"
results = search_system.search_similar_images(query_image)
print(f"查询图片: '{query_image}'")
search_system.display_results(results)

结果展示

参考:https://blog.csdn.net/weixin_44839084/article/details/142828988?ops_request_misc=\&request_id=\&biz_id=102\&utm_term=基于milvus的多模态检索\&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb\~default-0-142828988.142^v100^pc_search_result_base4\&spm=1018.2226.3001.4187

相关推荐
机器之心14 小时前
Sand.ai开源发布MagiCompiler:突破局部编译界限,定义训推性能上限
人工智能·openai
KieranYin14 小时前
AI编程 | 概念
人工智能
飞Link14 小时前
LangChain Core 架构深度剖析与 LCEL 高阶实战
人工智能·架构·langchain
liangdabiao14 小时前
Seedance 2.0 Skill 一键写好剧本上线了coze的技能商店了,免费
人工智能
喵飞云智AI研发社15 小时前
本土AI企业发力 喵飞科技AIGC开年分享会助力天津数字化转型
人工智能·科技·aigc
于过15 小时前
AgentMiddleware is All You Need
人工智能·langchain·llm
LLM精进之路15 小时前
频域+特征融合:深度学习的黄金组合,顶会顶刊的快速通道
人工智能·计算机视觉·目标跟踪
大橙子打游戏15 小时前
我做了一个 A2A 协议的 Postman —— A2A-Forge 开源了
人工智能
L-影15 小时前
Agent中的ReAct:类型、作用与避坑指南(下篇)
人工智能·ai·react
itwangyang52015 小时前
AIDD-人工智能药物发现与设计-利用深度学习从头设计药物,实现逆转疾病相关转录表型
人工智能·深度学习