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])

Categories: ,

Updated: