본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로AI › 언어 모델 › 응용

11. 자신의 데이터에 맞춘 임베딩 모델 만들기 : RAG 개선하기

목차

자신의 데이터에 맞춘 임베딩 모델 만들기 : RAG 개선하기

“장애 등급 P1의 초기 대응 시간은?”이라는 질문에 일반 검색 모델이 P1이라는 문자열만 있는 문서를 올리고, 실제 대응 시간을 담은 운영 문서를 놓칠 수 있다. 서비스 용어와 문서 형식은 범용 모델이 학습한 유사도와 다르기 때문이다. 이때 바로 모델을 재학습하기보다 검색 실패가 어느 단계에서 생겼는지 먼저 확인한다.

flowchart LR
  A[실제 질문과 정답 문서 수집] --> B[기준 모델 평가]
  B --> C{오류 원인}
  C -->|문서가 잘림| D[청크 조정]
  C -->|코드와 숫자 누락| E[키워드 검색 결합]
  C -->|도메인 의미 불일치| F[임베딩 미세 튜닝]
  D --> G[같은 평가 집합으로 재측정]
  E --> G
  F --> G

학습 데이터를 질문과 문서의 쌍으로 만들기

임베딩 모델의 학습 목표는 정답 문서가 질문에 더 가깝고, 무관한 문서는 멀어지게 하는 것이다. 아래는 작은 형식 예시다. 실제 학습에는 더 많은, 중복을 점검한 쌍이 필요하다.

질문(anchor)관련 문서(positive)구분할 문서(negative)
P1 초기 대응 시간은?P1 접수 후 15분 안에 담당자가 응답한다.P2는 영업일 기준 4시간 안에 응답한다.
환불 접수 기한은?결제일로부터 7일 이내 환불을 신청한다.환불 금액은 원 결제 수단으로 지급한다.

가까운 주제지만 질문의 답이 아닌 문서를 어려운 음성 예시(hard negative)라고 한다. 랜덤 문서만 음성 예시로 넣으면 쉬운 구분만 배울 수 있다. 다만 실제로 정답인 문서를 음성으로 잘못 표시하면 오히려 모델이 망가진다. 위 첫 행의 P2 문서처럼 문맥이 비슷하고 답은 다른지 사람이나 별도 검증 단계가 확인해야 한다.

질문 하나에 정답 청크가 여러 개라면 모두 관련 문서로 표시한다. 같은 원본에서 조금 겹쳐 잘린 청크가 학습·평가에 동시에 들어가지 않도록 문서 단위로 분리한다. 모델이 문서의 문장을 외워서 얻은 점수를 일반화 성능으로 오해하지 않기 위해서다.

미세 튜닝의 최소 흐름

Sentence Transformers는 질문·관련 문서 쌍으로 학습하는 MultipleNegativesRankingLoss를 제공한다. 같은 배치에 있는 다른 쌍의 문서를 음성 예시로 사용하므로, 배치 안에 의미가 같은 문서가 중복되지 않도록 주의한다. 아래 코드는 데이터 형태를 보여주는 학습 예시다. 실제 데이터, 모델 라이선스, 계산 자원에 맞춰 매개변수를 정해야 한다.

from datasets import Dataset
from sentence_transformers import (
    SentenceTransformer,
    SentenceTransformerTrainer,
    SentenceTransformerTrainingArguments,
    losses,
)
from sentence_transformers.training_args import BatchSamplers

pairs = Dataset.from_dict({
    "anchor": ["P1 초기 대응 시간은?", "환불 접수 기한은?"],
    "positive": [
        "P1 접수 후 15분 안에 담당자가 응답한다.",
        "결제일로부터 7일 이내 환불을 신청한다.",
    ],
})

model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
args = SentenceTransformerTrainingArguments(
    output_dir="./models/domain-embedding",
    num_train_epochs=1,
    per_device_train_batch_size=2,
    batch_sampler=BatchSamplers.NO_DUPLICATES,
)
trainer = SentenceTransformerTrainer(
    model=model,
    args=args,
    train_dataset=pairs,
    loss=losses.MultipleNegativesRankingLoss(model),
)
trainer.train()
model.save_pretrained("./models/domain-embedding")

이 두 쌍은 학습을 이해하기 위한 샘플일 뿐, 품질 향상을 주장할 수 있는 데이터 양이 아니다. 실제 운영 질문의 표현 차이, 답변이 없는 질문, 새 문서까지 별도로 검증해야 한다. 공식 학습 개요와 손실 함수 설명에 데이터 형식과 배치 주의사항이 정리되어 있다.

RAG 전체에서 평가하기

모델을 학습했더라도 문서 검색만 좋아지고 최종 답변은 나빠질 수 있다. 두 층을 따로 측정한다.

  1. 검색 평가: 고정된 질문·정답 문서 집합에서 Recall@k, MRR@k, nDCG@k를 기준 모델과 비교한다. 0건 질문은 잘못된 문서를 끌어오는 비율을 본다.
  2. 답변 평가: 검색된 근거에 정답이 있는지, 답변의 각 주장에 근거가 있는지, 근거가 없을 때 답을 보류하는지 확인한다.
  3. 운영 평가: 질문당 인코딩 지연, 검색 지연, 모델 크기, 메모리와 색인 재구축 시간을 기록한다.

