RAG

[RAG] Sparse Vector 방식 비교 (BM25 VS BM25 + 형태소 분석기 VS SPLADE VS BM42)

moonzoo 2025. 11. 4. 17:12

https://blog.vectorchord.ai/hybrid-search-with-postgres-native-bm25-and-vectorchord

0. 서론 : Sparse 검색 - BM25, 형태소 분석기, 그리고 BM42 전격 비교

최신 AI 기반 금융 문서 검색 시스템을 구축할 때, 우리는 두 가지 상반된 요구에 직면합니다.

  1. 사용자가 "삼성전자"를 검색하면, "삼성전자"라는 키워드가 명확히 포함된 문서를 정확히 찾아야 합니다. (키워드 검색)
  2. 사용자가 "돈 빌리는 법"을 검색하면, "대출 자격 요건"이나 "신용대출 가이드"처럼 의미가 통하는 문서를 찾아야 합니다. (의미 검색)

 

전자는 키워드 기반의 '희소 벡터(Sparse Vector)' 검색의 영역이고, 후자는 의미 기반의 '밀도 벡터(Dense Vector)' 검색의 영역입니다. 대부분의 검색에서 저는 이 둘을 혼합한 하이브리드 검색을 사용합니다.

여기서는 키워드를 정확하게 찾아내는 '희소 벡터' 검색의 대표적인 네 가지 접근 방식, 즉 1) 기본 BM25, 2) BM25 + 한국어 형태소 분석기, 3) SPLADE 4) 최신 BM42에 대해 자세히 알아보고, 가장 적합한 방식을 비교 분석해 보겠습니다.


1. BM25

BM25(Best Matching 25)는 지난 40년간 검색 엔진의 표준으로 자리 잡은, 통계에 기반한 고전적인 희소 벡터(Sparse Vector) 검색 알고리즘입니다. 그 원리는 매우 직관적이면서도 강력합니다.

 

BM25는 기본적으로 "문서와 쿼리의 연관성을 어떻게 점수화할 것인가?" 라는 질문에 대해 두 가지 핵심 축을 기준으로 답합니다.

 

  1. IDF (Inverse Document Frequency) - 단어의 희귀성:
    • "이 단어가 전체 문서 집합에서 얼마나 드물게 등장하는가?"
    • 사용자가 "삼성전자"와 "입니다"를 동시에 검색했을 때, "입니다"는 거의 모든 문서에 등장하므로 점수가 낮고, "삼성전자"는 특정 문서에만 등장하므로 점수가(중요도가) 높아야 합니다.
    • 즉, 검색어가 전체 문서에서 드물게 등장할수록 더 중요한 키워드로 판단해 높은 가중치를 부여합니다.
  2. TF (Term Frequency) - 단어의 빈도 (와 포화도):
    • "이 단어가 이 문서에 얼마나 자주 등장하는가?"
    • "삼성전자"가 한 번도 등장하지 않은 문서보다 10번 등장한 문서가 더 관련성이 높을 것입니다.
    • 다만, BM25는 단순한 TF-IDF와 달리 '포화도(Saturation)' 개념이 있습니다. "삼성전자"가 10번 등장한 문서와 100번 등장한 문서의 관련성 차이는, 0번 등장한 문서와 10번 등장한 문서의 차이보다 훨씬 작습니다. 즉, 특정 횟수 이상 등장하면 중요도가 더 이상 급격히 증가하지 않도록 보정합니다. (이것이 $k_1$ 파라미터의 역할입니다.)

이 두 가지 핵심 요소에 추가로 '문서 길이 정규화' 를 더합니다. 같은 "삼성전자"가 10번 등장했더라도, 100페이지짜리 보고서보다 1페이지짜리 요약 문서에서 10번 나오는 것이 훨씬 더 중요하고 밀접한 문서일 것입니다. (이것이 $b$ 파라미터의 역할입니다.)

BM25 공식

 

  • Score(D, Q) : 각 문서 쌍에 대한 점수를 계산한다는 의미입니다.  D는 문서이고 Q는 쿼리입니다.
  • $\sum_{i=1}^{N}$  : 질의에 포함된 N개의 각 단어가 가지는 개별 점수(용어 점수)를 모두 더한 것 입니다.
  • IDF(qi) : 역문서 빈도입니다. 용어가 더 희귀할수록 qi는 점수에 더 많이 기여합니다.
  • $f(q_i, D)$: 쿼리 단어가 문서 D에 등장한 횟수 (TF)
  • $|D|$$avgdl$: 개별 문서 길이와 평균 문서 길이 (문서 길이 보정)
  • $k_1$$b$: TF 포화도와 문서 길이 보정 강도를 조절하는 하이퍼파라미터

