728x90

Text Splitter란?

토큰 제한이 있는 LLM이 여러 문장 혹은 문서를 참고해서 답변할 수 있게 문서를 분할하는 역할

위 그림에서 문서를 chunking하는 과정을 text splitters가 담당한다. 이후에는 chunk들을 임베딩 벡터로 만들고 벡터 스토어에 저장을 해준다.

이때 chunk 1개당 1개의 벡터가 매칭이 된다. 즉, vector store의 벡터들은 chunk들을 임베딩한 벡터들이다.
이렇게 chunk별로 임베딩을 하는 이유는 질문 임베딩과 유사도가 높은 벡터의 청크를 llm에 전달해서 사용자가 원하는 답을 얻게 하기 위해서이다.

근데 LLM은 토큰 제한이 있기 때문에 chunk 사이즈, 몇개의 chunk를 LLM에 전달할지 등을 고려해야한다.

Character Text Splitter vs Recursive Character Text Splitter

두가지 모두 특정한 구분자를 기준으로 chunk를 나누고 chunk들의 사이즈를 제한하는 기능이 있다.

Character Text Splitter

  • 구분자 1개를 기준으로 문장을 구분
  • 예를 들어, 줄바꿈이 2번 되면 chunk를 나눠라~ 라고 설정할 수 있다.
  • 최대 토큰 개수를 설정할 수 있다.
  • 구분자 1개를 기준으로 하기 때문에 max_token을 못지키는 경우도 존재

Recursive Character Text Splitter

  • 구분자 여러 개(줄바꿈, 마침표, 쉼표 순으로)를 돌아가면서 재귀적으로 분할
  • 첫번째 구분자로 분할하다 max_token을 못지키면 다음 구분자를 기준으로 분할

실습

with open("/content/drive/MyDrive/코딩/LangChain 실습(모두의AI)/data/state_of_the_union.txt") as f:
    state_of_the_union = f.read()
state_of_the_union
!pip install langchain

CharacterTextSplitter

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
    separator = "\n\n",
    chunk_size = 1000,
    chunk_overlap = 1000,
    length_function = len,
)
  • separator : 구분자
  • chunk_size : 크기
  • chunk_overlap : chunk를 구분할때 그 이전 chunk의 일부를 포함해서 구분하게 하는 것. Overlap의 크기는 얼마나 중복해서 포함할지이다.
  • length_function : chunk_size를 측정할때 저 수치(1000)이 무슨 기준인지 정의하는 것. len으로 하는 경우 글자수 기준이다.

chunk overlap

texts = text_splitter.split_text(state_of_the_union)
print(texts[0])
print("-"*100)
print(texts[1])
print("-"*100)
print(texts[2])
char_list = []
for i in range(len(texts)):
    char_list.append(len(texts[i]))
print(char_list)
text_splitter.create_documents([state_of_the_union])

우리가 나눈걸 기준으로 document를 만들겠다 하면 create_documents로 document list로 만들 수 있다.

pdf 문서 나누기

!pip install pypdf
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("/content/drive/MyDrive/코딩/LangChain 실습(모두의AI)/data/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load_and_split()#load_and_split()으로 페이지 단위로 pdf를 분할
len(pages)
print(pages[1].page_content)

Character Text Splitter로 나눠보기

from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
    separator="\n",
    chunk_size=1000,
    chunk_overlap=100,
    length_function=len,
)
#loader를 통해서 가져오면 document 객체가 만들어진다.
#따라서, split_documents를 통해서 나눠줘야한다.
texts = text_splitter.split_documents(pages)
print(texts[1].page_content)

RecursiveCharacterTextSplit

  • RecursiveCharacterTextSplit은 재귀적으로 문서를 분할합니다. 먼저, "\n\n"(줄바꿈)을 기준으로 문서를 분할하고 이렇게 나눈 청크가 여전히 너무 클 경우에 "\n"(문장 단위)을 기준으로 문서를 분할합니다. 그렇게 했을 때에도 청크가 충분히 작아지지 않았다면 문장을 단어 단위로 자르게 되지만, 그렇게까지 세부적인 분할은 자주 필요하지 않습니다.
    분할 구분자 순서 = ["\n\n", "\n", " ", ""]
  • 이런 식의 분할 방법은 문장들의 의미를 최대한 보존하는 형태로 분할할 수 있도록 만들고, 그렇기 때문에 다수의 청크를 LLM에 활용함에 있어서 맥락이 유지되도록 하기에 용이합니다.
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 1000,
    chunk_overlap = 200,
    length_function = len,
)
texts = text_splitter.create_documents([state_of_the_union])
print(texts[0].page_content)
print("-"*500)
print(texts[1].page_content)
char_list = []
for i in range(len(texts)):
    char_list.append(len(texts[i].page_content))
print(char_list)
from langchain.document_loaders import PyPDFLoader

loader = PyPDFLoader("/content/drive/MyDrive/코딩/LangChain 실습(모두의AI)/data/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load_and_split()
len(pages)
print(pages[1].page_content)
texts = text_splitter.split_documents(pages)
#page 18개를 chunk 31개로 나눔
len(texts)
print(texts[1].page_content)
char_list = []
for i in range(len(texts)):
    char_list.append(len(texts[i].page_content))
print(char_list)

기타 splitter

일반적인 글로 된 문서는 모두 textsplitter로 분할할 수 있으며, 대부분의 경우가 커버된다. 그런데 코드, latex 등과 같이 컴퓨터 언어로 작성되는 문서의 경우 textsplitter로 처리할 수 없으며 해당 언어를 위해 특별하게 구분하는 splitter가 필요합니다. 예를 들어, Python 문서를 split하기 위해서는 def, class와 같이 하나의 단위로 묶이는 것을 기준으로 문서를 분할할 필요가 있습니다. 이러한 원리로 Latex, HTML, Code 등 다양한 문서도 분할 할 수 있습니다.

python 분할기

from langchain.text_splitter import(
    RecursiveCharacterTextSplitter,
    Language,
)
RecursiveCharacterTextSplitter.get_separators_for_language(Language.PYTHON)
PYTHON_CODE = """
def hello_world():
    print("Hello, World!")

# Call the function
hello_world()
"""
python_splitter = RecursiveCharacterTextSplitter.from_language(
    language = Language.PYTHON, chunk_size = 50,chunk_overlap = 0
)
python_docs = python_splitter.create_documents([PYTHON_CODE])
python_docs

python 코드를 list형태로 create_documents에 넣으면 잘 분할해준다.

토큰 단위 분할기

텍스트 분할의 목적은 LLM이 소화할 수 있을 정도의 텍스트만 호출하도록 만드는 것입니다. 따라서 LLM이 소화할 수 있는 양으로 청크를 제한하는 것은 LLM 앱을 개발할 때 필수적인 과정입니다.

LLM은 텍스트를 받아들일 때, 정해진 토큰 이상으로 소화할 수 없게 설계되어 있습니다. 따라서 글을 토큰 단위로 분할한다면 최대한 많은 글을 포함하도록 청크를 분할할 수 있습니다.

토큰이라는 것은, 텍스트와 달리 Transformer에서 처리하는 방식에 따라서 그 수가 달라질 수 있습니다. 따라서, LLM 앱을 개발하고자 한다면 앱에 얹힐 LLM의 토큰 제한을 파악하고, 해당 LLM이 사용하는 Embedder를 기반으로 토큰 수를 계산해야 합니다. 예를 들어, OpenAI의 GPT 모델은 tiktoken이라는 토크나이저를 기반으로 텍스트를 토큰화합니다. 따라서 tiktoken encoder를 기반으로 텍스트를 토큰화하고, 토큰 수를 기준으로 텍스트를 분할하는 것이 프로덕트 개발의 필수 요소라고 할 수 있습니다.

!pip install tiktoken
import tiktoken
tokenizer = tiktoken.get_encoding("cl100k_base")

def tiktoken_len(text):
    tokens = tokenizer.encode(text)
    return len(tokens)
  • get_encoding을 통해서 임베딩 모델 선언
  • cl100k_base는 gpt 계열 모델들을 임베딩할때 사용되는 임베딩 모델.
tiktoken_len(texts[1].page_content)
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 1000, chunk_overlap=0,length_function=tiktoken_len
)
texts = text_splitter.split_documents(pages)
  • length_function을 tiktoken_len으로 설정해서 tiktoken 기준으로 토큰의 길이를 잰다.
  • pages를 split_documents 함수를 통해서 나눈다.
