Milvus
Zilliz
Casa
  • Guida per l'utente
  • Home
  • Docs
  • Guida per l'utente

  • Schema e campi dati

  • Migliori pratiche

  • Progettazione del modello di dati con un array di strutture

Progettazione del modello di dati con un array di struttureCompatible with Milvus 2.6.4+

Le moderne applicazioni di intelligenza artificiale, soprattutto nell'Internet delle cose (IoT) e nella guida autonoma, ragionano tipicamente su eventi ricchi e strutturati: la lettura di un sensore con il suo timestamp e il suo embedding vettoriale, un log diagnostico con un codice di errore e un frammento audio, o un segmento di viaggio con posizione, velocità e contesto della scena. Per questo è necessario che il database supporti in modo nativo l'inserimento e la ricerca di dati annidati.

Invece di chiedere all'utente di convertire gli eventi strutturali atomici in modelli di dati piatti, Milvus introduce l'array di strutture, dove ogni struttura dell'array può contenere scalari e vettori, preservando l'integrità semantica.

Perché gli array di strutture

Le moderne applicazioni di intelligenza artificiale, dalla guida autonoma al recupero multimodale, si basano sempre più su dati eterogenei e annidati. I modelli di dati piatti tradizionali faticano a rappresentare relazioni complesse come"un documento con molti pezzi annotati" o"una scena di guida con più manovre osservate". È qui che il tipo di dati Array of Structs di Milvus si fa notare.

Un array di strutture consente di memorizzare un insieme ordinato di elementi strutturati, dove ogni struttura contiene la propria combinazione di campi scalari e incorporazioni vettoriali. Questo lo rende ideale per:

  • Dati gerarchici: Entità genitore con più record figlio, come un libro con molti pezzi di testo o un video con molti fotogrammi annotati.

  • Incorporazione multimodale: Ogni struttura può contenere più vettori, come l'incorporazione di testo e l'incorporazione di immagini, oltre ai metadati.

  • Dati temporali o sequenziali: Le strutture in un campo Array rappresentano naturalmente serie temporali o eventi graduali.

A differenza delle soluzioni tradizionali che memorizzano blob JSON o dividono i dati in più raccolte, l'Array di strutture offre l'applicazione nativa dello schema, l'indicizzazione dei vettori e l'archiviazione efficiente all'interno di Milvus.

Linee guida per la progettazione degli schemi

Oltre a tutte le linee guida discusse in Progettazione del modello di dati per la ricerca, prima di iniziare a usare un Array di strutture nel modello di dati è necessario considerare anche i seguenti aspetti.

Definire lo schema della struttura

Prima di aggiungere il campo Array alla collezione, occorre definire lo schema interno della struct. Ogni campo della struct deve essere esplicitamente tipizzato, scalare(VARCHAR, INT, BOOLEAN, ecc.) o vettoriale(FLOAT_VECTOR).

Si consiglia di mantenere lo schema Struct snello, includendo solo i campi che si useranno per il recupero o la visualizzazione. Evitare di gonfiare i metadati inutilizzati.

Impostare la capacità massima in modo ponderato

Ogni campo Array ha un attributo che specifica il numero massimo di elementi che il campo Array può contenere per ogni entità. Impostare questo attributo in base al limite superiore del caso d'uso. Ad esempio, ci sono 1.000 pezzi di testo per documento o 100 manovre per scena di guida.

Un valore troppo alto spreca memoria e sarà necessario fare alcuni calcoli per determinare il numero massimo di strutture nel campo Array.

Indicizzare i campi vettoriali nelle strutture

L'indicizzazione è obbligatoria per i campi vettoriali, compresi quelli di una collezione e quelli definiti in una struttura. Per i campi vettoriali in una struttura, si deve usare AUTOINDEX o HNSW come tipo di indice e la serie MAX_SIM come tipo di metrica.

Per i dettagli su tutti i limiti applicabili, fare riferimento ai limiti.

Un esempio reale: Modellare il set di dati CoVLA per la guida autonoma

Il set di dati Comprehensive Vision-Language-Action (CoVLA), introdotto da Turing Motors e accettato alla Winter Conference on Applications of Computer Vision (WACV) 2025, fornisce una ricca base per l'addestramento e la valutazione dei modelli Vision-Language-Action (VLA) nella guida autonoma. Ogni punto di dati, di solito un video clip, contiene non solo input visivi grezzi ma anche didascalie strutturate che descrivono:

  • Il comportamento del veicolo ego (ad esempio, "accodarsi a sinistra mentre si accosta al traffico in arrivo"),

  • Gli oggetti rilevati presenti (ad esempio, veicoli in testa, pedoni, semafori).

  • una didascalia della scena a livello di fotogramma.

