어휘 검색은 BM25 순위 알고리즘을 사용하여 저렴하고, 빠르며, 다양한 쿼리에 매우 효과적입니다. 하지만 문서와 토큰을 공유하지 않는 쿼리는 사각지대에 빠집니다. 이 글에서는 BM25의 부족한 부분을 정확히 측정해 보겠습니다. Elasticsearch의 순위 평가 API(rank_eval)를 사용하고, Elastic Inference Service(EIS)를 통해 Jina AI 임베딩을 추가하여 격차를 좁힐 것입니다. 리콜 점수가 0.43에서 0.75 로 올라가는 것을 보면 그 이유를 확인하실 수 있습니다.
리콜이란 무엇입니까?
리콜 척도는 사용자가 실제로 원하는 문서가 검색 결과 어딘가에 나타나는 정도를 0에서 1까지의 범위로 측정합니다. 만약 쿼리에 3개의 제품이 표시되어야 하는데 검색 결과 상위 10개 제품 중 2개만 표시되는 경우, 해당 쿼리에 대해 recall@10 = 0.67을 입력합니다. 이는 집합 기반 메트릭이므로 k 결과 내에서 관련 문서의 위치는 중요하지 않습니다. 10번 위치에 있는 관련 문서는 1번 위치에 있는 문서와 동일하게 계산됩니다. 리콜률이 높다는 것은 관련성 있는 결과를 놓치지 않고 있다는 뜻입니다.

