본문 바로가기

AI

MultipleNegativesRankingLoss를 활용한 임베딩 파인 튜닝

728x90

MultipleNegativesRankingLoss는 문장 임베딩 모델을 파인 튜닝하는 데 매우 효과적인 손실 함수로. 이 방법에 대해 더 자세히 공부해 보겠습니다.

 

손실 함수(Loss function): 오차를 계산하는 함수로. AI를 학습할 때는 학습 중인 AI의 예측값과 실제 정답간의 오차를 계산해서 해당 오차를 줄이는 식으로 AI 모델을 학습합니다.

 

포지티브 샘플과 네거티브 샘플

  • 포지티브 샘플(Positive Sample): 의미적으로 관련이 있는 문장 쌍을 의미합니다.
    • 예: (문서1: "서울의 인구는?", 문서2: "서울의 인구는 약 970만 명입니다.")
  • 네거티브 샘플(Negative Sample): 의미적으로 관련이 없거나 관련성이 낮은 문장 쌍을 의미합니다.
    • 예: (문서1: "서울의 인구는?", 문서2: "파리는 프랑스의 수도입니다.")

임베딩 학습의 기본 원리

임베딩 학습의 핵심 목표는 의미적으로 유사한 텍스트는 임베딩 공간에서 가깝게, 의미적으로 다른 텍스트는 멀게 위치시키는 것입니다. (실제로 의미가 비슷하면 임베딩 유사도가 높게, 실제로 의미가 비슷하지 않으면 임베딩 유사도가 낮게 임베딩 벡터 값들을 업데이트 하는 것입니다.) 이를 위해 일반적인 임베딩 학습 방법은 다음과 같은 프로세스를 따릅니다:

  1. 포지티브 샘플과 네거티브 샘플 준비: 학습 데이터로 포지티브 샘플(관련 있는 쌍)과 네거티브 샘플(관련 없는 쌍)을 모두 준비합니다.
  2. 대조 학습(Contrastive Learning): 모델이 포지티브 샘플 쌍의 임베딩 간 거리는 가깝게, 네거티브 샘플 쌍의 임베딩 간 거리는 멀게 학습합니다.
  3. 손실 함수 최적화: 임베딩 간 유사도(보통 코사인 유사도)를 계산하고, 포지티브 쌍의 유사도는 높이고 네거티브 쌍의 유사도는 낮추는 방향으로 손실 함수를 최적화합니다.

전통적인 임베딩 학습 데이터셋 구성

전통적인 대조 학습에서는 하나의 앵커(anchor)에 대해 하나의 포지티브와 여러 네거티브 샘플을 명시적으로 준비해야 했고. 이는 다음과 같은 방식으로 데이터셋을 구성해야 함을 의미합니다:

  1. 트리플렛(Triplet) 구성: 각 학습 데이터는 (앵커, 포지티브, 네거티브) 형태의 트리플렛으로 구성됩니다.
 
### 전통적인 임베딩 학습 데이터셋 구성

전통적인 대조 학습에서는 하나의 앵커(anchor)에 대해 하나의 포지티브와 여러 네거티브 샘플을 **명시적으로** 준비해야 했습니다. 이는 다음과 같은 방식으로 데이터셋을 구성해야 함을 의미합니다:

1. **트리플렛(Triplet) 구성**: 각 학습 데이터는 (앵커, 포지티브, 네거티브) 형태의 트리플렛으로 구성됩니다.
   ```python
   # 전통적인 트리플렛 구성 예시
   triplets = [
       # (앵커, 포지티브, 네거티브)
       ("강아지를 기르는 방법", "반려견 양육 가이드", "고양이 사료 추천"),
       ("파이썬 코딩 튜토리얼", "파이썬 프로그래밍 기초", "자바스크립트 입문 강의"),
       # ... 수천, 수만 개의 트리플렛 필요
   ]
   ```

2. **다중 네거티브 사례**: 효과적인 학습을 위해 하나의 앵커당 여러 개의 네거티브 샘플이 필요합니다.
   ```python
   # 다중 네거티브 샘플 구성 예시
   training_data = [
       {
           "anchor": "머신러닝이란?",
           "positive": "기계학습은 데이터로부터 패턴을 찾는 AI 기술입니다.",
           "negatives": [
               "오늘 날씨가 좋네요.",
               "내일 회의는 2시에 시작합니다.",
               "이 식당의 불고기가 맛있습니다.",
               # ... 여러 개의 네거티브 샘플
           ]
       },
       # ... 수천 개의 이런 구조
   ]
   ```

 

3. 데이터 준비의 어려움
   - 각 앵커에 대한 관련 없는 문장들을 수집해야 함
   - 적절한 난이도의 네거티브 샘플을 선택해야 함 (너무 쉽거나 어려우면 학습 효과 감소)
   - 데이터셋 크기가 기하급수적으로 증가 (앵커 × 네거티브 수)
   - 대규모 말뭉치에서 효과적인 네거티브 샘플링 전략 필요

이러한 방식은 데이터 준비 과정이 복잡하고 시간이 많이 소요되는 단점이 있었으며, 양질의 네거티브 샘플을 구성하는 것은 종종 전체 학습 프로세스에서 가장 어려운 부분 중 하나였습니다.

 

MultipleNegativesRankingLoss: 배치 내 네거티브 샘플링

여기서 MultipleNegativesRankingLoss의 혁신적인 접근 방식이 등장합니다. 기존 방식과 달리, 이 손실 함수는 명시적인 네거티브 샘플을 별도로 준비할 필요가 없다는 큰 장점이 있습니다. 대신, 현재 배치 내의 다른 샘플들을 자동으로 네거티브로 활용합니다.

이는 데이터 준비 과정을 크게 단순화하고, 학습 효율성을 높이는 핵심 요소입니다. 사용자는 포지티브 샘플만 제공하면 되며, 네거티브 샘플은 배치 내에서 자동으로 생성됩니다.

예를 들어, 배치 크기가 4인 경우:


배치 = [
    (앵커 문서1, 문서1),  # 포지티브 쌍 1
    (앵커 문서2, 문서2),  # 포지티브 쌍 2
    (앵커 문서3, 문서3),  # 포지티브 쌍 3
    (앵커 문서4, 문서4)   # 포지티브 쌍 4
]

 

앵커 문서1에 대해:

  • 문서1은 포지티브 샘플
  • 문서2, 문서3, 문서4는 네거티브 샘플로 취급됨

마찬가지로 앵커 문서2에 대해:

  • 문서2는 포지티브 샘플
  • 문서1, 문서3, 문서4는 네거티브 샘플

    MultipleNegativesRankingLoss의 실제 작동 예시

    다음과 같은 문장 쌍이 있다고 가정해 보겠습니다:
[
    ("AI란 무엇인가?", "AI는 인간의 지능을 모방한 기술입니다."),
    ("딥러닝이란?", "신경망을 여러 층 쌓아 데이터로부터 학습하는 기계학습 방법입니다."),
    ("Python은 어디에 쓰이나요?", "Python은 데이터 분석, 웹 개발, AI 등에 널리 사용됩니다."),
    ("자연어 처리란?", "컴퓨터가 인간의 언어를 이해하고 처리하는 AI의 한 분야입니다.")
]

 

배치 크기가 4일 때, "AI란 무엇인가?"라는 질문(앵커)에 대해:

  1. 포지티브 샘플: "AI는 인간의 지능을 모방한 기술입니다."
  2. 네거티브 샘플:
    • "신경망을 여러 층 쌓아 데이터로부터 학습하는 기계학습 방법입니다."
    • "Python은 데이터 분석, 웹 개발, AI 등에 널리 사용됩니다."
    • "컴퓨터가 인간의 언어를 이해하고 처리하는 AI의 한 분야입니다."

수학적 표현

MultipleNegativesRankingLoss는 다음과 같이 계산됩니다:

L = -log( exp(sim(q, p+)) / (exp(sim(q, p+)) + Σ exp(sim(q, p-))) )

여기서:

  • q: 쿼리/앵커 임베딩
  • p+: 포지티브 샘플 임베딩
  • p-: 네거티브 샘플 임베딩들
  • sim(): 유사도 함수 (일반적으로 코사인 유사도)

