llama3 rag 로 나만의 챗봇 만들기

Photo of author

By mimoofdm@naver.com

llama3 rag 로 나만의 챗봇을 만드는 방법에 관하여 자세하게 설명을 드립니다. 업무용 문서와 논문과 텍스트 파일에서 내가 원하는 키워드와 유사한 단어와 문장을 검색해주는 챗봇을 llama3 rag 를 이용해서 만드는 방법을 알려드립니다.

llama3 rag

이번 포스팅에서 제공되는 소스 코드는 google colab 에서 동작 되는 코드입니다. llama3 rag 를 실제로 colab 에서 코드를 작성해서 동작 시키는 것을 보여드리도록 하겠습니다.

RAG 란 무엇인가?

RAG 란 Retrieval Augmented Generation 의 약자로서 여러 문서를 가져와서 문서 안에 있는 내용을 검색하고 유사한 의미를 가진 문장과 텍스트를 찾아주는 기술을 의미합니다.

rag 가 도입되기 전에는 모델 자체를 섬세하게 트레이닝하여 정교한 모델을 생성하는 model fine tunning 분야에 대한 연구와 실험이 많았습니다. 생성형 AI 가 보여주는 환각 현상을 해결하기 위하여 입력한 데이터세트를 기준으로 정확한 검색 결과를 보여 달라는 수요가 증가하고 있습니다.

llama3 8B 모델이 무료로 공개되면서 rag 기술과 접목하려는 시도가 일어나고 있습니다. llama3 8B 는 GPU 가 없는 일반 데스크탑 컴퓨터나 노트북에서도 사용할 수 있을 만큼 가벼우면서도 정확한 답변을 해주는 정교한 언어 모델입니다.

llama3 RAG 이해하려면 함께 보면 좋은 글

지난번 포스팅에서 llama3 RAG 기술을 구현하기 전에 이해하면 도움이 되는 ollama 를 이용해서 windows 10 에서도 편하게 llama3 docker image 를 받아서 설치하고 무료로 이용할 수 있는 챗봇을 만드는 내용을 자세히 설명했습니다. 관심이 있으시면 아래 포스팅을 한번 읽어보시기 바랍니다.

ollama 를 이용해서 llama3 을 windows 10 에서 설치하는 방법 알아보기

llama3 으로 무료 챗봇 아주 쉽게 직접 만들어보기

llama3 rag 기술을 이용해서 문서에 있는 유사한 단어와 문장 검색을 할 때 이용하는 임베딩과 벡터에 관한 개념을 이해하시려면 아래 포스팅을 읽어보시면 도움이 됩니다.

임베딩 벡터 검색 엔진을 windows 10 에 가장 쉽게 설치하는 방법 알아보기

벡터 검색 엔진을 Web UI 로 편하기 이용하는 방법 알아보기

llama3 rag 를 위한 양자화 설정 방법

llama3 rag 를 구축하기 위해서 가장 먼저 해야 하는 것은 얼마나 정교한 답변을 하는 모델을 사용할 것인지 결정하고 양자화 레벨을 정하는 것입니다.

신경망을 구성할 때 양자화를 많이 할 수록 정보가 세분화되어서 추론의 정확도가 높아집니다. 그런데, 양자화 숫자를 높일수록 학습을 시킬 때 복잡도가 높아지고 높은 연산 능력을 가진 GPU 가 필요하게 되요. 그래서, 양자화 숫자를 낮춰서 보통은 4로 설정합니다.

테스트에 사용할 “미국과 한국 기준금리 전망 수정.pdf” 문서는 아래에서 다운로드 받으실 수 있습니다.

양자화를 하기 위해서 필요한 패키지들을 설치합니다.

!pip install -q -U bitsandbytes

!pip install -q -U git+https://github.com/huggingface/transformers.git


!pip install -q -U git+https://github.com/huggingface/peft.git

!pip install -q -U git+https://github.com/huggingface/accelerate.git

llama3 rag 양자화 방법

위의 패키지를 천천히 살펴보자면 bitsandbytes 는 Nvidia GPU 를 이용하는 CUDA 를 사용할 때 양자화 를 할 때 몇 비트로 설정해서 사용하는지 설정할 때 사용됩니다. 즉, float 실수를 디지털화 시킬 때 4비트의 정보로 실수값을 양자화해서 사용할 것인지 결정할 때 사용된다는 의미입니다.

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

한글 경량화 모델은 “kyojinpy/Ko-PlatYi-6B” 모델을 사용하였습니다. 한국인이 작업한 것 같습니다.

