Field Log · Entry

Dense Retriever 학습: Hard Negative·Synthetic Query (7/10)

문서에서 synthetic query와 positive pair를 만들고 BM25·ANN·teacher로 hard negative를 채굴해 bi-encoder를 학습·평가·재색인하는 retriever flywheel

오늘의 결론

  • Dense retriever 학습의 핵심은 model architecture보다 query-positive-negative data의 의미입니다.
  • Random negative만 쓰면 쉬운 구분만 배우고, hard negative만 과하게 쓰면 false negative에 무너집니다.
  • BM25, 현재 ANN retriever, cross-encoder teacher는 서로 다른 종류의 어려운 negative를 만듭니다.
  • Label이 부족하면 synthetic query가 출발점이 될 수 있지만, 생성 문장이 자연스럽다는 이유로 정답 pair라고 믿으면 안 됩니다.
  • Retriever를 바꾸면 embedding space가 바뀝니다. Model artifact와 document index를 하나의 version으로 배포해야 합니다.

앞 글에서는 query마다 RAG와 Long Context를 route했습니다. 이제 RAG route의 recall이 계속 낮다고 해 봅시다.

질문: "etch chamber의 endpoint drift를 줄인 변경 사항은?"

BM25 top-1: endpoint calibration 절차
Dense top-1: chamber cleaning 주기
Gold: optical emission window purge 개선 보고서

일반 embedding model은 endpoint drift와 해당 domain의 window purge가 연결된다는 것을 충분히 배우지 못했을 수 있습니다. Query rewrite와 hybrid search를 튜닝한 뒤에도 특정 domain slice가 반복해서 실패한다면 retriever adaptation을 검토합니다.

Positive와 hard negative를 만들고 bi-encoder를 학습한 뒤 새 index로 평가하는 dense retriever data flywheel

그림 1. Training은 한 번의 fine-tuning job이 아니다. Split을 먼저 고정하고, negative를 채굴하고, offline·end-to-end gate를 통과한 model-index pair만 배포한다.


먼저 묻기: 정말 학습이 필요한가

Retriever 학습은 다음 비용을 만듭니다.

  • query-document label 수집
  • GPU training과 hyperparameter search
  • 전체 corpus re-embedding
  • ANN index rebuild
  • model-index version rollout
  • 새 embedding drift monitoring
  • 정기적인 data refresh

따라서 다음 baseline을 먼저 확인합니다.

학습 전에 고칠 것

  1. Parser가 text와 metadata를 제대로 만들었는가?
  2. Chunk boundary가 answer evidence를 분리하지 않았는가?
  3. BM25가 domain keyword를 찾는가?
  4. Hybrid RRF가 lexical과 semantic hit를 합치는가?
  5. Query expansion이 acronym과 alias를 복구하는가?
  6. Reranker가 top-k 안의 gold를 살리는가?
  7. Evaluation label이 실제로 맞는가?

Gold evidence가 candidate pool에 있지만 reranker가 놓친다면 retriever가 아니라 reranker 문제입니다. Gold passage 자체가 index에 없으면 training으로 해결할 수 없습니다.

학습을 고려할 Signal

  • 일반 model이 반복적으로 놓치는 domain synonym이 있습니다.
  • User query가 문서 표현과 구조적으로 다릅니다.
  • 같은 용어의 irrelevant 문서가 많아 hard distinction이 필요합니다.
  • 충분한 query log와 relevance judgment가 있습니다.
  • Offline Recall@k 향상이 end-to-end 품질과 연결됩니다.
  • 재색인 비용을 감당할 수 있습니다.

Dense Bi-Encoder 복습

Dense retriever는 query와 passage를 각각 encoder에 넣습니다.

q = E_query(query)
d = E_document(document)
score(q, d) = similarity(q, d)

Similarity는 dot product 또는 cosine similarity를 흔히 사용합니다. Document vector를 미리 계산할 수 있어 대규모 ANN search가 가능합니다.

Cross-Encoder와 차이

Bi-encoder:
[query] → encoder → q
[doc]   → encoder → d
score(q, d)

Cross-encoder:
[query ; doc] → joint encoder → score

Cross-encoder는 query-document interaction을 깊게 보지만 모든 corpus 문서를 미리 한 vector로 만들 수 없습니다. 그래서 보통 bi-encoder candidate generation 뒤 reranker나 teacher로 씁니다.

Training Example의 최소 단위

type RetrievalExample = {
  queryId: string;
  query: string;
  positivePassageIds: string[];
  negativePassageIds: string[];
  relevance: Record<string, number>;
  source: "human" | "click" | "synthetic";
  split: "train" | "validation" | "test";
  timestamp: string;
  tenantScope?: string;
};

