Design do modelo de dados com um conjunto de estruturasCompatible with Milvus 2.6.4+
As aplicações modernas de IA, especialmente na Internet das Coisas (IoT) e na condução autónoma, normalmente raciocinam sobre eventos ricos e estruturados: uma leitura de sensor com o seu carimbo de data/hora e incorporação de vetor, um registo de diagnóstico com um código de erro e um trecho de áudio, ou um segmento de viagem com localização, velocidade e contexto de cena. Isto exige que a base de dados suporte nativamente a ingestão e a pesquisa de dados aninhados.
Em vez de pedir ao utilizador que converta os seus eventos estruturais atómicos em modelos de dados planos, Milvus introduz a matriz de estruturas, em que cada estrutura da matriz pode conter escalares e vectores, preservando a integridade semântica.
Porquê uma matriz de estruturas
As aplicações modernas de IA, desde a condução autónoma à recuperação multimodal, dependem cada vez mais de dados aninhados e heterogéneos. Os modelos de dados planos tradicionais têm dificuldade em representar relações complexas, como"um documento com muitas partes anotadas" ou"uma cena de condução com várias manobras observadas". É aqui que o tipo de dados Array of Structs do Milvus se destaca.
Um Array of Structs permite-lhe armazenar um conjunto ordenado de elementos estruturados, em que cada Struct contém a sua própria combinação de campos escalares e de incorporação de vectores. Isto torna-o ideal para:
Dados hierárquicos: Entidades pai com vários registos filho, como um livro com muitos pedaços de texto ou um vídeo com muitos quadros anotados.
Embeddings multimodais: Cada estrutura pode conter vários vectores, como a incorporação de texto e a incorporação de imagem, juntamente com metadados.
Dados temporais ou sequenciais: Structs num campo Array representam naturalmente séries temporais ou eventos passo a passo.
Ao contrário das soluções tradicionais que armazenam blobs JSON ou dividem os dados em várias colecções, o Array of Structs fornece uma aplicação nativa do esquema, indexação de vectores e armazenamento eficiente no Milvus.
Diretrizes de design de esquema
Além de todas as diretrizes discutidas no Design do modelo de dados para pesquisa, você também deve considerar os seguintes aspectos antes de começar a usar um Array of Structs no design do seu modelo de dados.
Definir o esquema Struct
Antes de adicionar o campo Matriz à sua coleção, defina o esquema Struct interno. Cada campo do struct deve ser explicitamente tipado, escalar(VARCHAR, INT, BOOLEAN, etc.) ou vetorial(FLOAT_VECTOR).
Aconselha-se a manter o esquema Struct simples, incluindo apenas campos que serão utilizados para recuperação ou visualização. Evite inchar com metadados não utilizados.
Defina a capacidade máxima com cuidado
Cada campo Array tem um atributo que especifica o número máximo de elementos que o campo Array pode conter para cada entidade. Defina-o com base no limite superior do seu caso de utilização. Por exemplo, existem 1000 blocos de texto por documento ou 100 manobras por cena de condução.
Um valor excessivamente elevado desperdiça memória e terá de efetuar alguns cálculos para determinar o número máximo de Structs no campo Matriz.
Indexar campos vectoriais em Structs
A indexação é obrigatória para campos vectoriais, incluindo os campos vectoriais de uma coleção e os definidos num Struct. Para campos vectoriais num Struct, deve utilizar AUTOINDEX ou HNSW como o tipo de índice e MAX_SIM series como o tipo de métrica.
Para obter detalhes sobre todos os limites aplicáveis, consulte os limites.
Um exemplo do mundo real: Modelação do conjunto de dados CoVLA para condução autónoma
O conjunto de dados Comprehensive Vision-Language-Action (CoVLA), introduzido pela Turing Motors e aceite na Winter Conference on Applications of Computer Vision (WACV) 2025, fornece uma base rica para treinar e avaliar modelos Vision-Language-Action (VLA) na condução autónoma. Cada ponto de dados, que normalmente é um clip de vídeo, contém não só dados visuais em bruto, mas também legendas estruturadas que descrevem:
Os comportamentos do veículo do ego (por exemplo, "Entrar à esquerda enquanto cede ao tráfego em sentido contrário"),
Os objectos detectados presentes (por exemplo, veículos da frente, peões, semáforos), e
Uma legenda da cena ao nível do fotograma.
Esta natureza hierárquica e multimodal torna-o um candidato ideal para a funcionalidade Array of Structs. Para mais informações sobre o conjunto de dados CoVLA, consulte o sítio Web do conjunto de dados CoVLA.
Passo 1: Mapear o conjunto de dados para um esquema de coleção
O conjunto de dados CoVLA é um conjunto de dados de condução multimodal em grande escala que inclui 10.000 clips de vídeo, totalizando mais de 80 horas de filmagens. Amostras de fotogramas a uma taxa de 20 Hz e anotações de cada fotograma com legendas detalhadas em linguagem natural, juntamente com informações sobre o estado do veículo e as coordenadas dos objectos detectados.
A estrutura do conjunto de dados é a seguinte:
├── 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
Pode constatar que a estrutura do conjunto de dados CoVLA é altamente hierárquica, dividindo os dados recolhidos em vários ficheiros .jsonl, juntamente com os clips de vídeo no formato .mp4.
Em Milvus, é possível utilizar um campo JSON ou um campo Array-of-Structs para criar estruturas aninhadas num esquema de coleção. Quando as incorporações de vectores fazem parte do formato aninhado, apenas é suportado um campo Array-of-Structs. No entanto, um campo Struct dentro de uma matriz não pode conter outras estruturas aninhadas. Para armazenar o conjunto de dados CoVLA mantendo as relações essenciais, é necessário remover a hierarquia desnecessária e aplanar os dados para que se ajustem ao esquema da coleção Milvus.
O diagrama abaixo ilustra como podemos modelar este conjunto de dados utilizando o esquema ilustrado no esquema seguinte:
Modelo de conjunto de dados
O diagrama acima ilustra a estrutura de um clip de vídeo, que inclui os seguintes campos:
video_idserve como chave primária, que aceita números inteiros do tipo INT64.statesé um corpo JSON em bruto que contém o estado do veículo ego em cada fotograma do vídeo atual.captionsé uma matriz de estruturas em que cada estrutura tem os seguintes campos:frame_ididentifica um fotograma específico dentro do vídeo atual.plain_captioné uma descrição da imagem atual sem o ambiente, como o clima, as condições da estrada, etc., eplain_cap_vectoré o seu vetor de incorporação correspondente.rich_captioné uma descrição do quadro atual com o ambiente, erich_cap_vectoré o seu vetor de incorporação correspondente.riské uma descrição do risco que o veículo do ego enfrenta na imagem atual, erisk_vectoré o seu correspondente vetor de incorporação, eTodos os outros atributos da imagem, tais como
road,weather,is_tunnel,has_pedestrain, etc...
traffic_lightsé um corpo JSON que contém todos os sinais de semáforo identificados no quadro atual.front_carsé também um Array of Structs que contém todos os carros principais identificados no quadro atual.
Etapa 2: inicializar os esquemas
Para começar, precisamos de inicializar o esquema para uma Caption Struct, uma Front_cars Struct e a coleção.
Inicializar o esquema para a Caption Struct.
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 o esquema para a estrutura Carro da frente
Embora um carro da frente não envolva embeddings vetoriais, você ainda precisa incluí-lo como uma matriz de Struct porque o tamanho dos dados excede o máximo para um 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 o esquema para a coleção
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" )
Etapa 3: definir parâmetros de índice
Todos os campos de vetor devem ser indexados. Para indexar os campos vectoriais num elemento Struct, é necessário utilizar AUTOINDEX ou HNSW como tipo de índice e o tipo de métrica da série MAX_SIM para medir as semelhanças entre as listas de incorporação.
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}
)
É aconselhável ativar a fragmentação JSON para campos JSON para acelerar a filtragem nestes campos.
Etapa 4: criar uma coleção
Quando os esquemas e índices estiverem prontos, você pode criar a coleção de destino da seguinte forma:
client.create_collection(
collection_name="covla_dataset",
schema=schema,
index_params=index_params
)
Etapa 5: inserir os dados
O Turing Motos organiza o conjunto de dados CoVLA em vários arquivos, incluindo clipes de vídeo brutos (.mp4), estados (states.jsonl), legendas (captions.jsonl), semáforos (traffic_lights.jsonl) e carros da frente (front_cars.jsonl).
É necessário fundir as peças de dados para cada clip de vídeo destes ficheiros e inserir os dados. Segue-se o guião para fundir as peças de dados para um clip de vídeo 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
}
Depois de ter processado os dados em conformidade, pode inseri-los da seguinte forma:
client.insert(
collection_name="covla_dataset",
data=[data]
)
# {'insert_count': 1, 'ids': ['0a0fc7a5db365174'], 'cost': 0}