milvus-logo
LFAI
Home
  • Guide de l'utilisateur

Champ de type tableau

Le type "tableau" est utilisé pour stocker des champs contenant plusieurs valeurs du même type de données. Il constitue un moyen souple de stocker des attributs comportant plusieurs éléments, ce qui le rend particulièrement utile dans les scénarios où un ensemble de données connexes doit être enregistré. Dans Milvus, vous pouvez stocker des champs de type tableau avec des données vectorielles, ce qui permet de répondre à des exigences plus complexes en matière de recherche et de filtrage.

Par exemple, dans un système de recommandation musicale, un champ Array peut stocker une liste de tags pour une chanson ; dans l'analyse du comportement des utilisateurs, il peut stocker les évaluations des utilisateurs pour les chansons. Vous trouverez ci-dessous un exemple de champ Array typique.

{
  "tags": ["pop", "rock", "classic"],
  "ratings": [5, 4, 3]
}

Dans cet exemple, tags et ratings sont tous deux des champs de type tableau. Le champ tags est un tableau de chaînes représentant des genres de chansons tels que pop, rock et classique, tandis que le champ ratings est un tableau d'entiers représentant les notes attribuées par les utilisateurs à la chanson, allant de 1 à 5. Ces champs Array constituent un moyen souple de stocker des données à valeurs multiples, ce qui facilite l'analyse détaillée lors des requêtes et du filtrage.

Ajout d'un champ Array

Pour utiliser des champs de type tableau dans Milvus, définissez le type de champ approprié lors de la création du schéma de collection. Ce processus comprend

  1. Définir datatype comme étant le type de données Array pris en charge, ARRAY.

  2. Utiliser le paramètre element_type pour spécifier le type de données des éléments du tableau. Il peut s'agir de n'importe quel type de données scalaires pris en charge par Milvus, comme VARCHAR ou INT64. Tous les éléments d'un même tableau doivent être du même type de données.

  3. Utiliser le paramètre max_capacity pour définir la capacité maximale du tableau, c'est-à-dire le nombre maximal d'éléments qu'il peut contenir.

Voici comment définir un schéma de collection qui inclut des champs de type Array.

from pymilvus import MilvusClient, DataType

client = MilvusClient(uri="http://localhost:19530")

schema = client.create_schema(
    auto_id=False,
    enable_dynamic_fields=True,
)

# Add an Array field with elements of type VARCHAR
schema.add_field(field_name="tags", datatype=DataType.ARRAY, element_type=DataType.VARCHAR, max_capacity=10)
# Add an Array field with elements of type INT64
schema.add_field(field_name="ratings", datatype=DataType.ARRAY, element_type=DataType.INT64, max_capacity=5)

# Add primary field
schema.add_field(field_name="pk", datatype=DataType.INT64, is_primary=True)

# Add vector field
schema.add_field(field_name="embedding", datatype=DataType.FLOAT_VECTOR, dim=3)

import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .build());
        
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.setEnableDynamicField(true);

schema.addField(AddFieldReq.builder()
        .fieldName("tags")
        .dataType(DataType.Array)
        .elementType(DataType.VarChar)
        .maxCapacity(10)
        .build());

schema.addField(AddFieldReq.builder()
        .fieldName("ratings")
        .dataType(DataType.Array)
        .elementType(DataType.Int64)
        .maxCapacity(5)
        .build());

schema.addField(AddFieldReq.builder()
        .fieldName("pk")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .build());

schema.addField(AddFieldReq.builder()
        .fieldName("embedding")
        .dataType(DataType.FloatVector)
        .dimension(3)
        .build());

import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const schema = [
  {
    name: "tags",
    data_type: DataType.Array,
    element_type: DataType.VarChar,
    max_capacity: 10,
    max_length: 65535
  },
  {
    name: "rating",
    data_type: DataType.Array,
    element_type: DataType.Int64,
    max_capacity: 5,
  },
  {
    name: "pk",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "embedding",
    data_type: DataType.FloatVector,
    dim: 3,
  },
];

export arrayField1='{
    "fieldName": "tags",
    "dataType": "Array",
    "elementDataType": "VarChar",
    "elementTypeParams": {
        "max_capacity": 10,
        "max_length": 100
    }
}'

export arrayField2='{
    "fieldName": "ratings",
    "dataType": "Array",
    "elementDataType": "Int64",
    "elementTypeParams": {
        "max_capacity": 5
    }
}'