이 손실 함수는 포지티브 쌍의 유사도를 높이고 네거티브 쌍의 유사도를 낮추어야 값이 작아지는 식이므로 이 식을 보고 오차를 줄이는 방향으로 모델을 학습시킵니다.

구현 예시 (더 상세한 코드)

from sentence_transformers import SentenceTransformer, losses, InputExample
from torch.utils.data import DataLoader
import torch

# 모델 로드
model = SentenceTransformer('distilbert-base-multilingual-cased')

# 훈련 데이터 준비
train_examples = [
    InputExample(texts=["AI란 무엇인가?", "AI는 인간의 지능을 모방한 기술입니다."]),
    InputExample(texts=["딥러닝이란?", "신경망을 여러 층 쌓아 데이터로부터 학습하는 기계학습 방법입니다."]),
    InputExample(texts=["Python은 어디에 쓰이나요?", "Python은 데이터 분석, 웹 개발, AI 등에 널리 사용됩니다."]),
    InputExample(texts=["자연어 처리란?", "컴퓨터가 인간의 언어를 이해하고 처리하는 AI의 한 분야입니다."])
]

# 배치 크기가 클수록 성능이 향상될 수 있음
batch_size = 32  # 실제 환경에서는 16-64 범위가 일반적
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=batch_size)

# MultipleNegativesRankingLoss 설정
# 온도(temperature) 파라미터를 조정하여 손실 함수의 강도 조절 가능
loss = losses.MultipleNegativesRankingLoss(model, scale=20.0)  # scale은 temperature의 역수

# 학습 설정
train_loss = losses.MultipleNegativesRankingLoss(model)
warmup_steps = int(len(train_dataloader) * 0.1)  # 전체 훈련 데이터의 10%

# 모델 학습
model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=3,
    warmup_steps=warmup_steps,
    optimizer_params={'lr': 2e-5},
    output_path='./korean-sentence-embedding-model'
)

 

배치 크기의 중요성

배치 크기가 클수록 더 많은 네거티브 샘플이 생성되므로 모델의 성능이 향상될 수 있습니다:

  • 배치 크기가 4인 경우: 각 질문에 대해 3개의 네거티브 샘플
  • 배치 크기가 32인 경우: 각 질문에 대해 31개의 네거티브 샘플
  • 배치 크기가 128인 경우: 각 질문에 대해 127개의 네거티브 샘플

 

하드 네거티브 샘플링

기본적인 배치 내 네거티브 샘플링만으로도 좋은 성능을 얻을 수 있지만, 더 어려운 네거티브 샘플을 추가하면 성능을 더욱 향상시킬 수 있습니다.

하드 네거티브의 이해

하드 네거티브는 명시적으로 사용자가 직접 선택하여 학습 데이터에 포함시키는 네거티브 샘플을 의미합니다. 일반적인 MultipleNegativesRankingLoss의 배치 내 네거티브 샘플링이 자동으로 선택되는 "이지 네거티브(easy negative)"인 반면, 하드 네거티브는 사용자가 의도적으로 선별하는 샘플입니다.

하드 네거티브 구현 방법

공식 문서에 따르면, MultipleNegativesRankingLoss에서 하드 네거티브는 다음과 같은 형태로 데이터를 구조화하여 제공합니다:

 

train_examples = [
    # (앵커, 포지티브, 하드 네거티브) 형태로 제공
    InputExample(texts=["AI란 무엇인가?", "AI는 인간의 지능을 모방한 기술입니다.", "AI는 로봇과 같은 물리적 형태를 가진 기계입니다."]),
    InputExample(texts=["딥러닝이란?", "신경망을 여러 층 쌓아 데이터로부터 학습하는 기계학습 방법입니다.", "컴퓨터가 스스로 생각하는 방법입니다."])
]

 

이 방식에서는:

  •  InputExample의 첫 번째 항목은 앵커(질문)입니다.
  • 두 번째 항목은 포지티브 샘플(관련 있는 응답)입니다.
  • 세 번째 이후 항목들은 하드 네거티브(관련 없지만 구분하기 어려운 응답)입니다.

손실 함수는 쌍 (a_i, p_i)에 대해 모든 p_j(j != i)와 모든 n_j를 네거티브로 사용합니다. 즉, 각 앵커는 자신의 포지티브와 유사도가 높아지도록 학습되고, 다른 앵커의 포지티브와 모든 하드 네거티브와는 유사도가 낮아지도록 학습됩니다.

하드 네거티브 vs 일반 네거티브

일반 네거티브(배치 내 무작위 네거티브):

  • 자동으로 배치 내에서 생성됨
  • 대부분 주제가 완전히 다른 무관한 문장들
  • 모델이 구분하기 상대적으로 쉬움

하드 네거티브(명시적 네거티브):

  • 사용자가 직접, 의도적으로 선택함
  • 포지티브와 주제는 유사하나 정확한 답변이 아님
  • 미묘한 의미 차이를 포함하여 모델에게 더 큰 도전이 됨

예시:

  • 질문: "당뇨병의 증상은 무엇인가요?"
  • 포지티브: "당뇨병의 주요 증상으로는 갈증 증가, 빈뇨, 체중 감소 등이 있습니다."
  • 하드 네거티브(명시적): "저혈당의 증상으로는 현기증, 발한, 불안감 등이 있습니다." (의료 관련 주제이지만 당뇨병이 아닌 저혈당에 관한 내용)
  • 일반 네거티브(암시적): "파이썬은 객체지향 프로그래밍 언어입니다." (완전히 다른 주제)

하드 네거티브 샘플을 사용하면 모델이 더 미묘한 의미 차이를 학습하게 되어 정확도가 크게 향상될 수 있습니다. 그러나 이러한 하드 네거티브를 구성하려면 도메인 지식과 추가 작업이 필요합니다.

실제 응용 사례

  1. 검색 시스템: 질의어와 문서 간의 의미적 관련성 학습
  2. RAG 챗봇(Retrieval-Augmented Generation): 사용자 질문과 관련된 정보를 데이터베이스에서 효과적으로 검색하여 응답 생성에 활용
  3. 텍스트 클러스터링: 비슷한 문장들끼리 군집화하는 데 사용
  4. 추천 시스템: 사용자 쿼리와 관련된 아이템을 매칭하는 데 활용

성능 최적화 팁

  1. 배치 크기 증가: GPU 메모리가 허용하는 한 배치 크기를 늘리세요.
  2. 하드 네거티브 샘플 포함: 무작위 네거티브보다 더 효과적입니다.
  3. 데이터 증강: 원본 문장의 변형(동의어 교체, 단어 순서 변경 등)을 추가하세요.
  4. 학습률 조정: 너무 크지 않은 학습률로 시작하고 필요에 따라 조정하세요.
  5. 온도(temperature) 파라미터 조정: 손실 함수의 scale 파라미터를 조절하여 학습 강도를 제어하세요.

이러한 방법으로 MultipleNegativesRankingLoss를 활용하면 적은 양의 데이터로도 효과적인 문장 임베딩 모델을 구축할 수 있습니다.

 

!pip install PyPDF2 datasets sentence-transformers==3.4.1
import os
import requests
import json
import pandas as pd
import numpy as np
from tqdm.notebook import tqdm
from openai import OpenAI
from torch.utils.data import DataLoader
from sentence_transformers import SentenceTransformer, losses, InputExample
from sentence_transformers.evaluation import InformationRetrievalEvaluator
import torch
from sklearn.metrics.pairwise import cosine_similarity

 

2. PDF 파일 로드

인터넷으로 '일본 ICT 동향 문서'와 '미국 ICT 동향 문서' 두 가지를 다운로드 할 것입니다.

저희는 '미국 ICT 동향 문서' 기준으로 임베딩 모델을 학습시키고, '일본 ICT 동향 문서'에 대해서 검색 성능을 평가해볼 것입니다.

 

for url in urls:
   filename = url.split("/")[-1]
   response = requests.get(url)
   with open(filename, "wb") as f:
       f.write(response.content)
   print(f"{filename} 다운로드 완료")

ict_japan_2024.pdf 다운로드 완료

ict_usa_2024.pdf 다운로드 완료

 

위의 두 개 파일을 PDF 로더를 이용하여 각각 로더하여 파이썬 문자열 리스트로 읽습니다.

# 2. PDF를 문서로 변환 (PyPDF2 사용)
import PyPDF2

