필터링 검색

ANN 검색은 지정된 벡터 임베딩과 가장 유사한 벡터 임베딩을 찾아냅니다. 그러나 검색 결과가 항상 정확한 것은 아닙니다. 검색 요청에 필터링 조건을 포함하면 Milvus가 ANN 검색을 수행하기 전에 메타데이터 필터링을 수행하여, 전체 컬렉션에서 지정된 필터링 조건에 부합하는 엔티티로만 검색 범위를 좁힐 수 있습니다.

개요

Milvus에서 필터링 검색은 필터링이 적용되는 단계에 따라 표준 필터링 과 반복 필터링의 두 가지 유형으로 분류됩니다.

표준 필터링

컬렉션에 벡터 임베딩과 해당 메타데이터가 모두 포함된 경우, ANN 검색 전에 메타데이터를 필터링하여 검색 결과의 관련성을 높일 수 있습니다. Milvus는 필터링 조건이 포함된 검색 요청을 수신하면, 지정된 필터링 조건에 부합하는 엔티티 내에서만 검색 범위를 제한합니다.

Filtered Search 필터링된 검색

위 다이어그램에서 볼 수 있듯이, 검색 요청에는 ‘ chunk like "%red%" ’가 필터링 조건으로 포함되어 있으며, 이는 Milvus가 ‘ chunk ’ 필드에 ‘ red ’라는 단어가 포함된 모든 엔티티 내에서 ANN 검색을 수행해야 함을 나타냅니다. 구체적으로 Milvus는 다음과 같은 작업을 수행합니다:

  • 검색 요청에 포함된 필터링 조건과 일치하는 엔티티를 필터링합니다.

  • 필터링된 엔티티 내에서 ANN 검색을 수행합니다.

  • 상위 K개 엔티티를 반환합니다.

반복 필터링

표준 필터링 프로세스는 검색 범위를 효과적으로 좁혀줍니다. 그러나 지나치게 복잡한 필터링 표현식은 검색 지연 시간을 매우 길게 만들 수 있습니다. 이러한 경우, 반복적 필터링을 대안으로 활용하면 스칼라 필터링의 처리 부하를 줄이는 데 도움이 됩니다.

Iterative Filtering 반복 필터링

위 다이어그램에서 볼 수 있듯이, 반복적 필터링을 사용하는 검색은 벡터 검색을 반복적으로 수행합니다. 반복자가 반환하는 각 엔티티는 스칼라 필터링을 거치며, 이 과정은 지정된 상위 K개 결과가 나올 때까지 계속됩니다.

이 방법은 스칼라 필터링을 적용받는 엔티티의 수를 대폭 줄여주므로, 특히 매우 복잡한 필터링 표현식을 처리할 때 유용합니다.

그러나 이터레이터가 엔티티를 한 번에 하나씩 처리한다는 점에 유의해야 합니다. 이러한 순차적 접근 방식은 특히 많은 수의 엔티티가 스칼라 필터링을 거칠 때 처리 시간이 길어지거나 잠재적인 성능 문제가 발생할 수 있습니다.

예제

이 섹션에서는 필터링된 검색을 수행하는 방법을 보여줍니다. 이 섹션의 코드 예제는 컬렉션에 이미 다음 엔티티들이 존재한다고 가정합니다. 각 엔티티는 id, vector, color, likes라는 네 개의 필드를 가지고 있습니다. 예제에서는 COSINE 메트릭으로 인덱싱된 5차원 vector 필드를 가진 ' my_collection '라는 로드된 컬렉션을 사용합니다.

[
    {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "color": "pink_8682", "likes": 165},
    {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "color": "red_7025", "likes": 25},
    {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "color": "orange_6781", "likes": 764},
    {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "color": "pink_9298", "likes": 234},
    {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "color": "red_4794", "likes": 122},
    {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "color": "yellow_4222", "likes": 12},
    {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "color": "red_9392", "likes": 58},
    {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "color": "grey_8510", "likes": 775},
    {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "color": "white_9381", "likes": 876},
    {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "color": "purple_4976", "likes": 765}
]