다이어그램은 모든 관련 문서(왼쪽)와 BM25가 실제로 검색한 문서(상위 10개, 오른쪽)의 두 가지 세트를 보여줍니다. 오직 교차점만이 리콜에 포함되며, prod_1과 prod_2는 발견되었지만, prod_3, prod_4, prod_6은 완전히 누락되었습니다. 결과: Recall@10 = 2/5 = 0.40.
필수 구성 요소
리콜이 어떻게 작동하는지에 대해 자세히 알아보겠습니다. 이 데모에서는 Python을 사용합니다. 제공된 노트북(notebook.ipynb)을 통해 따라 해보실 수 있습니다. 모든 코드 블록은 실행할 준비가 된 셀입니다.
제공된 코드는 다음을 사용합니다.
- Elasticsearch 9.3+
- Python 3.10+
- Elasticsearch 자격 증명이 포함된
.env파일
데이터 세트
신발, 전자제품, 공구 등의 카테고리를 아우르는 1,000개의 제품 카탈로그.
각 문서에는 네 개의 필드가 있습니다.
| 필드 | 유형 |
|---|---|
| `title` | 텍스트 |
| `설명` | 텍스트 |
| `브랜드` | 키워드 |
| '카테고리` | 키워드 |
데이터 세트는 dataset.csv에서 로드되었습니다.
어휘 검색의 힘과 한계
BM25는 Elasticsearch 및 대부분의 검색 엔진에서 기본 순위 알고리즘으로 사용됩니다. 쿼리 용어가 문서에 나타나는 빈도에 따라 점수를 매기며, 이는 문서 길이와 전체 인덱스에서 이러한 용어의 빈도에 따라 조정됩니다. 소문자 정규화, 어간 제거, 중단어 제거 등의 분석 기능을 상단에서 사용할 수 있습니다. '러닝화'를 검색하면 '러닝화'와 '실행'이 모두 검색 결과에 나타날 가능성이 높습니다.
이는 대규모 쿼리 클래스에 적합합니다.
- '러닝화'를 입력하면 제목에 토큰이 정확히 일치하는 제품이 즉시 검색됩니다.
- '블루투스 스피커'는 토큰이 문자 그대로 나타나기 때문에 휴대용 오디오 제품을 표시합니다.
결과는 결정론적이고 설명 가능합니다. 문서가 높은 순위를 차지한 이유는 쿼리 용어가 문서에 포함되어 있기 때문입니다. 디버깅 관련성은 간단합니다.
부족한 부분
이제 동일한 카탈로그에 대해 다음 쿼리를 실행해 보겠습니다.
- '스킨케어 루틴': '루틴'이라는 단어는 제품 제목에 나타나지 않습니다. BM25는 '스킨케어'와 부분적으로 일치시킬 수 있지만, 페이스 세럼, 바디 오일, 보습제 등은 '비타민 C', '레티놀', 또는 '브라이트닝' 등 검색어와 겹치지 않는 용어를 사용하여 설명됩니다. 완전한 스킨케어 루틴을 구성하는 제품들은 인덱스에 흩어져 있으며, 이를 연결할 공유 토큰이 없습니다.
- '반려동물 여행용 액세서리': 이는 제품 카테고리가 아닌 사용 사례 그룹입니다. 반려견 슬링 캐리어, 반려동물 카시트, 여행용 케이지는 모두 관련성이 있지만 '여행용 액세서리'보다는 휴대성, 안전성, 편안함에 대한 설명이 더 많습니다. BM25는 '반려동물'을 광범위하게 매칭하지만, 여행 관련 제품을 나머지 반려동물 카탈로그와 구분하는 신호가 없습니다.
이것은 리콜 문제입니다. 관련 문서가 인덱스에 존재합니다. 그러나 사용자의 입력 내용과 문서의 내용이 충분히 일치하지 않기 때문에 BM25가 해당 내용을 찾지 못합니다.
동의어를 추가하면 알려진 경우에 도움이 됩니다. 하지만 사용자가 의사를 표현하는 모든 방법을 열거할 수는 없습니다. 이것이 벡터가 중요한 이유입니다.
리콜을 측정해야 하는 이유
문제를 해결하기 전에 그것을 정량화해야 합니다.
Recall@k는 사용자가 실제로 원하는 문서가 검색 결과 위치에 관계없이 얼마나 많이 나타나는지를 측정합니다. 공식적으로
Precision@k는 상위 k개의 결과와 실제 연관성이 있는 결과의 수를 측정합니다.
정확도가 높다는 것은 반환되는 결과가 좋다는 것을 의미합니다. 전자상거래에서 관련 제품을 놓치는 것(낮은 리콜)은 약간 불완전한 결과(낮은 정확도)를 보여주는 것보다 더 나쁠 수 있습니다. 제품이 드러나지 않으면 판매 손실로 연결되기 때문입니다.
Elasticsearch의 rank_eval API를 사용하면 두 가지를 체계적으로 측정할 수 있습니다. 사용자가 각각 등급이 매겨진 문서 세트가 포함된 쿼리 목록을 제공하면 Elasticsearch가 모든 쿼리에 대해 메트릭을 계산합니다.
평가 설정
rank_eval API에는 관련성 등급(0 = 관련성 없음, 1 = 관련성 있음, 2 = 매우 관련성 있음)과 함께 각 쿼리에 대한 관련 문서 매핑인 평가 데이터 세트가 필요합니다.
노트북에서 이것은 심사 목록입니다:
이 조합은 의도적으로 구성된 것입니다.q1은 BM25가 잘 처리하는 쿼리(제품 제목의 정확한 토큰 포함)이고, q2, q3, q4는 사용자의 의도를 특정 제품 키워드가 아닌 개념으로 표현하는 의도 기반 쿼리입니다.
BM25 기준 리콜 측정
먼저, Elasticsearch 클라이언트를 설정하고 원시 텍스트 데이터를 색인합니다.
이제 BM25에 대한 rank_eval 요청을 생성합니다. 목록의 각 요청은 쿼리와 그 등급을 결합합니다.
결과:
0.43 이는 네 개의 쿼리 모두에서 BM25가 찾아야 하는 문서 중 43%만을 찾았다는 의미입니다. 부족한 부분은 의도 기반 쿼리에 집중되어 있습니다. '스킨케어 루틴'은 제품 제목에 '루틴'이 포함되지 않는 페이스 세럼과 바디 오일을 놓치고, '여행용 반려동물 액세서리'는 주제와 다른 반려동물 제품을 검색하는 반면, 휴대성과 안전성 측면을 강조해서 설명하는 캐리어와 케이지는 정작 빠져 있습니다.
여기가 기준이 됩니다. 이제 넘어야 할 목표가 생겼습니다.
Jina 임베딩으로 벡터 검색 추가
Vector search 문서와 쿼리를 고차원 벡터로 인코딩합니다. 고차원 벡터는 수백에서 수천 개의 수치 값으로 구성되며, 각 값은 해당 데이터의 특정한 특징을 인코딩합니다. 비슷한 의미를 가진 문서들은 단어가 공유되지 않더라도 벡터 공간에서 서로 가깝게 배치됩니다. '헬스 기구'와 '덤벨 세트'는 개념이 관련되어 있기 때문에 가까이에 배치될 것입니다. 하이브리드 검색을 지원하여 의미론적 이해와 키워드 정확도를 모두 제공하는 Elasticsearch를 벡터 데이터베이스로 선택했습니다.
EIS는 추론 API를 통해 모델 임베딩을 즉시 지원하는 기능을 포함하고 있습니다.
1단계: Jina 임베딩 v5를 추론 엔드포인트로 사용
클러스터에 GPU 리소스가 있는 경우(Elastic Cloud 및 Elasticsearch 9.3 이상에서 사용 가능), 임베딩이 GPU에서 생성되어 CPU 추론보다 훨씬 빠르며, 과거에 대규모 벡터 처리를 비용 부담으로 만들었던 성능 트레이드오프 문제를 해소합니다.
왜 Jina 임베딩을 사용할까요? jina-embeddings-v5-text는 32,000개 이상의 토큰 컨텍스트 창을 가진 다국어 모델(119개 이상의 언어)로, 작업별 LoRA(Low-Rank Adaptation) 어댑터를 지원합니다. 바로 사용할 수 있는 짧은 제품 설명에 적합합니다. jina-embeddings-v5-text 모델에 대한 자세한 내용은 여기에서 확인하실 수 있습니다.
2단계: 시맨틱 필드로 인덱스 생성
여기서는 semantic_text 필드 유형이 핵심입니다. dense_vector보다 더 높은 수준의 추상화입니다. 추론 엔드포인트를 가리키면 Elasticsearch가 자동으로 임베딩 생성을 처리합니다.
title과 description의 copy_to 속성은 두 필드의 콘텐츠가 semantic_field로 흘러들어가 임베딩되므로, 단일 벡터가 전체 제품 표현을 캡처합니다.
3단계: 제품 인덱스 생성
색인 시점에 Elasticsearch는 각 문서에 대해 추론 엔드포인트를 호출하고, 결과 임베딩을 semantic_field에 저장합니다. 사용자 측에서 추가 코드를 작성할 필요가 없습니다.
하이브리드 검색: BM25 및 벡터와 RRF의 결합
벡터를 추가하면 리콜이 향상되지만, 벡터만 사용하면 정확한 일치 쿼리에서 정확도를 잃을 위험이 있습니다. '러닝화'와 같은 용어는 여전히 문자 자체가 일치하는 결과가 우선 순위에 표시되어야 합니다. 하이브리드 검색은 정확성을 보장하기 위해 어휘 기반 검색 요소를 유지합니다.
상호 순위 결합(RRF)을 사용한 하이브리드 검색은 두 가지 장점을 모두 유지합니다.
- BM25는 정확하거나 거의 정확한 쿼리를 높은 정확도로 처리합니다.
- 의미 검색은 의도 기반 및 다국어 쿼리를 높은 리콜로 처리합니다.
- RRF는 두 개의 순위 목록을 단일 순위로 결합합니다.
RRF 공식은 각 문서의 결과 목록 순위에 따라 점수를 부여합니다.
두 목록 모두에서 높은 순위를 차지한 문서가 더 높은 합산 점수를 받습니다. rank_constant는 하위 등급의 문서가 받는 가중치를 조절합니다.
결과:
하이브리드는 BM25(0.43)에 비해 성능이 크게 향상되었으며, '러닝화'와 같은 정확한 일치 쿼리에 대한 정확확도를 유지합니다.
결과: 전후 비교
세 가지 접근 방식을 모두 비교한 전체 내용은 다음과 같습니다.
결과:
| 메서드 | Recall@10 |
|---|---|
| BM25(어휘 검색) | 0.43 |
| 하이브리드(BM25 + 벡터) | 0.75 |

