Milvus
Zilliz
Beranda
  • Panduan Pengguna
  • Home
  • Docs
  • Panduan Pengguna

  • Bidang Skema & Data

  • Praktik Terbaik

  • Desain Model Data dengan Array Struktur

Desain Model Data dengan Array StrukturCompatible with Milvus 2.6.4+

Aplikasi AI modern, terutama di Internet of Things (IoT) dan pengemudian otonom, biasanya menalar peristiwa yang kaya dan terstruktur: pembacaan sensor dengan stempel waktu dan penyematan vektor, log diagnostik dengan kode kesalahan dan cuplikan audio, atau segmen perjalanan dengan lokasi, kecepatan, dan konteks pemandangan. Semua ini membutuhkan database untuk mendukung konsumsi dan pencarian data bersarang.

Alih-alih meminta pengguna untuk mengubah peristiwa struktural atomik mereka menjadi model data datar, Milvus memperkenalkan Array of Structs, di mana setiap Struct dalam larik dapat menyimpan skalar dan vektor, menjaga integritas semantik.

Mengapa Array of Structs

Aplikasi AI modern, mulai dari pengemudian otonom hingga pengambilan multimodal, semakin bergantung pada data yang tersusun dan heterogen. Model data datar tradisional kesulitan untuk merepresentasikan hubungan yang kompleks seperti"satu dokumen dengan banyak potongan yang dianotasi" atau"satu adegan mengemudi dengan beberapa manuver yang diamati". Di sinilah tipe data Array of Structs di Milvus bersinar.

Array of Structs memungkinkan Anda untuk menyimpan sekumpulan elemen terstruktur yang terurut, di mana setiap Struct berisi kombinasi bidang skalar dan penyematan vektornya sendiri. Ini membuatnya ideal untuk:

  • Data hirarkis: Entitas induk dengan beberapa catatan anak, seperti buku dengan banyak potongan teks, atau video dengan banyak bingkai beranotasi.

  • Penyematan multimodal: Setiap Struct dapat menampung beberapa vektor, seperti penyematan teks plus penyematan gambar, di samping metadata.

  • Data temporal atau sekuensial: Struktur dalam bidang Array secara alami mewakili deret waktu atau peristiwa langkah demi langkah.

Tidak seperti solusi tradisional yang menyimpan gumpalan JSON atau membagi data di beberapa koleksi, Array of Structs menyediakan penegakan skema asli, pengindeksan vektor, dan penyimpanan yang efisien di dalam Milvus.

Panduan desain skema

Selain semua panduan yang dibahas di Desain Model Data untuk Pencarian, Anda juga harus mempertimbangkan hal-hal berikut ini sebelum mulai menggunakan Array of Structs dalam desain model data Anda.

Tentukan skema Struktur

Sebelum menambahkan bidang Array ke koleksi Anda, tentukan skema Struktur bagian dalam. Setiap field dalam struktur harus diketik secara eksplisit, skalar(VARCHAR, INT, BOOLEAN, dll.) atau vektor(FLOAT_VECTOR).

Anda disarankan untuk menjaga skema Struct tetap ramping dengan hanya menyertakan bidang yang akan Anda gunakan untuk pengambilan atau tampilan. Hindari membengkaknya metadata yang tidak terpakai.

Tetapkan kapasitas maksimum dengan bijaksana

Setiap bidang Array memiliki atribut yang menentukan jumlah maksimum elemen yang dapat ditampung oleh bidang Array untuk setiap entitas. Tetapkan ini berdasarkan batas atas kasus penggunaan Anda. Misalnya, ada 1.000 potongan teks per dokumen, atau 100 manuver per adegan mengemudi.

Nilai yang terlalu tinggi akan memboroskan memori, dan Anda harus melakukan beberapa perhitungan untuk menentukan jumlah maksimum Structs dalam bidang Array.

Mengindeks bidang vektor di Structs

Pengindeksan wajib dilakukan untuk bidang vektor, termasuk bidang vektor dalam koleksi dan bidang vektor yang didefinisikan dalam Struct. Untuk bidang vektor dalam sebuah Struct, Anda harus menggunakan AUTOINDEX atau HNSW sebagai tipe indeks dan seri MAX_SIM sebagai tipe metrik.

Untuk detail tentang semua batasan yang berlaku, lihat batasan.

