RAG와 LC를 사용한 LLM의 긴 문맥 응용에 대한 가이드라인을 제공하겠다는 2024 최신 연구 논문.

최신 LLM의 max-token이 기하급수적으로 늘어나면서 Long-Context를 점점 더 잘 처리하고 있습니다. 예를 들어서 구글 Gemini 1.5의 경우 1백만 토큰까지 입력받을 수가 있고, LLama3.2 경량 모델의 경우 128K(12만8천) 토큰 입력이 가능합니다. 그래서 "RAG를 굳이 써야 될까?"라는 의문을 가진 사람도 있어요. RAG의 문제점이 많은데, 그냥 최신 LLM한테 문서를 통째로 주면 훨씬 더 일처리를 잘하니까 그렇죠. 그럼 RAG는 이제 쓸모가 없어질까?하는 의문도 들기 시작하고요.

하지만 입출력 token의 갯수에 비용이 비례하는 현재 상황에서 여전히 RAG는 cost efficiently하다는 커다란 메리트를 가지고 있고, 그래서 사람들은 연구개발을 계속하고 있습니다. 

이러한 상황 속에서, 이 논문은 RAG와 Long-Context (LC) LLM을 포괄적으로 비교하여 각각의 장점을 활용할 수 있는 SELF-ROUTE라는 방법을 제안합니다.


바쁘신 분은 맨 아래
'논문 핵심 요약'만 보세요.

Abstract

We benchmark RAG and LC across various public datasets using three latest LLMs. Results reveal that when resourced sufficiently, LC consistently outperforms RAG in terms of average performance. However, RAG’s significantly lower cost remains a distinct advantage.

세 가지 최신 LLM을 사용하여 다양한 공개 데이터셋에서 RAG와 LC를 벤치마크한 결과, 충분한 자원이 있을 때 LC가 평균 성능 면에서 RAG를 일관되게 능가한다는 것을 확인, 그러나 RAG의 훨씬 낮은 비용은 여전히 독특한 이점

Based on this observation, we propose SELF-ROUTE, a simple yet effective method that routes queries to RAG or LC based on model self-reflection. SELFROUTE significantly reduces the computation cost while maintaining a comparable performance to LC.

이러한 관찰을 바탕으로, 저자들은 모델의 자체 반성을 통해 RAG나 LC로 쿼리를 라우팅하는 간단하면서도 효과적인 방법인 SELF-ROUTE를 제안. SELF-ROUTE는 계산 비용을 크게 줄이면서 LC와 유사한 성능을 유지함.


1 Introduction

Therefore, we are motivated to compare RAG and LC, evaluating both their performance and efficiency. (중략...) we find that LC consistently outperform RAG in almost all settings (when resourced sufficiently).

요즘 LLM의 max token이 기하급수적으로 늘어나면서, RAG와 Long-Context LLM을 성능과 효율성 면에서 비교하고 싶었다고 함. 그래서 비교를 해 봤더니, RAG를 쓰는 것보다, 그냥 LLM한테 통으로 문서를 줬을 때 오히려 성능이 훨씬 더 좋게 나왔다.

Despite the suboptimal performance, RAG remains relevant due to its significantly lower computational cost. In contrast to LC, RAG significantly decreases the input length to LLMs, leading to reduced costs, as LLM API pricing is typically based on the number of input tokens. Moreover, our analysis reveals that the predictions from LC and RAG are identical for over 60% of queries. For these queries, RAG can reduce cost without sacrificing performance.

그치만 RAG를 쓰면 입력 토큰이 확 줄어들기 때문에 비용 면에서 무척 효율적이라는 장점은 무시할수가 없는데, 연구를 통해 Long Context LLM과 RAG의 답변이 60%가량 일치한다는 사실을 발견했고, 연구진들은 이 정도면 RAG의 답변도 상당히 수준급이라고 생각한 모양. 이 정도면 비용 절감을 고려했을 때 RAG는 여전히 메리트 있다는 것.

[Figure 1]을 보면 LC(Long Context LLM), RAG를 붙인 LLM, 그리고 저자들이 제안하는 Self-Route의 성능과 비용을 최신 LLM 모델별로 비교할 수가 있다. LC가 가장 비싸고 성능이 좋으며, RAG를 붙인 LLM은 싸지만 성능이 LC의 60%정도 수준이고, Self-Route는 비용이 그 중간 어디쯤에 있으며 성능은 LC에 준한다는 것.

Our analysis serves as a starting point, inspiring future improvements of RAG, and as a empirical guide for building longcontext applications using RAG and LC.

본인의 연구가 RAG의 향후 개선을 위한 출발점이자, RAG와 LC를 사용한 long-context 처리에 경험적인 가이드로서의 역할을 하기를 바란다고 함.


2 Related Work

입력 토큰 수가 많을수록 계산 비용이 기하급수적으로 증가하는 Transformer 구조의 한계 - 이를 해결하기 위해 프롬프트 압축, 모델 디스틸레이션, 단계별 모델 사용 등의 방법이 제안되어 왔음. 이후 Retrieval Augmented Generation (RAG)가 제안되었고, 최근에는 RAG의 정확성을 높이기 위해 검색 과정에 교정, 비판, 검증, 적응적 검색을 추가하는 방식이 연구됨.

Long-Context를 처리하는 LLM의 능력 평가를 위해 "LongBench", "∞Bench"와 같은 다양한 벤치마크가 제안되었으며, 이들 벤치마크는 여러 언어와 다양한 데이터셋을 포함해 긴 문맥에서의 모델 성능을 종합적으로 평가하도록 설계됨.

기존 연구에서 RAG와 LC를 비교한 결과, LC 모델이 RAG를 능가한다는 결론을 도출한 바 있음. 이 논문에서는 더 강력한 최신 LLM과 더 긴 문맥을 고려하여 다른 결과를 도출하고자 함. (Self-Route로 그 사이 절충안을 제시하는 것)


3 Benchmarking RAG versus LC

3.1 Datasets and metrics

평가 데이터

  • LongBench - 21개의 데이터셋으로 구성, 평균 7천 개의 단어 포함
  • ∞Bench - 더 긴 문맥을 포함해 평균 10만 개의 토큰을 포함
  • 연구는 영어로 된 실제 데이터를 중심으로 하고, LongBench와 ∞Bench중에서 쿼리가 포함된 데이터셋만 사용하여 총 9개의 데이터셋을 평가에 사용

평가 지표(evaluation metrics)

  • open-ended QA tasks - F1
  • multichoice QA tasks - Accuracy
  • summarization tasks - ROUGE

3.2 Models and Retrievers

  • 세 가지 최신 LLM인 Gemini-1.5-Pro, GPT-4O, GPT-3.5-Turbo를 평가에 사용했으며 Retriever는 Contriever와 Dragon을 사용(청킹:300단어/top-5선택)
최신 모델을 써서 굉장히 마음에 든다

3.3 Benchmarking results

  • 세 모델 모두에서 LC가 RAG보다 우수한 성능을 보였으며 특히 최신 모델일수록 성능 차이가 두드러졌음 - 이는 최신 LLM이 긴 문맥을 이해하는 능력이 뛰어나다는 것을 보여줌
  • 다만, ∞Bench의 두 긴 문맥 데이터셋에서는 GPT-3.5-Turbo에서 RAG가 LC보다 우수한 성능을 보여 예외적인 결과를 보임 - 이는 이 데이터셋이 평균 147,000 단어로 이루어져 있어 모델의 최대 입력 크기를 크게 초과했기 때문

4 Self-Route

4.1 Motivation

 

X축은 RAG와 LC의 예측 점수 차이(𝑆_{RAG} - 𝑆_{LC})를 나타내며, 0을 기준으로 왼쪽은 LC 점수가 더 높고, 오른쪽은 RAG 점수가 더 높은 경우를 의미

그래프는 X축의 0에 상당히 집중되어 있어, 대다수의 쿼리에 대해 두 모델의 예측이 유사하게 나타난다는 것을 확인할 수 있음 - RAG and LC predictions are highly identical, for both correct and incorrect ones - 맞는 답변도 틀린 답변도 상당히 유사하다고 함

이러한 관찰을 통해 저자들은 대부분의 쿼리에 대해 비용 효율적인 RAG를 활용하고, RAG가 성능을 발휘하지 못하는 소수의 쿼리에만 LC를 적용하는 방식의 필요성을 느낌 - 이를 통해 전체 성능을 유지하면서도 계산 비용을 크게 절감할 수 있을 것으로 기대


4.2 Self-Route

SELF-ROUTE는 LLM 자체가 제공된 문맥을 통해 질문에 답할 수 있는지 스스로 반성하는 방식으로 쿼리를 라우팅 (SELF-ROUTE utilizes LLM itself to route queries based on self-reflection) - Self-Route는 아래 2단계로 작동함.

  1. RAG-and-Route 단계
    • 쿼리와 검색된 청크들을 LLM에 제공하고, 쿼리에 답변할 수 있는지 여부를 예측하도록 요청
    • 만약 답변할 수 있다면, 답변을 생성
    • 일반적인 RAG와 차이점 : LLM은 다음의 프롬프트를 통해 답변을 거부할 수 있는 옵션을 가짐
      (
      given the option to decline answering)
      • “Write unanswerable if the query can not be answered based on the provided text”.
      • "제공된 텍스트에 기반해 쿼리를 답변할 수 없다면 'unanswerable'을 출력해라."
    • 답변이 가능하다고 판단된 쿼리들에 대해서는 RAG 예측을 최종 답변으로 수락함
    • 답변이 불가능하다고 판단된 쿼리들에 대해서는 두 번째 단계로 진행하여, 긴 문맥 LLM을 사용하여 최종 예측을 얻습니다. (providing the full context to the long-context LLMs to obtain the final prediction)
    • 대부분의 쿼리는 1단계에서 해결 가능 (e.g., 82% for Gemini-1.5-Pro)
  2. LC 예측 단계
    • 1단계에서 "답변 불가"로 분류된 쿼리에 대해서는 긴 문맥 LLM에 전체 문맥을 제공하여 답변을 생성

  1.  