def extract_text_from_pdf(pdf_path):
   """PDF 파일에서 텍스트를 추출하는 함수"""
   text_chunks = []
   with open(pdf_path, 'rb') as file:
       pdf_reader = PyPDF2.PdfReader(file)
       for page_num in range(len(pdf_reader.pages)):
           page = pdf_reader.pages[page_num]
           text = page.extract_text()
           # 페이지 단위로 청크 생성
           if text.strip():
               text = text.strip()
               # 문서 길이가 10자 초과인 경우만 추가
               if len(text) > 10:
                   text_chunks.append(text)
   return text_chunks

# 미국 ICT 동향 (학습 데이터)
train_corpus = extract_text_from_pdf('ict_usa_2024.pdf')
print(f'학습 데이터 문서 개수: {len(train_corpus)}')

# 일본 ICT 동향 (검증 데이터)
val_corpus = extract_text_from_pdf('ict_japan_2024.pdf')
print(f'검증 데이터 문서 개수: {len(val_corpus)}')

학습 데이터 문서 개수: 26

검증 데이터 문서 개수: 27

 

train_corpus에는 '미국 ICT 동향 문서'가 청킹되어서 26개의 문서가 저장되어져 있습니다.
val_corpus에는 '일본 ICT 동향 문서'가 청킹되어서 27개의 문서가 저장되어져 있습니다.

 

print('10번 문서:', train_corpus[10])
10번 문서: 13 Ⅰ. ICT 국가 산업 현황 4.ICT 주요 법령 및 규제 ② 반도체 과학법 (CHIPS and Science Act) 반도체 ·전자 기업, $1,660 억 규모 투자 유치 • 조 바이든 (Joe Biden) 미국 대통령은 2022년 7월 ‘반도체 과학법 (CHIPS and Science Act)’을 승인함 • 반도체 과학법은 미국의 경쟁력을 강화하고 , 미국의 공급망을 탄력적으로 구축해 국가 안보를 공고히 하며 국가의 주요 기술에 대한 접근을 지원하는 것을 목표로 함. 법률 제정으로 미국 내 반도체 생산 제조사 관련 자본 투자는 25%의 세액 공제 혜택이 제공됨 • 미국 백악관은 2023년 8월, 반도체 과학법이 서명된 지 1년 만에 반도체와 전자 관련 기업들이 1,660 억 달러(221조6,100 억 원)의 투자를 유치했다고 발표함 . 바이든 행정부의 집권 이후 기업들은 미국 내 반도체와 전자 분야 투자에 총 2,310 억 달러(약 308조 3,850 억 원) 이상의 투자를 약속함 [표 8] 반도체 과학법 주요 이정표 및 진척 현황 주요 이정표 진척 현황 (23년 8월 기준) 미국 반도체 제조 지원‣ 상무부는 CHIPS 통과 6개월 만에 해당 법에서 제공하는 390억 달러 반도체 제조 인센티브에 대한 첫 번째 자금 조달 기회 시작 ‣ 상무부는 42개 주에서 CHIPS 자금 지원에 관심 있는 460개 기업으로부터 소개서 접수 ‣ 상무부는 CHIPS 인센티브 프로그램 관련 140명 이상의 인력으로 구성된 ‘칩스 포 아메리카 (CHIPS for America)’ 팀 구성 ‣ 재무부는 투자에 대한 25% 세액 공제 관련 지침 제공 위해 규칙 제안 발표 국가 안보를 보호하고 동맹국 및 파트너와 협력‣ 국무부는 국제 기술 보안 및 혁신 기금 시행 계획 발표 ‣ 국방부와 상무부는 CHIPS 투자를 통한 안보 관련 반도체 제조를 위해 협력 확대 협의 ‣ CHIPS 를 시행하면서 상무부는 여러 파트너 및 동맹국과 긴밀한 접촉 유지 미국 근로자를 위한 일자리 및 인력 파이프라인 창출‣ 반도체 및 기타 산업에서 양질의 일자리를 창출할 파이프라인 구축을 위해 5개의 인력 허브 초기 세트 발표 ‣ 최소 50개 이상의 커뮤니티 대학에서 신규 및 확장된 반도체 인력 프로그램 발표 ‣ 국립과학재단 (NSF) 은 제조 인력 및 연구자 지원, 커리큘럼 개발에 초점을 맞춘 새로운 계획을 통해 미국 반도체 인력 투자 ‣ 반도체 기업의 정규직 채용에 지원한 학생 수는 2022~2023 년에 79% 증가 혁신 투자‣ 상무부는 기타 부처들과 국립 반도체 기술 센터 설립 추진 ‣ 상무부는 110억 달러 규모의 R&D 자금을 계측 프로그램 , 국립 고급 포장 제조 프로그램 , 최대 3개의 신규 ‘Manufacturing USA’ 연구소 등 기타 부문에도 투자 ‣ 국방부는 마이크로전자공학 공용 R&D 프로그램에 대한 솔루션 요청서 발표 지역 경제 개발 및 혁신 지원‣ 상무부는 5억 달러 규모의 기술 허브 프로그램 1단계를 위한 자금 조달 기회 발표 ‣ 낙후 지역 경제발전 및 일자리 창출을 위한 Recompete 프로그램 1단계에 2억 달러 자금 조달 기회 발표 ‣ 국립과학재단은 기술, 혁신, 파트너십을 위한 새로운 부서 설립 무선 혁신 및 보안 지원‣ 상무부는 개방적이고 상호 운용 가능한 무선 네트워크 개발 지원을 위해 15억 달러 규모의 공공 무선 공급망 혁신 기금의 첫 번째 보조금 발표 출처 : 미국 백악관

3. 각 문서에 대해서 질문 2개씩 생성

# OpenAI 클라이언트 초기화
client = OpenAI()

# 3. 각 문서에 대한 질문 생성 (OpenAI API 사용)
def generate_queries(corpus, num_questions_per_chunk=2):
    all_queries = []
    all_positive_docs = []

    # 기본 프롬프트 템플릿 설정
    prompt_template = """\
    다음은 참고할 내용입니다.

    ---------------------
    {context_str}
    ---------------------

    위 내용을 바탕으로 낼 수 있는 질문을 {num_questions_per_chunk}개 만들어주세요.
    질문만 작성하고 실제 정답이나 보기 등은 작성하지 않습니다.

    해당 질문은 본문을 볼 수 없다고 가정합니다.
    따라서 '위 본문을 바탕으로~' 라는 식의 질문은 할 수 없습니다.

    질문은 아래와 같은 형식으로 번호를 나열하여 생성하십시오.

    1. (질문)
    2. (질문)
    """

    # corpus의 각 문서에 대해 반복 실행
    for text in tqdm(corpus):
        # 현재 문서에 대한 프롬프트 생성
        messages = [
            {"role": "system", "content": "You are a helpful assistant that generates questions based on provided content."},
            {"role": "user", "content": prompt_template.format(
                context_str=text,
                num_questions_per_chunk=num_questions_per_chunk
            )}
        ]

        # GPT 모델을 사용해 질문 생성
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            temperature=0.7,
        )

        # 응답을 줄바꿈을 기준으로 분리하여 개별 질문으로 만듦
        result = response.choices[0].message.content.strip().split("\n")

        # 질문 형식 정리
        questions = []
        for line in result:
            if line.strip():
                parts = line.strip().split('. ', 1)
                if len(parts) > 1:
                    questions.append(parts[1])
                else:
                    questions.append(parts[0])

        # 빈 질문 제거
        questions = [q for q in questions if len(q) > 0]

        # 각 질문에 대해 문서 매칭 및 저장
        for question in questions:
            all_queries.append(question)
            all_positive_docs.append(text)

    return all_queries, all_positive_docs

 

함수는 PDF에서 추출한 텍스트 데이터(corpus)를 입력받아 처리합니다. 각 텍스트 문서마다 OpenAI API를 통해 GPT-4o 모델에게 질문 생성을 요청합니다. num_questions_per_chunk=2 파라미터는 각 문서당 2개의 질문을 생성하도록 지정합니다.

실제 예시로, 문서 내용이 "2024년 일본의 반도체 산업은 전년 대비 15% 성장했으며, 정부는 300억 엔의 추가 투자를 발표했다"라면, GPT-4o는 다음과 같은 질문을 생성할 수 있습니다:

  1. "2024년 일본 반도체 산업의 성장률은 얼마인가?"
  2. "일본 정부가 반도체 산업에 발표한 추가 투자 금액은?"