Questa natura gerarchica e multimodale lo rende un candidato ideale per la funzione Array of Structs. Per maggiori dettagli sul set di dati CoVLA, consultare il sito web del set di dati CoVLA.

Passo 1: mappare il dataset in uno schema di raccolta

Il dataset CoVLA è un dataset di guida multimodale su larga scala che comprende 10.000 videoclip, per un totale di oltre 80 ore di filmati. Campiona i fotogrammi a una frequenza di 20Hz e annota ogni fotogramma con didascalie dettagliate in linguaggio naturale, oltre a informazioni sullo stato del veicolo e sulle coordinate degli oggetti rilevati.

La struttura del dataset è la seguente:

├── 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

Si può notare che la struttura del dataset CoVLA è altamente gerarchica, dividendo i dati raccolti in più file .jsonl, insieme ai video clip nel formato .mp4.

In Milvus, è possibile utilizzare un campo JSON o un campo Array-of-Structs per creare strutture annidate all'interno di uno schema di raccolta. Quando le incorporazioni vettoriali fanno parte del formato annidato, è supportato solo un campo Array-of-Structs. Tuttavia, una struttura all'interno di un array non può a sua volta contenere altre strutture annidate. Per memorizzare il set di dati CoVLA mantenendo le relazioni essenziali, è necessario rimuovere la gerarchia non necessaria e appiattire i dati in modo che si adattino allo schema della raccolta Milvus.

Il diagramma seguente illustra come possiamo modellare questo set di dati utilizzando lo schema illustrato nello schema seguente:

Dataset Model Modello del set di dati

Il diagramma precedente illustra la struttura di un videoclip, che comprende i seguenti campi:

  • video_id serve come chiave primaria, che accetta numeri interi di tipo INT64.

  • states è un corpo JSON grezzo che contiene lo stato del veicolo ego in ogni fotogramma del video corrente.

  • captions è un array di strutture e ogni struttura ha i seguenti campi:

    • frame_id identifica un fotogramma specifico all'interno del video corrente.

    • plain_caption è una descrizione del fotogramma corrente senza l'ambiente circostante, come il tempo, le condizioni della strada, ecc. e plain_cap_vector è il suo corrispondente embedding vettoriale.

    • rich_caption è una descrizione del fotogramma corrente con l'ambiente circostante e rich_cap_vector è il suo corrispondente embedding vettoriale.

    • risk è una descrizione del rischio che il veicolo ego affronta nel fotogramma corrente, e risk_vector è il suo corrispondente vettore embeddings, e

    • Tutti gli altri attributi del frame, come road, weather, is_tunnel, has_pedestrain, ecc...

  • traffic_lights è un corpo JSON che contiene tutti i segnali semaforici identificati nel frame corrente.

  • front_cars è anche un array di strutture che contiene tutte le auto in testa identificate nel frame corrente.

Passo 2: inizializzare gli schemi

Per iniziare, occorre inizializzare gli schemi per una struttura Caption, una struttura Front_cars e la collezione.

  • Inizializzare lo schema per la struttura 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"
    )
    
  • Inizializzare lo schema per la struttura Front Car

    Anche se un'auto anteriore non comporta incorporazioni vettoriali, è comunque necessario includerla come array di Struct, perché la dimensione dei dati supera il massimo per un campo 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"
    )
    
  • Inizializzare lo schema per la collezione

    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"
    )
    

Passo 3: Impostare i parametri dell'indice

Tutti i campi vettoriali devono essere indicizzati. Per indicizzare i campi vettoriali in un elemento Struct, è necessario utilizzare AUTOINDEX o HNSW come tipo di indice e il tipo di metrica della serie MAX_SIM per misurare le somiglianze tra gli elenchi di incorporazioni.

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}
)

Si consiglia di abilitare il JSON shredding per i campi JSON, per accelerare il filtraggio all'interno di questi campi.

Passo 4: Creare una raccolta

Una volta che gli schemi e gli indici sono pronti, è possibile creare la raccolta di destinazione come segue:

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

Passo 5: Inserire i dati

Turing Motos organizza il set di dati CoVLA in più file, tra cui i video grezzi (.mp4), gli stati (states.jsonl), le didascalie (captions.jsonl), i semafori (traffic_lights.jsonl) e le auto frontali (front_cars.jsonl).

È necessario unire i dati di ciascun video clip da questi file e inserire i dati. Di seguito è riportato lo script per unire i dati di uno specifico videoclip.

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
}

Una volta elaborati i dati, è possibile inserirli come segue:

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

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