4.3 Results

Table 1

모든 모델이 쿼리의 절반 이상을 RAG로 라우팅했으며, Gemini-1.5-Pro의 경우 응답 가능 비율이 81.74%에 달했음 → 이는 대부분의 쿼리를 LC 없이 RAG로 응답할 수 있음을 의미함함

높은 응답 가능 비율 덕분에 사용된 토큰 수가 크게 감소 (Table 1에서 1-5, 2-5, 3-5 token% 부분 확인)


5 Analysis

5.1 Ablations of k

RAG와 SELF-ROUTE는 쿼리 시 최상위 k개의 텍스트 청크를 검색하여 사용하는데, k 값이 증가할수록 성능이 향상되지만, 비용도 함께 증가함. 해당 연구의 경우 k=5에서 비용이 최소화 되었지만, 맡은 task와 dataset에 따라 이 부분은 직접 실험해야 할듯.

5.3 Different retrievers

연구에서 사용한 검색기 Contriever와 Dragon 모두에 대해 일관된 결과가 나왔음


논문 핵심 요약

  1. RAG를 쓰되, 답을 찾을 수 없는 경우에는 ‘unanswerable’이라고 답하도록 프롬프팅을 함. (1단계) ⭐
    • 1단계에서 ‘unanswerable’을 뱉는 경우는 많지 않음. 모든 모델이 쿼리의 절반 이상을 RAG로 라우팅했으며, Gemini-1.5-Pro의 경우 응답 가능 비율이 81.74%에 달했음.
  2. 만약 unanswerable이 출력되면? 리트리버로 검색된 context가 아닌 전체 full context를 LLM에게 던짐. (2단계)
    • 그러니까 1차적으로는 일반적인 RAG를 쓰고, RAG가 제대로 안 돌아가는 경우에만 2단계로 가는 것
  3. 요즘 LLM max token이 기하급수적으로 늘어서 이게 가능함. 확인해 보니 사실 RAG를 안 쓰는게 오히려 성능은 더 좋음. 다만 이렇게 하는 이유는 RAG를 썼을 때 input token 수가 확 줄어들면서 cost friendly하기 때문이다.

https://arxiv.org/pdf/2005.11401

pre-train된 LLM은 학습한 시점 이후의 데이터를 가지고 있지 않기때문에 outdated된 정보를 제공할 수 있으며 특히 domain-specific한 지식이 부족한 단점을 가지고 있습니다. 이를 보완하기 위해 주기적으로 LLM을 재학습시키거나 파인튜닝을 하는 방법이 고안되었지만 시간이나 비용적인 측면에서 비효율적이라는 문제점이 꾸준히 제기되어 왔습니다.

저 역시 domain-specific한 QA task를 실현하기 위해 LLM을 fine-tuning하고자 하는 시도를 하였지만 생각만큼 성능이 잘 확보되지 않으며 오히려 기존에 학습된 파라미터가 fine-tuning을 통해 망가지면서 답변 생성 성능이 저하되는 것을 여러번 경험한 바가 있습니다. 이럴 때 필요한 것이 RAG - Retrieval-Augmented Generation - 검색 증강 생성으로, LLM을 더 잘 활용하기 위해 널리 사용되고 있는 기술입니다.

RAG는 새로운 정보를 벡터 데이터베이스에 저장하고, 쿼리를 받았을때 데이터베이스에서 검색을 실행합니다. 이후 반환받은 관련 정보를 쿼리에 함께 담아 넘겨주면서 context 정보를 구체적으로 제공함으로써 기존의 문제점을 해결하고자 도모합니다.

본 포스팅에서는 RAG에 관해 처음으로 언급한 논문 <Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks>를 읽고 주요 내용을 리뷰해 보도록 하겠습니다.


Abstract

Large pre-trained language models have been shown to store factual knowledge in their parameters, and achieve state-of-the-art results when fine-tuned on downstream NLP tasks. However, their ability to access and precisely manipulate knowledge is still limited, and hence on knowledge-intensive tasks, their performance lags behind task-specific architectures. Additionally, providing provenance for their decisions and updating their world knowledge remain open research problems. (중략)

LLM은 많은 NLP task에서 SoTA 성능을 보여주며 다양하게 활용되어 왔지만, 지식을 습득하고 조종하는 부분에 있어 여전히 많은 한계점을 가지고 있습니다. 저 역시 개인적인 목적으로 GPT4.0등을 활용할 때와 달리 프로젝트를 위해 오픈소스 LLM 등을 활용할 때 특히 그러한 한계점을 많이 체감하게 되곤 했는데요. 특히 task-specific한 구조로 인해서 knowledge-intensive한 task에 있어서는 취약한 점이 많았죠. 나아가 결정에 대한 출처를 제공하는 일, 기존의 지식을 업데이트하는 일은 여전히 미해결 연구 문제로 남아 있었다고 합니다.

