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