Positive는 “같은 topic”이 아니라 그 query에 필요한 evidence를 제공하는 passage여야 합니다.

Query: "2026년 계약 해지 통보 기한은?"

Positive: 2026 active 계약의 30일 통보 조항
Near-topic negative: 2024 계약의 60일 통보 조항
Irrelevant negative: cafeteria 운영 시간

Near-topic passage는 lexical·semantic하게 매우 비슷하지만 answer에는 틀립니다. 좋은 hard negative 후보입니다. 단, 2024 규정도 역사 비교 질문에는 positive가 될 수 있으므로 query 조건을 함께 판단해야 합니다.

Contrastive Learning Objective

Query q에 positive d⁺와 negatives d⁻가 있을 때 positive score를 높이고 negative score를 낮춥니다.

단순화한 InfoNCE 형태는 다음과 같습니다.

L(q) = -log exp(sim(q,d⁺)/τ)
             ─────────────────────────────
             Σⱼ exp(sim(q,dⱼ)/τ)
  • τ는 temperature입니다.
  • 분모에는 positive와 batch의 negative가 들어갑니다.
  • 작은 temperature는 score 차이를 더 날카롭게 만들 수 있습니다.

In-Batch Negative

Batch 안의 다른 query positive를 현재 query의 negative로 재사용합니다.

Batch:
(q1, d1+), (q2, d2+), (q3, d3+)

For q1:
positive = d1
negatives = d2, d3

추가 document encoding 없이 negative 수를 늘릴 수 있습니다. DPR은 in-batch negative를 활용한 대표적인 dense passage retrieval 연구입니다.

Batch Size의 의미

Batch가 크면 negative pool이 커지지만 GPU memory도 증가합니다. Gradient accumulation은 optimization batch를 키우지만, 구현에 따라 같은 forward pass의 in-batch negative 수를 늘리지 못할 수 있습니다. Cross-device negative gathering을 사용할 때는 distributed communication과 duplicate 처리도 확인해야 합니다.

False Negative: 관련 문서를 밀어내는 오류

In-batch의 다른 positive가 현재 query에도 relevant할 수 있습니다.

q1: "휴가 이월 한도는?"
d1: 인사규정 4.2 휴가 이월

q2: "남은 연차는 다음 해로 얼마나 넘어가나?"
d2: 직원 FAQ 연차 이월 예시

d2는 q1에도 정답 evidence입니다. 이를 negative로 밀면 model이 semantic paraphrase를 잘못 배웁니다.

완화 방법

  • document ID뿐 아니라 semantic duplicate cluster를 제거합니다.
  • 여러 positive를 허용하는 multi-positive loss를 씁니다.
  • Cross-encoder teacher로 high-relevance candidate를 negative에서 제외합니다.
  • 같은 answer·entity·policy section을 공유하는 sample을 한 batch에서 피합니다.
  • Ambiguous negative에는 낮은 weight를 줍니다.
  • Human adjudication sample로 false-negative rate를 측정합니다.
def filter_false_negatives(query, candidates, teacher):
    kept = []
    for passage in candidates:
        relevance = teacher.score(query, passage.text)
        if relevance < NEGATIVE_MAX_RELEVANCE:
            kept.append(passage)
    return kept

Teacher도 완벽하지 않으므로 threshold 주변을 사람이 audit합니다.

Negative의 난이도 Spectrum

Easy random
  → same-domain random
  → BM25 lexical hard
  → dense ANN semantic hard
  → teacher-selected boundary case
  → adversarial / temporal / entity-conflict case

모든 sample을 가장 어렵게 만들 필요는 없습니다. Easy negative는 broad separation을, hard negative는 decision boundary를 가르칩니다.

Batch Composition 예시

per_query:
  positive: 1
  easy_random: 2
  same_domain_random: 2
  bm25_hard: 3
  ann_hard: 3
  teacher_boundary: 1
false_negative_filter: true
multi_positive_probability: 0.25

비율은 예시입니다. Negative source별 validation ablation으로 정합니다.

BM25 Hard Negative

BM25 top result 중 gold가 아닌 passage를 가져옵니다.

def mine_bm25_negatives(example, k=100):
    hits = bm25.search(example.query, k=k)
    return [
        hit for hit in hits
        if hit.id not in example.positive_passage_ids
        and same_acl_scope(hit, example)
    ]

이 negative는 query keyword를 공유하지만 의미가 다릅니다.

query: "Orion S2 emergency shutdown 온도"

BM25 hard negatives:
- Orion S1 shutdown 온도
- Orion S2 normal operating 온도
- Orion S2 shutdown pressure