쿼리 벡터가 대상 컬렉션에 이미 존재하는 경우, 검색 전에 벡터를 다시 불러오는 대신 ids 를 사용하는 것을 고려해 보십시오. 자세한 내용은 ‘기본 키 검색(Primary-Key Search)’을 참조하십시오.

이 예제에서는 PyMilvus 3.0.2, Java SDK 3.0.10, Go SDK 3.0.0 (client/v3), Rust SDK 3.0.2, C++ SDK 3.0.4, NodeJS SDK 3.0.6 및 C# SDK 3.0.0을 사용합니다.

표준 필터링을 사용한 검색

다음 코드 예제는 표준 필터링을 사용한 검색을 보여줍니다. 다음 코드 예제의 요청에는 필터링 조건과 여러 출력 필드가 포함되어 있습니다.

from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]

res = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=5,
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"]
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)
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;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build());

FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(queryVector))
        .limit(5)
        .filter("color like \"red%\" and likes > 50")
        .outputFields(Arrays.asList("color", "likes"))
        .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={color=red_4794, likes=122}, score=0.5975797, id=4)
// SearchResp.SearchResult(entity={color=red_9392, likes=58}, score=-0.24996188, id=6)
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v3/entity"
    "github.com/milvus-io/milvus/client/v3/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

milvusAddr := "http://localhost:19530"
token := "root:Milvus"

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
    APIKey:  token,
})
if err != nil {
    panic(err)
}
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)},
).WithConsistencyLevel(entity.ClStrong).
    WithANNSField("vector").
    WithFilter("color like 'red%' and likes > 50").
    WithOutputFields("color", "likes"))
if err != nil {
    panic(err)
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("color: ", resultSet.GetColumn("color").FieldData().GetScalars())
    fmt.Println("likes: ", resultSet.GetColumn("likes").FieldData().GetScalars())
}
use milvus::v2::error::Result;
use milvus::v2::prelude::*;

let config = ConnectConfig::new().uri("http://localhost:19530").token("root:Milvus");
let client = ClientV2::new(&config).await?;

let query_vector = vec![0.3580376395471989f32, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592];

let res = client
    .search(
        SearchRequest::builder()
            .collection_name("my_collection")
            .vector_field("vector")
            .vectors(SearchVectors::Float(vec![query_vector]))
            .limit(5)
            .filter("color like \"red%\" and likes > 50")
            .output_fields(["color", "likes"])
            .build()?,
    )
    .await?;

println!("{:?}", res.results());
#include "milvus/MilvusClientV2.h"
#include <iostream>
#include <stdexcept>
#include <vector>

auto client = milvus::MilvusClientV2::Create();

milvus::ConnectParam connect_param{"http://localhost:19530", "root:Milvus"};
auto status = client->Connect(connect_param);
if (!status.IsOk()) {
    throw std::runtime_error(status.Message());
}

std::vector<float> query_vector = {0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f};
auto request = milvus::SearchRequest()
                   .WithCollectionName("my_collection")
                   .AddFloatVector(query_vector)
                   .WithLimit(5)
                   .WithAnnsField("vector")
                   .WithFilter(R"(color like "red%" and likes > 50)")
                   .AddOutputField("color")
                   .AddOutputField("likes");

milvus::SearchResponse response;
status = client->Search(request, response);
if (!status.IsOk()) {
    throw std::runtime_error(status.Message());
}

for (auto& result : response.Results().Results()) {
    std::cout << "TopK results:" << std::endl;
    milvus::EntityRows output_rows;
    status = result.OutputRows(output_rows);
    if (!status.IsOk()) {
        throw std::runtime_error(status.Message());
    }
    for (const auto& row : output_rows) {
        std::cout << "\t" << row << std::endl;
    }
}
import { MilvusClient } from "@zilliz/milvus2-sdk-node";

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});

const query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]

const res = await client.search({
    collection_name: "my_collection",
    data: [query_vector],
    limit: 5,
    filter: 'color like "red%" and likes > 50',
    output_fields: ["color", "likes"]
})
using System;
using System.Linq;
using Milvus.Client.V2;
using Milvus.Client.V2.Requests.Dql;
using Milvus.Client.V2.Responses.Dql;
using Milvus.Client.V2.Types;

