home
🔍

작은 청크 검색 문제를 해결하는 Contextual BM25F 전략 엿보기 👀

Author
Sigrid Jin (Jin Hyung Park) / ML DevRel Engineer
Category
Hands-on
Tags
BM25F
Keyword Search
Published
2025/04/30
5 more properties

BM25와 BM25F는 어떤 차이가 있을까요?

정보 검색 분야에서 BM25 알고리즘은 오랜 기간 안정적이고 뛰어난 성능을 제공해 온 표준적인 방법론입니다. BM25 알고리즘은 기본적으로 단어 빈도(Term Frequency, TF)와 역문서 빈도(Inverse Document Frequency, IDF)를 활용해 문서의 중요도를 평가합니다.
TF(t,D) 는 단어 t 가 문서 D 에서 나타나는 빈도입니다.
∣D∣ 는 문서의 길이입니다.
avgdl 은 문서 집합 내 문서 길이의 평균입니다.
k1과 b는 파라미터로, 일반적으로 각각 1.2~2.0 및 0.75의 값을 사용합니다.
IDF(t)=log(Nn(t)+0.5)/(n(t)+0.5)IDF(t)=log (⁡N−n(t)+0.5) / (n(t)+0.5) 이며, N은 전체 문서의 수, n(t) 는 단어 t가 나타난 문서의 수입니다.
즉, 문서 내에서 특정 단어가 얼마나 자주 등장하는지와 전체 문서 중에서 얼마나 드물게 등장하는지에 따라 점수를 매기고, 이를 바탕으로 문서를 정렬하여 사용자에게 결과를 제공합니다.
그러나 기존의 BM25는 하나의 문서를 단일 텍스트로 취급합니다. 이 방식은 문서가 제목, 본문, 태그, 파일명과 같은 다양한 필드를 가지고 있을 때 각각의 필드가 가진 중요도를 구분하지 못합니다. 예를 들어 뉴스 기사라면 제목에서 매칭된 검색어가 본문에서 매칭된 것보다 더 의미가 클 수 있습니다.
소스 코드의 경우 파일명이나 심볼에서 검색어가 발견된 경우 일반 코드 본문이나 주석에서 발견된 경우보다 사용자의 의도와 더욱 밀접할 가능성이 큽니다.
이런 필드 간 중요도 차이를 반영하지 못하는 BM25의 한계를 극복하기 위해 등장한 것이 바로 BM25F입니다. BM25F 는 문서 내 여러 필드의 중요도를 별도로 가중치를 두어 처리한 후, 이를 가상 단일 필드로 병합해 계산하는 방법을 사용합니다.
여기서 TFF(t,D)TF_F(t,D)는 여러 필드를 고려하여 계산된 빈도이며, 필드별로 다른 가중치를 적용합니다. 참고
즉, 필드별로 단어 빈도를 계산할 때 중요한 필드에서 발견된 단어의 빈도에 가중치를 곱해 실제 중요도를 높입니다. 예를 들어 코드 검색 시 파일명이나 심볼 필드에서 검색어가 발견되면 그 빈도에 5배의 가중치를 곱할 수 있습니다. 이렇게 하면 마치 해당 필드에서의 단어가 5배 더 자주 등장한 것처럼 처리됩니다.
BM25F가 필드 간 가중치를 적용하면서도 기존 BM25의 중요한 특징인 "빈도 포화(saturation) 함수"를 유지합니다. 이 빈도 포화 함수는 특정 단어가 과도하게 반복되었을 때 그 중요도가 비례적으로 계속 증가하지 않고 일정 수준에서 포화되도록 만듭니다.
이는 문서가 단순히 특정 단어를 많이 포함했다고 해서 무조건 높은 점수를 받지 않도록 하는 역할을 합니다. BM25F에서도 필드별 가중치를 적용한 빈도 계산이 이 포화 함수 내에서 이루어지므로, 특정 필드에서의 단어가 지나치게 많은 점수를 얻는 것을 방지합니다.
실제 Sourcegraph에서는 BM25F 알고리즘을 코드 검색에 적용하여 품질 평가 결과 20% 이상의 개선 효과를 얻었습니다. 특히 소스 코드 검색에서 "extract tar"와 같은 검색어가 입력되었을 때, 함수 이름이나 파일 이름에서 직접적으로 매칭되는 파일을 정확하게 상위에 표시할 수 있었습니다. 이는 사용자들이 직관적으로 기대하는 검색 결과를 보다 잘 충족시킬 수 있다는 것을 보여주는 사례입니다.
예를 들어, vscode changelog라고 검색했을 때, 파일명에 정확히 일치하는 client/vscode/CHANGELOG.md 문서가 가장 먼저 나타나는 게 사용자 입장에서 더 적절합니다. 하지만 기존의 BM25로는 이를 보장할 수 없습니다.
하지만 파일명, 심볼이 더 중요하다고 생각하여 5배 가중치 를 부여하고, 코드 본문에 매치될 경우 일반적 중요도라고 생각하여 1배 가중치를 부여하였을 때 검색 결과의 품질이 높아졌다는 주장입니다.
이 외에도 쇼핑몰 사이트에서 상품명을 4배, 브랜드를 3배, 설명을 1배로 설정하여 원하는 제품을 정확히 상단을 노출한다던지, 뉴스 포털 사이트에서는 검색 시에 헤드라인을 6배, 본문을 1배로 설정하여 정확한 정보를 빠르게 제공한다던지, 사내 위키를 데이터 연결하여 사내 QA 챗봇을 만든 경우에는 제목을 5배, 본문은 1배로 설정하여 검색 품질을 향상할 수 있겠지요.