BM25는 이 두 가지를 조합해 점수를 매깁니다. 단순하지만 강력합니다.

 

[BM25의 약점]

이처럼 정교하게 설계된 BM25도 '한국어'와 'RAG'라는 두 가지 틀에서는 약점이 되는 부분이 있습니다.

 

1. 하지만 한국어 환경에서는 치명적인 약점을 가집니다. BM25는 언어의 문법을 모르고 공백으로만 단어를 구분합니다.

  • 사용자 검색어: "삼성전자"
  • 문서 내 텍스트: "삼성전자는 어제..."
  • BM25의 판단: "삼성전자"와 "삼성전자는"은 서로 다른 단어입니다.

결국, 조사가 붙는 한국어의 특성 때문에 가장 기본적인 키워드 매칭조차 실패합니다. 

 

2. 문서 크기가 작고 고정된 RAG에서는 용어 중요도의 영향력이 줄어듭니다.

BM25가 웹 검색과 같은 긴 문서 환경에서 빛을 발했던 이유는 TF와 문서 길이 보정 로직이 잘 작동했기 때문입니다. 하지만 RAG환경에서는 이 장점들이 대부분 사라집니다.

 

RAG는 LLM에게 컨텍스트를 제공하기 위해, 원본 문서를 512~1024자 정도의 짧고 균일한 청크(Chunk)로 잘라서 저장합니다.  이러한 이유로 크게 두 가지 장점이 사라집니다. 

  • a) TF(단어 빈도)의 무력화: "삼성전자"라는 키워드는 이 짧은 청크 안에서 기껏해야 0번 아니면 1번 등장합니다. 100페이지짜리 문서에서는 10번 등장하는 것과 100번 등장하는 것의 차이가 의미 있었지만, RAG 청크에서는 TF 점수가 사실상 0 또는 1이 되어버립니다. "이 문서에서 얼마나 자주 등장하는가?"라는 TF의 변별력이 사라지는 것입니다.
  • b) 문서 길이 보정의 무의미: 모든 청크의 길이가 512자로 거의 비슷하다고 가정해 보면, BM25 공식의 문서 길이 보정 부분($|D| / avgdl$)은 항상 1에 가까운 값이 됩니다. "짧은 문서에서 나왔으니 더 중요하다"고 판단해 주던 수식이 제 역할을 못하게 됩니다.

결론적으로, RAG 환경에서 BM25는 TF와 문서 길이 보정 기능을 잃고, 단순히 'IDF(단어 희귀성)' 하나만 보고 작동하는 반쪽짜리 알고리즘으로 전락할 위험이 큽니다.

 


 

2. BM25 + 한국어 형태소 분석기

https://wikidocs.net/213362

앞서 확인했듯이, BM25는 조사때문에 한국어 환경에서 제대로 작동하지 못합니다. "삼성전자"와 "삼성전자는"을 다른 단어로 인식하는 문제를 해결하기 위해 도입된 표준적인 해결책이 바로 한국어 형태소 분석기입니다.

 

형태소 분석기란?

형태소 분석기(Morphological Analyzer, 예: Mecab, Komoran, Okt 등)는 문장을 의미를 가진 가장 작은 단위(형태소)로 분해하는 역할을 합니다.

  • 원본 텍스트: "삼성전자는 어제 실적을 발표했다."
  • 형태소 분석: "삼성전자" (명사) + "는" (조사) + "어제" (명사) + "실적" (명사) + "을" (조사) + ...

BM25에 이 분석기를 결합한다는 것은, 문서를 저장(Indexing)할 때사용자가 검색(Querying)할 때 모두 이 '분해된' 형태소를 사용한다는 의미입니다.

  1. 인덱싱(저장) 단계: "삼성전자는..."이라는 텍스트를 저장할 때, "삼성전자", "는", "어제", "실적", "을"... 등으로 분해한 뒤, "삼성전자", "어제", "실적"처럼 실질적인 의미를 가진 명사나 어간 등 핵심 단어들만 추출하여 BM25 인덱스에 저장합니다.
  2. 쿼리(검색) 단계: 사용자가 "삼성전자 실적"이라고 검색하든, "삼성전자의 실적은?"이라고 검색하든, 이 쿼리 역시 형태소 분석기를 통과시켜 "삼성전자", "실적"이라는 핵심 키워드를 추출합니다.

형태소 분석기 적용 결과

