[카테고리:] 미분류

  • 앙상블

    1) 점수 정규화 후 합치기 (CombSUM의 기본형)

    쿼리마다 각 점수를 정규화(min-max, z-score, 혹은 rank→percentile) 한 뒤 가중합합니다.

    import numpy as np
    from sklearn.preprocessing import minmax_scale
    from sklearn.metrics.pairwise import cosine_similarity
    
    def ensemble_search(query, k=3, w_bm25=0.6, w_tfidf=0.4):
        # 1) 개별 점수 계산
        bm25_scores = np.array(bm25.get_scores(query.split()))          # 길이 = N
        cos = cosine_similarity(tfidf.transform([query]), tfidf_mat).ravel()  # 길이 = N, [0,1]
    
        # 2) 쿼리 단위 정규화 (min-max)
        bm25_n = minmax_scale(bm25_scores)   # -> [0,1]
        cos_n  = minmax_scale(cos)           # -> [0,1]
    
        # 3) 가중합
        scores = w_bm25 * bm25_n + w_tfidf * cos_n
    
        # 4) 상위 k
        top_idx = np.argsort(scores)[::-1][:k]
        return [(int(i), float(scores[i]), corpus[i]) for i in top_idx]
    
    • 장점: 간단하고 효과적.
    • : min-max 대신 z-score(평균/표준편차 표준화)도 자주 씁니다. 분포 꼬리가 긴 BM25에 유리할 때가 있어요.
    • 더 안전하게: min-max는 outlier에 민감 → 랭크 기반 정규화(아래 2번)도 고려.

    2) 랭크 융합 (Reciprocal Rank Fusion, RRF)

    점수 대신 순위만 사용하므로 스케일 문제를 원천적으로 제거합니다. 최신 랭킹 대회에서도 baseline으로 강력합니다.

    def rrf_fusion(ranks_list, k=3, C=60):
        # ranks_list: 각 모델이 준 정렬 인덱스 리스트 [idx_sorted_by_bm25, idx_sorted_by_tfidf, ...]
        # RRF: sum_i 1 / (C + rank_i(d))
        n = len(ranks_list[0])
        rrf = np.zeros(n)
        for ranks in ranks_list:
            pos = np.empty(n, dtype=int)
            pos[ranks] = np.arange(n)  # 문서→순위
            rrf += 1.0 / (C + pos)
        top_idx = np.argsort(rrf)[::-1][:k]
        return top_idx, rrf
    
    def ensemble_search_rrf(query, k=3, C=60):
        bm25_scores = np.array(bm25.get_scores(query.split()))
        cos = cosine_similarity(tfidf.transform([query]), tfidf_mat).ravel()
    
        bm25_order = np.argsort(bm25_scores)[::-1]
        cos_order  = np.argsort(cos)[::-1]
    
        top_idx, rrf = rrf_fusion([bm25_order, cos_order], k=k, C=C)
        return [(int(i), float(rrf[i]), corpus[i]) for i in top_idx]
    
    • 장점: 스케일 문제 없음, 구현 쉬움, 튼튼한 성능.
    • C는 보통 10~100 사이에서 개발셋으로 튜닝.

    3) CombMNZ (정규화 + 활성 신호 수 반영)

    정규화한 점수의 합을 비영(>0) 모델 수로 곱합니다. 여러 모델이 동의하는 문서를 올려줍니다.

    def ensemble_search_mnz(query, k=3, eps=1e-12):
        bm25_scores = np.array(bm25.get_scores(query.split()))
        cos = cosine_similarity(tfidf.transform([query]), tfidf_mat).ravel()
    
        bm25_n = (bm25_scores - bm25_scores.min()) / (bm25_scores.ptp() + eps)
        cos_n  = (cos - cos.min()) / (cos.ptp() + eps)
    
        S = bm25_n + cos_n
        active = (bm25_n > 0).astype(int) + (cos_n > 0).astype(int)
        scores = S * active
    
        top_idx = np.argsort(scores)[::-1][:k]
        return [(int(i), float(scores[i]), corpus[i]) for i in top_idx]
    

    4) 가중치에 대한 팁

    • w_bm25 + w_tfidf = 1로 둘 필요는 없지만, 정규화 후에는 그렇게 두면 해석이 직관적입니다.
    • 최적 가중치는 개발셋에서 그리드 서치로 찾으세요. (예: w_bm25 ∈ {0.2,0.4,0.6,0.8})

    5) 구현 체크리스트

    • BM25 토크나이저와 TF-IDF 벡터라이저가 동일한 전처리(소문자화, 형태소·불용어 등)를 공유하는지 확인.
    • 코퍼스가 크면 TF-IDF는 sparse, BM25는 배열: 정렬/인덱싱 길이 일치 확인.
    • 쿼리 길이가 1인 경우 BM25가 과도하게 강해질 수 있어 정규화/랭크 융합이 특히 유효.