함수는 이 질문들을 처리하여 두 개의 중요한 리스트를 생성합니다:

  • all_queries에는 생성된 모든 질문들이 들어있습니다.
  • all_positive_docs에는 각 질문의 출처가 된 문서들이 순서대로 들어있습니다.

이 두 리스트는 서로 인덱스로 연결되어 있어서, 예를 들어 all_queries[0]에 있는 질문은 all_positive_docs[0]에 있는 문서에서 생성된 것입니다.

이 데이터는 나중에 임베딩 모델을 훈련시키는 데 사용되어, 새로운 질문이 들어왔을 때 관련 있는 문서를 찾아낼 수 있게 합니다. 모델은 "이 질문과 이 문서는 관련이 있다"는 정보를 학습하게 됩니다.

 

# 학습 데이터 질문 생성
train_queries, train_positive_docs = generate_queries(train_corpus)
print(f'생성된 학습용 질문 개수: {len(train_queries)}')

# 검증 데이터 질문 생성
val_queries, val_positive_docs = generate_queries(val_corpus)
print(f'생성된 검증용 질문 개수: {len(val_queries)}')

# 4. 학습 데이터 준비
train_examples = []
for query, doc in zip(train_queries, train_positive_docs):
    example = InputExample(texts=[query, doc])
    train_examples.append(example)
100%
 
 26/26 [00:29<00:00,  1.17s/it]
생성된 학습용 질문 개수: 52
100%
 
 27/27 [00:34<00:00,  1.16s/it]
생성된 검증용 질문 개수: 54

 

# 첫번째 샘플
train_examples[0].texts
['미국과 일본은 어떤 분야에서 협력을 통해 기술 개발을 추진하고 있나요?',
 'Ⅰ ICT국가산업현황  4\n(*) SUMMARY\n1. 국가 개황\n2. ICT 정부기구\n3. ICT 주요정책\n4. ICT 주요법령및규제\n5. ICT 주요기업\n6. 한국 협력 및 국내기업 진출사례\nⅡ ICT이슈Top 10  16\n(*) SUMMARY\n① 미국 빅테크 기업, 인공지능 챗봇 개발에 주력\n② 미국, 일본과 양자컴퓨팅 개발 협력\n③ 미국, 우주 클라우드 컴퓨팅 시장 주도\n④ 미국, 드론 배송 도입 활발\n⑤ 미국, 긍정적인 의료 AI 인식 바탕으로 연구 활발\n⑥ 미국, 반도체 산업 활성화에 박차\n⑦ 미국, 기술 교류를 위한 국가 간 협력 활발\n⑧ 미국, 사이버 보안 대응 강화\n⑨ 미국, 6G 주도권 확보 위한 연구 추진\n⑩ 미 국방부 , 디지털 트윈 기술 도입 확대\n※ 참고문헌']
# 두번째 샘플
train_examples[1].texts
['미국에서 긍정적인 인식을 바탕으로 연구가 활발히 진행되고 있는 AI 분야는 무엇인가요?',
 'Ⅰ ICT국가산업현황  4\n(*) SUMMARY\n1. 국가 개황\n2. ICT 정부기구\n3. ICT 주요정책\n4. ICT 주요법령및규제\n5. ICT 주요기업\n6. 한국 협력 및 국내기업 진출사례\nⅡ ICT이슈Top 10  16\n(*) SUMMARY\n① 미국 빅테크 기업, 인공지능 챗봇 개발에 주력\n② 미국, 일본과 양자컴퓨팅 개발 협력\n③ 미국, 우주 클라우드 컴퓨팅 시장 주도\n④ 미국, 드론 배송 도입 활발\n⑤ 미국, 긍정적인 의료 AI 인식 바탕으로 연구 활발\n⑥ 미국, 반도체 산업 활성화에 박차\n⑦ 미국, 기술 교류를 위한 국가 간 협력 활발\n⑧ 미국, 사이버 보안 대응 강화\n⑨ 미국, 6G 주도권 확보 위한 연구 추진\n⑩ 미 국방부 , 디지털 트윈 기술 도입 확대\n※ 참고문헌']
# 세번째 샘플
train_examples[2].texts
['ICT 주요 정책에는 어떤 것들이 포함되어 있습니까?',
 'Ⅰ ICT 국가 산업 현황                   4\n   (*) SUMMARY\n   1. 국가 개황\n   2. ICT 정부기구\n   3. ICT 주요 정책\n   4. ICT 주요 법령 및 규제\n   5. ICT 주요기업\n   6. 한국 협력 및 국내기업 진출사례']
# 네번째 샘플
train_examples[3].texts
['ICT 분야에서 한국과의 협력 사례나 국내 기업의 진출 사례는 무엇이 있습니까?',
 'Ⅰ ICT 국가 산업 현황                   4\n   (*) SUMMARY\n   1. 국가 개황\n   2. ICT 정부기구\n   3. ICT 주요 정책\n   4. ICT 주요 법령 및 규제\n   5. ICT 주요기업\n   6. 한국 협력 및 국내기업 진출사례']
queries = [sample.texts[0] for sample in train_examples]
len(queries)

52

 

위 결과에서는 26개의 문서에 대해서 52개의 검색어가 생성되었습니다.
train_examples를 출력하면 '질문 id'와 '질문'의 쌍이 저장되어져 있습니다.

 

train_examples[0].texts
['미국이 주도하고 있는 ICT 관련 산업이나 기술 분야에는 어떤 것들이 있는가?',
 'Ⅰ ICT국가산업현황  4\n(*) SUMMARY\n1. 국가 개황\n2. ICT 정부기구\n3. ICT 주요정책\n4. ICT 주요법령및규제\n5. ICT 주요기업\n6. 한국 협력 및 국내기업 진출사례\nⅡ ICT이슈Top 10  16\n(*) SUMMARY\n① 미국 빅테크 기업, 인공지능 챗봇 개발에 주력\n② 미국, 일본과 양자컴퓨팅 개발 협력\n③ 미국, 우주 클라우드 컴퓨팅 시장 주도\n④ 미국, 드론 배송 도입 활발\n⑤ 미국, 긍정적인 의료 AI 인식 바탕으로 연구 활발\n⑥ 미국, 반도체 산업 활성화에 박차\n⑦ 미국, 기술 교류를 위한 국가 간 협력 활발\n⑧ 미국, 사이버 보안 대응 강화\n⑨ 미국, 6G 주도권 확보 위한 연구 추진\n⑩ 미 국방부 , 디지털 트윈 기술 도입 확대\n※ 참고문헌']
train_examples[1].texts
['미국과 다른 국가들이 협력하여 개발하고 있는 첨단 기술은 무엇인가?',
 'Ⅰ ICT국가산업현황  4\n(*) SUMMARY\n1. 국가 개황\n2. ICT 정부기구\n3. ICT 주요정책\n4. ICT 주요법령및규제\n5. ICT 주요기업\n6. 한국 협력 및 국내기업 진출사례\nⅡ ICT이슈Top 10  16\n(*) SUMMARY\n① 미국 빅테크 기업, 인공지능 챗봇 개발에 주력\n② 미국, 일본과 양자컴퓨팅 개발 협력\n③ 미국, 우주 클라우드 컴퓨팅 시장 주도\n④ 미국, 드론 배송 도입 활발\n⑤ 미국, 긍정적인 의료 AI 인식 바탕으로 연구 활발\n⑥ 미국, 반도체 산업 활성화에 박차\n⑦ 미국, 기술 교류를 위한 국가 간 협력 활발\n⑧ 미국, 사이버 보안 대응 강화\n⑨ 미국, 6G 주도권 확보 위한 연구 추진\n⑩ 미 국방부 , 디지털 트윈 기술 도입 확대\n※ 참고문헌']
train_examples[2].texts
['ICT 주요 정책에 대해 설명해 주세요.',
 'Ⅰ ICT 국가 산업 현황                   4\n   (*) SUMMARY\n   1. 국가 개황\n   2. ICT 정부기구\n   3. ICT 주요 정책\n   4. ICT 주요 법령 및 규제\n   5. ICT 주요기업\n   6. 한국 협력 및 국내기업 진출사례']