이제 시스템은 "삼성전자"(쿼리)와 "삼성전자"(인덱스)를 정확하게 매칭시킬 수 있습니다. 드디어 "삼성전자", "삼성전자는", "삼성전자가", "삼성전자를"이 모두 같은 단어임을 인식하게 된 것입니다. 이 방식을 통해 한국어 키워드 매칭의 정확도를 향상시켰습니다.

 

BM25 + 형태소 분석기의 새로운 한계

하지만 이 방식은 BM25의 두 가지 약점 중 첫 번째(한국어 문제)만 해결했을 뿐입니다.

 

1. RAG 환경의 약점 유지

가장 중요한 것은, 이 방식이 여전히 BM25라는 사실입니다. 앞서 지적했던 RAG 환경의 근본적인 문제점, 즉 짧고 균일한 청크로 인한 TF(단어 빈도) 및 문서 길이 보정 기능이 약화되는 문제가 전혀 해결되지 않았습니다.

RAG 환경에서 BM25 + 형태소 분석기는 결국 형태소 분석 기능이 추가된 IDF 조회기 수준으로 성능이 저하될 수밖에 없습니다.

 

2. 새로운 복잡성: 의존성의 발생

오히려 이 방식은 시스템에 새로운 의존성을 추가하여 복잡성을 높입니다.

  • 성능 병목: 모든 문서와 쿼리는 BM25 계산 전에 형태소 분석이라는 추가적인 단계를 거쳐야 하므로, 시스템의 속도 저하를 유발할 수 있습니다.
  • 사전 관리: 형태소 분석기의 성능은 사전(Dictionary)에 크게 의존합니다. 만약 "뱅크데믹"나 "피서가치" 같은 새로운 금융 신조어나 "신한은행"같은 고유명사가 사전에 등록되어 있지 않다면, "신한은행은"를 "신한" + "은행" + "은" 등으로 잘못 분해하여 검색 품질이 다시 저하될 수 있습니다. (이를 위해 사용자 사전을 계속 관리해 줘야합니다.)

이처럼 BM25 + 형태소 분석기는 한국어 키워드 검색을 가능하게 만들었지만, RAG 환경에는 여전히 최적화되어 있지 않으며, 시스템 유지보수 비용을 증가시키는 한계를 동시에 안고 있습니다.


3. SPLADE: 딥러닝 희소 벡터

https://github.com/naver/splade?tab=readme-ov-file

BM25가 RAG 환경에서 한계를 보이자, 연구자들은 통계(TF-IDF)가 아닌 딥러닝(Deep Learning)으로 단어의 중요도 자체를 학습하자는 아이디어에 주목했습니다. 그 결과물이 바로 SPLADE (Sparse Lexical and Expansion Model)입니다. 아래와 같이 논문도 나와있습니다.

https://arxiv.org/abs/2107.05720

 

SPLADE: Sparse Lexical and Expansion Model for First Stage Ranking

In neural Information Retrieval, ongoing research is directed towards improving the first retriever in ranking pipelines. Learning dense embeddings to conduct retrieval using efficient approximate nearest neighbors methods has proven to work well. Meanwhil

arxiv.org

 

SPLADE의 작동 방식

SPLADE는 BERT와 같은 트랜스포머 모델을 기반으로 합니다. 이 모델은 문장을 입력받아, 그 문장에서 어떤 단어(토큰)가 검색에 중요한지를 학습하여, 수만 개의 차원(전체 어휘 사전 크기)을 가진 학습된 희소 벡터를 생성합니다.

  • BM25: 통계에 기반해 "삼성전자"라는 단어에 IDF 점수를 부여합니다.
  • SPLADE: 데이터를 학습하여 "삼성전자"라는 단어가 문맥상 중요하다고 판단되면, "삼성전자" 토큰에 해당하는 차원에 높은 가중치(중요도 점수)를 직접 부여합니다.

SPLADE의 장점

SPLADE는 BM25를 능가하는 두 가지 강력한 기능을 제공합니다. 1. 문맥적 가중치 부여와 2. 토큰 확장 입니다.

 

BM25는 문맥을 이해하지 못합니다. 단순히 "말"이라는 명사의 IDF점수만 계산합니다.

  • 문서 A: "제주도 목장에서 을 탔다."
  • 문서 B: "회의에서 을 아껴야 한다."

사용자가 "동물"이라는 키워드로 검색한다고 가정해 봅시다. BM25는 문서 A와 문서 B에 똑같은 "말"이라는 단어가 존재한다고만 인식합니다. 따라서 "말"이라는 단어가 기여하는 점수는 두 문서에서 거의 동일합니다. "동물"과 "말"의 연관성을 파악할 방법이 없습니다.

 

