LLM을 이용한 의미 기반 검색
쉽고 빠르게 익히는 실전 LLM (https://product.kyobobook.co.kr/detail/S000212147276)
실습 3. Open source alternative to embedding
( 참고: https://github.com/sinanuozdemir/quick-start-guide-to-llms/blob/main/notebooks/02_semantic_search.ipynb )
Procedures
- Step 1) 관련 패키지 불러오기
- Step 2) 재순위화 하는 함수
- Step 3) 재순위화를 위한 모델
- Step 4) 테스트 데이터셋에 있는 질문들 가져오기
- Step 5) 특정 query에 대한 정보/정답 반환
Step 1) 모델 불러오기
Sentence Transformer 불러오기
from sentence_transformers import SentenceTransformer
bi_encoder = SentenceTransformer("sentence-transformers/all-mpnet-base-v2")
예시: 해당 인코더로 특정 문맥 임베딩하기
( 총 1148개의 텍스트, 각각 768차원으로 임베딩 )
docs = dataset['test']['context']
doc_emb = bi_encoder.encode(docs,
batch_size=32,
show_progress_bar=True)
print(doc_emb.shape)
(1148, 768)
Step 2) 가장 비슷한 문서 찾기
semantic_search()
함수 사용하기
semantic_search(query_embedding, embeddings, top_k=k)
인자
- (1) query embedding: 질문에 대한 임베딩 (1개의 임베딩)
- (2) embedding: 정보 검색 대상들에 대한 임베딩 (1148개의 문서)
- (3) top_k
가장 비슷한 문서를 찾는 함수
from sentence_transformers.util import semantic_search
# Top K개를 찾고, 그 중 1등을 반환
def find_most_similar(text, embeddings, documents, k=3):
query_embedding = bi_encoder.encode([text], show_progress_bar=False)
similarities = semantic_search(query_embedding, embeddings, top_k=k)
return [(documents[sim['corpus_id']], sim['score'], sim['corpus_id']) for sim in similarities[0]]
Step 3) 재순위화
reranking 모델이 재순위화한 결과값 반환하는 함수
def eval_ranking_open_source(query, top_k=3, re_rank_model=None):
ans = {'retrieved_correct_position': None}
correct_hash = q_to_hash[query]
# (1) 질문에 대해 가장 유사한 top K개의 문서
results = find_most_similar(query, doc_emb, docs, k=top_k)
for idx, (passage, score, doc_idx) in enumerate(results):
if correct_hash == my_hash(passage):
ans['retrieved_correct_position'] = idx
# (2) top K개의 문서를 모델에 따라 재순위화
if re_rank_model is not None:
ans['reranked_correct_position'] = None
sentence_combinations = [(query, r[0]) for r in results]
similarity_scores = re_rank_model.predict(sentence_combinations,
activation_fct=nn.Sigmoid())
sim_scores_argsort = list(reversed(np.argsort(similarity_scores)))
for i, idx in enumerate(sim_scores_argsort):
r = results[idx]
if correct_hash and my_hash(r[0]) == correct_hash:
ans['reranked_correct_position'] = i
return ans
특정 query에 대한
- (1)
retrieved_correct_position
: 최초의 1등값 - (2)
reranked_correct_position
: 재순위 후 1등값
eval_ranking_open_source(query, top_k=TOP_K, re_rank_model=cross_encoder)
{'retrieved_correct_position': 1, 'reranked_correct_position': 0}
모든 query에 대해 계산
- 가끔 이 둘의 결과가 불일치 한 것을 확인할 수 있다!
os_predictions = []
for i, question in tqdm(enumerate(test_sample), total=len(test_sample)):
os_predictions.append(eval_ranking_open_source(question['question'],
top_k=TOP_K,
re_rank_model=cross_encoder))
os_predictions_df = pd.DataFrame(os_predictions)
print(os_predictions_df.head())
retrieved_correct_position reranked_correct_position
0 0.0 0.0
1 0.0 0.0
2 0.0 0.0
3 1.0 0.0
4 0.0 0.0
Step 4) 성능 평가
raw_accuracy = sum([p['retrieved_correct_position'] == 0 for p in os_predictions])/len(os_predictions)
reranked_accuracy = sum([p['reranked_correct_position'] == 0 for p in os_predictions])/len(os_predictions)
print(f'Accuracy without re-ranking: {raw_accuracy}')
print(f'Accuracy with re-ranking: {reranked_accuracy}')
Accuracy without re-ranking: 0.5017421602787456
Accuracy with re-ranking: 0.6202090592334495
다양한 K에 대해 결과 계산해보기
OPEN_SOURCE_RETRIEVAL = []
OPEN_SOURCE_RETRIEVAL_PLUS_PRE_CE = []
for k in (1, 3, 5, 10, 25, 50):
embedding_only_recall = os_predictions_df[os_predictions_df['retrieved_correct_position'] < k].shape[0]
reranked_recall = os_predictions_df[os_predictions_df['reranked_correct_position'] < k].shape[0]
print(k, embedding_only_recall, reranked_recall)
OPEN_SOURCE_RETRIEVAL.append(embedding_only_recall / os_predictions_df.shape[0])
OPEN_SOURCE_RETRIEVAL_PLUS_PRE_CE.append(reranked_recall / os_predictions_df.shape[0])