# 임의의 25 샘플
print(train_examples[25].texts)  # 질문과 문서 내용이 리스트로 출력됨

['최근 몇 년간 한국 기업들이 미국 시장에 진출하기 위해 어떤 활동을 했나요?', "15 Ⅰ. ICT 국가 산업 현황\n 6.한국 협력 및 국내기업 진출사례\n 한국-미국 FTA 체결 여부\n• 2012년 3월 한-미 FTA 발효됨\n• 2019년 1월 한-미 FTA 개정 의정서 발효, 투자자 -국가분쟁해결제도 (ISDS) 와 수출기업에 부담이 \n된 무역구제 절차 개선됨\n• 2022년 한-미 FTA 10주년을 기념해 정부·국회 대표단이 워싱턴 D.C(Washington DC), \n미시간 (Michigan), 뉴욕(New York) 등을 방문함\n 한국-미국 ICT 기관 협력 사례\n• 과기정통부와 미국 기관 간 ICT 협력 사례가 주목됨 \n 한국-미국 ICT 기업 진출 사례\n• 다양한 산업과 규모의 한국 기업들이 미국 시장 진출을 위해 노력하고 있음 \n[표 10] 한국-미국 협력 현황\n구분 날짜 내용\nFTA 체결여부\n(발효)12.03 ‣ 한-미 FTA 발효\n19.01 ‣ 한-미 FTA 개정 의정서 발효\n22.03 ‣ 한-미 FTA 10주년 기념해 정부·국회 대표단 방미 일정 진행\n정부23.01‣ 경북도 , 산타클라라 한인상공회의소와 '실리콘밸리 스타트업 아카데미 \n경북 MOU' 개최\n23.01 ‣ 광주시 , AI 스타트업 미국 진출 지원\n23.01‣ 화성시 , 실리콘밸리 산타클라라 한인상공회의소와 ‘스타트업 기업의 \n글로벌 진출 지원 업무협약 ’ 체결\n23.07 ‣ 과기정통부 , 한·미 WRC 협력회의 개최\n24.01 ‣ 과기정통부 , 미국 CES 2024서 ‘디지털 청년 인재 포럼’ 개최\n기업23.01 ‣ 바이든 대통령 , 한화 솔루션 미국 조지아주 ‘솔라 허브’ 투자 결정 환영\n23.01 ‣ 필워크 , 미국 법인 설립 및 앱 서비스 론칭으로 미국 시장 본격 진출\n23.08 ‣ 현대차그룹 모셔널 , ‘아이오닉 5 로보 택시’ 로스앤젤레스로 서비스 확장\n23.08 ‣ 토마토시스템 , 미국 원격 진료 시장 진출\n24.01 ‣ SK텔레콤 , 동물 AI 진단 솔루션 엑스칼리버 미국시장 본격 공략\n24.02 ‣ 올거나이즈 , SOC-2 및 HIPPA 미국 보안인증 획득\n출처 : 주요 ICT 매체 발표 기사 취합"]

 

print("문서:")
print(train_examples[25].texts[1])  # 두 번째 요소는 문서 내용
print('--' * 50)
print("문서로부터 생성한 질문:")
print(train_examples[25].texts[0])  # 첫 번째 요소는 질문
문서:
15 Ⅰ. ICT 국가 산업 현황
 6.한국 협력 및 국내기업 진출사례
 한국-미국 FTA 체결 여부
• 2012년 3월 한-미 FTA 발효됨
• 2019년 1월 한-미 FTA 개정 의정서 발효, 투자자 -국가분쟁해결제도 (ISDS) 와 수출기업에 부담이 
된 무역구제 절차 개선됨
• 2022년 한-미 FTA 10주년을 기념해 정부·국회 대표단이 워싱턴 D.C(Washington DC), 
미시간 (Michigan), 뉴욕(New York) 등을 방문함
 한국-미국 ICT 기관 협력 사례
• 과기정통부와 미국 기관 간 ICT 협력 사례가 주목됨 
 한국-미국 ICT 기업 진출 사례
• 다양한 산업과 규모의 한국 기업들이 미국 시장 진출을 위해 노력하고 있음 
[표 10] 한국-미국 협력 현황
구분 날짜 내용
FTA 체결여부
(발효)12.03 ‣ 한-미 FTA 발효
19.01 ‣ 한-미 FTA 개정 의정서 발효
22.03 ‣ 한-미 FTA 10주년 기념해 정부·국회 대표단 방미 일정 진행
정부23.01‣ 경북도 , 산타클라라 한인상공회의소와 '실리콘밸리 스타트업 아카데미 
경북 MOU' 개최
23.01 ‣ 광주시 , AI 스타트업 미국 진출 지원
23.01‣ 화성시 , 실리콘밸리 산타클라라 한인상공회의소와 ‘스타트업 기업의 
글로벌 진출 지원 업무협약 ’ 체결
23.07 ‣ 과기정통부 , 한·미 WRC 협력회의 개최
24.01 ‣ 과기정통부 , 미국 CES 2024서 ‘디지털 청년 인재 포럼’ 개최
기업23.01 ‣ 바이든 대통령 , 한화 솔루션 미국 조지아주 ‘솔라 허브’ 투자 결정 환영
23.01 ‣ 필워크 , 미국 법인 설립 및 앱 서비스 론칭으로 미국 시장 본격 진출
23.08 ‣ 현대차그룹 모셔널 , ‘아이오닉 5 로보 택시’ 로스앤젤레스로 서비스 확장
23.08 ‣ 토마토시스템 , 미국 원격 진료 시장 진출
24.01 ‣ SK텔레콤 , 동물 AI 진단 솔루션 엑스칼리버 미국시장 본격 공략
24.02 ‣ 올거나이즈 , SOC-2 및 HIPPA 미국 보안인증 획득
출처 : 주요 ICT 매체 발표 기사 취합
----------------------------------------------------------------------------------------------------
문서로부터 생성한 질문:
최근 몇 년간 한국 기업들이 미국 시장에 진출하기 위해 어떤 활동을 했나요?

 

4. 센텐스 트랜스포머 학습 형식으로 변환

# 5. 데이터 로더 생성
# 현재 Colab GPU의 한계로 매우 작은 배치 크기인 5를 선택했습니다.
# 일반적으로는 배치 크기가 클수록 성능이 좋습니다.
BATCH_SIZE = 5  # 배치 크기 조정
loader = DataLoader(train_examples, batch_size=BATCH_SIZE, shuffle=True)

 

MultipleNegativesRankingLoss는 유사도를 높이고 싶은 문장의 쌍들의 데이터를 준비했을 경우 학습에 사용할 수 있는 함수입니다.

  • 유사 문장 쌍들
  • (질의어, 응답) 쌍들
  • (번역하고자 하는 언어, 번역된 언어) 쌍들

이 손실 함수는 positive 쌍(예: (검색어, 관련 문서))가 있는 상황에서 각 배치에서 n-1개의 negative 문서를 무작위로 샘플링하여 학습하며, 일반적으로 배치 크기가 증가할수록 성능이 향상됩니다.

예시 코드는 다음과 같습니다.

 

from sentence_transformers import SentenceTransformer, losses, InputExample

# 모델 및 학습 데이터 정의
model = SentenceTransformer('distilbert-base-nli-stsb-mean-tokens')
train_examples = [
    InputExample(texts=["AI란 무엇인가?", "AI는 인간의 지능을 모방한 기술입니다."]),
    InputExample(texts=["Python 프로그래밍 언어", "Python은 데이터 분석에 사용됩니다."])
]

# 데이터 로더와 손실 함수 설정
train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=32)
loss = losses.MultipleNegativesRankingLoss(model)

# 모델 학습
model.fit(train_objectives=[(train_dataloader, loss)], epochs=1, output_path='./output')

 

작동 원리

입력 데이터는 다음으로 구성됩니다:

  • 검색어: "AI란 무엇인가?"
  • 포지티브 문서(Positive): "AI는 인간의 지능을 모방한 기술입니다."
  • 배치 내 다른 문서: 배치 내 포지티브 문서가 아닌 모든 문서는 암묵적으로 네거티브로 취급됩니다.
    모델은 검색어와 모든 문서(포지티브 및 암묵적인 네거티브)의 임베딩을 계산합니다.