We explore a general-purpose fine-tuning recipe for retrieval-augmented generation (RAG)models which combine pre-trained parametric and non-parametric memory for language generation. We introduce RAG models where the parametric memory is a pre-trained seq2seq model and the non-parametric memory is a dense vector index of Wikipedia, accessed with a pre-trained neural retriever.

해당 논문에서는 2가지 종류의 knowledge를 사용하고 있습니다.

  • Parametric knowledge : 모델이 사전에 학습한 파라미터 정보
  • Non-parametric knowledge : retriever를 이용하여 외부 문서로부터 가져오는 파라미터 정보 (논문에서는 Wikipedea 활용)

그런데 논문에서 말하는 general-purpose fine-tuning recipe for RAG란 무엇일까요?

이어서 살펴보겠지만 RAG 모델의 파인튜닝은 리트리버와 생성기를 함께 엔드-투-엔드로 학습하여 모델을 최적화하는 과정을 의미합니다. 오늘날 우리가 RAG를 구현할 때는 이미 학습된 리트리버와 생성기를 LangChain과 같은 라이브러리에서 가져다 사용하기 때문에 별도의 파인튜닝 과정은 사실 필요 없습니다. 덕분에 보다 손쉽게 RAG 기능을 구현할 수 있는 것이죠. 물론 파인튜닝을 추가해서 더 고도화된 모델을 구현할 수도 있겠지만요.

We compare two RAG formulations, one which conditions on the same retrieved passages across the whole generated sequence, and another which can use different passages per token. (중략) For language generation tasks, we find that RAG models generate more specific, diverse and factual language than a state-of-the-art parametric-only seq2seq baseline.

본 논문에서는 2가지의 RAG 포뮬라를 비교 제시하는데요. 하나는 생성된 전체 시퀀스에 걸쳐 동일한 검색된 구절을 조건으로 하고, 다른 하나는 토큰당 다른 구절을 사용합니다. 이를 통해 생성 작업에 있어서 RAG 모델이 더 구체적이고 다양하며 사실에 기반한 언어를 생성한다는 사실을 발견했다고 합니다.


1 Introduction

They cannot easily expand or revise their memory, can’t straightforwardly provide insight into their predictions, and may produce “hallucinations”. (중략) 

기존의 Pre-train된 neural language model들은 파라미터에 저장된 정보를 이용하여 훌륭한 답변을 생성해 내곤 하였는데요. 하지만 이러한 모델들은 기억을 쉽게 확장하거나 수정할 수 없는 단점, 예측에 대한 통찰력을 직접적으로 제공할 수 없는 단점, 그리고 가장 치명적으로 "환각(Hallucinations)"을 일으킬 수 있는 단점을 가지고 있었습니다.

The retriever (Dense Passage Retriever [26], henceforth DPR) provides latent documents conditioned on the input, and the seq2seq model (BART [32]) then conditions on these latent documents together with the input to generate the output. We marginalize the latent documents with a top-K approximation, either on a per-output basis (assuming the same document is responsible for all tokens) or a per-token basis (where different documents are responsible for different tokens). Like T5 [51] or BART, RAG can be fine-tuned on any seq2seq task, whereby both the generator and retriever are jointly learned.

Retriever는 인풋을 받아 latent documents을 제공하고, seq2seq 모델은 이러한 문서들을 묶어서 출력을 생성합니다. RAG는 잠재 문서(latent documents)들 중에서 상위 K개의 문서를 선택(top-K approximation)하여 이 문서들에 기반해 출력을 생성하는데, 이 과정은 두 가지 방식으로 이루어질 수 있습니다. 예시 질문 "팥빙수를 맛있게 만드는 방법은?"을 가지고 각각의 방법이 어떻게 사용되는지 구체화 해보도록 하겠습니다.

  1. 출력 단위로(per-output basis): 하나의 문서가 모든 토큰에 대해 사용된다고 가정.
    • 모델이 가장 관련성 높은 문서 1개를 찾습니다. 예를 들어, 한 문서가 '팥빙수 레시피' 전체를 담고 있다고 가정합니다. 그 문서에서 얻은 모든 정보를 바탕으로 답변을 생성합니다.
    • 답변: "팥빙수를 맛있게 만들려면, 먼저 팥을 준비하고, 얼음을 갈아 위에 얹습니다. 그 다음, 연유와 다양한 토핑을 추가합니다."
  2. 토큰 단위로(per-token basis): 각 토큰이 서로 다른 문서에 의해 지원될 수 있음.
    • 모델이 여러 문서를 찾습니다. 만약 한 문서는 '팥 조리법'을 설명하고, 또 다른 문서는 '팥빙수 토핑 아이디어'를 제공한다면, 각 단어(토큰)를 생성할 때 가장 적절한 문서에서 해당하는 정보를 가져옵니다. 예를 들어, 첫 번째 문서에서 "팥을 준비하고" 정보를 가져오고, 두 번째 문서에서 "얼음을 갈아 얹고" 정보를 가져오며, 세 번째 문서에서 "연유와 다양한 토핑을 추가합니다" 정보를 가져옵니다.
    • 답변: "팥을 준비하고, 얼음을 갈아 얹고, 연유와 다양한 토핑을 추가합니다.
