Function score 쿼리
Function score 쿼리
function_score 쿼리는 결과에 반환된 문서의 관련성 점수를 변경해야 할 때 사용해요. 쿼리와 하나 이상의 함수를 정의해 모든 결과 또는 결과의 일부 하위 집합에 적용해 관련성 점수를 재계산합니다. 문서가 반환되는 집합을 바꾸는 게 아니라 문서가 어떻게 순위가 매겨지는지를 바꿉니다.
출처: 문서
본문
function_score 쿼리는 결과에 반환된 문서의 관련성 점수를 변경해야 할 때 사용해요. function_score 쿼리는 쿼리 하나와 모든 결과 또는 결과의 일부 하위 집합에 적용해 관련성 점수를 재계산할 수 있는 하나 이상의 함수를 정의해요. function_score 쿼리는 어떤 문서가 반환되는지를 바꾸는 것이 아니라 문서가 어떻게 순위가 매겨지는지를 바꿔요. 최상위 query 파라미터를 생략하면 function_score는 match_all로 실행되어 인덱스의 모든 문서가 일치하고 기본 쿼리 점수 1을 받아요. 반환되는 문서를 제한하려면 명시적인 최상위 쿼리를 제공하거나, function_score를 bool 쿼리로 감싸거나, min_score를 지정하세요.
이 섹션의 예시들은 다음 문서를 포함한 blogs 인덱스를 사용해요.
POST _bulk
{ "index": { "_index": "blogs", "_id": "1" } }
{ "name": "Semantic search in OpenSearch", "views": 1200, "likes": 150, "comments": 16, "date_posted": "2022-04-17" }
{ "index": { "_index": "blogs", "_id": "2" } }
{ "name": "Get started with OpenSearch 2.7", "views": 1400, "likes": 100, "comments": 20, "date_posted": "2022-05-02" }
{ "index": { "_index": "blogs", "_id": "3" } }
{ "name": "Distributed tracing with Data Prepper", "views": 800, "likes": 50, "comments": 5, "date_posted": "2022-04-25" }
{ "index": { "_index": "blogs", "_id": "4" } }
{ "name": "A very old blog", "views": 100, "likes": 20, "comments": 3, "date_posted": "2000-04-25" }
하나의 점수 함수 사용하기 (Using one scoring function)
function_score 쿼리의 가장 기본적인 예시는 하나의 함수를 사용해 점수를 재계산하는 거예요. 다음 쿼리는 weight 함수를 사용해 모든 관련성 점수를 두 배로 해요. 최상위 query 파라미터가 지정되지 않아 function_score가 match_all로 실행되므로 이 함수는 결과의 모든 문서에 적용돼요.
GET blogs/_search
{
"query": {
"function_score": {
"weight": "2"
}
}
}
점수를 매길 문서 제한하기 (Limiting which documents are scored)
최상위 query 파라미터를 사용해 function_score가 실행될 문서를 정의하세요. 이 쿼리와 일치하는 문서만 반환돼요. 다음 쿼리는 OpenSearch와 일치하는 블로그 게시물로 결과를 제한한 뒤 그들의 관련성 점수를 두 배로 해요.
GET blogs/_search
{
"query": {
"function_score": {
"query": {
"match": {
"name": "OpenSearch"
}
},
"weight": "2"
}
}
}
문서의 하위 집합에 점수 함수 적용하기 (Applying a scoring function to a subset of documents)
점수 함수를 일치 문서의 일부 하위 집합에만 적용하려면 functions 배열의 함수에 filter를 지정하세요. 함수는 해당 필터와 일치하는 문서에만 점수에 기여해요. 필터를 생략하는 것은 match_all을 지정하는 것과 동일하므로 해당 함수는 모든 문서에 적용돼요. filter 쿼리가 산출한 관련성 점수는 계산에 사용되지 않아요.
함수 필터는 어떤 문서에 함수가 점수를 매길지 결정하지, 어떤 문서가 반환될지를 결정하지 않아요. 최상위 쿼리(또는 암시적 match_all)와 일치하지만 어떤 함수 필터와도 일치하지 않는 문서는 여전히 반환돼요.
다음 쿼리는 조회수가 1,000 이상인 블로그 게시물의 점수에 0.5를 더하고, 좋아요가 150 이상인 블로그 게시물의 점수에 1을 더해요. 최상위 쿼리가 지정되지 않았으므로 function_score는 match_all로 실행되어 인덱스의 모든 문서를 반환해요.
GET blogs/_search
{
"query": {
"function_score": {
"score_mode": "sum",
"functions": [
{
"filter": {
"range": {
"views": {
"gte": 1000
}
}
},
"weight": 0.5
},
{
"filter": {
"range": {
"likes": {
"gte": 150
}
}
},
"weight": 1
}
]
}
}
}
네 블로그 게시물이 모두 반환돼요.
- 문서 1은 두 필터 모두와 일치하므로 1.5의 점수를 받아요(함수 계수 0.5 + 1에 match_all 쿼리 점수 1을 곱한 값).
- 문서 2는 views 필터만 일치하므로 0.5의 점수를 받아요.
- 문서 3과 4는 어떤 필터와도 일치하지 않아 각각 1의 점수를 받아요. 암시적 match_all 쿼리가 1을 기여하고, 일치한 함수가 없으므로 함수 계수도 1이에요.
최종 점수 = 쿼리 점수 × 함수 계수 = 1 × 1 = 1
따라서 어떤 함수 필터와도 일치하지 않는 문서가 일치하는 문서보다 위에 순위가 매겨질 수 있어요. 앞의 결과에서 문서 3과 4는 1점으로, 문서 2는 0.5점이에요. 예상치 못한 점수의 원인이 이 때문인지 확인하려면 explain을 true로 설정하고 설명에서 No function matched 항목을 찾아보세요. 이런 문서를 제외하려면 Returning only documents that match a function filter를 참조하세요.
지원되는 함수 (Supported functions)
function_score 쿼리 타입은 다음 함수를 지원해요.
내장(Built-in):
weight: 문서 점수에 미리 정의된 부스트 계수를 곱함.random_score: 단일 사용자에게는 일관되지만 사용자 간에는 다른 임의 점수를 제공함.field_value_factor: 지정된 문서 필드의 값을 사용해 점수를 재계산함.- Decay 함수(gauss, exp, linear): 지정된 감쇠(decay) 함수를 사용해 점수를 재계산함.
사용자 정의(Custom):
script_score: 스크립트를 사용해 문서에 점수를 매김.
weight 함수 (The weight function)
weight 함수를 사용하면 원래 관련성 점수에 weight의 부동소수점 값이 곱해져요.
GET blogs/_search
{
"query": {
"function_score": {
"weight": "2"
}
}
}
boost 값과 달리 weight 함수는 정규화되지 않아요.
다른 함수 없이 weight만 지정하면 그것은 weight 값을 반환하는 함수로 작동해요. functions 배열에서 다른 함수와 함께 지정하면 그것은 다른 함수가 산출한 점수를 곱해요.
random score 함수 (The random score function)
random_score 함수는 단일 사용자에게는 일관되지만 사용자 간에는 다른 임의 점수를 제공해요. 점수는 [0, 1) 범위의 부동소수점 수예요. seed를 제공하지 않으면 OpenSearch는 현재 시간에서 seed를 파생하고 내부 Lucene 문서 ID를 사용해 문서에 점수를 매겨요. 결과 점수는 재현 가능하지 않아요. 요청 간에 바뀌고, 세그먼트 병합 후 문서가 다시 번호가 매겨질 수도 있어요. 임의 값 생성에서 일관성을 얻으려면 seed와 field 파라미터를 제공하세요. field는 fielddata가 활성화된 필드여야 해요(보통 숫자 필드). 점수는 seed, 필드의 fielddata 값, 그리고 인덱스 이름과 샤드 ID를 사용해 계산된 salt를 사용해 계산돼요. 같은 샤드에 있는 문서는 인덱스 이름과 샤드 ID가 같으므로, 필드 값이 같은 문서는 같은 점수를 할당받아요. 같은 샤드의 모든 문서에 다른 점수를 보장하려면 모든 문서에 대해 고유한 값을 가진 필드를 사용하세요. 한 가지 옵션은 _seq_no 필드를 사용하는 거예요. 하지만 이 필드를 선택하면 문서가 업데이트될 때 해당 _seq_no 업데이트 때문에 점수가 바뀔 수 있어요.
다음 쿼리는 random_score 함수를 seed와 field와 함께 사용해요.
GET blogs/_search
{
"query": {
"function_score": {
"random_score": {
"seed": 20,
"field": "_seq_no"
}
}
}
}
field 없이 seed만 지정하는 것은 비권장(고지)되어 있어요. 이 경우 OpenSearch는 _id 필드를 사용하는데, 이것은 fielddata 로딩을 필요로 하고 많은 메모리를 소모해요. seed를 지정할 때는 항상 field를 제공하세요.
field value factor 함수 (The field value factor function)
field_value_factor 함수는 지정된 문서 필드의 값을 사용해 점수를 재계산해요. 필드가 다중 값 필드라면 계산에는 첫 번째 값만 사용되고 나머지는 고려되지 않아요.
field_value_factor 함수는 다음 옵션을 지원해요.
field: 점수 계산에 사용할 필드.factor: 필드 값에 곱해지는 선택 계수. 기본값은 1.modifier: 필드 값v에 적용할 수정자 중 하나. 다음 표는 지원되는 모든 수정자를 나열해요.
| 수정자 | 공식 | 설명 |
|---|---|---|
log |
log(v) |
값의 밑 10 로그를 취함. 양수가 아닌 수의 로그를 취하는 것은 불법 연산이며 오류를 발생시켜요. 0(제외)과 1(포함) 사이의 값에 대해 이 함수는 오류를 발생시키는 음수가 아닌 값을 반환해요. log 대신 log1p 또는 log2p를 사용하는 걸 권장해요. |
log1p |
log(1 + v) |
1과 값의 합의 밑 10 로그를 취함. |
log2p |
log(2 + v) |
2와 값의 합의 밑 10 로그를 취함. |
ln |
ln(v) |
값의 자연 로그를 취함. 양수가 아닌 수의 로그를 취하는 것은 불법 연산이며 오류를 발생시켜요. 0(제외)과 1(포함) 사이의 값에 대해 이 함수는 오류를 발생시키는 음수가 아닌 값을 반환해요. ln 대신 ln1p 또는 ln2p를 사용하는 걸 권장해요. |
ln1p |
ln(1 + v) |
1과 값의 합의 자연 로그를 취함. |
ln2p |
ln(2 + v) |
2와 값의 합의 자연 로그를 취함. |
reciprocal |
1/v |
값의 역수를 취함. |
square |
v² |
값을 제곱함. |
sqrt |
√v |
값의 제곱근을 취함. 음수의 제곱근을 취하는 것은 불법 연산이며 오류를 발생시켜요. v가 음수가 아닌지 확인하세요. |
none |
해당 없음 | 어떤 수정자도 적용하지 않음. |
missing: 필드가 문서에 없을 때 사용할 값.factor와modifier는 누락된 필드 값 대신 이 값에 적용돼요.
예를 들어 다음 쿼리는 field_value_factor 함수를 사용해 views 필드에 더 많은 가중치를 줘요.
GET blogs/_search
{
"query": {
"function_score": {
"field_value_factor": {
"field": "views",
"factor": 1.5,
"modifier": "log1p",
"missing": 1
}
}
}
}
앞의 쿼리는 다음 공식을 사용해 관련성 점수를 계산해요.
점수 = 원래 점수 · log(1 + 1.5 · views)
script score 함수 (The script score function)
script_score 함수를 사용하면 문서 점수를 매기는 사용자 정의 스크립트를 작성할 수 있고, 선택적으로 문서의 필드 값을 포함시킬 수 있어요. 원래 관련성 점수는 _score 변수로 접근할 수 있어요.
계산된 점수는 음수일 수 없어요. 음수 점수는 오류를 발생시켜요. 문서 점수는 양의 32비트 부동소수점 값이에요. 더 높은 정밀도의 점수는 가장 가까운 32비트 부동소수점 수로 변환돼요.
예를 들어 다음 쿼리는 script_score 함수를 사용해 원래 점수와 블로그 게시물의 조회수·좋아요 수를 기반으로 점수를 계산해요. 조회수와 좋아요 수에 더 작은 가중치를 주기 위해 이 공식은 views와 likes의 합의 로그를 취해요. views와 likes 수가 0이어도 로그가 유효하도록 그 합에 1을 더해요.
GET blogs/_search
{
"query": {
"function_score": {
"query": {"match": {"name": "opensearch"}},
"script_score": {
"script": "_score * Math.log(1 + doc['likes'].value + doc['views'].value)"
}
}
}
}
스크립트는 더 빠른 성능을 위해 컴파일되고 캐시돼요. 따라서 같은 스크립트를 재사용하고 스크립트가 필요한 파라미터를 전달하는 것이 좋아요.
GET blogs/_search
{
"query": {
"function_score": {
"query": {
"match": { "name": "opensearch" }
},
"script_score": {
"script": {
"params": {
"add": 1
},
"source": "_score * Math.log(params.add + doc['likes'].value + doc['views'].value)"
}
}
}
}
}
기본적으로 쿼리 점수에 스크립트 결과가 곱해져요. 스크립트 결과를 최종 점수로 사용하려면 boost_mode를 replace로 설정하세요. 자세한 내용은 Combining the score for all functions with the query score를 참조하세요.
Decay 함수 (Decay functions)
많은 애플리케이션에서 근접성이나 최신성을 기준으로 결과를 정렬해야 해요. 이는 decay 함수로 할 수 있어요. decay 함수는 Gaussian, exponential, linear의 세 가지 감쇠 곡선 중 하나를 사용해 문서 점수를 계산해요.
decay 함수는 숫자, 날짜, geopoint 필드에서만 작동해요.
decay 함수는 다음 그림과 같이 origin, scale, offset, decay를 기반으로 점수를 계산해요.
예시: Geopoint 필드
사무실 근처의 호텔을 찾고 있다고 가정해 볼게요. location 필드를 geopoint로 매핑한 hotels 인덱스를 만들어요.
PUT hotels
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
근처 호텔에 해당하는 두 문서를 인덱싱해요.
PUT hotels/_doc/1
{
"name": "Hotel Within 200",
"location": {
"lat": 40.7105,
"lon": 74.00
}
}
PUT hotels/_doc/2
{
"name": "Hotel Outside 500",
"location": {
"lat": 40.7115,
"lon": 74.00
}
}
origin은 거리가 계산되는 지점(사무실 위치)을 정의해요. offset은 문서에 전체 점수 1이 주어지는 origin으로부터의 거리를 지정해요. 사무실에서 200ft 이내의 호텔에 같은 최고 점수를 줄 수 있어요. scale은 그래프의 감쇠율을 정의하고, decay는 origin으로부터 scale + offset 거리에 있는 문서에 할당할 점수를 정의해요. 200ft 반경을 벗어난다면, 호텔에 도달하기 위해 300ft를 더 걸어야 한다면(scale = 300ft) 원래 점수의 4분의 1(decay = 0.25)을 할당하기로 결정할 수 있어요.
origin을 (74.00, 40.71)로 하여 다음 쿼리를 작성해요.
GET hotels/_search
{
"query": {
"function_score": {
"functions": [
{
"exp": {
"location": {
"origin": "40.71,74.00",
"offset": "200ft",
"scale": "300ft",
"decay": 0.25
}
}
}
]
}
}
}
응답에는 두 호텔이 모두 포함돼요. 사무실에서 200ft 이내의 호텔은 1의 점수를 가지며, 500ft 반경 밖의 호텔은 decay 파라미터 0.25보다 작은 0.20의 점수를 가져요.
응답
{
"took": 854,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "hotels",
"_id": "1",
"_score": 1,
"_source": {
"name": "Hotel Within 200",
"location": {
"lat": 40.7105,
"lon": 74
}
}
},
{
"_index": "hotels",
"_id": "2",
"_score": 0.20099315,
"_source": {
"name": "Hotel Outside 500",
"location": {
"lat": 40.7115,
"lon": 74
}
}
}
]
}
}
파라미터 (Parameters)
다음 표는 gauss, exp, linear 함수가 지원하는 모든 파라미터를 나열해요.
| 파라미터 | 설명 |
|---|---|
origin |
거리를 계산할 지점. 숫자 필드에는 숫자로, 날짜 필드에는 날짜로, geopoint 필드에는 geopoint로 제공해야 해요. geopoint와 숫자 필드에는 필수. 날짜 필드에는 선택(기본값은 now). 날짜 필드에서는 날짜 수학이 지원돼요(예: now-2d). |
offset |
문서에 점수 1이 주어지는 origin으로부터의 거리를 정의함. 선택 사항. 기본값은 0. |
scale |
origin으로부터 scale + offset 거리에 있는 문서에 decay 점수가 할당됨. 필수. 숫자 필드에서는 scale이 아무 숫자나 될 수 있어요. 날짜 필드에서는 scale이 단위가 있는 숫자로 정의될 수 있어요(5h, 1d). 단위가 제공되지 않으면 scale은 밀리초로 기본 설정돼요. geopoint 필드에서는 scale이 단위가 있는 숫자로 정의될 수 있어요(1mi, 5km). 단위가 제공되지 않으면 scale은 미터로 기본 설정돼요. |
decay |
origin으로부터 scale + offset 거리에 있는 문서의 점수를 정의함. 선택 사항. 기본값은 0.5. |
문서에 없는 필드에 대해 decay 함수는 점수 1을 반환해요.
예시: 숫자 필드
다음 쿼리는 지수 감쇠 함수를 사용해 댓글 수로 블로그 게시물의 우선순위를 정해요.
GET blogs/_search
{
"query": {
"function_score": {
"functions": [
{
"exp": {
"comments": {
"origin": "20",
"offset": "5",
"scale": "10"
}
}
}
]
}
}
}
결과의 처음 두 블로그 게시물은 하나가 origin(20)에 있고 다른 하나는 거리 16에 있어 전체 점수를 받는 범위(20 ± 5, 즉 [15, 25]) 안이므로 점수가 1이에요. 세 번째 블로그 게시물은 origin에서 scale + offset 거리(20 − (5 + 10) = 15)만큼 떨어져 있어 기본 decay 점수(0.5)가 주어져요.
응답
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "blogs",
"_id": "1",
"_score": 1,
"_source": {
"name": "Semantic search in OpenSearch",
"views": 1200,
"likes": 150,
"comments": 16,
"date_posted": "2022-04-17"
}
},
{
"_index": "blogs",
"_id": "2",
"_score": 1,
"_source": {
"name": "Get started with OpenSearch 2.7",
"views": 1400,
"likes": 100,
"comments": 20,
"date_posted": "2022-05-02"
}
},
{
"_index": "blogs",
"_id": "3",
"_score": 0.5,
"_source": {
"name": "Distributed tracing with Data Prepper",
"views": 800,
"likes": 50,
"comments": 5,
"date_posted": "2022-04-25"
}
},
{
"_index": "blogs",
"_id": "4",
"_score": 0.4352753,
"_source": {
"name": "A very old blog",
"views": 100,
"likes": 20,
"comments": 3,
"date_posted": "2000-04-25"
}
}
]
}
}
예시: 날짜 필드
다음 쿼리는 Gaussian 감쇠 함수를 사용해 04/24/2022 근처에 게시된 블로그 게시물의 우선순위를 정해요.
GET blogs/_search
{
"query": {
"function_score": {
"functions": [
{
"gauss": {
"date_posted": {
"origin": "2022-04-24",
"offset": "1d",
"scale": "6d",
"decay": 0.25
}
}
}
]
}
}
}
결과에서 첫 번째 블로그 게시물은 04/24/2022로부터 1일 이내에 게시되어 가장 높은 점수 1을 가져요. 두 번째 블로그 게시물은 04/17/2022에 게시되었는데, 이는 offset + scale(1d + 6d) 안이라 decay(0.25)와 같은 점수를 가져요. 세 번째 블로그 게시물은 04/24/2022 이후 7일 이상 지나 게시되어 더 낮은 점수를 가져요. 마지막 블로그 게시물은 몇 년 전에 게시되어 점수 0을 가져요.
응답
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "blogs",
"_id": "3",
"_score": 1,
"_source": {
"name": "Distributed tracing with Data Prepper",
"views": 800,
"likes": 50,
"comments": 5,
"date_posted": "2022-04-25"
}
},
{
"_index": "blogs",
"_id": "1",
"_score": 0.25,
"_source": {
"name": "Semantic search in OpenSearch",
"views": 1200,
"likes": 150,
"comments": 16,
"date_posted": "2022-04-17"
}
},
{
"_index": "blogs",
"_id": "2",
"_score": 0.15154076,
"_source": {
"name": "Get started with OpenSearch 2.7",
"views": 1400,
"likes": 100,
"comments": 20,
"date_posted": "2022-05-02"
}
},
{
"_index": "blogs",
"_id": "4",
"_score": 0,
"_source": {
"name": "A very old blog",
"views": 100,
"likes": 20,
"comments": 3,
"date_posted": "2000-04-25"
}
}
]
}
}
다중 값 필드 (Multi-valued fields)
decay 계산을 위해 지정한 필드가 여러 값을 포함하면 multi_value_mode 파라미터를 사용할 수 있어요. 이 파라미터는 계산에 사용될 필드 값을 결정하는 다음 함수 중 하나를 지정해요.
min: (기본값) origin으로부터의 최소 거리.max: origin으로부터의 최대 거리.avg: origin으로부터의 평균 거리.sum: origin으로부터의 모든 거리의 합.
예를 들어 거리 배열이 있는 문서를 인덱싱해요.
PUT testindex/_doc/1
{
"distances": [1, 2, 3, 4, 5]
}
다음 쿼리는 다중 값 필드 distances의 최대 거리를 사용해 decay를 계산해요.
GET testindex/_search
{
"query": {
"function_score": {
"functions": [
{
"exp": {
"distances": {
"origin": "6",
"offset": "5",
"scale": "1"
},
"multi_value_mode": "max"
}
}
]
}
}
}
origin으로부터의 최대 거리(1)가 origin으로부터의 offset 안이므로 문서에 점수 1이 주어져요.
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1,
"hits": [
{
"_index": "testindex",
"_id": "1",
"_score": 1,
"_source": {
"distances": [
1,
2,
3,
4,
5
]
}
}
]
}
}
Decay 곡선 계산 (Decay curve calculation)
다음 공식들은 다양한 decay 함수의 점수 계산을 정의해요(v는 문서 필드 값을 나타냄).
Gaussian
score = exp(−(max(0, |v − origin| − offset))² / (2σ²))
여기서 σ는 origin으로부터 offset + scale 거리에서 점수가 decay와 같아지도록 계산됩니다.
σ² = −scale² / (2·ln(decay))
Exponential
score = exp(λ · max(0, |v − origin| − offset))
여기서 λ는 origin으로부터 offset + scale 거리에서 점수가 decay와 같아지도록 계산됩니다.
λ = ln(decay) / scale
Linear
score = max((s − max(0, |v − origin| − offset)) / s)
여기서 s는 origin으로부터 offset + scale 거리에서 점수가 decay와 같아지도록 계산됩니다.
s = scale / (1 − decay)
여러 점수 함수 사용하기 (Using multiple scoring functions)
functions 배열에 나열해 function score 쿼리에서 여러 점수 함수를 지정할 수 있어요.
여러 함수의 점수 결합하기 (Combining scores from multiple functions)
서로 다른 함수는 점수에 서로 다른 척도를 사용할 수 있어요. 예를 들어 random_score 함수는 0과 1 사이의 점수를 제공하지만 field_value_factor는 점수에 특정 척도가 없어요. 또한 다른 함수가 준 점수를 다르게 가중하려 할 수도 있어요. 다른 함수의 점수를 조정하려면 각 함수에 weight 파라미터를 지정할 수 있어요. 그러면 각 함수가 준 점수에 weight가 곱해져 해당 함수의 최종 점수가 산출돼요. weight 파라미터는 weight 함수와 구별하기 위해 functions 배열 안에 제공해야 해요.
각 함수가 준 점수는 다음 값 중 하나를 취하는 score_mode 파라미터를 사용해 결합돼요.
multiply: (기본값) 점수를 곱함.sum: 점수를 더함.avg: 점수를 평균함.weight가 지정되면 가중 평균이 돼요. 예를 들어 weight 1의 첫 번째 함수가 점수 10을 반환하고 weight 4의 두 번째 함수가 점수 20을 반환하면 평균은 (10·1 + 20·4)/(1 + 4) = 18로 계산돼요.first: 일치하는 필터가 있는 첫 번째 함수의 점수를 취함.max: 최대 점수를 취함.min: 최소 점수를 취함.
문서가 어떤 함수 필터와도 일치하지 않으면 모든 score_mode 값에 대해 함수 점수는 중립 값 1로 유지돼요.
점수의 상한 지정하기 (Specifying an upper limit for a score)
max_boost 파라미터에 function score의 상한을 지정할 수 있어요. 기본 상한은 float 값의 최대 크기인 (2 − 2⁻²³)·2¹²⁷이에요.
전체 쿼리 부스트하기 (Boosting the whole query)
최상위 boost 파라미터를 사용해 function_score 쿼리 전체를 부스트하세요. boost 값은 최상위 쿼리가 지정되지 않았을 때 암시적 match_all 쿼리의 점수를 포함한 쿼리 점수에 곱해져요. 기본값은 1.
max_boost는 최종 점수가 아니라 결합된 함수 점수를 상한으로 막기 때문에, 1보다 큰 boost 값은 max_boost를 초과하는 점수를 생성할 수 있어요. 예를 들어 weight 10, max_boost 2, boost 5를 가진 쿼리는 점수 10을 반환해요. 함수 점수는 2에서 상한이 막히고 그 다음 부스트된 쿼리 점수 5가 곱해져요.
모든 함수의 점수를 쿼리 점수와 결합하기 (Combining the score for all functions with the query score)
모든 함수를 사용해 계산된 점수를 쿼리 점수와 어떻게 결합할지는 boost_mode 파라미터로 지정할 수 있고, 다음 값 중 하나를 취해요.
multiply: (기본값) 쿼리 점수에 함수 점수를 곱함.replace: 쿼리 점수를 무시하고 함수 점수를 사용함.sum: 쿼리 점수와 함수 점수를 더함.avg: 쿼리 점수와 함수 점수의 평균.max: 쿼리 점수와 함수 점수 중 더 큰 값을 취함.min: 쿼리 점수와 함수 점수 중 더 작은 값을 취함.
기본 boost_mode인 multiply와 암시적 match_all 쿼리에서 어떤 함수와도 일치하지 않는 문서는 점수 1을 받아요.
임계값을 충족하지 못하는 문서 필터링하기 (Filtering documents that don't meet a threshold)
관련성 점수를 바꿔도 일치 문서 목록은 바뀌지 않아요. 임계값을 충족하지 못하는 일부 문서를 제외하려면 min_score 파라미터에 임계값을 지정하세요. 그러면 쿼리가 반환한 모든 문서가 점수화되고 임계값으로 필터링돼요.
min_score는 점수화 후에 적용되므로 함수 필터에 일치했는지로 문서를 제외하지 않아요. 앞의 예시에서 min_score를 0.9로 설정하면 views 필터와 일치해 0.5점을 받은 문서 2는 제외되지만, 필터와 일치하지 않아 1점을 받은 문서 3과 4는 유지돼요.
함수 필터와 일치하는 문서만 반환하기 (Returning only documents that match a function filter)
함수 필터와 일치하는 문서만 반환하려면 함수 필터에 의존하는 대신 minimum_should_match가 있는 bool 쿼리를 사용하세요.
GET blogs/_search
{
"query": {
"bool": {
"should": [
{ "range": { "views": { "gte": 1000 } } },
{ "range": { "likes": { "gte": 150 } } }
],
"minimum_should_match": 1
}
}
}
문서 1과 2만 반환돼요. 점수 함수를 사용해 그 문서들에 순위를 매기려면 function_score 쿼리의 최상위 쿼리로 bool 쿼리를 제공하세요.
예시 (Example)
다음 요청은 "OpenSearch Data Prepper"라는 단어를 포함한 블로그 게시물을 검색하며, 04/24/2022 근처에 게시된 게시물을 선호해요. 또한 조회수와 좋아요 수가 고려돼요. 마지막으로 컷오프 임계값은 점수 6으로 설정돼 있어요.
GET blogs/_search
{
"query": {
"function_score": {
"boost": "5",
"functions": [
{
"gauss": {
"date_posted": {
"origin": "2022-04-24",
"offset": "1d",
"scale": "6d"
}
},
"weight": 1
},
{
"gauss": {
"likes": {
"origin": 200,
"scale": 200
}
},
"weight": 4
},
{
"gauss": {
"views": {
"origin": 1000,
"scale": 800
}
},
"weight": 2
}
],
"query": {
"match": {
"name": "opensearch data prepper"
}
},
"max_boost": 10,
"score_mode": "max",
"boost_mode": "multiply",
"min_score": 6
}
}
}
세 블로그 게시물이 쿼리와 일치하지만 min_score 임계값 6이 가장 낮은 점수의 게시물을 제외하므로 응답에는 두 블로그 게시물이 포함돼요.
응답
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 14.178148,
"hits": [
{
"_index": "blogs",
"_id": "3",
"_score": 14.178148,
"_source": {
"name": "Distributed tracing with Data Prepper",
"views": 800,
"likes": 50,
"comments": 5,
"date_posted": "2022-04-25"
}
},
{
"_index": "blogs",
"_id": "1",
"_score": 6.321524,
"_source": {
"name": "Semantic search in OpenSearch",
"views": 1200,
"likes": 150,
"comments": 16,
"date_posted": "2022-04-17"
}
}
]
}
}
이름 있는 함수 (Named functions)
함수를 정의할 때 최상위 레벨에서 _name 파라미터로 이름을 지정할 수 있어요. 이 이름은 디버깅과 점수 산정 과정을 이해하는 데 유용해요. 지정되면 함수 이름은 가능할 때마다 점수 계산 설명에 포함돼요(함수, 필터, 쿼리에 적용됨). 응답에서 _name으로 함수를 식별할 수 있어요.
예시 (Example)
다음 요청은 디버깅 목적으로 explain을 true로 설정해 응답에서 점수 산정 설명을 얻어요. 각 함수는 _name 파라미터를 포함하므로 함수를 명확히 식별할 수 있어요.
GET blogs/_search
{
"explain": true,
"size": 1,
"query": {
"function_score": {
"functions": [
{
"_name": "likes_function",
"script_score": {
"script": {
"lang": "painless",
"source": "return doc['likes'].value * 2;"
}
},
"weight": 0.6
},
{
"_name": "views_function",
"field_value_factor": {
"field": "views",
"factor": 1.5,
"modifier": "log1p",
"missing": 1
},
"weight": 0.3
},
{
"_name": "comments_function",
"gauss": {
"comments": {
"origin": 1000,
"scale": 800
}
},
"weight": 0.1
}
]
}
}
}
응답은 점수 산정 과정을 설명해요. 각 함수에 대해 설명은 설명 텍스트에 함수 _name을 포함해요. 값 1의 *:* 항목은 최상위 쿼리가 지정되지 않았기 때문의 암시적 match_all 쿼리의 점수예요.
응답
{
"took": 14,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 6.1600614,
"hits": [
{
"_shard": "[blogs][0]",
"_node": "_yndTaZHQWimcDgAfOfRtQ",
"_index": "blogs",
"_id": "1",
"_score": 6.1600614,
"_source": {
"name": "Semantic search in OpenSearch",
"views": 1200,
"likes": 150,
"comments": 16,
"date_posted": "2022-04-17"
},
"_explanation": {
"value": 6.1600614,
"description": "function score, product of:",
"details": [
{
"value": 1,
"description": "*:*",
"details": []
},
{
"value": 6.1600614,
"description": "min of:",
"details": [
{
"value": 6.1600614,
"description": "function score, score mode [multiply]",
"details": [
{
"value": 180,
"description": "product of:",
"details": [
{
"value": 300,
"description": "script score function(_name: likes_function), computed with script:\"Script{type=inline, lang='painless', idOrCode='return doc['likes'].value * 2;', options={}, params={}}\"",
"details": [
{
"value": 1,
"description": "_score: ",
"details": [
{
"value": 1,
"description": "*:*",
"details": []
}
]
}
]
},
{
"value": 0.6,
"description": "weight",
"details": []
}
]
},
{
"value": 0.9766541,
"description": "product of:",
"details": [
{
"value": 3.2555137,
"description": "field value function(_name: views_function): log1p(doc['views'].value?:1.0 * factor=1.5)",
"details": []
},
{
"value": 0.3,
"description": "weight",
"details": []
}
]
},
{
"value": 0.035040613,
"description": "product of:",
"details": [
{
"value": 0.35040614,
"description": "Function for field comments:",
"details": [
{
"value": 0.35040614,
"description": "exp(-0.5*pow(MIN[Math.max(Math.abs(16.0(=doc value) - 1000.0(=origin))) - 0.0(=offset), 0)],2.0)/461662.4130844683, _name: comments_function)",
"details": []
}
]
},
{
"value": 0.1,
"description": "weight",
"details": []
}
]
}
]
},
{
"value": 3.4028235e+38,
"description": "maxBoost",
"details": []
}
]
}
]
}
}
]
}
}