코사인 유사도(Cosine Similarity) 또는 다른 유사도 측정 방법을 사용하여 다음을 수행합니다:

  1. 검색어와 포지티브 문서 간의 유사도를 최대화합니다.
  2. 검색어와 네거티브 문서 간의 유사도를 최소화합니다.

손실 함수 계산:
여기에서 사용되는 손실 함수는 MultipleNegativesRankingLoss입니다.
손실 함수는 모델이 얼마나 잘 작동하는지를 측정하는 기준입니다. 이 손실 함수는 다음과 같은 방식으로 작동합니다:

  • 포지티브 문서와의 유사도가 높아질수록, 즉 검색어와 관련된 문서가 더 가까워질수록 모델은 더 잘 학습하고 있다고 평가됩니다.
  • 네거티브 문서와의 유사도가 낮아질수록, 즉 검색어와 관련 없는 문서가 멀어질수록 모델은 더 잘 배우고 있는 상태입니다.
    MultipleNegativesRankingLoss는 이 두 가지 목표를 달성하기 위해 설계된 특별한 손실 함수입니다.
    손실 값이 작아진다는 것은 모델이 검색어와 문서 간의 관계를 더 정확히 이해하고 있다는 뜻입니다. 이를 통해 검색어와 관련 없는 문서를 검색 결과에서 밀어내고, 관련성이 높은 문서를 강조할 수 있습니다.
# 6. 모델 및 손실 함수 설정
model_id = "BAAI/bge-m3"
model = SentenceTransformer(model_id)

# 손실 함수 정의: 모델이 검색어와 포지티브 문서는 가깝게, 네거티브 문서는 멀게 학습하도록 유도
loss = losses.MultipleNegativesRankingLoss(model)

 

5. 평가를 위해서 테스트 데이터를 특정 형식으로 변환

일본 ICT 문서로부터 질문 2개씩 만든 (질문과 문서의 쌍)

# 7. 검증 데이터 평가기 설정
# 검증 데이터셋 구성
val_dataset = {
    'queries': {},
    'corpus': {},
    'relevant_docs': {}
}

# 문서 ID를 먼저 생성
doc_ids = {}
for i, doc in enumerate(val_corpus):
    doc_id = f"d{i}"
    val_dataset['corpus'][doc_id] = doc
    doc_ids[doc] = doc_id

# 질문에 ID 부여하고 관련 문서 설정
for i, (query, doc) in enumerate(zip(val_queries, val_positive_docs)):
    query_id = f"q{i}"
    val_dataset['queries'][query_id] = query

    # 이 질문이 어떤 문서에서 왔는지 찾기
    doc_id = doc_ids[doc]

    # 관련 문서 설정
    if query_id not in val_dataset['relevant_docs']:
        val_dataset['relevant_docs'][query_id] = set()
    val_dataset['relevant_docs'][query_id].add(doc_id)

# 검증 데이터셋 설정: 평가를 위한 쿼리, 문서, 정답 문서 목록
dataset = val_dataset

# 검증 데이터셋에서 코퍼스(전체 문서), 쿼리, 그리고 각 쿼리와 관련된 문서 가져오기
corpus = dataset['corpus']  # 검색 대상 문서
queries = dataset['queries']  # 검색어(쿼리)
relevant_docs = dataset['relevant_docs']  # 각 쿼리와 관련된 문서 (포지티브)

# Information Retrieval 평가 도구 설정: 쿼리-문서 검색 성능 평가
evaluator = InformationRetrievalEvaluator(queries, corpus, relevant_docs)
dataset['corpus']
dataset['queries']
dataset['relevant_docs']

 

InformationRetrievalEvaluator 데이터 구조 및 원리 완전 설명:

InformationRetrievalEvaluator는 정보 검색 모델을 평가하는 도구로, 다음 세 가지 필수 데이터 구조를 입력받습니다:

  1. queries (딕셔너리):
    • 질문 ID를 키로, 질문 텍스트를 값으로 갖는 딕셔너리
    • 예시:

   {
     "q1": "인공지능의 정의는 무엇인가?",
     "q2": "머신러닝과 딥러닝의 차이점은?",
     "q3": "자연어 처리란 무엇인가?"
   }

 

2. corpus (딕셔너리):

  • 문서 ID를 키로, 문서 텍스트를 값으로 갖는 딕셔너리
  • 예시:
   {
     "d1": "인공지능(AI)은 인간의 학습, 추론, this 결정 능력 등을 컴퓨터 시스템으로 구현한 기술이다. 인공지능은 머신러닝, 딥러닝 등 다양한 하위 분야를 포함한다.",
     "d2": "머신러닝은 컴퓨터가 데이터로부터 패턴을 학습하여 예측이나 의사결정을 수행하는 기술이다. 반면 딥러닝은 인공 신경망을 활용하여 더 복잡한 패턴을 학습하는 머신러닝의 한 분야이다.",
     "d3": "자연어 처리(NLP)는 컴퓨터가 인간의 언어를 이해, 해석, 생성할 수 있도록 하는 인공지능의 한 분야이다. 기계번역, 감성분석, 텍스트 요약 등의 응용이 있다."
   }
 

 

3. relevant_docs (딕셔너리):

  • 질문 ID를 키로, 해당 질문에 관련된 문서 ID들의 집합(set)을 값으로 갖는 딕셔너리
  • 예시:
   {
     "q1": set(["d1"]),             # 인공지능 질문은 d1 문서와 관련
     "q2": set(["d2"]),             # 머신러닝/딥러닝 질문은 d2 문서와 관련
     "q3": set(["d3"])              # 자연어 처리 질문은 d3 문서와 관련
   }

 

# 원래 파인튜닝 코드 형태:
val_queries = ["인공지능의 정의는 무엇인가?", "머신러닝과 딥러닝의 차이점은?", "자연어 처리란 무엇인가?"]
val_positive_docs = ["인공지능(AI)은 인간의...", "머신러닝은 컴퓨터가...", "자연어 처리(NLP)는..."]

# InformationRetrievalEvaluator를 위한 변환:
val_dataset = {
    'queries': {
        "q0": "인공지능의 정의는 무엇인가?",
        "q1": "머신러닝과 딥러닝의 차이점은?",
        "q2": "자연어 처리란 무엇인가?"
    },
    'corpus': {
        "d0": "인공지능(AI)은 인간의...",
        "d1": "머신러닝은 컴퓨터가...",
        "d2": "자연어 처리(NLP)는..."
    },
    'relevant_docs': {
        "q0": set(["d0"]),  # 첫 번째 질문은 첫 번째 문서와 관련
        "q1": set(["d1"]),  # 두 번째 질문은 두 번째 문서와 관련
        "q2": set(["d2"])   # 세 번째 질문은 세 번째 문서와 관련
    }
}

InformationRetrievalEvaluator의 동작 방식:

  1. 모델이 각 질문에 대해 corpus 내 모든 문서의 관련성 점수를 계산합니다.
  2. 문서들을 점수 기준으로 정렬합니다.
  3. 정렬된 결과에서 relevant_docs에 명시된 문서들이 얼마나 높은 순위에 있는지 평가합니다.
  4. MRR(Mean Reciprocal Rank), NDCG(Normalized Discounted Cumulative Gain), Precision@k, Recall@k 등의 메트릭을 계산합니다.

예를 들어, "인공지능의 정의는 무엇인가?" 질문에 대해 모델이 문서들을 다음과 같이 순위매겼다고 가정합니다:

  1. "d0" (인공지능 문서) - 0.95점
  2. "d2" (자연어 처리 문서) - 0.70점
  3. "d1" (머신러닝 문서) - 0.60점

이 경우 relevant_docs에 따르면 "q0"는 "d0"와 관련이 있는데, 모델이 이를 1위로 정확히 찾았으므로 높은 점수를 받게 됩니다.

이런 방식으로 평가를 통해 파인튜닝된 모델이 원본 모델보다 질문에 관련된 문서를 더 정확히 찾아내는지 비교할 수 있습니다.

6. 실제 학습