Crucially, by using pre-trained access mechanisms, the ability to access knowledge is present without additional training.

특히 RAG 모델에서 리트리버와 생성기를 사전 학습된 상태로 사용하기 때문에 별도의 추가 학습 과정 없이도 외부 지식을 효과적으로 사용할 수 있다는 것을 강조하고 있습니다. 보통 RAG를 실현할 때 langchain같은 라이브러리에서 사전 학습된 리트리버와 생성기를 간단하게 가져다 쓰기만 하면 되는 것처럼, 무척 편리한 기능 구현이 가능한 것이 바로 RAG의 핵심이기도 하죠.

Finally, we demonstrate that the non-parametric memory can be replaced to update the models’ knowledge as the world changes.

non-parametric memory의 경우에는 업데이트가 가능하기 때문에 유지보수를 통해 최신 정보를 반영하고 정확도를 개선할 수가 있겠습니다.


2 Methods

We explore RAG models, which use the input sequence x to retrieve text documents z and use them as additional context when generating the target sequence y.

RAG 모델은 입력 시퀀스 를 사용하여 텍스트 문서 를 검색하고, 이를 추가 컨텍스트로 활용하여 목표 시퀀스 를 생성합니다.


Figure 1.

  1. Query Encoder (q(x)): 입력된 질문(query)을 벡터 형태로 인코딩합니다.
  2. Retriever (Non-Parametric Memory): 인코딩된 질문을 기반으로 최대 내적 탐색(MIPS)을 사용하여 top-K 문서들을 찾습니다. (주어진 질문 x에 대해 상위 K개의 텍스트 구절 분포를 반환)
  3. Document Index (d(z)): 검색된 문서들의 벡터 인덱스입니다.
  4. Generator (Parametric Memory): 선택된 문서들(z)을 입력으로 받아 최종 출력을 생성합니다. 본 논문의 경우 seq2seq 모델(BART)을 채택하고 있습니다.
  5. Marginalize: 여러 문서들로부터 얻은 출력을 종합하여 최종 답변(y)을 도출합니다. 다양한 문서에 대해 seq2seq 예측을 주변화(marginalize)합니다.

이처럼 RAG 모델은 질문을 인코딩하여 관련 문서를 검색하고, 이를 바탕으로 답변을 생성합니다. 그렇다면 RAG는 retriever와 generator를 어떻게 학습시켰을까요?

To train the retriever and generator end-to-end, we treat the retrieved document as a latent variable. We propose two models that marginalize over the latent documents in different ways to produce a distribution over generated text. In one approach, RAG-Sequence, the model uses the same document to predict each target token. The second approach, RAG-Token, can predict each target token based on a different document.

RAG는 retrieved된 document를 latent variable로 취급한다고 명시하고 있습니다. 본 논문에서는 이와 같은 latent document에 대해 다른 방식으로 marginalization하는 두 가지 모델을 제안합니다.

  1. RAG-Sequence 모델 : Same document를 사용하여 each target token을 예측
  2. RAG-Token 모델 : Different document를 사용하여 each target token을 예측

RAG-Sequence 모델

 

하나의 문서 z에 대해 전체 시퀀스 의 모든 토큰 yi에 대한 확률을 계산합니다. 모든 top-K 문서에 대해 이 과정을 반복한 후, 최종 값을 주변화(marginalize)하여 계산합니다. 즉, 각 문서에 대해 시퀀스 전체를 고려하여 최종 출력을 생성합니다.

RAG-Token 모델

RAG-Token model은 RAG-Sequence와 다르게 각 토큰 yi를 생성할 때마다 모든 top-K 문서에 대해 확률을 계산하고, 그 후 주변화(marginalize)하여 최종 출력을 생성합니다. 즉, 각 토큰에 대해 개별 문서를 고려하여 출력을 생성합니다.

Retriever: DPR

앞서 언급한 바와 같이 RAG에서는 retriever 𝑝𝜂(𝑧|𝑥)로 DPR을 사용합니다. DPR은 기본적으로 bi-encoder 구조 - 사전 학습된 BERT 기반 인코더를 사용하여 리트리버를 초기화하고 문서 인덱스를 구축합니다.

https://arxiv.org/pdf/2004.04906

DPR은 논문 <Dense Passage Retrieval for Open-Domain Question Answering>에서 고안한 방법으로, RAG 논문보다 1년 앞서 출간되었습니다. 나중에 기회가 되면 DPR 논문도 리뷰해 보도록 하겠습니다.

Calculating top-k(pη(·|x)), the list of k documents z with highest prior probability pη(z|x), is a Maximum Inner Product Search (MIPS) problem, which can be approximately solved in sub-linear time [23]