평가 질문을 학습 쌍에서 복사하면 점수가 과대평가된다. 상품명·약어·한국어 띄어쓰기·최신 문서·표 질문을 따로 묶어 보면 어디가 개선되었는지 알 수 있다. 모델이 바뀌면 기존 문서 벡터와 질문 벡터를 섞어 쓰지 않는다. 새 모델로 전체 문서를 다시 인코딩하고 별도 색인에 넣은 다음, 평가를 통과하면 전환한다.

실패 신호먼저 해볼 일
훈련 점수만 오름원본 문서 단위 분리, 중복 질문 제거
유사한 오답이 상위에 옴정답 라벨과 hard negative 재검토
코드·날짜·ID 검색이 약함키워드 검색과 결합, 필드별 필터
답변이 근거와 다름생성 단계의 인용·보류 규칙 점검

임베딩 미세 튜닝은 검색 오류 중 의미 표현의 문제를 해결하는 수단이다. 다음 장에서는 좋은 임베딩을 여러 문서에 적용할 때 저장, 색인, 권한을 어떻게 다룰지 살펴본다.

배치 안의 음성 예시는 어떻게 만들어지는가

위 코드의 MultipleNegativesRankingLoss에 (질문 A, 정답 문서 A)와 (질문 B, 정답 문서 B)가 같은 배치로 들어간다고 하자. 질문 A에서 문서 A는 가깝게, 문서 B는 멀게 학습한다. 질문 B에서는 반대다. 별도로 음성 문서를 수집하지 않아도 배치 속 다른 문서가 음성 신호가 되는 것이 장점이다. 하지만 두 질문의 정답이 사실상 같은 문서라면 문서 B를 음성으로 취급하는 순간 잘못된 학습 신호가 된다. 중복 제거와 NO_DUPLICATES 배치 샘플러가 필요한 이유다.

도메인 데이터에서 특히 조심할 것은 제목만 다른 중복 문서다. 예를 들어 “P1 초기 대응 시간”과 “긴급 장애 최초 응답”이 같은 15분 규정을 설명한다면 한쪽을 다른 질문의 음성으로 넣지 않아야 한다. 문서 URL이 달라도 내용 해시, 개정 이력, 표제어를 비교해 중복 후보를 확인한다. 선호하는 문서 하나만 정답으로 표시하지 말고, 같은 질문에 답하는 다른 문서도 관련 문서 집합에 포함한다.

학습 전에 평가기를 붙이기

훈련 손실이 내려가는 것과 사용자가 찾는 문서를 잘 찾는 것은 별개다. InformationRetrievalEvaluator는 질문 ID, 문서 ID, 관련 문서 ID 집합으로 검색 품질을 계산한다. 다음은 입력 구조를 확인하는 작은 예시이며 두 질문만으로 모델의 품질을 주장할 수 없다.

from sentence_transformers.evaluation import InformationRetrievalEvaluator

queries = {
    "q1": "P1 초기 대응 시간은?",
    "q2": "환불 접수 기한은?",
}
corpus = {
    "d1": "P1 접수 후 15분 안에 담당자가 응답한다.",
    "d2": "P2는 영업일 기준 4시간 안에 응답한다.",
    "d3": "결제일로부터 7일 이내 환불을 신청한다.",
}
relevant = {"q1": {"d1"}, "q2": {"d3"}}
evaluator = InformationRetrievalEvaluator(
    queries, corpus, relevant, mrr_at_k=[3], precision_recall_at_k=[1, 3]
)
metrics = evaluator(model)
print(metrics)

평가기는 q1에서 d1이 몇 번째에 나왔는지, 상위 1개와 3개 안에 들어왔는지를 계산한다. 모델을 교체하기 전과 후에 동일한 질문·문서 집합을 사용해야 숫자를 비교할 수 있다. 다만 운영 문서가 추가되면 평가 집합 자체도 갱신해야 한다. Sentence Transformers 평가 API는 반환 지표와 입력 구조를 설명한다.

숫자가 좋아져도 롤아웃을 멈춰야 하는 경우

가상의 평가에서 Recall@5가 82%에서 89%로 올랐다고 하자. 전체 평균만 보면 개선이지만 상품 코드 질문의 성공률이 90%에서 60%로 떨어졌다면 고객지원 검색에는 손해일 수 있다. 문서 유형별 지표를 나누고 절대 놓치면 안 되는 질문 범주의 하한을 정한다. 미세 튜닝으로 도메인 의미를 더 잘 잡아도 새 문서의 ID·날짜 검색은 키워드 검색이 더 안정적일 수 있다.

학습한 임베딩 모델을 배포할 때는 질문만 새 모델로 바꾸지 않는다. 문서 벡터를 모두 새 모델로 만들어 새 색인에 보관하고, 검색 요청의 질문 인코딩과 문서 색인 버전을 함께 전환한다. 전환 전에 두 색인에서 같은 평가 질문과 운영 샘플을 비교하며, 품질이 떨어질 경우 이전 색인으로 돌아갈 수 있게 둔다.

참고 자료

같은 카테고리의 글