단일 벡터 검색
데이터를 삽입하고 나면 다음 단계는 Milvus에서 컬렉션에 대한 유사도 검색을 수행하는 것입니다.
Milvus에서는 컬렉션의 벡터 필드 수에 따라 두 가지 유형의 검색을 수행할 수 있습니다:
- 단일 벡터 검색: 컬렉션에 벡터 필드가 하나만 있는 경우, 가장 유사한 엔티티를 찾기 위해
search()
메서드를 사용하여 가장 유사한 엔티티를 찾습니다. 이 방법은 쿼리 벡터를 컬렉션의 기존 벡터와 비교하여 가장 가까운 일치 항목의 ID와 그 사이의 거리를 반환합니다. 선택적으로 결과의 벡터 값과 메타데이터도 반환할 수 있습니다. - 하이브리드 검색: 두 개 이상의 벡터 필드가 있는 컬렉션의 경우에는
hybrid_search()
메서드를 사용합니다. 이 방법은 여러 개의 근사 이웃(ANN) 검색 요청을 수행하고 그 결과를 결합하여 순위를 다시 매긴 후 가장 관련성이 높은 일치 항목을 반환합니다.
이 가이드는 Milvus에서 단일 벡터 검색을 수행하는 방법에 중점을 두고 있습니다. 하이브리드 검색에 대한 자세한 내용은 하이브리드 검색을 참조하세요.
개요
다양한 요구 사항을 충족하는 다양한 검색 유형이 있습니다:
기본 검색: 단일 벡터 검색, 일괄 벡터 검색, 파티션 검색, 지정된 출력 필드를 사용한 검색이 포함됩니다.
필터링된 검색: 스칼라 필드를 기반으로 필터링 기준을 적용하여 검색 결과를 구체화합니다.
범위 검색: 쿼리 벡터로부터 특정 거리 범위 내의 벡터를 찾습니다.
그룹화 검색: 특정 필드를 기준으로 검색 결과를 그룹화하여 결과의 다양성을 보장합니다.
준비 사항
아래 코드 스니펫은 기존 코드를 재구성하여 Milvus에 연결하고 컬렉션을 빠르게 설정합니다.
# 1. Set up a Milvus client
client = MilvusClient(
uri=CLUSTER_ENDPOINT,
token=TOKEN
)
# 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
요청을 보낼 때 쿼리 임베딩을 나타내는 하나 이상의 벡터 값과 반환할 결과의 수를 나타내는 limit
값을 제공할 수 있습니다.
데이터와 쿼리 벡터에 따라 limit
결과보다 적은 수의 결과를 얻을 수 있습니다. 이는 limit
이 쿼리에 대해 가능한 일치하는 벡터의 수보다 클 때 발생합니다.
단일 벡터 검색
단일 벡터 검색은 주어진 쿼리 벡터와 가장 유사한 벡터를 찾기 위해 고안된 Milvus의 가장 간단한 형태의 search
연산입니다.
단일 벡터 검색을 수행하려면 대상 컬렉션 이름, 쿼리 벡터 및 원하는 결과 수를 지정합니다(limit
). 이 작업은 쿼리 벡터와 가장 유사한 벡터, 해당 벡터의 ID 및 거리로 구성된 결과 집합을 반환합니다.
다음은 쿼리 벡터와 가장 유사한 상위 5개 엔티티를 검색하는 예제입니다:
# Single vector search
res = client.search(
collection_name="test_collection", # 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 |
반환할 총 엔티티 수. 이 파라미터를 파라미터의 오프셋과 함께 사용하여 페이지 매김을 활성화할 수 있습니다. 이 값과 파라미터의 오프셋의 합은 16,384보다 작아야 합니다. |
search_params |
이 작업과 관련된 매개변수 설정
|
파라미터 | 설명 |
---|---|
collectionName |
기존 컬렉션의 이름입니다. |
data |
벡터 임베딩의 목록. Milvus는 지정된 벡터 임베딩과 가장 유사한 벡터 임베딩을 검색합니다. |
topK |
검색 결과에서 반환할 레코드 수입니다. 이 매개변수는 limit 매개변수와 동일한 구문을 사용하므로 둘 중 하나만 설정해야 합니다. 이 매개변수를 offset in param과 함께 사용하여 페이지 매김을 활성화할 수 있습니다. 이 값과 offset in param의 합은 16,384보다 작아야 합니다. |
파라미터 | 설명 |
---|---|
collection_name |
기존 컬렉션의 이름입니다. |
data |
벡터 임베딩의 목록. Milvus는 지정된 벡터 임베딩과 가장 유사한 벡터 임베딩을 검색합니다. |
limit |
반환할 총 엔티티 수. 이 매개변수를 파라미터의 오프셋과 함께 사용하여 페이지 매김을 활성화할 수 있습니다. 이 값과 파라미터의 오프셋의 합은 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와 계산된 거리를 포함하여 표시됩니다.
대량 벡터 검색
대량 벡터 검색은 단일 요청으로 여러 쿼리 벡터를 검색할 수 있도록 하여 단일 벡터 검색 개념을 확장한 것입니다. 이 유형의 검색은 쿼리 벡터 집합에 대해 유사한 벡터를 찾아야 하는 시나리오에 이상적이며, 필요한 시간과 계산 리소스를 크게 줄여줍니다.
대량 벡터 검색에서는 data
필드에 여러 개의 쿼리 벡터를 포함할 수 있습니다. 시스템은 이러한 벡터를 병렬로 처리하여 각 쿼리 벡터에 대해 별도의 결과 집합을 반환하며, 각 집합에는 컬렉션 내에서 발견된 가장 가까운 일치 항목이 포함되어 있습니다.
다음은 두 개의 쿼리 벡터에서 가장 유사한 엔티티의 서로 다른 두 집합을 검색하는 예제입니다:
# Bulk-vector search
res = client.search(
collection_name="test_collection", # 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' }
]
]
결과에는 각 쿼리 벡터에 대해 하나씩 두 개의 가장 가까운 이웃 세트가 포함되어 있어 여러 쿼리 벡터를 한 번에 처리하는 대량 벡터 검색의 효율성을 보여줍니다.
파티션 검색
파티션 검색은 컬렉션의 특정 하위 집합이나 파티션으로 검색 범위를 좁혀줍니다. 이 기능은 데이터가 논리적 또는 범주별 구분으로 세분화된 조직화된 데이터 세트에 특히 유용하며, 검색할 데이터의 양을 줄여 검색 작업을 더 빠르게 수행할 수 있게 해줍니다.
파티션 검색을 수행하려면 검색 요청의 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="test_collection", # 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
가 포함되어 일치하는 각 벡터에 대해 더 풍부한 정보를 제공합니다.
필터 검색
필터링된 검색은 벡터 검색에 스칼라 필터를 적용하여 특정 기준에 따라 검색 결과를 구체화할 수 있도록 해줍니다. 필터 표현식에 대한 자세한 내용은 부울 표현식 규칙에서, 예제는 가져오기 및 스칼라 쿼리에서 확인할 수 있습니다.
like
연산자 사용
like
연산자는 접두사, 접두사, 접미사 등의 패턴을 평가하여 문자열 검색을 향상시킵니다:
- 접두사 일치: 특정 접두사로 시작하는 값을 찾으려면
'like "prefix%"'
구문을 사용합니다. - 접두사 일치: 문자열 내에서 특정 문자 시퀀스가 포함된 값을 찾으려면
'like "%infix%"'
구문을 사용합니다. - 접미사 일치: 특정 접미사로 끝나는 값을 찾으려면
'like "%suffix"'
구문을 사용합니다.
단일 문자 일치의 경우 밑줄(_
)은 한 문자에 대한 와일드카드 역할을 합니다(예: 'like "y_llow"'
).
검색 문자열의 특수 문자
일반적으로 검색 패턴에서 와일드카드로 사용되는 밑줄(_
) 또는 퍼센트 기호(%
)와 같은 특수 문자가 포함된 문자열을 검색하려면(단일 문자의 경우_
, 문자 시퀀스의 경우 %
) 이러한 문자를 이스케이프 처리하여 리터럴 문자로 취급해야 합니다. 특수 문자를 이스케이프하려면 백슬래시(\
)를 사용하고, 백슬래시 자체도 이스케이프해야 한다는 점을 잊지 마세요. 예를 들어
- 리터럴 밑줄을 검색하려면
\\_
을 사용합니다. - 리터럴 퍼센트 기호를 검색하려면
\\%
을 사용합니다.
따라서 "_version_"
이라는 텍스트를 검색해야 하는 경우 쿼리 형식을 'like "\\_version\\_"'
으로 지정하여 밑줄이 와일드카드가 아닌 검색어의 일부로 취급되도록 해야 합니다.
색 앞에 빨간색이 붙은 결과를 필터링합니다:
# Search with filter
res = client.search(
collection_name="test_collection", # 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="test_collection", # 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="test_collection", # 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 <= 거리 < radius |
IP | IP 거리가 클수록 유사성이 높음을 나타냅니다. | 가장 가까운 벡터를 결과에서 제외하려면radius <거리 <=를 확인하세요. range_filter |
COSINE | 코사인 값이 클수록 유사도가 높음을 나타냅니다. | 가장 가까운 벡터를 결과에서 제외하려면radius <거리 <=를 확인하세요. range_filter |
JACCARD | 자카드 거리가 작을수록 유사도가 높음을 나타냅니다. | 결과에서 가장 가까운 벡터를 제외하려면range_filter <= 거리 <= 다음을 확인합니다. radius |
HAMMING | 해밍 거리가 작을수록 유사도가 높음을 나타냅니다. | 결과에서 가장 가까운 벡터를 제외하려면range_filter <= 거리 <를 확인하세요. radius |
거리 메트릭 유형에 대해 자세히 알아보려면 유사성 메트릭을 참조하세요.
그룹 검색
Milvus에서 특정 필드별로 그룹 검색을 하면 결과에서 동일한 필드 항목의 중복을 피할 수 있습니다. 특정 필드에 대한 다양한 결과 집합을 얻을 수 있습니다.
각 문서가 여러 구절로 나뉘어져 있는 문서 모음을 생각해 보세요. 각 구절은 하나의 벡터 임베딩으로 표현되며 하나의 문서에 속합니다. 유사한 구절 대신 관련성 있는 문서를 찾으려면 search()
옵션에 group_by_field
인수를 포함하여 문서 ID별로 결과를 그룹화할 수 있습니다. 이렇게 하면 같은 문서에서 분리된 구절이 아닌 가장 관련성이 높고 고유한 문서를 반환하는 데 도움이 됩니다.
다음은 필드별로 검색 결과를 그룹화하는 예제 코드입니다:
# 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=10, # Max. number of search results to return
group_by_field="doc_id", # Group results by document ID
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]]
print(doc_ids)
출력은 다음과 비슷합니다:
[5, 10, 1, 7, 9, 6, 3, 4, 8, 2]
주어진 출력에서 반환된 엔티티에 중복된 doc_id
값이 포함되어 있지 않음을 확인할 수 있습니다.
비교를 위해 group_by_field
을 주석 처리하고 일반 검색을 수행해 보겠습니다:
# 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=10, # Max. number of search results to return
# group_by_field="doc_id", # Group results by document ID
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]]
print(doc_ids)
출력은 다음과 비슷합니다:
[1, 10, 3, 10, 1, 9, 4, 4, 8, 6]
주어진 출력에서 반환된 엔티티에 중복된 doc_id
값이 포함되어 있음을 확인할 수 있습니다.
제한 사항
인덱싱: 이 그룹화 기능은 HNSW, IVF_FLAT 또는 FLAT 유형으로 인덱싱된 컬렉션에서만 작동합니다. 자세한 내용은 인메모리 인덱스를 참조하세요.
벡터: 현재 그룹화 검색은 BINARY_VECTOR 유형의 벡터 필드를 지원하지 않습니다. 데이터 유형에 대한 자세한 내용은 지원되는 데이터 유형을 참조하세요.
필드: 현재 그룹 검색에서는 단일 열만 허용됩니다.
group_by_field
구성에서는 여러 필드 이름을 지정할 수 없습니다. 또한 그룹화 검색은 JSON, FLOAT, DOUBLE, ARRAY 또는 벡터 필드의 데이터 유형과 호환되지 않습니다.성능 영향: 쿼리 벡터 수가 증가하면 성능이 저하된다는 점에 유의하세요. 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] 검색에서 반환할 엔티티의 수입니다.