Contoh dunia nyata: Memodelkan kumpulan data CoVLA untuk pengemudian otonom

Kumpulan data Comprehensive Vision-Language-Action (CoVLA), yang diperkenalkan oleh Turing Motors dan diterima pada Konferensi Musim Dingin tentang Aplikasi Visi Komputer (WACV) 2025, memberikan dasar yang kaya untuk melatih dan mengevaluasi model Visi-Bahasa-Tindakan (VLA) dalam pengemudian otonom. Setiap titik data, yang biasanya berupa klip video, tidak hanya berisi input visual mentah tetapi juga teks terstruktur yang menjelaskan:

  • Perilaku kendaraan ego (misalnya, "Belok kiri sambil mengalah pada lalu lintas yang datang"),

  • Objek yang terdeteksi hadir (misalnya, kendaraan terdepan, pejalan kaki, lampu lalu lintas), dan

  • Keterangan tingkat bingkai dari pemandangan.

Sifat hirarkis dan multi-modal ini membuatnya menjadi kandidat yang ideal untuk fitur Array of Structs. Untuk informasi lebih lanjut mengenai dataset CoVLA, lihat Situs Web Dataset CoVLA.

Langkah 1: Petakan dataset ke dalam skema koleksi

Dataset CoVLA adalah dataset penggerak multimodal berskala besar yang terdiri dari 10.000 klip video, dengan total lebih dari 80 jam rekaman. Dataset ini mengambil sampel frame dengan kecepatan 20Hz dan memberi keterangan pada setiap frame dengan keterangan bahasa alami yang mendetail bersama dengan informasi mengenai status kendaraan dan koordinat objek yang terdeteksi.

Struktur kumpulan data adalah sebagai berikut:

├── video_1                                       (VIDEO) # video.mp4
│   ├── video_id                                  (INT)
│   ├── video_url                                 (STRING)
│   ├── frames                                    (ARRAY)
│   │   ├── frame_1                               (STRUCT)
│   │   │   ├── caption                           (STRUCT) # captions.jsonl
│   │   │   │   ├── plain_caption                 (STRING)
│   │   │   │   ├── rich_caption                  (STRING)
│   │   │   │   ├── risk                          (STRING)
│   │   │   │   ├── risk_correct                  (BOOL)
│   │   │   │   ├── risk_yes_rate                 (FLOAT)
│   │   │   │   ├── weather                       (STRING)
│   │   │   │   ├── weather_rate                  (FLOAT)
│   │   │   │   ├── road                          (STRING)
│   │   │   │   ├── road_rate                     (FLOAT)
│   │   │   │   ├── is_tunnel                     (BOOL)
│   │   │   │   ├── is_tunnel_yes_rate            (FLOAT)
│   │   │   │   ├── is_highway                    (BOOL)
│   │   │   │   ├── is_highway_yes_rate           (FLOAT)
│   │   │   │   ├── has_pedestrain                (BOOL)
│   │   │   │   ├── has_pedestrain_yes_rate       (FLOAT)
│   │   │   │   ├── has_carrier_car               (BOOL)
│   │   │   ├── traffic_light                     (STRUCT) # traffic_lights.jsonl
│   │   │   │   ├── index                         (INT)
│   │   │   │   ├── class                         (STRING)
│   │   │   │   ├── bbox                          (LIST<FLOAT>)
│   │   │   ├── front_car                         (STRUCT) # front_cars.jsonl
│   │   │   │   ├── has_lead                      (BOOL)
│   │   │   │   ├── lead_prob                     (FLOAT)
│   │   │   │   ├── lead_x                        (FLOAT)
│   │   │   │   ├── lead_y                        (FLOAT)
│   │   │   │   ├── lead_speed_kmh                (FLOAT)
│   │   │   │   ├── lead_a                        (FLOAT)
│   │   ├── frame_2                               (STRUCT)
│   │   ├── ...                                   (STRUCT)
│   │   ├── frame_n                               (STRUCT)
├── video_2
├── ...
├── video_n

Anda dapat melihat bahwa struktur dataset CoVLA sangat hirarkis, membagi data yang dikumpulkan ke dalam beberapa file .jsonl, bersama dengan klip video dalam format .mp4.

