โ๏ธ Summary
โ ํ ์คํธ์ ์๋ฏธ๋ฅผ ๋ด์ ์ซ์๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ : ์ํซ์ธ์ฝ๋ฉ, BoW, TF-IDF, Word2Vec(๋ฐ์ง์๋ฒ ๋ฉ)
โ ๋ฌธ์ฅ์๋ฒ ๋ฉ๋ฐฉ์ : ๊ต์ฐจ์ธ์ฝ๋, ๋ฐ์ด์ธ์ฝ๋(BERT,ํ๋ง์ธต), Sentence-Transformers
โ ์๋ฏธ๊ฒ์, ํค์๋๊ฒ์, ํ์ด๋ธ๋ฆฌ๋๊ฒ์
3. ์๋ฏธ๊ฒ์ ๊ตฌํํ๊ธฐ
3.1 ์๋ฏธ๊ฒ์๊ตฌํํ๊ธฐ
โ ์๋ฏธ๊ฒ์
โช๏ธ ๋จ์ํ ํค์๋ ๋งค์นญ์ ํตํ ๊ฒ์์ด ์๋๋ผ, ๋ฐ์ง ์๋ฒ ๋ฉ์ ์ด์ฉํด ๋ฌธ์ฅ์ด๋ ๋ฌธ์์ ์๋ฏธ๋ฅผ ๊ณ ๋ คํ ๊ฒ์์ ์ํํ๋ ๊ฒ
โ faiss
โช๏ธ ๋ฉํ๊ฐ ๊ฐ๋ฐํ ๋ฒกํฐ ์ฐ์ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ์ฝ์ฌ์ธ์ ์ฌ๋, ์ ํด๋ฆฌ๋ ๊ฑฐ๋ฆฌ ๋ฑ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ์ง์ํ ๋ฟ ์๋๋ผ, ๋ฒกํฐ ๊ฒ์ ์๋๋ฅผ ํฅ์ํด์ฃผ๋ ANN์๊ณ ๋ฆฌ์ฆ๋ ๋ค์ํ๊ฒ ์ ๊ณตํ๋ค.
โ ์ค์ต
โ 1) ๋ชจ๋ธ๊ณผ ๋ฐ์ดํฐ์ ๋ถ๋ฌ์ค๊ธฐ
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
# ๊ธฐ์ฌ ๋ณธ๋ฌธ๊ณผ ๊ธฐ์ฌ๋ณธ๋ฌธ์ ๊ด๋ จ๋ ์ง๋ฌธ์ ๋ชจ์ ๋ฐ์ดํฐ์
klue_mrc_dataset = load_dataset('klue', 'mrc', split='train')
# ํ๊ตญ์ด ์๋ฒ ๋ฉ ๋ชจ๋ธ
sentence_model = SentenceTransformer('snunlp/KR-SBERT-V40K-klueNLI-augSTS')
โ 2) ์ค์ต ๋ฐ์ดํฐ์์ 1000๊ฐ๋ง ์ ํํ๊ณ ๋ฌธ์ฅ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํ
klue_mrc_dataset = klue_mrc_dataset.train_test_split(train_size=1000, shuffle=False)['train']
# ๊ธฐ์ฌ ๋ณธ๋ฌธ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์๋ context์นผ๋ผ์ ๋ฌธ์ฅ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์
๋ ฅ์ผ๋ก ๋ฃ์ด ๋ฌธ์ฅ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํ
embeddings = sentence_model.encode(klue_mrc_dataset['context'])
embeddings.shape
# ์ถ๋ ฅ ๊ฒฐ๊ณผ
# (1000, 768)
โ 3) KNN ๊ฒ์ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๊ณ ๋ฌธ์ฅ ์๋ฒ ๋ฉ ์ ์ฅ
โช๏ธ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ํ ์ด๋ธ์ ์์ฑํ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
โช๏ธ ์๋ฏธ๊ฒ์์ ๊ตฌํํ๊ธฐ ์ํด์๋, ๊ฒ์ ์ฟผ๋ฆฌ ๋ฌธ์ฅ์ ๋ฌธ์ฅ์๋ฒ ๋ฉ์ผ๋ก ๋ณํํ๊ณ , ์ธ๋ฑ์ค์์ ๊ฒ์ํ๋ฉด ๋๋ค.
import faiss
# ์ธ๋ฑ์ค ๋ง๋ค๊ธฐ
index = faiss.IndexFlatL2(embeddings.shape[1])
# ์ธ๋ฑ์ค์ ์๋ฒ ๋ฉ ์ ์ฅํ๊ธฐ
index.add(embeddings)
โ 4) ์๋ฏธ๊ฒ์
โช๏ธ ๊ฒ์์ฟผ๋ฆฌ๋ฌธ์ฅ์ ๋ฌธ์ฅ์๋ฒ ๋ฉ ๋ชจ๋ธ์ encode ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ฌธ์ฅ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํํ๊ณ ์ธ๋ฑ์ค์ search ๋ฉ์๋๋ก ์ฟผ๋ฆฌ์๋ฒ ๋ฉ๊ณผ ๊ฐ์ฅ ๊ฐ๊น์ด 3๊ฐ ๋ฌธ์๋ฅผ ๋ฐํ๋ฐ๋๋ค.
query = "์ด๋ฒ ์ฐ๋์๋ ์ธ์ ๋น๊ฐ ๋ง์ด ์ฌ๊น?"
query_embedding = sentence_model.encode([query])
distances, indices = index.search(query_embedding, 3)
for idx in indices[0]:
print(klue_mrc_dataset['context'][idx][:50])
# ์ถ๋ ฅ ๊ฒฐ๊ณผ
# ์ฌ์ฌ๋ฆ ์ฅ๋ง๊ฐ 17์ผ ์ ์ฃผ๋์์ ์์๋๋ค. ์์ธ ๋ฑ ์ค๋ถ์ง๋ฐฉ์ ์๋
๋ณด๋ค ์ฌ๋ํ ์ ๋ ๋ฆ์ (์ ๋ต)
# ์ฐ๊ตฌ ๊ฒฐ๊ณผ์ ๋ฐ๋ฅด๋ฉด, ์ค๋ฆฌ๋๊ตฌ๋ฆฌ์ ๋์ ๋๋ถ๋ถ์ ํฌ์ ๋ฅ๋ณด๋ค๋ ์ด๋ฅ์ธ ์น ์ฑ์ฅ์ด๋ ๋จน์ฅ์ด, ๊ทธ (์ค๋ต)
# ์ฐ๊ตฌ ๊ฒฐ๊ณผ์ ๋ฐ๋ฅด๋ฉด, ์ค๋ฆฌ๋๊ตฌ๋ฆฌ์ ๋์ ๋๋ถ๋ถ์ ํฌ์ ๋ฅ๋ณด๋ค๋ ์ด๋ฅ์ธ ์น ์ฑ์ฅ์ด๋ ๋จน์ฅ์ด, ๊ทธ (์ค๋ต)
โ 5) ์๋ฏธ๊ฒ์์ ํ๊ณ
โช๏ธ ํค์๋๊ฐ ๋์ผํ์ง ์์๋, ์๋ฏธ๊ฐ ์ ์ฌํ๋ฉด ๋ด์ฉ์ด ๊ด๋ จ ์๋๋ผ๋ ๊ฒ์ ๊ฒฐ๊ณผ๋ก ๋์ฌ ์ ์๋ค.
query = klue_mrc_dataset[3]['question'] # ๋ก๋ฒํธ ํจ๋ฆฌ ๋์ด 1946๋
์ ๋งค์ฌ์ถ์ธ์ธ ์ฐ๊ตฌ์์์ ๊ฐ๋ฐํ ๊ฒ์ ๋ฌด์์ธ๊ฐ?
query_embedding = sentence_model.encode([query])
distances, indices = index.search(query_embedding, 3)
for idx in indices[0]:
print(klue_mrc_dataset['context'][idx][:50])
# ์ถ๋ ฅ ๊ฒฐ๊ณผ
# ํํ์ ์ ์ ์ค ๋ด๊ธฐ๋ ๋ฐฉ๋ฉด์์ ์ง๊ณต ์์ ์ ์ค์ํด ์จ ๋๊ธ๋ฌ์ค ๋งฅ์๋ ์ฅ๊ตฐ์ ์ฌ๋ น๊ด์ผ๋ก (์ค๋ต)
# ํํ์ ์ ์ ์ค ๋ด๊ธฐ๋ ๋ฐฉ๋ฉด์์ ์ง๊ณต ์์ ์ ์ค์ํด ์จ ๋๊ธ๋ฌ์ค ๋งฅ์๋ ์ฅ๊ตฐ์ ์ฌ๋ น๊ด์ผ๋ก (์ค๋ต)
# ๋ฏธ๊ตญ ์ธ์ธํธ๋ฃจ์ด์ค์์ ํ์ด๋ฌ๊ณ , ํ๋ฆฐ์คํด ๋ํ๊ต์์ ํ์ฌ ํ์๋ฅผ ๋ง์น๊ณ 1939๋
์ ๋ก์ฒด์ค (์ ๋ต)
โช๏ธ ๋ก๋ฒํธํจ๋ฆฌ๋์ด ๊ฐ๋ฐํ ๊ฒ์ ๋ํ ์ง๋ฌธ ์ฟผ๋ฆฌ์ ๋ํด, ๊ด๋ จ์๋ ๋ต๋ณ์ด ๋จผ์ ์์์ ๋์ค๋ ๊ฒฐ๊ณผ (ํค์๋๋ ๋์ผํ์ง ์์ง๋ง, ์๋ฏธ์ ์ ์ฌํ๋ค๊ณ ํ๋จ๋ ๊ฒฝ์ฐ) โ ์ด๋ฌํ ํ๊ณ๋ ํ์ด๋ธ๋ฆฌ๋๊ฒ์์ผ๋ก ๊ฐ์
3.2 ๋ผ๋ง์ธ๋ฑ์ค์์ Sentence-Transformers ๋ชจ๋ธ ์ฌ์ฉํ๊ธฐ
from llama_index.core import VectorStoreIndex, ServiceContext
from llama_index.core import Document
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# ํ๊น
ํ์ด์ค ๊ด๋ จ ํด๋์ค์ ์ ์ฅํด๋ ๋ชจ๋ธ์ ๋ถ๋ฌ์ค๊ธฐ
embed_model = HuggingFaceEmbedding(model_name="snunlp/KR-SBERT-V40K-klueNLI-augSTS")
service_context = ServiceContext.from_defaults(embed_model=embed_model, llm=None)
# ๋ก์ปฌ ๋ชจ๋ธ ํ์ฉํ๊ธฐ
# service_context = ServiceContext.from_defaults(embed_model="local")
text_list = klue_mrc_dataset[:100]['context']
documents = [Document(text=t) for t in text_list]
index_llama = VectorStoreIndex.from_documents(
documents,
service_context=service_context,
)
โช๏ธ ๋ผ๋ง์ธ๋ฑ์ค๋ Sentence-Transformers์ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ํตํฉํ ์ ์๋ ๊ธฐ๋ฅ์ ์ง์
4. ๊ฒ์ ๋ฐฉ์์ ์กฐํฉํด ์ฑ๋ฅ ๋์ด๊ธฐ
4.1 ํค์๋ ๊ฒ์๋ฐฉ์ : BM25
โ ํค์๋๊ฒ์
โช๏ธ ์๋ฏธ๊ฒ์๊ณผ ๋ฌ๋ฆฌ, ๋์ผํ ํค์๋๊ฐ ๋ง์ด ํฌํจ๋ ์๋ก ์ ์ฌ๋๋ฅผ ๋๊ฒ ํ๊ฐํ๋ ๊ฒ์๋ฐฉ์์ ๋งํ๋ค.
โช๏ธ ๊ด๋ จ์ฑ์ด ๋จ์ด์ง๋ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ๋ํ๋ ๊ฐ๋ฅ์ฑ์ด ๋ฎ๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ๋์ผํ ํค์๋๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์๋ฏธ๊ฐ ์ ์ฌํ๋๋ผ๋ ๊ฒ์ํ์ง ๋ชปํ๋ค๋ ๋จ์ ์ด ์๋ค. ๋ฐ๋ผ์ ์ฃผ๋ก ์๋ฏธ๊ฒ์๊ณผ ํค์๋๊ฒ์์ ์กฐํฉํ ํ์ด๋ธ๋ฆฌ๋๊ฒ์์ ํ์ฉํ๋ค.
โ BM25
โช๏ธ TF-IDF์ ์ ์ฌํ ํต๊ณ๊ธฐ๋ฐ ์ค์ฝ์ด๋ง ๋ฐฉ๋ฒ์ผ๋ก, TF-IDF์ ๋ฌธ์์ ๊ธธ์ด์ ๋ํ ๊ฐ์ค์น๋ฅผ ์ถ๊ฐํ ์๊ณ ๋ฆฌ์ฆ์ด๋ค.
โช๏ธ ๊ฐ๋จํ๊ณ ๊ณ์ฐ๋์ด ์ ์ผ๋ฉฐ, ๋ฐ์ด๋ ์ฑ๋ฅ์ ๋ณด์ฌ ๋ํ์ ์ธ ๊ฒ์์์ง์ธ Elasticsearch์ ๊ธฐ๋ณธ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ฌ์ฉ๋๊ธฐ๋ ํ๋ค.

