Learning to Rank (LTR)

OpenSearch + XGBoost 기반 검색 랭킹 최적화

Searchdoc | ess-ltr-legacy

검색 랭킹의 접근 방식

BM25 (단어 분포 기반) Embedding (벡터 유사도) Hybrid (수동 조합) LTR (자동 학습)

Feature 활용의 복잡도: 단일 Feature → 다중 Feature, 수동 설계 → 자동 학습

1. BM25 - 단어 분포 기반 Feature

단어의 출현 빈도(TF)와 희소성(IDF)을 수치화

Feature: 단어 분포 통계

score(D,Q) = Σ IDF(qᵢ) × TF(qᵢ, D) × (k₁ + 1) / (TF + k₁ × (1 - b + b × |D|/avgdl))


  • TF = 문서 내 단어 출현 횟수 (많을수록 관련)
  • IDF = 전체 문서에서의 희소성 (희귀할수록 중요)
  • 핵심: 단어가 얼마나 자주, 얼마나 특별하게 등장하는가

BM25의 특징

✅ 장점

  • 빠른 검색 속도 (역색인)
  • 키워드 정확 매칭
  • 해석 가능한 스코어
  • 대규모 확장 용이

❌ 한계

  • 어휘 불일치 (Vocabulary Mismatch)
  • 동의어/유의어 처리 불가
  • 의미적 유사성 무시
  • "자동차" vs "차량" 매칭 실패

2. Embedding - 벡터 유사도 Feature

텍스트를 벡터로 변환하고 Cosine Similarity로 비교

Feature: 벡터 간 코사인 유사도

sim(q, d) = cos(θ) = (q · d) / (||q|| × ||d||)


  • q, d = 쿼리/문서를 Encoder가 변환한 Dense Vector
  • cos(θ) = 두 벡터가 같은 방향이면 1, 수직이면 0
  • 핵심: 의미적으로 비슷한 텍스트는 비슷한 벡터

Embedding의 특징

✅ 장점

  • 의미적 유사성 포착
  • 동의어/유의어 자연 처리
  • "자동차" ≈ "차량" 매칭
  • Cross-lingual 가능

❌ 한계

  • 희귀어/고유명사 약함
  • 정확한 키워드 매칭 부족
  • "iPhone 15" 검색 시 부정확
  • 벡터 인덱싱 비용

3. Hybrid - 수동 조합

사람이 직접 두 Feature의 비율과 조합 방식을 설계

Linear Combination (수동 가중치):

score(d) = α × BM25(d) + (1-α) × KNN(d)


  • α = 사람이 설정하는 가중치 (예: BM25에 0.7, KNN에 0.3)
  • RRF = 랭킹 순위 기반 조합 (가중치 대신 순위 역수 합)
  • 핵심: 어떤 비율로 조합할지 사람이 결정

Hybrid의 특징

✅ 장점

  • 키워드 + 의미 검색 동시
  • Recall 향상
  • 다양한 쿼리 타입 대응
  • 실무에서 가장 많이 사용

❌ 한계

  • 가중치 수동 튜닝 필요
  • 2개 Feature만 사용
  • 도메인별 최적값 다름
  • 복잡한 관련성 표현 한계

4. LTR - 자동 학습 조합

N개 Feature의 최적 조합을 ML 모델이 데이터로부터 학습

ML Model이 Feature 가중치 학습:

score(q, d) = f(x₁, x₂, ..., xₙ)


  • xᵢ = BM25, KNN, 제목매칭, 요약매칭 등 다양한 Feature
  • f = XGBoost, LambdaMART 등이 학습한 모델
  • 핵심: 어떤 Feature를 얼마나 중요하게 볼지 데이터로부터 학습

검색 랭킹 접근법 비교

접근법 사용 Feature 조합 방식 핵심 아이디어
BM25 단어 분포 (TF-IDF) 수식 고정 단어 빈도 + 희소성
Embedding 벡터 유사도 Cosine Sim 의미적 유사성
Hybrid BM25 + KNN 사람이 설계 수동 가중치 조합
LTR N개 Feature ML이 학습 데이터 기반 자동 최적화

Feature 복잡도 증가 + 조합 방식 자동화 = LTR

LTR 학습 방법론

Pointwise Pairwise Listwise

"순위"를 어떻게 학습할 것인가? - 학습 단위의 차이

Pointwise - 문서 하나씩

각 문서의 절대적 관련도 점수를 예측

학습 방식: 회귀(Regression) 또는 분류(Classification)

Query "파이썬 입문" → Doc A: 4점, Doc B: 2점, Doc C: 1점


  • 장점: 구현 간단, 기존 ML 모델 활용 가능
  • 단점: 문서 간 상대적 순서를 직접 학습하지 않음
  • 📝 비유: 학생들 시험 점수 각각 채점 (서로 비교 X)