Entity, version, attribute를 정교하게 구분하는 데 유용합니다.

ANN Hard Negative와 ANCE

현재 dense retriever가 높은 score를 주지만 relevant하지 않은 passage를 채굴합니다.

current model → encode corpus → ANN index
             → search training queries
             → collect high-score non-positive passages
             → train next checkpoint

ANCE 연구는 training 중 asynchronously updated ANN index를 이용해 전체 corpus에서 negative를 찾는 방향을 제시했습니다. 핵심 문제는 stale negative입니다.

checkpoint t model
index built by checkpoint t-2
mined negatives reflect old embedding space

운영 단순화

Production team은 완전 asynchronous training system부터 만들 필요가 없습니다.

Round 0: base model + BM25 negatives
Round 1: train → full encode → ANN mine
Round 2: train → full encode → ANN mine
Stop: validation gain saturates

Round 기반 offline mining은 느리지만 재현하기 쉽습니다.

Self-Mining의 Blind Spot

현재 model이 전혀 비슷하다고 생각하지 않는 failure는 top ANN candidate에 나오지 않습니다. BM25, taxonomy, entity constraint, human failure log를 섞어야 합니다.

Cross-Encoder Teacher와 Pseudo Label

Cross-encoder는 candidate query-passage pair에 relevance score를 줄 수 있습니다.

BM25 / ANN candidate pool
  → cross-encoder score
  → positive / ambiguous / hard-negative band
teacher_bands:
  positive: score >= 0.85
  ambiguous: 0.45 <= score < 0.85
  hard_negative: 0.15 <= score < 0.45
  easy_negative: score < 0.15

Threshold는 예시이며 teacher calibration에 따라 달라집니다. Ambiguous sample을 억지 binary label로 만들지 않는 것이 중요합니다.

Margin Distillation

Teacher가 positive와 negative의 score margin을 주고 student bi-encoder가 이를 근사하도록 할 수 있습니다.

teacher_margin = T(q, d⁺) - T(q, d⁻)
student_margin = S(q, d⁺) - S(q, d⁻)

L_margin = (student_margin - teacher_margin)²

GPL은 query generation과 cross-encoder pseudo labeling을 이용해 unlabeled target corpus에 dense retriever를 적응시키는 대표적 접근입니다.

Synthetic Query가 필요한 이유

Domain document는 많지만 실제 query label이 없을 수 있습니다.

Unlabeled passage
  → LLM generates plausible user query
  → passage becomes candidate positive
  → mine negatives
  → filter or pseudo-label
  → train retriever

Passage에서 Query 만들기

Passage:
"진공 pump 교체 후 4시간 동안 base pressure를 모니터링하고
2.0e-6 Torr 이하인지 확인한다."

Good synthetic query:
"진공 펌프 교체 뒤 base pressure 합격 기준은?"

Bad synthetic query:
"진공 펌프 교체 후 4시간 동안 확인해야 하는 압력 기준은
2.0e-6 Torr 이하인가?"

Bad query는 passage 문장을 복사하고 답을 노출합니다. 실제 user query distribution과 다릅니다.

InPars, Promptagator, GPL의 공통점과 차이

InPars

InPars는 language model을 이용해 passage에 대응하는 synthetic query를 만들고, 검색 model 학습에 활용하는 방향을 보여 줍니다. Few-shot prompt로 target query 스타일을 유도하고 생성 pair를 filtering하는 것이 중요합니다.

Promptagator

Promptagator는 소수의 task example을 prompt에 사용해 많은 synthetic query를 만들고, task-specific retriever 학습과 filtering을 결합하는 방향을 제안합니다. Few-shot example이 곧 data specification 역할을 합니다.

GPL

GPL은 target corpus passage에서 query를 생성하고, cross-encoder가 positive-negative margin pseudo label을 제공해 dense retriever를 domain adaptation합니다.

공통 원칙은 다음입니다.

generation alone is not supervision
generation + candidate mining + filtering / teacher scoring = usable signal

Synthetic Query Quality Gate

1. Answerability

Query가 source passage로 실제 답할 수 있어야 합니다.

2. No Answer Leakage

Query에 정답 숫자나 고유 phrase를 그대로 넣지 않습니다.

3. User-Likeness

실제 log의 길이, 언어, acronym, typo, context omission을 닮아야 합니다.

4. Specificity Diversity

exact fact
procedure
why / cause
comparison
exception
temporal
multi-hop bridge

한 passage에서 비슷한 question 열 개를 만들지 않습니다.

5. Deduplication

Exact hash, normalized text, embedding similarity로 near-duplicate query를 제거합니다.

