본문 바로가기
딥러닝

[딥러닝] 언어 모델 활용 (2) RAG _ 20241106

by 황오독 2024. 11. 6.

LLM 모델 학습의 어려움

 

1. RAG : Retrieval Augmented Generation

- 생성모델 + 검색 시스템 => 검색 기반 생성 모델

- 나의 데이터를 가지고 직접 학습시킨다. Modeling
사전 학습된 LLM 나의 데이터를 가지고 추가 학습시킨다. Fine-tuning
나의 데이터를 가지고 답변 시킨다. RAG

출처 : https://www.linkedin.com/pulse/power-your-enterprise-llm-fine-tuning-vs-retrieval-augmentation-pgvif

- LLM with RAG

① 사용자 질문을 받음

지식DB에서 답변에 필요한 문서 검색

③ 필요한 문서를 포함한 프롬프트 생성

④ LLM이 답변 생성하기

 

1) Vector DB : 대규모 텍스트 데이터 및 임베딩 벡터를 저장, 검색용

① 사용자 질문을 받음  지식DB에서 답변에 필요한 문서 검색
임베딩 : 벡터로 변환(질문 벡터)
토크나이저 + 임베딩 전처리를 통해
[질문 벡터]와 DB 내 저장된 [문서 벡터]와 유사도 계산
가장 유사도가 높은 문서 n개 찾기

 

 (1) Vector DB 구축 절차 (Chroma DB)

① 텍스트 추출
Document Loader
② 텍스트 분할
Text Splitter
③ 텍스트 벡터화
Text Embedding
④ Vector DB로 저장
Vector Store
다양한 문서 (word, pdf, web page 등)로부터 텍스트 추출 chunk 단위로 분할(문장,문단)
Document 객체로 만들기
- -

 

 (2) DB 구성

# 임베딩 모델 지정
embedding = OpenAIEmbedding(model='text-embedding-ada-002')

# DB 경로 지정
database = Chroma(persist_directory = path + 'db', # 임베딩 저장할 디렉토리 설정
			      embedding_function = embeddings)

 

 (3) Insert 방법

   ① 단순 텍스트 입력(.add_texts()) : 각 단위 텍스트를 리스트 형태로 입력

input_list1 = ['test 데이터 입력1', 'test 데이터 입력2']

# 입력 시 인덱스 저장 (조회시 사용)
ind = database.add_texts(input_list1)

 

   ② 텍스트와 메타데이터 입력 (.add_documents()) : 각 단위 텍스트와 메타정보를 함께 입력

input_list2 = ['오늘 날씨는 매우 맑음.', '어제 주가는 큰 폭으로 상승했습니다.']
metadata = [{'category':'test'}, {'category':'test'}] # category라는 키로 'test' 값을 할당

# 각 문서의 텍스트(input_list2)와 해당 문서의 메타데이터(metadata) 결합하여 Document 객체 생성
doc2 = [Document(page_content = input_list2[i], metadata = metadata[i] for i in range(len(input_list2))]

# Chroma 데이터베이스에 추가
ind2 = database.add_document(doc2)

 

* database 내부 구성

   ① 단순 텍스트 입력(.add_texts())    ② 텍스트와 메타데이터 입력 (.add_documents()) 

 

 (4) 조회

# 전체 조회
database.get() # 전부 조회하는 방법이나, 실무에선 많이 쓰이진 않는다. (너무 방대하기 때문)

# 인덱스 조회
database.get(index)

# 조건 조회 : ChromaDB에서는 제공하지 않아, Dataframe으로 변환 후 사용 가능
data = database.get()
data = pd.DataFrame(data)
data.loc[data['metadatas']=={'category':'test'}]

 

 

2. 유사도

 : Chroma DB를 이용하여 검색 시 계산되는 유사도 점수 (Similarity Score)

  - Cosin Distance 이용 (1에 코사인 유사도를 뺀 값)

    : Cosine Distance(a,b) = 1 - Cosine Similarity(a,b)

    : Cosine Similarity (-1 ~ 1 범위) => Cosine Distance (0~2 범위)

    : Cosine Distance가 0에 가까울 수록 유사도가 높다.

# .similarity_search_with_score()

# 문서 조회
query = '오늘 낮 기온은?' # 질문할 문장
k = 3 # 유사도 상위 k 개 문서 가져오기

# 데이터베이스에서 유사도가 높은 문서를 가져옴
result = database.similarity_search_with_score(query, k=k)
print(result)

for doc in result:
	print(f'유사도 점수 : {round(doc[1], 5)}, 문서내용 : {doc[0].page_content}')

 

더보기

* 참고

.similarity_search_with_score(query, k=k)는 튜플 형태로 반환

(문서내용, 유사도점수)

=> doc[0] : 문서내용 , doc[1] : 유사도 점수

 

3. RAG 구성 함수

1) Retrieval QA : RAG용 QA chat 함수

- llm : 언어 모델

- retriver : RAG로 연결한 VectorDB (검색기)

