이 글은 제가 작업했던 내용을 정리하기 위해 수기 형식으로 작성 된 글입니다.
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-1(개요)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-2(데이터 수집, 스크래핑)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-3(셀레니움 최소화)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-4(한국어 전처리 및 워드임베딩)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-5(아이템 벡터화)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-6(웹서버 구축)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-7(검색성능향상)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-8(포트포워딩)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-9(WSGI)
2022.12.11 - [[신.만.추]] - 신입이 만드는 추천시스템-10(엘라스틱서치에 관하여)
2022.12.11 - [[신.만.추]] - 신입이 만드는 추천시스템-11(인덱스 매핑)
2022.12.11 - [[신.만.추]] - 신입이 만드는 추천시스템-12(데이터 bulk)
- 엘라스틱서치에 관하여
- 인덱스 매핑에 관하여
- 엘라스틱서치에 데이터 Bulk
- 검색쿼리와 점수산정식
- 필터 적용
저번에 작성했던 글에서 데이터를 엘라스틱서치의 bulk api 기능을 활용하여 저장했다. 이제 저장한 데이터를 기반으로 검색해보고 원하는 결과가 나오게끔 점수산정식을 조절해보려한다.

4. 검색쿼리와 점수산정식
엘라스틱서치는 RESTful api 이므로 데이터를 검색하는데도 규칙에 맞추어 요청해야한다.
서비스 부분에서는 이러한 규칙부분이 자동적으로 적용되게끔 코드를 구성해야 한다.
이 자동적으로 적용되게 만드는게 꽤나 까다로웠다.
단순히 text 타입의 전문검색만들 하는 것이었다면 좀 더 수월 했을텐데 이전 글에서 볼 수 있던 것처럼 filter기능이 적용되어있어 조금 까다로웠던 것 같다.
엘라스틱서버 검색에 활용된 Query DSL은 아래와 같이 구성하였다.
index = 'test'
body = {
"_source": ["channelLogo", "channelName", "subscribers","simTag", "videoUrl"],
"sort" : ["_score"],
"query": {
"script_score": {
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": search_key,
"fields": ["title","moreTtext","simTag"]
}
}
] ,
"should": [
{
"multi_match": {
"query": search_key,
"fields": ["title","moreText","simTag"],
"type": "phrase",
"slop": 3
}
}
],
"filter": el_filter,
"must_not": filter_seq_list
}
},
"script": {
"source": "_score*0.8 + cosineSimilarity(params.queryVector, 'youtuberVector')*100",
"params": {
"queryVector": query_vector
}
}
}
}
}
result = es.search(index=index, body=body, size=200)
검색쿼리에는 여러가지 방법이 있다. 그 방법들을 조합해서 Query DSL을 짜는 것이 포인트가 될 수 있다.
위의 구성된 내용을 위에서 부터 쭉 살펴보면, “_source” 와 “sort”가 눈에 띈다.
해당 부분은 엘라스틱서치에서 리턴받을 때 어떠한 필드만 리턴받을지, 어떻게 정렬하여 리턴받을시를 알려주는 부분이다.
그 이후를 살펴보면 query 안에 script_score가 있고, 그 안에 다시 query와 script가 있다.
간단하게 표현하면 아래와 같다.
{"query": # (1)
{"script_score": # (2)
{"query":{}, # (3)
"script":{} # (4)
}
}
}
(1) 검색에 활용 될 쿼리문 전체를 말한다.
(2) 점수 산정방식 중 자유도가 가장 높은 script_score 방식을 사용하겠다는 의미이다.
(3) 점수를 산정하기 위한 검색을 어디서 하고, 조건을 어떻게 할지를 정해주는 쿼리 부분이다.
(4) 최종적으로 검색 후에 점수 산정을 어떻게 할 것인지 정해주는 부분이다.
(2)부터 정리해보겠다.
이전 글에서도 적었던 내용이었지만 엘라스틱서치는 기본적으로 TF-IDF기반의 BM25 방식에 기반하여 검색 결과를 정렬해 준다.
BM25기반의 점수는 _score라는 필드에서 확인이 가능하다.
이러한 점수 산정 방식은 여러가지 방법으로 변형 할 수 있다. 해당 내용에 대한 자세한 설명은 참조링크에 추가하겠다.
내가 사용한 방법은 앞에서 적었던 것처럼 자유도가 높은 script_score 방식이었다.
해당 방식은 스크립트형식을 사용하여 점수를 산정하는 방식으로 다양한 파라미터를 추가하여 점수산정이 가능하다.
(3)은 (4)에서 _score를 활용하고 벡터 간 코사인 유사도를 계산하기 위해 키워드를 기반으로 검색하기 위한 역할이다.
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": search_key,
"fields": ["title","moreTtext","simTag"]
}
}
] ,
"should": [
{
"multi_match": {
"query": search_key,
"fields": ["title","moreText","simTag"],
"type": "phrase",
"slop": 3
}
}
],
"filter": el_filter,
"must_not": filter_seq_list
}
}
}
“query” 안의 내용을 살펴보면 크게 bool 안에 must, should, filter, must_not이 있는 것을 확인 할 수 있다.
must는 일반적으로 안에 있는 내용을 and형태로 묶는 역할을 한다.
따라서 안의 내용들을 모두 만족하는 데이터를 검색한다.
안의 값을 살펴보면 리스트 형식(array)으로 되어있다. ex.) “must” : [ {”multi_match”:…} ]
이는 여러 개의 조건이 들어갈 수 있음을 의미한다.
should도 값이 리스트 형태로 되어있는데 must와는 다르게 or형태로 묶는다.
이는 안의 내용들 중 하나라도 만족하는 데이터를 검색한다는 의미이다.
must와 should 모두 multi_match형태의 검색구문을 사용하는데 multi_match는 다수의 필드에서 검색단어를 찾게 된다.
위의 코드에 적용 된 type은 multi_match의 best_field(기본값)와 phrase타입니다.
best_field는 검색 된 필드 중 가장 점수가 높은 필드의 점수로 산정한다는 의미이다.
phrase타입의 경우 키워드와 완전 일치 검색을 한다는 의미이며 slop는 완전 일치 검색을 진행하되, 단어 사이에 토큰이 3개정도 포함이 되어있어도 일치한다고 보겠다는 의미이다.
필터 부분의 경우 조금 복잡해 다음글에 적어보려 한다.
(4)의 경우는 생각보다 간단하다.
"script": {
"source": "_score*0.8 + cosineSimilarity(params.queryVector, 'youtuberVector')*100",
"params": {
"queryVector": query_vector
}
}
script 안의 값들을 살펴보면 2개로 나뉜다. “source”와 “params”.
“source”는 점수를 어떻게 계산할지를 정의하는 부분이다. 안의 값을 살펴보면 이해가 될 것이다.
단, 여기서 “_score”는 BM25로 계산된 점수를 의미하며 이 점수와 검색 키워드의 백터-아이템벡터 간의 유사도를 더한 값이다. 유사도는 0.85와 같이 소수단위로 나오므로 여기에 100을 곱해준다.
참조링크
주요 검색 쿼리 예제
https://12bme.tistory.com/589
https://velog.io/@yaincoding/엘라스틱서치의-주요-검색-쿼리-정리
다양한 언어 검색기능 추가
https://www.elastic.co/kr/blog/multilingual-search-using-language-identification-in-elasticsearch
검색쿼리 관련 예제
각 쿼리문의 대한 예제
https://bakyeono.net/post/2016-08-20-elasticsearch-querydsl-basic.html#match-쿼리
bool관련 예제
https://stdhsw.tistory.com/entry/Elasticsearch-검색하기2-bool-must-mustnot-should
검색쿼리의 정렬방법
https://kazaana2009.tistory.com/4
검색결과 가공하는 방법
'[신.만.추]' 카테고리의 다른 글
| 신입이 만드는 추천시스템-14(필터 적용) (0) | 2022.12.12 |
|---|---|
| 신입이 만드는 추천시스템-12(데이터 bulk) (0) | 2022.12.11 |
| 신입이 만드는 추천시스템-11(인덱스 매핑) (0) | 2022.12.11 |
| 신입이 만드는 추천시스템-10(엘라스틱서치에 관하여) (0) | 2022.12.11 |
| 엘라스틱서치 설치 및 환경구성 (0) | 2022.12.10 |