LLM을 활용한 실전 AI 애플리케이션 개발
- 허정준
🪧검색 방식 조합해 성능 높이기
키워드 검색 방식
동일한 키워드가 많이 포함될수록 유사도를 높게 평가
- 장 : 관련성이 높은 검색 결과가 나올 확률이 높음
- 단 : 키워드를 포함하지 않는다면 의미가 상통하더라도 검색하지 못함
의미 검색 방식
의미가 가까우면 유사도를 높게 평가
- 장 : 키워드를 포함하지 않아도 의미가 상통하면 검색 가능
- 단 : 관련성이 낮은 검색 결과가 나올 확률이 있음
하이브리드 검색 방식 🎉
키워드 검색 방식 +의미 검색 방식
🫔 키워드 검색 방식 (BM25)
TF-IDF 와 유사한 통계 알고리즘
(TF-IDF : 단어의 빈도를 이용해서 카테고리화 + 많은 문서에 등장하는 단어의 중요도를 작게 설정)
간단 / 적은 계산량 / 뛰어난 성능
<BM25 수식>
TF-IDF
포화 효과
문서 길이
TF-IDF와의 공통점
- 특정 문서에 해당 토큰이 많이 나오면 중요도는 높아진다
- 전체 문서에 해당 토큰이 많이 나오면 중요도는 낮아진다
TF-IDF와의 차이점
- 포화 효과 : 특정 문서 내에서 토큰이 많이 나오면 무한정 중요도가 높아지는 것이 아닌 (K+1) 값 이상으로 높아지지 않는다.
- 문서 길이도 고려하여 중요도를 책정한다(짧은 문서일수록 토큰의 중요도 업)
🫔 상호 순위 조합 이해하기
하이브리드 검색 : 통계 기반 점수(ex BM25) + 임베딩 유사도 점수 ( ex 밀집 임베딩 방식)
두 방식의 합으로 점수를 산출해야 함
하지만 점수 분포가 다를 시 어느 한 쪽의 영향을 더 크게 받게 될 가능성이 존재함
문제 해결을 위한
"상호 순위 조합" Reciprocal Rank Fusion / RRF
: 각 점수에서의 순위를 활용해 점수를 산출
🪧하이브리드 검색 구현하기
밀집 임베딩 기반의 의미 검색에 BM25 검색을 더해 하이브리드 검색 구현하기
🫔 BM25 구현하기
예제 10.14 BM25 클래스 구현
import math
import numpy as np
from typing import List
from transformers import PreTrainedTokenizer
from collections import defaultdict
class BM25:
def __init__(self, corpus:List[List[str]], tokenizer:PreTrainedTokenizer):
self.tokenizer = tokenizer
self.corpus = corpus
self.tokenized_corpus = self.tokenizer(corpus, add_special_tokens=False)['input_ids']
self.n_docs = len(self.tokenized_corpus)
self.avg_doc_lens = sum(len(lst) for lst in self.tokenized_corpus) / len(self.tokenized_corpus)
self.idf = self._calculate_idf()
self.term_freqs = self._calculate_term_freqs()
def _calculate_idf(self):
idf = defaultdict(float)
for doc in self.tokenized_corpus:
for token_id in set(doc):
idf[token_id] += 1
for token_id, doc_frequency in idf.items():
idf[token_id] = math.log(((self.n_docs - doc_frequency + 0.5) / (doc_frequency + 0.5)) + 1)
return idf
def _calculate_term_freqs(self):
term_freqs = [defaultdict(int) for _ in range(self.n_docs)]
for i, doc in enumerate(self.tokenized_corpus):
for token_id in doc:
term_freqs[i][token_id] += 1
return term_freqs
def get_scores(self, query:str, k1:float = 1.2, b:float=0.75):
query = self.tokenizer([query], add_special_tokens=False)['input_ids'][0]
scores = np.zeros(self.n_docs)
for q in query:
idf = self.idf[q]
for i, term_freq in enumerate(self.term_freqs):
q_frequency = term_freq[q]
doc_len = len(self.tokenized_corpus[i])
score_q = idf * (q_frequency * (k1 + 1)) / ((q_frequency) + k1 * (1 - b + b * (doc_len / self.avg_doc_lens)))
scores[i] += score_q
return scores
def get_top_k(self, query:str, k:int):
scores = self.get_scores(query)
top_k_indices = np.argsort(scores)[-k:][::-1]
top_k_scores = scores[top_k_indices]
return top_k_scores, top_k_indices
get_scores() : 저장한 문서와 점수를 계산
get_top_k(): 상위 k 개의 점수와 인덱스를 추출
_calculate_idf() : 각 토큰이 몇 개의 문서에 등장하는지 집계
예제 10.15 BM25 점수 계산 확인해 보기
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('klue/roberta-base')
bm25 = BM25(['안녕하세요', '반갑습니다', '안녕 서울'], tokenizer)
bm25.get_scores('안녕')
# array([0.44713859, 0. , 0.52354835])
"안녕" 토큰이 겹치는 두 문서의 유사도가 측정.
의미가 통하는 반갑습니다는 0
예제 10.16 BM25 검색 결과의 한계
# BM25 검색 준비
bm25 = BM25(klue_mrc_dataset['context'], tokenizer)
query = "이번 연도에는 언제 비가 많이 올까?"
_, bm25_search_ranking = bm25.get_top_k(query, 100)
for idx in bm25_search_ranking[:3]:
print(klue_mrc_dataset['context'][idx][:50])
# 출력 결과
# 갤럭시S5 언제 발매한다는 건지언제는 “27일 판매한다”고 했다가 “이르면 26일 판매한다 (오답)
# 인구 비율당 노벨상을 세계에서 가장 많이 받은 나라, 과학 논문을 가장 많이 쓰고 의료 특 (오답)
# 올여름 장마가 17일 제주도에서 시작됐다. 서울 등 중부지방은 예년보다 사나흘 정도 늦은 (정답)
예제 10.17 BM25 검색 결과의 장점
query = klue_mrc_dataset[3]['question'] # 로버트 헨리 딕이 1946년에 매사추세츠 연구소에서 개발한 것은 무엇인가?
_, bm25_search_ranking = bm25.get_top_k(query, 100)
for idx in bm25_search_ranking[:3]:
print(klue_mrc_dataset['context'][idx][:50])
# 출력 결과
# 미국 세인트루이스에서 태어났고, 프린스턴 대학교에서 학사 학위를 마치고 1939년에 로체스 (정답)
# ;메카동(メカドン) (오답)
# :성우 : 나라하시 미키(ならはしみき)
# 길가에 버려져 있던 낡은 느티나
# ;메카동(メカドン) (오답)
# :성우 : 나라하시 미키(ならはしみき)
키워드 중심으로 의미가 통하는 건 잘 찾고 / 키워드가 적은 정답은 못 찾고
🫔 상호 순위 조합 구현하기
BM25의 순위와 의미 검색 순위를 조합하는 상호 순위 조합 함수 구현
예제 10.18 상호 순위 조합 함수 구현
from collections import defaultdict
def reciprocal_rank_fusion(rankings:List[List[int]], k=5):
rrf = defaultdict(float)
for ranking in rankings:
for i, doc_id in enumerate(ranking, 1):
rrf[doc_id] += 1.0 / (k + i)
return sorted(rrf.items(), key=lambda x: x[1], reverse=True)
각각의 순위 리스트를 순회하며
각각의 문서 인덱스에 1/(k+순위)의 점수 더하기
점수를 종합한 딕셔너리를 점수에 따라 높은 순으로 정렬해 반환함
rankings = [[1, 4, 3, 5, 6], [2, 1, 3, 6, 4]]
reciprocal_rank_fusion(rankings)
# [(1, 0.30952380952380953),
# (3, 0.25),
# (4, 0.24285714285714285),
# (6, 0.2111111111111111),
# (2, 0.16666666666666666),
# (5, 0.1111111111111111)]
1은 1/6 + 1/7
2는 1/8 + 1/8
...
🫔 하이브리드 검색 구현하기
예제 10.20 하이브리드 검색 구현하기
def dense_vector_search(query:str, k:int):
query_embedding = sentence_model.encode([query])
distances, indices = index.search(query_embedding, k)
return distances[0], indices[0]
def hybrid_search(query, k=20):
_, dense_search_ranking = dense_vector_search(query, 100)
_, bm25_search_ranking = bm25.get_top_k(query, 100)
results = reciprocal_rank_fusion([dense_search_ranking, bm25_search_ranking], k=k)
return results
입력받은 쿼리 문장을
1. 의미 검색, 2.BM25 키워드 검색 을 수행한 뒤
상호 순위 조합을 사용해 순위를 조합하고 결과를 반환함
예제 10.21 예시 데이터에 대한 하이브리드 검색 결과 확인
query = "이번 연도에는 언제 비가 많이 올까?"
print("검색 쿼리 문장: ", query)
results = hybrid_search(query)
for idx, score in results[:3]:
print(klue_mrc_dataset['context'][idx][:50])
print("=" * 80)
query = klue_mrc_dataset[3]['question'] # 로버트 헨리 딕이 1946년에 매사추세츠 연구소에서 개발한 것은 무엇인가?
print("검색 쿼리 문장: ", query)
results = hybrid_search(query)
for idx, score in results[:3]:
print(klue_mrc_dataset['context'][idx][:50])
# 출력 결과
# 검색 쿼리 문장: 이번 연도에는 언제 비가 많이 올까?
# 올여름 장마가 17일 제주도에서 시작됐다. 서울 등 중부지방은 예년보다 사나흘 정도 늦은 (정답)
# 갤럭시S5 언제 발매한다는 건지언제는 “27일 판매한다”고 했다가 “이르면 26일 판매한다 (오답)
# 연구 결과에 따르면, 오리너구리의 눈은 대부분의 포유류보다는 어류인 칠성장어나 먹장어, 그 (오답)
# ================================================================================
# 검색 쿼리 문장: 로버트 헨리 딕이 1946년에 매사추세츠 연구소에서 개발한 것은 무엇인가?
# 미국 세인트루이스에서 태어났고, 프린스턴 대학교에서 학사 학위를 마치고 1939년에 로체스 (정답)
# 1950년대 말 매사추세츠 공과대학교의 동아리 테크모델철도클럽에서 ‘해커’라는 용어가 처음 (오답)
# 1950년대 말 매사추세츠 공과대학교의 동아리 테크모델철도클럽에서 ‘해커’라는 용어가 처음 (오답)
'🤖 AI > AI' 카테고리의 다른 글
🐋 deepseek (0) | 2025.02.03 |
---|---|
🔬자신의 데이터에 맞춘 임베딩 모델 만들기 : RAG 개선하기 - 언어 모델을 임베딩 모델로 (0) | 2025.01.20 |
🔬임베딩 모델로 데이터 의미 압축하기 : 의미 검색 구현 (0) | 2025.01.20 |
🔬임베딩 모델로 데이터 의미 압축하기 : 문장 임베딩 (0) | 2025.01.17 |
🔬임베딩 모델로 데이터 의미 압축하기 : 텍스트 임베딩 (0) | 2025.01.17 |