export pkField='{
    "fieldName": "pk",
    "dataType": "Int64",
    "isPrimary": true
}'

export vectorField='{
    "fieldName": "embedding",
    "dataType": "FloatVector",
    "elementTypeParams": {
        "dim": 3
    }
}'

export schema="{
    \"autoID\": false,
    \"fields\": [
        $arrayField1,
        $arrayField2,
        $pkField,
        $vectorField
    ]
}"

Dans cet exemple.

  • tags est un tableau de chaînes avec element_type fixé à VARCHAR, indiquant que les éléments du tableau doivent être des chaînes. max_capacity est fixé à 10, ce qui signifie que le tableau peut contenir jusqu'à 10 éléments.

  • ratings est un tableau d'entiers avec element_type défini sur INT64, indiquant que les éléments doivent être des entiers. max_capacity est défini sur 5, autorisant jusqu'à 5 notations.

  • Nous ajoutons également un champ de clé primaire pk et un champ vectoriel embedding.

Le champ primaire et le champ vectoriel sont obligatoires lorsque vous créez une collection. Le champ primaire identifie chaque entité de manière unique, tandis que le champ vectoriel est essentiel pour la recherche de similarités. Pour plus de détails, reportez-vous aux sections Champ primaire et AutoID, Vecteur dense, Vecteur binaire ou Vecteur clairsemé.

Définir les paramètres d'index

La définition de paramètres d'index pour les champs de type tableau est facultative, mais elle peut améliorer considérablement l'efficacité de la recherche.

Dans l'exemple suivant, nous créons un AUTOINDEX pour le champ tags, ce qui signifie que Milvus créera automatiquement un index scalaire approprié en fonction du type de données.

# Prepare index parameters
index_params = client.prepare_index_params()  # Prepare IndexParams object

index_params.add_index(
    field_name="tags",  # Name of the Array field to index
    index_type="AUTOINDEX",  # Index type
    index_name="inverted_index"  # Index name
)

import io.milvus.v2.common.IndexParam;
import java.util.*;

List<IndexParam> indexes = new ArrayList<>();
indexes.add(IndexParam.builder()
        .fieldName("tags")
        .indexName("inverted_index")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .build());

const indexParams = [{
    index_name: 'inverted_index',
    field_name: 'tags',
    index_type: IndexType.AUTOINDEX,
)];

export indexParams='[
        {
            "fieldName": "tags",
            "indexName": "inverted_index",
            "indexType": "AUTOINDEX"
        }
    ]'

Outre AUTOINDEX, vous pouvez spécifier d'autres types d'index scalaire comme INVERTED ou BITMAP. Pour connaître les types d'index pris en charge, reportez-vous à la section Index scalaires.

De plus, vous devez créer un index pour le champ vectoriel avant de créer la collection. Dans cet exemple, nous utilisons AUTOINDEX pour simplifier la configuration de l'index vectoriel.

# Add vector index
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",  # Use automatic indexing to simplify complex index settings
    metric_type="COSINE"  # Specify similarity metric type, such as L2, COSINE, or IP
)

indexes.add(IndexParam.builder()
        .fieldName("embedding")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.COSINE)
        .build());

 indexParams.push({
    index_name: 'embedding_index',
    field_name: 'embedding',
    index_type: IndexType.AUTOINDEX,
});

export indexParams='[
        {
            "fieldName": "tags",
            "indexName": "inverted_index",
            "indexType": "AUTOINDEX"
        },
        {
            "fieldName": "embedding",
            "metricType": "COSINE",
            "indexType": "AUTOINDEX"
        }
    ]'

Créer une collection

Utilisez les paramètres de schéma et d'index définis pour créer une collection.

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

CreateCollectionReq requestCreate = CreateCollectionReq.builder()
        .collectionName("my_array_collection")
        .collectionSchema(schema)
        .indexParams(indexes)
        .build();
client.createCollection(requestCreate);

client.create_collection({
    collection_name: "my_array_collection",
    schema: schema,
    index_params: indexParams
})

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
    \"collectionName\": \"my_array_collection\",
    \"schema\": $schema,
    \"indexParams\": $indexParams
}"

Insérer des données

Après avoir créé la collection, vous pouvez insérer des données qui comprennent des champs Array.