주어진 질문 x에 대해 가장 높은 사전 확률 을 가진 상위 K개의 문서 목록을 계산하는 것은 최대 내적 탐색(MIPS) 문제로 해결합니다. 즉, input x 에 대한 document z의 분포는 위에서 산출한 d(z) 와 q(x)의 내적 연산을 통해 계산되며, 이 내적 값이 높은 순서대로 top-k document를 골라 retrieve를 하게 되는데, 이 과정은 REALM에서 사용되었던 MIPS 알고리즘을 사용하여 효율적인(sub-linear time) 탐색을 가능하게 했다고 합니다.

Generator: BART

생성기로는 무엇을 사용해도 상관이 없으며 본 논문에서는 BART-large를 사용했다고 밝히고 있습니다. 

Training

We jointly train the retriever and generator components without any direct supervision on what document should be retrieved

DPR 기반의 리트리버와 BART-large 기반의 생성기는 학습 과정에서 동시에 학습됩니다. 이때, 어떤 문서가 검색되어야 하는지에 대한 직접적인 감독 없이 진행되는 비지도 학습(unsupervised learning) 방식이 적용됩니다. 오로지 출력 시퀀스에 대한 NLL(Negative marginal Log-Likelihood)을 최소화하는 방향으로 학습되며, 리트리버 또한 이 과정에서 NLL을 최소화하는 방향으로 학습됩니다.

Decoding

앞서 살펴본 두 가지 모델 RAG-Sequence와 RAG-Token은 각각 output 산출 방법이 다르므로 token decoding 하는 과정도 달라지게 됩니다.

  1. RAG-Token 모델 : 표준 시퀀스-투-시퀀스 생성기처럼 작동하며, 여러 문서의 확률을 합산하여 전이 확률을 계산합니다. 이를 표준 빔 디코더에 적용하여 디코딩합니다.
  2. RAG-Sequence 모델 : 각 문서에 대해 별도의 빔 서치를 실행하고, 최종 확률을 추정하기 위해 추가적인 forward pass를 실행합니다. 이를 "Thorough Decoding"이라 하며, 효율성을 위해 추가 패스를 생략하는 "Fast Decoding"도 있습니다.

이 때, 빔 서치는 가장 가능성 높은 N개의 후보를 유지하며 다음 토큰을 생성, 이 과정을 반복해 최종 출력 시퀀스를 생성하는 디코딩 방식을 의미합니다.

출처 : https://slideplayer.com/slide/14552918/


Experiment & Result

해당 부분은 GPT4.0-Turbo를 이용하여 핵심 내용 정리 요약 후 패스하도록 하겠습니다.

  1. Dense Passage Retriever (DPR): DPR은 dense encoding을 통해 질문과 패시지 간의 의미적 관계를 잘 파악합니다.
  2. 성능 비교: BM25와 비교한 실험에서 대부분의 데이터셋에서 DPR이 더 우수한 성능을 보였습니다.
  3. 효율성: 적은 수의 학습 예제로도 고품질의 dense retriever를 학습할 수 있습니다.
  4. 일반화 성능: 학습된 데이터셋 외에도 어느 정도 잘 작동하며, BM25보다 우월한 성능을 보입니다.
  5. 질적 분석: BM25는 키워드에 민감하고, DPR은 의미적 관계를 잘 파악합니다.
  6. 종합 성능: 대부분의 데이터셋에서 DPR이 최상의 성능을 냈습니다.

RAG의 가장 큰 장점은 parametric & non-parametric memory의 결합을 통해 보다 정확하고 다양한 정보를 생성할 수 있다는 점입니다. 이를 통해 기존의 파라미터를 대량 업데이트하거나 fine-tuning 하지 않으면서도 domain-specific한 downstream task을 잘 수행할 수 있는 모델을 구현할 수 있게 되었습니다. 특히 검색 인덱스를 간단히 교체하여 모델을 업데이트할 수 있다는 점에서 매우 효율적이고 유용한 기법이라고 할 수 있겠습니다.

본 논문을 읽기 전에 langchain을 이용하여 RAG을 이미 구현해 본 입장에서, 논문을 통해 핵심 아이디어와 학습 방안에 대해 구체화하고 더 깊이 이해할 수 있어 무척 좋은 기회였습니다. 앞으로 RAG를 다양한 응용 분야에 적용해보고, task에 따라 어떤 방식으로 알맞게 사용할 수 있는지 실험해 보는 과정을 거쳐 보고자 합니다.

읽어주셔서 감사합니다 :)

 

 

 

재밌는 토이 프로젝트! RAG 기술을 활용하여 pdf 파일에서 내용을 검색, 질의응답(QA)을 구현하는 챗봇 시스템을 만들어 보았습니다.

* 본 포스팅 활용하시는 경우 출처를 밝혀주세요 :-)
* 전체 코드 .py파일을 원하시는 분들께서는 포스팅 하단에 github 링크 참고 바랍니다.


* 사용한 라이브러리

pdfplumber, pytesseract, langchain, ollama, chromadb, gradio (전부 무료)

