본문 바로가기
WIKI 기술 지식 베이스

Similarity

원문 보기 위키 갱신

출처: 문서

본문

유사도

유사도는 검색 작업 중 일치하는 문서가 어떻게 점수화되고 순위가 매겨지는지를 정의해요. OpenSearch는 유사도 알고리즘을 사용하여 검색 결과의 순서를 결정하는 관련성 점수를 계산해요.

각 필드는 고유한 유사도 구성을 가질 수 있어서, 다양한 콘텐츠 유형이 점수화되는 방식을 세밀하게 제어할 수 있어요. 인덱스 설정에서 인덱스 수준으로 사용자 정의 유사도 알고리즘을 정의할 수 있어요. 일단 구성되면, similarity 매핑 파라미터를 사용하여 이러한 알고리즘을 특정 필드에 적용할 수 있어요.

사용자 정의 유사도 설정을 구성하는 것은 고급 기능이에요. 대부분의 사용 사례에서는 내장 유사도로 충분해요.

인덱스 수준에서 유사도 구성하기

OpenSearch는 모든 필드의 기본 유사도로 BM25를 사용해요. 인덱스의 기본 유사도를 변경하여 해당 인덱스의 모든 필드에 단일 유사도 알고리즘을 적용할 수 있어요.

인덱스 생성 시 기본 유사도 설정

인덱스를 생성할 때 기본 유사도를 설정할 수 있어요:

PUT /product_catalog
{
  "settings": {
    "index": {
      "similarity": {
        "default": {
          "type": "boolean"
        }
      }
    }
  }
}

{% include copy-curl.html %}

특별한 default 이름을 사용하면 유사도가 인덱스의 모든 필드에 자동으로 적용돼요. 개별 필드 매핑에서 유사도를 지정할 필요가 없어요.

인덱스 생성 후 기본 유사도 변경

유사도 설정은 동적이지 않아서 열려 있는 인덱스에서 업데이트하면 실패해요. 인덱스 생성 후 기본 유사도를 변경하려면 인덱스를 닫고, 설정을 업데이트한 다음, 다시 열어야 해요.

먼저, 인덱스를 닫아요:

POST /product_catalog/_close

{% include copy-curl.html %}

다음으로, 기본 유사도를 업데이트해요:

PUT /product_catalog/_settings
{
  "index": {
    "similarity": {
      "default": {
        "type": "DFR",
        "basic_model": "g",
        "after_effect": "l",
        "normalization": "h2",
        "normalization.h2.c": "3.0"
      }
    }
  }
}

{% include copy-curl.html %}

마지막으로, 인덱스를 다시 열어요:

POST /product_catalog/_open

{% include copy-curl.html %}

기본 유사도가 변경되었는지 확인하려면 다음 요청을 보내요:

GET /product_catalog/_settings

{% include copy-curl.html %}

응답은 기본 유사도가 업데이트되었음을 확인해줘요:

{
  "product_catalog": {
    "settings": {
      "index": {
        "replication": {
          "type": "DOCUMENT"
        },
        "number_of_shards": "1",
        "provided_name": "product_catalog",
        "similarity": {
          "default": {
            "basic_model": "g",
            "type": "DFR",
            "normalization": "h2",
            "after_effect": "l",
            "normalization.h2": {
              "c": "3.0"
            }
          }
        },
        "creation_date": "1761328470322",
        "number_of_replicas": "1",
        "uuid": "YFr9ts8VSaSV-YkHZP3mYQ",
        "version": {
          "created": "137227827"
        }
      }
    }
  }
}

유사도 유형 변경 시 파라미터 지속성

한 유사도 유형에서 다른 유형으로 변경할 때 OpenSearch는 이전 구성의 파라미터를 유지해요. 새 유사도 유형이 이러한 파라미터를 지원하지 않으면 업데이트가 실패해요.

예를 들어, 기본 유사도를 DFR에서 boolean으로 변경해 보아요. 먼저, 인덱스를 닫아요:

POST /product_catalog/_close

{% include copy-curl.html %}

그런 다음 기본 유사도를 업데이트해요:

PUT /product_catalog/_settings
{
  "index": {
    "similarity": {
      "default": {
        "type": "boolean"
      }
    }
  }
}

{% include copy-curl.html %}

DFR 파라미터가 여전히 설정되어 있기 때문에 요청이 실패해요:

{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "Unknown settings for similarity of type [boolean]: [normalization.h2.c, normalization, after_effect, basic_model]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "Unknown settings for similarity of type [boolean]: [normalization.h2.c, normalization, after_effect, basic_model]"
  },
  "status": 400
}

이 문제를 해결하려면 동일한 요청에서 이전 파라미터를 명시적으로 null로 설정해요:

PUT /product_catalog/_settings
{
  "index": {
    "similarity": {
      "default": {
        "type": "boolean",
        "basic_model": null,
        "after_effect": null,
        "normalization": null,
        "normalization.h2.c": null
      }
    }
  }
}

{% include copy-curl.html %}

그런 다음 인덱스를 다시 열어요:

POST /product_catalog/_open

{% include copy-curl.html %}

필드 수준에서 내장 유사도 구성하기

OpenSearch는 필드 매핑에서 직접 사용할 수 있는 내장 유사도 알고리즘(BM25 및 boolean)을 제공해요. 필드 수준에서 유사도를 구성하려면 인덱스를 생성할 때 해당 필드에 적용해요. 다음 예제는 content 필드에 boolean 유사도를 적용해요:

PUT /blog_posts_2/
{
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "similarity": "boolean"
      }
    }
  }
}

{% include copy-curl.html %}

이 예제에서는 content 필드만 boolean 유사도를 사용해요. 인덱스의 다른 필드는 기본 BM25 유사도를 사용해요.

사용자 정의 유사도 구성하기

더 고급 사용 사례를 위해 특정 필드에 적용할 수 있는 명명된 사용자 정의 유사도를 정의할 수 있어요. 이 접근 방식은 인덱스의 다른 필드에 다른 점수 알고리즘이 필요할 때 더 큰 유연성을 제공해요.

명명된 사용자 정의 유사도를 구성하려면 두 단계 과정이 필요해요:

  1. 인덱스 설정에서 사용자 정의 이름으로 유사도를 정의해요.
  2. 매핑에서 특정 필드에 유사도를 적용해요.

1단계: 사용자 정의 유사도 정의

다음 예제는 사용자 정의 DFR 유사도 구성으로 인덱스를 생성해요:

PUT /blog_posts
{
  "settings": {
    "index": {
      "similarity": {
        "blog_similarity": {
          "type": "DFR",
          "basic_model": "g",
          "after_effect": "l",
          "normalization": "h2",
          "normalization.h2.c": "3.0"
        }
      }
    }
  }
}

{% include copy-curl.html %}

2단계: 필드에 사용자 정의 유사도 적용

유사도를 정의한 후에는 매핑의 특정 필드에 명시적으로 적용해야 해요. 기본 유사도와 달리 사용자 정의 유사도는 모든 필드에 자동으로 적용되지 않아요:

PUT /blog_posts/_mapping
{
  "properties": {
    "content": {
      "type": "text",
      "similarity": "blog_similarity"
    }
  }
}

{% include copy-curl.html %}

이 예제에서는 content 필드만 사용자 정의 blog_similarity를 사용해요. 인덱스의 다른 필드(있는 경우)는 명시적으로 다르게 구성되지 않는 한 기본 BM25 유사도를 계속 사용해요.

사용 가능한 유사도 유형

OpenSearch는 다음 유사도 유형을 지원해요.

BM25 유사도 (기본값)

BM25 유사도는 TF/IDF를 기반으로 하며 내장된 용어 빈도 정규화를 포함해요. 대부분의 텍스트 필드, 특히 제목이나 이름 같은 짧은 필드에서 잘 작동해요.

BM25 유사도는 다음 매개변수를 지원해요.