[SPLADE 문맥적 가중치 부여]

반면에, SPLADE는 트랜스포머 모델을 통해 문장의 문맥(Context) 을 이해합니다.

  • 문서 A 처리 시: "제주도", "목장", "탔다"라는 단어와 함께 "말"을 처리합니다. 모델은 이 문맥에서 "말"이 '동물' 과 강하게 연관된다는 것을 학습했습니다.
  • 문서 B 처리 시: "회의", "아껴야 한다"라는 단어와 함께 "말"을 처리합니다. 모델은 이 문맥에서 "말"이 '언어', '대화' 와 강하게 연관된다는 것을 학습했습니다.

[SPLADE 토큰 확장]

사용자가 "동물"이라는 쿼리로 검색하면 모델이 학습한 내용을 바탕으로 "소", "강아지", "농장" 등 의미적으로 연관된 다른 단어들까지 자동으로 확장하여 검색합니다. 즉 단순 키워드 매칭을 넘어선 일종의 의미 검색을 희소 벡터에서 구현한 것입니다.

 

이제 사용자가 "동물"이라는 키워드로 검색하면, SPLADE는 다음과 같이 작동합니다.

  1. SPLADE는 "동물"이라는 쿼리를 처리하면서, 이와 연관된 "말", "소", "농장" 등의 토큰에도 가중치를 부여할 수 있습니다. (토큰 확장)
  2. 문서 A("제주도 ...")를 볼 때, 이 문서의 "말" 토큰에 '동물' 문맥으로 인해 이미 높은 가중치가 부여되어 있습니다. 쿼리의 "동물"과 문서 A의 "말"이 강하게 연결되어 높은 점수를 받습니다.
  3. 문서 B("회의에서 ...")를 볼 때, 이 문서의 "말" 토큰은 '언어' 문맥의 가중치를 가집니다. 쿼리 "동물"과는 관련성이 낮다고 판단하여 매우 낮은 점수를 받습니다.

결론: BM25는 두 문서의 "말"을 동일하게 취급하지만, SPLADE는 문맥을 파악하여 '동물'과 관련된 문서 A의 "말"에만 높은 중요도(가중치)를 부여합니다. 이것이 바로 딥러닝 기반의 '문맥적 가중치 부여'입니다.

 

이러한 장점 덕분에 SPLADE는 수많은 벤치마크에서 BM25의 성능을 크게 능가하는 결과를 보여주었습니다.

https://arxiv.org/pdf/2107.05720

 

SPLADE의 한계

이론적으로는 완벽해 보이는 SPLADE지만, 실제 운영 환경(Production)에 적용하기에는 다음과 같은 비용 문제에 부딪힙니다.

 

1. 부적절한 토크나이저 (Tokenizer)

SPLADE는 트랜스포머의 표준 토크나이저(예: WordPiece, BPE)를 사용합니다. 이 토크나이저들은 언어 모델링(LM)을 위해 설계되었을 뿐, 검색(Retrieval) 작업에는 매우 부적합합니다.

  • 단어 분리: "삼성전자"나 "KODEX" 같은 고유명사를 삼, ##성, ##전자나 KOD, ##EX처럼 의미 없는 조각으로 분리할 수 있습니다.
  • [UNK] 토큰: "피서가치"처럼 사전에 없는 최신 신조어는 [UNK]토큰으로 처리되어, 검색 자체가 불가능해질 수 있습니다.

2. 비싼 토큰 확장 (Expensive Token Expansion)

SPLADE의 핵심 기능인 토큰 확장은 엄청난 대가를 요구합니다. "금융" 하나를 검색하기 위해 "금융기관", "투자" 등 수십 개의 연관 단어를 함께 벡터에 포함시키면, 희소 벡터의 크기(저장 공간)와 계산 비용이 폭발적으로 증가합니다.

 

3. 도메인 및 언어 의존성

SPLADE 모델은 특정 데이터셋(코퍼스)으로 사전 학습됩니다. 따라서 한국어 금융이라는 특수 도메인에서 제 성능을 내기 위해서는, 해당 도메인 데이터로 파인튜닝(Fine-tuning) 과정을 거쳐야 합니다. 이 과정 없이는 한국어 금융 용어를 제대로 이해하지 못할 가능성이 큽니다.

 

4. 느린 추론 속도 (Inference Time)

SPLADE는 결국 거대한 트랜스포머 모델입니다. 문서를 인덱싱하거나 쿼리를 처리할 때마다 이 무거운 모델을 돌려야 하므로, BM25와 비교할 수 없을 정도로 느린 속도를 보이며, 종종 GPU를 요구합니다.

 