- return_source_documents=True : 모델이 답변을 생성할 뿐만 아니라 그 답변에 사용된 출처 문서(document)도 함께 반환

chat = ChatOpenAI(model='gpt-3.5-turbo')
retriever = database2.as_retriever()
qa = RetrievalQA.from_llm(llm=chat, retriever=retriever, return_source_documents=True)

query='생성형 AI 도입 시 예상되는 보안 위협은 어떤 것들이 있어?'
result=qa(query)

print(result['result'])

 

 2) Memory

  - 이전 질문 답변을 Memory에 저장하고, 이를 Prompt에 포함시킨다.

# ConversationBufferMemory

from langchain.memory import ConversationBufferMemory

# 메모리 선언하기(초기화)
memory = ConversationBufferMemory(retrun_messages=True)

# 저장
memory.save_context({'input':'안녕하세요!'}.
					'output':'안녕하세요! 어떻게 도와드릴까요?'})
memory.save_context({'input':'메일을 써야하는데 도와줘'}.
					'output':'누구에게 보내는 어떤 메일인가요?}')

# 현재 담겨 있는 메모리 내용 전체 확인
memory.load_memory_variables({})

 

qa = RetrievalQA.from_llm(llm=chat, retriever=retriever, return_source_documents=True)

query = '생성형 AI 도입시 예상되는 보안 위협은 어떤 것들이 있어?'
result = qa(query)

memory = ConversationBufferMemory(return_messages=True)

memory.save_context({'input':query},
					{'output':result['result']})
                    
memory.load_memory_variables({})

 

3) Chain : 각 모듈 모두 연결해보기

- LLM, retriever, memory

- ConversationalRetrieverChain 함수

qa = ConversationalRetrievalChain.from_llm(llm=chat, retriever=retriever, memory=memory,
										  return_source_documents=True, output_key='answer')
# Chain 함수를 위한 memory 설정
memory = ConversationBufferMemory(memory_key='chat_history', input_key='question', output_key='answer',
								  return_messages=True)

 

 

4. 종합실습 : KT AIVLE SCHOOL FAQ 챗봇 만들기

1) 환경 준비

# 구글 드라이브 연결 (langchain 폴더 우성 생성하고, 제공받은 파일 다운로드 해놓기)
from google.colab import drive
drive.mount('/content/drive')

# 라이브러리
!pip install -r /content/drive/MyDrive/langchain/requirements.txt

import pandas as pd
import numpy as np
import os
import sqlite3
from datetime import datetime

import openai

from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage, SystemMessage, Document
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA, ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

# OpenAI API Key 등록
def load_api_key(filepath):
    with open(filepath, 'r') as file:
        return file.readline().strip()

path = '/content/drive/MyDrive/langchain/'

# 텍스트 파일로 저장한 API 키 로드 및 환경변수 설정
openai.api_key = load_api_key(path + 'api_key.txt')
os.environ['OPENAI_API_KEY'] = openai.api_key
print(openai.api_key[:15])

2) Vector DB 만들기

data.head()

data = pd.read_csv(path + 'aivleschool_qa.csv', encoding='UTF-8')
data = pd.DataFrame(data)
data.head()

# 벡터 데이터 베이스
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

database = Chroma(persist_directory = path + "./db3",  # 경로 지정(구글 드라이브에서 db 폴더 생성)
                    embedding_function = embeddings)  # 임베딩 벡터로 만들 모델 지정
                    
# 데이터 입력
ids = database.get() 
if ids['ids']:
  database.delete(ids=ids['ids']) # 기존 입력 있으면 제거

# 질문 리스트로 변환
qa_list = data['QA'].tolist()
cat_list = [{'category':text} for text in data['구분'].tolist()]

# 각 행의 데이터를 Document 객체로 변환
documents = [Document(page_content=qa_list[i], metadata = cat_list[i]) for i in range(len(qa_list))]

database.add_documents(documents)

 

 3) RAG+LLM 모델

# 모델 선언
chat = ChatOpenAI(model="gpt-3.5-turbo")
k=3

# 리트리버 선언
retriever = database.as_retriever(search_kwargs={"k":k}) # 검색 설정 인자 3개로 지정

# 대화 메모리 생성
memory = ConversationBufferMemory(memory_key="chat_history",
                                  input_key="question",
                                  output_key="answer",
                                  return_messages=True)

# ConversationalRetrievalQA 체인 생성
qa = ConversationalRetrievalChain.from_llm(llm=chat, retriever=retriever, memory=memory,
                                           return_source_documents=True, output_key='answer')

 

4) 모델 사용

# 질문
query1 = "취업상태에 에이블스쿨을 지원할 수 있나요?"


# 답변
result = qa(query1)
result['answer']
# 네, KT 에이블스쿨은 미취업자를 대상으로 하며, 교육 시작일 기준으로 재직자는 지원이 불가능합니다.

 

 

## 참고1. 토크나이저와 벡터 DB 청크의 차이점