Pairwise - 문서 쌍 비교

두 문서 중 어느 것이 더 관련있는지 학습

학습 방식: 쌍(Pair) 비교 → 이진 분류

Doc A vs Doc B → A가 더 관련 (1) or B가 더 관련 (0)


  • 장점: 순서 관계 직접 학습, 실제 랭킹과 더 밀접
  • 단점: 쌍의 개수가 O(n²)로 증가
  • 📝 비유: 토너먼트 방식 - "A vs B 누가 더 잘했나?"
  • 🔧 대표: RankNet, RankSVM

Listwise - 전체 리스트

쿼리에 대한 전체 문서 리스트의 순서를 최적화

학습 방식: 전체 리스트의 랭킹 품질(NDCG 등) 직접 최적화

[A, B, C, D] 순서 자체를 평가 → NDCG@10 = 0.85


  • 장점: 실제 랭킹 메트릭과 가장 잘 align
  • 단점: 구현 복잡, 계산 비용 높음
  • 📝 비유: 전체 순위표 자체를 평가하고 개선
  • 🔧 대표: ListNet, LambdaMART

LambdaMART - 실전 표준

Pairwise + Listwise의 장점을 결합한 알고리즘

핵심 아이디어:

λ = Pairwise Loss의 기울기 × NDCG 변화량


  • Lambda: 두 문서 순서가 바뀌면 NDCG가 얼마나 변하는지
  • MART: Gradient Boosted Decision Trees
  • 🔥 장점: NDCG를 직접 미분할 수 없는 문제 우회
  • 🏆 검색 엔진에서 가장 널리 사용되는 LTR 알고리즘

LTR 학습 방법 비교

방법 학습 단위 Loss 함수 대표 알고리즘
Pointwise 문서 1개 MSE, Cross-entropy Linear Reg, Random Forest
Pairwise 문서 쌍 Pairwise Loss RankNet, RankSVM
Listwise 전체 리스트 NDCG-like LambdaMART, ListNet

실무에서는 LambdaMART 또는 XGBoost (Pairwise/Listwise) 가장 많이 사용

LTR이란?

Learning to Rank는 머신러닝을 사용하여
검색 결과의 순위(Ranking)를 최적화하는 기법

기존 검색

TF-IDF, BM25 등
정적인 점수 계산

LTR 적용

여러 피처를 ML 모델이
학습하여 동적 랭킹

전체 아키텍처

1. Featureset
2. Dataset
3. Train
단계 Task 설명
1 create-featureset OpenSearch에 Feature 정의 등록
2 generate-dataset LLM으로 Judgement + Feature 추출
3 train-model XGBoost 모델 학습 & 업로드

실행 방법

# 1. Featureset 생성
python main.py --task create-featureset --env .env.local

# 2. Dataset 생성 (Judgement + Features)
python main.py --task generate-dataset --env .env.local

# 3. 모델 학습
python main.py --task train-model --env .env.local

환경변수: OpenSearch 연결정보, Bedrock 설정, 경로 등

Step 1: Create Featureset

OpenSearch LTR Plugin에 Feature 정의 등록

Featureset = 검색 시 추출할 Feature들의 템플릿 집합

Feature 종류 (10개)

Text Match (7개)

  • txt_en_1: chunk_text
  • txt_en_3: absolute_title
  • txt_en_4: qr (질의응답)
  • txt_en_5: keywords
  • txt_en_6: chunk_summary
  • txt_en_7: node_summary

Embedding KNN (3개)

  • emb_en_1: chunk_embedding
  • emb_en_2: qr_1_embedding
  • emb_en_5: chunk_summary_embedding

Featureset 등록 예시

{
  "featureset": {
    "name": "my_featureset",
    "features": [
      {
        "name": "txt_en_1",
        "params": ["query_text"],
        "template": {
          "match": { "chunk_text": "{{query_text}}" }
        }
      },
      {
        "name": "emb_en_1",
        "params": ["query_embedding_str"],
        "template": {
          "knn": { "chunk_embedding": { "vector": ..., "k": 16 }}
        }
      }
    ]
  }
}

Step 2: Generate Dataset

학습 데이터 생성 - 3개 서브태스크

Judgement
Metadata
Features

2-1. Judgement Scale Dataset

Claude Sonnet이 Query-Document 관련도 평가

Query: "AWS Lambda 콜드스타트 최적화"
Document: "Lambda 함수의 콜드스타트를 줄이는 방법..."

→ Claude 평가: 4 (매우 관련있음)

Judgement Scale:
  0: 관련없음
  1: 약간 관련
  2: 관련있음
  3: 많이 관련
  4: 매우 관련 (정답)

2-2. Metadata Generation

문서에서 검색 가능한 메타데이터 추출