결국 SPLADE는 학술적으로는 매우 뛰어나지만, 실제 상용 서비스에 적용하기에는 느리고, 비싸고, 복잡하며, 한국어 특화 작업이 추가로 필요한 솔루션입니다.


4. BM42

이전 글을 살펴보겠습니다.

  1. BM25 (+형태소 분석기): 한국어는 해결했지만, RAG 환경에서 TF(단어 빈도)가 무력화되는 근본적인 문제가 있습니다.
  2. SPLADE: RAG 환경에 맞게 단어 중요도를 학습하지만, 너무 무겁고 느리며 비싸고(GPU/파인튜닝) 한국어 처리가 번거롭습니다.

여기서 Qdrant가 BM42라는 접근 방식을 제시합니다. BM42의 핵심 철학은 "BM25의 장점은 살리고, 부족한 부분만 딥러닝으로 교체하자" 는 것입니다.

 

BM42의 핵심 아이디어: TF를 'Attention'으로 대체하다

BM42 개발자들은 BM25가 RAG에서 실패하는 원인을 정확히 파악했습니다.

  • 부족한 부분: TF(단어 빈도)와 문서 길이 보정. (짧고 균일한 청크 때문)
  • 여전히 유효한 부분: IDF (역 문서 빈도). "삼성전자"가 "입니다"보다 희귀하고 중요하다는 사실은 RAG 환경에서도 변하지 않습니다.

그렇다면, 부족한 TF, 즉 이 청크 내에서 이 단어가 얼마나 중요한가?를 평가할 새로운 기준이 필요합니다. BM42는 그 답을 트랜스포머(Transformer) 모델의 '어텐션(Attention) 메커니즘'에서 찾았습니다.

트랜스포머 모델(BERT, MiniLM 등)이 문장을 처리할 때, 문장 전체의 의미를 요약하는 특별한 [CLS] 토큰을 사용합니다. 이 [CLS] 토큰은 문장의 의미를 파악하기 위해 문장 내의 다른 모든 단어에 주목(Attention)합니다.

 

BM42의 가설: [CLS] 토큰이 특정 단어에 강하게 주목(Attention)했다면, 그 단어는 이 문장의 의미에 핵심적일 것이다. 이 어텐션 가중치야말로 RAG 환경에서 TF를 대체할 수 있는 문서 내 단어 중요도 점수라는 것입니다.

 

https://qdrant.tech/articles/bm42/

 

BM42의 공식

  • IDF(qi): BM25의 공식을 그대로 사용합니다. (Qdrant 엔진이 계산)
  • AttentionWeight (중요도): 트랜스포머 모델(예: all-MiniLM-L6-v2)이 계산한 [CLS] 어텐션 가중치를 사용합니다.

예전에 트랜스포머 논문 리뷰를 하면서 알아본 것 처럼 전통적인 트랜스포머 모델은 여러 개의 어텐션 헤드를 가지고 있으므로, 동일한 문서에 대해 여러 개의 어텐션 가중치 벡터를 얻을 수 있습니다. BM42는 '문장 전체 요약'이라는 임무를 맡은 [CLS] 토큰의 관점에서, 이 여러 개의 어텐션 헤드가 계산해낸 '단어 중요도 벡터'들을 모두 가져옵니다.

그리고 이 벡터들을 단순히 평균(Average) 내어 최종적인 '평균 어텐션 벡터(averaged attention vector)' 하나를 만듭니다.

 

이러한 어텐션 가중치 벡터를 평균내어 만들어진 '평균 어텐션 벡터'"문장 전체의 의미를 요약하는 데 '삼성전자'라는 토큰이 그만큼 중요하게 기여했다"는 의미로 해석할 수 있습니다. 이것이 BM25의 TF점수를 대체할 값으로 사용되는 것이죠.

 

이 '평균 어텐션 벡터'는 "문장 전체의 의미를 요약하는 데 '삼성전자'라는 토큰이 0.15만큼, '실적'이라는 토큰이 0.1만큼 중요하게 기여했다"는 '문맥적 단어 중요도' 로 해석할 수 있습니다. 이것이 바로 BM25의 TF 점수를 대체할 새로운 핵심 값으로 사용되는 것이죠.

 

음 이 부분이 조금 헷갈리실 수도 있는데, 제가 예전에 트랜스포머 논문 리뷰에서 했던 '나는 학생 이다'라는 문장으로 설명드리겠습니다.

 