Di Milvus, Anda dapat menggunakan bidang JSON atau bidang Array-of-Structs untuk membuat struktur bersarang di dalam skema koleksi. Ketika penyematan vektor merupakan bagian dari format bersarang, hanya bidang Array-of-Structs yang didukung. Namun, sebuah Struktur di dalam Array tidak dapat berisi struktur bersarang lebih lanjut. Untuk menyimpan kumpulan data CoVLA dengan tetap mempertahankan hubungan yang penting, Anda perlu menghapus hierarki yang tidak perlu dan meratakan data agar sesuai dengan skema koleksi Milvus.

Diagram di bawah ini mengilustrasikan bagaimana kita dapat memodelkan dataset ini menggunakan skema yang diilustrasikan dalam skema berikut:

Dataset Model Model Dataset

Diagram di atas mengilustrasikan struktur klip video, yang terdiri dari bidang-bidang berikut:

  • video_id berfungsi sebagai kunci utama, yang menerima bilangan bulat bertipe INT64.

  • states adalah badan JSON mentah yang berisi status kendaraan ego di setiap bingkai video saat ini.

  • captions adalah Array Struktur dengan setiap Struktur memiliki bidang berikut:

    • frame_id mengidentifikasi bingkai tertentu dalam video saat ini.

    • plain_caption adalah deskripsi frame saat ini tanpa lingkungan sekitar, seperti cuaca, kondisi jalan, dll., dan plain_cap_vector adalah penyematan vektor yang sesuai.

    • rich_caption adalah deskripsi bingkai saat ini dengan lingkungan sekitar, dan rich_cap_vector adalah penyematan vektor yang sesuai.

    • risk adalah deskripsi risiko yang dihadapi kendaraan ego pada frame saat ini, dan risk_vector adalah embeddings vektor yang sesuai, dan

    • Semua atribut lain dari frame, seperti road, weather, is_tunnel, has_pedestrain, dll...

  • traffic_lights adalah badan JSON yang berisi semua sinyal lampu lalu lintas yang diidentifikasi dalam bingkai saat ini.

  • front_cars juga merupakan Array of Structs yang berisi semua mobil terdepan yang diidentifikasi dalam frame saat ini.

Langkah 2: Menginisialisasi skema

