배경
- rank_bm25.py를 분석해서 진짜 bm25의 원리를 알고자함
이론적 배경
1. BM25 변형 알고리즘의 상세 비교 및 사용 사례
(1) BM25Okapi
- 설명:
- BM25의 기본 변형으로 가장 널리 사용되는 알고리즘.
- 문서 길이와 단어 빈도를 보정하여 검색 점수를 계산.
- (b) 파라미터로 긴 문서와 짧은 문서 간의 점수 균형을 맞추며, (k1) 파라미터로 단어 빈도(TF)의 민감도를 조정.
- 공식:
- IDF(역문서빈도) : **IDF(Inverse Document Frequency)**를 사용하여 특정 단어가 전체 문서에서 얼마나 중요한지를 측정
- TF : 단어 빈도(해당 단어가 문서에서 나타난 횟수).
- k1 : 의 기여도를 조정하는 파라미터로, TF에 대해 얼마나 민감하게 반응할지를 결정.
- b : 문서 길이 보정 파라미터 ((b = 0): 길이 보정 없음, (b = 1): 완전 보정).
- avgL : 평균길이
- IDF(역문서빈도) : **IDF(Inverse Document Frequency)**를 사용하여 특정 단어가 전체 문서에서 얼마나 중요한지를 측정
- 장점:
- 계산이 단순하고 대부분의 데이터에서 안정적으로 작동.
- 검색 엔진, 텍스트 기반 추천 시스템에 적합.
- 단점:
- 짧은 문서가 과도하게 높은 점수를 받을 가능성이 있음.
- 짧은 문서의 길이 L가 평균 길이 avgL보다 작으면, 문서 점수가 길이에 의해 더 크게 보정됩니다.
- 반면, 긴 문서는 L 값이 커져 점수가 감소하는 경향이 있습니다.
- 짧은 문서가 과도하게 높은 점수를 받을 가능성이 있음.
- 사용 사례
- 일반 검색 엔진: Google, Bing과 같은 텍스트 검색 시스템.
- 기본 정보 검색: 간단한 문서 검색, 제품 추천 등.
(2) BM25L
- 설명:
- 짧은 문서가 높은 점수를 받는 문제를 해결하기 위해 설계.
- (\delta) 파라미터를 추가하여 짧은 문서의 점수를 완화.
- 뉴스, 트윗 등 짧은 텍스트와 긴 텍스트가 혼재된 데이터에서 특히 유용.
- 공식:
- (\delta): 짧은 문서 점수를 조정하는 파라미터.
- 장점:
- 짧은 문서와 긴 문서 간의 점수 균형을 유지.
- 트위터, 블로그 게시물 등 짧은 텍스트에 적합.
- 단점:
- (\delta) 값을 적절히 설정하지 않으면 점수 분포 왜곡 가능.
- 사용 사례:
- 소셜 미디어 분석: 트윗, 인스타그램 게시물 검색.
- 뉴스 검색: 뉴스 기사와 헤드라인 검색.
(3) BM25Plus
- 설명:
- 모든 문서에 최소 점수를 추가로 부여하여 점수 분포를 확장.
- (\delta)를 이용해 관련성이 낮은 문서에도 기본 점수를 부여.
- 검색 결과의 다양성을 확보하려는 시스템에서 유용.
- 공식:
- 장점:
- 관련성이 낮은 문서에도 최소 점수를 제공하여 검색 결과 다양성 향상.
- 다양한 데이터에서 안정적으로 작동.
- 단점:
- (\delta) 값이 크면 비관련 문서가 과대평가될 가능성.
- 사용 사례:
- 이커머스 추천 시스템: 구매 가능성이 낮은 제품도 추천 목록에 포함.
- 대학 논문 검색: 관련 논문뿐만 아니라 비슷한 분야의 논문도 추천.
(4) BM25Adpt
- 설명:
- (k1) 값을 문서 상황에 따라 동적으로 조정하여 점수를 계산.
- 도메인 특화 데이터(예: 의료 기록, 법률 문서)에서 특히 효과적.
- 공식:
- 장점:
- 문서 특성에 맞게 동적 조정 가능.
- 복잡한 도메인에 적합.
- 단점:
- 동적 (k1) 계산 기준이 복잡.
- 동적 (k1) 계산 기준이 복잡.
- 사용 사례:
- 의료 데이터 분석: 환자 기록 검색.
- 법률 문서 검색: 계약서, 판례 검색.
(5) BM25T
- 설명:
- 단어별로 서로 다른 (k1) 값을 계산하여 점수를 반영.
- 단어별 중요도를 반영하는 고도화된 검색 시스템에 적합.
- 공식:
- 장점:
- 단어별 중요도를 세밀히 조정 가능.
- 특정 키워드 중심 검색에서 높은 성능 발휘.
- 단점:
- 계산 비용 증가.
- 단어별 (k1) 설정이 까다로움.
예>
- 사용 사례:
- 과학 논문 검색: 특정 주제나 용어 중심의 검색.
- 기술 문서 분석: 특정 기술 용어가 포함된 문서 검색.
2. 알고리즘 비교 표 (세부설명 추가)
특징/알고리즘 | BM25Okapi | BM25L | BM25Plus | BM25Adpt | BM25T |
---|---|---|---|---|---|
주요 목표 | 기본 BM25 알고리즘 | 짧은 문서의 점수 과대평가 방지 | 문서 간 최소 점수 제공 | 특정 상황에서 (k1) 값 동적 조정 | 단어별로 중요도를 다르게 반영 |
점수 보정 방식 | 문서 길이와 단어 빈도를 보정 | 문서 길이 보정 + 짧은 문서 점수 완화 | 문서 길이 보정 + 모든 문서에 일정 점수 추가 | 문서 특성을 반영해 (k1) 값 동적으로 조정 | 단어별 (k1) 값을 계산하여 점수에 반영 |
추가 파라미터 | (k1, b) | (k1, b, \delta) | (k1, b, \delta) | (k1, b, \delta) | (k1, b, \delta) |
복잡성 | 낮음 | 중간 | 중간 | 중간 | 높음 |
문서 길이 반영 | (b)를 사용한 문서 길이 보정 | (b)와 (\delta)를 활용하여 점수 균형 조정 | (b) 보정 후 모든 문서에 (\delta) 추가 | 문서 길이와 빈도를 기반으로 점수를 세밀히 조정 | 문서 길이와 빈도를 단어별로 조정 |
장점 | 간단하고 빠르며 안정적 | 짧은 문서와 긴 문서 간의 점수 균형을 맞춤 | 비관련 문서에도 최소 점수를 부여 가능 | 점수를 상황에 따라 동적으로 조정 가능 | 단어별 중요도를 세밀히 반영 |
단점 | 짧은 문서가 과대 평가될 수 있음 | 파라미터 조정이 어려움 | (\delta) 값이 크면 비관련 문서도 높은 점수 | 동적 조정 기준이 복잡 | 계산 비용 증가 및 단어별 기준 설정 필요 |
적합한 데이터 | 일반 텍스트 데이터 (검색 엔진, 문서 요약 등) | 뉴스 기사, 트윗 등 짧은 문서와 긴 문서 혼재 | 모든 문서가 일정 수준 점수를 가져야 하는 경우 | 도메인 특화 문서 (법률, 의료, 학술 논문 등) | 키워드 중심의 고도화된 검색 시스템 |
3. 결론
선택 기준
- 일반적인 검색:
- BM25Okapi가 적합 (간단하고 효율적).
- 짧은 텍스트와 긴 텍스트가 혼재된 데이터:
- BM25L을 추천.
- 검색 결과 다양성을 강조:
- BM25Plus가 적합.
- 도메인 특화 데이터:
- BM25Adpt로 동적 조정을 활용.
- 단어 중요도를 강조:
- BM25T로 세밀한 조정 가능.
더 자세한 상황별 적용 사례 : https://makenow90.tistory.com/103
코드
import math
import numpy as np
from multiprocessing import Pool, cpu_count
"""
이 코드는 Trotman et al., "Improvements to BM25 and Language Models Examined" 논문에서 제안된 BM25 알고리즘과
그 변형(BM25Okapi, BM25L, BM25Plus)을 구현합니다. BM25 알고리즘은 문서와 쿼리 간의 관련성을 계산하기 위해
사용됩니다. 각 변형은 검색 성능 향상을 목표로 추가적인 개선점을 제공합니다.
"""
# BM25의 기본 클래스
class BM25:
def __init__(self, corpus, tokenizer=None):
"""
BM25 클래스 초기화.
Args:
corpus: 말뭉치(문서 리스트). 각 문서는 단어 리스트로 구성됨.
tokenizer: 선택적 토크나이저 함수. 문서를 토큰으로 변환하는 함수.
"""
self.corpus_size = 0 # 전체 문서의 개수
self.avgdl = 0 # 문서의 평균 길이
self.doc_freqs = [] # 각 문서에서 단어 빈도를 저장하는 리스트
self.idf = {} # 각 단어의 역문서빈도(IDF) 값
self.doc_len = [] # 각 문서의 길이를 저장하는 리스트
self.tokenizer = tokenizer # 선택적 토크나이저 함수
# 토크나이저가 제공되었으면, 말뭉치를 토큰화
if tokenizer:
corpus = self._tokenize_corpus(corpus)
# 초기화 및 IDF 계산
nd = self._initialize(corpus)
self._calc_idf(nd)
def _initialize(self, corpus):
"""
말뭉치를 초기화하고, 문서 빈도와 길이를 계산.
Args:
corpus: 말뭉치(문서 리스트).
Returns:
nd: 각 단어가 등장한 문서의 수를 저장하는 딕셔너리.
"""
nd = {} # 각 단어가 등장한 문서의 수
num_doc = 0 # 전체 단어 수
for document in corpus:
# 각 문서의 길이를 저장
self.doc_len.append(len(document))
num_doc += len(document)
# 단어 빈도 계산
frequencies = {}
for word in document:
if word not in frequencies:
frequencies[word] = 0
frequencies[word] += 1
# 문서 빈도 리스트에 추가
self.doc_freqs.append(frequencies)
# 각 단어가 등장한 문서 수(nd) 계산
for word, freq in frequencies.items():
try:
nd[word] += 1
except KeyError:
nd[word] = 1
# 전체 문서 수 증가
self.corpus_size += 1
# 문서의 평균 길이 계산
self.avgdl = num_doc / self.corpus_size
return nd
def _tokenize_corpus(self, corpus):
"""
말뭉치를 병렬로 토큰화.
Args:
corpus: 말뭉치(문서 리스트).
Returns:
tokenized_corpus: 토큰화된 문서 리스트.
"""
pool = Pool(cpu_count()) # CPU 코어 수만큼 병렬 처리
tokenized_corpus = pool.map(self.tokenizer, corpus) # 토크나이저를 각 문서에 적용
return tokenized_corpus
def _calc_idf(self, nd):
"""
IDF 계산. 하위 클래스에서 구현.
"""
raise NotImplementedError()
def get_scores(self, query):
"""
쿼리에 대한 점수 계산. 하위 클래스에서 구현.
"""
raise NotImplementedError()
def get_batch_scores(self, query, doc_ids):
"""
특정 문서 집합에 대한 점수 계산. 하위 클래스에서 구현.
"""
raise NotImplementedError()
def get_top_n(self, query, documents, n=5):
"""
상위 n개의 문서를 반환.
Args:
query: 검색할 쿼리(단어 리스트).
documents: 전체 문서 리스트.
n: 반환할 문서의 수.
Returns:
상위 n개의 문서 리스트.
"""
assert self.corpus_size == len(documents), "문서 리스트가 초기화된 말뭉치와 일치하지 않습니다!"
# 쿼리에 대한 점수를 계산하고, 상위 n개의 문서를 반환
scores = self.get_scores(query)
top_n = np.argsort(scores)[::-1][:n] # 점수를 기준으로 내림차순 정렬
return [documents[i] for i in top_n]
# BM25의 표준 구현 (BM25Okapi)
class BM25Okapi(BM25):
def __init__(self, corpus, tokenizer=None, k1=1.5, b=0.75, epsilon=0.25):
"""
BM25Okapi 초기화.
Args:
corpus: 말뭉치(문서 리스트).
tokenizer: 선택적 토크나이저 함수.
k1: TF에 대한 가중치(기본값: 1.5).
b: 문서 길이 보정 파라미터(기본값: 0.75).
epsilon: 음수 IDF 방지를 위한 하한값.
"""
self.k1 = k1 # TF 가중치
self.b = b # 문서 길이 보정 파라미터
self.epsilon = epsilon # 음수 IDF 하한값
super().__init__(corpus, tokenizer)
def _calc_idf(self, nd):
"""
IDF(역문서빈도) 계산.
음수 IDF를 방지하기 위해 epsilon 값을 사용.
Args:
nd: 각 단어가 등장한 문서 수.
"""
idf_sum = 0
negative_idfs = [] # 음수 IDF를 가진 단어 리스트
for word, freq in nd.items():
idf = math.log(self.corpus_size - freq + 0.5) - math.log(freq + 0.5)
self.idf[word] = idf
idf_sum += idf
if idf < 0:
negative_idfs.append(word)
# 평균 IDF 계산 및 epsilon 설정
self.average_idf = idf_sum / len(self.idf)
eps = self.epsilon * self.average_idf
# 음수 IDF를 epsilon 값으로 설정
for word in negative_idfs:
self.idf[word] = eps
def get_scores(self, query):
"""
쿼리에 대한 점수 계산.
Args:
query: 검색할 쿼리(단어 리스트).
Returns:
각 문서의 점수 배열.
"""
score = np.zeros(self.corpus_size) # 문서 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len) # 문서 길이 배열
for q in query:
# 각 문서에서 쿼리 단어의 빈도 계산
q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs])
# BM25 공식에 따라 점수 계산
score += (self.idf.get(q) or 0) * (q_freq * (self.k1 + 1) /
(q_freq + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)))
return score
def get_batch_scores(self, query, doc_ids):
"""
특정 문서 집합에 대해 쿼리 점수 계산.
Args:
query: 검색할 쿼리(단어 리스트).
doc_ids: 점수를 계산할 문서 ID 리스트.
Returns:
각 문서의 점수 리스트.
"""
assert all(di < len(self.doc_freqs) for di in doc_ids), "문서 ID가 유효하지 않습니다!"
score = np.zeros(len(doc_ids)) # 문서 ID 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len)[doc_ids] # 선택한 문서의 길이 배열
for q in query:
# 선택한 문서에서 쿼리 단어의 빈도 계산
q_freq = np.array([(self.doc_freqs[di].get(q) or 0) for di in doc_ids])
# BM25 공식에 따라 점수 계산
score += (self.idf.get(q) or 0) * (q_freq * (self.k1 + 1) /
(q_freq + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)))
return score.tolist()
def get_batch_scores(self, query, doc_ids):
"""
특정 문서 집합에 대해 쿼리 점수 계산.
Args:
query: 검색할 쿼리(단어 리스트).
doc_ids: 점수를 계산할 문서 ID 리스트.
Returns:
각 문서의 점수 리스트.
"""
assert all(di < len(self.doc_freqs) for di in doc_ids), "문서 ID가 유효하지 않습니다!"
score = np.zeros(len(doc_ids)) # 문서 ID 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len)[doc_ids] # 선택한 문서의 길이 배열
for q in query:
# 선택한 문서에서 쿼리 단어의 빈도 계산
q_freq = np.array([(self.doc_freqs[di].get(q) or 0) for di in doc_ids])
# BM25 공식에 따라 점수 계산
score += (self.idf.get(q) or 0) * (q_freq * (self.k1 + 1) /
(q_freq + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)))
return score.tolist()
class BM25L(BM25):
def __init__(self, corpus, tokenizer=None, k1=1.5, b=0.75, delta=0.5):
"""
BM25L 클래스 초기화
Args:
corpus: 말뭉치(문서 리스트)
tokenizer: 선택적 토크나이저 함수
k1: TF(단어 빈도)에 대한 가중치 (기본값: 1.5)
b: 문서 길이 보정을 위한 파라미터 (기본값: 0.75)
delta: 짧은 문서의 점수를 조정하기 위한 파라미터 (기본값: 0.5)
"""
self.k1 = k1 # TF 가중치
self.b = b # 문서 길이 보정 파라미터
self.delta = delta # 짧은 문서 점수를 보정하는 추가 파라미터
super().__init__(corpus, tokenizer)
def _calc_idf(self, nd):
"""
역문서빈도(IDF) 계산
Args:
nd: 단어별로 등장한 문서의 수
"""
for word, freq in nd.items():
# IDF 계산: 문서의 총 개수와 단어 등장 빈도를 기반으로 로그 값 계산
idf = math.log(self.corpus_size + 1) - math.log(freq + 0.5)
self.idf[word] = idf # 각 단어의 IDF 값을 저장
def get_scores(self, query):
"""
쿼리에 대한 BM25L 점수 계산
Args:
query: 검색할 쿼리(단어 리스트)
Returns:
각 문서의 점수 배열
"""
score = np.zeros(self.corpus_size) # 문서 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len) # 각 문서의 길이 배열
for q in query:
# 각 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs])
# 문서 길이를 보정한 TF 계산
ctd = q_freq / (1 - self.b + self.b * doc_len / self.avgdl)
# BM25L 점수 공식
score += (self.idf.get(q) or 0) * q_freq * (self.k1 + 1) * (ctd + self.delta) / \
(self.k1 + ctd + self.delta)
return score # 각 문서의 점수 배열 반환
def get_batch_scores(self, query, doc_ids):
"""
특정 문서 집합에 대해 BM25L 점수를 계산
Args:
query: 검색할 쿼리(단어 리스트)
doc_ids: 점수를 계산할 문서 ID 리스트
Returns:
각 문서의 점수 리스트
"""
# 입력된 문서 ID가 유효한지 확인
assert all(di < len(self.doc_freqs) for di in doc_ids)
score = np.zeros(len(doc_ids)) # 문서 ID 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len)[doc_ids] # 선택한 문서의 길이 배열
for q in query:
# 선택된 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(self.doc_freqs[di].get(q) or 0) for di in doc_ids])
# 문서 길이를 보정한 TF 계산
ctd = q_freq / (1 - self.b + self.b * doc_len / self.avgdl)
# BM25L 점수 공식
score += (self.idf.get(q) or 0) * q_freq * (self.k1 + 1) * (ctd + self.delta) / \
(self.k1 + ctd + self.delta)
return score.tolist() # 각 문서의 점수 리스트 반환
class BM25Plus(BM25):
def __init__(self, corpus, tokenizer=None, k1=1.5, b=0.75, delta=1):
"""
BM25Plus 클래스 초기화
Args:
corpus: 말뭉치(문서 리스트)
tokenizer: 선택적 토크나이저 함수
k1: TF(단어 빈도)에 대한 가중치 (기본값: 1.5)
b: 문서 길이 보정을 위한 파라미터 (기본값: 0.75)
delta: 추가적인 점수를 부여하기 위한 파라미터 (기본값: 1)
"""
self.k1 = k1 # TF 가중치
self.b = b # 문서 길이 보정 파라미터
self.delta = delta # 모든 문서에 일정 점수를 추가하는 파라미터
super().__init__(corpus, tokenizer)
def _calc_idf(self, nd):
"""
역문서빈도(IDF) 계산
Args:
nd: 단어별로 등장한 문서의 수
"""
for word, freq in nd.items():
# IDF 계산: 말뭉치 내 단어 희소성을 반영
idf = math.log((self.corpus_size + 1) / freq)
self.idf[word] = idf # 각 단어의 IDF 값을 저장
def get_scores(self, query):
"""
쿼리에 대한 BM25Plus 점수 계산
Args:
query: 검색할 쿼리(단어 리스트)
Returns:
각 문서의 점수 배열
"""
score = np.zeros(self.corpus_size) # 문서 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len) # 각 문서의 길이 배열
for q in query:
# 각 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs])
# BM25Plus 점수 공식
score += (self.idf.get(q) or 0) * (self.delta + (q_freq * (self.k1 + 1)) /
(self.k1 * (1 - self.b + self.b * doc_len / self.avgdl) + q_freq))
return score # 각 문서의 점수 배열 반환
def get_batch_scores(self, query, doc_ids):
"""
특정 문서 집합에 대해 BM25Plus 점수를 계산
Args:
query: 검색할 쿼리(단어 리스트)
doc_ids: 점수를 계산할 문서 ID 리스트
Returns:
각 문서의 점수 리스트
"""
# 입력된 문서 ID가 유효한지 확인
assert all(di < len(self.doc_freqs) for di in doc_ids)
score = np.zeros(len(doc_ids)) # 문서 ID 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len)[doc_ids] # 선택된 문서의 길이 배열
for q in query:
# 선택된 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(self.doc_freqs[di].get(q) or 0) for di in doc_ids])
# BM25Plus 점수 공식
score += (self.idf.get(q) or 0) * (self.delta + (q_freq * (self.k1 + 1)) /
(self.k1 * (1 - self.b + self.b * doc_len / self.avgdl) + q_freq))
return score.tolist() # 각 문서의 점수 리스트 반환
class BM25Adpt(BM25):
def __init__(self, corpus, k1=1.5, b=0.75, delta=1):
"""
BM25Adpt 초기화
Args:
corpus: 말뭉치(문서 리스트)
k1: TF(단어 빈도)에 대한 가중치의 초기값 (기본값: 1.5)
b: 문서 길이 보정 파라미터 (기본값: 0.75)
delta: 추가적인 점수를 부여하기 위한 파라미터 (기본값: 1)
"""
self.k1 = k1 # 기본 TF 가중치
self.b = b # 문서 길이 보정 파라미터
self.delta = delta # 추가 점수를 위한 파라미터
super().__init__(corpus) # BM25 기본 클래스 초기화
def _calc_idf(self, nd):
"""
역문서빈도(IDF) 계산
Args:
nd: 단어별로 등장한 문서의 수
"""
for word, freq in nd.items():
# IDF 계산: 말뭉치 내 단어 희소성을 반영
idf = math.log((self.corpus_size + 1) / freq)
self.idf[word] = idf # 각 단어의 IDF 값을 저장
def get_scores(self, query):
"""
쿼리에 대한 점수 계산
Args:
query: 검색할 쿼리(단어 리스트)
Returns:
각 문서의 점수 배열
"""
score = np.zeros(self.corpus_size) # 문서 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len) # 각 문서의 길이 배열
for q in query:
# 각 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs])
# 점수 계산: BM25Adpt는 특정 조건에 따라 \(k1\) 조정 가능
score += (self.idf.get(q) or 0) * (self.delta + (q_freq * (self.k1 + 1)) /
(self.k1 * (1 - self.b + self.b * doc_len / self.avgdl) + q_freq))
return score # 각 문서의 점수 배열 반환
class BM25T(BM25):
def __init__(self, corpus, k1=1.5, b=0.75, delta=1):
"""
BM25T 초기화
Args:
corpus: 말뭉치(문서 리스트)
k1: TF(단어 빈도)에 대한 가중치의 초기값 (기본값: 1.5)
b: 문서 길이 보정 파라미터 (기본값: 0.75)
delta: 추가적인 점수를 부여하기 위한 파라미터 (기본값: 1)
"""
self.k1 = k1 # 기본 TF 가중치
self.b = b # 문서 길이 보정 파라미터
self.delta = delta # 추가 점수를 위한 파라미터
super().__init__(corpus) # BM25 기본 클래스 초기화
def _calc_idf(self, nd):
"""
역문서빈도(IDF) 계산
Args:
nd: 단어별로 등장한 문서의 수
"""
for word, freq in nd.items():
# IDF 계산: 말뭉치 내 단어 희소성을 반영
idf = math.log((self.corpus_size + 1) / freq)
self.idf[word] = idf # 각 단어의 IDF 값을 저장
def get_scores(self, query):
"""
쿼리에 대한 점수 계산
Args:
query: 검색할 쿼리(단어 리스트)
Returns:
각 문서의 점수 배열
"""
score = np.zeros(self.corpus_size) # 문서 수만큼 점수 배열 초기화
doc_len = np.array(self.doc_len) # 각 문서의 길이 배열
for q in query:
# 각 문서에서 쿼리 단어의 빈도를 계산
q_freq = np.array([(doc.get(q) or 0) for doc in self.doc_freqs])
# 점수 계산: BM25T는 term-specific \(k1\) 값을 계산
score += (self.idf.get(q) or 0) * (self.delta + (q_freq * (self.k1 + 1)) /
(self.k1 * (1 - self.b + self.b * doc_len / self.avgdl) + q_freq))
return score # 각 문서의 점수 배열 반환
'langchain 공부' 카테고리의 다른 글
PromptTemplate 내부코드 분석 (0) | 2024.11.24 |
---|---|
BM25 알고리즘 선택 기준 및 상세 사례 (3) | 2024.11.21 |
BM25Retriever1 내부 코드 분석 (1) | 2024.11.19 |
ChatOllama 내부 코드 분석 2 (0) | 2024.11.18 |
ChatOllama 내부 코드 분석 1 (0) | 2024.11.17 |