Recherche groupée
Une recherche de regroupement permet à Milvus de regrouper les résultats de la recherche en fonction des valeurs d'un champ spécifié afin d'agréger les données à un niveau supérieur. Par exemple, vous pouvez utiliser une recherche ANN de base pour trouver des livres similaires à celui qui vous intéresse, mais vous pouvez utiliser une recherche de regroupement pour trouver les catégories de livres qui peuvent concerner les sujets abordés dans ce livre. Cette rubrique décrit l'utilisation de la recherche par regroupement, ainsi que les principales considérations à prendre en compte.
Vue d'ensemble
Lorsque des entités dans les résultats de recherche partagent la même valeur dans un champ scalaire, cela indique qu'elles sont similaires dans un attribut particulier, ce qui peut avoir un impact négatif sur les résultats de la recherche.
Supposons qu'une collection stocke plusieurs documents (désignés par docId). Pour conserver autant d'informations sémantiques que possible lors de la conversion des documents en vecteurs, chaque document est divisé en paragraphes (ou morceaux) plus petits et gérables, et stocké en tant qu'entités distinctes. Même si le document est divisé en sections plus petites, les utilisateurs sont souvent intéressés par l'identification des documents les plus pertinents pour leurs besoins.
Recherche ANN
Lorsqu'une recherche par approximation du plus proche voisin (ANN) est effectuée sur une telle collection, les résultats de la recherche peuvent inclure plusieurs paragraphes du même document, ce qui peut entraîner l'oubli d'autres documents, ce qui peut ne pas correspondre au cas d'utilisation prévu.
Recherche groupée
Pour améliorer la diversité des résultats de la recherche, vous pouvez ajouter le paramètre group_by_field
dans la requête de recherche afin d'activer la recherche groupée. Comme le montre le diagramme, vous pouvez définir group_by_field
sur docId
. A la réception de cette demande, Milvus va.
Effectuer une recherche ANN basée sur le vecteur de requête fourni pour trouver toutes les entités les plus similaires à la requête.
Regrouper les résultats de la recherche en fonction de l'adresse
group_by_field
spécifiée, par exempledocId
.Renvoie les meilleurs résultats pour chaque groupe, tel que défini par le paramètre
limit
, avec l'entité la plus similaire de chaque groupe.
Par défaut, la recherche par regroupement ne renvoie qu'une seule entité par groupe. Si vous souhaitez augmenter le nombre de résultats renvoyés par groupe, vous pouvez le contrôler à l'aide des paramètres group_size
et strict_group_size
.
Exécution de la recherche par regroupement
Cette section fournit un exemple de code pour démontrer l'utilisation de la recherche par regroupement. L'exemple suivant suppose que la collection comprend les champs id
, vector
, chunk
et docId
.
[
{"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "chunk": "pink_8682", "docId": 1},
{"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "chunk": "red_7025", "docId": 5},
{"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "chunk": "orange_6781", "docId": 2},
{"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "chunk": "pink_9298", "docId": 3},
{"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "chunk": "red_4794", "docId": 3},
{"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "chunk": "yellow_4222", "docId": 4},
{"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "chunk": "red_9392", "docId": 1},
{"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "chunk": "grey_8510", "docId": 2},
{"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "chunk": "white_9381", "docId": 5},
{"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "chunk": "purple_4976", "docId": 3},
]
Dans la requête de recherche, définissez group_by_field
et output_fields
sur docId
. Milvus regroupera les résultats en fonction du champ spécifié et renverra l'entité la plus similaire de chaque groupe, y compris la valeur de docId
pour chaque entité renvoyée.
from pymilvus import MilvusClient
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
query_vectors = [
[0.14529211512077012, 0.9147257273453546, 0.7965055218724449, 0.7009258593102812, 0.5605206522382088]]
# Group search results
res = client.search(
collection_name="group_search_collection",
data=query_vectors,
limit=3,
group_by_field="docId",
output_fields=["docId"]
)
# Retrieve the values in the `docId` column
doc_ids = [result['entity']['docId'] for result in res[0]]
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.vector.request.SearchReq
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.token("root:Milvus")
.build());
FloatVec queryVector = new FloatVec(new float[]{0.14529211512077012f, 0.9147257273453546f, 0.7965055218724449f, 0.7009258593102812f, 0.5605206522382088f});
SearchReq searchReq = SearchReq.builder()
.collectionName("group_search_collection")
.data(Collections.singletonList(queryVector))
.topK(3)
.groupByFieldName("docId")
.outputFields(Collections.singletonList("docId"))
.build();
SearchResp searchResp = client.search(searchReq);
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
System.out.println("TopK results:");
for (SearchResp.SearchResult result : results) {
System.out.println(result);
}
}
// Output
// TopK results:
// SearchResp.SearchResult(entity={docId=5}, score=0.74767184, id=1)
// SearchResp.SearchResult(entity={docId=2}, score=0.6254269, id=7)
// SearchResp.SearchResult(entity={docId=3}, score=0.3611898, id=3)
// nolint
func ExampleClient_Search_grouping() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "127.0.0.1:19530"
token := "root:Milvus"
cli, err := client.New(ctx, &client.ClientConfig{
Address: milvusAddr,
APIKey: token,
})
if err != nil {
log.Fatal("failed to connect to milvus server: ", err.Error())
}
defer cli.Close(ctx)
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := cli.Search(ctx, client.NewSearchOption(
"my_collection", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithGroupByField("docId"))
if err != nil {
log.Fatal("failed to perform basic ANN search collection: ", err.Error())
}
for _, resultSet := range resultSets {
log.Println("IDs: ", resultSet.IDs)
log.Println("Scores: ", resultSet.Scores)
}
// Output:
// IDs:
// Scores:
}
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "my_collection",
data: [query_vector],
limit: 3,
// highlight-start
group_by_field: "docId"
// highlight-end
})
// Retrieve the values in the `docId` column
var docIds = res.results.map(result => result.entity.docId)
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"collectionName": "group_search_collection",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3,
"groupingField": "docId",
"outputFields": ["docId"]
}'
Dans la requête ci-dessus, limit=3
indique que le système renverra les résultats de recherche de trois groupes, chaque groupe contenant l'entité la plus similaire au vecteur de la requête.
Configurer la taille des groupes
Par défaut, la recherche groupée ne renvoie qu'une seule entité par groupe. Si vous souhaitez obtenir plusieurs résultats par groupe, modifiez les paramètres group_size
et strict_group_size
.
# Group search results
res = client.search(
collection_name="group_search_collection",
data=query_vectors, # Query vector
limit=5, # Top K results to return
group_by_field="docId", # Group by docId
group_size=2, # Return 2 entities per group
strict_group_size=True, # Ensure each group has 2 entities
output_fields=["docId"]
)
FloatVec queryVector = new FloatVec(new float[]{0.14529211512077012f, 0.9147257273453546f, 0.7965055218724449f, 0.7009258593102812f, 0.5605206522382088f});
SearchReq searchReq = SearchReq.builder()
.collectionName("group_search_collection")
.data(Collections.singletonList(queryVector))
.topK(5)
.groupByFieldName("docId")
.groupSize(2)
.strictGroupSize(true)
.outputFields(Collections.singletonList("docId"))
.build();
SearchResp searchResp = client.search(searchReq);
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
System.out.println("TopK results:");
for (SearchResp.SearchResult result : results) {
System.out.println(result);
}
}
// Output
// TopK results:
// SearchResp.SearchResult(entity={docId=5}, score=0.74767184, id=1)
// SearchResp.SearchResult(entity={docId=5}, score=-0.49148706, id=8)
// SearchResp.SearchResult(entity={docId=2}, score=0.6254269, id=7)
// SearchResp.SearchResult(entity={docId=2}, score=0.38515577, id=2)
// SearchResp.SearchResult(entity={docId=3}, score=0.3611898, id=3)
// SearchResp.SearchResult(entity={docId=3}, score=0.19556211, id=4)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "my_collection",
data: [query_vector],
limit: 3,
group_by_field: "docId",
// highlight-start
group_size: 2,
strict_group_size: true
// highlight-end
})
// Retrieve the values in the `docId` column
var docIds = res.results.map(result => result.entity.docId)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"collectionName": "group_search_collection",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 5,
"groupingField": "docId",
"groupSize":2,
"strictGroupSize":true,
"outputFields": ["docId"]
}'
Dans l'exemple ci-dessus.
group_size
: Spécifie le nombre d'entités à renvoyer par groupe. Par exemple, le paramètregroup_size=2
signifie que chaque groupe (ou chaquedocId
) devrait idéalement renvoyer deux des paragraphes (ou morceaux) les plus similaires. Sigroup_size
n'est pas défini, le système renvoie par défaut un résultat par groupe.strict_group_size
: Ce paramètre booléen détermine si le système doit appliquer strictement le décompte défini pargroup_size
. Sistrict_group_size=True
, le système tentera d'inclure le nombre exact d'entités spécifié pargroup_size
dans chaque groupe (par exemple, deux paragraphes), à moins qu'il n'y ait pas assez de données dans ce groupe. Par défaut (strict_group_size=False
), le système donne la priorité au respect du nombre de groupes spécifié par le paramètrelimit
, plutôt que de s'assurer que chaque groupe contient des entitésgroup_size
. Cette approche est généralement plus efficace dans les cas où la distribution des données est inégale.
Pour plus de détails sur les paramètres, reportez-vous à search().
Points à prendre en compte
Nombre de groupes: Le paramètre
limit
contrôle le nombre de groupes à partir desquels les résultats de la recherche sont renvoyés, plutôt que le nombre spécifique d'entités dans chaque groupe. La définition d'une valeur appropriée pourlimit
permet de contrôler la diversité des recherches et les performances des requêtes. La réduction delimit
peut réduire les coûts de calcul si les données sont densément distribuées ou si les performances sont un problème.Entités par groupe: Le paramètre
group_size
contrôle le nombre d'entités renvoyées par groupe. L'ajustement degroup_size
en fonction de votre cas d'utilisation peut augmenter la richesse des résultats de la recherche. Toutefois, si les données sont réparties de manière inégale, certains groupes peuvent renvoyer moins d'entités que celles spécifiées pargroup_size
, en particulier dans les scénarios où les données sont limitées.Taille de groupe stricte: Avec
strict_group_size=True
, le système tentera de renvoyer le nombre d'entités spécifié (group_size
) pour chaque groupe, à moins qu'il n'y ait pas assez de données dans ce groupe. Ce paramètre garantit un nombre cohérent d'entités par groupe, mais peut entraîner une dégradation des performances en cas de distribution inégale des données ou de ressources limitées. Si un nombre strict d'entités n'est pas nécessaire, le paramètrestrict_group_size=False
peut améliorer la vitesse de la requête.