Loading the catalog…
Loading the catalog…
리랭커란, RAG가 생성한 후보 문서들에 대해 질문에 대한 관련성 및 일관성을 판단하여 문서의 우선 순위를 재정렬하는 것 즉, 질문과 관련성 있는 문서들을 컨텍스트의 상위권에 위치시킴으로써, 답변의 정확도를 올림 1. 기존 RAG에서의 문제 RAG는 문서에서 의미론적 검색(Semantic search) 과정을 수행함 → 벡터 검색 이 과정에서 발생되는 정보 손실 문서의 임베딩 벡터 변환 과정에서 손실 : 문서가 긴 경우에 정해진 벡터의 차원으로 표현하기 어려움 검색 과정에서 손실 : 시간 단축을 위해 ANNs 기술을 사용함 (Approximate Nearest Neighbor search) → 이러한 문제를 해결하기 위해 검색 후 반환되는 문서 수를 늘림 (k 증가) → but, LLM에게 전달되는 컨텍스트가 늘어나서 비용이 비효율적 => 컨텍스트 내 존재 유무가 중요한 게 아니라, 순서가 중요함 2. Lost in the Middle Lost in Middle: 질문에 대한 관련 문서가 컨텍스트 중간에 위치할 경우, LLM 응답 정확도가 낮아진다. (출처: AWS 기술 블로그, 한국어 Reranker를 활용한 검색 증강 생성(RAG) 성능 올리기 / 원 출처: Liu et al., 2023 ) RAG의 정확도는 관련 정보의 컨텍스트 내 존재 유무가 아니라 순서이다. → 관련 정보가 컨텍스트 내 상위권에 위치하고 있을 때 좋은 답변을 얻을 수 있다. 3. 리랭커 💡 가장 관련성 높은 문서를 상위권에 배치(순위 재정렬, Reranking)하기 질문과 문서 사이의 유사도를 측정하는 것이 목적 Cross-encoder 사용 그림 2: Encoder 종류 (a) Bi-encoder (b) Cross-encoder (출처: AWS 기술 블로그, 한국어 Reranker를 활용한 검색 증강 생성(RAG) 성능 올리기 ) 질문과 문서를 하나의 input으로 활용 : 동시에 분석(Self-attention) rerank를 사용할 때는 검색 단계에서 상위 k개 문서에 한해서만 순위를 재조정함 유사도를 이용한 검색은 전체 문서에 대해서 빠르게 결과값을 얻을 수 있지만, rerank는 질의와 문서 사이의 의미론적 유사성을 탐색함 → 오래 걸려서 검색을 통해서 추출된 상위 문서에만 한해서 수행 4. 1차 검색(Bi-encoder)과 리랭커(Cross-encoder)의 차이 💡 둘 다 의미를 계산한다. 차이는 "질문과 문서가 모델 안에서 언제 서로를 보느냐"이다. Bi-encoder (1차 검색) 질문과 문서를 각각 따로 인코더에 넣는다. 질문 → 인코더 → 벡터 q 문서 → 인코더 → 벡터 d 점수 = cos(q, d) (또는 내적) 문서 벡터 d를 만들 때 질문은 입력에 없다. → 문서 벡터는 질문과 상관없이 미리 계산해서 저장 해 둘 수 있다 (벡터 DB, 인덱스). 검색 시점에 하는 일: 질문 인코딩 1번 + 저장된 벡터와의 내적 N번 → 빠르다. 한계 문서 벡터를 만들 때 어떤 질문이 올지 모르므로, 문서의 모든 정보를 고정된 차원의 벡터 하나에 미리 압축해야 한다. 질문의 단어와 문서의 단어가 모델 안에서 직접 비교되는 과정이 없다. 비교는 맨 마지막에 벡터 두 개끼리 한 번만 일어난다. 이 방식을 representation-based (표현 기반) 방식이라고 부른다. 참고: late interaction 은 ColBERT처럼 토큰 단위 벡터들을 마지막에 비교하는 방식을 가리키는 용어로, Bi-encoder와는 구분된다. Cross-encoder (리랭커) 질문과 문서를 하나의 시퀀스로 이어 붙여 한 번에 넣는다. 입력: [CLS] 질문 토큰들 [SEP] 문서 토큰들 [SEP] Transformer의 모든 layer에서 self-attention이 시퀀스 안의 모든 토큰 사이에 계산된다. → 질문 토큰과 문서 토큰이 처음부터 서로를 직접 참조 한다. 예: 질문의 데뷔 토큰이 문서의 2022년 , 데뷔하여 토큰에 직접 attention 할 수 있다. 마지막 layer의 [CLS] 출력 → linear layer → 관련도 점수(실수 1개). 이 방식을 interaction-based (상호작용 기반) 방식이라고 부른다. 한계 점수가 (질문, 문서) 쌍 에 대해서만 정의된다. 질문이 들어오기 전에는 아무것도 미리 계산할 수 없다. 문서가 N개면 모델 forward를 N번 해야 한다. 그래서 2단계로 나눈다 1단계: Bi-encoder로 전체 문서에서 상위 k개를 빠르게 추린다. 2단계: Cross-encoder로 그 k개만 정밀하게 다시 점수 매긴다. 비용 예시 후보 문서 N개 전체에 cross-encoder → 검색어 1개당 forward N번 1차 검색 top-50에만 cross-encoder → 검색어 1개당 forward 50번 5. 코드 파이썬: v3.8.16 transformers: v4.41.2 Dongjin-kr/ko-reranker · Hugging Face BAAI/bge-reranker-large 기반 한국어 데이터에 대한 fine-tuned model # 1. 모델과 데이터 처리에 필요한 라이브러리 불러오기 from transformers import AutoModelForSequenceClassification, AutoTokenizer import torch import numpy as np # 2. 비교할 문장 쌍 데이터 설정 (질문과 4개의 후보 답변들) pairs = [ ['뉴진스는 몇 년도에 데뷔했나요?', '안녕하세요.'], ['뉴진스는 몇 년도에 데뷔했나요?', '뉴진스의 데뷔 연도는 2022년입니다.'], ['뉴진스는 몇 년도에 데뷔했나요?', '2022년에는 뉴진스, 르세라핌, 엔믹스가 데뷔했습니다.'], ['뉴진스는 몇 년도에 데뷔했나요?', '뉴진스는 2022년에 데뷔하여 Hype boy 등 여러 노래를 발표했습니다.'], ] # 3. 로짓(모델의 원시 출력값)을 확률 분포(0~1)로 변환하는 정규화 함수 정의 def exp_normalize(x): b = x.max() # 수치적 안정성을 위해(오버플로우 방지) 최댓값 추출 y = np.exp(x - b) # 각 값에서 최댓값을 뺀 후 자연상수 e의 지수승 계산 return y / y.sum() # 모든 값의 합으로 나누어 총합이 1이 되는 확률 값으로 반환 # 4. 사전 학습된 한국어 Reranker 모델의 Hugging Face 저장소 경로 지정 model_path = "Dongjin-kr/ko-reranker" # 5. 지정된 경로에서 텍스트를 처리할 토크나이저와 모델 가중치 불러오기 tokenizer = AutoTokenizer.from_pretrained(model_path) model = AutoModelForSequenceClassification.from_pretrained(model_path) # 6. 모델을 추론(평가) 모드로 전환 (학습용 기능인 Dropout 등 비활성화) model.eval() # 7. 기울기(Gradient) 계산을 비활성화하여 메모리 사용량을 줄이고 속도 향상 with torch.no_grad(): # 문장 쌍을 모델이 이해할 수 있는 토큰 형태로 변환 (패딩 및 절단 적용, 최대 512토큰) inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512) # 모델에 토큰화된 입력을 통과시켜 연관성 점수(logits)를 계산하고, 1차원 실수 배열로 변환 scores = model(**inputs, return_dict=True).logits.view(-1, ).float() # 텐서를 NumPy 배열로 변환한 뒤, 직접 정의한 함수를 통해 0~1 사이의 확률값으로 정규화 scores = exp_normalize(scores.numpy()) # 8. 계산된 확률값에 100을 곱해 퍼센트(%)로 바꾸고, 소수점 둘째 자리까지 반올림하여 출력 print(np.round(scores * 100, 2)) pairs 의 각 원소가 [질문, 문서] 이고, tokenizer(pairs, ...) 가 이 둘을 하나의 시퀀스로 합친다. → 4개 쌍 = 시퀀스 4개 = cross-encoder 입력 4개. 6. 코드의 점수(logits)와 exp_normalize (softmax)의 의미 코드에서 실제로 일어나는 일 model(**inputs).logits → shape (4, 1) . 쌍 하나당 실수 1개. .view(-1) → shape (4,) 로 펴기. exp_normalize → 4개 값의 합이 1이 되도록 변환. 이 함수는 softmax 와 같은 계산이다. 포인트 1: 모델은 쌍마다 독립적으로 점수를 매긴다 (pointwise) 4개 쌍을 batch로 한 번에 넣었지만, self-attention은 각 시퀀스 내부에서만 계산된다. 즉 2번 쌍의 점수를 계산할 때 모델은 1·3·4번 문서를 전혀 보지 않는다. 이처럼 (질문, 문서 1개) → 점수 1개 구조를 pointwise 방식이라고 한다. 포인트 2: softmax 확률은 "후보들 사이의 상대값"이다 softmax는 모델이 계산을 끝낸 뒤에 적용하는 후처리다. 모델이 후보들을 서로 비교한 결과가 아니다. softmax 값은 같이 넣은 후보 집합에 따라 달라진다. 가상의 logit으로 계산한 예: 4개 후보 logit이 [-5, 5, 3, 4] 일 때 softmax → 약 [0.0%, 66.5%, 9.0%, 24.5%] 같은 2번 문서를 1번 문서( 안녕하세요. )와만 비교하면 logit [-5, 5] → 약 [0.0%, 100.0%] 2번 문서의 logit(5)은 그대로인데, 확률은 66.5% → 100%로 바뀐다. 따라서 softmax 확률은 "이 문서가 절대적으로 얼마나 관련 있는가"를 나타내지 않는다. 포인트 3: 순위만 필요하면 softmax는 없어도 된다 softmax는 큰 값이 항상 큰 값으로 남는 변환(단조 증가)이다. → softmax 전후의 순위는 같다. 재정렬 목적이면 logit을 그대로 정렬해도 결과가 같다. "점수 X 이상만 남긴다" 같은 절대 기준이 필요하면 softmax 확률이 아니라 logit 자체나 sigmoid(logit)을 기준으로 삼아야 한다. (어떤 변환을 쓰는지는 모델 카드 확인) 7.End-to-End PipeLine 질문 → 1단계 Bi-encoder로 top-k 후보 추출 → 2단계 Cross-encoder로 후보 재점수 → 상위 n개를 컨텍스트 앞쪽에 배치 → LLM 문서 임베딩은 질문이 들어오기 전에 미리 계산한다. (4장의 Bi-encoder 특징) 예제는 문서 수가 적어서 전체 문서와 내적을 계산했지만, 실제로는 벡터 DB와 ANN 검색을 사용한다. 리랭킹 단계는 softmax 없이 logit을 그대로 정렬한다. (6장 포인트 3) top_k (리랭커에 넘길 후보 수)와 top_n (LLM에 넘길 문서 수)은 다른 값이다. top_k 가 클수록 1차 검색에서 놓치는 문서가 줄지만, 리랭커 forward 횟수가 늘어난다. top_n 이 작을수록 LLM에 전달되는 컨텍스트가 줄어든다. from sentence_transformers import SentenceTransformer from transformers import AutoModelForSequenceClassification, AutoTokenizer import torch import numpy as np # 문서 저장소 documents = [ '안녕하세요.', '뉴진스의 데뷔 연도는 2022년입니다.', '2022년에는 뉴진스, 르세라핌, 엔믹스가 데뷔했습니다.', '뉴진스는 2022년에 데뷔하여 Hype boy 등 여러 노래를 발표했습니다.', '르세라핌은 2022년 5월에 데뷔했습니다.', '아이브는 2021년에 데뷔했습니다.', ] query = '뉴진스는 몇 년도에 데뷔했나요?' # 1단계: Bi-encoder 1차 검색 embedder = SentenceTransformer('BAAI/bge-m3') doc_embs = embedder.encode(documents, normalize_embeddings=True) # 질문과 무관하게 미리 계산 query_emb = embedder.encode(query, normalize_embeddings=True) sims = doc_embs @ query_emb # 정규화된 벡터의 내적 = 코사인 유사도 top_k = 4 candidate_idx = np.argsort(-sims)[:top_k] candidates = [documents[i] for i in candidate_idx] # 2단계: Cross-encoder 리랭킹 model_path = 'Dongjin-kr/ko-reranker' tokenizer = AutoTokenizer.from_pretrained(model_path) reranker = AutoModelForSequenceClassification.from_pretrained(model_path) reranker.eval() pairs = [[query, doc] for doc in candidates] with torch.no_grad(): inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512) logits = reranker(**inputs, return_dict=True).logits.view(-1).float().numpy() top_n = 2 order = np.argsort(-logits) # logit 그대로 내림차순 정렬 reranked = [candidates[i] for i in order[:top_n]] # 3단계: 관련도 높은 문서를 컨텍스트 앞쪽에 배치하여 LLM에 전달 context = '\n'.join(f'[문서 {i + 1}] {doc}' for i, doc in enumerate(reranked)) prompt = f"""다음 문서를 참고하여 질문에 답하세요. {context} 질문: {query} 답변:""" # answer = call_llm(prompt) # 사용하는 LLM API로 대체 print(prompt) 8. Pointwise / Pairwise / Listwise Pointwise: (질문, 문서 1개) → 점수. 5장의 코드가 이 방식. Pairwise: (질문, 문서 A, 문서 B) → A와 B 중 어느 쪽이 나은가. Listwise: (질문, 문서 여러 개를 한 입력에) → 순서 전체. 모델이 입력 안에서 후보들을 직접 비교 한다. 5장 코드의 softmax는 겉보기에 후보를 비교하는 것처럼 보이지만, 비교는 모델 밖에서 사후에 일어난 것이다. Listwise는 비교가 모델 안에서 일어난다는 점이 다르다. 방식 평가 메커니즘 특징 장단점 Pointwise (포인트와이즈) 하나의 쿼리와 단 하나의 문서 를 독립적으로 비교하여 점수를 매김 주로 Cross-Encoder 모델 사용 ➕ 속도가 빠르고 병렬 처리가 쉬움 ➖ 다른 문서들과의 상대적 우위를 반영하지 못함 Pairwise (페어와이즈) 하나의 쿼리에 대해 두 개의 문서 를 짝지어 어느 쪽이 더 우수한지 비교 토너먼트 형식의 비교 가능 ➕ 상대적 비교가 가능해짐 ➖ 문서 수가 많아질수록 비교 횟수가 제곱에 비례하여 증가 Listwise (리스트와이즈) 하나의 쿼리와 전체 후보 문서 목록 을 동시에 입력받아 최종 순열(Permutation)을 한 번에 출력 최신 고성능 모델(LLM 기반)에서 주로 차용 ➕ 문서 간 중복성, 다양성, 미세한 품질 차이를 가장 잘 포착함 ➖ 연산 비용이 크고 실시간 처리 속도(Latency)가 느림 Listwise 관련: ZipRerank Pointwise와 Listwise의 처리 방식 💡 처리 방식(병렬이냐 한 번이냐)은 결과로 따라오는 특징이다. 본질적인 차이는 "모델이 다른 후보를 보느냐"이다. Pointwise: 병렬로 처리"해야" 하는 게 아니라, 병렬로 처리"할 수 있다" 후보마다 계산이 독립적이다. → 순서대로 10번 돌려도, 동시에 돌려도 결과가 같다. 실무에서는 속도 때문에 batch로 묶어서 한 번에 처리한다. 5장 코드에서 pairs 4개를 tokenizer(pairs, ...) 로 한꺼번에 넣은 것이 batch 처리다. batch로 묶여 있어도 각 쌍은 서로를 보지 않는다. 함께 넣는 것은 GPU를 효율적으로 쓰기 위한 것일 뿐이다. Listwise: 기본은 한 번, 후보가 많으면 여러 번 후보가 10개 정도면 호출 1번으로 끝난다. 후보가 입력 길이 한도를 넘으면 여러 번 나눠서 호출한다. 예: 후보 50개 → 뒤쪽 20개를 먼저 정렬 → 창을 앞으로 옮기며 다시 정렬 (sliding window) 각 호출이 앞 호출의 결과를 이어받으므로 순서대로 처리해야 한다. 병렬 처리가 안 된다. 정리 Pointwise 본질: 후보 하나씩 따로 판단 호출: K번. 서로 독립적이라 병렬 가능 출력: 후보마다 점수 Listwise 본질: 후보 여러 개를 같이 보고 판단 호출: 기본 1번. 후보가 많으면 여러 번 순차 처리 출력: 순서 "한 번에 처리" ≠ "빠르다" Listwise는 호출이 1번이어도 느릴 수 있다. 후보 전부가 들어가므로 입력이 길다. 순서를 token 하나씩 생성한다. Pointwise는 호출이 여러 번이어도 병렬로 돌리면 전체 시간이 짧을 수 있다. 어느 쪽이 빠른지는 모델 크기, 후보 수, 서빙 환경에 따라 달라진다. Listwise와 Lost in the Middle 여러 문서를 한 입력에 넣는 listwise 리랭커 자체도 입력 중간의 후보를 덜 보는 같은 문제를 겪을 수 있다. → 후보 순서를 바꿔 여러 번 돌리거나, 작은 묶음 단위로 나눠 정렬하는 방식으로 대응한다. 마무리 리랭커는 전체 문서를 정밀하게 보는 비용과 1차 검색의 정보 손실 사이에서, 상위 k개만 다시 보는 절충 지점이다. 관련 문서를 컨텍스트 앞쪽으로 옮겨 Lost in the Middle을 피해 간다. 참고 자료 한국어 Reranker를 활용한 검색 증강 생성(RAG) 성능 올리기 | Amazon Web Services Lost in the Middle: How Language Models Use Long Contexts (Liu et al., 2023) CH11 리랭커(Reranker) Very Efficient Listwise Multimodal Reranking for Long Documents <랭체인LangChain 노트> - LangChain 한국어 튜토리얼🇰🇷 RAG 시스템 성능을 한 단계 끌어올리는 재순위 지정 모델(Reranker) 완벽 가이드
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
리랭커(Reranker), 순위 재정렬. 리랭커란, RAG가 생성한 후보 문서들에 대해 질문에 대한 관련성 및 일관성을 판단하여 문서의 우선 순위를 재정렬하는 것 즉, 질문과 관련성 있는 문서들을 컨텍스트의 상위권에 위치시킴으로써, 답변의 정확도를 올림 1. 기존 RAG에서의 문제 RAG는 문서에서 의미론적 검색(Semantic search) 과정을 수행함 → 벡터 검색 이 과정에서 발생되는 정보 손실 문서의 임베딩 벡터 변환 과정에서 손실 : 문서가 긴 경우에 정해진 벡터의 차원으로 표현하기 어려움 검색 과정에서 손실 : 시간 단축을 위해 ANNs 기술을 사용함 (Approximate Nearest Neighbor search) → 이러한 문제를 해결하기 위해 검색 후 반환되는 문서 수를 늘림 (k…
Open source