โช๏ธ 1) ํฌํํจ๊ณผ ๊ณ ๋ ค : ํน์ ๋ฌธ์ ๋ด์ ํ ํฐ์ด ์์ฃผ ๋์ค๋๋ผ๋ TF ํญ์ด ์ผ์ ๊ฐ ์ด์์ผ๋ก ์ปค์ง์ง ์๋๋ค.
โช๏ธ 2) ๋ฌธ์ ๊ธธ์ด ๊ณ ๋ ค : ์งง์ ๋ฌธ์์ ํ ํฐ q๊ฐ ๋ฑ์ฅํ ๊ฒฝ์ฐ, ๋ ์ค์๋๋ฅผ ๋๊ฒ ํ๋จํ๋ค.
4.2 ์ํธ์์์กฐํฉ ์ดํดํ๊ธฐ
โ ์ํธ์์์กฐํฉ

โช๏ธ ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์์ ์ํด์๋ ํต๊ณ๊ธฐ๋ฐ ์ ์์ ์๋ฒ ๋ฉ ์ ์ฌ๋ ์ ์๋ฅผ ํ๋๋ก ํฉ์ณ์ผ ํ๋ค. ๊ทธ๋ฌ๋, ์ ์๋ง๋ค ๋ถํฌ๊ฐ ๋ค๋ฅด๋ฏ๋ก ์ด๋ฅผ ๋ง์ถฐ์ฃผ๋ ๊ฒ์ด ํ์ํ๋ค.
โช๏ธ ์ํธ์์์กฐํฉ์ ๊ฐ ์ ์์์์ '์์'๋ฅผ ํ์ฉํด ์ ์๋ฅผ ์ฐ์ถํ๋ค. ์์์ ๋ฐ๋ผ ์ ์ (1/(k+์์)) ๋ฅผ ๋ถ์ฌํ๋ค. k๋ ๊ฐ ๋ชจ๋ธ์์ ๊ณ ๋ คํ ์์์ด๋ค. (๊ต์ฌ 10.17 ๊ทธ๋ฆผ ์ฐธ๊ณ ํ๊ธฐ)
5. ํ์ด๋ธ๋ฆฌ๋๊ฒ์๊ตฌํ
5.1 BM25๊ตฌํํ๊ธฐ
โ Class์ ์
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 : ์ ์ ๊ณ์ฐ
โช๏ธ idf์ term_freqs๋ฅผ ํตํด, ๊ฒ์ํ๋ ค๋ ์ฟผ๋ฆฌ์ ๊ฐ ๋ฌธ์ ์ฌ์ด์ ์ ์๋ฅผ ๊ณ์ฐ
โช๏ธ get_top_k : ์์ k๊ฐ์ ์ ์์ ์ธ๋ฑ์ค ์ถ์ถ
โช๏ธ ์ฟผ๋ฆฌ์ ๋ฌธ์ ์ฌ์ด์ ๊ฒ์๊ฐ ๊ฐ์ฅ ๋์ k๊ฐ์ ๋ฌธ์์ ์ธ๋ฑ์ค์ ์ ์๋ฅผ ๋ฐํ
โช๏ธ _calculate_term_freqs : ๊ฐ ํ ํฐ์ด ๊ฐ ๋ฌธ์ ๋ด์์ ๋ช ๋ฒ ๋ฑ์ฅํ๋์ง ์ง๊ณ
โช๏ธ self.n_docs ๋ฌธ์์ ๋งํผ์ ๋์ ๋๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ๊ฐ ๋ฌธ์ ๋ด์ ์ด๋ค ํ ํฐ์ด ๋ช ๋ฒ ๋ฑ์ฅํ๋์ง ์ง๊ณํ๋ค.
โ 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์ด ๋๋ค.
โ 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์ผ ์ ์ฃผ๋์์ ์์๋๋ค. ์์ธ ๋ฑ ์ค๋ถ์ง๋ฐฉ์ ์๋
๋ณด๋ค ์ฌ๋ํ ์ ๋ ๋ฆ์ (์ ๋ต)
โช๏ธ ์ง๋ฌธ์ ๋ํด, ์ ๋ต์ด ๋๋ ๋ฌธ์ฅ์ ์ธ๋ฒ์งธ๋ก ์ถ๋ ฅํ๋ค. ๊ฒ์ ์ฟผ๋ฆฌ ๋ฌธ์ฅ๊ณผ ์ ๋ต ๊ธฐ์ฌ ์ฌ์ด์ ์ผ์นํ๋ ํค์๋๊ฐ ์ ์ด ๊ฐ์ฅ ๋จผ์ ๊ฒ์๋์ง ์์ ๊ฒ์ด๋ค.
โ 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์ ์ผ์นํ๋ ํค์๋ ๋ฐํ์ ๊ด๋ จ ๊ธฐ์ฌ ๊ฒ์์ ์ฅ์ ์ ์ ๋ณด์ฌ์ค๋ค.
5.2 ์ํธ์์์กฐํฉ ๊ตฌํํ๊ธฐ
โ ์ํธ์์์กฐํฉํจ์๊ตฌํ
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) # ์ ์๋ฅผ ์ข
ํฉํ ๋์
๋๋ฆฌ๋ฅผ ์ ์์ ๋ฐ๋ผ ๋์ ์์ผ๋ก ์ ๋ ฌํด ๋ฐํ
โช๏ธ reciprocal_rank_fusion : ๊ฐ ๊ฒ์ ๋ฐฉ์์ผ๋ก ๊ณ์ฐํด ์ ํด์ง ๋ฌธ์์ ์์๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ์, ์ํธ ์์ ์กฐํฉ ์ ์๊ฐ ๋์ ์๋๋ก ์ ๋ ฌํด ๋ฐํ
โช๏ธ rankings ์ธ์ : ์ฌ๋ฌ ๊ฒ์ ๋ฐฉ์์์ ์ ํด์ง ์ ์ฌํ ๋ฌธ์์ ์ธ๋ฑ์ค ๋ฆฌ์คํธ๋ฅผ ์ ๋ ฅ์ผ๋ก ๋ฐ๋๋ค.
โช๏ธ ์ฌ๋ฌ ๊ฒ์ ๋ฐฉ์์ ์ ์๋ฅผ ์ข ํฉํ๊ณ ๋์ ์ ์๋ฅผ ๋ฐ์ ์์๋๋ก ์ ๋ ฌํด ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
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)]
โช๏ธ ์์ ๋ฐ์ดํฐ๋ก ํจ์์ ๊ตฌํ ๊ฒฐ๊ณผ ํ์ธ
โ ํ์ด๋ธ๋ฆฌ๋๊ฒ์ ๊ตฌํํ๊ธฐ
# ์๋ฏธ ๊ฒ์์์ ๋ฐ๋ณต์ ์ผ๋ก ์ํํ๋ ๊ฒ์์ฟผ๋ฆฌ ๋ฌธ์ฅ ์๋ฒ ๋ฉ ๋ณํ๊ณผ ์ธ๋ฑ์ค ๊ฒ์ ๋ถ๋ถ์ ํ๋ฒ์ ์ํํ ์ ์๋๋ก ์ ์ํ ํจ์
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]
# ๊ฒ์ ์ฟผ๋ฆฌ ๋ฌธ์ฅ๊ณผ ์ํธ ์์ ์กฐํฉ์ ์ฌ์ฉํ ํ๋ผ๋ฏธํฐ k๋ฅผ ์
๋ ฅ์ผ๋ก ๋ฐ์
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
โ ์์๋ฐ์ดํฐ์ ๋ํ ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์ ๊ฒฐ๊ณผ ํ์ธ
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๋
๋ ๋ง ๋งค์ฌ์ถ์ธ์ธ ๊ณต๊ณผ๋ํ๊ต์ ๋์๋ฆฌ ํ
ํฌ๋ชจ๋ธ์ฒ ๋ํด๋ฝ์์ ‘ํด์ปค’๋ผ๋ ์ฉ์ด๊ฐ ์ฒ์ (์ค๋ต)
โช๏ธ ํ์ด๋ธ๋ฆฌ๋ ๊ฒ์์ ์ฌ์ฉํ๋, ๊ฒ์์ฟผ๋ฆฌ๋ฌธ์ฅ์ ๋ํด ๋ชจ๋ ์ ๋ต ๋ฌธ์ฅ์ด ์ฒซ๋ฒ์งธ ๊ฒฐ๊ณผ๋ก ์ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
'1๏ธโฃ AIโขDS > ๐ LLM' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| 12. ๋ฒกํฐ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ํ์ฅํ๊ธฐ : RAG ๊ตฌํํ๊ธฐ (0) | 2025.10.22 |
|---|---|
| 11. ์์ ์ ๋ฐ์ดํฐ์ ๋ง์ถ ์๋ฒ ๋ฉ ๋ชจ๋ธ ๋ง๋ค๊ธฐ : RAG ๊ฐ์ ํ๊ธฐ (0) | 2025.10.19 |
| [์ฑ ์คํฐ๋] 10-(1). ์๋ฒ ๋ฉ ๋ชจ๋ธ๋ก ๋ฐ์ดํฐ ์๋ฏธ ์์ถํ๊ธฐ (0) | 2025.09.18 |
| [์ฑ ์คํฐ๋] 9. LLM ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐํ๊ธฐ (1) | 2025.09.08 |
| [์ฑ ์คํฐ๋] 8. sLLM ์๋นํ๊ธฐ (0) | 2025.09.06 |
๋๊ธ