var client = new MilvusClientV2(new ConnectConfig
{
    Uri = "http://localhost:19530",
    ApiKey = "root:Milvus"
});
await client.ConnectAsync();

var queryVector = new ReadOnlyMemory<float>(new[]
{
    0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f
});

SearchResp res = await client.SearchAsync(new SearchReq
{
    CollectionName = "my_collection",
    VectorFieldName = "vector",
    Vectors = new[] { queryVector },
    Limit = 5,
    Parameters = new SearchParameters
    {
        Expression = "color like \"red%\" and likes > 50",
        OutputFields = { "color", "likes" }
    }
});
if (res.SingleResults is { } results)
{
    Console.WriteLine(string.Join(", ", results.SelectMany(r => r.Scores)));
}
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",
    "filter": "color like \"red%\" and likes > 50",
    "limit": 5,
    "outputFields": ["color", "likes"]
}'
# Illustrative response; scores and cost can vary.
# {"code":0,"cost":0,"data":[{"color":"red_4794","distance":0.5975796,"id":4,"likes":122},{"color":"red_9392","distance":-0.2499619,"id":6,"likes":58}],"topks":[2]}

검색 요청에 포함된 필터링 조건은 color like "red%" and likes > 50 입니다. 이 조건은 'and' 연산자를 사용하여 두 가지 조건을 포함합니다. 첫 번째 조건은 color 필드의 값이 red 로 시작하는 엔티티를, 두 번째 조건은 likes 필드의 값이 50 보다 큰 엔티티를 찾습니다. 이 조건을 충족하는 엔티티는 단 두 개뿐입니다. top-K를 5 로 설정하면, Milvus는 이 두 엔티티와 쿼리 벡터 간의 거리를 계산하여 검색 결과로 반환합니다.

다음 예제는 코사인 점수를 예시로 든 Python 결과의 형식을 보여줍니다. 점수는 부동 소수점 정밀도에 따라 달라질 수 있습니다. entity 에는 요청된 출력 필드만 표시되며, REST 예제에서는 각 검색 결과의 최상위 수준에서 해당 필드를 반환합니다.

[
    {
        "id": 4,
        "distance": 0.5975796,
        "entity": {
            "color": "red_4794",
            "likes": 122
        }
    },
    {
        "id": 6,
        "distance": -0.2499619,
        "entity": {
            "color": "red_9392",
            "likes": 58
        }
    }
]

메타데이터 필터링에 사용할 수 있는 연산자에 대한 자세한 내용은 ‘필터링 설명’을 참조하십시오.

반복 필터링을 사용한 검색

반복 필터링을 사용하여 필터링된 검색을 수행하려면 다음과 같이 할 수 있습니다.

from pymilvus import MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]

res = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=5,
    filter='color like "red%" and likes > 50',
    output_fields=["color", "likes"],
    search_params={
        "hints": "iterative_filter"
    }
)

for hits in res:
    print("TopK results:")
    for hit in hits:
        print(hit)
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;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build());

FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(queryVector))
        .limit(5)
        .filter("color like \"red%\" and likes > 50")
        .outputFields(Arrays.asList("color", "likes"))
        .searchParams(Collections.<String, Object>singletonMap("hints", "iterative_filter"))
        .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={color=red_4794, likes=122}, score=0.5975797, id=4)
// SearchResp.SearchResult(entity={color=red_9392, likes=58}, score=-0.24996188, id=6)
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v3/entity"
    "github.com/milvus-io/milvus/client/v3/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

milvusAddr := "http://localhost:19530"
token := "root:Milvus"

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
    APIKey:  token,
})
if err != nil {
    panic(err)
}
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)},
).WithConsistencyLevel(entity.ClStrong).
    WithANNSField("vector").
    WithFilter("color like 'red%' and likes > 50").
    WithOutputFields("color", "likes").
    WithSearchParam("hints", "iterative_filter"))
if err != nil {
    panic(err)
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("color: ", resultSet.GetColumn("color").FieldData().GetScalars())
    fmt.Println("likes: ", resultSet.GetColumn("likes").FieldData().GetScalars())
}
use milvus::v2::error::Result;
use milvus::v2::prelude::*;
use std::collections::HashMap;

