全部产品
Search
文档中心

Vector Retrieval Service for Milvus:Implementasi pencarian multimodal dengan Alibaba Cloud Milvus dan Qwen-VL

更新时间:Sep 24, 2025

Topik ini menunjukkan cara membangun sistem pencarian multimodal dengan mengintegrasikan Vector Retrieval Service for Milvus (Milvus) dan model Qwen-VL Large Vision-Language Model (LVLM). Integrasi ini memungkinkan ekstraksi fitur gambar dan penggunaan model penyematan multimodal untuk pencarian yang efisien. Metode pencarian mencakup teks-ke-gambar, teks-ke-teks, gambar-ke-gambar, dan gambar-ke-teks.

Informasi latar belakang

Dalam pencarian multimodal, data tidak terstruktur seperti gambar dan teks diubah menjadi representasi vektor. Teknologi pengambilan vektor digunakan untuk menemukan konten serupa dengan cepat. Topik ini melibatkan penggunaan alat-alat berikut:

  • Vector Retrieval Service Milvus: Database vektor yang efisien untuk menyimpan dan mengambil vektor.

  • Qwen-VL: Mengekstrak deskripsi gambar dan kata kunci. Untuk informasi lebih lanjut, lihat Qwen-VL.

  • DashScope Embedding API: Mengonversi gambar dan teks menjadi vektor. Untuk informasi lebih lanjut, lihat Detail API Multimodal-Embedding.

Fitur-fiturnya mencakup hal-hal berikut:

  • Pencarian teks-ke-gambar: Masukkan kueri teks untuk mencari gambar yang paling mirip.

  • Pencarian teks-ke-teks: Masukkan kueri teks untuk mencari deskripsi gambar yang paling mirip.

  • Pencarian gambar-ke-gambar: Masukkan kueri gambar untuk mencari gambar yang paling mirip.

  • Pencarian gambar-ke-teks: Masukkan kueri gambar untuk mencari deskripsi gambar yang paling mirip.

Arsitektur sistem

Gambar berikut menunjukkan arsitektur keseluruhan dari sistem pencarian multimodal yang digunakan dalam topik ini.

Prasyarat

  • Buat instans Milvus. Untuk informasi lebih lanjut, lihat Buat instans Milvus dengan cepat.

  • Aktifkan Alibaba Cloud Model Studio dan dapatkan Kunci API. Untuk informasi lebih lanjut, lihat Dapatkan Kunci API Anda.

  • Instal paket dependensi yang diperlukan.

    pip3 install dashscope pymilvus==2.5.0

    Contoh dalam topik ini berjalan di lingkungan Python 3.9.

  • Unduh dan ekstrak set data sampel.

    wget https://github.com/milvus-io/pymilvus-assets/releases/download/imagedata/reverse_image_search.zip
    unzip -q -o reverse_image_search.zip

    Set data sampel berisi file CSV bernama reverse_image_search.csv dan beberapa file gambar.

    Catatan

    Topik ini menggunakan set data sampel dan gambar dari proyek sumber terbuka Milvus.

Ikhtisar kode inti

Dalam contoh-contoh dalam topik ini, model Qwen-VL pertama-tama mengekstrak deskripsi gambar dan menyimpannya di bidang image_description. Kemudian, model penyematan multimodal mentransformasikan gambar dan deskripsinya menjadi representasi vektor, bernama image_embedding dan text_embedding. Proses ini memungkinkan pengambilan dan analisis lintas modalitas.

Untuk kesederhanaan, contoh ini hanya mengekstrak data dari 200 gambar pertama.

import base64
import csv
import dashscope
import os
import pandas as pd
import sys
import time
from tqdm import tqdm
from pymilvus import (
    connections,
    FieldSchema,
    CollectionSchema,
    DataType,
    Collection,
    MilvusException,
    utility,
)

