유사성 검색
기계 학습의 많은 응용 프로그램에서 가장 중요한 요소 중 하나는 가장 가까운 벡터를 검색하는 것입니다. 현대 신경망은 객체를 벡터로 변환하는 방식으로 훈련되어, 벡터 공간에서 가까운 거리에 있는 객체들은 실제 세계에서도 가까운 위치에 있게 됩니다. 예를 들어, 비슷한 의미를 가진 텍스트, 시각적으로 비슷한 이미지, 또는 같은 장르에 속하는 노래 등이 여기에 해당합니다.
유사성 측정
벡터 간 유사성을 평가하는 많은 방법들이 존재합니다. Qdrant에서는 이러한 방법을 유사성 측정이라고 합니다. 유사성 측정 방법의 선택은 벡터들을 어떻게 얻었느냐에 따라 달라지며, 특히 신경망 인코더를 훈련하는 데 사용된 방법에 따라 달라집니다.
Qdrant은 다음과 같은 가장 일반적인 유사성 측정 유형을 지원합니다:
- 내적(Dot product):
Dot
- 코사인 유사도(Cosine similarity):
Cosine
- 유클리드 거리(Euclidean distance):
Euclid
유사성 학습 모델에서 가장 일반적으로 사용되는 측정은 코사인 측정입니다.
Qdrant는 이 측정을 두 단계로 계산하여 더 빠른 검색 속도를 달성합니다. 첫 번째 단계는 벡터를 컬렉션에 추가할 때 벡터를 정규화하는 것입니다. 이 작업은 각 벡터에 대해 한 번만 수행됩니다.
두 번째 단계는 벡터 비교입니다. 이 경우, SIMD의 빠른 연산으로 인해 닷 프로덕트 작업과 동일합니다.
쿼리 계획
검색에 사용된 필터에 따라 쿼리 실행에는 몇 가지 가능한 시나리오가 있습니다. Qdrant는 사용 가능한 인덱스, 조건의 복잡성 및 필터링된 결과의 기수에 따라 쿼리 실행 옵션 중 하나를 선택합니다. 이 과정을 쿼리 계획이라고 합니다.
전략 선택 프로세스는 휴리스틱 알고리즘에 의존하며 버전에 따라 다를 수 있습니다. 그러나 일반적인 원칙은 다음과 같습니다:
- 각 세그먼트에 대해 독립적으로 쿼리 계획 실행(세그먼트에 대한 상세한 정보는 저장소를 참조하세요).
- 점의 수가 적으면 전체 스캔에 우선 순위를 둡니다.
- 전략을 선택하기 전에 필터링된 결과의 기수를 예측합니다.
- 기수가 낮으면 페이로드 인덱스를 사용하여 점을 검색합니다(인덱스 참조).
- 기수가 높으면 필터링 가능한 벡터 인덱스를 사용합니다.
임계값은 구성 파일을 통해 각 컬렉션마다 독립적으로 조정될 수 있습니다.
검색 API
검색 쿼리의 예제를 살펴보겠습니다.
REST API - API 스키마 정의는 여기에서 찾을 수 있습니다.
POST /collections/{collection_name}/points/search
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "London"
}
}
]
},
"params": {
"hnsw_ef": 128,
"exact": false
},
"vector": [0.2, 0.1, 0.9, 0.7],
"limit": 3
}
이 예제에서는 [0.2, 0.1, 0.9, 0.7]
과 유사한 벡터를 찾고 있습니다. limit
(또는 별칭인 top
) 매개변수는 검색할 가장 유사한 결과의 수를 지정합니다.
params
키 아래의 값들은 사용자 정의 검색 매개변수를 지정합니다. 현재 사용 가능한 매개변수는 다음과 같습니다:
-
hnsw_ef
- HNSW 알고리즘의ef
매개변수 값을 지정합니다. -
exact
- 정확한 (ANN) 검색 옵션을 사용할지 여부를 지정합니다. True로 설정하면 정확한 결과를 얻기 위해 완전한 스캔을 수행하므로 시간이 오래 걸릴 수 있습니다. -
indexed_only
- 이 옵션을 사용하면 아직 벡터 인덱스를 작성하지 않은 세그먼트에서의 검색을 비활성화할 수 있습니다. 이 옵션은 업데이트 중의 검색 성능에 미치는 영향을 최소화하기 위해 유용할 수 있습니다. 이 옵션을 사용하면 컬렉션이 완전히 색인화되지 않은 경우 부분 결과가 발생할 수 있으므로 필요한 경우에만 사용하십시오.
filter
매개변수가 지정되었기 때문에 검색은 필터링 기준을 충족하는 점들 사이에서만 수행됩니다. 가능한 필터 및 기능에 대한 자세한 정보는 필터 섹션을 참조하십시오.
이 API의 예상 결과는 다음과 같습니다:
{
"result": [
{ "id": 10, "score": 0.81 },
{ "id": 14, "score": 0.75 },
{ "id": 11, "score": 0.73 }
],
"status": "ok",
"time": 0.001
}
result
는 score
에 따라 정렬된 발견된 점들의 목록을 포함합니다.
기본적으로 이러한 결과는 페이로드 및 벡터 데이터가 누락됩니다. 결과에 페이로드 및 벡터를 포함하는 방법에 대해서는 결과 섹션의 페이로드 및 벡터를 참조하십시오.
버전 v0.10.0부터 사용 가능
여러 벡터로 만든 컬렉션이 있다면, 검색에 사용할 벡터의 이름을 제공해야 합니다:
POST /collections/{collection_name}/points/search
{
"vector": {
"name": "image",
"vector": [0.2, 0.1, 0.9, 0.7]
},
"limit": 3
}
검색은 동일한 이름을 가진 벡터들 사이에서만 수행됩니다.
점수에 의한 결과 필터링
페이로드 필터링 외에도 유사도 점수가 낮은 결과를 걸러내는 것이 유용할 수 있습니다. 예를 들어, 모델의 최소 허용 점수를 알고 있고 임계값 이하의 유사도 결과를 원치 않는 경우, 검색 쿼리에 score_threshold
매개변수를 사용할 수 있습니다. 이 매개변수는 주어진 값보다 낮은 점수를 가진 모든 결과를 제외합니다.
이 매개변수는 사용하는 메트릭에 따라 낮은 및 높은 점수를 제외할 수 있습니다. 예를 들어, 유클리드 메트릭에서 높은 점수는 더 멀리 있는 것으로 간주되어 제외될 수 있습니다.
결과의 페이로드와 벡터
기본적으로 검색 방법은 페이로드 및 벡터와 같은 저장된 정보를 반환하지 않습니다. 추가 매개변수인 with_vectors
및 with_payload
는 이 동작을 수정할 수 있습니다.
예시:
POST /collections/{collection_name}/points/search
{
"vector": [0.2, 0.1, 0.9, 0.7],
"with_vectors": true,
"with_payload": true
}
with_payload
매개변수는 특정 필드를 포함하거나 제외하는 데에도 사용될 수 있습니다:
POST /collections/{collection_name}/points/search
{
"vector": [0.2, 0.1, 0.9, 0.7],
"with_payload": {
"exclude": ["city"]
}
}
일괄 검색 API
v0.10.0부터 사용 가능
일괄 검색 API를 사용하면 단일 요청을 통해 여러 검색 요청을 실행할 수 있습니다.
이의 의미는 간단합니다. n
개의 일괄 검색 요청은 n
개의 별도 검색 요청과 동등합니다.
이 방법에는 여러 가지 이점이 있습니다. 논리적으로는 더 적은 네트워크 연결이 필요하며, 이 자체로 유익합니다.
더 중요한 것은, 일괄 요청이 동일한 필터
를 가지고 있는 경우, 일괄 요청은 쿼리 플래너를 통해 효율적으로 처리되고 최적화될 것입니다.
이는 중요한 영향을 미치며, 비트험한 필터의 경우 중간 결과를 요청 사이에서 공유할 수 있습니다.
사용하려면 간단히 검색 요청을 함께 묶어주면 됩니다. 물론 일반적인 검색 요청 속성을 모두 사용할 수 있습니다.
POST /collections/{collection_name}/points/search/batch
{
"searches": [
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "런던"
}
}
]
},
"vector": [0.2, 0.1, 0.9, 0.7],
"limit": 3
},
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "런던"
}
}
]
},
"vector": [0.5, 0.3, 0.2, 0.3],
"limit": 3
}
]
}
이 API의 결과는 각 검색 요청에 대한 배열을 포함합니다.
{
"result": [
[
{ "id": 10, "score": 0.81 },
{ "id": 14, "score": 0.75 },
{ "id": 11, "score": 0.73 }
],
[
{ "id": 1, "score": 0.92 },
{ "id": 3, "score": 0.89 },
{ "id": 9, "score": 0.75 }
]
],
"status": "ok",
"time": 0.001
}
추천 API
부정 벡터는 실험 기능이며 모든 종류의 임베딩과 함께 작동을 보장하지 않습니다. 일반 검색 외에도 Qdrant는 컬렉션에 이미 저장된 여러 벡터를 기반으로 검색할 수 있도록 합니다. 이 API는 신경망 인코더를 사용하지 않고 인코딩된 객체의 벡터 검색에 사용됩니다.
추천 API를 사용하면 여러 양의 및 음의 벡터 ID를 지정할 수 있으며, 서비스는 이들을 특정 평균 벡터로 병합합니다.
평균_벡터 = avg(양_벡터) + ( avg(양_벡터) - avg(음_벡터) )
한 개의 양의 ID만 제공되는 경우, 이 요청은 해당 지점의 벡터에 대한 일반 검색과 동등합니다.
부정 벡터의 값이 큰 요소는 벌점을 받고, 양의 벡터의 값이 큰 요소는 증폭됩니다. 그런 다음 이 평균 벡터가 컬렉션 내에서 가장 유사한 벡터를 찾는 데 사용됩니다.
REST API의 API 스키마 정의는 여기에서 찾을 수 있습니다.
POST /collections/{collection_name}/points/recommend
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "런던"
}
}
]
},
"negative": [718],
"positive": [100, 231],
"limit": 10
}
이 API의 샘플 결과는 다음과 같습니다:
{
"result": [
{ "id": 10, "score": 0.81 },
{ "id": 14, "score": 0.75 },
{ "id": 11, "score": 0.73 }
],
"status": "ok",
"time": 0.001
}
v0.10.0 이후 사용 가능
컬렉션을 여러 벡터로 생성한 경우, 추천 요청에서 사용되는 벡터의 이름을 지정해야 합니다:
POST /collections/{collection_name}/points/recommend
{
"positive": [100, 231],
"negative": [718],
"using": "image",
"limit": 10
}
using
매개변수는 추천에 사용되는 저장된 벡터를 지정합니다.
일괄 권장 API
v0.10.0부터 사용 가능
일괄 검색 API와 유사한 방식과 혜택을 제공하는 일괄 권장 요청을 처리할 수 있습니다.
POST /collections/{collection_name}/points/recommend/batch
{
"searches": [
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "런던"
}
}
]
},
"negative": [718],
"positive": [100, 231],
"limit": 10
},
{
"filter": {
"must": [
{
"key": "city",
"match": {
"value": "런던"
}
}
]
},
"negative": [300],
"positive": [200, 67],
"limit": 10
}
]
}
이 API의 결과는 각 권장 요청마다 배열을 포함합니다.
{
"result": [
[
{ "id": 10, "score": 0.81 },
{ "id": 14, "score": 0.75 },
{ "id": 11, "score": 0.73 }
],
[
{ "id": 1, "score": 0.92 },
{ "id": 3, "score": 0.89 },
{ "id": 9, "score": 0.75 }
]
],
"status": "ok",
"time": 0.001
}
페이지네이션
v0.8.3부터 사용 가능
검색 및 권장 API는 검색 결과의 처음 몇 개를 건너뛰고 특정 오프셋부터 결과를 반환하는 것을 허용합니다.
예시:
POST /collections/{collection_name}/points/search
{
"vector": [0.2, 0.1, 0.9, 0.7],
"with_vectors": true,
"with_payload": true,
"limit": 10,
"offset": 100
}
이는 10개의 레코드를 한 페이지로 하는 11번째 페이지를 검색하는 것과 동일합니다.
큰 오프셋 값은 성능 문제를 야기할 수 있으며, 벡터 기반 검색 방법은 일반적으로 페이지네이션을 지원하지 않습니다. 첫 N개의 벡터를 검색하지 않으면 N번째 가장 가까운 벡터를 검색할 수 없습니다.
그러나 오프셋 매개변수를 사용하면 네트워크 트래픽과 저장소 접근을 줄이는 방식으로 리소스를 절약할 수 있습니다.
offset
매개변수를 사용할 때는 내부적으로 offset + limit
개의 포인트를 검색하지만, 실제로 저장소에서 반환된 포인트의 페이로드와 벡터에만 액세스합니다.
그룹화 API
버전 v1.2.0부터 사용 가능
결과는 특정 필드를 기준으로 그룹화할 수 있습니다. 동일한 항목에 대해 여러 점이 있는 경우 결과에서 중복 항목을 피하고 싶을 때 매우 유용합니다.
예를 들어, 대량 문서를 여러 청크로 나누고 각 문서를 기반으로 검색하거나 추천하려는 경우, 문서 ID별로 결과를 그룹화할 수 있습니다.
페이로드가 있는 점들이 있다고 가정해봅시다:
{
{
"id": 0,
"payload": {
"chunk_part": 0,
"document_id": "a",
},
"vector": [0.91],
},
{
"id": 1,
"payload": {
"chunk_part": 1,
"document_id": ["a", "b"],
},
"vector": [0.8],
},
{
"id": 2,
"payload": {
"chunk_part": 2,
"document_id": "a",
},
"vector": [0.2],
},
{
"id": 3,
"payload": {
"chunk_part": 0,
"document_id": 123,
},
"vector": [0.79],
},
{
"id": 4,
"payload": {
"chunk_part": 1,
"document_id": 123,
},
"vector": [0.75],
},
{
"id": 5,
"payload": {
"chunk_part": 0,
"document_id": -10,
},
"vector": [0.6],
},
}
groups API를 사용하여, 각 문서의 상위 N 점을 가져올 수 있습니다. 점의 페이로드에 문서 ID가 포함되어 있다고 가정합니다. 물론, 점이 부족하거나 쿼리로부터 상대적으로 먼 경우에는 최상의 N 점을 만족시키지 못할 수도 있습니다. 각 경우에 따라 group_size
는 limit
파라미터와 유사한 노력을 기반으로 합니다.
그룹 검색
REST API (스키마):
POST /collections/{collection_name}/points/search/groups
{
// 일반 검색 API와 동일합니다
"vector": [1.1],
...,
// 그룹화 파라미터
"group_by": "document_id", // 그룹화할 필드 경로
"limit": 4, // 최대 그룹 수
"group_size": 2, // 그룹 당 최대 점 수
}
그룹 추천
REST API (스키마):
POST /collections/{collection_name}/points/recommend/groups
{
// 일반 추천 API와 동일
"negative": [1],
"positive": [2, 5],
...,
// 그룹화 매개변수
"group_by": "document_id", // 그룹화할 필드 경로
"limit": 4, // 최대 그룹 수
"group_size": 2, // 그룹 당 최대 포인트 수
}
검색이든 추천이든, 출력 결과는 다음과 같습니다:
{
"result": {
"groups": [
{
"id": "a",
"hits": [
{ "id": 0, "score": 0.91 },
{ "id": 1, "score": 0.85 }
]
},
{
"id": "b",
"hits": [
{ "id": 1, "score": 0.85 }
]
},
{
"id": 123,
"hits": [
{ "id": 3, "score": 0.79 },
{ "id": 4, "score": 0.75 }
]
},
{
"id": -10,
"hits": [
{ "id": 5, "score": 0.6 }
]
}
]
},
"status": "ok",
"time": 0.001
}
그룹은 각 그룹 내의 포인트들의 가장 높은 점수에 따라 정렬됩니다. 각 그룹 내에서도 포인트들이 정렬됩니다.
포인트의 group_by
필드가 배열인 경우 (예: "document_id": ["a", "b"]
), 해당 포인트는 여러 그룹에 포함될 수 있습니다 (예: "document_id": "a"
및 document_id: "b"
).
이 기능은 제공된 group_by
키에 크게 의존합니다. 성능을 향상시키려면 해당 필드에 대한 전용 인덱스를 생성해야 합니다. 제한 사항:
-
group_by
매개변수는 키워드 및 정수 페이로드 값만 지원합니다. 다른 페이로드 값 유형은 무시됩니다. - 현재 그룹을 사용할 때 페이지네이션은 지원되지 않으므로
offset
매개변수는 허용되지 않습니다.
그룹 내에서 검색하기
v1.3.0부터 사용 가능
동일한 항목의 다른 부분에 대해 여러 지점이 있는 경우, 저장된 데이터에 중복이 종종 발생합니다. 지점 간에 공유 정보가 최소한이고 경우에 따라서는 허용될 수 있지만, 그룹 내의 지점 수에 따라 저장 공간을 계산하는 것이 무거워지면 문제가 발생할 수 있습니다.
그룹을 사용할 때 저장 공간을 최적화하는 방법은 같은 그룹 ID를 기반으로 한 지점 간의 공유 정보를 다른 컬렉션 내의 단일 지점에 저장하는 것입니다. 그런 다음 groups API를 사용할 때, 각 그룹에 대해 이 정보를 추가하기 위해 with_lookup
매개변수를 추가합니다.
이 방법의 추가적인 이점은 그룹 지점 내의 공유 정보가 변경되어도 해당 단일 지점만 업데이트하면 된다는 것입니다.
예를 들어 문서 컬렉션을 가지고 있다면, 이를 청크로 나누고 해당 청크에 속하는 지점을 별도의 컬렉션에 저장하여 문서에 속하는 지점 ID를 청크 지점의 데이터에 저장할 수 있습니다.
이러한 시나리오에서, 문서에서 정보를 청크로 가져오려면 with_lookup
매개변수를 사용할 수 있습니다:
POST /collections/chunks/points/search/groups
{
// 일반 검색 API와 동일한 매개변수
"vector": [1.1],
...,
// 그룹화 매개변수
"group_by": "document_id",
"limit": 2,
"group_size": 2,
// 검색 매개변수
"with_lookup": {
// 검색할 지점의 컬렉션 이름
"collection": "documents",
// 검색된 지점의 페이로드로부터 가져올 내용을 지정하는 옵션, 기본값은 true
"with_payload": ["title", "text"],
// 검색된 지점의 벡터로부터 가져올 내용을 지정하는 옵션, 기본값은 true
"with_vectors": false
}
}
with_lookup
매개변수에 대해 with_lookup="documents"
와 같은 간략한 표현을 사용하여 명시적으로 지정하지 않아도 전체 페이로드와 벡터를 가져올 수 있습니다.
검색 결과는 각 그룹 아래의 lookup
필드에 표시됩니다.
{
"result": {
"groups": [
{
"id": 1,
"hits": [
{ "id": 0, "score": 0.91 },
{ "id": 1, "score": 0.85 }
],
"lookup": {
"id": 1,
"payload": {
"title": "문서 A",
"text": "이것은 문서 A입니다"
}
}
},
{
"id": 2,
"hits": [
{ "id": 1, "score": 0.85 }
],
"lookup": {
"id": 2,
"payload": {
"title": "문서 B",
"text": "이것은 문서 B입니다"
}
}
}
]
},
"status": "ok",
"time": 0.001
}
지점 ID를 직접 매칭하여 검색하기 때문에 존재하지 않는 (유효하지 않은) 그룹 ID는 무시되며, lookup
필드는 비어 있을 것입니다.