2024. 6. 22. 00:49ㆍDL
우리는 RAG를 구성하기 위해서 다향한 데이터를 Embedding하여 VectorDB에 저장해야 한다고 바로 이전글에서 알아보았는데요 그럼 어떻게 Embedding할 데이터를 만들까요?
일반적으로 html, Text, PDF, MS Document(Excel, ppt, docs)등 다양한 문서의 형태가 있는데 이것들을 Read -> Chunk로 분할 하여 Embedding에 사용되기 직전 까지의 과정을 한번 살펴볼겁니다.
너무 많은 Text Splitter가 있는데 그중에서 Character Splitter를 살펴보려고 합니다. 더 많은 Splitter에 대해서는 아래 링크를 참고하시기 바랍니다!
https://python.langchain.com/v0.1/docs/modules/data_connection/document_transformers/
1. TextLoader
문서를 불러와 langchain에서 제공하는 Document라는 객채로 생성해주는 역할을 하는 Class입니다. 특별한 사용법이 없기 때문에 바로 코드로 확인하시죠
from langchain_community.document_loaders import TextLoader
from glob import glob
file_list = glob("./text_data/*.txt")
print(len(file_list))
texts = []
for file in file_list:
loader = TextLoader(file)
documents = loader.load()
texts.append(documents)
위와 같이 선언을 하고 TextLoader에 File경로와 함깨 loader객체를 생성해서 이를 load 메서드로 불러옵니다. 이렇게 불러와 Texts를 찍어보면
[Document(page_content='# README #\n\nThis README would normally document whatever steps are necessary to get your application up and running.\n\n### What is this repository for? ###\n\n* Quick summary\n* Version\n*...)] 과 같은 형태의 Document 객체로 불러온것을 볼 수 있습니다.
2. RecursivelyCharacterTextSplitter
두번째로 살펴볼 녀석은 RecursivelyCharecterTextSplitter입니다. 오늘 메인으로 다룰 일반적인 문자열 형태의 문서를 원하는 크기로 잘라내주는 역할을 합니다. 이름 그대로 "재귀적 문자열 텍스트 분할기" 입니다. 내부적으로 재귀를 돌며 문자열 덩어리를 충분히 작어질 때 까지 분할해 냅니다.
입력받은 여러 파라메터들이 있는데, 어떤 문자를 기준으로 문서를 분할할것인지, 얼마정도의 크기로 분할할것인지, 전체 문서를 분할 할 때 얼마씩 overlap할 것인지. 다양한 파라메터가 존재합니다. 코드로 살펴보시죠.
def __init__(
self,
chunk_size: int = 4000,
chunk_overlap: int = 200,
length_function: Callable[[str], int] = len,
keep_separator: Union[bool, Literal["start", "end"]] = False,
add_start_index: bool = False,
strip_whitespace: bool = True,
) -> None:
"""Create a new TextSplitter.
Args:
chunk_size: Maximum size of chunks to return
chunk_overlap: Overlap in characters between chunks
length_function: Function that measures the length of given chunks
keep_separator: Whether to keep the separator and where to place it
in each corresponding chunk (True='start')
add_start_index: If `True`, includes chunk's start index in metadata
strip_whitespace: If `True`, strips whitespace from the start and end of
every document
"""
if chunk_overlap > chunk_size:
raise ValueError(
f"Got a larger chunk overlap ({chunk_overlap}) than chunk size "
f"({chunk_size}), should be smaller."
)
self._chunk_size = chunk_size
self._chunk_overlap = chunk_overlap
self._length_function = length_function
self._keep_separator = keep_separator
self._add_start_index = add_start_index
self._strip_whitespace = strip_whitespace
요 코드는 RecursivelyCharecterTextSplitter가 상속받는 TextSpliter의 init함수입니다. 어떤 값들을 요구하는지 알아볼 수 있는데요
- chunk_size : 정해진 길이측정 함수에 따라서 각 분할문서의 최대 값을 결정합니다.
- chunk_overlap : 각 분할된 문서마다, 어느정도의 길이만큼 서로 겹치게 분할할 것인지 결정합니다. 이런 행위를 하는 이유는 RAG를 이용한 prompt에 Context로 이 문서를 넣어줄 때 최대한 문맥의 흐름이 끊기지 않도록 하기 위한 하나의 방법입니다.
- Length_function : 문서를 자를때 문서의 Chunk를 어떻게 측정할것인지 측정하는 함수를 넣어줍니다. 대표적으로는 python 기본함수인 len이 있죠
요 정도가 가장 중요하고 이외 필요한것은 영문으로 적혀있으니 한번 천천히 읽어보시길 추천드립니다.
from langchain_text_splitters import RecursiveCharacterTextSplitter
import tiktoken
tokenizer = tiktoken.get_encoding("cl100k_base")
def tiktoken_len(text):
tokens = tokenizer.encode(text)
return len(tokens)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap= 100,
length_function=tiktoken_len
)
splited_documents = text_splitter.split_documents(texts[0])
다음은 실제 분할을 하는 코드입니다. 그냥 사용하기가 뭐 별게 없습니다. 오히려 파라메터를 이해하는게 더 어렵죠..
이런식으로 분할된 문서는 RAG를 구성하는 VectorDB로 보내지게 됩니다.
RecursivelyCharecterTextSplitter의 장점은 문장의 여러 요소들을 최대한 엮으려 한다는데 있습니다. 단순히 Chunk로 딱딱 자르는게 아니라 문장요소를 고려해서 자른다는데 있는데요 저도 정확한 동작원리를 내부 함수를 다 열어보지 못했지만.
우선 Langchin 공식문서에서는 이렇게 표현하고 있습니다. 사실 이마저도 굉장히 문제가 있다보니, 저는 결국 다른 방법을 고려하게 되었지만요
RAG구현 시리즈는 계속됩니다. 감사합니다.
'DL' 카테고리의 다른 글
[Langchain] Langserve에서 LLM Agent를 활용한 Response Streaming (0) | 2024.06.26 |
---|---|
[LLM] Claude 3.5 Sonnet 출시 개인적인 느낀점 (0) | 2024.06.22 |
[LLM] RAG란 무엇인가? (0) | 2024.06.20 |
[GPT]GPT-3.5 Turbo Fine Tuning 시켜보기 (0) | 2024.03.09 |
[GPT]GPT API를 사용하여 간단한 쳇 시스템 만들어보기 (0) | 2024.03.01 |