추출 항목

  • QR (예상 질문-답변)
  • Keywords
  • Chunk Summary
  • Node Summary

사용 모델

  • Claude Sonnet (요약/QR)
  • Titan Embedding (벡터)

2-3. Feature Dataset

OpenSearch SLTR로 Feature 값 추출

# LTR 포맷 (LibSVM style)
# Label qid:N feat1:val1 feat2:val2 ...

4 qid:1 1:0.85 2:0.72 3:0.91 4:0.45 5:0.88 ...
0 qid:1 1:0.12 2:0.08 3:0.15 4:0.03 5:0.11 ...
3 qid:2 1:0.78 2:0.65 3:0.82 4:0.55 5:0.71 ...

Query마다 여러 Document의 Feature값 + Judgement Label

Step 3: Train Model

XGBoost로 랭킹 모델 학습

Feature Dataset → XGBoost Classifier → OpenSearch 모델 업로드

일반적인 ML 학습 vs HPO

일반 학습

  • Hyperparameter 고정
  • 예: max_depth=3, lr=0.01
  • 한 번 학습 후 평가
  • ⚠️ 최적값 찾기 어려움

HPO (Hyperparameter Optimization)

  • Hyperparameter 자동 탐색
  • 다양한 조합 시도 (100+ trials)
  • 최적 조합 자동 선택
  • ✅ 성능 극대화

Hyperparameter란?

모델이 학습 전에 사람이 정해주는 설정값

Parameter (학습됨) Hyperparameter (설정)
트리의 분기 기준값 max_depth (트리 깊이)
각 Feature의 가중치 learning_rate (학습 속도)
분류 경계선 n_estimators (트리 개수)

📝 비유: 요리 레시피(Parameter) vs 오븐 온도(Hyperparameter)
레시피는 재료로 만들어지고, 오븐 온도는 요리 전에 설정

Optuna - HPO 프레임워크

베이지안 최적화 기반 효율적 탐색

Grid Search vs Optuna:

  • Grid Search: 모든 조합 시도 → O(n^k) 비효율
  • Random Search: 랜덤 샘플링 → 운에 의존
  • Optuna (TPE): 이전 결과 기반 유망한 영역 집중 탐색

🎯 100번 시도로 1000번 Grid Search보다 나은 결과

학습 파이프라인 (코드)

# 1. 데이터 로드 & 파싱
train_X, train_y, train_qid = generate_dataset(train_data)
# train_X: 10개 Feature 컬럼
# train_y: Judgement Label (0-4)
# train_qid: Query ID (그룹핑용)

# 2. HPO with Optuna (100 trials)
study = optuna.create_study(direction='maximize')
study.optimize(hpo_objective, n_trials=100)

# 3. Final Model Training
model = XGBClassifier(**best_params)
model.fit(X_train, y_train, eval_set=[(X_val, y_val)])

HPO Objective 함수

Optuna가 매 Trial마다 호출하는 함수

def hpo_objective(trial):
    # 1. Optuna가 제안하는 Hyperparameter
    params = {
        'max_depth': trial.suggest_int('max_depth', 1, 5),
        'learning_rate': trial.suggest_float('learning_rate', 1e-4, 1e-2, log=True),
        'n_estimators': trial.suggest_int('n_estimators', 150, 1500),
        'subsample': trial.suggest_float('subsample', 0.4, 1.0),
        'early_stopping_rounds': trial.suggest_int('early_stopping_rounds', 15, 40),
    }

    # 2. 해당 Hyperparameter로 모델 학습
    model = XGBClassifier(**params)
    model.fit(train_X, train_y, eval_set=[(val_X, val_y)])

    # 3. 평가 지표 반환 → Optuna가 최대화
    return recall_score(val_y, model.predict(val_X))

Hyperparameter 탐색 범위

Parameter Range 설명
max_depth 1 ~ 5 트리 깊이
n_estimators 150 ~ 1500 트리 개수
learning_rate 1e-4 ~ 1e-2 학습률 (log scale)
subsample 0.4 ~ 1.0 샘플링 비율
early_stopping 15 ~ 40 조기 종료 라운드

모델 평가 지표

Primary

  • Recall - 주 최적화 지표
  • AUROC
  • Accuracy

Analysis

  • Classification Report
  • Confusion Matrix
  • Feature Importance

* Recall 최적화: 관련 문서를 놓치지 않는 것이 핵심

OpenSearch 모델 업로드

# XGBoost → JSON dump
reg_booster = model.get_booster()
dump_tree_list = reg_booster.get_dump(
    dump_format='json',
    with_stats=False
)

# OpenSearch LTR Plugin에 등록
model_payload = {
    "model": {
        "name": "my-model-xgbr-tree-107-depth-2",
        "model": {
            "type": "model/xgboost+json",
            "definition": json.dumps(json_tree)
        }
    }
}
# POST /_ltr/_featureset/{name}/_createmodel

