[RAG] 텍스트 분할

avatar
2024.12.30
·
7 min read

2675

01. 텍스트 분할의 개념과 중요성

  • RAG에서 LOAD 이후의 과정

  • 사용 이유

    • LLM의 input token 길이 제한에 걸리지 않기 위해

    • 참고할만한 정보는 일부이기에 리소스 최적화

    • 할루시네이션 방지

  • chunk를 잘 만들면, 유사도도 잘 잡힐 것이다.

  • 불필요한 답변을 포함하면 제대로된 답변으로 연결되지 않는다.

  • 분할 전략

    • chunk overlap : 겹쳐지는 부분

02. CharacterTextSplitter

  • 기본적으로 \n\n(문단)을 기준으로 문자 단위로 텍스트를 분할

  • 청크의 크기를 문자 수로 측정

  • 210자 언저리로 분할

  • chunk_size, chunk_overlap 지정

from langchain_text_splitters import CharacterTextSplitter

text_splitter = CharacterTextSplitter(
    separator="\\n\\n",
    chunk_size=210,
    chunk_overlap=0,
    length_function=len,
)

# 메타데이터와 문서 함께 전달 

metadatas = [
    {"document": 1},
    {"document": 2},
]  # 문서에 대한 메타데이터 리스트를 정의
documents = text_splitter.create_documents(
    [
        file,
        file,
    ],  # 분할할 텍스트 데이터를 리스트로 전달
    metadatas=metadatas,  # 각 문서에 해당하는 메타데이터를 전달
)

text_splitter.split_documents([file]) # 문서분할
text_splitter.split_text(file)[0] # 텍스트 분할

03. RecursiveCharacterTextSplit

  • 가장 많이 쓰이는 분할기

  • 재귀적인 분할로 효율적인 방식

    • 문단(\n\n) → 문장(\n) → 단어(” “) → 글자수 순서로 재귀적 분할

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(

    chunk_size=250,
    chunk_overlap=50,
    length_function=len,
    is_separator_regex=False,
)
  • Document 객체로 자르고 싶으면 create_documents 사용

# text_splitter를 사용하여 file 텍스트를 문서로 분할 

texts = text_splitter.create_documents([file])
print(texts[0])  # 분할된 문서의 첫 번째 문서를 출력
print(texts[1])  # 분할된 문서의 두 번째 문서를 출력
  • 결국 split_text는 반환값이 문자열, create_documents는 Documents 객체이다.

04. TokenTextSplitter

  • LLM에 입력될때는 token 개수로 입력되기에, 토큰 개수로 자르는 방식

  • Tokenizer: 다양한 알고리즘으로 청킹 결과가 달라짐

  • 언어모델에는 토큰 제한이 있기에, 토큰 제한을 초과하지 않게 만들어준다.

tiktoken

from langchain_text_splitters import CharacterTextSplitter

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=300,
    chunk_overlap=0,
)
# file 텍스트를 청크 단위로 분할, 약간 클 수 있음
texts = text_splitter.split_text(file)
  • RecursiveCharacterTextSplitter또한 from_tiktoken_encoder를 사용 가능

    • 청크 크기보다 작음을 보장

TokenTextSplitter

from langchain_text_splitters import TokenTextSplitter

text_splitter = TokenTextSplitter(
    chunk_size=300,  
    chunk_overlap=0, 
)

texts = text_splitter.split_text(file)
print(texts[0]) 

SpaCy

  • 고급 자연어처리를 위한 오픈소스 라이브러리

import warnings
from langchain_text_splitters import SpacyTextSplitter

warnings.filterwarnings("ignore")

text_splitter = SpacyTextSplitter(
    chunk_size=200, 
    chunk_overlap=50,  
)

SentenceTransformers

  • sentence-transformer 모델에 특화된 텍스트 분할기

from langchain_text_splitters import SentenceTransformersTokenTextSplitter

splitter = SentenceTransformersTokenTextSplitter(chunk_size=200, chunk_overlap=0)

