Pencarian Pengelompokan
Pencarian pengelompokan memungkinkan Milvus mengelompokkan hasil pencarian berdasarkan nilai dalam bidang tertentu untuk mengumpulkan data pada tingkat yang lebih tinggi. Sebagai contoh, Anda dapat menggunakan pencarian ANN dasar untuk menemukan buku yang mirip dengan buku yang sedang dicari, tetapi Anda dapat menggunakan pencarian pengelompokan untuk menemukan kategori buku yang mungkin melibatkan topik-topik yang dibahas dalam buku tersebut. Topik ini menjelaskan cara menggunakan Pencarian Pengelompokan bersama dengan pertimbangan-pertimbangan utama.
Ikhtisar
Ketika entitas dalam hasil pencarian memiliki nilai yang sama dalam bidang skalar, hal ini mengindikasikan bahwa entitas tersebut memiliki kemiripan dalam atribut tertentu, yang dapat berdampak negatif pada hasil pencarian.
Anggaplah sebuah koleksi menyimpan beberapa dokumen (dilambangkan dengan docId). Untuk mempertahankan informasi semantik sebanyak mungkin saat mengubah dokumen menjadi vektor, setiap dokumen dipecah menjadi paragraf yang lebih kecil dan mudah diatur (atau potongan) dan disimpan sebagai entitas terpisah. Meskipun dokumen dibagi menjadi bagian yang lebih kecil, pengguna sering kali masih tertarik untuk mengidentifikasi dokumen mana yang paling relevan dengan kebutuhan mereka.
Pencarian Ann
Ketika melakukan pencarian Approximate Nearest Neighbor (ANN) pada koleksi seperti itu, hasil pencarian dapat mencakup beberapa paragraf dari dokumen yang sama, yang berpotensi menyebabkan dokumen lain terlewatkan, yang mungkin tidak selaras dengan kasus penggunaan yang dimaksudkan.
Mengelompokkan Pencarian
Untuk meningkatkan keragaman hasil pencarian, Anda dapat menambahkan parameter group_by_field dalam permintaan pencarian untuk mengaktifkan Pencarian Pengelompokan. Seperti yang ditunjukkan pada diagram, Anda dapat mengatur group_by_field ke docId. Setelah menerima permintaan ini, Milvus akan:
Melakukan pencarian ANN berdasarkan vektor kueri yang disediakan untuk menemukan semua entitas yang paling mirip dengan kueri.
Mengelompokkan hasil pencarian berdasarkan
group_by_fieldyang ditentukan, sepertidocId.Mengembalikan hasil teratas untuk setiap kelompok, seperti yang ditentukan oleh parameter
limit, dengan entitas yang paling mirip dari setiap kelompok.
Secara default, Pencarian Pengelompokan hanya mengembalikan satu entitas per grup. Jika Anda ingin menambah jumlah hasil yang dikembalikan per grup, Anda dapat mengontrolnya dengan parameter group_size dan strict_group_size.
Melakukan Pencarian Pengelompokan
Bagian ini memberikan contoh kode untuk mendemonstrasikan penggunaan Pencarian Pengelompokan. Contoh berikut ini mengasumsikan koleksi mencakup bidang untuk id, vector, chunk, dan 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},
]
Dalam permintaan pencarian, setel group_by_field dan output_fields ke docId. Milvus akan mengelompokkan hasil berdasarkan bidang yang ditentukan dan mengembalikan entitas yang paling mirip dari setiap kelompok, termasuk nilai docId untuk setiap entitas yang dikembalikan.
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" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "my_collection",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3,
"groupingField": "docId",
"outputFields": ["docId"]
}'
Pada permintaan di atas, limit=3 menunjukkan bahwa sistem akan mengembalikan hasil pencarian dari tiga grup, dengan masing-masing grup berisi entitas tunggal yang paling mirip dengan vektor kueri.
Mengonfigurasi ukuran grup
Secara default, Pencarian Pengelompokan hanya mengembalikan satu entitas per grup. Jika Anda menginginkan beberapa hasil per grup, sesuaikan parameter group_size dan 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" \
--header "Request-Timeout: 10" \
-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"]
}'
Dalam contoh di atas:
group_size: Menentukan jumlah entitas yang diinginkan untuk dikembalikan per grup. Misalnya, pengaturangroup_size=2berarti setiap grup (atau setiapdocId) idealnya mengembalikan dua paragraf (atau potongan) yang paling mirip. Jikagroup_sizetidak disetel, sistem secara default mengembalikan satu hasil per grup.strict_group_size: Parameter boolean ini mengontrol apakah sistem harus secara ketat memberlakukan hitungan yang ditetapkan olehgroup_size. Ketikastrict_group_size=True, sistem akan berusaha memasukkan jumlah entitas yang tepat yang ditentukan olehgroup_sizedalam setiap grup (misalnya, dua paragraf), kecuali jika tidak ada cukup data dalam grup tersebut. Secara default (strict_group_size=False), sistem akan memprioritaskan untuk memenuhi jumlah grup yang ditentukan oleh parameterlimit, daripada memastikan setiap grup berisi entitasgroup_size. Pendekatan ini umumnya lebih efisien dalam kasus-kasus di mana distribusi data tidak merata.
Untuk detail parameter tambahan, lihat pencarian.
Pertimbangan
Pengindeksan: Fitur pengelompokan ini hanya berfungsi untuk koleksi yang diindeks dengan jenis indeks berikut: FLAT, IVF_FLAT, IVF_SQ8, HNSW, HNSW_PQ, HNSW_PRQ, HNSW_SQ, DISKANN, SPARSE_INVERTED_INDEX.
Jumlah kelompok: Parameter
limitmengontrol jumlah grup dari mana hasil pencarian dikembalikan, dan bukan jumlah spesifik entitas dalam setiap grup. Menetapkanlimityang sesuai membantu mengontrol keragaman pencarian dan kinerja kueri. Mengurangilimitdapat mengurangi biaya komputasi jika data terdistribusi secara padat atau kinerja menjadi perhatian.Entitas per grup: Parameter
group_sizemengontrol jumlah entitas yang dikembalikan per grup. Menyesuaikangroup_sizeberdasarkan kasus penggunaan Anda dapat meningkatkan kekayaan hasil pencarian. Namun, jika data tidak terdistribusi secara merata, beberapa grup mungkin mengembalikan lebih sedikit entitas daripada yang ditentukan olehgroup_size, terutama dalam skenario data terbatas.Ukuran kelompok yang ketat: Ketika
strict_group_size=True, sistem akan berusaha mengembalikan jumlah entitas yang ditentukan (group_size) untuk setiap grup, kecuali jika tidak ada cukup data dalam grup tersebut. Pengaturan ini memastikan jumlah entitas yang konsisten per grup, tetapi dapat menyebabkan penurunan kinerja dengan distribusi data yang tidak merata atau sumber daya yang terbatas. Jika jumlah entitas yang ketat tidak diperlukan, pengaturanstrict_group_size=Falsedapat meningkatkan kecepatan kueri.Jika vektor kueri sudah ada di koleksi target, pertimbangkan untuk menggunakan
idsalih-alih mengambilnya sebelum pencarian. Untuk detailnya, lihat Pencarian Kunci Utama.