Feature 상세

Feature Type Field 설명
txt_en_1 Text chunk_text 본문 텍스트 매칭
txt_en_3 Text absolute_title 문서 제목 매칭
txt_en_4 Text qr 예상 Q&A 매칭
txt_en_5 Text keywords 키워드 매칭
txt_en_6 Text chunk_summary 청크 요약 매칭
txt_en_7 Text node_summary 노드 요약 매칭
emb_en_1 KNN chunk_embedding 청크 벡터 유사도
emb_en_2 KNN qr_1_embedding Q&A 벡터 유사도
emb_en_5 KNN chunk_summary_embedding 요약 벡터 유사도

프로젝트 구조

ess-ltr-legacy/
├── main.py                    # CLI 진입점
├── app/
│   ├── bedrock/               # LLM Wrapper
│   │   ├── claude_sonnet.py   # Judgement, QR, Summary
│   │   └── titan_embedding.py # Vector Embedding
│   ├── core/                  # Config, Task 정의
│   ├── prompt/                # 18개 프롬프트 템플릿
│   │   ├── generate_qr.txt
│   │   ├── judgement_scale.txt
│   │   └── ...
│   ├── service/               # 핵심 서비스
│   │   ├── opensearch_featureset_service.py
│   │   ├── judgement_scale_dataset_service.py
│   │   ├── metadata_generate_service.py
│   │   ├── feature_dataset_service.py
│   │   └── train_ranking_model_service.py
│   └── utils/                 # OpenSearch Helper
└── dataset/                   # 생성된 데이터셋

검색 파이프라인 구조

Query → Base RankingReranking → Final Results

2-Stage Ranking: 속도와 정확도의 균형

Stage 1: Base Ranking (Retrieval)

빠른 후보 추출 - 수백만 → 수백 개

목적: 전체 문서에서 관련 후보군 빠르게 추출

방식:

  • BM25 - 역색인 기반, O(1) 수준 검색
  • KNN (ANN) - HNSW로 근사 탐색
  • Hybrid - BM25 + KNN 결합

⚡ 속도 우선: 수백만 문서를 ms 단위로 필터링

Stage 2: Reranking

정밀 재정렬 - 상위 N개만 정교하게

목적: Base Ranking 결과를 더 정교하게 재정렬

방식:

  • LTR (XGBoost) - 다중 Feature 기반 ML 모델
  • Cross-Encoder - Query-Doc 쌍 직접 비교
  • LLM Reranker - GPT/Claude로 관련성 평가

🎯 정확도 우선: 상위 100~1000개만 대상

왜 2-Stage인가?

전체 Rerank 불가

  • 1M 문서 × Cross-Encoder
  • = 수십 분 ~ 수시간 소요
  • 실시간 검색 불가능

Base Only 한계

  • 단순 스코어 조합
  • 복잡한 관련성 포착 X
  • 정밀도 한계

Base Ranking (Recall 확보) + Reranking (Precision 향상)

Reranking 방식 비교

방식 속도 정확도 특징
LTR (XGBoost) ⚡ 빠름 ★★★☆ Feature 기반, OpenSearch 내장
Cross-Encoder 보통 ★★★★ Query-Doc 쌍 직접 비교
LLM Reranker 느림 ★★★★★ Cohere, GPT 기반
ColBERT 빠름 ★★★★ Token 레벨 매칭

LTR = 속도와 정확도의 균형점, 검색엔진 내장 가능

LTR Reranking의 장점

검색엔진 내장

  • OpenSearch SLTR Plugin
  • 별도 서버 불필요
  • Rescore Query로 적용

빠른 속도

  • XGBoost = ms 단위
  • Cross-Encoder 대비 10x↑
  • 실시간 검색 가능

해석 가능

  • Feature Importance 확인
  • 어떤 시그널이 중요한지
  • 디버깅 용이

커스터마이징

  • 도메인 특화 Feature 추가
  • 비즈니스 로직 반영
  • 지속적 개선 가능

검색 시 LTR 적용

Query → Base Retrieval (BM25 + KNN) → LTR Rescore → Final Ranking

{
  "query": { ... },
  "rescore": {
    "query": {
      "rescore_query": {
        "sltr": {
          "model": "my-model-xgbr-tree-107-depth-2",
          "params": {
            "query_text": "검색어",
            "query_embedding_str": "[0.1, 0.2, ...]"
          }
        }
      }
    }
  }
}

요약

구성요소 기술
검색 엔진 OpenSearch + LTR Plugin
Feature Text Match (7) + KNN (3)
Judgement Claude Sonnet (5-scale)
Embedding Amazon Titan
ML Model XGBoost + Optuna HPO
평가 지표 Recall (primary)

Q&A

감사합니다