count_start_and_stop_tokens = 2  # 시작과 종료 토큰의 개수를 2로 설정

# 텍스트의 토큰 개수에서 시작과 종료 토큰의 개수를 뺍니다.
text_token_count = splitter.count_tokens(text=file) - count_start_and_stop_tokens
print(text_token_count) 

text_chunks = splitter.split_text(text=file)  # 텍스트를 청크로 분할

NLTK

from langchain_text_splitters import NLTKTextSplitter

text_splitter = NLTKTextSplitter(
    chunk_size=200,  
    chunk_overlap=0,  
)

texts = text_splitter.split_text(file)
print(texts[0])

KoNLPy

  • 한글 처리에 유용, 형태소 분석기 포함

import chunk
from langchain_text_splitters import KonlpyTextSplitter

text_splitter = KonlpyTextSplitter(chunk_size=200, chunk_overlap=50)

texts = text_splitter.split_text(file)  # 한국어 문서를 문장 단위로 분할
print(texts[0])

HuggingFace Tokenizer


from transformers import GPT2TokenizerFast

hf_tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")

text_splitter = CharacterTextSplitter.from_huggingface_tokenizer(
    # 허깅페이스 토크나이저를 사용하여 CharacterTextSplitter 객체를 생성
    hf_tokenizer,
    chunk_size=300,
    chunk_overlap=50,
)

texts = text_splitter.split_text(file)

05. SemanticChunker

  • 텍스트를 의미론적 유사성에 기반해 분할

  • chunk_size, chunk_overlap 옵션을 설정하지 않는다.

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings

# OpenAI 임베딩을 사용하여 의미론적 청크 분할기를 초기화
text_splitter = SemanticChunker(OpenAIEmbeddings())

Breakpoints

  • 분리할 시점을 결정해 작동

  • 기준은 어떻게 설정할까? breakpoint_threshold_type 지정

    • Percentile: 백분위수 기준

    • Standard Deviation: 표준편차 기준

    • Interquartile: 사분위수 기준

06. Code Splitter

  • code기반 RAG를 하고자할 때 사용

  • RecursiveCharacterTextSplitter.get_separators_for_language(Language.PYTHON) 파이썬 code splitter 지정

    • ['\nclass ', '\ndef ', '\n\tdef ', '\n\n', '\n', ' ', '']

PYTHON_CODE = """
def hello_world():
    print("Hello, World!")

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
  • markdown , HTML splitter는 전용 분할기가 따로 있음

07. MarkdownHeaderTextSplitter

  • Header를 기준으로 분할

  • 분할하는 header를 튜플 형식으로 지정 가능

  • 메타 정보를 같이 줘서 트리구조를 알 수 있다.

headers_to_split_on = [  
    (
        "#",
        "Header 1",
    ), 
    (
        "##",
        "Header 2",
    ),  
    (
        "###",
        "Header 3",
    ), 
]

markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)

md_header_splits = markdown_splitter.split_text(markdown_document)

for header in md_header_splits:
    print(f"{header.page_content}")
    print(f"{header.metadata}", end="\\n=====================\\n")

08. HTMLHeaderTextSplitter

  • 구조인식 청크 생성기

  • 마크다운 분할기와 유사

  • 좀 더 재귀적

headers_to_split_on = [
    ("h1", "Header 1"), 
    ("h2", "Header 2"),
    ("h3", "Header 3"),
]

html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)

html_header_splits = html_splitter.split_text(html_string)

for header in html_header_splits:
    print(f"{header.page_content}")
    print(f"{header.metadata}", end="\\n=====================\\n")
  • 다른 splitter와 파이프라인으로 연결할 수 있다. (url을 가져올 때 사용)

  • 때로는 특정 header를 누락할 수 있다.

09. RecursiveJSONSplitter

  • DFS 알고리즘을 사용해 더 작은 JSON 청크를 생성

  • 리스트 객체는 분할하지 않는다.







- 컬렉션 아티클