본문 바로가기
AI/NLP

Sparse Model

by Mujae98 2026. 2. 1.

들어가며

검색 서비스를 운영하다 보면 한국어의 특성 때문에 고민이 깊어지는 순간이 있습니다. Dense + BM25 하이브리드 방식이 표준처럼 자리잡았지만, BM25의 근본적인 한계는 여전히 남아있습니다. 이 글에서는 BM25의 한계를 짚어보고, BGE-M3 Sparse가 어떻게 이를 보완할 수 있는지 MIRACL 한국어 벤치마크 실험 결과와 함께 공유합니다.


1. BM25 + 형태소 분석, 현재 우리의 선택

현재 많은 한국어 검색 시스템은 이런 구조를 가지고 있습니다:

사용자 쿼리 → (Kiwi) 형태소 분석 → BM25 스코어링 → 검색

왜 형태소 분석이 필수인가

한국어는 교착어입니다. "갤럭시를"에서 "갤럭시"를 찾으려면 조사 "를"을 분리해야 합니다. 형태소 분석 없이 BM25를 돌리면 조사에 따라 같은 단어가 앞에 있어도 찾을 수 없게 됩니다.

BM25 파이프라인

BM25 코드에서 핵심 로직을 살펴보면:

def tokenize_and_filter(self, text: str) -> str:
    tokens = self.kiwi.tokenize(text)
    
    # 유지할 품사: 명사, 고유명사, 한자 등
    keep_tags = {"SH", "NNG", "NNP"}
    
    
    # 불용어 제거 후 반환
    return " ".join(out)

명사 중심으로 토큰을 추출하고 한국어 특성에 맞게 튜닝 및 불용어를 제거합니다.


2. BM25의 구조적 한계

BM25가 "단순히 단어 빈도만 체크한다"는 말은 정확하지 않습니다. 실제로는 TF(Term Frequency), IDF(Inverse Document Frequency), 문서 길이 정규화를 모두 고려합니다. 하지만 본질적인 한계는 명확합니다.

한계 1: 정확한 토큰 매칭만 가능

문서: "강남구 아파트 매매"
쿼리: "강남 부동산 구매"

BM25 결과: 매칭 실패 ❌

"강남구"와 "강남"은 다른 토큰입니다. "매매"와 "구매"도 마찬가지입니다. 형태소 분석기가 다르게 분리하면 검색이 안 됩니다.

한계 2: 형태소 분석 오류

문서: "SIO-AI8F 8ch 오디오 인터페이스"
Kiwi 분석: SIO | AI | 8 | F | 8 | ch | 오디오 | 인터페이스

문서: "갤럭시S24울트라"
Kiwi 분석: 갤럭시 | S | 24 | 울트라

상품명, 모델명 같은 복합어가 분리됩니다. "SIO-AI8F"는 하나의 제품 코드인데 여러 토큰으로 쪼개집니다. 이런 분리가 검색 품질을 떨어뜨립니다.

한계 3: 의미적 관계 이해 불가

문서: "우리나라 충주에서는 사과가 특산품입니다."
쿼리: "어느 지역의 apple이 맛있어?"

BM25 결과: 매칭 실패 ❌

"apple"과 "사과"는 같은 의미입니다. 하지만 BM25는 이를 전혀 알 수 없습니다. 영한 혼용 텍스트가 많은 서비스에서 이런 문제가 자주 발생합니다.


3. BGE-M3 Sparse

BM25의 한계를 인식하고 대안을 찾기 시작했습니다. 조건은 명확했습니다:

  1. 형태소 분석 의존성을 줄일 것
  2. 의미적 유사도를 어느 정도 파악할 것
  3. Dense 임베딩처럼 느리지 않을 것

여러 모델을 조사한 결과, BGE-M3의 Sparse가 눈에 들어왔습니다.

BGE-M3란?

BGE-M3는 BAAI에서 개발한 다목적 임베딩 모델입니다. 이름의 M3는 Multi-Linguality, Multi-Functionality, Multi-Granularity를 의미합니다. 특이한 점은 하나의 모델에서 Dense, Sparse, ColBERT 벡터를 동시에 추출할 수 있다는 것입니다.

from FlagEmbedding import BGEM3FlagModel

model = BGEM3FlagModel('BAAI/bge-m3')
output = model.encode(
    texts,
    return_dense=True,      # Dense 벡터 (1024차원)
    return_sparse=True,     # Sparse 벡터 (학습된 term weight)
    return_colbert_vecs=False
)

4. BGE-M3 Sparse의 장점

장점 1: 형태소 분석 불필요

BGE-M3는 자체 서브워드 토크나이저를 사용합니다. 한국어 형태소 분석이 필요 없습니다.

입력: "갤럭시S24울트라"
BGE-M3 내부: 서브워드 토크나이징 → 학습된 가중치 적용

→ 복합어가 그대로 의미 있는 벡터로 변환

상품명 "SIO-AI8F"도 통째로 처리됩니다. 형태소 분석기가 잘못 쪼개서 발생하는 오류가 원천 차단됩니다.

장점 2: 의미적 관계 인코딩

BGE-M3는 대규모 다국어 데이터로 학습되었습니다. 학습 과정에서 의미적 관계가 sparse 가중치에 반영됩니다.

"apple" 쿼리 → [사과, apple, 과일] 관련 토큰에 높은 가중치
"경기" 쿼리 → [대회, 시합, 게임] 관련 토큰에 높은 가중치

BM25로는 불가능한 동의어, 유사어 매칭이 어느 정도 가능해집니다.

장점 3: 다국어 지원

