AI 부트캠프 - 33일차

Cookie Baking·2024년 11월 19일

AI 부트 캠프 TIL

목록 보기
26/42

RAG 성능 올리기

기본 모델의 성능을 살펴보기 위한 기본 코드를 돌려보았는데 여러 질문을 해 본 결과 꽤나 잘못된 정보를 답변하는 경우가 존재했다.
택한 데이터가 논문이었기 때문에 챗봇으로 활성화를 했을 경우 치명적일 것이라고 판단했다.

대대적으로 전처리부터 다시했다.

전처리

  • 논문 특성상 "\n" 이 꽤나 존재했다. 삭제할까 생각했는데 해당 문자를 추가함으로 인해 추가되는 토큰이 유의미하다고 생각했다. story가 아니기 때문에
  • 논문 말단 부분에 표가 존재했는데 처음에는 이를 함께 포함해서 RAG를 구성했다. 대충봤을 때 열에 따라 제대로 value가 표현되어있다고 판단했는데 오산이었다.
    열에 맞춰 value가 표현되지 않았다. 이는 hallcination을 이끌었다.
    때문에 애초에 잘못된 Documnet를 구성하게 하는 표 데이터를 docs에서 제거했다.
import re

def remove_table_section(text):
    # "표"라는 단어가 포함된 부분부터 그 이후의 내용 제거
    text_without_table = re.split(r"\n표", text, maxsplit=1)[0]
    return text_without_table.strip()

for doc in docs:
    doc.page_content = remove_table_section(doc.page_content)

청크로 나누기

  • chunk_size=100: 각 청크의 길이를 100글자로 지정함 이때 한국어, 영어 모두 같은 동일하게 100글자 제한이 적용됨, 논문의 문장의 길이를 살펴본 결과 짧은 문장 구분을 하고 있다는 특징을 파악해 이에적합한 청크 길이를 선택함 (문장의 단위 자체 가 긴 글의 경우 보통 512 토큰을 할당한다고 함)
  • chunk_overlap=10 : 청크 간 10글자씩 겹침, 앞 뒤로 해당 겹침은 적용이 되며 이는 모델의 청크 간의 문맥을 잃지 않도록 도와줌 (긴 글의 경우 보통 100토큰)
  • length_function=len : 각 청크의 길이를 측정할 때 len함수를 이용해 계산함
  • is_sperate_regex=False : 구분자로 정규식을 사용하지 않음, 복잡하지 않은 패턴 매칭일 경우에 사용함

CharacterTextPlitter : 텍스트를 나눌 때 사용할 구분자를 지정해서 나누는 방법
RecursiveCharacterTextSplitter : 단일 구분자 기준으로 텍스트를 분할하는 것이 아닌 우선순위에 따라 재귀적으로 적용하여 텍스트를 나눔


결론 : 불러온 문서는 논문임. 때문에 문장단위 유사도보다 문단별 유사도를 통해 찾는 것이 더 적합하다는 판단을 함

Retriever 선택

  • 기본 유사도 기반 FAISS retriever를 사용한 것에 더해 BM25 retriever를 함께 앙상블 시킴
  • BM25Retriever는 단어 빈도 기반 점수 계산 retriever임
  • 앙상블의 비율은 0.5씩 할당해 단어 빈도 점수, FAISS 기반 유사도가 높은 상위 5개의 문서가 추출됨

질문

아래 질문들의 경우 표 데이터를 제거한 후 제대로 추출할 수 있었던 질문, 표 데이터도 같은 문맥의 문서로 취급되었기 때문

1. Open  Ko-LLM  Leaderboard에는 어떤 기업들이 참여하고 있어?
2. Open  Ko-LLM  Leaderboard에는 카카오가 참여하고 있어?


아래 질문들의 경우 retriever에 BM25를 적용함으로 제대로 추출할 수 있었던 질문, 적용하지 않을 시 Hullcination을 일으킴



3. 한국의 LLM 리더보드에 ETRI가 참여하고 있어?
4. 한국의 LLM 리더보드에 카카오가 참여하고 있어?
5. Open  Ko-LLM  Leaderboard에는 카카오가 참여하고 있어?
6. Open  Ko-LLM  Leaderboard에는 ETRI가 참여하고 있어?
7. 카카오의 인공지능 윤리 원칙에 책임성이 포함되어 있어?

이를 통해 RAG의 성능을 원하는 수준까지 올릴 수 있게 되었다.
더 높은 성능을 원한다면 프롬프트 엔지니어링을 고차원하는 방법이 있을 수 있겠다.


프롬프트 라이브러리로부터 pull

# set the LANGCHAIN_API_KEY environment variable (create key in settings)
from langchain import hub
import os
from dotenv import load_dotenv

load_dotenv()
langchain_api_key = os.getenv("LANGCHAIN_API_KEY")

# Langchain 라이브러리에서 원하는 프롬프트 댕겨오기

os.environ["LANGCHAIN_API_KEY"] = langchain_api_key

prompt = hub.pull("rlm/rag-answer-hallucination")

# 프롬프트 텍스트만 추출
prompt_text = prompt.messages[0].prompt.template

# 댕겨온 프롬프트 template 저장
output_dir = "Prompts"
output_path = os.path.join(output_dir, "prompt3.txt")

# 프롬프트 데이터를 파일에 저장
with open(output_path, "w", encoding="utf-8") as file:
    file.write(prompt_text)

print(f"Prompt saved to {output_path}")

main.ipynb

output_dir = "Prompts"
output_path = os.path.join(output_dir, "prompt3.txt")

promt_txt = ""
# 프롬프트 데이터를 파일에 저장
with open(output_path, "r", encoding="utf-8") as file:
    promt_txt = file.read()

promt_txt

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough


# 프롬프트 템플릿 정의 (prompt1, prompt3)
contextual_prompt = ChatPromptTemplate.from_messages([
    ("system", promt_txt),
    ("user", "Context: {context}\\n\\nQuestion: {question}")
])

0개의 댓글