"나는 학생 이다"가 [CLS] 나는 학생 이다 [SEP]로 토큰화되었다고 가정하면, (단순화를 위해 5x5 행렬이라고 하겠습니다)

어텐션 행렬(각 헤드마다)은 5x5 크기일 것입니다.

  1. "나는"의 관점 (1번 행): [<CLS점수>, <'나는'점수>, <'학생'점수>, <'이다'점수>, <SEP점수>]
  2. "학생"의 관점 (2번 행): [<CLS점수>, <'나는'점수>, <'학생'점수>, <'이다'점수>, <SEP점수>]
  3. ...

BM42는 1번, 2번 등 다른 모든 행(관점)은 전부 무시합니다.

그리고 오직 [CLS] 토큰의 관점 (0번 행) 인 이 한 줄의 가중치 벡터만 콕 집어서 가져옵니다.

  • [CLS]의 관점 (0번 행): [<CLS점수>, <'나는'점수>, <'학생'점수>, <'이다'점수>, <SEP점수>]

이 벡터(0번 행)가 바로 [CLS] 토큰이 "문장 전체를 요약"하기 위해 다른 모든 단어에 부여한 중요도 점수표입니다. (그리고 이 '점수표' 6개(헤드 개수)를 평균 내서 최종 가중치로 사용하는 것입니다.)

 

평균 가중치 벡터를 구하는 예제

sentences = "Hello, World - is the starting point in most programming languages"

features = transformer.tokenize(sentences)

# ...

attentions = transformer.auto_model(**features, output_attentions=True).attentions

weights = torch.mean(attentions[-1][0,:,0], axis=0)                       
#                ▲               ▲  ▲   ▲                                 
#                │               │  │   └─── [CLS] token is the first one
#                │               │  └─────── First item of the batch         
#                │               └────────── Last transformer layer       
#                └────────────────────────── Average all 6 attention heads

for weight, token in zip(weights, tokens):
    print(f"{token}: {weight}")

# [CLS]       : 0.434 // Filter out the [CLS] token
# hello       : 0.039
# ,           : 0.039
# world       : 0.107 // <-- The most important token
# -           : 0.033
# is          : 0.024
# the         : 0.031
# starting    : 0.054
# point       : 0.028
# in          : 0.018
# most        : 0.016
# programming : 0.060 // <-- The third most important token
# languages   : 0.062 // <-- The second most important token
# [SEP]       : 0.047 // Filter out the [SEP] token

 

결국 BM42는 위와 같이 문장 전체 요약([CLS] 토큰에 대한 평균 어텐션 가중치 벡터)를 통해 "전체 문서에서 희귀하면서(IDF) + 이 청크 내에서 문맥상 중요한(Attention)" 단어가 높은 점수를 받도록합니다.

BM42의 WordPiece 재토큰화 (Retokenization)