BM25와 BM25F의 차이를 다음과 같이 정리해봅시다.

특징
BM25
BM25F
문서 모델
단일 텍스트 필드
다중 필드를 단일 가상 필드로 결합 후 처리
필드별 가중치
지원하지 않음
필드별 TF(Term Frequency)에 가중치를 곱하여 반영
빈도 포화(Saturation)
있음 (k₁, b 값 사용)
동일 (가중치 반영 후에도 포화 함수 유지)
장점
간단, 직관적, 성능 우수
필드 중요도를 반영하여 정확도 향상 가능
잠재적 한계
필드 구분 없이 모든 단어 동일 취급
필드별 가중치 튜닝 필요
Qdrant 같은 일부 Vector DB 제품군에서 사용하는 Score-Boosting Reranker 는 벡터 기반 검색 결과에 추가적인 메타데이터나 비즈니스 로직을 적용하여 점수를 조정하는 방식입니다. 예를 들어 최신성을 중요하게 여기는 뉴스 검색이나 지역 근접성을 중요하게 여기는 레스토랑 검색 등에서 매우 유용합니다.
하지만 BM25F와 Score-Boosting Reranker는 근본적으로 다릅니다. BM25F는 텍스트 기반 역색인을 사용하는 키워드 검색 과정에서 필드별 중요도를 사전에 계산해 점수화하는 반면, Score-Boosting Reranker는 이미 계산된 벡터 유사도 점수 위에 추가적인 비즈니스 로직을 사후적으로 적용하는 방식입니다.

RAG 상황에서 적용하기 - Contextual BM25F

기업 고객을 위한 RAG 시스템에서 실제 문서를 다룰 때에는, 작은 단위의 청크(chunk) 로 나누어 처리하는 경우가 많습니다. 하지만 BM25 알고리즘은 청크의 길이가 짧아질수록 단어 빈도(TF) 및 문서 평균 길이(avgLen)에 따라 점수의 변동이 커지는 문제가 있습니다. 특히 짧은 청크는 BM25의 계산식에서 IDF 값이 음수로 떨어지는 경우도 발생할 수 있습니다.
이런 문제를 해결하기 위해 주변 청크(contextual chunk)를 함께 고려하는 "Contextual BM25F" 를 소개합니다. 이는 메인 청크 ± 주변 청크에 서로 다른 가중치를 부여하는 변형 BM25F 입니다. 이는 전통적인 Overlab 형태의 RAG 보다 훨씬 더 큰 문맥적 (Contextual) 정보를 포함할 수 있습니다.
예를 들어, 특정 메인 청크에 1.0 가중치를 주고, 주변 청크들에 각각 0.3 또는 0.1 가중치를 주어 합산하여 전체적인 맥락을 유지하면서도 검색 정확성을 향상시킬 수 있습니다.
tffinal=tfmain1.0+tfprev0.3+tfnext0.3+tfprev20.1+tfnext20.1tf_{final} = tf_{main} * 1.0 + tf_{prev} * 0.3 + tf_{next} * 0.3 + tf_{prev2} * 0.1 + tf_{next2} * 0.1
필드별 가중치는 직관적으로 설정하는 경우가 많지만, 실질적인 성능 향상을 위해서는 체계적인 튜닝이 필요합니다. 일반적으로 다음과 같은 기준점을 가지고 가중치를 설정하게 됩니다. Storm Platform 과 같은 최적화 도구를 사용하면 이러한 가중치 설정을 자동으로 추천하고 관리자가 피드백을 할 수 있습니다.
실제 엔터프라이즈 레벨에서 (Contextual) BM25F 는 여러 필드 간 중요도를 효율적으로 처리할 수 있다는 점에서 매우 유용합니다. 하지만 실제 환경에서 적용할 때에는 고려해야 할 몇 가지 지점이 존재합니다.
청크가 매우 짧아지면 avgdl|D| 값이 모두 작아져 BM25의 길이 정규화 효과가 과도하게 적용됩니다.
긴 질의(query) 또는 복합적인 맥락을 담은 질의에 대한 대응력에 한계가 발생하게 됩니다.
인접 청크가 물리적으로 가까워도 실제 의미가 동떨어진 경우가 있어, “거리 기반 가중치”만으로는 문맥 품질이 떨어질 수 있습니다.
이러한 문제를 해결하기 위하여 실제 엔터프라이즈 프로젝트에서는 주변 청크를 가중치 기반으로 병합하여 문맥을 강화하거나, 필드 가중치를 고정하지 않고 질의별로 최적화된 가중치를 동적으로 학습하여 적용하여 문제를 해결하기도 합니다.
Contextual BM25F에서는 메인 청크(거리 0)에 1.0, 바로 앞·뒤 청크(거리 1)에 0.3, 두 칸 떨어진 청크(거리 2)에 0.1을 곱해 단순 합산하는 것에서 그치지 않습니다. 여기에 ColBERT로 측정한 의미적 유사도를 정규화 하여 (0 ~ 1) 연관항을 추가합니다.
ColBERT는 각 청크를 BERT로 임베딩한 뒤, 쿼리와 청크 간 최대 토큰 유사도 합계를 점수로 사용하게 됩니다. 여기서의 핵심은 청크 간 물리적인 거리가 가까워도 ColBERT 유사도가 낮다면 해당 청크는 계산식에 참여하지 않습니다. 반대로 의미적으로 매우 비슷하면 Sk=1S_k=1 이 되어 기존 거리 가중치만큼 반영됩니다.
tf0(t)tf_0(t) 은 메인 청크에서 단어 빈도, w0=1w_0=1.
tfk(t)tf_k(t) 은 거리 만큼 떨어진 청크의 빈도.
WkW_k 은 거리 기반 가중치(e.g. 0.3, 0.1).
SkS_k 은 ColBERT 유사도이며 미리 정한 임계치 보다 낮으면 0으로 잘라 의미가 어긋난 청크를 자동 제외 합니다.