from http import HTTPStatus
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class FeatureExtractor:
    def __init__(self, DASHSCOPE_API_KEY):
        self._api_key = DASHSCOPE_API_KEY  # Simpan Kunci API dalam variabel lingkungan

    def __call__(self, input_data, input_type):
        if input_type not in ("image", "text"):
            raise ValueError("Tipe input tidak valid. Harus 'image' atau 'text'.")

        try:
            if input_type == "image":
                _, ext = os.path.splitext(input_data)
                image_format = ext.lstrip(".").lower()
                with open(input_data, "rb") as image_file:
                    base64_image = base64.b64encode(image_file.read()).decode("utf-8")
                input_data = f"data:image/{image_format};base64,{base64_image}"
                payload = [{"image": input_data}]
            else:
                payload = [{"text": input_data}]

            resp = dashscope.MultiModalEmbedding.call(
                model="multimodal-embedding-v1",
                input=payload,
                api_key=self._api_key,
            )

            if resp.status_code == HTTPStatus.OK:
                return resp.output["embeddings"][0]["embedding"]
            else:
                raise RuntimeError(
                    f"Panggilan API gagal. Kode status: {resp.status_code}, Pesan kesalahan: {resp.message}"
                )
        except Exception as e:
            logger.error(f"Pemrosesan gagal: {str(e)}")
            raise


class FeatureExtractorVL:
    def __init__(self, DASHSCOPE_API_KEY):
        self._api_key = DASHSCOPE_API_KEY  # Simpan Kunci API dalam variabel lingkungan

    def __call__(self, input_data, input_type):
        if input_type not in ("image"):
            raise ValueError("Tipe input tidak valid. Harus 'image'.")

        try:
            if input_type == "image":
                payload=[
                            {
                                "role": "system",
                                "content": [{"type":"text","text": "Anda adalah asisten yang membantu."}]
                            },
                            {
                                "role": "user",
                                "content": [
                                            # {"image": "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg"},
                                            {"image": input_data},
                                            {"text": "Pertama, jelaskan gambar ini dalam kurang dari 50 kata, lalu berikan 5 kata kunci"}
                                            ],
                            }
                        ]

            resp = dashscope.MultiModalConversation.call(
                model="qwen-vl-plus",
                messages=payload,
                api_key=self._api_key,
            )

            if resp.status_code == HTTPStatus.OK:
                return resp.output["choices"][0]["message"].content[0]["text"]
            else:
                raise RuntimeError(
                    f"Panggilan API gagal. Kode status: {resp.status_code}, Pesan kesalahan: {resp.message}"
                )
        except Exception as e:
            logger.error(f"Pemrosesan gagal: {str(e)}")
            raise