매개변수 설명 기본값 필수 여부
k1 비선형 용어 빈도 정규화(포화)를 제어해요. 1.2 아니요
b 문서 길이가 용어 빈도 값을 정규화하는 정도를 제어해요. 0.75 아니요
discount_overlaps 규범 계산 시 중복 토큰(위치 증가분이 0인 토큰)을 무시할지 결정해요. true (규범 계산 시 중복 토큰 무시) 아니요

Boolean 유사도

내장된 boolean 유사도는 일치하는 모든 문서에 동일한 상수 점수를 할당해요. 문서의 관련성보다는 일치 여부만 확인하면 될 때 유용해요. 이 유사도는 용어 빈도, 문서 길이 및 기타 점수 요소를 무시해요.

boolean 유사도는 매개변수를 지원하지 않아요.

DFR 유사도

DFR 유사도는 문서 점수 매기기를 위해 무작위성으로부터의 발산 프레임워크를 구현해요.

DFR 유사도는 다음 매개변수를 지원해요.

매개변수 설명 유효 값 필수 여부
basic_model DFR 프레임워크의 기본 모델이에요. g, if, in, ine 예
after_effect DFR 프레임워크의 사후 효과 모델이에요. b, l 예
normalization DFR 프레임워크의 정규화 모델이에요. h1, h2, h3는 선택적 c 매개변수를 받으며, normalization.h1.c, normalization.h2.c, normalization.h3.c로 지정해요. z는 선택적 z 매개변수를 받으며, normalization.z.z로 지정하고 값은 0보다 크고 0.5보다 작아야 해요. no는 매개변수를 받지 않아요. no, h1, h2, h3, z 예

DFI 유사도

DFI 유사도는 독립성으로부터의 발산(DFI) 모델을 구현해요.

DFI 유사도는 다음 매개변수를 지원해요.

매개변수 설명 유효 값 필수 여부
independence_measure DFI 모델의 독립성 측정이에요. standardized, saturated, chisquared 예

DFI 유사도를 사용할 때는 최적의 관련성을 위해 불용어를 제거하지 않는 것이 좋아요. 예상보다 빈도가 낮은 용어는 0점을 받아요.

IB 유사도

IB 유사도는 정보 기반 모델을 사용하며, 기호 분포에서 기본 요소의 반복 사용을 분석해요.

IB 유사도는 다음 매개변수를 지원해요.

매개변수 설명 유효 값 필수 여부
distribution IB 프레임워크의 분포 모델이에요. ll, spl 예
lambda IB 프레임워크의 람다 모델이에요. df, ttf 예
normalization IB 프레임워크의 정규화 모델이에요. DFR 유사도와 동일한 옵션 예

LM Dirichlet 유사도

LMDirichlet 유사도는 Dirichlet 평활화를 사용한 언어 모델 유사도를 사용해요.

LMDirichlet 유사도는 다음 매개변수를 지원해요.

매개변수 설명 기본값 필수 여부
mu 평활화 매개변수예요. 2000 아니요

언어 모델이 예측한 것보다 발생 횟수가 적은 용어는 0점을 받아요.

LM Jelinek Mercer 유사도

LMJelinekMercer 유사도는 Jelinek-Mercer 평활화를 사용한 언어 모델 유사도를 사용해요.

LMJelinekMercer 유사도는 다음 매개변수를 지원해요.

매개변수 설명 기본값 필수 여부
lambda 보간 매개변수예요. 0.1 정도의 값은 제목 쿼리에 잘 작동하고, 0.7은 더 긴 쿼리에 더 좋아요. 0.1 아니요

Scripted 유사도

scripted 유사도는 OpenSearch의 스크립팅 기능을 사용하여 사용자 정의 점수 로직을 허용해요.

scripted 유사도용 스크립트를 작성할 때는 다음 변수에 접근할 수 있어요. 이 변수들을 사용하면 용어 빈도, 문서 빈도, 필드 통계 및 문서 특성을 기반으로 사용자 정의 점수 알고리즘을 구현할 수 있어요.