print(len(texts[1].page_content))
print(tiktoken_len(texts[1].page_content))
token_list = []
for i in range(len(texts)):
    token_list.append(len(texts[i].page_content))
print(token_list)

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

LangChain (8) Retrieval - Vectorstores  (0) 2024.04.08
LangChain (7) Retrieval - Text Embeddings  (0) 2024.04.08
Knowledge Distillation  (0) 2024.04.06
LangChain (5) Retrieval - Document Loaders  (0) 2024.03.23
LangChain (4) Prompt Template  (1) 2024.03.23
728x90

Distilling the Knowledge in a Neural Network

Knowledge Distillation 개념이 처음 등장한 논문은 Geoffrey Hinton, Oriol Vinyals, Jeff Dean이 공동 저술한 "Distilling the Knowledge in a Neural Network"

KD란 더 크고 복잡한 모델의 정보 혹은 지식을 더 작고 효율적인 모델에 주입하는 기술입니다.

KD 등장하게된 이유는 ML에서는 훈련 단계와 배포 단계에서 서로 요구사항이 다른데도 동일한 모델을 사용한다는 문제의식

  • 음성, 객체 탐지 같은 task에서는 훈련 과정에서는 대규모, 중복적인 데이터 세트에서 구조를 추출해야 하지만 실시간으로 작동할 필요는 없고 엄청난 양의 계산이 가능
  • 근데 막상 배포 단계에서 중요한건 지연시간과 계산을 위한 리소스이다.

따라서, 모델이 복잡하더라도 데이터에서 구조를 추출하는게 더 쉽다면 복잡한 모델을 택해야한다.

복잡한 모델을 학습시킨 이후에는 배포에 용이한 단순한 모델에 distillation 다른말로 정보를 전달할 수 있는 훈련을 진행할 수 있음.

이게 가능한 이유는 딥러닝 모델의 지식이란 학습된 모델의 파라미터가 아니라 input vector와 output vector의 학습된 사상(mapping)이라는 것이다.

지식을 전달하기 위해 택한 방법은 복잡한 모델이 생성하는 클래스 확률(class probability)을 “soft target”으로 해서 소형 모델을 학습시키는 것. 반대로 “hard target”은 정답. 이 전달 단계(transfer stage)에서 동일한 훈련 세트나 별개의 transfer set을 사용할 수 있다.

https://blog.roboflow.com/what-is-knowledge-distillation/

결과

  1. MNIST 데이터셋 실험: 큰 모델에서 생성된 소프트 타겟을 사용하여 작은 모델을 훈련시키는 실험에서, 작은 모델은 하드 타겟(실제 레이블)만을 사용했을 때보다 훨씬 낮은 오류율을 보였습니다. 이는 소프트 타겟이 작은 모델에 추가적인 정보를 제공하고, 일반화 능력을 향상시킨다는 것을 입증함.
  2. 음성 인식 실험: 음성 인식 분야에서도 비슷한 실험이 진행되었고, 증류된 작은 모델은 복잡한 모델들의 앙상블과 비슷한 성능을 달성했습니다. 이 결과는 증류 기법이 모델의 성능을 유지하면서도 계산 효율성을 높일 수 있음을 보여줌.

A Survey on Knowledge Distillation of Large Language Models

이러한 개념을 LLM에 적용시키는 여러 사례가 있음.

https://arxiv.org/abs/2402.13116

KD의 역할 3가지

  1. 능력치 향상
  2. 정보 압축 효율성 향상
  3. self-generated knowledge를 통한 self-improvement

다양한 LLM KD 적용 방안

https://arxiv.org/abs/2402.13116

LLM KD pipeline

https://arxiv.org/abs/2402.13116

 

KD 방법론

Textual Output (텍스트 출력물)

  1. Teacher 모델의 Textual Output 생성: Teacher 모델이 입력 데이터(예: 질문, 문장, 문서)에 대한 텍스트 출력물(예: 답변, 요약, 설명) 생성
  2. 학생 모델 학습: 준비된 데이터셋을 사용하여 학생 모델을 훈련. 이때 학생 모델은 교사 모델이 생성한 텍스트 출력물을 최대한 잘 재현하도록 학습. 학습 과정은 보통 지도 학습 방식, 손실 함수(loss function)를 최소화하는 방향으로 모델의 파라미터를 조정
  3. Fine-Tuning: 텍스트 출력물에 기반한 학습을 통해, 학생 모델은 교사 모델의 언어 처리 방식을 모방하고 해당 지식을 내재화함. 학생 모델을 특정 도메인에 fine-tuning도 가능

Soft Target

  1. Soft Targets 생성: Teacher ****모델이 주어진 입력에 대한 출력을 생성할 때, 각 출력(예: 각 단어 또는 토큰)에 대한 확률 분포를 생성. 즉, Teacher 모델이 문장을 생성할 때 각 단어의 선택에 대한 확률을 출력합니다.
  2. Student 모델 학습: Teacher 모델의 확률 분포를 가능한 한 정확하게 모방하도록 학습. 주로 Cross-Entropy Loss Function 같은 확률적 손실 함수 사용.
  3. Fine-Tuning: Student 모델이 teacher 모델의 확률적 판단을 잘 따르도록 fine-tuning 수행KD의 역할 3가지

Knowledge Distillation의 2가지 단계

Knowledge Elicitation

큰 모델 혹은 복잡한 모델에서 정보를 추출하는 과정

  1. Labeling: 교사 모델이 주어진 입력 데이터에 대해 출력을 생성하고, 이 출력 데이터를 학생 모델이 학습할 수 있는 형태로 레이블링하는 방법. 예를 들어, 질문에 대한 답변, 문장에 대한 요약 등
  2. Expansion: 교사 모델이 제공된 seed 데이터 또는 데모를 바탕으로 유사한 새로운 데이터를 생성 → 데이터셋의 다양성과 크기 증가
  3. Data Curation: 교사 모델이 메타정보를 사용하여 데이터를 합성하는 과정. 높은 품질과 다양성을 가진 데이터 생성에 효과적
  4. Feature: 교사 모델의 출력 분포나 hidden layer들을 이용해서 지식을 추출.
  5. Feedback: 교사 모델이 학생 모델의 출력에 대해 피드백을 제공.
  6. Self-Knowledge: 학생 모델이 자체적으로 생성한 데이터를 기반으로 지식을 추출하고 이를 통해 자가 학습.