# 7. 모델 학습
EPOCHS = 2
# W&B(WandB, Weights and Biases) 로깅 비활성화
# W&B는 학습 과정을 실시간으로 추적하고 시각화할 수 있는 도구입니다.
# 하지만 이 코드는 W&B 기능을 사용하지 않으므로, 이를 비활성화하여 로깅을 중단합니다.
os.environ["WANDB_DISABLED"] = "true"
# 학습 초기에 학습률을 점진적으로 증가시키는 단계 수 설정
# 전체 학습 단계의 10%를 워밍업으로 사용
warmup_steps = int(len(loader) * EPOCHS * 0.1)

# 모델 학습
model.fit(
    train_objectives=[(loader, loss)],  # 학습 데이터 로더와 손실 함수 설정
    epochs=EPOCHS,  # 총 에포크 수
    warmup_steps=warmup_steps,  # 워밍업 단계
    output_path='exp_finetune',  # 학습된 모델 저장 경로
    show_progress_bar=True,  # 학습 진행률 표시 여부
    evaluator=evaluator,  # 학습 중간에 평가를 수행할 도구
    evaluation_steps=50,  # 50단계마다 평가 수행

7. 평가

def evaluate(dataset_queries, dataset_corpus, dataset_relevant_docs, model_path, top_k=5, verbose=False):
    """
    SentenceTransformer 모델을 사용하여 문서 검색 모델의 성능을 평가하는 함수

    Args:
        dataset_queries (dict): 쿼리 ID를 키로, 쿼리 텍스트를 값으로 하는 딕셔너리
        dataset_corpus (dict): 문서 ID를 키로, 문서 텍스트를 값으로 하는 딕셔너리
        dataset_relevant_docs (dict): 쿼리 ID를 키로, 관련 문서 ID 집합을 값으로 하는 딕셔너리
        model_path (str): SentenceTransformer 모델의 경로 또는 이름
        top_k (int): 각 질문당 검색할 상위 문서 개수 (기본값: 5)
        verbose (bool): 상세 출력 여부 (기본값: False)
    """
    # SentenceTransformer 모델 로드
    model = SentenceTransformer(model_path)

    # 평가 결과를 저장할 리스트
    eval_results = []

    # 문서 텍스트와 ID 준비
    corpus_texts = list(dataset_corpus.values())
    corpus_ids = list(dataset_corpus.keys())

    # 문서 임베딩 계산
    if verbose:
        print(f"문서 임베딩 계산 중 ({len(corpus_texts)}개)...")
    corpus_embeddings = model.encode(corpus_texts, show_progress_bar=True, convert_to_numpy=True)

    # 각 질문에 대해 검색 수행 및 평가
    for query_id, query in tqdm(dataset_queries.items()):
        # 질문 임베딩 계산
        query_embedding = model.encode(query, convert_to_numpy=True)

        # 코사인 유사도 계산
        similarities = []
        for doc_embedding in corpus_embeddings:
            similarity = np.dot(query_embedding, doc_embedding) / (
                np.linalg.norm(query_embedding) * np.linalg.norm(doc_embedding)
            )
            similarities.append(similarity)

        # 유사도가 높은 상위 k개 문서 추출
        top_indices = np.argsort(similarities)[::-1][:top_k]
        retrieved_ids = [corpus_ids[idx] for idx in top_indices]

        # 해당 질문에 대한 실제 정답 문서 ID
        expected_ids = dataset_relevant_docs.get(query_id, set())

        # 정답 문서가 검색된 문서들 안에 있는지 확인
        is_hit = any(doc_id in retrieved_ids for doc_id in expected_ids)

        # 평가 결과 저장
        eval_result = {
            'is_hit': is_hit,          # 정답 문서가 검색 결과에 포함되었는지 여부 (True/False)
            'retrieved': retrieved_ids, # 검색된 상위 k개 문서들의 ID 목록
            'expected': list(expected_ids),    # 실제 정답 문서의 ID 목록
            'query': query_id,          # 현재 평가 중인 질문의 ID
        }
        eval_results.append(eval_result)

        if verbose and is_hit:
            print(f"쿼리 '{query[:50]}...' 에 대한 검색 성공")

    return eval_results

 

위 함수는 사용자의 질문에 상위 k개에 정답이 있는지를 평가하는 함수입니다.

상위 k는 함수의 파라미터 중 top_k로 컨트롤합니다. 시스템은 각 질문마다 가장 관련성 높은 k개의 문서를 반환하며, 이 문서들 중에 정답 문서가 포함되어 있으면 'is_hit'가 True가 됩니다.

함수는 문서 모음(corpus), 질문들(queries), 정답 문서 정보(relevant_docs)를 입력으로 받아 처리합니다. 모든 문서는 벡터 검색이 가능한 형태로 변환되어 인덱스가 구축되고, 이를 통해 각 질문별로 상위 k개의 문서를 검색합니다.

결과로는 각 질문마다 정답 포함 여부(is_hit), 검색된 문서 목록(retrieved), 정답 문서(expected), 질문 ID(query)를 반환하며, 이를 통해 검색 시스템의 정확도를 평가할 수 있습니다.

# 10. 원본 evaluate_st 함수
def evaluate_st(dataset, model_id, name):
    """
    SentenceTransformer 모델의 검색 성능을 평가하는 함수

    Args:
        dataset (dict): 평가에 사용할 데이터셋. corpus, queries, relevant_docs를 포함하는 사전
        model_id (str): 평가할 SentenceTransformer 모델의 ID 또는 경로
        name (str): 평가 결과를 저장할 때 사용할 이름
    """
    # 평가 결과를 저장할 디렉토리 생성 (없으면 새로 만들고, 있어도 에러 없음)
    os.makedirs('results', exist_ok=True)

    # 데이터셋에서 필요한 데이터 추출
    corpus = dataset['corpus']          # 문서 ID를 키로, 문서 내용을 값으로 하는 사전
    queries = dataset['queries']        # 질문 ID를 키로, 질문 내용을 값으로 하는 사전
    relevant_docs = dataset['relevant_docs']  # 질문 ID를 키로, 관련 문서 ID 집합을 값으로 하는 사전

    # InformationRetrievalEvaluator 객체 생성 (검색 성능 평가를 위한 도구)
    evaluator = InformationRetrievalEvaluator(queries, corpus, relevant_docs, name=name)

    # 평가할 SentenceTransformer 모델 로드
    model = SentenceTransformer(model_id)

    # 모델 평가 수행 및 결과 반환 (results/ 디렉토리에 결과가 저장됨)
    return evaluator(model, output_path='results/')

 

evaluate_st 함수는 이전에 설명했던 evaluate 함수와 비슷한 역할을 하지만, SentenceTransformer에서 제공하는 전용 평가 도구를 사용한다는 점이 다릅니다.

이 함수는 데이터셋, 평가할 모델, 그리고 평가 결과를 저장할 파일의 경로 이 세가지를 입력받습니다.

평가를 수행하고 그 결과는 results 디렉토리 안에 CSV 파일로 저장합니다. 이 CSV 파일에는 상세한 평가 결과가 기록됩니다.

 

# 파인튜닝된 모델 평가
print("파인튜닝 모델 평가 시작...")
finetuned_model_path = "exp_finetune"  # 파인튜닝된 모델 경로
finetuned_results = evaluate(
    val_dataset['queries'],
    val_dataset['corpus'],
    val_dataset['relevant_docs'],
    finetuned_model_path
)
파인튜닝 모델 평가 시작...
Batches: 100%
 
 1/1 [00:04<00:00,  4.24s/it]
100%
 
 54/54 [00:01<00:00, 42.07it/s]

 

# 데이터프레임으로 변환
df_finetuned = pd.DataFrame(finetuned_results)
hit_rate_finetuned = df_finetuned['is_hit'].mean()
print(f"파인튜닝 모델 적중률: {hit_rate_finetuned:.4f}")

# SentenceTransformer 평가 (추가 지표 계산)
evaluate_st(val_dataset, finetuned_model_path, name='finetuned')
print("파인튜닝 모델 평가 완료")

파인튜닝 모델 적중률: 1.0000

파인튜닝 모델 평가 완료

 

# 원본 모델 평가
print("원본 모델 평가 시작...")
original_model_path = "BAAI/bge-m3"  # 원본 모델 ID
original_results = evaluate(
    val_dataset['queries'],
    val_dataset['corpus'],
    val_dataset['relevant_docs'],
    original_model_path
)
원본 모델 평가 시작...
Batches: 100%
 
 1/1 [00:04<00:00,  4.29s/it]
100%
 
 54/54 [00:01<00:00, 46.72it/s]

 

# 데이터프레임으로 변환
df_original = pd.DataFrame(original_results)
hit_rate_original = df_original['is_hit'].mean()
print(f"원본 모델 적중률: {hit_rate_original:.4f}")

# SentenceTransformer 평가 (추가 지표 계산)
evaluate_st(val_dataset, original_model_path, name='original')
print("원본 모델 평가 완료")

원본 모델 적중률: 0.9815

원본 모델 평가 완료

 

# 결과 비교
df_st_original = pd.read_csv('results/Information-Retrieval_evaluation_original_results.csv')
df_st_finetuned = pd.read_csv('results/Information-Retrieval_evaluation_finetuned_results.csv')

df_st_original['model'] = 'bge-m3'
df_st_finetuned['model'] = 'fine_tuned'
df_st_all = pd.concat([df_st_original, df_st_finetuned])
df_st_all = df_st_all.set_index('model')

print("\n모델 성능 비교:")
df_st_all

 

Accuracy
Accuracy는 검색 결과에서 상위 몇 개 안에 정답이 포함되었는지를 평가하는 지표입니다. 중요한 점은 정답이 포함되기만 하면 성공으로 간주된다는 것입니다. 예를 들어, Accuracy@5가 0.92라는 값은 전체 질문 중 약 92%에서 상위 5개의 결과 안에 정답이 하나라도 포함되었다는 뜻입니다. Accuracy는 검색 시스템이 얼마나 자주 정답을 포함하는지를 측정하며, 정답의 개수나 위치는 고려하지 않습니다.

예시:
질문에 대해 상위 5개의 검색 결과가 다음과 같다고 가정합시다.

  • 질문 1: [정답, 오답, 오답, 오답, 오답] → 포함 (성공)
  • 질문 2: [오답, 오답, 정답, 오답, 오답] → 포함 (성공)
  • 질문 3: [오답, 오답, 오답, 오답, 오답] → 미포함 (실패)

Accuracy@5 = 2/3 = 0.666, 즉 약 66.6%입니다.


Precision
Precision은 상위 검색 결과가 얼마나 "정확히 정답으로 이루어져 있는가?"를 평가합니다. Precision@k는 상위 k개의 검색 결과 중 정답이 차지하는 비율을 측정합니다. 예를 들어, Precision@5가 0.20이라는 값은 상위 5개의 결과 중 평균적으로 20%가 정답이라는 뜻입니다. Precision은 검색 결과가 불필요한 정보를 얼마나 적게 포함하고 있는지를 보여줍니다.

예시:
질문에 대해 상위 5개의 검색 결과가 다음과 같다고 가정합시다.

  • 질문 1: [정답, 오답, 오답, 오답, 오답] → Precision@5 = 1/5=0.2
  • 질문 2: [오답, 정답, 오답, 오답, 오답] → Precision@5 = 1/5=0.2
  • 질문 3: [오답, 오답, 오답, 오답, 오답] → Precision@5 = 0/5=0.0

평균 Precision@5 = 0.2+0.2+0.0 / 3=0.133.

 

Recall
Recall은 검색 결과가 얼마나 "포괄적으로" 정답을 포함하고 있는지를 평가합니다. Recall@k는 전체 정답 중 검색 결과 상위 k개 안에 포함된 정답의 비율을 나타냅니다. Recall은 정답을 놓치지 않고 얼마나 잘 찾아내는지를 보여줍니다. 이 데이터에서는 각 질문당 정답이 하나씩만 있기 때문에 Recall과 Accuracy의 값이 동일합니다.

예시:
질문 하나에 정답이 두 개 있다고 가정합시다.

  • 질문 1: [정답, 정답, 오답, 오답, 오답] → Recall@5 = 2/2=1.0
  • 질문 2: [정답, 오답, 오답, 오답, 오답] → Recall@5 = 1/2=0.5
  • 질문 3: [오답, 오답, 오답, 오답, 오답] → Recall@5 = 0/2=0.0.

평균 Recall@5 = 1.0+0.5+0.0 / 3=0.5.

 

NDCG (Normalized Discounted Cumulative Gain)
NDCG는 검색 결과에서 정답이 높은 순위에 배치될수록 높은 점수를 부여합니다. 이는 단순히 정답이 포함되었는지를 넘어서, 정답의 순위가 사용자에게 얼마나 유용한지를 평가하는 지표입니다. NDCG@10이 0.85라는 값은 정답이 대체로 높은 순위에 배치되었음을 의미합니다.

예시:

  • 질문 1: [정답, 정답, 오답] → NDCG = 1.0 (정답이 모두 상위에 있음)
  • 질문 2: [오답, 정답, 오답] → NDCG는 약 0.63 (정답이 두 번째 위치에 있음)
  • 질문 3: [오답, 오답, 정답] → NDCG는 약 0.39 (정답이 세 번째 위치에 있음).

평균 NDCG@3는 1.0+0.63+0.39 / 3 0.673.

 

MRR (Mean Reciprocal Rank)
MRR은 첫 정답이 나타난 순위의 역수를 평균낸 값입니다. 즉, 각 질문에 대해 정답이 검색 결과에서 처음 등장한 순위의 역수를 계산한 후, 모든 질문에 대해 그 값을 평균냅니다. MRR@10에서의 @10 상위 10개의 검색 결과까지만 고려한다는 의미입니다. 즉, 정답이 10위 이후에 등장하면 해당 질문은 계산에서 제외되거나 Reciprocal Rank는 0으로 간주됩니다. 이 지표는 사용자가 정답을 얼마나 "빠르게" 찾을 수 있는지를 평가합니다. MRR 값이 높을수록, 정답이 더 상위 순위에 배치되어 있다는 것을 의미합니다.

예시:
질문에 대해 상위 10개의 검색 결과가 다음과 같다고 가정합니다.

  • 질문 1: [정답, 오답, 오답, 오답, ...] Reciprocal Rank = 1/1=1.0 (정답이 1위에 있음)
  • 질문 2: [오답, 정답, 오답, 오답, ...] Reciprocal Rank = 1/2=0.5 (정답이 2위에 있음)
  • 질문 3: [오답, 오답, 정답, 오답, ...] Reciprocal Rank = 1/3=0.33 (정답이 3위에 있음)
  • 질문 4: [오답, 오답, 오답, ...] Reciprocal Rank = 0 (정답이 상위 10위 안에 없음)

평균 MRR@10은 1.0+0.5+0.33+0 / 4=0.458

요약: MRR@10에서 10은 검색 결과의 범위를 제한하는 역할을 합니다. 정답이 10위 안에 나타나지 않으면 해당 질문의 Reciprocal Rank는 0으로 계산됩니다. 이를 통해 MRR 값은 상위 검색 결과에서 정답이 얼마나 빠르게 나타나는지를 더욱 세부적으로 평가할 수 있습니다.

 

MAP (Mean Average Precision)
MAP는 각 정답을 찾을 때마다의 Precision 값을 계산하여 평균을 낸 값입니다. 검색 결과의 전반적인 정확도 일관성을 평가합니다. MAP@100에서 @100은 검색 결과의 상위 100개 항목까지만 Precision 값을 계산한다는 의미입니다. 정답이 101위 이후에 있다면 해당 정답은 계산에서 제외됩니다. 이는 평가 범위를 제한함으로써 특정 상위 결과 내에서의 성능을 측정합니다.

예시:
질문에 대해 상위 5개의 결과 중 [정답, 정답, 오답, 오답, 정답].

  • 첫 번째 정답을 찾았을 때: Precision@1 = 1/1=1.0
  • 두 번째 정답을 찾았을 때: Precision@2 = 2/2=1.0
  • 세 번째 정답을 찾았을 때: Precision@5 = 3/5=0.6

MAP@5 = 1.0+1.0+0.6 / 3=0.866

MAP@100의 의미:
예를 들어, MAP@100 = 0.818이라는 값은, 상위 100개의 검색 결과 내에서 정답을 찾을 때마다 계산된 Precision 값의 평균이 0.818이라는 뜻입니다. 이는 시스템이 상위 100개의 결과에서 정답을 얼마나 정확하고 일관되게 제공하는지를 평가합니다.

728x90

'AI' 카테고리의 다른 글