tokenizer 는 pretrained 모델을 사용합니다. 모델은 huggingface 에서 제공되는 AutoModelForCausalLM 모델에서 제공되는 pretrained 모델인 model을 사용합니다. 이때 경량화 시키기 위해서 양자화 방법은 bnb_config 에 기재된 방법을 사용합니다. 아직 한글로 최적화된 llama3 8b 모델을 구하지 못하여 Ko-PlatYi-6B 모델을 사용하였습니다.

model_id = "kyujinpy/Ko-PlatYi-6B"

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map="auto")

print(model)

아래는 model 을 로딩을 완료한 모습입니다. 모델 설치를 완료하는데는 5분 정도가 소요됩니다.

진짜로 한국어 경량화 모델이 잘 작동하는지 확인해 보았어요. 일단 google colab 에서 할당된 디바이스가 cuda 를 할당해 달라고 설정합니다. 그리고, 초거대 언어 모델에게 “올해 금리 인상 전망에 대해서 설명해 달라” 고 instruction 명령어를 입력합니다. 만일 첨부한 문서에 “금리 인상 전망” 과 똑같은 단어가 존재한다면

device = "cuda:0"

messages = [
    {"role": "user", "content": "올해 금리 인상 전망에 대해서 설명해줘."}
]


tokenizer 에 채팅 템플릿을 적용하도록 지정했어요. 이때 tokenizer.apply_chat_template( ) API는 메세지와 텐서의 종류를 설정하면 pytorch 형태의 텐서로 인코딩된 아웃풋을 출력해줍니다.

인코딩된 메세지를 cuda 디바이스 에게 입력해서 model_input 을 만들어냅니다.

encodeds = tokenizer.apply_chat_template(messages, return_tensors="pt")

model_inputs = encodeds.to(device)

모델을 이제 본격적으로 생성을 합니다. 입력 값으로는 방금 생성한 model_input 과 최대 토큰 개수를 1000개로 지정합니다. 샘플링도 하도록 설정합니다.

모델이 생성되면 생성된 모델의 ID 를 generated_ids 라고 지칭을 하고 다시 tokenizer.batch_decode 에 집어 넣어서 디코딩 시킵니다.

generated_ids = model.generate(model_inputs, max_new_tokens=1000, do_sample=True)
decoded = tokenizer.batch_decode(generated_ids)
print(decoded[0])

한글을 입력하기 위해서 국가 지역에 맞는 문자 인코딩 방식을 “UTF-8” 로 설정합니다.

import locale
def getpreferredencoding(do_setlocale = True):
    return "UTF-8"
locale.getpreferredencoding = getpreferredencoding

!pip -q install langchain pypdf chromadb sentence-transformers faiss-gpu

Huggingface Langchain 설정 방법

langchain 에서 대부분의 기능을 huggingface 의 패키지들을 이용합니다. 임베딩도 HuggingFaceEmbeddings 을 사용합니다. temperature 는 언어 모델이 얼마나 자유롭게 답변을 작성하도록 할 것인지 설정하는 파라미터입니다. 0으로 설정하면 사실에 입각한 문서의 내용만 출력해주고 파라미터 값이 1에 가까워질 수록 자유롭게 상상해서 글을 작성합니다.

from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from transformers import pipeline
from langchain.chains import LLMChain

text_generation_pipeline = pipeline(
    model=model,
    tokenizer=tokenizer,
    task="text-generation",
    temperature=0.2,
    return_full_text=True,
    max_new_tokens=300,
)

프롬프트를 입력하는 방식은 큰 따옴표 3개 (“”” ) 로 부터 프롬프트가 시작되서 마지막 프롬프트를 작성하고 큰 따옴표 3개 (“””) 를 기재하여 마무리합니다.


prompt_template = """
### [INST]
Instruction: Answer the question based on your knowledge.
Here is context to help:

{context}

### QUESTION:
{question}

[/INST]
 """

huggingface 에서 제공하는 파이프라인을 사용하도록 설정합니다.


koplatyi_llm = HuggingFacePipeline(pipeline=text_generation_pipeline)

# Create prompt from prompt template
prompt = PromptTemplate(
    input_variables=["context", "question"],
    template=prompt_template,
)

# Create llm chain
llm_chain = LLMChain(llm=koplatyi_llm, prompt=prompt)

PDF 문서에서 뽑아낸 텍스트의 길이가 너무 길어서 더 작은 단위인 chunk 로 나누기 위해서 RecursiveCharacterTextSplitter 를 사용합니다. pdf 문서를 로딩하기 위해서 PyPDFLoader 패키지도 가져옵니다. 또한 langchain 에서 RunnablePassthrough 을 활용하기로 합니다.

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import PyPDFLoader
from langchain.schema.runnable import RunnablePassthrough