Knowledge Distillation Algorithm

  1. Supervised Fine-Tuning : Teacher 모델이 생성한 데이터(예: 텍스트 응답)를 사용하여 Student 모델을 지도 학습합니다. 목표는 Student 모델이 Teacher 모델의 출력을 최대한 재현하도록 하는 것.
  2. Divergence and Similarity : Teacher 모델과 student 모델 간의 편차(KLD)를 최소화하는 것을 목표로 함. 즉, Student 모델 출력과 Teacher 모델의 출력 사이의 유사성을 높이는 것.
  3. Reinforcement Learning : 강화 학습 기반의 KD는 학생 모델이 특정 목표(예: 최적의 대화 응답 생성)를 달성하도록 보상 메커니즘을 사용. 복잡한 시나리오나 고급 작업에 적합.
  4. Rank Optimization : 교사 모델의 다양한 출력 사이에서 '최적'이라고 평가되는 결과를 선정하고, 학생 모델이 이러한 최적의 결과를 생성할 수 있도록 학습하는 과정
    1. 교사 모델의 다양한 출력 생성: 교사 모델은 주어진 입력에 대해 여러 가능한 출력을 생성합니다. 동일한 입력에 대한 다양한 해석이나 반응을 나타낼 수 있음.
    2. 최적의 출력 선정: 생성된 출력들 중에서 최적의 결과를 선정. 선정 과정은 교사 모델의 내부 기준, 예를 들어 특정 작업에 대한 효과성, 언어의 자연스러움, 문맥적 정확성 등에 따라 이뤄짐. 최적화 과정에서는 여러 출력들을 비교하고 순위를 매겨 최고의 결과를 선별합니다.
    3. 학생 모델의 순위화 학습: 선정된 최적의 출력을 기준으로 학생 모델을 훈련. 이 과정에서 학생 모델은 교사 모델이 선택한 최적의 출력을 생성할 수 있도록 학습. 이를 위해, 순위화 학습이나 특정 목표를 최적화하는 학습 방법이 사용됨.
    Rank Optimization은 학생 모델이 단순히 교사 모델의 평균적인 성능을 모방하는 것이 아니라, 교사 모델이 생성할 수 있는 최상의 결과를 목표로 삼는다.
728x90

RAG란?

RAG(Retrieval Augmented Generation)이란 외부 데이터를 참조하여 LLM이 답변할 수 있도록 해주는 프레임워크

RAG는 Fine-tuning과 엮여서 설명되는 경우가 많은데 두 방법 모두 LLM이 기존에 갖고 있지 않은 지식을 포함해서 답변할 수 있도록 해주는 프레임워크이다.

Fine-tuning은 LLM을 학습시키는 과정에서 GPU, 데이터셋등 비용이 많이 드는 단점이 존재. 따라서, 일반 상용자가 사용하기 힘들다.

RAG에서는 사용자가 질문을 했을때 QA 시스템이 외부 데이터 저장소에서 사용자의 질문과 유사한 데이터를 검색한다.
그럼 QA 시스템이 검색한 유사 문장과 사용자의 질문을 합쳐서 LLM에게 prompt로 전달을 하고
LLM은 이러한 정보를 종합하여 답변을 한다.
따라서, LLM이 외부 데이터 소스에서 정보를 받아 답변하기 때문에 기존에 없던 정보에 대해서도 답변을 할 수 있다.
특히, LangChain의 Retrieval이란 구조가 QA 시스템, 외부 데이터 저장소, LLM을 엮는 중요한 역할을 한다.
그중에서 오늘은 Document Loaders에 대해 다룬다.

Retrieval

LangChain의 Retrieval은 RAG의 대부분의 구성요소를 아우르며, 구성 요소 하나하나가 RAG의 품질을 좌우한다.

RAG 프레임워크를 완성하기 위해 랭체인에는 Retrieval의 순서가 있다.

  1. Document Loaders : 문서를 불러오는 역할을 한다.
  2. Text Splitters : 문서를 불러온 후 텍스트를 분할하는 과정
  3. Vector Embeddings : 텍스트를 그대로 저장하는게 아니라 벡터로 임베딩 후 저장하기 위해 임베딩 모델을 거친다.
    이후 임베딩 모델을 통해서 수치화된 임베딩 값을 Vector Store에 저장한다.
  4. Retrievers : 수치 데이터를 저장소에 저장하고 사용자의 질문과 가장 유사한 문장을 찾는 역할
  5. Chain : 일련의 과정을 통해 가져온 데이터와 사용자의 질문을 Chain을 통해 LLM이 문장을 생성한다.

Document Loaders란?

Document Loaders는 다양한 형태의 문서를 RAG 전용 객체로 불러들이는 모듈.
ex) pdf, word, ppt, youtube scripts...
이러한 문서들을 가져올때 Document Loaders로 가져와야 LangChain에서 활용할 수 있는 형식으로 불러올 수 있다.
이렇게 불러온 데이터는 2가지 내용을 포함하는데 Page_content와 Metadata이다.

  • Page_content : 문서의 내용
  • Metadata : 문서의 위치, 제목, 페이지 넘버 등

위의 사진에서 answer는 LLM의 답변이고 sources는 답변의 출처이다. sources를 담당하는게 metadata이다.
sources가 중요한 이유는 LLM의 답변이 어떤 자료를 근거로 하는지 확인이 필요할 수 있기 때문이다 -> 사람이 확인해야함

실습

라이브러리 설치

!pip install unstructured==0.6.1 langchain
!pip install langchain pypdf pdf2image docx2txt pdfminer
!pip install openpyxl

파일을 불러올때 필요한 라이브러리들이다.

from google.colab import drive
drive.mount('/content/drive')# 본인 파일 위치

구글 드라이브를 마운트하는 코드로 코딩으로 해도되고 클릭해서 해도됨

Document Loaders

Document Loader는 다양한 형식의 문서를 불러오고 이를 Langchain에 결합하기 쉬운 텍스트 형태로 변환하는 기능을 합니다. 이를 통해 사용자는 txt. 형식의 문서 뿐만 아니라 pdf, word, ppt, xlsx, csv 등 거의 모든 형식의 문서를 기반으로 LLM을 구동할 수 있습니다.

URL Document Loader

LangChain은 웹에 기록된 글도 텍스트 형식으로 가져와 LLM에 활용할 수 있습니다. 대표적인 URL Loader는 WebBaseLoader와 UnstructuredURLLoader가 있습니다.

WebBaseLoader

from langchain.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://n.news.naver.com/mnews/article/092/0002307222?sid=105")

data = loader.load()
print(data[0].page_content)
  • 링크는 뉴스 기사에 해당하는 링크이고 data에 정보를 로드하고 data의 첫번째에 해당하는 값의 page_content를 가져오는 코드
  • loader를 통해 로딩을 하면 list로 불러온다. 따라서, 리스트 값에 바로 page_content로 접근할 수 없다.
  • page_content는 뉴스 기사의 내용에 해당하는 부분이다.
  • URL 로더의 문제점은 URL 상에 페이지의 모든 데이터를 가져온다. 불필요한 데이터까지. LLM한테 전달해도 문제는 없음

UnstructuredURLLoader

from langchain.document_loaders import UnstructuredURLLoader

urls = [
    "https://n.news.naver.com/mnews/article/092/0002307222?sid=105",
    "https://n.news.naver.com/mnews/article/052/0001944792?sid=105"
]
loaders = UnstructuredURLLoader(urls=urls)

data = loader.load()
data
  • urls라는 리스트에 url을 넣어주고 UnstructuredURLLoader에 매개변수로 전달해준다.
  • 로딩해주면 텍스트가 출력되는데 원하지 않는 정보들도 같이 온다.

PDF Document Loader

많은 문서들이 pdf로 구성되어있기 때문에 pdf에서 정보를 추출할 수 있다면 매우 효율적이다.

from langchain.document_loaders import PyPDFLoader 

loader = PyPDFLoader("pdf 파일 아무거나 경로")
pages = loader.load_and_split()
pages[0]
  • load_and_split : pdf 문서를 페이지별로 자르고 리스트로 반환.
    • 메타데이터, 내용 등이 포함됨
  • 따라서, pages에는 각 페이지의 정보가 list로 저장되어있다.
    • pages[0] 에는 첫 번째 페이지의 내용, 메타데이터등이 저장되어있다.
print(pages[1].page_content)

Word Document Loader

from langchain.document_loaders import Docx2txtLoader
loader = Docx2txtLoader("word(docx) 파일 아무거나 경로")
data = loader.load_and_split()
data
data[1].metadata
print(type(data[0].page_content))
print(data[0].page_content)

