안녕하세요, 저는 최근 SK Devocean OpenLab [LLMOps]에 참여하게 되었는데요!!
이번 포스팅에서는 앞으로 OpenLab에서 스터디하게 되는 AutoRAG에 대해 소개해드리겠습니다. 🙂
0️⃣ RAG에 대해 아시나요?
요즘 비즈니스나 여러 프로젝트를 살펴보면 심심찮게 LLM이라는 단어를 만나볼 수 있는데요. 실제 비즈니스관점에서 LLM을 서비스로 구현할 때 가장 고민되는 점이 무엇이라 생각하시나요?
대다수의 사람들이 가장 먼저 언급하는 점 중 하나는 LLM의 환각현상으로 인한 답변의 불안정성입니다.
현재 LLM은 오랜 시간동안 대량의 데이터를 학습하는 구조로 빠르게 발전하고 있는데요. 학습하는데 쓰인 데이터는 시간이 지날수록 예전의 정보가 되고, 새롭게 만들어지는 정보들(ex. 자사 신규 서비스, 제품 등)에 대해서는 학습이 반영되어 있지 않다 보니 이러한 정보에 관련된 질문을 하게 될 경우 부정확한 정보를 답변할 가능성이 높아지게 됩니다. 이는 비즈니스적 관점에서 보면 상당히 치명적이라고 볼 수 있습니다.
그렇다면, 최신 정보를 반영하기 위해선 fine-tuning만이 정답일까요? ❎❎❎
작년 LLM이 각광받은 순간부터 같이 떠오르는 기술이 한 가지 있는데요. 바로 이러한 문제를 해결하기 위해 대표적인 기술로 떠오르는 RAG 입니다!
Retrieval-augmented generation(RAG; 검색증강생성)은 LLM 학습에 사용된 데이터 외에 추가 데이터를 통하여 LLM의 지식을 보강하는 기술을 말합니다.
1️⃣ 기본적인 RAG 아키텍처
그렇다면 이렇게 핫해진 RAG의 아키텍처에 대해 간단히 소개만 하고 넘어가 보겠습니다.
RAG는 크게 Indexing과 Retrieval and Generation의 2 가지 단계로 구분이 됩니다.
1.Indexing
Indexing 단계에서는 특정 데이터 소스로부터 데이터를 모아 인덱싱하는 파이프라인을 진행하게 되는데요.
가장 대표적인 과정으로 Vector Database 공간에 데이터를 인덱싱하는 Load - Split - Embed - Store 과정으로 진행됩니다.
2. Retrieval and Generation
Retrieval은 사용자의 질문 query로 앞서 indexing 단계에서 저장한 데이터를 검색해서 데이터를 획득하는 단계입니다.
Generation은 사용자의 질문 query와 retrieval로 검색된 데이터를 LLM에 Prompt Injection 하여 답변을 생성하는 단계입니다.
2️⃣ RAG의 3가지 패러다임
RAG기술은 현재 많은 관심 속에 끊임없이 발전하고 있는데요.
그중 주요 3가지 패러다임에 대해 소개해드리려 합니다.
기본 RAG(Naive RAG)
바로 위에서 RAG 개념에 대해 설명할 때 사용했던 패러다임입니다.
가장 초창기의 방법론으로서, 전통적인 인덱싱, 검색 및 생성과정을 포함하고 있습니다.
기본 RAG의 경우 LLM만 사용하는 것보다 성능이 뛰어났지만 낮은 검색 정확도, 잘못된 문맥 통합 등 여러 가지 방면에서 다소 아쉬운 측면이 존재했습니다.
고급 RAG(Advanced RAG)
기본 RAG의 부족한 점을 개선하기 위해 개발된 패러다임으로 고급 RAG가 있습니다. 고급 RAG에서는 검색 및 생성의 질을 향상사키기 위해 기본 RAG에서 사전 및 사후 검색 방법이 추가된 패러다임입니다.
고급 RAG는 크게 검색 전 절차(Pre-Retrieval Process0, 검색 후 절차(Post-Retrieval Process), RAG 파이프라인 최적화(RAG Pipeline Optimization)의 3단계로 나뉘어서 볼 수 있습니다.
모듈형 RAG(Modular RAG)
모듈식 RAG는 고급 RAG의 발전된 형태로 기존의 RAG 프레임워크에서 한 단계 더 나아가 다양한 모듈과 기능을 통합하여 더 큰 다양성과 유연성을 제공하는 패러다임 형태입니다.
3️⃣ Auto의 필요성
지금까지 우리는 RAG란 무엇인지 그리고 RAG 기술 발전에 따른 3가지 패러다임이 존재한다는 것을 알게 되었는데요. 기억해야 할 점은 RAG는 데이터와 목적에 따라 어울리는 조합이 모두 다르다는 것입니다!!
따라서, 최적의 조합을 찾기 위해서는 많은 실험과 평가를 반복하는 과정이 필수적으로 따라오게 되는데요.
기본 RAG 구조로 생각해 보았을 때,
위 5가지 조합만으로도 360,000,000+ 의 조합이 생기게 됩니다...
우린.. 이 모든 조합을 당. 연. 히 실험을 해볼 수 없기에….. ㅠ퓨ㅠㅠㅜ
경험적인 측면과 데이터에 맞게 일부 조합만을 선택해서 하게 될 확률이 높은데요. 이 또한 반복적인 작업이 지속적으로 필요하게 될 텐데요. 어떠신가요? 자동화의 필요성이 느껴지시지 않나요!? 🧐
4️⃣ AutoRAG 란
AutoRAG는 이러한 다양하고 반복적인 실험작업을 자동으로 최적화해 주기 위해 탄생한 모듈입니다.
이러한 AutoRAG의 몇 가지 특징에 대해 소개해 드리자면 다음과 같습니다.
- ML을 자동으로 최적화해주는 AutoML처럼 RAG의 파이프라인을 자동으로 최적화해 줍니다.
- 특히 RAG 파이프라인의 각 단계들을 자동으로 평가하여 최적의 파이프라인을 제시해 줌으로써 시간비용을 절감해 줄 수 있습니다.
- 다만, 현재 존재하는 모든 조합의 경우의 수를 평가해 줄 순 없지만 현재 12개의 모듈을 지원하며 이는 960 여 가지의 조합에 대해 테스트 및 검증해 줄 수 있습니다.
5️⃣ 그래서 AutoRAG는 어떻게 쓰는 건데?
앞으로 OpenLAB Study - LLMOps에서 AutoRAG에 대해 조금씩 알아보는 시간을 가져볼 텐데요.
앞으로 알아가 볼 목차에 대해 소개해 드리겠습니다!
- Data Creation
- Optimization
- Query Expansion
- Retrieval
- Passage_Reranker
- Passage_Compressor
- Prompt Marker
- Generator
- Passage Filter
사실.. 글이 길어졌지만 이번 포스팅에서 AutoRAG 목차 중 첫 번째, Data Creation에 대해 추가로 정리해보려 합니다.
본격적으로 설명하기 앞서 Data를 무엇을 위해 준비해야 하는 걸까요? 🧐
위에서 언급했던 RAG 파이프라인을 최적화해 주기 위해서는 “평가”라는 단계가 들어가야 합니다. 평가를 통해 각 단계 혹은 비교가 되는 방법들 간의 우위를 정할 수 있기 때문인데요.
그렇다면 이 평가는 무엇으로 진행이 될까요?
이 질문에 대한 답은 추후 포스팅에서 알아가 보기로 하고 지금은 이 평가를 하기 위한 데이터 세트를 준비하는 시간이라고 생각해 주시면 좋을 것 같습니다.
6️⃣ Dataset Format
먼저 AutoRAG를 사용하기 위해 준비해야 하는 Dataset format에 대해 알아보겠습니다.
Dataset 은 Corpus Dataset, QA Dataset 총 2 가지로 나뉘게 되는데요
1. Corpus Dataset
Corpus Dataset은 RAG에 사용할 문서를 이용하여 생성할 수 있습니다.
따라서 일반적으로 문서를 텍스트로 로드하고 이를 구절로 분할하는 작업을 거칩니다. 청크가 만들어지게 되면 각 토큰 텍스트들은 Corpus Dataset의 한 행이 될 수 있는데요. 이 각각의 행들이 실제 검색단계에서 찾게 되는 데이터가 됩니다.
Corpus Dataset은 doc_id, contents, metadata로 구성되어 있습니다.
doc_id: 각 구절에 대한 유니크한 식별자입니다.
contents: 실제 문서에서 청크 된 구절입니다.
metadata: 구절에 대한 메타데이터를 기록하는 스키마입니다.
2. QA Dataset
QA Dataset은 corpus Dataset을 이용해서 qa-generation을 거쳐 생성한 데이터셋을 의미합니다.
qa dataset에는 qid, query, retrieval_gt, generation_gt라는 스키마가 보장되어야 합니다.
각 스키마에 대한 설명은 다음과 같습니다.
qid: queries의 유니크한 식별자입니다.
query: RAG 시스템의 input으로 사용될 사용자의 질문 query입니다.
retrieval_gt: query를 사용하여 답변을 생성할 때 참고해야 하는 doc_id를 의미합니다. 실제 AutoRAG가 검색 성능을 평가할 때 이 스키마를 사용하기 때문에 중요한 스키마입니다.
generation_gt: LLM 모델이 생성할 것으로 기대하는 답변의 목록입니다.
💡 즉, 우리가 기대하는 이상적인 상황은 사용자가 query를 입력하면 RAG 시스템에서 retrieval_gt의 doc을 참고하여 generation_gt를 답변해 내는 것입니다.
아무래도 Dataset Format만 봐서는 이해하기가 쉽지 않으니, Dataset이 어떻게 생겼는지 살펴보겠습니다. 이번 데이터 생성에 사용된 데이터는 Devocean OpenLab에서 제공해 주신 json 데이터세트를 사용해 볼 예정입니다.
7️⃣ Corpus Dataset
먼저 Corpus Dataset 은
- llama_index
- langchain
위 2가지 프레임워크를 사용하여 쉽게 생성이 가능합니다.
1. llama_index 사용
def convert_to_corpus_with_llama(self, raw_data_path: str, output_path: str) -> DataFrame:
"""
Convert raw data to Corpus Dataset using LLAMA tool.
Args:
raw_data_path (str): Path to the raw data.
output_path (str): Path to save the Corpus Dataset.
"""
documents = SimpleDirectoryReader(raw_data_path).load_data()
nodes = TokenTextSplitter().get_nodes_from_documents(documents=documents, chunk_size=512, chunk_overlap=128)
corpus_df = llama_text_node_to_parquet(nodes, f"{output_path}/corpus.parquet")
return corpus_df
output 예시
doc_id: e64cd58a-09be-44ba-928f-185ccd0e049f
contents:
'제가 패소하고 이름도 바꿔야하나요 된다는사람도 있고 안된다는 사람도 있고 .... 뭔지 도대체 모르겠습니다",\n "permalink": "https://ceo.baemin.com/qna/6002",\n "category": "법률·지식재산권",\n "writer": "양정1동 빛나는 털발말똥가리" ….'
metadata
{'creation_date': '2024-05-05', 'file_name': 'Baemin.json', 'file_type': 'application/json', 'last_modified_date': '2024-05-05', ….}
위와 같은 형태로 Corpus Data가 생성이 됩니다.
2. langchain 사용
이번 예시에서 사용할 데이터는 json 형태이다 보니 별도의 전처리를 거치지 않으면 앞선 예시처럼 contents 안에 여러 key - value 값들이 들어가 있는걸 확인해보 실 수 있는데요.
이번에는 원하는 key 값만 contents에 넣어보도록 하겠습니다.
def convert_to_corpus_with_langchain(self, raw_data_path: str, output_path: str) -> DataFrame:
"""
Convert raw data to Corpus Dataset using LangChain tool.
Args:
raw_data_path (str): Path to the raw data.
output_path (str): Path to save the Corpus Dataset.
"""
documents = JSONLoader(
file_path=raw_data_path,
jq_schema=".[]",
content_key="content",
text_content=False,
metadata_func=extract_json_metadata).load()
documents = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=128).split_documents(documents)
corpus_df = langchain_documents_to_parquet(documents, f"{output_path}/corpus.parquet")
return corpus_df
def extract_json_metadata(record: dict, metadata: dict) -> dict:
metadata["title"] = record.get("title")
metadata["title_sub"] = record.get("title_sub")
metadata["permalink"] = record.get("permalink")
metadata["category"] = record.get("category")
metadata["writer"] = record.get("writer")
metadata["answer_writer"] = record.get("answer_writer")
if "source" in metadata:
source = metadata["source"].split("/")
source = source[source.index("docs"):]
metadata["source"] = "/".join(source)
return metadata
output 예시
doc_id: 91d950e7-7553-4700-a054-8417741a6453
contents: '안녕하세요, 로시컴-배민외식업광장 전문가 Q&A 노무사 이기쁨입니다.\n\n \n\n\n근로자의 해고와 관련하여 근로기준법 위반 등에 대해 문의한 것으로 보여집니다.\n\n …..’
metadata:
{'answer_writer': '이기쁨 노무사', 'category': '노무·고용/해고', 'last_modified_datetime': datetime.datetime(2024, 5, 5, 16, 59, 9, 14063), 'permalink': '
https://ceo.baemin.com/qna/6049
', 'seq_num': 1, 'source': 'raw_data/baemin/baemin.json', 'title': 'Q. 일용직인데 노동청에 신고했어요', 'title_sub': '일용직으로만 (근로계약서도안쓴다고하고 일당도현금으로만이체도안된다하고)일하시던분이 2~3일쉬고나오라고 다른사람 불렀더니 해고통지없이 짤랐다고 노동청에신고했는데 어떻게해야될까요.', 'writer': '성실한 두메방풍'}
이렇게 llama_index, langchain 프레임워크를 사용하여 손쉽게 Corpus Dataset을 만들 수 있습니다.
💡전처리에 따라서 생성되는 contents의 값 또한 바뀌기 때문에 항상 데이터 형식 및 목적에 맞게 전처리하는 과정을 염두에 두어야 합니다.
8️⃣ QA Dataset
다음으로는 Corpus Dataset으로 QA Dataset을 만들어 보겠습니다.
QA Dataset 은 custom prompt를 사용하거나 multiple prompts를 사용하여 생성할 수 있습니다.
1. custom prompt
def convert_to_qa(self, prompt: str = None,corpus_data_path: str = None, output_path: str = None) -> DataFrame:
"""
Convert Corpus Dataset to QA Dataset using Custom Prompt.
Args:
prompt (str): Custom Prompt.
corpus_data_path (str): Path to the Corpus Dataset.
output_path (str): Path to save the QA Dataset.
"""
corpus_df = pd.read_parquet(corpus_data_path)
llm = OpenAI(model='gpt-3.5-turbo', temperature=1.0)
qa_df = make_single_content_qa(corpus_df, content_size=50, qa_creation_func=generate_qa_llama_index, llm=llm,
prompt=prompt, question_num_per_content=1,
output_filepath=output_path)
return qa_df
- custom prompt를 사용 안 하는 경우
- custom prompt를 사용하는 경우
💡 prompt template에 따라 생성되는 QA Dataset이 달라질 수 있습니다.
2. multiple prompts
ratio_dict = {
"prompt_ko.txt": 2,
"prompt_en.txt": 2
}
def convert_to_qa_with_multi_prompt(self, radio_dict: dict[str, int], corpus_data_path: str, output_path: str) -> DataFrame:
"""
Convert Corpus Dataset to QA Dataset using Multi Custom Prompt.
Args:
radio_dict (dict[str, int]): Multi Custom Prompt.
corpus_data_path (str): Path to the Corpus Dataset.
output_path (str): Path to save the QA Dataset.
"""
corpus_df_lc = pd.read_parquet(corpus_data_path)
llm = OpenAI(model='gpt-3.5-turbo', temperature=1.0)
qa_df = make_single_content_qa(
corpus_df_lc, content_size=50, qa_creation_func=generate_qa_llama_index_by_ratio, llm=llm,
prompts_ratio=radio_dict, question_num_per_content=1,
output_filepath=output_path)
return qa_df
multiple prompts를 사용할 때는 단일 prompt를 사용하는 것과 다르게 한 가지 주의해야 하는 점이 있습니다.
radio_dict로 정의되어 있는 prompts_ratio의 경우 string 형태의 prompts template가 아닌 실제 prompt template가 저장된 .txt 파일을 key 값으로 가집니다.
또한 value인 2는 radio라는 변수명에서 알 수 있듯이 multiple prompt 내에서 생성되는 비율을 의미합니다.
💡 즉, 현재 예시에서는 ko, en 형식의 template을 2 : 2 비율인 50 : 50으로 사용한다라는 의미입니다.
3. RAGAS 사용
distributions = { # uniform distribution
simple: 0.25,
reasoning: 0.25,
multi_context: 0.25,
conditional: 0.25
}
def convert_to_qa_with_ragas(self, distributions: Optional[Dict[str, int]] = None, corpus_data_path: str = None, output_path: str = None) -> DataFrame:
"""
Convert Corpus Dataset to QA Dataset using RAGAS.
Args:
distributions (dict[str, int]): Question types by making distribution list.
corpus_data_path (str): Path to the Corpus Dataset.
output_path (str): Path to save the QA Dataset.
"""
corpus_df = pd.read_parquet(corpus_data_path)
qa_df = generate_qa_ragas(corpus_df, test_size=50, distributions=distributions)
return qa_df
마지막으로는 RAGAS를 사용하는 방법입니다.
RAGAS는 RAG 파이프라인을 평가하는 데 사용되는 프레임워크입니다. 이에 대한 자세한 설명은 추후 포스팅에서 다뤄보기로 하고 이번에는 간단히 RAGAS로 QA Dataset을 만드는 방법에 대해서만 소개해드리겠습니다.
RAGAS는 기존 방식과 달리 distributions라는 파라미터가 존재하는데요. 이 파라미터는 추론, 조건부, 다중 문맥, 대화형 의 방법으로 질문쿼리를 작성하게 해주는 파라미터라고 생각하시면 됩니다!
🦋 이렇게 RAG를 평가하기 위해 LLM을 이용해서 AutoRAG에서 사용하는 형태의 데이터를 생성하는 방법을 알아보았습니다.
다음 포스팅에서는 생성한 데이터세트를 이용하여 어떻게 RAG를 최적화하는지에 대해 알아보겠습니다! 긴 글 읽어주셔서 감사합니다!! 🙇♂️
References
- https://marker-inc-korea.github.io/AutoRAG/data_creation/tutorial.html
- https://python.langchain.com/docs/use_cases/question_answering/
- https://www.skelterlabs.com/blog/2024-year-of-the-rag
- https://velog.io/@jjlee6496/RAG란
- https://ko.upstage.ai/feed/tech/llm-rag-answer-verification
- https://news.hada.io/topic?id=12669
- https://discuss.pytorch.kr/t/rag-1-2/3135