변수 설명
weight 문서 독립적인 가중치예요(weight_script가 제공되면 그 값, 그렇지 않으면 1.0).
query.boost 용어에 적용되는 쿼리 수준 부스트 요소예요.
field.docCount 이 필드를 가진 문서의 총 개수예요.
field.sumDocFreq 이 필드의 모든 용어에 대한 문서 빈도의 합계예요.
field.sumTotalTermFreq 이 필드의 모든 용어에 대한 총 용어 빈도의 합계예요.
term.docFreq 이 특정 용어를 포함하는 문서의 수예요.
term.totalTermFreq 모든 문서에서 이 용어가 나타난 총 횟수예요.
doc.freq 현재 문서에서 이 용어의 빈도예요.
doc.length 현재 문서의 총 용어 수예요.

올바른 검색 동작을 보장하려면 scripted 유사도는 다음 규칙을 따라야 해요.

  • 반환된 점수는 양수여야 해요.
  • 다른 모든 변수가 동일할 때 doc.freq가 증가해도 점수가 감소하지 않아야 해요.
  • 다른 모든 변수가 동일할 때 doc.length가 증가해도 점수가 증가하지 않아야 해요.

다음 예제는 사용자 정의 TF-IDF 구현을 보여줘요.

먼저 인덱스를 생성하고 유사도를 계산할 스크립트를 정의해요.

PUT /research_papers
{
  "settings": {
    "number_of_shards": 1,
    "similarity": {
      "custom_tfidf": {
        "type": "scripted",
        "script": {
          "source": "double tf = Math.sqrt(doc.freq); double idf = Math.log((field.docCount+1.0)/(term.docFreq+1.0)) + 1.0; double norm = 1/Math.sqrt(doc.length); return query.boost * tf * idf * norm;"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "abstract": {
        "type": "text",
        "similarity": "custom_tfidf"
      }
    }
  }
}

{% include copy-curl.html %}

샘플 문서를 인덱싱해요.

PUT /research_papers/_doc/1
{
  "abstract": "machine learning algorithms data mining"
}

{% include copy-curl.html %}

PUT /research_papers/_doc/2
{
  "abstract": "data analysis statistical methods"
}

{% include copy-curl.html %}

인덱스를 새로고침해요.

POST /research_papers/_refresh

{% include copy-curl.html %}

이제 인덱스를 검색하고 점수 설명을 요청할 수 있어요.

GET /research_papers/_search?explain=true
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "abstract": "machine"
          }
        }
      ],
      "boost": 2.0
    }
  }
}

{% include copy-curl.html %}

응답은 일치하는 문서에 대해 1.2570862 점수의 사용자 정의 TF-IDF 계산을 보여줘요. _explanation 섹션은 스크립트에서 사용할 수 있는 모든 변수와 이 특정 검색에 대한 값을 보여줘요.