data = [
    {
        "tags": ["pop", "rock", "classic"],
        "ratings": [5, 4, 3],
        "pk": 1,
        "embedding": [0.12, 0.34, 0.56]
    },
    {
        "tags": ["jazz", "blues"],
        "ratings": [4, 5],
        "pk": 2,
        "embedding": [0.78, 0.91, 0.23]
    },
    {
        "tags": ["electronic", "dance"],
        "ratings": [3, 3, 4],
        "pk": 3,
        "embedding": [0.67, 0.45, 0.89]
    }
]

client.insert(
    collection_name="my_array_collection",
    data=data
)

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;

List<JsonObject> rows = new ArrayList<>();
Gson gson = new Gson();
rows.add(gson.fromJson("{\"tags\": [\"pop\", \"rock\", \"classic\"], \"ratings\": [5, 4, 3], \"pk\": 1, \"embedding\": [0.1, 0.2, 0.3]}", JsonObject.class));
rows.add(gson.fromJson("{\"tags\": [\"jazz\", \"blues\"], \"ratings\": [4, 5], \"pk\": 2, \"embedding\": [0.4, 0.5, 0.6]}", JsonObject.class));
rows.add(gson.fromJson("{\"tags\": [\"electronic\", \"dance\"], \"ratings\": [3, 3, 4], \"pk\": 3, \"embedding\": [0.7, 0.8, 0.9]}", JsonObject.class));

InsertResp insertR = client.insert(InsertReq.builder()
        .collectionName("my_array_collection")
        .data(rows)
        .build());

const data = [
    {
        "tags": ["pop", "rock", "classic"],
        "ratings": [5, 4, 3],
        "pk": 1,
        "embedding": [0.12, 0.34, 0.56]
    },
    {
        "tags": ["jazz", "blues"],
        "ratings": [4, 5],
        "pk": 2,
        "embedding": [0.78, 0.91, 0.23]
    },
    {
        "tags": ["electronic", "dance"],
        "ratings": [3, 3, 4],
        "pk": 3,
        "embedding": [0.67, 0.45, 0.89]
    }
];

client.insert({
  collection_name: "my_array_collection",
  data: data,
});

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
    "data": [
        {
        "tags": ["pop", "rock", "classic"],
        "ratings": [5, 4, 3],
        "pk": 1,
        "embedding": [0.12, 0.34, 0.56]
    },
    {
        "tags": ["jazz", "blues"],
        "ratings": [4, 5],
        "pk": 2,
        "embedding": [0.78, 0.91, 0.23]
    },
    {
        "tags": ["electronic", "dance"],
        "ratings": [3, 3, 4],
        "pk": 3,
        "embedding": [0.67, 0.45, 0.89]
    }       
    ],
    "collectionName": "my_array_collection"
}'

Dans cet exemple.

  • Chaque entrée de données comprend un champ primaire (pk), tandis que tags et ratings sont des champs de type tableau utilisés pour stocker les étiquettes et les évaluations.

  • embedding est un champ vectoriel tridimensionnel utilisé pour les recherches de similarité vectorielle.

Recherche et interrogation

Les champs de tableau permettent un filtrage scalaire pendant les recherches, améliorant ainsi les capacités de recherche vectorielle de Milvus. Vous pouvez effectuer des requêtes basées sur les propriétés des champs de tableau parallèlement aux recherches de similarité vectorielle.

Filtrer les requêtes

Vous pouvez filtrer les données en fonction des propriétés des champs Tableau, par exemple en accédant à un élément spécifique ou en vérifiant si un élément du tableau répond à une certaine condition.

filter = 'ratings[0] < 4'

res = client.query(
    collection_name="my_array_collection",
    filter=filter,
    output_fields=["tags", "ratings", "embedding"]
)

print(res)

# Output
# data: ["{'pk': 3, 'tags': ['electronic', 'dance'], 'ratings': [3, 3, 4], 'embedding': [np.float32(0.67), np.float32(0.45), np.float32(0.89)]}"] 

import io.milvus.v2.service.vector.request.QueryReq;
import io.milvus.v2.service.vector.response.QueryResp;

String filter = "ratings[0] < 4";
QueryResp resp = client.query(QueryReq.builder()
        .collectionName("my_array_collection")
        .filter(filter)
        .outputFields(Arrays.asList("tags", "ratings", "embedding"))
        .build());

System.out.println(resp.getQueryResults());

// Output
//
// [QueryResp.QueryResult(entity={ratings=[3, 3, 4], pk=3, embedding=[0.7, 0.8, 0.9], tags=[electronic, dance]})]

