単一ベクトル検索
データを挿入したら、次のステップはMilvusでコレクションの類似性検索を行うことです。
Milvusでは、コレクション内のベクトルフィールドの数に応じて2種類の検索を行うことができます:
- 単一ベクトル検索:コレクションにベクトルフィールドが1つしかない場合は
search()
メソッドを使用します。このメソッドは、クエリ・ベクタをコレクション内の既存のベクタと比較し、最も近い一致の ID をそれらの間の距離とともに返します。オプションで、結果のベクトル値とメタデータを返すこともできます。 - ハイブリッド検索:2つ以上のベクトルフィールドを持つコレクションには
hybrid_search()
メソッドを使用します。このメソッドは、複数の近似最近傍(ANN)検索要求を実行し、結果を組み合わせて、再ランク付け後に最も関連性の高いマッチを返します。
このガイドでは、Milvusで単一ベクトル検索を実行する方法を中心に説明します。ハイブリッド検索の詳細については、ハイブリッド検索を参照してください。
概要
様々な要件に対応するため、様々な検索タイプがあります:
基本検索:基本検索:単一ベクトル検索、一括ベクトル検索、パーティション検索、出力フィールドを指定した検索。
フィルタリング検索:スカラー・フィールドに基づくフィルタリング基準を適用し、検索結果を絞り込む。
範囲検索:クエリ・ベクトルから特定の距離範囲内にあるベクトルを検索します。
グルーピング検索:特定のフィールドに基づいて検索結果をグループ化し、結果の多様性を確保します。
準備
以下のコードスニペットは、Milvusへの接続を確立し、コレクションを素早くセットアップするために既存のコードを再利用しています。
from pymilvus import MilvusClient
import random
# 1. Set up a Milvus client
client = MilvusClient(
uri="http://localhost:19530"
)
# 2. Create a collection
client.create_collection(
collection_name="quick_setup",
dimension=5,
metric_type="IP"
)
# 3. Insert randomly generated vectors
colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
data = []
for i in range(1000):
current_color = random.choice(colors)
data.append({
"id": i,
"vector": [ random.uniform(-1, 1) for _ in range(5) ],
"color": current_color,
"color_tag": f"{current_color}_{str(random.randint(1000, 9999))}"
})
res = client.insert(
collection_name="quick_setup",
data=data
)
print(res)
# Output
#
# {
# "insert_count": 1000,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(990 more items hidden)"
# ]
# }
# 6.1 Create partitions
client.create_partition(
collection_name="quick_setup",
partition_name="red"
)
client.create_partition(
collection_name="quick_setup",
partition_name="blue"
)
# 6.1 Insert data into partitions
red_data = [ {"id": i, "vector": [ random.uniform(-1, 1) for _ in range(5) ], "color": "red", "color_tag": f"red_{str(random.randint(1000, 9999))}" } for i in range(500) ]
blue_data = [ {"id": i, "vector": [ random.uniform(-1, 1) for _ in range(5) ], "color": "blue", "color_tag": f"blue_{str(random.randint(1000, 9999))}" } for i in range(500) ]
res = client.insert(
collection_name="quick_setup",
data=red_data,
partition_name="red"
)
print(res)
# Output
#
# {
# "insert_count": 500,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(490 more items hidden)"
# ]
# }
res = client.insert(
collection_name="quick_setup",
data=blue_data,
partition_name="blue"
)
print(res)
# Output
#
# {
# "insert_count": 500,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(490 more items hidden)"
# ]
# }
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import com.alibaba.fastjson.JSONObject;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.collection.request.GetLoadStateReq;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;
String CLUSTER_ENDPOINT = "http://localhost:19530";
// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
.uri(CLUSTER_ENDPOINT)
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);
// 2. Create a collection in quick setup mode
CreateCollectionReq quickSetupReq = CreateCollectionReq.builder()
.collectionName("quick_setup")
.dimension(5)
.metricType("IP")
.build();
client.createCollection(quickSetupReq);
GetLoadStateReq loadStateReq = GetLoadStateReq.builder()
.collectionName("quick_setup")
.build();
boolean state = client.getLoadState(loadStateReq);
System.out.println(state);
// Output:
// true
// 3. Insert randomly generated vectors into the collection
List<String> colors = Arrays.asList("green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey");
List<JSONObject> data = new ArrayList<>();
for (int i=0; i<1000; i++) {
Random rand = new Random();
String current_color = colors.get(rand.nextInt(colors.size()-1));
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
InsertReq insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.build();
InsertResp insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 1000}
// 6.1. Create a partition
CreatePartitionReq partitionReq = CreatePartitionReq.builder()
.collectionName("quick_setup")
.partitionName("red")
.build();
client.createPartition(partitionReq);
partitionReq = CreatePartitionReq.builder()
.collectionName("quick_setup")
.partitionName("blue")
.build();
client.createPartition(partitionReq);
// 6.2 Insert data into the partition
data = new ArrayList<>();
for (int i=1000; i<1500; i++) {
Random rand = new Random();
String current_color = "red";
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color", current_color);
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.partitionName("red")
.build();
insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 500}
data = new ArrayList<>();
for (int i=1500; i<2000; i++) {
Random rand = new Random();
String current_color = "blue";
JSONObject row = new JSONObject();
row.put("id", Long.valueOf(i));
row.put("vector", Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat()));
row.put("color", current_color);
row.put("color_tag", current_color + "_" + String.valueOf(rand.nextInt(8999) + 1000));
data.add(row);
}
insertReq = InsertReq.builder()
.collectionName("quick_setup")
.data(data)
.partitionName("blue")
.build();
insertResp = client.insert(insertReq);
System.out.println(JSONObject.toJSON(insertResp));
// Output:
// {"insertCnt": 500}
const { MilvusClient, DataType, sleep } = require("@zilliz/milvus2-sdk-node")
const address = "http://localhost:19530"
// 1. Set up a Milvus Client
client = new MilvusClient({address});
// 2. Create a collection in quick setup mode
await client.createCollection({
collection_name: "quick_setup",
dimension: 5,
metric_type: "IP"
});
// 3. Insert randomly generated vectors
const colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
data = []
for (let i = 0; i < 1000; i++) {
current_color = colors[Math.floor(Math.random() * colors.length)]
data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: current_color,
color_tag: `${current_color}_${Math.floor(Math.random() * 8999) + 1000}`
})
}
var res = await client.insert({
collection_name: "quick_setup",
data: data
})
console.log(res.insert_cnt)
// Output
//
// 1000
//
await client.createPartition({
collection_name: "quick_setup",
partition_name: "red"
})
await client.createPartition({
collection_name: "quick_setup",
partition_name: "blue"
})
// 6.1 Insert data into partitions
var red_data = []
var blue_data = []
for (let i = 1000; i < 1500; i++) {
red_data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: "red",
color_tag: `red_${Math.floor(Math.random() * 8999) + 1000}`
})
}
for (let i = 1500; i < 2000; i++) {
blue_data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: "blue",
color_tag: `blue_${Math.floor(Math.random() * 8999) + 1000}`
})
}
res = await client.insert({
collection_name: "quick_setup",
data: red_data,
partition_name: "red"
})
console.log(res.insert_cnt)
// Output
//
// 500
//
res = await client.insert({
collection_name: "quick_setup",
data: blue_data,
partition_name: "blue"
})
console.log(res.insert_cnt)
// Output
//
// 500
//
基本的な検索
search
リクエストを送信する際、クエリの埋め込みを表す1つ以上のベクトル値と、返す結果の数を示すlimit
値を指定することができます。
データとクエリーベクトルによっては、limit
より少ない結果しか得られないかもしれません。これは、limit
がクエリにマッチする可能性のあるベクトルの数よりも大きい場合に起こります。
単一ベクトル検索
単一ベクトル検索はMilvusのsearch
操作の中で最も単純なもので、与えられたクエリーベクトルに最も類似したベクトルを検索するように設計されています。
単一ベクトル検索を実行するには、ターゲットコレクション名、クエリベクトル、希望する結果数 (limit
) を指定します。この操作は、最も類似したベクトル、そのID、クエリ・ベクトルからの距離からなる結果セットを返します。
以下は、クエリ・ベクトルに最も似ている上位 5 つのエンティティを検索する例です:
# Single vector search
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
# Replace with your query vector
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}} # Search parameters
)
# Convert the output to a formatted JSON string
result = json.dumps(res, indent=4)
print(result)
// 4. Single vector search
List<List<Float>> query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.topK(3) // The number of results to return
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 3, // The number of results to return
})
console.log(res.results)
パラメータ | 説明 |
---|---|
collection_name |
既存のコレクションの名前。 |
data |
Milvusは指定されたベクトル埋め込みに最も類似したベクトル埋め込みを検索する。 |
limit |
このパラメータとparamの offsetを組み合わせて使用すると、ページ分割が可能になる。 この値とparamの offsetの和は、16,384未満でなければならない。 |
search_params |
この操作に固有のパラメータ設定。
|
パラメータ | 説明 |
---|---|
collectionName |
既存のコレクションの名前。 |
data |
Milvusは指定されたものに最も類似したベクトル埋め込みを検索する。 |
topK |
検索結果で返すレコード数。このパラメータはlimitパラメータと同じ構文を使用するので、どちらか一方だけを設定する必要があります。 このパラメータはparamの offsetと組み合わせて使用することで、ページ分割を有効にすることができます。 この値とparamの offsetの合計は16,384未満でなければなりません。 |
パラメータ | 説明 |
---|---|
collection_name |
既存のコレクションの名前。 |
data |
Milvusは指定されたベクトル埋め込みに最も類似したベクトル埋め込みを検索する。 |
limit |
このパラメータとparamの offsetを組み合わせて使用すると、ページ分割が可能になります。 この値とparamの offsetの和は、16,384未満でなければなりません。 |
出力は以下のようになる:
[
[
{
"id": 0,
"distance": 1.4093276262283325,
"entity": {}
},
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {}
},
{
"id": 5,
"distance": 0.7972343564033508,
"entity": {}
},
{
"id": 2,
"distance": 0.5928734540939331,
"entity": {}
}
]
]
{"searchResults": [[
{
"score": 1.263043,
"fields": {
"vector": [
0.9533119,
0.02538395,
0.76714665,
0.35481733,
0.9845762
],
"id": 740
}
},
{
"score": 1.2377806,
"fields": {
"vector": [
0.7411156,
0.08687937,
0.8254139,
0.08370924,
0.99095553
],
"id": 640
}
},
{
"score": 1.1869997,
"fields": {
"vector": [
0.87928146,
0.05324632,
0.6312755,
0.28005534,
0.9542448
],
"id": 455
}
}
]]}
[
{ score: 1.7463608980178833, id: '854' },
{ score: 1.744946002960205, id: '425' },
{ score: 1.7258622646331787, id: '718' }
]
出力には、クエリベクトルに最も近い上位5つの近傍ベクトルが表示され、一意のIDと計算された距離も表示されます。
一括ベクトル検索
一括ベクトル検索は、1回のリクエストで複数のクエリベクトルを検索できるようにすることで、単一ベクトル検索の概念を拡張します。このタイプの検索は、クエリベクターのセットに対して類似したベクトルを検索する必要があるシナリオに最適で、必要な時間と計算リソースを大幅に削減します。
一括ベクトル検索では、data
フィールドに複数のクエリベクトルを含めることができます。システムはこれらのベクトルを並列処理し、クエリ・ベクタごとに個別の結果セットを返し、各セットにはコレクション内で見つかった最も近い一致が含まれます。
以下は、2 つのクエリ・ベクタから、最も類似したエンティティの 2 つの異なるセットを検索する例です:
# Bulk-vector search
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104],
[0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345]
], # Replace with your query vectors
limit=2, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}} # Search parameters
)
result = json.dumps(res, indent=4)
print(result)
// 5. Batch vector search
query_vectors = Arrays.asList(
Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f),
Arrays.asList(0.19886812562848388f, 0.06023560599112088f, 0.6976963061752597f, 0.2614474506242501f, 0.838729485096104f)
);
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.topK(2)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 5. Batch vector search
var query_vectors = [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]
]
res = await client.search({
collection_name: "quick_setup",
data: query_vectors,
limit: 2,
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 1,
"distance": 1.3017789125442505,
"entity": {}
},
{
"id": 7,
"distance": 1.2419954538345337,
"entity": {}
}
], # Result set 1
[
{
"id": 3,
"distance": 2.3358664512634277,
"entity": {}
},
{
"id": 8,
"distance": 0.5642921924591064,
"entity": {}
}
] # Result set 2
]
// Two sets of vectors are returned as expected
{"searchResults": [
[
{
"score": 1.263043,
"fields": {
"vector": [
0.9533119,
0.02538395,
0.76714665,
0.35481733,
0.9845762
],
"id": 740
}
},
{
"score": 1.2377806,
"fields": {
"vector": [
0.7411156,
0.08687937,
0.8254139,
0.08370924,
0.99095553
],
"id": 640
}
}
],
[
{
"score": 1.8654699,
"fields": {
"vector": [
0.4671427,
0.8378432,
0.98844475,
0.82763994,
0.9729997
],
"id": 638
}
},
{
"score": 1.8581753,
"fields": {
"vector": [
0.735541,
0.60140246,
0.86730254,
0.93152493,
0.98603314
],
"id": 855
}
}
]
]}
[
[
{ score: 2.3590476512908936, id: '854' },
{ score: 2.2896690368652344, id: '59' }
[
{ score: 2.664059638977051, id: '59' },
{ score: 2.59483003616333, id: '854' }
]
]
その結果、各クエリベクトルに対して1つずつ、2つの最近傍セットが含まれており、複数のクエリベクトルを一度に処理するバルクベクトル検索の効率性を示している。
パーティション検索
パーティション検索は、検索範囲をコレクションの特定のサブセットまたはパーティションに絞り込みます。これは、データが論理的またはカテゴリー的に分割されている組織化されたデータセットに特に有効で、スキャンするデータ量を減らすことにより、より高速な検索操作を可能にします。
パーティション検索を行うには、検索要求のpartition_names
にターゲット・パーティション名を含めるだけです。これは、search
操作が、指定されたパーティション内のベクトルだけを考慮することを指定します。
以下は、red
内のエンティティを検索する例です:
# 6.2 Search within a partition
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=5,
search_params={"metric_type": "IP", "params": {"level": 1}},
partition_names=["red"]
)
print(res)
// 6.3 Search within partitions
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.partitionNames(Arrays.asList("red"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 6.2 Search within partitions
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
partition_names: ["red"],
limit: 5,
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 16,
"distance": 0.9200337529182434,
"entity": {}
},
{
"id": 14,
"distance": 0.4505271911621094,
"entity": {}
},
{
"id": 15,
"distance": 0.19924677908420563,
"entity": {}
},
{
"id": 17,
"distance": 0.0075093843042850494,
"entity": {}
},
{
"id": 13,
"distance": -0.14609718322753906,
"entity": {}
}
]
]
{"searchResults": [
[
{
"score": 1.1677284,
"fields": {
"vector": [
0.9986977,
0.17964739,
0.49086612,
0.23155272,
0.98438674
],
"id": 1435
}
},
{
"score": 1.1476475,
"fields": {
"vector": [
0.6952647,
0.13417172,
0.91045254,
0.119336545,
0.9338931
],
"id": 1291
}
},
{
"score": 1.0969629,
"fields": {
"vector": [
0.3363194,
0.028906643,
0.6675426,
0.030419827,
0.9735209
],
"id": 1168
}
},
{
"score": 1.0741848,
"fields": {
"vector": [
0.9980543,
0.36063594,
0.66427994,
0.17359233,
0.94954175
],
"id": 1164
}
},
{
"score": 1.0584627,
"fields": {
"vector": [
0.7187005,
0.12674773,
0.987718,
0.3110777,
0.86093885
],
"id": 1085
}
}
],
[
{
"score": 1.8030131,
"fields": {
"vector": [
0.59726167,
0.7054632,
0.9573117,
0.94529945,
0.8664103
],
"id": 1203
}
},
{
"score": 1.7728865,
"fields": {
"vector": [
0.6672442,
0.60448086,
0.9325822,
0.80272985,
0.8861626
],
"id": 1448
}
},
{
"score": 1.7536311,
"fields": {
"vector": [
0.59663296,
0.77831805,
0.8578314,
0.88818026,
0.9030075
],
"id": 1010
}
},
{
"score": 1.7520742,
"fields": {
"vector": [
0.854198,
0.72294194,
0.9245805,
0.86126596,
0.7969224
],
"id": 1219
}
},
{
"score": 1.7452049,
"fields": {
"vector": [
0.96419,
0.943535,
0.87611496,
0.8268136,
0.79786557
],
"id": 1149
}
}
]
]}
[
{ score: 3.0258803367614746, id: '1201' },
{ score: 3.004319190979004, id: '1458' },
{ score: 2.880324363708496, id: '1187' },
{ score: 2.8246407508850098, id: '1347' },
{ score: 2.797295093536377, id: '1406' }
]
次に、blue
でエンティティを検索する:
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=5,
search_params={"metric_type": "IP", "params": {"level": 1}},
partition_names=["blue"]
)
print(res)
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.partitionNames(Arrays.asList("blue"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
partition_names: ["blue"],
limit: 5,
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 20,
"distance": 2.363696813583374,
"entity": {}
},
{
"id": 26,
"distance": 1.0665391683578491,
"entity": {}
},
{
"id": 23,
"distance": 1.066049575805664,
"entity": {}
},
{
"id": 29,
"distance": 0.8353596925735474,
"entity": {}
},
{
"id": 28,
"distance": 0.7484277486801147,
"entity": {}
}
]
]
{"searchResults": [
[
{
"score": 1.1628494,
"fields": {
"vector": [
0.7442872,
0.046407282,
0.71031404,
0.3544345,
0.9819991
],
"id": 1992
}
},
{
"score": 1.1470042,
"fields": {
"vector": [
0.5505825,
0.04367262,
0.9985836,
0.18922359,
0.93255126
],
"id": 1977
}
},
{
"score": 1.1450152,
"fields": {
"vector": [
0.89994013,
0.052991092,
0.8645576,
0.6406729,
0.95679337
],
"id": 1573
}
},
{
"score": 1.1439825,
"fields": {
"vector": [
0.9253267,
0.15890503,
0.7999555,
0.19126713,
0.898583
],
"id": 1552
}
},
{
"score": 1.1029172,
"fields": {
"vector": [
0.95661926,
0.18777144,
0.38115507,
0.14323527,
0.93137646
],
"id": 1823
}
}
],
[
{
"score": 1.8005109,
"fields": {
"vector": [
0.5953582,
0.7794224,
0.9388869,
0.79825854,
0.9197286
],
"id": 1888
}
},
{
"score": 1.7714822,
"fields": {
"vector": [
0.56805456,
0.89422905,
0.88187534,
0.914824,
0.8944365
],
"id": 1648
}
},
{
"score": 1.7561421,
"fields": {
"vector": [
0.83421993,
0.39865613,
0.92319834,
0.42695504,
0.96633124
],
"id": 1688
}
},
{
"score": 1.7553532,
"fields": {
"vector": [
0.89994013,
0.052991092,
0.8645576,
0.6406729,
0.95679337
],
"id": 1573
}
},
{
"score": 1.7543385,
"fields": {
"vector": [
0.16542226,
0.38248396,
0.9888778,
0.80913955,
0.9501492
],
"id": 1544
}
}
]
]}
[
{ score: 2.8421106338500977, id: '1745' },
{ score: 2.838560104370117, id: '1782' },
{ score: 2.8134000301361084, id: '1511' },
{ score: 2.718268871307373, id: '1679' },
{ score: 2.7014894485473633, id: '1597' }
]
red
のデータはblue
のデータとは異なる。したがって、検索結果は、指定されたパーティションに制約され、そのサブセットの固有の特性とデータ分布が反映されます。
出力フィールド付き検索
出力フィールド付き検索では、一致したベクトルのどの属性またはフィールドを検索結果に含めるかを指定でき ます。
リクエストでoutput_fields
を指定すると、特定のフィールドを含む結果を返すことができます。
以下は、color
属性値を含む結果を返す例です:
# Search with output fields
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"] # Output fields to return
)
result = json.dumps(res, indent=4)
print(result)
// 7. Search with output fields
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color"))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 7. Search with output fields
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
output_fields: ["color"],
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 0,
"distance": 1.4093276262283325,
"entity": {
"color": "pink_8682"
}
},
{
"id": 16,
"distance": 1.0159327983856201,
"entity": {
"color": "yellow_1496"
}
},
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 14,
"distance": 0.9803846478462219,
"entity": {
"color": "green_2899"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
}
]
]
{"searchResults": [
[
{
"score": 1.263043,
"fields": {}
},
{
"score": 1.2377806,
"fields": {}
},
{
"score": 1.1869997,
"fields": {}
},
{
"score": 1.1748955,
"fields": {}
},
{
"score": 1.1720343,
"fields": {}
}
]
]}
[
{ score: 3.036271572113037, id: '59', color: 'orange' },
{ score: 3.0267879962921143, id: '1745', color: 'blue' },
{ score: 3.0069446563720703, id: '854', color: 'black' },
{ score: 2.984386682510376, id: '718', color: 'black' },
{ score: 2.916019916534424, id: '425', color: 'purple' }
]
検索結果には、最近傍情報とともに、指定されたフィールドcolor
が含まれ、各マッチングベクトルに対してより豊富な情報が提供されます。
フィルター検索
フィルタリング検索はスカラー・フィルタをベクトル検索に適用し、特定の条件に基づいて検索結果を絞り込むことができます。フィルタ式の詳細については、「Boolean Expression Rules(ブーリアン式のルール)」と「Get & Scalar Query(取得とスカラー・クエリ)」の例を参照してください。
like
演算子の使用
like
演算子は、接頭辞、接尾辞、接尾辞を含むパターンを評価することで、文字列検索を強化します:
- 接頭辞マッチング: 特定の接頭辞で始まる値を検索するには、構文
'like "prefix%"'
を使用します。 - 接尾辞マッチング: 文字列内の任意の場所で特定の文字列を含む値を検索するには、構文
'like "%infix%"'
を使用します。 - 接尾辞マッチング:特定の接尾辞で終わる値を検索するには、構文
'like "%suffix"'
を使用します。
1文字マッチングでは、アンダースコア(_
)が1文字に対するワイルドカードとして機能します(例:'like "y_llow"'
)。
検索文字列の特殊文字
アンダースコア(_
)やパーセント記号(%
)のような特殊文字を含む文字列を検索したい場合、通常、検索パターンではワイルドカードとして使用されます(_
は任意の1文字、%
は任意の連続した文字)。これらの文字をエスケープして、リテラル文字として扱う必要があります。特殊文字をエスケープするにはバックスラッシュ (\
) を使用し、バックスラッシュ自体をエスケープすることを忘れない。例えば
- リテラル・アンダースコアを検索するには、
\\_
を使用します。 - パーセント記号を検索するには、
\\%
を使用します。
したがって、"_version_"
というテキストを検索する必要がある場合は、'like "\\_version\\_"'
と書式を整え、アンダースコアがワイルドカードとしてではなく、検索語の一部として扱われるようにします。
赤で始まる色を持つ結果をフィルタリングします:
# Search with filter
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"], # Output fields to return
filter='color like "red%"'
)
result = json.dumps(res, indent=4)
print(result)
// 8. Filtered search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.filter("color_tag like \"red%\"")
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 8. Filtered search
// 8.1 Filter with "like" operator and prefix wildcard
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
filters: "color_tag like \"red%\"",
output_fields: ["color_tag"]
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
},
{
"id": 6,
"distance": -0.4113418459892273,
"entity": {
"color": "red_9392"
}
}
]
]
{"searchResults": [
[
{
"score": 1.1869997,
"fields": {"color_tag": "red_3026"}
},
{
"score": 1.1677284,
"fields": {"color_tag": "red_9030"}
},
{
"score": 1.1476475,
"fields": {"color_tag": "red_3744"}
},
{
"score": 1.0969629,
"fields": {"color_tag": "red_4168"}
},
{
"score": 1.0741848,
"fields": {"color_tag": "red_9678"}
}
]
]}
[
{ score: 2.5080761909484863, id: '1201', color_tag: 'red_8904' },
{ score: 2.491129159927368, id: '425', color_tag: 'purple_8212' },
{ score: 2.4889798164367676, id: '1458', color_tag: 'red_6891' },
{ score: 2.42964243888855, id: '724', color_tag: 'black_9885' },
{ score: 2.4004223346710205, id: '854', color_tag: 'black_5990' }
]
文字列のどこかにll の 文字が含まれる結果をフィルタリングする:
# Infix match on color field
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=5, # Max. number of search results to return
search_params={"metric_type": "IP", "params": {}}, # Search parameters
output_fields=["color"], # Output fields to return
filter='color like "%ll%"' # Filter on color field, infix match on "ll"
)
result = json.dumps(res, indent=4)
print(result)
// 8. Filtered search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.filter("color like \"%ll%\"")
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 8. Filtered search
// 8.1 Filter with "like" operator and prefix wildcard
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
filters: "color_tag like \"%ll%\"",
output_fields: ["color_tag"]
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 5,
"distance": 0.7972343564033508,
"entity": {
"color": "yellow_4222"
}
}
]
]
{"searchResults": [
[
{
"score": 1.1869997,
"fields": {"color_tag": "yellow_4222"}
}
]
]}
[
{ score: 2.5080761909484863, id: '1201', color_tag: 'yellow_4222' }
]
範囲検索
範囲検索では、クエリ・ベクトルから指定した距離範囲内にあるベクトルを見つけることができます。
radius
とオプションでrange_filter
を設定することで、クエリ・ベクトルにある程度似ているベクトルも含めるように検索の幅を調整することができ、マッチする可能性のあるベクトルをより包括的に表示することができます。
radius
:検索空間の外側の境界を定義します。クエリ・ベクトルからこの距離内にあるベクトルだけが、マッチする可能性があるとみなされます。range_filter
:radius
は検索の外枠を設定しますが、range_filter
はオプションで内側の境界を定義するために使用することができます。
# Conduct a range search
search_params = {
"metric_type": "IP",
"params": {
"radius": 0.8, # Radius of the search circle
"range_filter": 1.0 # Range filter to filter out vectors that are not within the search circle
}
}
res = client.search(
collection_name="quick_setup", # Replace with the actual name of your collection
data=[[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]],
limit=3, # Max. number of search results to return
search_params=search_params, # Search parameters
output_fields=["color"], # Output fields to return
)
result = json.dumps(res, indent=4)
print(result)
// 9. Range search
query_vectors = Arrays.asList(Arrays.asList(0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f));
searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(query_vectors)
.outputFields(Arrays.asList("color_tag"))
.searchParams(Map.of("radius", 0.1, "range", 1.0))
.topK(5)
.build();
searchResp = client.search(searchReq);
System.out.println(JSONObject.toJSON(searchResp));
// 9. Range search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "quick_setup",
data: [query_vector],
limit: 5,
params: {
radius: 0.1,
range: 1.0
},
output_fields: ["color_tag"]
})
console.log(res.results)
出力は以下のようになる:
[
[
{
"id": 4,
"distance": 0.9902134537696838,
"entity": {
"color": "red_4794"
}
},
{
"id": 14,
"distance": 0.9803846478462219,
"entity": {
"color": "green_2899"
}
},
{
"id": 1,
"distance": 0.8519943356513977,
"entity": {
"color": "red_7025"
}
}
]
]
{"searchResults": [
[
{
"score": 1.263043,
"fields": {"color_tag": "green_2052"}
},
{
"score": 1.2377806,
"fields": {"color_tag": "purple_3709"}
},
{
"score": 1.1869997,
"fields": {"color_tag": "red_3026"}
},
{
"score": 1.1748955,
"fields": {"color_tag": "black_1646"}
},
{
"score": 1.1720343,
"fields": {"color_tag": "green_4853"}
}
]
]}
[
{ score: 2.3387961387634277, id: '718', color_tag: 'black_7154' },
{ score: 2.3352415561676025, id: '1745', color_tag: 'blue_8741' },
{ score: 2.290485382080078, id: '1408', color_tag: 'red_2324' },
{ score: 2.285870313644409, id: '854', color_tag: 'black_5990' },
{ score: 2.2593345642089844, id: '1309', color_tag: 'red_8458' }
]
返されるすべてのエンティティの距離が、クエリベクトルから0.8~1.0の範囲内にあることがわかります。
radius
とrange_filter
のパラメータ設定は、使用するメトリック・タイプによって異なります。
メトリックタイプ | 特性 | 範囲検索の設定 |
---|---|---|
L2 | L2 距離が小さいほど、類似度が高いことを示します。 | 最も近いベクトルを結果から除外するには、次のようにします:range_filter <= distance <radius |
IP | IP距離が大きいほど類似度が高い. | 最も近いベクトルを結果から除外するには、次のようにします:radius < distance <= 。range_filter |
COSINE | コサイン値が大きいほど類似度が高いことを示します。 | 最も近いベクトルを結果から除外するには、次のようにします:radius < distance <=。range_filter |
JACCARD | Jaccard距離が小さいほど、類似度が高いことを示す。 | 最も近いベクトルを結果から除外するには、次のようにします:range_filter <= distance <radius |
HAMMING | ハミング距離が小さいほど、類似度が高いことを示します。 | 最も近いベクトルを結果から除外するには、次のようにします:range_filter <= distance <radius |
距離メトリックの種類については、類似度メトリックを参照してください。
グループ化検索
Milvusでは、検索結果の網羅性と精度を向上させるためにグループ化検索が設計されています。
RAGのシナリオを考えてみましょう。ここでは、ロードされた文書が様々なパッセージに分割され、各パッセージが1つのベクトル埋め込みで表現されています。ユーザはLLMを正確に促すために最も関連性の高い文章を見つけたい。Milvusの通常の検索機能はこの要求を満たすことができるが、検索結果が非常に偏ったものになる可能性がある:ほとんどのパッセージは数個の文書からしか得られず、検索結果の包括性は非常に低い。これは、LLMが提供する検索結果の正確さ、あるいは正しさを著しく損ない、LLM利用者の経験に悪影響を与える可能性がある。
グループ化検索は、この問題を効果的に解決することができます。group_by_fieldとgroup_sizeを渡すことにより、Milvusユーザーは検索結果をいくつかのグループに分け、各グループのエンティティ数が特定のgroup_sizeを超えないようにすることができる。この機能により、検索結果の網羅性と公平性が大幅に向上し、LLM出力の品質が顕著に改善されます。
検索結果をフィールドごとにグループ化するコード例を以下に示す:
# Connect to Milvus
client = MilvusClient(uri='http://localhost:19530') # Milvus server address
# Load data into collection
client.load_collection("group_search") # Collection name
# Group search results
res = client.search(
collection_name="group_search", # Collection name
data=[[0.14529211512077012, 0.9147257273453546, 0.7965055218724449, 0.7009258593102812, 0.5605206522382088]], # Query vector
search_params={
"metric_type": "L2",
"params": {"nprobe": 10},
}, # Search parameters
limit=5, # Max. number of groups to return
group_by_field="doc_id", # Group results by document ID
group_size=2, # returned at most 2 passages per document, the default value is 1
group_strict_size=True, # ensure every group contains exactly 3 passages
output_fields=["doc_id", "passage_id"]
)
# Retrieve the values in the `doc_id` column
doc_ids = [result['entity']['doc_id'] for result in res[0]]
passage_ids = [result['entity']['passage_id'] for result in res[0]]
print(doc_ids)
print(passage_ids)
出力は以下のようになる:
["doc_11", "doc_11", "doc_7", "doc_7", "doc_3", "doc_3", "doc_2", "doc_2", "doc_8", "doc_8"]
[5, 10, 11, 10, 9, 6, 5, 4, 9, 2]
与えられた出力では、各文書に対してちょうど2つの文章が検索され、合計で5つの文書が結果を構成していることがわかる。
比較のために、グループ関連のパラメーターをコメントアウトして、通常の検索を行ってみよう:
# Connect to Milvus
client = MilvusClient(uri='http://localhost:19530') # Milvus server address
# Load data into collection
client.load_collection("group_search") # Collection name
# Search without `group_by_field`
res = client.search(
collection_name="group_search", # Collection name
data=query_passage_vector, # Replace with your query vector
search_params={
"metric_type": "L2",
"params": {"nprobe": 10},
}, # Search parameters
limit=5, # Max. number of search results to return
# group_by_field="doc_id", # Group results by document ID
# group_size=2,
# group_strict_size=True,
output_fields=["doc_id", "passage_id"]
)
# Retrieve the values in the `doc_id` column
doc_ids = [result['entity']['doc_id'] for result in res[0]]
passage_ids = [result['entity']['passage_id'] for result in res[0]]
print(doc_ids)
print(passage_ids)
出力は以下のようになる:
["doc_11", "doc_11", "doc_11", "doc_11", "doc_11"]
[1, 10, 3, 12, 9]
与えられた出力では、"doc_11 "が検索結果を完全に支配し、他の文書からの質の高いパラグラフを覆い隠していることが観察できる。
もう一つ注意すべき点があります。デフォルトでは、grouping_searchは十分なグループ数があれば即座に結果を返しますが、その結果、各グループの検索結果数がgroup_sizeを満たさないことがあります。各グループの検索結果数を気にするのであれば、上のコードのようにgroup_strict_size=Trueを設定してください。これにより、Milvusは各グループで十分な結果を得ようと努力するようになりますが、パフォーマンスは若干低下します。
制限事項
インデックス作成:このグループ化機能はHNSW、IVF_FLAT、またはFLATタイプでインデックスされたコレクションに対してのみ機能します。詳細については、メモリ内インデックスを参照してください。
ベクトル:現在、グループ化検索はBINARY_VECTOR型のベクトル・フィールドをサポートしていません。データ型の詳細については、サポートされるデータ型を参照してください。
フィールド:現在のところ、グループ化検索では1つの列しか使用できません。
group_by_field
コンフィグで複数のフィールド名を指定することはできません。 また、グループ化検索は、JSON、FLOAT、DOUBLE、ARRAY、またはvectorフィールドのデータ型とは互換性がありません。パフォーマンスへの影響:クエリ・ベクタ数が増えるとパフォーマンスが低下することに注意してください。CPUコア2個、メモリ8GBのクラスタを例にとると、グルーピング検索の実行時間は入力クエリベクタ数に比例して増加します。
検索パラメータ
範囲検索を除く上記の検索では、デフォルトの検索パラメータが適用されます。通常の場合、検索パラメータを手動で設定する必要はありません。
# In normal cases, you do not need to set search parameters manually
# Except for range searches.
search_parameters = {
'metric_type': 'L2',
'params': {
'nprobe': 10,
'level': 1,
'radius': 1.0
'range_filter': 0.8
}
}
次の表は、検索パラメータで設定可能なすべての一覧です。
パラメータ名 | パラメータ説明 |
---|---|
metric_type | ベクトル埋め込み間の類似度を測定する方法。 指定可能な値は IP ,L2 ,COSINE ,JACCARD ,HAMMING で、デフォルトは読み込まれたインデックス・ファイルの値。 |
params.nprobe | 検索中にクエリーするユニット数。 値は[1, nlist[1]]の範囲です。 |
params.level | 検索精度レベル。 指定可能な値は 1 、2 、および3 で、デフォルトは1 です。値を高くすると、より正確な結果が得られますが、パフォーマンスは低下します。 |
params.radius | 検索空間の外側の境界を定義します。 値の範囲は metric_type パラメータによって決まります。たとえば、metric_type がL2 に設定されている場合、有効な値域は[0, ∞] です。metric_type がCOSINE に設定されている場合、有効な値域は[-1, 1] です。詳細については、「類似度メトリクス」を参照のこと。 |
params.range_filter | radius は検索の外側の限界を設定しますが、range_filter はオプションで内側の境界を定義するために使用することができ、ベクターが一致とみなされるために必要な距離範囲を作成します。値の範囲は metric_type パラメータによって決定されます。例えば、metric_type がL2 に設定されている場合、有効な値の範囲は[0, ∞] です。metric_type がCOSINE に設定されている場合、有効な値の範囲は[-1, 1] です。詳細については、「類似度メトリクス」を参照してください。 |
注釈
[1] インデックス作成後のクラスタ単位数。Milvusはコレクションのインデックスを作成する際、ベクトルデータを複数のクラスタ単位に分割します。
[2] 検索で返すエンティティの数。