let config = ConnectConfig::new().uri("http://localhost:19530").token("root:Milvus");
let client = ClientV2::new(&config).await?;

let query_vector = vec![0.3580376395471989f32, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592];

let res = client
    .search(
        SearchRequest::builder()
            .collection_name("my_collection")
            .vector_field("vector")
            .vectors(SearchVectors::Float(vec![query_vector]))
            .limit(5)
            .filter("color like \"red%\" and likes > 50")
            .output_fields(["color", "likes"])
            .extra_params(HashMap::from([("hints".into(), "iterative_filter".into())]))
            .build()?,
    )
    .await?;

println!("{:?}", res.results());
#include "milvus/MilvusClientV2.h"
#include <iostream>
#include <stdexcept>
#include <vector>

auto client = milvus::MilvusClientV2::Create();

milvus::ConnectParam connect_param{"http://localhost:19530", "root:Milvus"};
auto status = client->Connect(connect_param);
if (!status.IsOk()) {
    throw std::runtime_error(status.Message());
}

std::vector<float> query_vector = {0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f};
auto request = milvus::SearchRequest()
                   .WithCollectionName("my_collection")
                   .AddFloatVector(query_vector)
                   .WithLimit(5)
                   .WithAnnsField("vector")
                   .WithFilter(R"(color like "red%" and likes > 50)")
                   .AddExtraParam("hints", "iterative_filter")
                   .AddOutputField("color")
                   .AddOutputField("likes");

milvus::SearchResponse response;
status = client->Search(request, response);
if (!status.IsOk()) {
    throw std::runtime_error(status.Message());
}

for (auto& result : response.Results().Results()) {
    std::cout << "TopK results:" << std::endl;
    milvus::EntityRows output_rows;
    status = result.OutputRows(output_rows);
    if (!status.IsOk()) {
        throw std::runtime_error(status.Message());
    }
    for (const auto& row : output_rows) {
        std::cout << "\t" << row << std::endl;
    }
}
import { MilvusClient } from "@zilliz/milvus2-sdk-node";

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});

const query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]

const res = await client.search({
    collection_name: "my_collection",
    data: [query_vector],
    limit: 5,
    filter: 'color like "red%" and likes > 50',
    hints: "iterative_filter",
    output_fields: ["color", "likes"]
})
using System;
using System.Linq;
using Milvus.Client.V2;
using Milvus.Client.V2.Requests.Dql;
using Milvus.Client.V2.Responses.Dql;
using Milvus.Client.V2.Types;

var client = new MilvusClientV2(new ConnectConfig
{
    Uri = "http://localhost:19530",
    ApiKey = "root:Milvus"
});
await client.ConnectAsync();

var queryVector = new ReadOnlyMemory<float>(new[]
{
    0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f
});

var searchParams = new SearchParameters
{
    Expression = "color like \"red%\" and likes > 50",
    Hints = "iterative_filter",
    OutputFields = { "color", "likes" }
};

SearchResp res = await client.SearchAsync(new SearchReq
{
    CollectionName = "my_collection",
    VectorFieldName = "vector",
    Vectors = new[] { queryVector },
    Limit = 5,
    Parameters = searchParams
});
if (res.SingleResults is { } results)
{
    Console.WriteLine(string.Join(", ", results.SelectMany(r => r.Scores)));
}
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",
    "filter": "color like \"red%\" and likes > 50",
    "searchParams": {"hints": "iterative_filter"},
    "limit": 5,
    "outputFields": ["color", "likes"]
}'
# Illustrative response; scores and cost can vary.
# {"code":0,"cost":0,"data":[{"color":"red_4794","distance":0.5975796,"id":4,"likes":122},{"color":"red_9392","distance":-0.2499619,"id":6,"likes":58}],"topks":[2]}

번역DeepL

관리형 Milvus를 무료로 사용해 보세요

Zilliz Cloud는 번거로움이 없으며, Milvus 기반으로 10배 더 빠릅니다.

시작하기
피드백

이 페이지가 도움이 되었나요?