client.query({
    collection_name: 'my_array_collection',
    filter: 'ratings[0] < 4',
    output_fields: ['tags', 'ratings', 'embedding']
});

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/query" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
    "collectionName": "my_array_collection",
    "filter": "ratings[0] < 4",
    "outputFields": ["tags", "ratings", "embedding"]
}'
# {"code":0,"cost":0,"data":[{"embedding":[0.67,0.45,0.89],"pk":3,"ratings":{"Data":{"LongData":{"data":[3,3,4]}}},"tags":{"Data":{"StringData":{"data":["electronic","dance"]}}}}]}

Dans cette requête, Milvus filtre les entités dont le premier élément du tableau ratings est inférieur à 4, en renvoyant les entités qui répondent à la condition.

Recherche vectorielle avec filtrage de tableau

En combinant la similarité vectorielle avec le filtrage de tableau, vous pouvez vous assurer que les données extraites sont non seulement similaires sur le plan sémantique, mais qu'elles répondent également à des conditions spécifiques, ce qui rend les résultats de la recherche plus précis et plus conformes aux besoins de l'entreprise.

filter = 'tags[0] == "pop"'

res = client.search(
    collection_name="my_array_collection",
    data=[[0.3, -0.6, 0.1]],
    limit=5,
    search_params={"params": {"nprobe": 10}},
    output_fields=["tags", "ratings", "embedding"],
    filter=filter
)

print(res)

# Output
# data: ["[{'id': 1, 'distance': 1.1276001930236816, 'entity': {'ratings': [5, 4, 3], 'embedding': [0.11999999731779099, 0.3400000035762787, 0.5600000023841858], 'tags': ['pop', 'rock', 'classic']}}]"]

import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.response.SearchResp;

String filter = "tags[0] == \"pop\"";
SearchResp resp = client.search(SearchReq.builder()
        .collectionName("my_array_collection")
        .annsField("embedding")
        .data(Collections.singletonList(new FloatVec(new float[]{0.3f, -0.6f, 0.1f})))
        .topK(5)
        .outputFields(Arrays.asList("tags", "ratings", "embedding"))
        .filter(filter)
        .build());

System.out.println(resp.getSearchResults());

// Output
//
// [[SearchResp.SearchResult(entity={ratings=[5, 4, 3], embedding=[0.1, 0.2, 0.3], tags=[pop, rock, classic]}, score=-0.2364331, id=1)]]

client.search({
    collection_name: 'my_array_collection',
    data: [0.3, -0.6, 0.1],
    limit: 5,
    output_fields: ['tags', 'ratings', 'embdding'],
    filter: 'tags[0] == "pop"'
});

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
    "collectionName": "my_array_collection",
    "data": [
        [0.3, -0.6, 0.1]
    ],
    "annsField": "embedding",
    "limit": 5,
    "filter": "tags[0] == \"pop\"",
    "outputFields": ["tags", "ratings", "embedding"]
}'

# {"code":0,"cost":0,"data":[{"distance":-0.24793813,"embedding":[0.12,0.34,0.56],"id":1,"ratings":{"Data":{"LongData":{"data":[5,4,3]}}},"tags":{"Data":{"StringData":{"data":["pop","rock","classic"]}}}}]}

Dans cet exemple, Milvus renvoie les 5 entités les plus similaires au vecteur de la requête, le premier élément du tableau tags étant "pop".

En outre, Milvus prend en charge des opérateurs de filtrage de tableau avancés tels que ARRAY_CONTAINS, ARRAY_CONTAINS_ALL, ARRAY_CONTAINS_ANY et ARRAY_LENGTH pour améliorer encore les capacités de recherche. Pour plus de détails, voir Filtrage des métadonnées.

Limites

  • Type de données: Tous les éléments d'un champ de tableau doivent avoir le même type de données, comme spécifié par element_type.

  • Capacité du tableau: Le nombre d'éléments d'un champ de tableau doit être inférieur ou égal à la capacité maximale définie lors de la création du tableau, comme indiqué dans max_capacity.

  • Traitement des chaînes de caractères: Les valeurs des chaînes de caractères dans les champs du tableau sont stockées telles quelles, sans échappement sémantique ni conversion. Par exemple, 'a"b', "a'b", 'a\'b', et "a\"b" sont stockées telles qu'elles ont été saisies, tandis que 'a'b' et "a"b" sont considérées comme des valeurs non valides.

Traduit parDeepL

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started
Feedback

Cette page a-t - elle été utile ?