6. Source Isolation

Test 문서에서 synthetic train query를 만들면 leakage입니다. Split을 먼저 정한 후 train corpus에서만 생성합니다.

Split을 먼저 고정하라

Random query split은 같은 문서의 paraphrase가 train과 test에 들어갈 수 있습니다.

더 강한 Split

  • document-level split
  • entity-level split
  • customer/tenant-level split
  • time-based split
  • template/family split
Bad:
train query from doc-17 paragraph 2
test query  from doc-17 paragraph 2 paraphrase

Better:
all queries and synthetic derivatives of doc-17 stay in one split

Time Split

train: documents observed through 2026-03
validation: 2026-04 to 2026-05
test: 2026-06 onward

새 version과 새로운 terminology에 일반화하는지 볼 수 있습니다.

Data Lineage

{
  "example_id": "ex-9821",
  "query": "...",
  "query_source": "synthetic",
  "generator_model": "model@version",
  "generator_prompt_hash": "sha256:...",
  "positive_id": "doc-81@v4#c7",
  "negative_ids": ["doc-91@v2#c1"],
  "negative_sources": ["ann_round_2"],
  "teacher_model": "cross-encoder@version",
  "teacher_scores": { "positive": 0.93, "negative": 0.31 },
  "split": "train",
  "dataset_version": "retrieval-ds-17"
}

어떤 generator·prompt·miner·teacher가 sample을 만들었는지 알아야 error cluster를 제거할 수 있습니다.

Training Loop

for batch in dataloader:
    q = query_encoder(batch.queries)
    p = doc_encoder(batch.positives)
    n = doc_encoder(batch.negatives)

    # [batch, 1 + negatives + cross-batch docs]
    scores = similarity(q, concat(p, n)) / temperature
    labels = positive_positions(batch)

    loss = multi_positive_contrastive_loss(
        scores=scores,
        labels=labels,
        negative_weights=batch.negative_weights,
    )

    loss.backward()
    clip_grad_norm_(model.parameters(), max_norm=1.0)
    optimizer.step()
    scheduler.step()
    optimizer.zero_grad()

Query와 Document Encoder 공유

같은 encoder weight를 공유할 수도 있고 별도 tower를 둘 수도 있습니다.

  • Shared: parameter가 적고 embedding space 일관성이 단순합니다.
  • Separate: query와 document style 차이를 더 유연하게 학습할 수 있습니다.

Serving과 export 복잡도까지 포함해 결정합니다.

Prefix와 Instruction

Base embedding model이 query: / passage: prefix나 instruction format을 요구한다면 training과 serving에서 정확히 같아야 합니다.

train query:     "query: ..."
production query:"query: ..."
document index:  "passage: ..."

한쪽에서 prefix가 빠지면 silent distribution shift가 생깁니다.

Negative Curriculum

처음부터 가장 어려운 negative만 넣으면 noisy label에 과적합할 수 있습니다.

epoch_1:
  easy: 0.5
  bm25: 0.4
  ann: 0.1
epoch_2:
  easy: 0.2
  bm25: 0.4
  ann: 0.4
epoch_3:
  easy: 0.1
  bm25: 0.25
  ann: 0.55
  teacher_boundary: 0.1

이 역시 고정 recipe가 아니라 실험 가설입니다. Source별 ablation을 보고 조정합니다.

Evaluation: Retrieval만 보지 않기

Offline Retrieval

  • Recall@k
  • MRR@k
  • nDCG@k
  • MAP
  • query slice별 first relevant rank

Hard Slice

slices:
  - acronym_alias
  - same_entity_wrong_attribute
  - old_version_vs_current
  - numeric_constraint
  - paraphrased_procedure
  - multilingual
  - no_answer
  - long_tail_entity

End-to-End

  • answer correctness
  • faithfulness
  • citation recall
  • abstention
  • retrieval + rerank + generation latency

Recall@20이 올라도 top-5 context에 irrelevant hard negative가 많아지면 generation이 나빠질 수 있습니다. 기존 reranker와 함께 평가합니다.

ANN까지 포함

Exact dot-product evaluation만 좋아지고 실제 quantized ANN index에서는 recall이 떨어질 수 있습니다.

model exact retrieval
  → index quantization
  → ANN search parameters
  → metadata filter
  → production Recall@k

전체 serving path를 측정합니다.

Model-Index Pair로 배포하기

Document vector는 encoder parameter의 함수입니다.

document_vector = E_document@model_version(document_text)

Model v2 query를 model v1 document index에 검색하면 score space가 맞지 않을 수 있습니다.

