Normalization processor
출처: 문서
본문
정규화 프로세서
2.10에 도입됨 {: .label .label-purple }
normalization-processor는 검색 실행의 쿼리 페이즈와 페치 페이즈 사이에서 동작하는 검색 페이즈 결과 프로세서예요. 쿼리 페이즈 결과를 가로채서, 문서를 페치 페이즈로 넘기기 전에 서로 다른 쿼리 절(clause)의 문서 점수를 정규화하고 결합해요.
점수 정규화와 결합
많은 애플리케이션에서 키워드 매칭과 시맨틱 이해가 둘 다 필요해요. 예를 들어 BM25는 키워드가 포함된 쿼리에 대해 관련성 있는 검색 결과를 정확하게 제공하고, 신경망은 쿼리에 자연어 이해가 필요할 때 좋은 성능을 보여요. 그래서 BM25 검색 결과를 k-NN 또는 neural search 결과와 결합하고 싶을 수 있어요. 하지만 BM25와 k-NN 검색은 매칭된 문서의 관련성 점수를 계산할 때 서로 다른 스케일을 사용해요. 여러 쿼리의 점수를 결합하기 전에는 실험 데이터에서 알 수 있듯이 점수를 같은 스케일에 놓이도록 정규화하는 것이 유리해요. 벤치마크와 다양한 기법을 포함해 점수 정규화와 결합에 대해 더 알아보려면 이 시맨틱 검색 블로그 포스트를 참고하세요.
정규화 기법
OpenSearch는 각 쿼리 절을 독립적으로 정규화해요. 주어진 절에 대해 기법이 필요로 하는 통계를 해당 절 자신의 결과만으로 계산한 다음, 절 내의 모든 점수를 재조정해요. 그 절이 반환하지 않은 문서는 그 통계에 아무런 기여도 하지 않아요.
통계가 인덱스 전체가 아니라 반환된 결과에서 도출되기 때문에, 정규화된 점수는 각 절이 반환하는 결과 수에 따라 달라져요. 자세한 내용은 검색 튜닝 권장 사항을 참고하세요.
Min-max 정규화
Min-max 정규화는 절의 최소 점수를 빼고 절의 점수 범위로 나누어 쿼리 절의 점수를 [0.0, 1.0] 범위로 재조정해요:
$$\text{n_score} = \frac {\text{score} - \text{min_score}} {\text{max_score} - \text{min_score}}$$
절에서 가장 높은 점수를 받은 문서는 1.0 점수를 받아요. OpenSearch는 정규화된 점수가 정확히 0.0일 때 0.001로 대체하는데, 0.0 점수가 match_none이라는 특별한 의미를 가지기 때문이에요. 따라서 절에서 가장 낮은 점수를 받은 문서는 0.001을 받아요. 문서를 하나만 반환하는 절을 포함해 절의 모든 문서가 같은 점수를 가진다면, 절의 최소 점수와 최대 점수가 같아지고 절의 모든 문서는 1.0을 받아요.
정확히 0.0인 경우만 대체되기 때문에, 이 치환 때문에 절 내 저점수 문서들의 순서가 바뀔 수 있어요. 정규화 점수가 0.001 미만인 문서는 정확히 0.001을 받는 가장 낮은 점수의 문서보다 아래에 순위가 매겨져요. 예를 들어 원 점수 10000, 9, 8을 반환하는 절은 이를 1.0, 0.00010008, 0.001로 정규화하므로, 8점을 받은 문서가 9점을 받은 문서보다 위에 순위를 차지해요. 문서의 점수가 절 최소 점수를 초과하는 폭이 절 점수 범위의 0.1% 미만일 때 이러한 영향을 받는데, 이는 절의 한 점수가 다른 점수들보다 대략 1,000배 클 때 발생해요.
반환된 결과의 최소/최대 점수가 아니라 고정된 임계값을 기준으로 정규화하려면 lower_bounds와 upper_bounds 파라미터를 설정하세요. 자세한 내용은 요청 본문 필드를 참고하세요.
L2 정규화
L2 정규화는 쿼리 절의 각 점수를 그 절의 모든 점수에 대한 유클리드 노름으로 나눠요:
$$\text{n_score}_i = \frac {\text{score}_i} {\sqrt{\text{score}_1^2 + \text{score}_2^2 + \dots + \text{score}_n^2}}$$
정규화된 점수는 원 점수의 비율을 보존하므로, 같은 절에서 다른 문서보다 두 배 높은 점수를 받은 문서는 정규화 후에도 여전히 두 배 높은 점수를 받아요. 모든 점수가 같은 노름으로 나뉘고 어떤 점수도 노름을 초과할 수 없기 때문에, 정규화된 점수는 [0.0, 1.0] 범위에 속해요. Min-max 정규화와 달리, L2 정규화는 가장 높은 점수를 받은 문서에 1.0을 부여하지 않아요. 절이 결과를 하나 더 반환할 때마다 노름이 커지므로, 절 내 다른 모든 문서의 정규화 점수는 낮아져요. 절이 문서를 하나만 반환할 때만 노름이 그 문서의 점수와 같아지고, 그 문서는 1.0을 받아요.
Z-score 정규화
Z-score 정규화는 각 점수에서 쿼리 절 점수들의 평균을 빼고, 그 결과를 이 점수들의 표본 표준편차로 나눠요:
$$\text{n_score} = \frac {\text{score} - \text{mean}} {\text{sd}}$$
자신이 속한 절의 평균보다 낮은 점수를 받은 문서는 음의 z-score를 갖게 돼요. 음수는 유효한 관련성 점수가 아니기 때문에, OpenSearch는 0.0 이하인 모든 정규화 점수를 0.001로 대체해요. 따라서 쿼리 절의 평균보다 낮은 점수를 받은 모든 문서는 같은 정규화 점수를 받고, 그 절 내에서의 상대적 순서는 사라져요. 평균보다 높은 점수를 받은 문서는 고정된 범위에 갇히지 않으며, 1.0보다 큰 정규화 점수를 받을 수 있어요.
점수가 자신이 속한 절의 평균과 정확히 같은 문서는 특수한 사례예요. 이 문서는 z-score 대신 절에서 가장 높은 원 점수를 받아요. 문서를 하나만 반환하는 절을 포함해 절의 모든 문서가 같은 점수를 가진다면 모든 문서가 평균과 일치하므로, 절의 점수는 전혀 재조정되지 않아요.
z_score 기법은 arithmetic_mean 조합 기법만 지원해요.
정규화 기법 선택하기
다음 표를 사용해 세 가지 기법을 비교해 보세요.
min_max |
l2 |
z_score |
|
|---|---|---|---|
| 출력 범위 | [0.0, 1.0], 0.0은 0.001로 대체 |
[0.0, 1.0] | 고정된 범위가 아니며, 0.0 이하의 모든 값은 0.001로 대체 |
| 절에서 가장 높은 점수를 받은 문서의 점수 | 항상 1.0 |
절이 단일 문서를 반환하는 경우가 아니면 1.0 미만 |
다양하며 1.0을 초과할 수 있음 |
| 절 내 점수 비율 | 보존되지 않음 | 보존됨 | 보존되지 않음 |
| 사용되는 통계 | 최저 점수와 최고 점수 | 모든 점수 | 모든 점수 |
| 단일 이상치가 절 내 다른 문서에 미치는 영향 | 점수를 압축하지만 서로 구별되게 유지함 | 점수를 압축하지만 서로 구별되게 유지함 | 절 평균 미만으로 밀어내서 모두 0.001로 수렴함 |
| 절 내 문서 순서 | 보존됨. 단, 정규화 점수가 0.001 미만인 문서는 가장 낮은 점수의 문서보다 아래에 순위가 매겨짐 |
보존됨 | 절 평균 미만의 점수를 받은 모든 문서에서 상실됨 |
| 지원되는 조합 기법 | 모두 | 모두 | arithmetic_mean |
각 기법에 적용되는 가이드라인은 다음과 같아요:
- 직접 추론할 수 있는 예측 가능한 스케일의 정규화 점수가 필요하거나,
lower_bounds와upper_bounds파라미터를 통해 고정된 임계값을 기준으로 정규화하고 싶을 때 기본값인min_max를 사용하세요. - 쿼리 절 내 점수들 간의 비율이 최종 랭킹이 반영해야 할 정보를 담고 있을 때
l2를 사용하세요. 단,l2가min_max보다 나머지 문서들을 더 넓게 펼치지는 않는다는 점에 유의하세요. 절의 L2 노름은 항상 절의 점수 범위보다 크거나 같기 때문에,l2는 임의의 두 문서를min_max가 벌리는 간격 이하로만 벌려요. - 쿼리 절들이 만들어내는 점수 분포가 주로 퍼짐(spread)에서 차이가 날 때
z_score를 사용하세요. 절이 적은 수의 결과를 반환하거나 이상치를 포함할 때는 피하세요. 절 평균보다 낮은 점수를 받은 모든 문서가0.001로 수렴하기 때문이에요. 높은 이상치 하나가 평균을 나머지 모든 점수 위로 끌어올려서 절 내 다른 모든 문서의 순서를 지워버릴 수 있어요.
예시: 정규화 기법 비교하기
다음 예시는 같은 데이터에 세 가지 기법을 모두 적용해서 출력을 비교할 수 있게 해줘요. 각 문서에 직접 지정된 2차원 벡터를 사용해서 산술을 직접 검증할 수 있고, 단일 샤드를 사용해서 점수가 재현 가능해요.
이 예시는 Reciprocal rank fusion과 같은 인덱스와 쿼리를 사용하므로, 이 결과를 순위 기반 조합의 결과와도 비교할 수 있어요. {: .note}
키워드 매칭용 텍스트 필드와 시맨틱 매칭용 벡터 필드를 가진 인덱스를 생성해요:
PUT /products
{
"settings": {
"index.knn": true,
"number_of_shards": 1
},
"mappings": {
"properties": {
"item_name": {
"type": "text"
},
"item_vector": {
"type": "knn_vector",
"dimension": 2,
"space_type": "l2",
"method": {
"name": "hnsw",
"engine": "lucene"
}
}
}
}
}
{% include copy-curl.html %}
다섯 개의 제품을 인덱싱해요:
POST /_bulk?refresh=true
{ "index": { "_index": "products", "_id": "1" } }
{ "item_name": "kids running shoes", "item_vector": [0.2, 0.8] }
{ "index": { "_index": "products", "_id": "2" } }
{ "item_name": "mens lightweight running shoes for road racing", "item_vector": [0.9, 0.1] }
{ "index": { "_index": "products", "_id": "3" } }
{ "item_name": "trail runners", "item_vector": [0.8, 0.2] }
{ "index": { "_index": "products", "_id": "4" } }
{ "item_name": "athletic socks", "item_vector": [0.5, 0.5] }
{ "index": { "_index": "products", "_id": "5" } }
{ "item_name": "winter parka", "item_vector": [0.0, 1.0] }
{% include copy-curl.html %}
두 쿼리 절을 따로 실행하면 서로 다른 스케일의 점수가 나와요. running shoes에 대한 match 절은 두 개의 문서를 반환하고, k가 3으로 설정된 [0.9, 0.1]에 대한 knn
요청 본문 필드
다음 표는 사용 가능한 모든 요청 필드를 나열한 거예요.
| 필드 | 데이터 타입 | 설명 |
|---|---|---|
normalization.technique |
String | 점수를 정규화하는 기법이에요. 유효한 값은 min_max, l2, z_score예요. 선택 사항이에요. 기본값은 min_max예요. |
normalization.parameters.lower_bounds |
Array of objects | 각 쿼리의 하한값(최소 임계 점수)을 정의해요. 배열에는 쿼리 개수와 동일한 개수의 객체가 포함되어야 해요. 선택 사항이에요. 정규화 기법이 min_max일 때만 적용돼요. 제공하지 않으면 OpenSearch는 어떤 하위 쿼리에도 하한을 적용하지 않고, 검색된 결과의 실제 최소 점수를 정규화에 사용해요. |
normalization.parameters.lower_bounds.mode |
String | 쿼리에 하한을 적용하는 방식을 지정해요. 유효한 값은 다음과 같아요: - apply: 원본 점수를 수정하지 않고 min_score를 정규화에 사용해요. 공식: min_max_score = if (score < lowerBoundScore) then (score - minScore) / (maxScore - minScore) else (score - lowerBoundScore) / (maxScore - lowerBoundScore). - clip: 하한 미만의 점수를 min_score로 대체해요. 공식: min_max_score = if (score < lowerBoundScore) then 0.0 else (score - lowerBoundScore) / (maxScore - lowerBoundScore). - ignore: 이 쿼리에는 하한을 적용하지 않고 대신 표준 min_max 공식을 사용해요. 선택 사항이에요. 기본값은 apply예요. |
normalization.parameters.lower_bounds.min_score |
Float | 하한 임계값이에요. 유효한 값은 [-10000.0, 10000.0] 범위에 있어요. mode가 ignore로 설정되면 이 값은 아무런 효과가 없어요. 선택 사항이에요. 기본값은 0.0이에요. |
normalization.parameters.upper_bounds |
Array of objects | 각 쿼리의 상한값(최대 임계 점수)을 정의해요. 배열에는 쿼리 개수와 동일한 개수의 객체가 포함되어야 해요. 선택 사항이에요. normalization.technique이 min_max로 설정된 경우에만 적용돼요. 제공하지 않으면 OpenSearch는 어떤 하위 쿼리에도 상한을 적용하지 않고, 검색된 결과의 실제 최대 점수를 정규화에 사용해요. |
normalization.parameters.upper_bounds.mode |
String | 쿼리에 상한을 적용하는 방식을 지정해요. 유효한 값은 다음과 같아요: - apply: 원본 점수를 수정하지 않고 max_score를 정규화에 사용해요. 공식: min_max_score = if (score > upperBoundScore) then (score - minScore) / (maxScore - minScore) else (score - minScore) / (upperBoundScore - minScore). - clip: 상한을 초과하는 점수를 max_score로 대체해요. 공식: min_max_score = if (score > upperBoundScore) then 1.0 else (score - minScore) / (upperBoundScore - minScore). - ignore: 이 쿼리에는 상한을 적용하지 않고 대신 표준 min_max 공식을 사용해요. 선택 사항이에요. 기본값은 apply예요. |
normalization.parameters.upper_bounds.max_score |
Float | 상한 임계값이에요. 유효한 값은 [-10000.0, 10000.0] 범위에 있어요. mode가 ignore로 설정되면 이 값은 아무런 효과가 없어요. 선택 사항이에요. 기본값은 1.0이에요. |
combination.technique |
String | 점수를 결합하는 기법이에요. 유효한 값은 arithmetic_mean, geometric_mean, harmonic_mean이에요. 선택 사항이에요. 기본값은 arithmetic_mean이에요. z_score는 arithmetic_mean만 지원해요. |
combination.parameters.weights |
Array of floating-point values | 각 쿼리에 사용할 가중치를 지정해요. 유효한 값은 [0.0, 1.0] 범위에 있으며 소수 형태의 백분율을 나타내요. 가중치가 1.0에 가까울수록 해당 쿼리에 더 많은 가중치가 부여돼요. weights 배열의 값 개수는 쿼리 개수와 같아야 해요. 배열에 있는 값들의 합은 1.0이어야 해요. 선택 사항이에요. 제공하지 않으면 모든 쿼리에 동일한 가중치가 부여돼요. |
tag |
String | 프로세서의 식별자예요. 선택 사항이에요. |
description |
String | 프로세서에 대한 설명이에요. 선택 사항이에요. |
ignore_failure |
Boolean | 이 프로세서의 경우 이 값은 무시돼요. 프로세서가 실패하면 파이프라인은 항상 실패하고 오류를 반환해요. |
예제
다음 예제는 normalization-processor가 포함된 검색 파이프라인을 사용하는 방법을 보여줘요.
포괄적인 예제는 시맨틱 및 하이브리드 검색 시작하기를 따라 해 보세요.
검색 파이프라인 생성하기
다음 요청은 min_max 정규화 기법과 arithmetic_mean 결합 기법을 사용하는 normalization-processor가 포함된 검색 파이프라인을 생성해요. 결합 기법은 첫 번째 쿼리에 30%, 두 번째 쿼리에 70%의 가중치를 부여해요:
PUT /_search/pipeline/nlp-search-pipeline
{
"description": "Post processor for hybrid search",
"phase_results_processors": [
{
"normalization-processor": {
"normalization": {
"technique": "min_max"
},
"combination": {
"technique": "arithmetic_mean",
"parameters": {
"weights": [
0.3,
0.7
]
}
}
}
}
]
}
{% include copy-curl.html %}
다음 예제는 min_max 정규화 기법과 함께 lower_bounds 및 upper_bounds 파라미터를 사용하는 방법을 보여줘요. 이 예제는 결합 기법에서 weights 파라미터를 생략하기 때문에 기본적으로 쿼리에 동일한 가중치가 부여돼요. 이 예제에서 lower_bounds 파라미터는 하이브리드 검색에서 각 쿼리에 서로 다른 하한을 설정하는 데 사용되고, upper_bounds 파라미터는 서로 다른 상한을 설정하는 데 사용돼요. 첫 번째 쿼리에는 0.5의 하한이 적용되고 0.8의 상한이 클리핑돼요. 두 번째 쿼리에서는 하한과 상한이 모두 무시돼요. 이를 통해 하이브리드 검색에서 각 개별 쿼리에 대해 정규화 과정을 세밀하게 조정할 수 있어요:
PUT /_search/pipeline/nlp-search-pipeline
{
"description": "Post processor for hybrid search",
"phase_results_processors": [
{
"normalization-processor": {
"normalization": {
"technique": "min_max",
"parameters": {
"lower_bounds": [
{
"mode": "apply",
"min_score": 0.5
},
{
"mode": "ignore"
}
],
"upper_bounds": [
{
"mode": "clip",
"max_score": 0.8
},
{
"mode": "ignore"
}
]
}
},
"combination": {
"technique": "arithmetic_mean"
}
}
}
]
}
{% include copy-curl.html %}
검색 파이프라인 사용하기
결합하려는 쿼리 절들을 hybrid 쿼리에 지정하고, 이전 섹션에서 생성한 검색 파이프라인을 적용해서 선택한 기법들로 점수가 결합되도록 하세요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"_source": {
"exclude": [
"passage_embedding"
]
},
"query": {
"hybrid": {
"queries": [
{
"match": {
"text": {
"query": "horse"
}
}
},
{
"neural": {
"passage_embedding": {
"query_text": "wild west",
"model_id": "aVeif4oB5Vm0Tdw8zYO2",
"k": 5
}
}
}
]
}
}
}
{% include copy-curl.html %}
하이브리드 검색 설정에 대한 자세한 내용은 하이브리드 검색을 참조하세요.
검색 튜닝 권장 사항
검색 관련성을 높이려면 샘플 크기를 늘리는 것을 권장해요.
하이브리드 쿼리가 기대했던 일부 결과를 반환하지 않는다면, 하위 쿼리가 너무 적은 수의 문서를 반환하기 때문일 수 있어요. normalization-processor는 각 하위 쿼리가 반환한 결과를 변환할 뿐, 추가적인 샘플링은 수행하지 않아요. 실험에서는 반환된 문서 수(크기)에 따른 정보 검색 품질을 측정하기 위해 nDCG@10을 사용했어요. 그 결과 1,000만 개 문서까지의 데이터셋에서는 [100, 200] 범위의 크기가 가장 잘 작동한다는 것을 발견했어요. 더 높은 크기 값은 검색 관련성을 개선하지 않으면서 검색 지연 시간만 늘리기 때문에, 권장 값 이상으로 크기를 늘리는 것은 권장하지 않아요.