Группировочный поиск

Группировочный поиск позволяет Milvus группировать результаты поиска по значениям в указанном поле, чтобы собрать данные на более высоком уровне. Например, вы можете использовать базовый поиск ANN, чтобы найти книги, похожие на ту, которую вы рассматриваете, но вы можете использовать группирующий поиск, чтобы найти категории книг, которые могут быть связаны с темами, обсуждаемыми в этой книге. В этой теме описывается использование группирующего поиска, а также основные моменты.

Обзор

Когда сущности в результатах поиска имеют одинаковое значение в скалярном поле, это указывает на то, что они похожи по определенному атрибуту, что может негативно повлиять на результаты поиска.

Предположим, что в коллекции хранится несколько документов (обозначаемых docId). Чтобы сохранить как можно больше семантической информации при преобразовании документов в векторы, каждый документ разбивается на более мелкие, управляемые абзацы (или куски) и хранится как отдельные сущности. Даже если документ разделен на более мелкие части, пользователи часто заинтересованы в том, чтобы определить, какие документы наиболее релевантны их потребностям.

Ann Search Поиск по аннам

При выполнении поиска по приближенным ближайшим соседям (ANN) в такой коллекции результаты поиска могут включать несколько абзацев из одного и того же документа, что может привести к пропуску других документов, что может не соответствовать предполагаемому сценарию использования.

Grouping Search Группировка поиска

Чтобы улучшить разнообразие результатов поиска, можно добавить параметр 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, чтобы не извлекать их перед поиском. Подробнее см. в разделе Поиск по первичному ключу.

Попробуйте Managed Milvus бесплатно

Zilliz Cloud работает без проблем, поддерживается Milvus и в 10 раз быстрее.

Начать
Обратная связь

Была ли эта страница полезной?