CSV Document Loader

from langchain.document_loaders.csv_loader import CSVLoader
loader = CSVLoader(file_path = "csv 파일 아무거나 경로")

data = loader.load()
data[:10]
for i in range():
    print(f"row{i}")
    print("-" * 100)
    print(data[i].page_content)
    print(data[i].metadata)

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

LangChain (6) Retrieval - Text Splitters  (0) 2024.04.08
Knowledge Distillation  (0) 2024.04.06
LangChain (4) Prompt Template  (1) 2024.03.23
LangChain (3) OpenAI API 사용법  (0) 2024.03.23
LangChain (2) LLM 개요  (0) 2024.03.23
728x90

Prompt란

Prompt란 모델에 대한 입력. 이 입력이 하드 코딩되는 경우는 거의 없지만 여러 구성요소로 구성되는 경우가 많다.
-> 그냥 프롬프트는 챗지피티 사이트에서 적는 말
PromptTemplate은 이 입력의 구성을 담당한다. LangChain은 Prompt를 쉽게 구성하고 작업할 수 있게 여러 클래스와 함수를 제공한다.
특정 기능을 사용할때 사용자들이 반복적으로 입력해야하는 문장이 있는데 해당 문장을 중복 작성할 필요 없게 구조화하는 방법이다.
예를 들어, 운동 루틴을 짜는 경우 하고싶은 동작, 시간, 부위를 전달하면 루틴을 짜줄텐데 매번 귀찮게 목적을 설명하는 과정없이 동작, 시간, 부위만 입력하면 운동 루틴 짜주게 하는 것이다.

(1) 실습을 위한 라이브러리 설치

오늘도 어김없이 라이브러리를 설치한다.

!pip install -q openai langchain langchain-openai
!pip install -q load_dotenv
!pip show langchain

라이브러리를 import 해준다

import langchain
import load_dotenv
import os,dotenv
dotenv.load_dotenv()

langchain version 확인

langchain.__version__
API_KEY = os.getenv("OPENAI_API_KEY")

(2) Prompt Template

📌프롬프트 템플릿은 크게 2가지가 존재한다.
    1. PromptTemplate
    2. ChatPromptTemplate
이 둘은 사용하는 모델이 일반적인 모델인지, chat 모델인지에 따라 다르다.
1번 PromptTemplate은 일반적인 프롬프트를 생성할때 활용한다.
2번 ChatPromptTemplate은 채팅 LLM에 프롬프트를 전달하는데 활용할 수 있는 특화 프롬프트 템플릿이다.

PromptTemplate 맛보기

템플릿 없이 출력을 만든다면 아래와 같다.

from langchain_openai import OpenAI
llm = OpenAI()
result = llm.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")
print(result)
!pip install icecream

아래와 같이 코드를 짜면 사용할 수 있다.

from icecream import ic
from langchain.prompts import PromptTemplate,ChatPromptTemplate
#프롬프트 템플릿을 통해 매개변수 삽입 가능한 문자열로 변경
string_prompt = PromptTemplate.from_template("tell me a joke about {subject}") # 중괄호 내의 내용만 가변적

#매개변수 삽입한 결과를 string_prompt_value에 할당
string_prompt_value = string_prompt.format_prompt(subject="soccer")

ic(string_prompt_value)
ic(string_prompt_value.to_string());

PromptTemplate.from_template을 통해 템플릿을 만들고 템플릿
안에 변수로 들어갈 값은 .format_prompt(subject="")를 통해 설정할 수 있다.

ChatPromptTemplate

1번

chat_prompt = ChatPromptTemplate.from_template("tell me a joke about {subject}")
chat_prompt_value = chat_prompt.format_prompt(subject = "soccer")
ic(chat_prompt_value)
ic(chat_prompt_value.to_string());

2번

from langchain.schema import AIMessage, HumanMessage, SystemMessage
prompt = SystemMessage(content="You are a nice pirate")
new_prompt= (
    prompt + HumanMessage(content="hi")+AIMessage(content="what")+"{input}"
    )
new_prompt.format_messages(input="i said hi")

아래와 같이 chain을 활용해서 답변을 받을수도 있다.

from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
model = ChatOpenAI()
chain = LLMChain(llm = model,prompt = new_prompt)
chain.run("i said hi")#invoke 

PromptTemplate 활용해보기

반복적인 프롬프트를 삽입하는 경우, PromptTemplate으로 간편하게 LLM을 활용할 수 있다.

from langchain.prompts import PromptTemplate

template = """
너는 요리사야. 내가 가진 재료들을 갖고 만들 수 있는 요리를 추천하고, 그 요리의 레시피를 제시해줘.
내가 가진 재료는 아래와 같아.

<재료>
{재료}

"""

prompt_template = PromptTemplate(
    input_variables = ['재료'],
    template=template
)

prompt_template

위와 같이 template을 구성할 수 있다. input_variables에 재료를 리스트 형태로 잡아주는데 이렇게 하면 재료에 여러 값들이 들어갈 수 있다.

print(prompt_template.format(재료="양파,계란,사과,빵"))

format을 통해 input_variables에 들어가는 값을 지정할 수 있다.
위 코드의 실행 결과를 보면 template 내에 {재료} 부분에 내가 추가한 재료들이 들어가있다.

model = ChatOpenAI(model_name = "gpt-3.5-turbo",max_tokens=512)
chain = LLMChain(llm=model,prompt=prompt_template)
answer=chain.run(재료='양파,계란,사과,빵')

print(answer)

LLMChain을 통해 답변을 받으면 우리가 원하는 답변을 출력하는 것을 확인할 수 있다.

(3) Few-shot 예제를 통한 프롬프트 템플릿

Few-shot이란, 딥러닝 모델이 결과물을 출력할 때 예시 결과물을 제시함으로써 원하는 결과물로 유도하는 방법론입니다.

LLM 역시, Few-shot 예제를 제공하면 예제와 유사한 형태의 결과물을 출력합니다.

내가 원하는 결과물의 형태가 특수하거나, 구조화된 답변을 원할 경우, 결과물의 예시를 수 개 제시함으로써 결과물의 품질을 향상시킬 수 있습니다.

Few Shot Prompt Template 예제

from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate

examples = [
    {
        "question" : "아이유로 삼행시 만들어줘",
        "answer":
"""
아 : 아이유는
이 : 이런 강의를 들을 이
유 : 유가 없다.
"""
    },

    {"question": "김민수로 삼행시 만들어줘",
     "answer":
"""
김 : 김치는 맛있다
민 : 민달팽이도 좋아하는 김치!
수 : 수억을 줘도 김치는 내꺼!
"""
     }
]

다음과 같이 몇가지 예제를 줘서 GPT가 원래 하지 못했던 대답을 하게 하거나 원하는 답변을 할 수 있도록 할 수 있다.
examples 리스트에 "question","answer"를 딕셔너리 타입으로 저장할 수 있다. 이를 통해 여러 개의 예제를 전달할 수 있다.

example_prompt = PromptTemplate(input_variables = ["question","answer"],template="Question: {question}\n{answer}")

print(example_prompt.format(**examples[0]))

위 코드로 examples를 살펴볼 수 있다.

Few Shot Template 선언

prompt = FewShotPromptTemplate(
    examples = examples,
    example_prompt=example_prompt,
    suffix = "Question: {input}",
    input_variables=["input"]
)

print(prompt.format(input="호날두로 삼행시 만들어줘"))
  • suffix는 prompt의 뒤에 "Question: {input}"을 고정해줌
  • 앞에서 넣어준 examples도 포함된 프롬프트를 전달한다.
prompt.format(input="호날두로 삼행시 만들어줘")

Zero Shot