* 프로젝트 개요

  1. PDF 파일의 텍스트를 pdfplumber, pytesseract로 추출합니다.
  2. 추출한 내용을 langchain을 이용해서 split, 임베딩하여 벡터화한 다음 Chroma 벡터 저장소에 저장합니다.
  3. 벡터 저장소에서 질문에 해당하는 내용을 검색하여 context로 준비합니다.
  4. Ollama 라이브러리를 이용해서 LLaMA3 모델에게 질문과 context를 프롬프트로 제공하고 답변을 받습니다.
  5. 모든 과정을 gradio와 연동하여 GUI로 실행할 수 있도록 설정했습니다.

1. 환경설정 및 라이브러리 준비

먼저 새로운 콘다 환경을 만들고 해당 환경 안에서 프로젝트를 진행하도록 하겠습니다. 

conda create -n ragchatbot python=3.11 -y # 환경 생성
conda activate ragchatbot                 # 활성화

필요한 라이브러리를 설치 후 임포트합니다.

# 설치 (터미널에서 실행)
pip install pdfplumber pytesseract ollama gradio langchain

# 임포트
import gradio as gr
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
import ollama
import pdfplumber
import pytesseract
from PIL import Image

2. 텍스트 추출, 임베딩, chroma에 저장

텍스트 추출, 임베딩, 벡터 데이터베이스 저장에 필요한 2가지 함수를 작성하겠습니다.

# PDF page에서 텍스트 추출하는 함수 작성
def extract_text_with_ocr(page):
    text = page.extract_text()
    if not text: # 만약 추출할 텍스트가 없다면
        # PDF page를 이미지로 변환
        image = page.to_image()
        # 이미지에서 OCR 재실행하여 텍스트 추출
        text = pytesseract.image_to_string(image)
    return text
    
# PDF 파일을 열어서 extract_text_with_ocr 함수 실행 -> 벡터 데이터베이스에 저장하는 함수 작성
def load_and_retrieve_docs(file):
    text = ""
    try:
        with pdfplumber.open(file) as pdf:
            for page in pdf.pages:
                page_text = extract_text_with_ocr(page)
                # 페이지에서 추출한 텍스트가 있을 때마다 text에 누적해서 저장
                if page_text:
                    text += page_text
    except Exception as e:
        return f"Error reading PDF file: {e}"
    
    # 만약 추출한 텍스트가 하나도 없는 경우 아래와 같은 메세지 출력하고 함수 종료
    if not text:
        return "No text found in the PDF file."
    
    # 추출한 텍스트가 있는 경우 
    docs = [Document(page_content=text)]
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    splits = text_splitter.split_documents(docs)
    embeddings = OllamaEmbeddings(model="mxbai-embed-large")
    vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)
    return vectorstore.as_retriever()
  • extract_text_with_ocr
    • 본 함수는 pdf 개별 페이지를 Input으로 받아서 페이지 내의 텍스트를 추출, 반환하는 함수입니다.
    • 텍스트 추출에는 pdfplumber 라이브러리의 extract_text() 함수를 사용했습니다. 만약 페이지 내에서 추출할 텍스트가 없는 경우 pdfplumber 라이브러리의 to_image() 함수를 사용해서 pdf파일을 pdfplumber가 핸들할 수 있는 PageImage object객체화(이미지화)합니다. 이후 해당 이미지에서 pytesseractimage_to_string() 함수를 사용해서 이미지로부터 텍스트를 추출하도록 했습니다.
    • pytesseract docs : https://pypi.org/project/pytesseract/
    • pdfplumber docs : https://github.com/jsvine/pdfplumber
  • load_and_retrieve_docs
    • 본 함수는 pdf파일을 열어서 모든 페이지마다 extract_text_with_ocr함수를 반복, text에 누적해서 더해주어 하나의 string으로 완성합니다.
    • pdf파일을 열기 위해서(load) pdfplumber 라이브러리의 open()함수를 사용했습니다.
    • 완성된 text를 langchainDocument 객체로 만든 다음, RecursiveCharacterTextSplitter 객체를 생성한 뒤 만들어 둔 Document 객체를 집어넣어 적당히 청킹을 해주었습니다. 이후 청킹이 완료된 텍스트들을 벡터화하기 위한 OllamaEmbeddings 객체를 생성해 주었습니다.
    • langchain에 연동된 Chroma 라이브러리를 불러온 다음, from_documents() 함수를 이용해 Chroma에 load를 해 줍니다. 이 때, 저장할 텍스트 청킹 객체와 임베딩 객체를 지정해 주어 무엇을 어떻게 임베딩하여 저장할지를 지정해주면 자동으로 임베딩을 실행하면서 vectorstore에 저장을 완료하기 때문에 사용이 매우 간편합니다.
    • 마지막으로 load_and_retrieve_docs 함수는 결과값으로 vectorstore에 as_retriever() 함수를 적용한 객체를 반환합니다.
    • langchain docs(Chroma) : click
    • langchain docs(RecursiveCharacterTextSplitter) : click

3. 검색 실행

먼저 터미널에서 LLaMA3 모델을 다운로드(pull) 해줍니다.