loader = PyPDFLoader("/content/drive/MyDrive/미국과 한국 기준금리 전망 수정.pdf")
pages = loader.load_and_split()

text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
texts = text_splitter.split_documents(pages)

from langchain.embeddings import HuggingFaceEmbeddings

모델은 ko-sbert-nli 을 이용합니다. 임베딩은 텍스트를 숫자 형태의 벡터로 변환해주는 기능을 의미합니다. 이번에는 HuggingFaceEmbeddings 를 사용합니다.

HuggingFace embeddings 인 hf와 텍스트들을 입력하여 데이터베이스 db 를 생성합니다. 여기에는 여러 문서 안에 담긴 텍스트들이 여러 개가 벡터화 되어서 담겨있습니다. db 에서 유사도를 바탕으로 가장 유사한 문장을 3개를 찾는 retriever 를 만듭니다.

RAG 문서 검색 설정 방법

RAG 기능의 가장 핵심이라 할 수 있는 내가 제공한 문서에서 검색하는 기능을 제공하는 핵심이 retriever 라 할 수 있습니다.

model_name = "jhgan/ko-sbert-nli"
encode_kwargs = {'normalize_embeddings': True}
hf = HuggingFaceEmbeddings(
    model_name=model_name,
    encode_kwargs=encode_kwargs
)

db = FAISS.from_documents(texts, hf)
retriever = db.as_retriever(
                            search_type="similarity",
                            search_kwargs={'k': 3}
                        )

rag_chain을 만드는데 방금 만든 retriever 를 검색할 컨텍스로 지정하고 질문은 RunnablePassthrough( )에서 입력받은 것을 dictionary 형태의 데이터로 만듭니다. 이 dictionary 데이터를 llm_chain 에게 입력하는 형태로 rag_chain 을 구성합니다.

rag_chain = (
 {"context": retriever, "question": RunnablePassthrough()}
    | llm_chain
)

작업 도중에 경고가 발생하면 무시합니다.

import warnings
warnings.filterwarnings('ignore')

result = rag_chain.invoke("올해 하반기에 한국의 기준금리는 인상되나요?")

for i in result['context']:
    print(f"주어진 근거: {i.page_content} / 출처: {i.metadata['source']} - {i.metadata['page']} \n\n")

print(f"\n답변: {result['text']}")

llama3 rag

llama3 rag

아래의 글은 pdf 문서의 내용 중에서 “올해 하반기 한국 금리 인상되나요?”에 대해서 RAG 로 요약한 글입니다.

2024. 3. 13 \n미국과 한국, 기준금리 전망 수정 \n \nSamsung Securities (Korea) \nwww.samsungpop.com 2 \n 2월 금통위 의사록 주요 내용 \n위원 금리 결정 및 \n포워드 가이던스 (추정) 2월 금통위 주요 내용 \nA 동결/3.5% 유지 – 작년 하반기 이후의 회복 추세를 지속하고 있는 것으로 판단. 반도체 부문을 중심으로 수출과 설비 투자의 개선세가 이\n어지고 있으나 민간 소비의 회복 흐름이 약화되고 건설 투자는 감소 추세로 돌아섰음. \n- 앞으로도 높은 원리금 상환 부담, 베이비부머의 은퇴에 따른 소비 성향 약화 등을 고려할 때 민간 소비의 증가세가 의\n미 있게 확대될 가능성은 크지 않아 보임. 이 같은 국내 수요 약화는 우리나라 디스인플레이션의 주요 배경’, metadata={‘source’: ‘/content/drive/MyDrive/미국과 한국 기준금리 전망 수정.pdf’, ‘page’: 1})

요약문의 마지막에 있는 metadata 를 살펴보면 pdf 문서의 어느 페이지에서 가져온 내용을 요약했는지 정보를 제공하고 있어요.

마무리

llm rag 에 관하여 공부를 하면서 실제로 동작이 가능한 소스 코드를 작성하고 실제로 pdf 문서의 내용을 검색하는 실습을 진행하였습니다. google colab에서 hugginface langchain 과 tokenizer 를 이용해서 rag 를 구성하여서 환경 구성이 어렵지 않았습니다.

llm rag 에 관하여 테스트를 진행하려고 할 때 집에 있는 데스크탑 컴퓨터에는 GPU 가 장착된 그래픽 카드가 없어서 코드를 돌려보다가 작업이 중단되는 경우가 빈번 했습니다.

다행스럽게도 google colab 에서 직접 모델을 설치해서 rag 를 구성할 수 있어서 만족스럽네요. 다음 포스팅에서 llama3 를 이용해서 rag 를 구성하고 여러 개의 문서에서 검색 키워드와 유사한 의미를 가진 문장을 얼마나 잘 찾는지 실습하도록 하겠습니다.