print(model.invoke("호날두로 삼행시 만들어줘"))

실행 결과를 살펴보면 few shot을 제공하지 않은 경우 답변을 제대로 못한다.

Few Shot

print(model.invoke(prompt.format(input="호날두로 삼행시 만들어줘")))

우리가 원하는대로 삼행시를 만들어주는 것을 확인할 수 있다.

(4) Example Selector를 이용한 동적 Few-shot 러닝

Few-shot 예제를 동적으로 입력하고 싶은 경우, Example Selector를 활용할 수 있습니다.
LLM이 여러 작업을 수행하도록 만들되 내가 원하는 범위의 대답을 출력하도록 하려면 사용자의 입력에 동적으로 반응해야합니다.
이와 동시에, 예제를 모두 학습시키는 것이 아니라 적절한 예시만 포함하도록 함으로써 입력 프롬프트의 길이를 제한하고,
이를 통해 오류가 발생하지 않도록 조절할 수 있습니다.

패키지 설치

!pip install chromadb
!pip install tiktoken

Example Selector

from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain.vectorstores import Chroma 
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate

example_prompt = PromptTemplate(
    input_variables=["input","output"],
    template="input: {input}\nOutput: {output}",
)

#These are a lot of example of task of creating antonyms
examples = [
    {"input": "행복", "output": "슬픔"},
    {"input": "흥미", "output": "지루"},
    {"input": "불안", "output": "안정"},
    {"input": "긴 기차", "output": "짧은 기차"},
    {"input": "큰 공", "output": "작은 공"},
]
  • example_selector는 사용자의 질문과 유사한 예시를 가져온다.
  • Chroma는 vectorstore를 통해서 임베딩된 값을 비교하게 해준다.
  • Langchain 임베딩/ openai 임베딩을 통해 문자를 벡터화 하고
  • Langchain prompt를 통해서 fewshotPromptTemplate, PromptTemplate을 만들게 된다.
example_selector = SemanticSimilarityExampleSelector.from_examples(
    #This is the list of examples available to select from.
    examples,
    #This is the Embedding class used to produce embeddings which are used to measure semantic similarity.
    OpenAIEmbeddings(),
    #This is the VectorStore class that is used to store the embeddings and do a similarity search over.
    Chroma,
    #This is the number of examples to produce.
    k=1
)
similar_prompt = FewShotPromptTemplate(
    #We provide an ExampleSelector instead of examples.
    example_selector=example_selector,
    example_prompt = example_prompt,
    prefix="주어진 입력에 대해 반대의 의미를 가진 단어를 출력해줘",
    suffix="Input: {단어}\nOutput:",
    input_variables=["단어"],
)
  • example_selector를 통해 가장 유사성이 높은 예제를 가져오고 example_prompt는 위에서 설정한 것처럼 input, output 들어간 부분을 넣어준다.
  • pefix는 맨 앞에서 고정할 부분을 추가하고
  • suffix는 뒤에 고정할 부분이다.
  • similar_prompt는 FewShotPromptTemplate을 통해서 prefix와 suffix로 고정된 부분중 input_variable에 해당하는 부분과 가장 유사한 example을 example_selector를 통해서 가져오고 example_prompt를 통해서 하나로 묶어준다.

감정 비교

# Input is a feeling, so should select the happy/sad example
print(similar_prompt.format(단어="무서운"))

대소 비교

# Input is a feeling, so should select the happy/sad example
print(similar_prompt.format(단어="큰 비행기"))
query = "큰 비행기"
model = OpenAI(model_name = "gpt-3.5-turbo-instruct",max_tokens=512)
print(model(similar_prompt.format(단어=query)))

무서운의 경우 감정적으로 반대에 해당하는 단어를 출력해야하고 큰 비행기의 경우에는 대소비교를 통해서 단어를 출력해야한다.
이런식으로 단어를 유도해야할때 참고해야하는 예제가 다를 수 있다. 따라서, 이런 목적을 달성하기 위해서 존재하는게 example_selector이다.
동적으로 few-shot learning을 수행한다.
내가 원하는 답변을 출력하도록 하려면 사용자가 어떤 질문을 했느냐에 따라서 다른 example을 넣어줘야한다. 그리고 예제를 모두 학습시키는게 아니라 적절한 예제만 학습하게 해서 입력되는 프롬프트가 너무 길지 않고 오류가 발생하지 않도록 조절해야한다.

(5) Partial Prompt Template

from datetime import datetime
def _get_datetime():
    now = datetime.now()
    return now.strftime("%Y/%d/%m")
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}",
    input_variables=["adjective","date"],
)
partial_prompt = prompt.partial(date=_get_datetime)
print(partial_prompt.format(adjective="funny"))
prompt = PromptTemplate(
    template="Tell me a {adjective} joke about the day {date}",
    input_variables=["adjective"],
    partial_variables={"date":_get_datetime},
)
print(prompt.format(adjective="funny"))

(6) Output Parser를 활용한 출력값 조정

LLM의 답변을 내가 원하는 형태로 고정하고 싶다면 Outputparser 함수를 활용할 수 있다. 리스트, JSON 형태 등 다양한 형식의 답변을 고정하여 출력할 수 있습니다.

from langchain.output_parsers import CommaSeparatedListOutputParser
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_openai import OpenAI, ChatOpenAI

CommaSeparatedListOutputParser를 통해서 콤마로 구분된 리스트형태로 출력을 받을 수 있다.

output_parser = CommaSeparatedListOutputParser()
format_instructions = output_parser.get_format_instructions()
format_instructions
prompt = PromptTemplate(
    template = "{주제} 5개를 추천해줘.\n{format_instructions}",
    input_variables=["주제"],
    partial_variables={"format_instructions":format_instructions}
)
  • input_variables는 사용자가 입력하는 부분, 사용자가 수정 가능한 부분
  • partial_variables : 위에서 선언한 format_instruction을 넣어준다.
  • model = OpenAI(temperature=0) _input = prompt.format(주제="영화") output = model(_input) output
output_parser.parse(output)

위에서 설정한 output parser를 기준으로 parse를 하면 리스트로 감싸진 형태로 얻을 수 있다.

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

Knowledge Distillation  (0) 2024.04.06
LangChain (5) Retrieval - Document Loaders  (0) 2024.03.23
LangChain (3) OpenAI API 사용법  (0) 2024.03.23
LangChain (2) LLM 개요  (0) 2024.03.23
LangChain (1) 개요  (0) 2024.03.23
728x90

LLM 코드를 실습할때 필요한 실습

실습을 위해 필요한 패키지 설치

!pip install langchain
!pip install openai
!pip install langchain-openai

OpenAI API Key 환경변수 설정

import os

os.environ["OPENAI_API_KEY"] ="Your API Key"

사실 그냥 매번 깡으로 OPENAI_API_KEY 변수에 넣어줘도 되는데 github같은데 올리면 api key 노출됐다고 연락오고 openai에서 해당 키를 막아버림. 매우 귀찮으니까 그냥 환경변수로 설정하자

Langchain 활용해서 GPT 답변받기

GPT-3.5-Turbo-instruct 모델

from langchain_openai import OpenAI
llm = OpenAI()
result = llm.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")
print(result)

OpenAI의 디폴트는 gpt-3.5-turbo-instruct 모델이다. OpenAI()의 invoke메서드를 통해서 답변을 받을 수 있다.

아래와 같이 명시적으로 모델명을 적어줘도 된다. max_token은 답변의 최대 토큰을 설정하는 매개변수이다.

from langchain_openai import OpenAI
llm = OpenAI()
llm = OpenAI(model_name = "gpt-3.5-turbo-instruct", max_tokens = -1)
result = llm.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")
print(result)

instruct 모델의 특징은 답변을 내놓기는 하지만 대화형식이 아니라는 점이다.