트랜스포머를 쓰면 SPLADE와 똑같은 토크나이저 문제(예: "삼성전자" -> 삼, ##성, ##전)가 생긴다는 의문이 들 수 있습니다.

BM42는 이 문제를 'WordPiece 재토큰화(Retokenization)' 라는 기법으로 해결합니다.

  1. 1단계: 트랜스포머 토큰화
    • "삼성전자는" -> [CLS], 삼, ##성, ##전, ##자, ##는, [SEP]
  2. 2단계: 어텐션 가중치 계산
    • 경량 모델을 돌려 각 서브워드(삼, ##성, ##전...)의 [CLS] 어텐션 점수를 계산합니다.
  3. 3단계: 재병합 및 점수 합산 (Retokenization)
    • ##가 붙은 서브워드들을 다시 원본 단어로 병합합니다.
    • 이때, 각 서브워드의 어텐션 점수를 **모두 합산(Sum)**하여 "삼성전자"라는 원본 단어의 최종 어텐션 점수로 삼습니다.
    • (예: 삼(0.05) + ##성(0.1) + ##전(0.15) + ##자(0.1) = "삼성전자"(0.4))
  4. 4단계: 후처리 (Post-processing)
    • 이제 "삼성전자"(0.4), "는"(0.01) 처럼 '원본 단어'와 '중요도 점수' 쌍이 남았습니다.
    • 여기서 "는"과 같은 불용어(Stopword)를 제거합니다.

BM42의 이점

이러한 접근 방식은 기존에 있던 문제를 해결합니다.

 

1. RAG 환경 완벽 대응: TF 문제를 '어텐션'이라는 문맥적 중요도로 대체하여 RAG 청크에서 최고의 성능을 발휘합니다.

 

2. 한국어 자동 처리 (형태소 분석기 불필요): "삼성전자는", "삼성전자가" 모두 3단계에서 "삼성전자"라는 원본 단어로 복원되고, "는", "가"는 4단계에서 불용어로 제거됩니다. 별도의 형태소 분석기나 사용자 사전 관리가 전혀 필요 없습니다.

 

3. SPLADE의 비용 문제 해결 (경량 & 고속): 무거운 모델로 파인튜닝하거나 토큰 확장을 하는 대신, all-MiniLM-L6-v2 같은 작고 빠른 경량 모델을 사용합니다. GPU가 필요 없으며, CPU에서도 고속으로 추론이 가능합니다. 메모리 사용량도 매우 적습니다.

 

4. BM25의 장점 계승: 여전히 IDF를 사용하며, 키워드 기반의 희소 벡터입니다.

 

BM42는 RAG 환경에서 BM25가 가진 한계를 '어텐션' 으로 해결하고, SPLADE가 가진 비용 문제를 '경량 모델' 로 해결했습니다. 동시에, '재토큰화' 기법으로 한국어 처리 문제까지 해결한, RAG를 위한 균형 잡힌 희소 벡터 솔루션이라고 할 수 있습니다.

 


5. 어떤 희소 벡터 전략을 선택해야 하는가?

BM25 VS SPLADE VS BM42 비교표 (Qdrant 공식 페이지)

 

지금까지 우리는 한국어 금융 문서 검색을 위한 네 가지 희소 벡터(키워드 검색) 전략을 깊이 있게 비교 분석했습니다.

  1. 기본 BM25: 한국어 조사 처리가 불가능해 성능이 떨어집니다.
  2. BM25 + 형태소 분석기: 한국어 문제를 해결했지만, RAG 환경에서는 길이가 짧은 문서들이 많아 TF/문서 길이 보정 기능이 무력화되는 한계가 있습니다.
  3. SPLADE: 강력한 문맥 인식과 토큰 확장 기능을 가졌지만, GPU/파인튜닝 등 막대한 도입/운영 비용과 토크나이저 문제로 현실성이 떨어집니다.
  4. BM42: RAG 환경의 TF 문제를 '어텐션'으로 해결하고, 한국어 문제를 '재토큰화'로 해결하며, 경량 모델을 사용해 비용까지 잡은 가장 균형 잡힌 솔루션입니다.

그러나, BM42가 항상 정답은 아닙니다. 상황에 맞는 '정답'은 따로 있을 수 있습니다.

이 표를 보면 BM42가 모든 면에서 우월해 보이지만, '가장 좋은' 기술은 없고 '상황에 맞는' 기술만 있을 뿐입니다.

 

1. RAG 기반 하이브리드 검색을 구축한다면? (짧고 균일한 청크)

정답: BM42

이것이 바로 이 글의 핵심 주제였습니다. RAG 환경에서는 BM25의 TF 기능이 무력화되기 때문에, 이를 '어텐션 가중치'라는 문맥적 중요도로 대체한 BM42가 이론적으로나 실용적으로나 가장 합리적인 선택입니다. 별도의 형태소 분석기 없이 한국어 처리가 가능하고, 경량 모델로 비용까지 절약할 수 있다는 것은 엄청난 이점입니다.

 

2. 전통적인 검색 시스템을 구축한다면? (길고 다양한 문서)

정답: BM25 + 한국어 형태소 분석기

만약 RAG가 아닌, 뉴스 기사나 블로그 원문처럼 '적당히 길고 길이가 다양한' 문서를 검색하는 경우, 이야기가 달라집니다. 이 환경에서는 BM25의 TF(단어 빈도)와 문서 길이 보정 로직이 완벽하게 작동합니다. 오히려 'Mecab'처럼 고도로 특화된 한국어 형태소 분석기가 BM42의 다국어 모델 기반 '재토큰화'보다 더 정교하고 안정적인 키워드 매칭을 보여줄 수 있습니다.

 


 

6. 고려 사항 : 하이브리드 검색을 사용하는 이유

RAG 환경에 BM42가 가장 적합하다고 잠정 결론 내렸습니다. 하지만 BM42조차 완벽하지 않습니다. Sparse(희소) 벡터 검색은 태생적으로 '키워드 밀도'에 편향될 수밖에 없으며, 이는 '청크 분할 전략'에 따라 함정을 만들어낼 수 있습니다.

 

자투리 청크가 '알짜 청크'를 이기는 현상

사용자가 "IBK 굴리기 통장에 대해 알려줘"라고 검색했다고 가정해 봅시다. 이 쿼리의 의도는 '정보(금리, 대상)'입니다. 하지만 DB에 다음과 같은 두 청크가 있다면 어떻게 될까요?

  • 청크 1 (짧은 청크): "IBK 굴리기 통장 개정안" (11자)
  • 청크 2 (본문 청크): "IBK 굴리기 통장 가입대상은 ~, 기간은 ~, 금리는 ~" (512자)

Sparse 검색(BM25, BM42)은 청크 1에 더 높은 점수를 줄 확률이 매우 높습니다.

  • BM25의 판단: 청크 1이 평균 길이(512자)보다 극단적으로 짧으므로, '문서 길이 보정' 로직에 따라 엄청난 보너스 점수를 받습니다.
  • BM42의 판단: "IBK 굴리기 통장 개정안"이라는 문장에서 [CLS] 토큰은 문장 요약을 위해 어텐션 가중치의 대부분을 "IBK", "굴리기", "통장"에 집중시킵니다. 반면, 청크 2는 "가입대상", "기간", "금리" 등 중요한 단어가 많아 어텐션이 분산됩니다.

결국, 사용자의 의도("알려줘")와는 전혀 상관없는 청크 1이 키워드 점수가 높다는 이유만으로 본문인 청크 2를 이기고 1위로 올라오는 문제가 발생합니다.

 

청크 분할 전략이 이 문제를 악화시킨다

이 문제는 청크 분할 방식에 따라 더 심각해집니다.

  1. 단순 길이 분할 (Fixed-Length): 문서당 최대 1개의 '꽁무니 자투리'만 생성합니다. (예: 1200자 -> 512, 512, 176자)
  2. 의미 기반 분할 (Semantic): Dense 검색을 위해 문맥을 살리는 좋은 전략이지만, Sparse 검색에는 재앙이 될 수 있습니다. 이 방식은 '제목', '주석', '짧은 문단' 등 의미는 있지만 길이가 극단적으로 짧은 청크를 대량으로 생성하기 때문입니다.

즉, 의미 기반으로 자르면 "IBK 굴리기 통장"(제목)이나 "IBK 굴리기 통장 설명서"(주석) 같은 11자짜리 청크가 DB에 가득 차게 되고, 이 청크들이 BM25와 BM42 양쪽에서 모두 최고의 점수를 받으며 검색 결과를 오염시킵니다.

 

해결책: Dense 검색과의 '하이브리드'

이것이 바로 Dense 검색(의미 검색)을 결합하는 것이 선택이 아닌 필수인 이유입니다.

  • Sparse (BM42): "청크 1(개정안)이 키워드 밀도가 최고야!"
  • Dense (Embedding): "쿼리('알려줘')의 의도는 '정보'니까, 청크 2(가입대상, 금리)가 정답이야"

하이브리드 검색(예: RRF)은 이 두 검색의 의견을 모두 듣습니다. Sparse가 아무리 청크 1을 외쳐도, Dense가 청크 2에 압도적인 점수를 주면, 시스템은 사용자의 '의도'에 맞는 청크 2를 최종 검색 결과 1위로 올릴 수 있습니다.

 


7. 마무리하며: 대부분에서 하이브리드 검색은 선택이 아닌 필수

지금까지 우리는 한국어 RAG 시스템을 위한 최적의 Sparse 검색 전략을 찾기 위해 BM25부터 BM42까지 알아봤습니다.

RAG 환경에서는 BM42가 기존 BM25의 한계를 극복하고, SPLADE의 비용 문제까지 해결한 실용적인 Sparse 솔루션임을 확인했습니다. 하지만 우리는 마지막 고려사항을 생각해야합니다. 아무리 뛰어난 Sparse 모델(BM42)이라도, 키워드의 밀도에 집중하는 태생적 한계로 인해 사용자의 '진짜 의도' 를 놓칠 수 있다는 것입니다.

 

결국 RAG 검색 시스템의 '정답'은 BM25, BM42, SPLADE 중 하나를 고르는 것이 아닙니다. 정답은 "Sparse의 키워드 정확성""Dense(Embedding)의 문맥 이해력" 을 결합하는 '하이브리드 검색'입니다. 

 

사실 이 마저도 반드시 정답이라고 할 수 는 없습니다. 문서 특성에 따라 Sparse만으로도 충분한 검색이 될 수도 있고, BM42보다 BM25가 더 강력할 수도 있습니다. 이 글의 핵심은 여러 검색 전략들의 특성을 이해하고 결합하여 균형을 잡아 검색 시스템을 만드는 것입니다.

 

이 글이 RAG 시스템을 만드시는 분들에게 도움이 되셨으면 좋겠습니다! 감사합니다.