쿼리별로 분석해 보겠습니다.

결론
이 포스팅에서는 사용자가 정확한 쿼리를 입력할 때 BM25 어휘 검색이 신뢰할 수 있지만, 키워드보다는 의도에 따라 검색할 때 리콜이 떨어진다는 것을 보았습니다. rank_eval을 사용하여 재현 가능한 기준선을 설정하고 실제 숫자로 그 차이를 측정했습니다. 그 후, Jina 임베딩을 기반으로 하는 semantic_text 필드를 추가하고 평가를 다시 실행했습니다. 결과적으로, 하이브리드 검색은 리콜을 0.43에서 0.75로 높이면서도 정확한 일치 쿼리의 정확도를 유지했지만, 실제 마진은 쿼리 구성에 따라 달라집니다.
이 패턴은 이 예제 이상으로 확장됩니다. 사용자의 실제 쿼리에서 판단을 수집하고 rank_eval을 기준선으로 실행한 다음 semantic_text를 추가하고 다시 측정하세요. 어떤 부분이 얼마나 개선되었는지 정확히 알 수 있습니다.
다음 단계
- 리콜과 벡터 검색에 대해 자세히 알아보기: Jeff Vestal의 리콜 및 벡터 검색 양자화
- 상위 결과의 정확도를 높이기 위해 재순위 추가
- Elasticsearch 하이브리드 검색 문서 살펴보기
rank_evalAPI에 대해 자세히 알아보기