{
  "took": 6,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.2570862,
    "hits": [
      {
        "_shard": "[research_papers][0]",
        "_node": "KfEEGG7_SsKZVFqI4ko2FA",
        "_index": "research_papers",
        "_id": "1",
        "_score": 1.2570862,
        "_source": {
          "abstract": "machine learning algorithms data mining"
        },
        "_explanation": {
          "value": 1.2570862,
          "description": "weight(abstract:machine in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 1.2570862,
              "description": "score from ScriptedSimilarity(weightScript=[null], script=[Script{type=inline, lang='painless', idOrCode='double tf = Math.sqrt(doc.freq); double idf = Math.log((field.docCount+1.0)/(term.docFreq+1.0)) + 1.0; double norm = 1/Math.sqrt(doc.length); return query.boost * tf * idf * norm;', options={}, params={}}]) computed from:",
              "details": [
                {
                  "value": 1.0,
                  "description": "weight",
                  "details": []
                },
                {
                  "value": 2.0,
                  "description": "query.boost",
                  "details": []
                },
                {
                  "value": 2,
                  "description": "field.docCount",
                  "details": []
                },
                {
                  "value": 9,
                  "description": "field.sumDocFreq",
                  "details": []
                },
                {
                  "value": 9,
                  "description": "field.sumTotalTermFreq",
                  "details": []
                },
                {
                  "value": 1,
                  "description": "term.docFreq",
                  "details": []
                },
                {
                  "value": 1,
                  "description": "term.totalTermFreq",
                  "details": []
                },
                {
                  "value": 1.0,
                  "description": "doc.freq",
                  "details": []
                },
                {
                  "value": 5,
                  "description": "doc.length",
                  "details": []
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

문서 독립적인 계산을 weight_script라는 별도 스크립트로 분리하면 성능을 향상시킬 수 있어요. 많은 문서와 일치하는 쿼리의 경우 weight_script는 용어당 한 번 실행되고, 기본 script는 문서당 한 번 실행돼요. weight_script가 생성한 점수는 weight 변수에서 사용할 수 있어요.

PUT /research_papers_optimized
{
  "settings": {
    "number_of_shards": 1,
    "similarity": {
      "optimized_tfidf": {
        "type": "scripted",
        "weight_script": {
          "source": "double idf = Math.log((field.docCount+1.0)/(term.docFreq+1.0)) + 1.0; return query.boost * idf;"
        },
        "script": {
          "source": "double tf = Math.sqrt(doc.freq); double norm = 1/Math.sqrt(doc.length); return weight * tf * norm;"
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "abstract": {
        "type": "text",
        "similarity": "optimized_tfidf"
      }
    }
  }
}

{% include copy-curl.html %}

동일한 샘플 문서를 research_papers_optimized에 인덱싱하고, 인덱스를 새로고침한 다음, 동일한 쿼리를 실행해요. 응답은 최적화된 유사도가 어떻게 작동하는지 보여줘요.

{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.2570862,
    "hits": [
      {
        "_shard": "[research_papers_optimized][0]",
        "_node": "KfEEGG7_SsKZVFqI4ko2FA",
        "_index": "research_papers_optimized",
        "_id": "1",
        "_score": 1.2570862,
        "_source": {
          "abstract": "machine learning algorithms data mining"
        },
        "_explanation": {
          "value": 1.2570862,
          "description": "weight(abstract:machine in 0) [PerFieldSimilarity], result of:",
          "details": [
            {
              "value": 1.2570862,
              "description": "score from ScriptedSimilarity(weightScript=[Script{type=inline, lang='painless', idOrCode='double idf = Math.log((field.docCount+1.0)/(term.docFreq+1.0)) + 1.0; return query.boost * idf;', options={}, params={}}], script=[Script{type=inline, lang='painless', idOrCode='double tf = Math.sqrt(doc.freq); double norm = 1/Math.sqrt(doc.length); return weight * tf * norm;', options={}, params={}}]) computed from:",
              "details": [
                {
                  "value": 2.8109303,
                  "description": "weight",
                  "details": []
                },
                {
                  "value": 2.0,
                  "description": "query.boost",
                  "details": []
                },
                {
                  "value": 2,
                  "description": "field.docCount",
                  "details": []
                },
                {
                  "value": 9,
                  "description": "field.sumDocFreq",
                  "details": []
                },
                {
                  "value": 9,
                  "description": "field.sumTotalTermFreq",
                  "details": []
                },
                {
                  "value": 1,
                  "description": "term.docFreq",
                  "details": []
                },
                {
                  "value": 1,
                  "description": "term.totalTermFreq",
                  "details": []
                },
                {
                  "value": 1.0,
                  "description": "doc.freq",
                  "details": []
                },
                {
                  "value": 5,
                  "description": "doc.length",
                  "details": []
                }
              ]
            }
          ]
        }
      }
    ]
  }
}

이 최적화된 예제에서:

  • weight_script는 2.8109303 점수를 계산해요(IDF + 쿼리 부스트, 모든 문서에 동일).
  • 기본 script는 미리 계산된 weight 값을 사용하여 문서별 요소를 계산해요.
  • 최종 점수는 1.2570862예요(단일 스크립트 방식과 동일하지만 더 효율적).

weight_script 매개변수 이름은 예약되어 있으며 변경할 수 없어요. 결과는 항상 기본 스크립트 내의 weight 변수에서 사용할 수 있어요. {: .note}

관련 문서

더 알아보기 (Learn more)