왜 랜덤 네거티브만으로는 부족한가
임베딩 검색 모델은 보통 "쿼리-정답 문서" 쌍과 함께 다수의 네거티브를 대조학습(contrastive)한다. 그런데 배치 안에서 무작위로 뽑은 네거티브는 대부분 쿼리와 주제가 완전히 동떨어져 있어, 모델이 조금만 학습해도 쉽게 구분한다. 손실이 금방 0에 수렴하지만 정작 실제 서비스에서 헷갈리는 문서는 "주제는 비슷한데 답은 아닌" 문서들이다. 이 간극 때문에 오프라인 지표는 좋은데 프로덕션 recall이 낮은 현상이 반복된다.
하드 네거티브는 이 격차를 메운다. 쿼리와 임베딩 공간에서 가깝지만 정답이 아닌 문서를 골라 학습에 넣어 결정 경계를 촘촘하게 만든다.
하드 네거티브의 정의와 함정
하드 네거티브는 "쿼리와 유사도가 높지만 관련 없는 문서"다. 문제는 상당수가 실제로는 정답인데 라벨링이 안 된 false negative라는 점이다. 이것을 그대로 네거티브로 넣으면 모델에게 잘못된 신호를 준다. 그래서 무조건 top-1을 쓰지 않고 일정 순위 구간을 샘플링하는 전략이 중요하다.
| 네거티브 종류 | 난이도 | false negative 위험 |
|---|---|---|
| 랜덤/인배치 | 낮음 | 거의 없음 |
| BM25 상위 | 중간 | 중간 |
| 임베딩 top-1~5 | 높음 | 높음 |
| 임베딩 top 30~100 구간 | 중상 | 낮음 |
마이닝 파이프라인
실무 순서는 다음과 같다. 1) 현재 모델로 코퍼스 전체를 인덱싱, 2) 각 학습 쿼리로 top-k 검색, 3) 정답 문서를 제외한 뒤 상위 구간에서 네거티브 샘플링, 4) false negative 필터링, 5) 재학습. 이 과정을 1~2 에폭마다 반복하는 것을 반복적(iterative) 하드 네거티브 마이닝이라 한다.
import faiss, numpy as np
# corpus_emb: (N, d) 정규화된 문서 임베딩
index = faiss.IndexFlatIP(corpus_emb.shape[1])
index.add(corpus_emb)
def mine_negatives(q_emb, pos_ids, topk=100, lo=30, hi=100, n_neg=5):
scores, ids = index.search(q_emb, topk) # (B, topk)
negs = []
for row_ids, pos in zip(ids, pos_ids):
cand = [i for i in row_ids[lo:hi] if i not in pos]
negs.append(np.random.choice(cand, size=n_neg, replace=False))
return np.array(negs)
핵심은 lo=30처럼 최상위를 잘라내는 것이다. top-5는 정답일 확률이 높으니 피하고, 30~100 구간에서 뽑으면 "어렵지만 대체로 오답"인 문서가 걸린다.
false negative 필터링
더 안전하게 하려면 강한 리랭커(cross-encoder)로 후보의 관련성 점수를 매겨 임계값 이상은 네거티브에서 제외한다. 리랭커가 없다면 정답과의 유사도 대비 상대 마진으로 근사할 수도 있다.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def drop_false_neg(query, cand_texts, thresh=0.6):
pairs = [[query, t] for t in cand_texts]
rel = reranker.predict(pairs) # 0~1 근사
return [t for t, s in zip(cand_texts, rel) if s < thresh]
학습 손실 구성
마이닝한 네거티브는 InfoNCE 손실에 명시적으로 넣는다. 인배치 네거티브와 하드 네거티브를 함께 쓰면 쉬운 예제와 어려운 예제를 균형 있게 학습한다. 온도(temperature) 파라미터는 하드 네거티브 비중이 커질수록 약간 높여 그래디언트 폭주를 막는 편이 안정적이다.
실무 주의점
- 스테일 임베딩: 마이닝 시점 모델과 학습 후 모델이 달라지므로, 매 에폭 재마이닝은 비싸다. 보통 1 에폭마다 갱신하고 그 사이엔 캐시를 재사용한다.
- 네거티브 개수: 쿼리당 4~8개면 충분하다. 무작정 늘리면 false negative 노출만 커진다.
- 도메인 편향: 특정 카테고리에 네거티브가 몰리면 그쪽만 과학습된다. 카테고리별 샘플링 비율을 로깅해 확인한다.
- 평가 분리: 마이닝에 쓴 코퍼스와 평가셋이 겹치면 지표가 부풀려진다. 반드시 분리한다.
정리
하드 네거티브 마이닝은 모델 구조를 바꾸지 않고도 검색 품질을 눈에 띄게 올리는 방법이다. 다만 그 효과는 false negative를 얼마나 잘 걸러내느냐에 달려 있다. 최상위 구간 배제와 리랭커 필터링, 반복 마이닝 주기 관리 — 이 세 가지를 지키면 오프라인과 프로덕션 지표의 간극을 실질적으로 줄일 수 있다.