1. RAG (Retrieval-Augmented Generation) 란?
RAG(Retrieval-Augmented Generation) 기법은 기존의 대규모 언어 모델(LLM)을 확장하여, 주어진 컨텍스트나 질문에 대해 더욱 정확하고 풍부한 정보를 제공하는 방법입니다. 모델이 학습 데이터에 포함되지 않은 외부 데이터를 실시간으로 검색(retrieval)하고, 이를 바탕으로 답변을 생성(generation)하는 과정을 포함합니다. 특히 환각(생성된 내용이 사실이 아닌 것으로 오인되는 현상)을 방지하고, 모델이 최신 정보를 반영하거나 더 넓은 지식을 활용할 수 있게 합니다.
2. RAG 모델의 기본 구조
검색 단계(Retrieval Phase): 사용자의 질문이나 컨텍스트를 입력으로 받아서, 이와 관련된 외부 데이터를 검색하는 단계입니다. 이 때 검색 엔진이나 데이터베이스 등 다양한 소스에서 필요한 정보를 찾아냅니다. 검색된 데이터는 질문에 대한 답변을 생성하는데 적합하고 상세한 정보를 포함하는 것을 목표로 합니다.
생성 단계(Generation Phase): 검색된 데이터를 기반으로 LLM 모델이 사용자의 질문에 답변을 생성하는 단계입니다. 이 단계에서 모델은 검색된 정보와 기존의 지식을 결합하여, 주어진 질문에 대한 답변을 생성합니다.
3. RAG 모델의 장점
풍부한 정보 제공: RAG 모델은 검색을 통해 얻은 외부 데이터를 활용하여, 보다 구체적이고 풍부한 정보를 제공할 수 있습니다.
실시간 정보 반영: 최신 데이터를 검색하여 반영함으로써, 모델이 실시간으로 변화하는 정보에 대응할 수 있습니다.
환각 방지: 검색을 통해 실제 데이터에 기반한 답변을 생성함으로써, 환각 현상이 발생할 위험을 줄이고 정확도를 높일 수 있습니다.
4. RAC 구현
1) 데이터 로드(Load Data)
RAG에 사용할 데이터를 불러오는 단계입니다. 외부 데이터 소스에서 정보를 수집하고, 필요한 형식으로 변환하여 시스템에 로드합니다. 예를 들면 공개 데이터셋, 웹 크롤링을 통해 얻은 데이터, 또는 사전에 정리된 자료일 수 있습니다. 가져온 데이터는 검색에 사용될 지식이나 정보를 담고 있어야 합니다.
다음 예제는 langchain_community.document_loaders 모듈에서 WebBaseLoader 클래스를 사용하여 특정 웹페이지(위키피디아 정책과 지침)의 데이터를 가져오는 방법을 보여줍니다. 웹 크롤링을 통해 웹페이지의 텍스트 데이터를 추출하여 Document 객체의 리스트로 변환합니다.
# Data Loader - 웹페이지 데이터 가져오기
from langchain_community.document_loaders import WebBaseLoader
# 위키피디아 정책과 지침
url = 'https://ko.wikipedia.org/wiki/%EC%9C%84%ED%82%A4%EB%B0%B1%EA%B3%BC:%EC%A0%95%EC%B1%85%EA%B3%BC_%EC%A7%80%EC%B9%A8'
loader = WebBaseLoader(url)
# 웹페이지 텍스트 -> Documents
docs = loader.load()
print(len(docs))
print(len(docs[0].page_content))
print(docs[0].page_content[5000:6000])
2) 텍스트 분할(Text Split)
불러온 데이터를 작은 크기의 단위(chunk)로 분할하는 과정입니다. 자연어 처리(NLP) 기술을 활용하여 큰 문서를 처리가 쉽도록 문단, 문장 또는 구 단위로 나누는 작업입니다. 검색 효율성을 높이기 위한 중요한 과정입니다.
다음 코드는 RecursiveCharacterTextSplitter라는 텍스트 분할 도구를 사용하고 있습니다. (이 도구에 대해서는 Text Splitter 챕터에서 상세하게 다룰 예정입니다. ) 간략하게 설명하면 12552 개의 문자로 이루어진 긴 문장을 최대 1000글자 단위로 분할하는 것입니다. 200글자는 각 분할마다 겹치게 하여 문맥이 잘려나가지 않고 유지되게 합니다. 실행 결과를 보면 18개 조각으로 나눠지게 됩니다.
LLM 모델이나 API의 입력 크기에 대한 제한이 있기 때문에, 제한에 걸리지 않도록 적정한 크기로 텍스트의 길이를 줄일 필요가 있습니다. 그리고, 프롬프트가 지나치게 길어질 경우 중요한 정보가 상대적으로 희석되는 문제가 있을 수도 있습니다. 따라서, 적정한 크기로 텍스트를 분할하는 과정이 필요합니다.
# Text Split (Documents -> small chunks: Documents)
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
print(len(splits))
print(splits[10])
3) 인덱싱(Indexing)
분할된 텍스트를 검색 가능한 형태로 만드는 단계입니다. 인덱싱은 검색 시간을 단축시키고, 검색의 정확도를 높이는 데 중요한 역할을 합니다. LangChain 라이브러리를 사용하여 텍스트를 임베딩으로 변환하고, 이를 저장한 후, 저장된 임베딩을 기반으로 유사성 검색을 수행하는 과정을 보여줍니다. (이 과정에 대해서는 다음 챕터에서 상세하게 다룰 예정입니다. )
간략하게 설명하면 OpenAI의 임베딩 모델을 사용하여 텍스트를 벡터로 변환하고, 이를 Chroma 벡터저장소에 저장합니다. vectorstore.similarity_search 메소드는 주어진 쿼리 문자열("격하 과정에 대해서 설명해주세요.")에 대해 저장된 문서들 중에서 가장 유사한 문서들을 찾아냅니다. 이때 유사성은 임베딩 간의 거리(또는 유사도)로 계산됩니다. 4개의 문서가 반환되는데, 가장 유사도가 높은 첫 번째 문서를 출력하여 확인합니다.
# Indexing (Texts -> Embedding -> Store)
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
vectorstore = Chroma.from_documents(documents=splits,
embedding=OpenAIEmbeddings())
docs = vectorstore.similarity_search("격하 과정에 대해서 설명해주세요.")
print(len(docs))
print(docs[0].page_content)
4) vectorStore를 검색(Retrieval) 클래스로 변환할 수도 있습니다.
사용자의 질문이나 주어진 컨텍스트에 가장 관련된 정보를 찾아내는 과정입니다. 사용자의 입력을 바탕으로 쿼리를 생성하고, 인덱싱된 데이터에서 가장 관련성 높은 정보를 검색합니다. LangChain의 retriever 메소드를 사용합니다.
5) 생성(Generation)
검색된 정보를 바탕으로 사용자의 질문에 답변을 생성하는 최종 단계입니다. LLM 모델에 검색 결과와 함께 사용자의 입력을 전달합니다. 모델은 사전 학습된 지식과 검색 결과를 결합하여 주어진 질문에 가장 적절한 답변을 생성합니다.
검색과 생성 단계를 수행하는 다음 코드를 살펴보겠습니다. vectorstore.as_retriever() 메소드는 Chroma 벡터 스토어를 검색기로 사용하여 사용자의 질문과 관련된 문서를 검색합니다. format_docs 함수는 검색된 문서들을 하나의 문자열로 반환합니다. RAG 체인을 구성하고, 주어진 질문에 대한 답변을 생성합니다.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# Prompt
template = '''Answer the question based only on the following context:
{context}
Question: {question}
'''
prompt = ChatPromptTemplate.from_template(template)
# LLM
model = ChatOpenAI(model='gpt-3.5-turbo-0125', temperature=0)
# Rretriever
retriever = vectorstore.as_retriever()
# Combine Documents
def format_docs(docs):
return '\n\n'.join(doc.page_content for doc in docs)
# RAG Chain 연결
rag_chain = (
{'context': retriever | format_docs, 'question': RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# Chain 실행
rag_chain.invoke("격하 과정에 대해서 설명해주세요.")
'AI * ML' 카테고리의 다른 글
LangChain 기본 - 임베딩(Embedding), 벡터저장소(VectorStore) (0) | 2024.07.03 |
---|---|
LangChain 기본 - 체인(Chains) (0) | 2024.07.03 |
LangChain 기본 - LLM 체인의 구성 요소 (0) | 2024.06.30 |
LangChain 시작하기 - Agent에 대해서 (0) | 2024.06.29 |
LangChain 시작하기 - 대화내용을 Context로 가지는 Retriever 설정 (0) | 2024.06.23 |