class MilvusClient:
    def __init__(self, MILVUS_TOKEN, MILVUS_HOST, MILVUS_PORT, INDEX, COLLECTION_NAME):
        self._token = MILVUS_TOKEN
        self._host = MILVUS_HOST
        self._port = MILVUS_PORT
        self._index = INDEX
        self._collection_name = COLLECTION_NAME

        self._connect()
        self._create_collection_if_not_exists()

    def _connect(self):
        try:
            connections.connect(alias="default", host=self._host, port=self._port, token=self._token)
            logger.info("Berhasil terhubung ke Milvus.")
        except Exception as e:
            logger.error(f"Gagal terhubung ke Milvus: {str(e)}")
            sys.exit(1)

    def _collection_exists(self):
        return self._collection_name in utility.list_collections()
    
    def _create_collection_if_not_exists(self):
        try:
            if not self._collection_exists():
                fields = [
                    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
                    FieldSchema(name="origin", dtype=DataType.VARCHAR, max_length=512),
                    FieldSchema(name="image_description", dtype=DataType.VARCHAR, max_length=1024),
                    FieldSchema(name="image_embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
                    FieldSchema(name="text_embedding", dtype=DataType.FLOAT_VECTOR, dim=1024)
                ]

                schema = CollectionSchema(fields)

                self._collection = Collection(self._collection_name, schema)

                if self._index == 'IVF_FLAT':
                    self._create_ivf_index()
                else:
                    self._create_hnsw_index()   
                logger.info("Koleksi berhasil dibuat.")
            else:
                self._collection = Collection(self._collection_name)
                logger.info("Koleksi sudah ada.")
        except Exception as e:
            logger.error(f"Gagal membuat atau memuat koleksi: {str(e)}")
            sys.exit(1)


    def _create_ivf_index(self):
        index_params = {
            "index_type": "IVF_FLAT",
            "params": {
                        "nlist": 1024, # Jumlah kluster untuk indeks
                    },
            "metric_type": "L2",
        }
        self._collection.create_index("image_embedding", index_params)
        self._collection.create_index("text_embedding", index_params)
        logger.info("Indeks berhasil dibuat.")

    def _create_hnsw_index(self):
        index_params = {
            "index_type": "HNSW",
            "params": {
                        "M": 64, # Jumlah maksimum tetangga yang dapat dihubungkan oleh setiap node dalam grafik
                        "efConstruction": 100, # Jumlah tetangga kandidat yang dipertimbangkan untuk koneksi selama pembuatan indeks
                    },
            "metric_type": "L2",
        }
        self._collection.create_index("image_embedding", index_params)
        self._collection.create_index("text_embedding", index_params)
        logger.info("Indeks berhasil dibuat.")
    
    def insert(self, data):
        try:
            self._collection.insert(data)
            self._collection.load()
            logger.info("Data berhasil dimasukkan dan dimuat.")
        except MilvusException as e:
            logger.error(f"Gagal memasukkan data: {str(e)}")
            raise

    def search(self, query_embedding, field, limit=3):
        try:
            if self._index == 'IVF_FLAT':
                param={"metric_type": "L2", "params": {"nprobe": 10}}
            else:
                param={"metric_type": "L2", "params": {"ef": 10}}

            result = self._collection.search(
                data=[query_embedding],
                anns_field=field,
                param=param,
                limit=limit,
                output_fields=["origin", "image_description"],
            )
            return [{"id": hit.id, "distance": hit.distance, "origin": hit.origin, "image_description": hit.image_description} for hit in result[0]]
        except Exception as e:
            logger.error(f"Pencarian gagal: {str(e)}")
            return None


# Muat data dan hasilkan penyematan
def load_image_embeddings(extractor, extractorVL, csv_path):
    df = pd.read_csv(csv_path)
    image_embeddings = {}

    for image_path in tqdm(df["path"].tolist()[:200], desc="Menghasilkan penyematan gambar"): # Gunakan hanya 200 gambar pertama untuk demo ini
        try:
            desc = extractorVL(image_path, "image")
            image_embeddings[image_path] = [desc, extractor(image_path, "image"), extractor(desc, "text")]
            time.sleep(1)  # Kontrol frekuensi panggilan API
        except Exception as e:
            logger.warning(f"Gagal memproses {image_path}, melewati: {str(e)}")

    return [{"origin": k, 'image_description':v[0], "image_embedding": v[1], 'text_embedding': v[2]} for k, v in image_embeddings.items()]
    

Di mana:

  • FeatureExtractor: Kelas ini memanggil DashScope Embedding API untuk mentransformasikan gambar atau teks menjadi representasi vektor.

  • FeatureExtractorVL: Kelas ini memanggil model Qwen-VL untuk mengekstrak deskripsi teks dan kunci dari gambar.

  • MilvusClient: Kelas ini mencakup operasi Milvus, termasuk membuat koneksi, mengelola koleksi, membangun indeks, menyisipkan data, dan melakukan pencarian.

Prosedur

Langkah 1: Muat set data

if __name__ == "__main__":
    # Konfigurasikan Milvus dan DashScope APIs
    MILVUS_TOKEN = "root:****"
    MILVUS_HOST = "c-0aa16b1****.milvus.aliyuncs.com"
    MILVUS_PORT = "19530"
    COLLECTION_NAME = "multimodal_search"
    INDEX = "IVF_FLAT"  # IVF_FLAT ATAU HNSW  
    script_dir = os.path.dirname(os.path.abspath(__file__))
    csv_path = os.path.join(script_dir, "reverse_image_search.csv")



    # Langkah 1: Inisialisasi klien Milvus
    milvus_client = MilvusClient(MILVUS_TOKEN, MILVUS_HOST, MILVUS_PORT, INDEX, COLLECTION_NAME)

    # Langkah 2: Inisialisasi model besar Qwen-VL dan model penyematan multimodal
    extractor = FeatureExtractor(DASHSCOPE_API_KEY)
    extractorVL = FeatureExtractorVL(DASHSCOPE_API_KEY)

    # Langkah 3: Hasilkan penyematan untuk set data gambar dan sisipkan ke Milvus
    embeddings = load_image_embeddings(extractor, extractorVL, csv_path)
    milvus_client.insert(embeddings)

Ganti parameter berikut dengan nilai sebenarnya Anda.

Parameter

Deskripsi

DASHSCOPE_API_KEY

Kunci API untuk DashScope. Digunakan untuk memanggil model Qwen-VL dan penyematan multimodal.

MILVUS_TOKEN

Kredensial akses untuk instans Milvus, dalam format username:password.

MILVUS_HOST

Titik akhir internal atau publik dari instans Milvus, seperti c-xxxxxxxxxxxx.milvus.aliyuncs.com. Anda dapat melihatnya di halaman Instance Details dari instans Milvus.

MILVUS_PORT

Nomor port dari instans Milvus. Nilai defaultnya adalah 19530.

COLLECTION_NAME

Nama koleksi Milvus yang digunakan untuk menyimpan data vektor gambar dan teks.

Jalankan file Python. Jika output mencakup informasi berikut, data telah dimuat dengan sukses.

Menghasilkan penyematan gambar: 100%
INFO:__main__:Data berhasil dimasukkan dan dimuat.

Anda juga dapat mengunjungi halaman Attu dan pergi ke tab Data untuk memverifikasi informasi set data.

Sebagai contoh, ketika model besar Qwen-VL menganalisis gambar, ia mengekstrak ringkasan teks yang secara hidup menggambarkan adegan tersebut: "Seseorang di pantai mengenakan celana jeans dan sepatu bot hijau. Pasir tertutup bekas air. Kata kunci: pantai, jejak kaki, pasir, sepatu, celana".

Deskripsi gambar menggunakan bahasa yang ringkas dan hidup untuk menyoroti fitur utama gambar, menciptakan gambar mental yang jelas tentang adegan tersebut.

image

Langkah 2: Lakukan pengambilan vektor multimodal

Contoh 1: Pencarian teks-ke-gambar dan teks-ke-teks

Dalam contoh ini, kueri teksnya adalah "anjing coklat". Model vektor multimodal mengonversi kueri ini menjadi penyematan. Penyematan ini kemudian digunakan untuk melakukan pencarian teks-ke-gambar pada bidang image_embedding dan pencarian teks-ke-teks pada bidang text_embedding. Hasil untuk kedua pencarian dikembalikan.

Dalam file Python, ganti bagian main dengan kode berikut dan jalankan file tersebut.

if __name__ == "__main__":
    MILVUS_HOST = "c-xxxxxxxxxxxx.milvus.aliyuncs.com"
    MILVUS_PORT = "19530"
    MILVUS_TOKEN = "root:****"
    COLLECTION_NAME = "multimodal_search"
    INDEX = "IVF_FLAT" # IVF_FLAT ATAU HNSW
    DASHSCOPE_API_KEY = "<YOUR_DASHSCOPE_API_KEY >"
    
    # Langkah 1: Inisialisasi klien Milvus
    milvus_client = MilvusClient(MILVUS_TOKEN, MILVUS_HOST, MILVUS_PORT, INDEX, COLLECTION_NAME)
    
    # Langkah 2: Inisialisasi model penyematan multimodal
    extractor = FeatureExtractor(DASHSCOPE_API_KEY)

    # Langkah 4: Contoh pencarian multimodal untuk pencarian teks-ke-gambar dan teks-ke-teks
    text_query = "anjing coklat"
    text_embedding = extractor(text_query, "text")
    text_results_1 = milvus_client.search(text_embedding, field = 'image_embedding')
    logger.info(f"Hasil pencarian teks-ke-gambar: {text_results_1}")
    text_results_2 = milvus_client.search(text_embedding, field = 'text_embedding')
    logger.info(f"Hasil pencarian teks-ke-teks: {text_results_2}")
  

Informasi berikut dikembalikan.

Catatan

Output model besar bersifat non-deterministik, sehingga hasil Anda mungkin sedikit berbeda dari contoh ini.

INFO:__main__:Hasil pencarian teks-ke-gambar: [
{'id': 456882250782308942, 'distance': 1.338853359222412, 'origin': './train/Rhodesian_ridgeback/n02087394_9675.JPEG', 'image_description': 'Foto seekor anak anjing berdiri di atas karpet. Ia memiliki bulu coklat dan mata biru.\nKata kunci: anak anjing, karpet, mata, warna bulu, berdiri'}, 
{'id': 456882250782308933, 'distance': 1.3568601608276367, 'origin': './train/Rhodesian_ridgeback/n02087394_6382.JPEG', 'image_description': 'Ini adalah anjing hound coklat dengan telinga menjuntai dan kalung di lehernya. Ia sedang menatap lurus ke depan.\n\nKata kunci: anjing, coklat, hound, telinga, kalung'}, 
{'id': 456882250782308940, 'distance': 1.3838427066802979, 'origin': './train/Rhodesian_ridgeback/n02087394_5846.JPEG', 'image_description': 'Dua anak anjing sedang bermain di atas selimut. Salah satu anjing berbaring di atas yang lain, dengan boneka beruang di latar belakang.\n\nKata kunci: anak anjing, bermain, selimut, boneka beruang, interaksi'}]
INFO:__main__:Hasil pencarian teks-ke-teks: [
{'id': 456882250782309025, 'distance': 0.6969608068466187, 'origin': './train/mongoose/n02137549_7552.JPEG', 'image_description': 'Ini adalah foto close-up seekor hewan kecil berbulu coklat. Ia memiliki wajah bulat dan mata besar.\n\nKata kunci: hewan kecil, bulu coklat, wajah bulat, mata besar, latar belakang alami'}, 
{'id': 456882250782308933, 'distance': 0.7110348343849182, 'origin': './train/Rhodesian_ridgeback/n02087394_6382.JPEG', 'image_description': 'Ini adalah anjing hound coklat dengan telinga menjuntai dan kalung di lehernya. Ia sedang menatap lurus ke depan.\n\nKata kunci: anjing, coklat, hound, telinga, kalung'}, 
{'id': 456882250782308992, 'distance': 0.7725887298583984, 'origin': './train/lion/n02129165_19310.JPEG', 'image_description': 'Ini adalah foto close-up seekor singa. Ia memiliki surai tebal dan mata tajam.\n\nKata kunci: singa, mata, surai, lingkungan alami, hewan liar'}]

Contoh 2: Pencarian gambar-ke-gambar dan gambar-ke-teks

Dalam contoh ini, pencarian kesamaan dilakukan menggunakan gambar singa dari direktori `test` (jalur: `test/lion/n02129165_13728.JPEG`).

image

Metode pencarian gambar-ke-gambar dan gambar-ke-teks mengambil konten terkait gambar target dari perspektif visual dan teks. Ini memungkinkan pencocokan kesamaan multi-dimensi.

if __name__ == "__main__":
    # Konfigurasikan Milvus dan DashScope APIs
    MILVUS_TOKEN = "root:****"
    MILVUS_HOST = "c-0aa16b1****.milvus.aliyuncs.com"
    MILVUS_PORT = "19530"
    COLLECTION_NAME = "multimodal_search"
    INDEX = "IVF_FLAT"  # IVF_FLAT ATAU HNSW
    DASHSCOPE_API_KEY = "<YOUR_DASHSCOPE_API_KEY >"

    # Langkah 1: Inisialisasi klien Milvus
    milvus_client = MilvusClient(MILVUS_TOKEN, MILVUS_HOST, MILVUS_PORT, INDEX, COLLECTION_NAME)
  
    # Langkah 2: Inisialisasi model penyematan multimodal
    extractor = FeatureExtractor(DASHSCOPE_API_KEY)

    # Langkah 5: Contoh pencarian multimodal untuk pencarian gambar-ke-gambar dan gambar-ke-teks
    image_query_path = "./test/lion/n02129165_13728.JPEG"
    image_embedding = extractor(image_query_path, "image")
    image_results_1 = milvus_client.search(image_embedding, field = 'image_embedding')
    logger.info(f"Hasil pencarian gambar-ke-gambar: {image_results_1}")
    image_results_2 = milvus_client.search(image_embedding, field = 'text_embedding')
    logger.info(f"Hasil pencarian gambar-ke-teks: {image_results_2}")

Output berikut dikembalikan.

Catatan

Output model besar bersifat acak hingga tingkat tertentu. Hasil Anda mungkin berbeda dari contoh ini.

INFO:__main__:Hasil pencarian gambar-ke-gambar: [
{'id': 456882250782308987, 'distance': 0.23892249166965485, 'origin': './train/lion/n02129165_19953.JPEG', 'image_description': 'Seekor singa megah berdiri di samping batu, dengan pohon dan semak-semak di latar belakang. Sinar matahari bersinar padanya.\n\nKata kunci: singa, batu, hutan, sinar matahari, kebuasan'}, 
{'id': 456882250782308989, 'distance': 0.4113130569458008, 'origin': './train/lion/n02129165_1142.JPEG', 'image_description': 'Seekor singa beristirahat di vegetasi hijau yang lebat. Latar belakang terdiri dari bambu dan pohon.\n\nKata kunci: singa, rumput, tanaman hijau, batang pohon, lingkungan alami'}, 
{'id': 456882250782308984, 'distance': 0.5206397175788879, 'origin': './train/lion/n02129165_16.JPEG', 'image_description': 'Gambar menunjukkan sepasang singa berdiri di atas rumput. Singa jantan memiliki surai tebal, sementara singa betina tampak lebih ramping.\n\nKata kunci: singa, rumput, jantan, betina, lingkungan alami'}]
INFO:__main__:Hasil pencarian gambar-ke-teks: 
[{'id': 456882250782308989, 'distance': 1.0935896635055542, 'origin': './train/lion/n02129165_1142.JPEG', 'image_description': 'Seekor singa beristirahat di vegetasi hijau yang lebat. Latar belakang terdiri dari bambu dan pohon.\n\nKata kunci: singa, rumput, tanaman hijau, batang pohon, lingkungan alami'}, 
{'id': 456882250782308987, 'distance': 1.2102885246276855, 'origin': './train/lion/n02129165_19953.JPEG', 'image_description': 'Seekor singa megah berdiri di samping batu, dengan pohon dan semak-semak di latar belakang. Sinar matahari bersinar padanya.\n\nKata kunci: singa, batu, hutan, sinar matahari, kebuasan'}, 
{'id': 456882250782308992, 'distance': 1.2725986242294312, 'origin': './train/lion/n02129165_19310.JPEG', 'image_description': 'Ini adalah foto close-up seekor singa. Ia memiliki surai tebal dan mata tajam.\n\nKata kunci: singa, mata, surai, lingkungan alami, hewan liar'}]