Untuk memulai, kita perlu menginisialisasi skema untuk Struktur keterangan, Struktur mobil_terdepan, dan koleksi.

  • Inisialisasi skema untuk Struktur Caption.

    client = MilvusClient("http://localhost:19530")
    
    # create the schema for the caption struct
    schema_for_caption = client.create_struct_field_schema()
    
    schema_for_caption.add_field(
        field_name="frame_id",
        datatype=DataType.INT64,
        description="ID of the frame to which the ego vehicle's behavior belongs"
    )
    
    schema_for_caption.add_field(
        field_name="plain_caption",
        datatype=DataType.VARCHAR,
        max_length=1024,
        description="plain description of the ego vehicle's behaviors"
    )
    
    schema_for_caption.add_field(
        field_name="plain_cap_vector",
        datatype=DataType.FLOAT_VECTOR,
        dim=768,
        description="vectors for the plain description of the ego vehicle's behaviors"
    )
    
    schema_for_caption.add_field(
        field_name="rich_caption",
        datatype=DataType.VARCHAR,
        max_length=1024,
        description="rich description of the ego vehicle's behaviors"
    )
    
    schema_for_caption.add_field(
        field_name="rich_cap_vector",
        datatype=DataType.FLOAT_VECTOR,
        dim=768,
        description="vectors for the rich description of the ego vehicle's behaviors"
    )
    
    schema_for_caption.add_field(
        field_name="risk",
        datatype=DataType.VARCHAR,
        max_length=1024,
        description="description of the ego vehicle's risks"
    )
    
    schema_for_caption.add_field(
        field_name="risk_vector",
        datatype=DataType.FLOAT_VECTOR,
        dim=768,
        description="vectors for the description of the ego vehicle's risks"
    )
    
    schema_for_caption.add_field(
        field_name="risk_correct",
        datatype=DataType.BOOL,
        description="whether the risk assessment is correct"
    )
    
    schema_for_caption.add_field(
        field_name="risk_yes_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of risk being present"
    )
    
    schema_for_caption.add_field(
        field_name="weather",
        datatype=DataType.VARCHAR,
        max_length=50,
        description="weather condition"
    )
    
    schema_for_caption.add_field(
        field_name="weather_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of the weather condition"
    )
    
    schema_for_caption.add_field(
        field_name="road",
        datatype=DataType.VARCHAR,
        max_length=50,
        description="road type"
    )
    
    schema_for_caption.add_field(
        field_name="road_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of the road type"
    )
    
    schema_for_caption.add_field(
        field_name="is_tunnel",
        datatype=DataType.BOOL,
        description="whether the road is a tunnel"
    )
    
    schema_for_caption.add_field(
        field_name="is_tunnel_yes_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of the road being a tunnel"
    )
    
    schema_for_caption.add_field(
        field_name="is_highway",
        datatype=DataType.BOOL,
        description="whether the road is a highway"
    )
    
    schema_for_caption.add_field(
        field_name="is_highway_yes_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of the road being a highway"
    )
    
    schema_for_caption.add_field(
        field_name="has_pedestrian",
        datatype=DataType.BOOL,
        description="whether there is a pedestrian present"
    )
    
    schema_for_caption.add_field(
        field_name="has_pedestrian_yes_rate",
        datatype=DataType.FLOAT,
        description="probability/confidence of pedestrian presence"
    )
    
    schema_for_caption.add_field(
        field_name="has_carrier_car",
        datatype=DataType.BOOL,
        description="whether there is a carrier car present"
    )
    
  • Inisialisasi skema untuk Struktur Mobil Depan

    Meskipun mobil depan tidak melibatkan penyematan vektor, Anda tetap perlu menyertakannya sebagai array Struct karena ukuran datanya melebihi batas maksimum untuk bidang JSON.

    schema_for_front_car = client.create_struct_field_schema()
    
    schema_for_front_car.add_field(
        field_name="frame_id",
        datatype=DataType.INT64,
        description="ID of the frame to which the ego vehicle's behavior belongs"
    )
    
    schema_for_front_car.add_field(
        field_name="has_lead",
        datatype=DataType.BOOL,
        description="whether there is a leading vehicle"
    )
    
    schema_for_front_car.add_field(
        field_name="lead_prob",
        datatype=DataType.FLOAT,
        description="probability/confidence of the leading vehicle's presence"
    )
    
    schema_for_front_car.add_field(
        field_name="lead_x",
        datatype=DataType.FLOAT,
        description="x position of the leading vehicle relative to the ego vehicle"
    )
    
    schema_for_front_car.add_field(
        field_name="lead_y",
        datatype=DataType.FLOAT,
        description="y position of the leading vehicle relative to the ego vehicle"
    )
    
    schema_for_front_car.add_field(
        field_name="lead_speed_kmh",
        datatype=DataType.FLOAT,
        description="speed of the leading vehicle in km/h"
    )
    
    schema_for_front_car.add_field(
        field_name="lead_a",
        datatype=DataType.FLOAT,
        description="acceleration of the leading vehicle"
    )
    
  • Inisialisasi skema untuk koleksi

    schema = client.create_schema()
    
    schema.add_field(
        field_name="video_id",
        datatype=DataType.VARCHAR,
        description="primary key",
        max_length=16,
        is_primary=True,
        auto_id=False
    )
    
    schema.add_field(
        field_name="video_url",
        datatype=DataType.VARCHAR,
        max_length=512,
        description="URL of the video"
    )
    
    schema.add_field(
        field_name="captions",
        datatype=DataType.ARRAY,
        element_type=DataType.STRUCT,
        struct_schema=schema_for_caption,
        max_capacity=600,
        description="captions for the current video"
    )
    
    schema.add_field(
        field_name="traffic_lights",
        datatype=DataType.JSON,
        description="frame-specific traffic lights identified in the current video"
    )
    
    schema.add_field(
        field_name="front_cars",
        datatype=DataType.ARRAY,
        element_type=DataType.STRUCT,
        struct_schema=schema_for_front_car,
        max_capacity=600,
        description="frame-specific leading cars identified in the current video"
    )
    

Langkah 3: Tetapkan parameter indeks

Semua bidang vektor harus diindeks. Untuk mengindeks bidang vektor dalam elemen Struct, Anda perlu menggunakan AUTOINDEX atau HNSW sebagai tipe indeks dan tipe metrik seri MAX_SIM untuk mengukur kemiripan di antara daftar penyematan.

index_params = client.prepare_index_params()

