Группировочный поиск
Группировочный поиск позволяет Milvus группировать результаты поиска по значениям в указанном поле, чтобы собрать данные на более высоком уровне. Например, вы можете использовать базовый поиск ANN, чтобы найти книги, похожие на ту, которую вы рассматриваете, но вы можете использовать группирующий поиск, чтобы найти категории книг, которые могут быть связаны с темами, обсуждаемыми в этой книге. В этой теме описывается использование группирующего поиска, а также основные моменты.
Обзор
Когда сущности в результатах поиска имеют одинаковое значение в скалярном поле, это указывает на то, что они похожи по определенному атрибуту, что может негативно повлиять на результаты поиска.
Предположим, что в коллекции хранится несколько документов (обозначаемых docId). Чтобы сохранить как можно больше семантической информации при преобразовании документов в векторы, каждый документ разбивается на более мелкие, управляемые абзацы (или куски) и хранится как отдельные сущности. Даже если документ разделен на более мелкие части, пользователи часто заинтересованы в том, чтобы определить, какие документы наиболее релевантны их потребностям.
Поиск по аннам
При выполнении поиска по приближенным ближайшим соседям (ANN) в такой коллекции результаты поиска могут включать несколько абзацев из одного и того же документа, что может привести к пропуску других документов, что может не соответствовать предполагаемому сценарию использования.
Группировка поиска
Чтобы улучшить разнообразие результатов поиска, можно добавить параметр group_by_field в запрос на поиск, чтобы включить группировочный поиск. Как показано на рисунке, можно установить group_by_field на docId. Получив этот запрос, Milvus:
Выполнит ANN-поиск на основе предоставленного вектора запроса, чтобы найти все сущности, наиболее похожие на запрос.
Сгруппирует результаты поиска по указанному
group_by_field, напримерdocId.Вернет верхние результаты для каждой группы, как определено параметром
limit, с наиболее похожими сущностями из каждой группы.
По умолчанию группировочный поиск возвращает только одну сущность на группу. Если вы хотите увеличить количество результатов, возвращаемых для каждой группы, вы можете управлять этим с помощью параметров group_size и strict_group_size.
Выполнение группировочного поиска
В этом разделе приведены примеры кода, демонстрирующие использование Grouping Search. В следующем примере предполагается, что коллекция включает поля id, vector, chunk и 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},
]
В поисковом запросе установите значения group_by_field и output_fields на docId. Milvus сгруппирует результаты по указанному полю и вернет наиболее похожую сущность из каждой группы, включая значение docId для каждой возвращенной сущности.
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="my_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("my_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)
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("vector").
WithGroupByField("docId").
WithOutputFields("docId"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
fmt.Println("docId: ", resultSet.GetColumn("docId").FieldData().GetScalars())
}
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"
})
// 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": "my_collection",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3,
"groupingField": "docId",
"outputFields": ["docId"]
}'
В приведенном выше запросе limit=3 указывает на то, что система вернет результаты поиска из трех групп, причем каждая группа будет содержать одну наиболее похожую на вектор запроса сущность.
Настройка размера группы
По умолчанию группировочный поиск возвращает только одну сущность на группу. Если вы хотите получить несколько результатов на группу, настройте параметры group_size и strict_group_size.
# Group search results
res = client.search(
collection_name="my_collection",
data=query_vectors, # query vector
limit=5, # number of groups to return
group_by_field="docId", # grouping field
group_size=2, # p to 2 entities to return from each group
strict_group_size=True, # return exact 2 entities from each group
output_fields=["docId"]
)
FloatVec queryVector = new FloatVec(new float[]{0.14529211512077012f, 0.9147257273453546f, 0.7965055218724449f, 0.7009258593102812f, 0.5605206522382088f});
SearchReq searchReq = SearchReq.builder()
.collectionName("my_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 (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection", // collectionName
5, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("vector").
WithGroupByField("docId").
WithStrictGroupSize(true).
WithGroupSize(2).
WithOutputFields("docId"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
fmt.Println("docId: ", resultSet.GetColumn("docId").FieldData().GetScalars())
}
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: 5,
group_by_field: "docId",
group_size: 2,
strict_group_size: true
})
// 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": "my_collection",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 5,
"groupingField": "docId",
"groupSize":2,
"strictGroupSize":true,
"outputFields": ["docId"]
}'
В примере выше:
group_size: Указывает желаемое количество сущностей, возвращаемых в каждой группе. Например, если задатьgroup_size=2, то каждая группа (или каждыйdocId) в идеале должна возвращать два наиболее похожих абзаца (или фрагмента). Еслиgroup_sizeне задан, система по умолчанию возвращает один результат на группу.strict_group_size: Этот булевский параметр управляет тем, должна ли система строго придерживаться подсчета, заданногоgroup_size. Если заданstrict_group_size=True, система попытается включить в каждую группу точное количество сущностей, заданноеgroup_size(например, два абзаца), если только в этой группе не будет достаточно данных. По умолчанию (strict_group_size=False), системе приоритетнее удовлетворить количество групп, заданное параметромlimit, чем гарантировать, что каждая группа содержит сущностиgroup_size. Такой подход обычно более эффективен в случаях, когда распределение данных неравномерно.
Дополнительные сведения о параметре см. в разделе Поиск.
Соображения
Индексирование: Эта функция группировки работает только для коллекций, проиндексированных этими типами индексов: FLAT, IVF_FLAT, IVF_SQ8, HNSW, HNSW_PQ, HNSW_PRQ, HNSW_SQ, DISKANN, SPARSE_INVERTED_INDEX.
Количество групп: Параметр
limitуправляет количеством групп, из которых возвращаются результаты поиска, а не конкретным количеством сущностей в каждой группе. Установка подходящего значенияlimitпомогает контролировать разнообразие поиска и производительность запросов. Уменьшениеlimitможет снизить затраты на вычисления, если данные распределены плотно или производительность вызывает беспокойство.Сущности на группу: Параметр
group_sizeуправляет количеством сущностей, возвращаемых на группу. Настройка параметраgroup_sizeв зависимости от конкретного случая использования может повысить насыщенность результатов поиска. Однако если данные распределены неравномерно, некоторые группы могут возвращать меньше сущностей, чем указано наgroup_size, особенно в сценариях с ограниченным количеством данных.Строгий размер группы: При использовании
strict_group_size=Trueсистема будет пытаться вернуть указанное количество сущностей (group_size) для каждой группы, если только в этой группе нет достаточного количества данных. Эта настройка обеспечивает постоянное количество сущностей в группе, но может привести к снижению производительности при неравномерном распределении данных или ограниченных ресурсах. Если строгий подсчет сущностей не требуется, установкаstrict_group_size=Falseможет повысить скорость выполнения запросов.Если векторы запроса уже существуют в целевой коллекции, используйте
ids, чтобы не извлекать их перед поиском. Подробнее см. в разделе Поиск по первичному ключу.