GPT-3.5-Turbo 모델

아래 코드로 채팅형식의 답변을 받을수 있다.

from langchain_openai import ChatOpenAI

chatgpt = ChatOpenAI(model_name = "gpt-3.5-turbo", max_tokens = 512)
answer = chatgpt.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")
print(answer.content)
print(answer)

여기서 answer에는 답변 외에도 다양한 매개변수 정보가 들어있는데 .content 메서드를 통해서 답변만 텍스트로 추출할 수 있다.

Temperature 조절

GPT를 비롯한 다양한 모델에는 Temperature 매개변수가 있는데 이는 답변의 일관성 관련있는 매개변수이다.
Temperature가 높으면 더 다양하고 창의적인 답변을 내놓는다.

chatgpt_temp0_1 = ChatOpenAI(model_name = "gpt-3.5-turbo",temperature=0,max_tokens=512)
chatgpt_temp0_2 = ChatOpenAI(model_name = "gpt-3.5-turbo",temperature=0,max_tokens=512)
chatgpt_temp1_1 = ChatOpenAI(model_name = "gpt-3.5-turbo",temperature=1,max_tokens=512)
chatgpt_temp1_2 = ChatOpenAI(model_name = "gpt-3.5-turbo",temperature=1,max_tokens=512)

model_list = [chatgpt_temp0_1, chatgpt_temp0_2,chatgpt_temp1_1,chatgpt_temp1_2]

for i in model_list:
    answer = i.invoke("왜 파이썬이 가장 인기있는 프로그래밍 언어야?",max_tokens=128)
    print("-"*100)
    print(">>>",answer.content)

따라서, 신뢰성이 중요한 답변은 Temperature가 낮아야한다.

실시간 응답 출력

Streaming 매개변수를 True로 놓고 StreamingStdOutCallbackHandler라는 걸 사용하면 답변을 스트림으로 받을 수 있다.
즉, 실제 chatgpt를 사용하는 것처럼 답변이 생성되는 모습을 볼 수 있다.

from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

chatgpt = ChatOpenAI(model_name = "gpt-3.5-turbo",streaming=True,callbacks=[StreamingStdOutCallbackHandler()],temperature = 1)
answer = chatgpt.predict("왜 파이썬이 가장 인기있는 프로그래밍 언어야?")

이 기능을 사용하면 사용자가 답변이 다소 느리더라도 용인하게 되는 경향이 있다고 카더라..

📌ChatGPT API는 기본 OpenAI LLM들과 다른 Input 형식을 갖고 있습니다.

ChatGPT는 대화에 특화된 LLM인만큼, 아래와 같은 2가지 독특한 매개변수를 지닙니다.

(1) SystemMessage: ChatGPT에게 역할을 부여하여, 대화의 맥락을 설정하는 메세지

(2) HumanMessage: 사용자가 ChatGPT에게 대화 또는 요청을 위해 보내는 메세지

위 두가지 형식을 적절히 활용하면, LLM을 더욱 효과적으로 사용할 수 있습니다.

ChatGPT에 역할 부여

역할 부여 X

chatgpt = ChatOpenAI(model_name="gpt-3.5-turbo",temperature=1)
response_langchain = chatgpt.invoke("파이썬의 장점에 대해서 설명해줘.")
print(response_langchain.content)

역할 부여 O

from langchain.chat_models import ChatOpenAI    #from langchain_openai import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage, SystemMessage

chatgpt = ChatOpenAI(model_name = "gpt-3.5-turbo",temperature=1)

messages=[
    SystemMessage(
        content = "너는 20년차 시니어 개발자야. 사용자의 질문에 매우 건방지게 대답해줘."
    ),
    HumanMessage(
        content="파이썬의 장점에 대해서 설명해줘."    
    ),
]
response_langchain = chatgpt.invoke(messages)
print(response_langchain.content)

LLM 응답 캐싱

from langchain.globals import set_llm_cache
from langchain_openai import OpenAI

llm = OpenAI(model_name = "gpt-3.5-turbo-instruct",n=2,best_of=2)

LLM의 응답을 캐싱해서 같은 질문에 더 빨리 답변받을 수 있다.

캐싱 X

%%time
from langchain.cache import InMemoryCache

set_llm_cache(InMemoryCache())

#첫 질문. 아직 캐쉬에 저장 안됨
llm.predict("Tell me a joke")

캐싱 O

%%time
#두번째 같은 질문. 캐쉬에 저장이 되어 답변이 훨씬 빠름
llm.predict("Tell me a joke")

 

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

Knowledge Distillation  (0) 2024.04.06
LangChain (5) Retrieval - Document Loaders  (0) 2024.03.23
LangChain (4) Prompt Template  (1) 2024.03.23
LangChain (2) LLM 개요  (0) 2024.03.23
LangChain (1) 개요  (0) 2024.03.23
728x90

Transformer 아키텍처

현재 NLP 분야에서 사용되는 언어모델의 대부분은 Transformer 아키텍처를 기반으로 하고, 모델의 용도에 따라

트랜스포머 아키텍처의 Encoder, Decoder를 사용하는 추세. (다 같이 사용하기도 함)

위의 이미지와 같이 생긴 구조인데 왼쪽이 Encoder, 오른쪽이 Decoder임.
쉽게 설명하면 Encoder는 해석을 잘하고, Decoder는 생성을 잘한다.

그림을 살펴보면 Encoder Only, Decoder Only, Encoder-Decoder 기반 모델들을 살펴볼 수 있다.

LLM 공개 여부

Closed Source

OpenAI, Google의 경우 GPT 모델과 PALM, Bard 모델들이 있는데 모두 코드가 공개되어있지 않다.
둘다 성능이 뛰어나고 API로 호출할 수 있어 편리하지만 보안문제와 비용문제가 존재한다.

Open Source

LLaMA 계열 LLM이 존재한다. LLAMA를 개량해서 나온 버전들로 vicuna, alpaca, LLAMA2를 개량한 upstage-llama2 등이 있다.
그 외의 Open-source LLM으로는 MPT-7B, WizardLM이 있다.
이들의 장점은 Closed Source에 못지 않은 성능과 높은 보안성, 모델 생성후 추가적인 비용이 필요 없다는데 있다.
하지만, 별도의 GPU 서버를 구축해야하고 개발의 난이도가 높다는 단점이 있다.

 

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

Knowledge Distillation  (0) 2024.04.06
LangChain (5) Retrieval - Document Loaders  (0) 2024.03.23
LangChain (4) Prompt Template  (1) 2024.03.23
LangChain (3) OpenAI API 사용법  (0) 2024.03.23
LangChain (1) 개요  (0) 2024.03.23
728x90

LangChain의 개념

  • Langchain은 언어 모델로 구동되는 애플리케이션을 개발하기 위한 프레임워크
  • 강력하고 차별화된 애플리케이션은 API를 통해 언어 모델을 호출하고 이를 통해 개발 가능하다
  • 기능
    • 데이터 인식 : 언어 모델을 다른 데이터 소스에 연결
    • 에이전트 기능 : 사용자가 구체적인 툴이나 방법론을 제시하지 않아도 언어 모델이 환경과 상호 작용할 수 있도록 한다.

결론적으로, 언어모델을 잘 활용하기 위해 사용할 수 있는 도구.

LangChain의 필요성

기존 LLM들의 한계

ChatGPT

ChatGPT의 경우 여러 한계가 존재합니다.

  1. 정보 접근 제한
    chatGPT는 2021년까지의 데이터를 학습한 LLM으로 2022년 이후의 정보에 대해서는 답변을 하지 못하거나, 거짓된 답변을 제공한다.
  2. 토큰 제한
    ChatGPT에서 제공하는 모델 GPT-3.5, GPT-4는 각각 4096, 8192 토큰이라는 입력 토큰 제한이 존재.
  3. 환각 현상
    사실에 대한 질문을 했을때, 잘못된 대답(거짓된 대답)을 내놓는 경우가 있다.

