Diseño de modelos de datos con una serie de estructurasCompatible with Milvus 2.6.4+
Las aplicaciones modernas de IA, especialmente en el Internet de las Cosas (IoT) y la conducción autónoma, suelen razonar sobre eventos ricos y estructurados: una lectura de sensor con su marca de tiempo e incrustación vectorial, un registro de diagnóstico con un código de error y un fragmento de audio, o un segmento de viaje con ubicación, velocidad y contexto de escena. Para ello es necesario que la base de datos admita de forma nativa la ingesta y búsqueda de datos anidados.
En lugar de pedir al usuario que convierta sus eventos estructurales atómicos en modelos de datos planos, Milvus introduce la matriz de estructuras, donde cada estructura de la matriz puede contener escalares y vectores, preservando la integridad semántica.
Por qué Array of Structs
Las aplicaciones modernas de IA, desde la conducción autónoma hasta la recuperación multimodal, dependen cada vez más de datos anidados y heterogéneos. Los modelos de datos planos tradicionales tienen dificultades para representar relaciones complejas como"un documento con muchos fragmentos anotados" o"una escena de conducción con múltiples maniobras observadas". Aquí es donde brilla el tipo de datos Array of Structs de Milvus.
Una matriz de Structs le permite almacenar un conjunto ordenado de elementos estructurados, donde cada Struct contiene su propia combinación de campos escalares e incrustaciones vectoriales. Esto lo hace ideal para:
Datos jerárquicos: Entidades padre con múltiples registros hijo, como un libro con muchos trozos de texto, o un vídeo con muchos fotogramas anotados.
Incrustaciones multimodales: Cada Estructura puede contener múltiples vectores, como incrustaciones de texto más incrustaciones de imagen, junto con metadatos.
Datos temporales o secuenciales: Los Structs de un campo Array representan de forma natural series temporales o eventos paso a paso.
A diferencia de las soluciones tradicionales que almacenan blobs JSON o dividen los datos en múltiples colecciones, la matriz de estructuras proporciona una aplicación nativa del esquema, indexación de vectores y almacenamiento eficiente dentro de Milvus.
Directrices de diseño de esquemas
Además de todas las directrices discutidas en Diseño del Modelo de Datos para la Búsqueda, también debe considerar las siguientes cosas antes de empezar a utilizar una Matriz de Structs en su diseño del modelo de datos.
Definir el esquema Struct
Antes de añadir el campo Array a su colección, defina el esquema Struct interno. Cada campo de la estructura debe tener un tipo explícito, escalar(VARCHAR, INT, BOOLEAN, etc.) o vectorial(FLOAT_VECTOR).
Se aconseja mantener el esquema Struct simplificado incluyendo sólo los campos que se utilizarán para la recuperación o visualización. Evite sobrecargarlo con metadatos no utilizados.
Establezca cuidadosamente la capacidad máxima
Cada campo Array tiene un atributo que especifica el número máximo de elementos que el campo Array puede contener para cada entidad. Establézcalo en función del límite superior de su caso de uso. Por ejemplo, hay 1.000 trozos de texto por documento, o 100 maniobras por escena de conducción.
Un valor excesivamente alto desperdicia memoria, y necesitarás hacer algunos cálculos para determinar el número máximo de Structs en el campo Array.
Indexar campos vectoriales en Structs
La indexación es obligatoria para los campos vectoriales, incluyendo tanto los campos vectoriales de una colección como los definidos en una Struct. Para los campos vectoriales de una Struct, debe utilizar AUTOINDEX o HNSW como tipo de índice y la serie MAX_SIM como tipo de métrica.
Para más detalles sobre todos los límites aplicables, consulte los límites.
Un ejemplo real: Modelado del conjunto de datos CoVLA para la conducción autónoma
El conjunto de datos Comprehensive Vision-Language-Action (CoVLA), presentado por Turing Motors y aceptado en la Winter Conference on Applications of Computer Vision (WACV) 2025, proporciona una rica base para el entrenamiento y la evaluación de modelos de Visión-Lenguaje-Acción (VLA) en la conducción autónoma. Cada punto de datos, que suele ser un clip de vídeo, contiene no sólo información visual sin procesar, sino también subtítulos estructurados que describen:
Los comportamientos del vehículo ego (por ejemplo, "Incorporarse a la izquierda cediendo el paso al tráfico que se aproxima"),
Los objetos presentes detectados (por ejemplo, vehículos precedentes, peatones, semáforos), y
Una leyenda de la escena a nivel de fotograma.
Esta naturaleza jerárquica y multimodal lo convierte en un candidato ideal para la función Array of Structs. Para obtener más información sobre el conjunto de datos CoVLA, consulte el sitio web del conjunto de datos CoVLA.
Paso 1: Asignar el conjunto de datos a un esquema de colección
El conjunto de datos CoVLA es un conjunto de datos de conducción multimodal a gran escala que incluye 10.000 clips de vídeo, con un total de más de 80 horas de metraje. Muestrea fotogramas a una frecuencia de 20 Hz y anota cada fotograma con leyendas detalladas en lenguaje natural, junto con información sobre el estado del vehículo y las coordenadas de los objetos detectados.
La estructura del conjunto de datos es la siguiente:
├── 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
La estructura del conjunto de datos CoVLA es altamente jerárquica, dividiendo los datos recogidos en múltiples archivos .jsonl, junto con los clips de vídeo en el formato .mp4.
En Milvus, puede utilizar un campo JSON o un campo Array-of-Structs para crear estructuras anidadas dentro de un esquema de colección. Cuando las incrustaciones vectoriales forman parte del formato anidado, sólo se admite un campo Array-of-Structs. Sin embargo, una estructura dentro de una matriz no puede contener otras estructuras anidadas. Para almacenar el conjunto de datos CoVLA conservando las relaciones esenciales, es necesario eliminar la jerarquía innecesaria y aplanar los datos para que se ajusten al esquema de la colección Milvus.
El diagrama siguiente ilustra cómo podemos modelar este conjunto de datos utilizando el esquema ilustrado en el esquema siguiente:
Modelo de conjunto de datos
El diagrama anterior ilustra la estructura de un videoclip, que comprende los siguientes campos:
video_idsirve como clave primaria, que acepta enteros del tipo INT64.stateses un cuerpo JSON sin procesar que contiene el estado del vehículo ego en cada fotograma del vídeo actual.captionses una matriz de estructuras, cada una de las cuales tiene los siguientes campos:frame_ididentifica un fotograma específico dentro del vídeo actual.plain_captiones una descripción del fotograma actual sin el entorno ambiental, como el tiempo, el estado de la carretera, etc., yplain_cap_vectores su correspondiente vector incrustado.rich_captiones una descripción del fotograma actual con el entorno ambiental, yrich_cap_vectores su correspondiente vector de incrustación.riskes una descripción del riesgo al que se enfrenta el vehículo ego en la trama actual, yrisk_vectores su correspondiente vector embeddings, yTodos los demás atributos de la trama, como
road,weather,is_tunnel,has_pedestrain, etc.
traffic_lightses un cuerpo JSON que contiene todas las señales de semáforo identificadas en el marco actual.front_carses también un Array of Structs que contiene todos los coches líderes identificados en el frame actual.
Paso 2: Inicializar los esquemas
Para empezar, necesitamos inicializar el esquema para un Struct caption, un Struct front_cars, y la colección.
Inicializar el esquema de la estructura 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" )Inicializar el esquema de la estructura del coche delantero
Aunque un coche delantero no implica incrustaciones vectoriales, es necesario incluirlo como una matriz de Struct porque el tamaño de los datos supera el máximo para 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" )Inicializar el esquema para la colección
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" )
Paso 3: Establecer parámetros de índice
Todos los campos vectoriales deben estar indexados. Para indexar los campos vectoriales en un elemento Struct, debe utilizar AUTOINDEX o HNSW como tipo de índice y el tipo de métrica de la serie MAX_SIM para medir las similitudes entre las listas de incrustación.
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}
)
Se recomienda activar la trituración JSON para los campos JSON para acelerar el filtrado dentro de estos campos.
Paso 4: Crear una colección
Una vez que los esquemas y los índices estén listos, puede crear la colección de destino como se indica a continuación:
client.create_collection(
collection_name="covla_dataset",
schema=schema,
index_params=index_params
)
Paso 5: Insertar los datos
Turing Motos organiza el conjunto de datos CoVLA en múltiples archivos, incluyendo clips de vídeo en bruto (.mp4), estados (states.jsonl), subtítulos (captions.jsonl), semáforos (traffic_lights.jsonl), y coches delanteros (front_cars.jsonl).
Es necesario combinar los datos de cada clip de vídeo de estos archivos e insertar los datos. A continuación se muestra el script para combinar los datos de un videoclip específico.
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 vez que haya procesado los datos como corresponde, puede insertarlos de la siguiente manera:
client.insert(
collection_name="covla_dataset",
data=[data]
)
# {'insert_count': 1, 'ids': ['0a0fc7a5db365174'], 'cost': 0}