다국어가 지원되며, 혼용 사용시에도 의미적 유사도가 반영될 수도 있습니다.

문서: "Apple 아이폰 최신 모델"
쿼리: "애플 아이폰"

BM25: "Apple" ≠ "애플" → 매칭 실패 ❌
BGE-M3: 의미적 유사도 반영 → 매칭 성공 ✅

장점 4: 인덱스 증분 업데이트

BM25는 IDF 계산을 위해 전체 문서에 대해 fit()이 필요합니다. 새 문서가 추가되면 전체를 다시 계산해야 합니다.

BGE-M3는 사전 학습이 완료된 모델입니다.


5. 실험 설계

"정말 BGE-M3가 더 좋을까?"를 검증하기 위해 공정한 실험이 필요했습니다.

데이터셋: MIRACL Korean

MIRACL(Multilingual Information Retrieval Across a Continuum of Languages)은 18개 언어의 Wikipedia 기반 검색 벤치마크입니다. 한국어 데이터는 다음과 같이 구성됩니다:

구분 수량

Corpus 1,486,752 documents
Dev queries 213 queries
평가 지표 nDCG@10 (primary), Recall@100, MRR

비교 대상

  1. BM25 + Kiwi: 실제 서비스 코드의 tokenize_and_filter 로직 그대로 적용
  2. BGE-M3 Sparse: 형태소 분석 없이 원문 그대로 인코딩

중요한 실험 원칙

단순화된 BM25 구현으로 실험하면 의미가 없습니다. 실제 서비스와 동일한 전처리 파이프라인을 적용해야 합니다:

  • 숫자+단위 결합 ("3" + "개" → "3개")
  • 특정 품사(NNG, NNP, SH)만 유지
  • 불용어 제거

이렇게 해야 "현재 시스템 대비 얼마나 개선되는가"를 정확히 측정할 수 있습니다.


6. 실험 결과

MIRACL Korean 전체 데이터(약 150만 문서)로 실험을 진행했습니다.

정량적 결과

지표 BM25 + Kiwi BGE-M3 Sparse 개선율

nDCG@10 0.3825 0.6147 +60.7%
Recall@100 0.7815 0.9006 +15.2%
MRR 0.4086 0.6742 +65.0%

모든 지표에서 BGE-M3 Sparse가 압도적으로 우수합니다.

결과 해석

nDCG@10 +60.7%: 상위 10개 결과의 순위 품질이 60% 이상 개선되었습니다. 사용자가 첫 페이지에서 원하는 문서를 찾을 확률이 크게 높아집니다.

Recall@100 +15.2%: Top-100에서 관련 문서를 찾아내는 비율이 78%에서 90%로 상승했습니다. 놓치는 문서가 줄어듭니다.

MRR +65.0%: 첫 번째 정답 문서의 평균 순위가 크게 개선되었습니다. 정답이 더 빨리 나타납니다.

기존 연구와의 비교

BGE-M3 논문에서 보고된 MIRACL 한국어 성능:

모델 nDCG@10

BM25 (Lucene) 37.1
BGE-M3 Sparse 61.5
BGE-M3 Dense 69.9

우리 실험의 BM25 + Kiwi(0.3825)는 Lucene BM25(37.1)와 유사한 수준입니다. BGE-M3 Sparse(0.6147)도 논문 결과(61.5)와 거의 일치합니다. 실험이 올바르게 수행되었음을 확인할 수 있습니다.


7. BGE-M3 Sparse의 한계

장점만 있는 것은 아닙니다.

속도

BM25 인코딩: ~400 docs/sec
BGE-M3 인코딩: ~150 docs/sec (GPU 사용 시)

BGE-M3가 약 2-3배 느립니다. 실시간 인코딩이 필요한 경우 고려가 필요합니다.

리소스

BGE-M3 모델 크기는 약 2GB입니다. GPU 메모리가 충분해야 합니다. 

Sparse 벡터 크기

BGE-M3 Sparse 벡터는 문서당 200-800개 토큰을 포함합니다. BM25보다 저장 공간이 더 필요할 수 있습니다. 실험에서는 max_tokens=700으로 제한하여 사용했습니다.


8. 결론 및 다음 단계

결론

MIRACL 한국어 벤치마크에서 BGE-M3 Sparse는 BM25 + Kiwi 대비 nDCG@10 기준 60% 이상의 성능 향상을 보여주었습니다. 이 차이는 단순히 통계적 유의미성을 넘어, 사용자 경험에 실질적인 영향을 줄 수 있는 수준입니다.

특히 다음 상황에서 BGE-M3 Sparse가 효과적입니다:

  • 상품명, 브랜드명 등 복합어가 많은 경우
  • 동의어/유사어 매칭이 중요한 경우
  • 영한 혼용 텍스트가 많은 경우

다음 단계

  1. Hybrid 실험: Dense + BGE-M3 Sparse 조합 테스트
  2. 도메인 특화 평가: 실제 서비스 데이터로 A/B 테스트
  3. dragonkue/BGE-m3-ko 테스트

BM25가 20년 넘게 검색의 표준이었습니다. 하지만 학습된 sparse 모델이 그 자리를 대체할 수 있다는 가능성이 보입니다. 특히 한국어처럼 형태소 분석이 필수인 언어에서 그 이점은 더욱 두드러집니다.


참고 자료

'AI > NLP' 카테고리의 다른 글

ModuleNotFoundError: No module named 'custom_st'  (0) 2024.11.29
[NLP스터디] NLP에 필요한 한국어 문법  (0) 2024.01.20
[NLP스터디] 1주차 토큰화  (0) 2024.01.19