LLM 개량 방법

ChatGPT를 개량하기 위해서는 다양한 방법이 존재한다.

  1. Fine-tuning
    기존의 LLM 모델뿐만 아니라 딥러닝 모델들의 가중치를 조정해서 원하는 Downstream Task(용도)에 맞게 재구성하는 방법
  2. N-shot Learning
    n개의 출력 예제를 제공해서 딥러닝 모델이 용도에 알맞은 출력을 하도록 조정하는 것.
  3. In-context Learning
    문맥을 제시하고, 이 문맥을 기반으로 모델이 출력을 하도록 조정

LangChain

위의 3가지 방법론 중에서 LangChain은 3번 In-context Learning을 통한 접근 방식을 택함
실무에서 사용하기 가장 적합한 방법론은 3번임.
Fine-tuning의 경우 모델을 재학습시켜야 하는데 이 과정에서 필요한 비용, 시간등을 고려해보면 비용이 많이 든다.
N-shot learning의 경우는 LLM에 정보를 제공해야하므로 보안문제가 발생할 수 있다.

LangChain을 통한 ChatGPT의 한계 극복

  1. 정보 접근 제한
    Vectorstore 기반의 정보 탐색, Agent를 활용한 검색을 결합해서 다양한 자료에 접근 가능하다.
  2. 토큰 제한
    TextSplitter를 활용해서 문서를 분할 할 수 있다. 문서들을 기반으로 답변을 요약하고 정리해서 최종 답변을 받을 수 있다.
  3. 환각 현상
    주어진 문서에 대해서만 답하도록 Prompting 가능

LangChain의 종류와 역할

LangChain의 구성 요소

1. LLM

LLM은 초거대 언어 모델로, 생성 모델의 엔진과 같은 역할을 하는 핵심 구성 요소입니다.
LLM의 예시로는 GPT-3.5, PALM-2,LLAMA,StableVicuna,WizardLM,MPT 등이 있습니다.

2. Prompt

LLM에 지시하는 지시문
요소: Prompt Templates, Chat Prompt Template, Example Selectors, Output Parsers

  • Example Selectors : 내가 원하는 출력 형식을 몇가지 예시를 통해서 전달할 수 있는데 이때 어떤 예시를 선택해서 LLM을 이해시키는 것도 자동으로 가능
  • Output Parser : 질문을 하면 LLM이 문장으로 출력해주는데 이때 원하는 형식으로 대답이 나오게 할 수 있다.

3. Index

LLM이 문서를 쉽게 탐색할 수 있게 구조화 하는 모델
예시 : Document Loaders, Text Splitter, Vectorstores, Retrievers...

4. Memory

채팅 이력을 기억하도록 하여, 이를 기반으로 대화가 가능하도록 하는 모듈
예시 : ConversationBufferMemory, Entity Memory, Conversation Knowledge Graph Memory...

5. Chain

LLM 사슬을 형성하여, 연속적인 LLM 호출이 가능하도록 하는 핵심 구성 요소
예시 : LLM Chain, Question Answering, Summarization, Retrieval Question/Answering...

  • 원래 Prompt를 주면 출력이 하나가 나오는데 거기서 끝나는게 아니라 Prompt를 줬을때 백엔드에서 LangChain이 그 Prompt를 받아서
    그 뒤에 Prompt를 만들고, 그걸 또 받고 만드는 과정
    chain을 구성할 때 어떤 목적을 통해서 출력을 받고 싶은지 생각하고 구성해야함. 예를 들어서 QA의 경우 간단하게 물어보더라고 그에 대해서
    더 나은 대답을 위해 백엔드에서 Prompt Template이 들어가있다.

6. Agent

LLM이 기존 Prompt Template으로 수행할 수 없는 작업을 가능케하는 모듈
예시 : Custom Agent, Custom MultiAction Agent, Conversation Agent
사실 Agent 자체보다는 agent에 들어가는 툴들이 다양하다. 예를 들어, web 검색이 가능하다던가, sql 쿼리를 통해 정보를 받는다던가 ...
그런 여러가지 Tool들을 LLM이 자체적으로 판단해서 사용하게 하는 것이 Agent이다.

LangChain 활용 예시

PDF 챗봇 구축

문서를 기반으로 챗봇을 구축하는 경우, 아래와 같은 과정을 통해 대화가 가능하도록 한다

  1. 문서 업로드
    문서를 업로드하는 과정에서도 LangChain이 사용된다. pdf를 Document Loader라는 객체를 통해 업로드하게 되면 추후에 유사도 검색을 통해서 정보를 가져올때 정보의 위치를 파악하는데 Document Loader를 통해서 한다.
  2. 문서 분할
    ChatGPT는 토큰 제한이 있기 때문에 대화를 할때 문서를 통째로 주고 대화하는건 불가능하다. 긴 문서를 여러 개로 분할하게 된다.
    분할 시 장점은 질문과 가장 유사한 내용을 분할된 pdf에서 일부만 가져올 수 있다는 점이다.
  3. 문서 임베딩
    문서를 LLM이 이해할 수 있도록 vector화한다.
  4. 임베딩 검색
    pdf를 기반으로 ChatGPT와 대화를 하겠다고 하면 결국 질문의 답에 해당하는 정보를 가져와서 ChatGPT에게 말을 시키는 과정이다.
    그 역할을 해주는게 임베딩 검색 과정이다. LangChain에서는 VectorStoreRetriever를 통해서 분할된 문서를 임베딩한 곳에서
    검색을 한다.
  5. 답변 생성
    LLM이 QA chain을 통해서 답변을 생성한다. 질문을 하게되면 가장 유사한 텍스트를 가져오고 그것을 Prompt 첫번째 것으로 만들어내고
    그걸 다시 ChatGPT한테 주고 Prompt를 생성하고 다시 질문을 해서 최종 답변을 생성한다.

Reference