retrieval_release:
  release_id: retriever-2026-07-16-02
  query_encoder: encoder-v2
  document_encoder: encoder-v2
  tokenizer: tokenizer-v2
  prefix_contract: rag-v3
  corpus_snapshot: corpus-118
  vector_index: hnsw-204
  dataset: retrieval-ds-17
  metrics_report: eval-441

Blue-Green Index

current alias → index-v1
build index-v2 fully
  → smoke test
  → shadow queries
  → canary
  → atomically move alias to index-v2

Rollback은 query encoder와 index alias를 함께 되돌립니다.

Feedback Loop를 조심하라

Click log를 그대로 positive로 쓰면 기존 ranker가 보여 준 문서만 더 강화됩니다.

old model ranks A
user can only click A
training labels A positive
new model ranks A even more

Position bias, exposure bias, satisfaction 없는 click을 보정해야 합니다. Randomized exploration이나 human judgment 없이 click만 쓰지 않습니다.

Synthetic query도 generator가 선호하는 표현만 반복해 좁은 distribution을 만들 수 있습니다. 실제 query log slice와 비율을 비교합니다.

Privacy와 Security

  • Query log의 PII를 제거하거나 목적에 맞게 보호합니다.
  • Tenant 간 negative mixing으로 정보가 새지 않게 합니다.
  • 권한 없는 passage를 training sample로 사용하지 않습니다.
  • 삭제 요청된 document의 derived query와 vector도 추적 삭제합니다.
  • Synthetic generator에 sensitive source를 보낼 권한과 retention을 확인합니다.
  • Training artifact가 원문을 memorization하는지 위험을 검토합니다.

흔한 오해와 처방

1. Negative는 Positive가 아닌 모든 문서다

Unlabeled relevant passage가 false negative일 수 있습니다.

처방: multi-positive, teacher filter, human audit를 사용합니다.

2. 가장 어려운 Negative만 많을수록 좋다

Hard candidate는 label noise와 false negative 비율도 높습니다.

처방: easy·domain·BM25·ANN negative를 섞고 source별 ablation을 합니다.

3. Synthetic Query가 자연스러우면 정답 Label이다

Passage로 답할 수 없거나 실제 user가 묻지 않는 문장일 수 있습니다.

처방: answerability·leakage·distribution·dedup gate를 둡니다.

4. Random Split이면 충분하다

같은 document의 paraphrase가 train과 test에 들어가 성능이 부풀려집니다.

처방: document·entity·time split을 사용합니다.

5. Model만 교체하면 된다

Document vector도 새 space로 다시 계산해야 합니다.

처방: query encoder와 index를 atomic release로 관리합니다.

6. Offline Recall이 오르면 Production도 좋아진다

ANN, filter, reranker, generator가 효과를 상쇄할 수 있습니다.

처방: exact retrieval부터 end-to-end answer까지 단계별 gate를 둡니다.

Production 체크리스트

  • parser·chunk·hybrid·reranker baseline을 먼저 고정했다.
  • positive가 topic이 아니라 answer evidence인지 검수한다.
  • easy·same-domain·BM25·ANN negative를 구분한다.
  • false-negative rate를 sample audit로 측정한다.
  • multi-positive query를 지원한다.
  • synthetic query에 answerability·leakage·dedup gate가 있다.
  • train split을 정한 뒤 synthetic data를 생성한다.
  • document·entity·time leakage를 검사한다.
  • generator·prompt·miner·teacher lineage를 기록한다.
  • negative source별 ablation을 수행한다.
  • actual ANN와 metadata filter를 포함해 평가한다.
  • hard slice와 end-to-end answer를 함께 본다.
  • model, tokenizer, prefix, corpus, index를 한 release로 묶는다.
  • blue-green reindex와 atomic rollback이 있다.
  • click bias와 synthetic distribution drift를 감시한다.
  • PII·tenant·deletion lineage를 관리한다.

스스로 확인하기

  1. In-batch negative가 계산 효율을 높이지만 false negative를 만들 수 있는 이유는 무엇인가?
  2. BM25 hard negative와 ANN hard negative는 각각 어떤 오류 경계를 가르치는가?
  3. Synthetic query 생성만으로 supervision이 완성되지 않는 이유는 무엇인가?
  4. Random query split보다 document·time split이 더 어려운 이유는 무엇인가?
  5. Retriever model을 배포할 때 corpus 전체를 다시 embedding해야 하는 이유는 무엇인가?

다음 글에서는 candidate를 정밀하게 정렬하는 monoT5·RankT5식 reranker와 retrieved evidence를 사용하는 generator·RA-DIT 학습을 연결합니다.

참고자료