참고로 docs에 따르면 llama3 기본 모델보다 chatqa 모델이 대화형 QA와 RAG 기능을 훨씬 잘 수행한다 - excels at conversational question answering (QA) and retrieval-augmented generation (RAG) - 라고 기재가 되어 있어서 처음에는 LLaMA3-chatqa모델을 선택하였는데, 실험 결과 LLaMA3-chatqa보다 기본 LLaMA3 모델이 더 답변이 정상적으로 생성되는 것을 확인할 수 있었습니다. 그 이유에 대해서는 좀더 살펴봐야 할 것 같아요.

참고로 모델 크기가 커서 pull에 시간이 좀 소요되는 편입니다.

ollama pull llama3

 

# 리스트 안의 모든 document 객체 내용을 추출해서 string으로 이어붙여 반환
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# Function that defines the RAG chain
def rag_chain(file, question):
    retriever = load_and_retrieve_docs(file)
    if isinstance(retriever, str):  # 리턴받은 값이 string인 경우 에러를 의미하므로 함수 중단
        return retriever
    
    retrieved_docs = retriever.get_relevant_documents(question)  # Use get_relevant_documents method
    formatted_context = format_docs(retrieved_docs)
    formatted_prompt = f"Question: {question}\n\nContext: {formatted_context}"
    response = ollama.chat(model='llama3', 
                           messages=[
                                {"role": "system",
                                 "content": "You are a helpful assistant. Check the pdf content and answer the question."
                                },
                                {"role": "user", "content": formatted_prompt}])
    return response['message']['content']
  • rag_chain
    • 위에서 작성했던 load_and_retrieve_docs 함수는 pdf 내용을 저장해 둔 vectorstore에 as_retriever() 함수를 적용한 객체를 반환합니다. 먼저 이 객체에 retriever라는 이름을 할당한 다음, 만약 retriever의 객체 타입이 string인 경우 에러 메세지(추출한 텍스트 없을 때)가 리턴된 것을 의미하므로 해당 에러를 프린트하면서 함수를 종료합니다.
    • docs에 따르면 get_relevant_documents() 함수주어진 쿼리(질문)에 가장 관련성이 높은 문서를 벡터 저장소에서 검색하는 기능을 합니다. 예를 들어 제가 lloco_paper.pdf 파일로 테스트를 했을 때, RecursiveCharacterTextSplitters를 이용해서 split한 document 객체는 총 55개였고, 그 중에서 검색을 통해 반환하는 document 객체는 항상 4개인 것으로 확인이 되었습니다. 이 수치를 변경할 수 있는지는 나중에 확인을 해 보도록 하겠습니다.
    • 반환받은 document 객체 리스트인 retrieved_docs를 format_docs 함수를 이용해서 하나의 string으로 변환하고, 해당 string은 prompt의 context로 주어지게 됩니다.
    • ollama 라이브러리chat() 함수를 이용해서 LLaMA 3 모델에 question과 context를 제공하고, 답변(response)을 받습니다. chat

만약 답변을 한국어로 받고 싶은 경우 messages의 context 안에 Generate your answer in Korean 등과 같이 한국어로 답변을 내놓으라는 한 문장을 추가해서 프롬프팅을 해 주시면 됩니다.

4. Gradio GUI 인터페이스 할당

# Gradio interface
iface = gr.Interface(
    fn=rag_chain,
    inputs=["file", "text"],
    outputs="text",
    title="[LLAMA 3] RAG 검색 활용 챗봇 시스템",
    description="PDF파일을 업로드하고 질문을 입력하면 답변을 생성해 드립니다. (영어로!)"
)

# app 실행
iface.launch()

주피터 파일로 실행하는 경우 바로 인터페이스가 보이실 것이고, 만약 .py파일로 실행하신다면 아래와 같이 할당되는 로컬 URL 주소로 접속을 하시면 인터페이스를 확인하실 수 있습니다.

URL로 접속하면 위와 같은 화면이 보이고, pdf파일 업로드 + question 작성 후 submit을 누르면 output에 답변이 생성됩니다.

저는 제가 현재 읽고 있는 LLoCO 논문(2024년 4월 발행)을 업로드해서 4.1장과 4.5장을 요약해달라는 요청을 해 보았구요

위와 같이 답변을 생성받았습니다 :-) 이모지 넣어달라고 했더니 야무지게 2개 넣어줬네요. 제공한 pdf 파일이 15장의 긴 파일임을 생각하면, 간단히 진행해본 것과 비교했을 때 성능이 꽤 잘 나온 것으로 생각하고 있습니다.


https://github.com/surisurikim/deep_learning/tree/main/RAG

 

deep_learning/RAG at main · surisurikim/deep_learning

PyTorch 기반 딥러닝 논문 코드구현을 공부하는 레포지토리입니다. Contribute to surisurikim/deep_learning development by creating an account on GitHub.

github.com

해당 토이 프로젝트는 위 깃허브에서 코드 파일을 확인하실 수 있습니다. 감사합니다 :)

+ Recent posts