index_params.add_index(
    field_name="captions[plain_cap_vector]", 
    index_type="AUTOINDEX", 
    metric_type="MAX_SIM_COSINE", 
    index_name="captions_plain_cap_vector_idx", # mandatory for now
    index_params={"M": 16, "efConstruction": 200}
)

index_params.add_index(
    field_name="captions[rich_cap_vector]", 
    index_type="AUTOINDEX", 
    metric_type="MAX_SIM_COSINE", 
    index_name="captions_rich_cap_vector_idx", # mandatory for now
    index_params={"M": 16, "efConstruction": 200}
)

index_params.add_index(
    field_name="captions[risk_vector]", 
    index_type="AUTOINDEX", 
    metric_type="MAX_SIM_COSINE", 
    index_name="captions_risk_vector_idx", # mandatory for now
    index_params={"M": 16, "efConstruction": 200}
)

Anda disarankan untuk mengaktifkan penghancuran JSON untuk bidang JSON untuk mempercepat pemfilteran di dalam bidang ini.

Langkah 4: Membuat koleksi

Setelah skema dan indeks siap, Anda dapat membuat koleksi target sebagai berikut:

client.create_collection(
    collection_name="covla_dataset",
    schema=schema,
    index_params=index_params
)

Langkah 5: Masukkan data

Turing Motos mengatur kumpulan data CoVLA dalam beberapa file, termasuk klip video mentah (.mp4), negara bagian (states.jsonl), keterangan (captions.jsonl), lampu lalu lintas (traffic_lights.jsonl), dan mobil depan (front_cars.jsonl).

Anda perlu menggabungkan potongan data untuk setiap klip video dari file-file ini dan menyisipkan datanya. Berikut ini adalah skrip untuk menggabungkan potongan data untuk klip video tertentu.

import json
from openai import OpenAI

openai_client = OpenAI(
    api_key='YOUR_OPENAI_API_KEY',
)

video_id = "0a0fc7a5db365174" # represent a single video with 600 frames

# get all front car records in the specified video clip
entries = []
front_cars = []
with open('data/front_car/{}.jsonl'.format(video_id), 'r') as f:
    for line in f:
        entries.append(json.loads(line))

for entry in entries:
    for key, value in entry.items():
        value['frame_id'] = int(key)
        front_cars.append(value)

# get all traffic lights identified in the specified video clip
entries = []
traffic_lights = []
frame_id = 0
with open('data/traffic_lights/{}.jsonl'.format(video_id), 'r') as f:
    for line in f:
        entries.append(json.loads(line))

for entry in entries:
    for key, value in entry.items():
        if not value or (value['index'] == 1 and key != '0'):
            frame_id+=1

        if value:
            value['frame_id'] = frame_id
            traffic_lights.append(value)
        else:
            value_dict = {}
            value_dict['frame_id'] = frame_id
            traffic_lights.append(value_dict)

# get all captions generated in the video clip and convert them into vector embeddings
entries = []
captions = []
with open('data/captions/{}.jsonl'.format(video_id), 'r') as f:
    for line in f:
        entries.append(json.loads(line))

def get_embedding(text, model="embeddinggemma:latest"):
    response = openai_client.embeddings.create(input=text, model=model)
    return response.data[0].embedding

# Add embeddings to each entry
for entry in entries:
    # Each entry is a dict with a single key (e.g., '0', '1', ...)
    for key, value in entry.items():
        value['frame_id'] = int(key)  # Convert key to integer and assign to frame_id

        if "plain_caption" in value and value["plain_caption"]:
            value["plain_cap_vector"] = get_embedding(value["plain_caption"])
        if "rich_caption" in value and value["rich_caption"]:
            value["rich_cap_vector"] = get_embedding(value["rich_caption"])
        if "risk" in value and value["risk"]:
            value["risk_vector"] = get_embedding(value["risk"])

        captions.append(value)

data = {
    "video_id": video_id,
    "video_url": "https://your-storage.com/{}".format(video_id),
    "captions": captions,
    "traffic_lights": traffic_lights,
    "front_cars": front_cars
}

Setelah Anda memproses data yang sesuai, Anda dapat menyisipkan data sebagai berikut:

client.insert(
    collection_name="covla_dataset",
    data=[data]
)

# {'insert_count': 1, 'ids': ['0a0fc7a5db365174'], 'cost': 0}