모두의 AI 유튜브 채널[https://www.youtube.com/@AI-km1yn]

'인공지능 > RAG' 카테고리의 다른 글

Knowledge Distillation  (0) 2024.04.06
LangChain (5) Retrieval - Document Loaders  (0) 2024.03.23
LangChain (4) Prompt Template  (1) 2024.03.23
LangChain (3) OpenAI API 사용법  (0) 2024.03.23
LangChain (2) LLM 개요  (0) 2024.03.23
728x90

1. 벡터, 행렬 그리고 텐서(Vector, Matrix and Tensor)

차원이 없는 값은 스칼라, 1차원 값은 벡터이다. 행렬은 모두가 알다시피 [1,2,3] 이런 값이다.
즉. 2차원으로 구성된 값을 행렬이라 한다.
이제 그 값들이 3차원으로 넘어가게 되면 그것을 Tensor(텐서)라고 부르게 된다.
그런데 컴퓨터 공학에서는 벡터를 1차원 텐서, 행렬을 2차원 텐서등으로 표현한다.

차원의 개념을 이해할때 1차원은 방, 2차원은 층, 3차원은 아파트 동, 4차원은 아파트 단지.
나는 이런 식으로 이해를 했다.

2차원 텐서 표현

그리고 2차원 텐서를 표현할때 행의 개수는 batch size, 열의 개수는 dimension으로 표기한다.
그 이유는 모델에 들어갈때 batch 단위로 들어가는데 이때 행 마다 들어가므로 batch size,
그리고 들어가는 입력의 길이 혹은 크기를 dimension이라고 하기 때문이다.

예를 들어 데이터가 6400개 있고, 각 데이터 하나의 크기가 64라고 하면 데이터의 차원은 64차원이고
전체 데이터셋의 크기는 6400 x 64이다.
하지만 모델이 데이터를 처리하는 기준은 batch size이다. batch size는 한번에 들어가는 데이터의 개수라고 생각하면 된다.
따라서, batch size가 64이면 64 x 64 의 데이터가 들어가고 이 과정을 100번 반복하게된다.

비전 분야의 3차원 텐서

이미지 데이터의 경우 RGB 값이 있고 각각의 영역이 2차원이므로 총 3차원이 된다.
여기서 이미지 데이터를 여러 장 사용하는 경우 3차원 데이터가 여러 개이므로 4차원 데이터가 된다.
이 경우 (batch size / # of images, width, height, channel)이 된다.

자연어 처리 분야의 3차원 텐서

자연어 처리 분야에서 3차원 텐서의 각 요소는 batch size, 문장 길이, 단어 벡터의 차원이 된다.
즉, (batch size, length, dimension)이 된다.

3D 텐서 예시

[[아 배고파 밥 줘] [집가서 빨리 게임하고 싶다] [운동해야 하는데 시간이 없다]]

컴퓨터는 이러한 데이터를 처리할때 단어가 몇개인지 알 수가 없다.
이를 컴퓨터가 처리할 수 있도록 하는 과정이 필요하다.
우선 단어별로 나눠준다.

[['아', '배고파', '밥','줘'],['집가서','빨리','게임하고','싶다'],['운동해야','하는데','시간이','없다']]

이제 위의 데이터는 3 x 4크기를 가지는 2D 텐서입니다. 컴퓨터는 텍스트보다는 숫자에 대한 처리가 빠릅니다.
이제 각 단어를 벡터로 만들어주는데 각 단어를 3차원의 벡터로 변환을 하게 됩니다.

'아' = [0.1,0.3,0.4]
'배고파' = [0.4,0.2,0.1]
'밥' = [0.2,0.2,0.1]
'줘' = [0.9,0.1,0.9]

... 이런식으로 변환한다.

그러면 이제 데이터를 벡터로 표현하면 아래와 같이 표현할 수 있습니다.

[[[0.1,0.3,0.4],[0.4,0.2,0.1],[0.2,0.2,0.1],[0.9,0.1,0.9]],
 [[~~                                                    ]],
 [[~~                                                    ]]]

데이터셋의 차원은 이제 3x4x4 차원 입니다.
여기서 batch size를 2로 하고 가운데 문장을 2번 사용한다고 할때,

첫번째 배치
[[[0.1,0.3,0.4],[0.4,0.2,0.1],[0.2,0.2,0.1],[0.9,0.1,0.9]],
 [[~~                                                    ]]]
두번째 배치
[[[~~                                                    ]],
 [[~~                                                    ]]]

각 배치의 텐서의 크기는 2 x 4 x 4가 된다.

Reference

https://wikidocs.net/52460

728x90

오늘은 Linear Regression 문제에서 Gradient descent를 직접 구현해보자

데이터셋 로드

오늘의 데이터셋은 사이킷런의 당뇨병 데이터셋이다

총 10가지의 feature 열이 있는데 그중에서 bmi만을 가지고 실습을 진행한다.

아래의 코드를 통해 데이터셋을 로드하고 훈련 데이터셋과 테스트 데이터셋으로 나눈다.

import matplotlib.pylab as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn import datasets

# Load the diabetes dataset.
diabetes_X, diabetes_y = datasets.load_diabetes(return_X_y=True)

# Select only one feature (BMI) and make into a 2-D array. The index of BMI feature is 2.
diabetes_X_new = diabetes_X[:, np.newaxis, 2]

# Separate training data from test data.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(diabetes_X_new, diabetes_y, test_size=0.1, random_state=0)

Gradient Descent 식

참고로 Gradient Descent의 식은 다음과 같다.
$$W^{n+1} = W^n - r_{W} \frac{\delta_{Loss}}{\delta_{W}} $$

그리고 Loss 값을 구하는 식은 다음과 같다.
$$ (y-f(x))^2 $$
$$ Loss(W,b) = \sum (y-f(x))^2 $$

따라서 우리는 Loss 식을 각각 W, b에 대해 편미분을 하고 GD 식에 적용하면 된다.
이제 코드로 구현해보자

코드 구현

# Train W and b using the training data only
# Use X_train and y_train only

W = np.random.rand()        # Initialization of W
b = np.random.rand()        # Initialization of b

epochs = 25000              # number of epochs
n = float(len(X_train))    # number of training samples
lr = 0.1                   # learning rate

train_loss = []
for k in range(epochs):
    y_pred = W * X_train + b
    loss = np.square(y_train-y_pred)
    loss = loss / n
    train_loss.append(np.sum(loss))

    dW = (-1/n) * np.sum((y_train-y_pred) * X_train)
    db = (-1/n) * np.sum(y_train-y_pred) / n

    W = W - lr * dW
    b = b - lr * db

위 코드를 실행하면 상당히 빠른 시간안에 25000번의 epoch을 돌고 W,b를 구한다.
그 값을 확인해보자

print("Trained parameters")
print("W:", W,", b:", b)

나의 경우에는 다음과 같은 값이 출력되었다.

Trained parameters
W: 963.1825770214525 , b: 150.92371110031215

데이터 시각화

이제 훈련데이터의 데이터 포인트와 우리의 Linear Regression 함수를 좌표계에 시각화하자

# Checking for traing: Using training data
# use X_train and y_train

y_pred = W*X_train + b
plt.scatter(X_train, y_train,  color='black')
plt.plot(X_train,y_pred, color='blue', linewidth=3)
plt.title("Linear Regression Training Results")
plt.show()

테스트 데이터의 데이터 포인트로 시각화를 한다.

# Prediction: Using only the test data
# use X_test and y test only

y_pred = W*X_test + b
plt.scatter(X_test, y_test,  color='black')
plt.plot(X_test,y_pred, color='blue', linewidth=3)
plt.title("Linear Regression Test")
plt.show()

이제 train loss값을 시각화하자. 에폭이 지나면서 추이를 살피면 학습이 잘 되는지 파악할 수 있다.
우하향하여 진동 없이 수렴하면 베스트

# Display the loss at every epoch (sum of squares error on your training data)
plt.plot(np.arange(epochs), train_loss,  color='black')
plt.title("Loss vs epochs")
plt.xlabel("epochs")
plt.ylabel("Training Loss")
plt.show()

'인공지능 > 인공지능 기초 개념' 카테고리의 다른 글

기초 개념편 (1) Machine Learning이란?  (0) 2024.04.13
Tensor에 대해  (0) 2024.03.19
(7)-2 비지도학습 실습  (4) 2024.03.15
(7) 비지도 학습  (1) 2024.03.15
(6)-2 지도학습 실습  (2) 2024.03.15
728x90

iterm2를 새로 설치하게 되면서 기존 디폴트 쉘이 bash에서 zsh로 변경되었다. 근데 쉘이 변경되면서 conda 명령어를 인식을 못해서
왜그런가 했더니 환경변수를 설정해주지 않아서 그랬던 것이었다.
conda 설치 경로를 확인하려고 우선 bash로 전환하고 아래 명령어를 통해 conda 경로를 확인했다.

#디폴트 쉘 bash로 설정
chsh -s /bin/bash
#디폴트 쉘 zsh로 설정
chsh -s /bin/zsh
#conda 경로 확인
which conda

conda 경로 확인하면 zshrc 파일을 열어서 맨 아래 코드를 추가한다

#환경변수 파일 즉,zshrc 파일을 열어야 한다.
vim ~/.zshrc

이때 /path/to/anaconda 부분을 본인 conda 경로로 바꿔준다.

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/path/to/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/path/to/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/path/to/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/path/to/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

이후 vim 종료하고 아래 코드로 환경변수 변경 사항을 반영한다.

source ~/.zhsrc

그럼 끝~

+ Recent posts