Evaluation

평가에 사용한 데이터는 공공 복지 정책 및 서비스에 관련된 내용으로 채워져 있으며, 멀티홉 청크 검색 문제가 필수적인 약 56개의 질문을 바탕으로 청크의 참조번호 (reference number) 의 Recall 비율을 관측하였습니다.
세 지표(Recall@3 / 5 / 10) 모두 BM25 → BM25F → ContextualBM25F 순으로 꾸준히 상승합니다. 특히 ContextualBM25F는 문맥을 고려해 인접 청크까지 가중치를 주므로, Recall@10 에서 0.2666 → 0.3303(약 24%p 향상)로 가장 크게 개선되었습니다.

마치며

BM25F의 핵심 가치는 문서가 하나의 필드로만 구성된 것이 아니라 제목, 본문 등 여러 개의 필드로 이루어졌을 때 의미가 있습니다. 따라서 문서를 학습할 때에 필드를 정의하는 전략이 매우 중요하고, 해당 전략에 따라 메타데이터 필드가 잘 정의되어 있다면 검색 결과를 극한으로 올리기 위해 사용할 수 있는 방법들 중 하나가 됩니다.
기존 BM25는 제목과 본문이 구분된 문서가 있을 때도 두 필드를 단순히 하나의 텍스트로 합쳐서 유사도를 계산하거나, 제목과 본문의 유사도를 따로 계산해 새로운 문서를 만드는 방식을 사용했습니다. 반면, BM25F는 각 필드의 중요도에 따라 가중치를 달리 부여하여 제목과 본문을 동시에 고려할 수 있다는 점에서 의미가 크다고 생각합니다.
임베딩 모델과 같이 Sparse하지 않은 Dense한 모델에서 제목과 같이 문서의 여러 메타적인 정보를 같이 학습시켜 문서의 유사도를 평가했을 때 성능이 오르지 않거나 제자리인 것을 주로 발견할 수 있습니다. 그런데 별도의 딥러닝 모델의 학습 없이 Sparse한 모델에서 여러 메타데이터 필드에 더 많은 가중치를 보고 검색할 수 있다는 측면에서 효율적인 성능 향상을 기대할 수 있습니다.
이러한 BM25F의 아이디어를 차용하여 검색하고자 하는 메인 청크 주위의 여러 청크에 대해 TF (Term Frequency) 가중치를 문맥이 가까운 만큼 많이 부여하고 합산하는 등의 Contextual한 맥락을 볼 수 있다면 실제 엔터프라이즈 환경에서 효과적인 성능 개선을 이끌 수 있겠습니다.