728x90
반응형

안녕하세요. 오늘은 32일차 학습 기록입니다.

어제까지는 CSVLoader, WebBaseLoader와 같은 데이터 로더를 통해 문서를 불러오는 방법을 알아봤습니다.

오늘은 그다음 단계인 텍스트 분할(Text Splitting)에 대해 살펴보겠습니다.

문서 분할은 Retrieval-Augmented Generation(RAG) 시스템에서 데이터를 효율적으로 다루기 위한 핵심 과정으로, 로드된 문서를 LLM이 소화할 수 있는 작은 단위의 청크(chunk)로 나누는 역할을 합니다.


1. 문서 분할이 필요한 이유

문서를 그냥 LLM에 통째로 넣는 것이 아니라 작게 나누어야 하는 이유는 다음과 같습니다.

  1. 정확성 (핀포인트 검색)
    • 문서를 세분화하면 질문(Query)과 관련된 작은 단위만 검색할 수 있습니다.
    • 이렇게 하면 LLM이 불필요한 맥락 없이 핵심 정보만 참고할 수 있습니다.
  2. 효율성 (리소스 최적화)
    • 문서 전체를 입력하면 토큰 비용이 커지고, 모델이 중요한 부분을 놓칠 수 있습니다.
    • 관련 없는 내용을 억지로 답변하다 보면 할루시네이션(hallucination) 가능성도 증가합니다.
    • 따라서 필요한 정보만 가져오기 위해 문서 분할이 필수적입니다.

2. 문서 분할 과정

  1. 문서 구조 파악: PDF 파일, 웹 페이지, 전자 책 등 다양한 형식의 문서에서 구조를 파악합니다. 이는 문서의 헤더, 푸터, 페이지 번호, 섹션 제목 등을 식별하는 과정을 포함할 수 있습니다.
  2. 단위 선정: 문서를 어떤 단위로 나눌지 결정합니다. 이는 페이지별, 섹션별, 또는 문단별일 수 있으며, 문서의 내용과 목적에 따라 다릅니다.
  3. 단어 크기 선정(chunk size): 문서를 몇 개의 토큰 단위로 나눌 것인지를 정합니다.
  4. 청크 오버랩(chunk overlap): 분할된 끝 부분에서 맥락이 이어질 수 있도록 일부를 겹쳐서(overlap) 분할하는 것이 일반적입니다.

3. CharacterTextSplitter

LangChain에서 가장 기본적인 텍스트 분할 도구는 CharacterTextSplitter입니다.

 

이 클래스는 문자를 기준으로 텍스트를 일정 크기로 나누는 방식을 제공합니다.

 

기본적으로 “\\n\\n”을 기준으로 문자 단위로 텍스트를 분할하고, 청크의 크기를 문자 수로 측정합니다.

  1. 텍스트 분할 방식: 단일 문자 기준
  2. 청크 크기 측정 방식: 문자 수 기준

 

예시를 통해 알아봅시다.

# data/appendix-keywords.txt 파일을 열어서 f라는 파일 객체를 생성합니다.
with open("./data/appendix-keywords.txt") as f:
    file = f.read()  # 파일의 내용을 읽어서 file 변수에 저장합니다.

 

파일로부터 읽은 파일의 일부 내용을 출력합니다.

# 파일으로부터 읽은 내용을 일부 출력합니다.
print(file[:500])

 

출력 결과:

Semantic Search

정의: 의미론적 검색은 사용자의 질의를 단순한 키워드 매칭을 넘어서 그 의미를 파악하여 관련된 결과를 반환하는 검색 방식입니다.
예시: 사용자가 "태양계 행성"이라고 검색하면, "목성", "화성" 등과 같이 관련된 행성에 대한 정보를 반환합니다.
연관키워드: 자연어 처리, 검색 알고리즘, 데이터 마이닝

Embedding

정의: 임베딩은 단어나 문장 같은 텍스트 데이터를 저차원의 연속적인 벡터로 변환하는 과정입니다. 이를 통해 컴퓨터가 텍스트를 이해하고 처리할 수 있게 합니다.
예시: "사과"라는 단어를 [0.65, -0.23, 0.17]과 같은 벡터로 표현합니다.
연관키워드: 자연어 처리, 벡터화, 딥러닝

Token

정의: 토큰은 텍스트를 더 작은 단위로 분할하는 것을 의미합니다. 이는 일반적으로 단어, 문장, 또는 구절일 수 있습니다.
예시: 문장 "나는 학교에 간다"를 "나는", "학교에", "간다"로 분할합니다.
연관키워드: 토큰화, 자연어

 

ChatacterTextSplitter를 사용하여 텍스트를 청크(chunk)로 분할하는 코드에 설명합니다.

  • separator: 분할한 기준을 설정합니다. 기본값은 “\\n\\n”입니다.
  • chunk_size: 각 청크의 최대 크기를 설정합니다. 예: 250자
  • chunk_overlap: 인접한 청크 간 중복을 허용합니다. 예: 50자
  • length_function: 텍스트의 길이를 계산하는 함수를 지정합니다. 예: len
from langchain_text_splitters import CharacterTextSplitter

# CharacterTextSplitter를 사용하여 텍스트를 청크(chunk)로 분할하는 코드
text_splitter = CharacterTextSplitter(
    # 텍스트를 분할할 때 사용할 구분자를 지정합니다. 기본값은 "\\n\\n"입니다.
    separator="\\n\\n",
    # 분할된 텍스트 청크의 최대 크기를 지정합니다 (문자 수).
    chunk_size=210,
    # 분할된 텍스트 청크 간의 중복되는 문자 수를 지정합니다.
    chunk_overlap=0,
    # 텍스트의 길이를 계산하는 함수를 지정합니다.
    length_function=len,
)
# 텍스트를 청크로 분할합니다.
texts = text_splitter.create_documents([file])
print(type(texts))
print(len(texts[0].page_content))  # 분할된 문서의 개수를 출력합니다.
print(texts[0])  # 분할된 문서 중 첫 번째 문서를 출력합니다.

 

출력 결과:

<class 'list'>
197
page_content='Semantic Search

정의: 의미론적 검색은 사용자의 질의를 단순한 키워드 매칭을 넘어서 그 의미를 파악하여 관련된 결과를 반환하는 검색 방식입니다.
예시: 사용자가 "태양계 행성"이라고 검색하면, "목성", "화성" 등과 같이 관련된 행성에 대한 정보를 반환합니다.
연관키워드: 자연어 처리, 검색 알고리즘, 데이터 마이닝

Embedding'

👉 긴 문서가 210자 단위로 나뉘어 Document 리스트로 반환된 것을 확인할 수 있습니다.

 

split_text() 메서드 활용

create_documents() 외에도 split_text()를 통해 순수 문자열 단위로 분할할 수도 있습니다.

# text_splitter를 사용하여 file 텍스트를 분할하고, 분할된 텍스트의 첫 번째 요소를 반환합니다.
text_splitter.split_text(file)[0]

 

출력 결과:

'Semantic Search\\n\\n정의: 의미론적 검색은 사용자의 질의를 단순한 키워드 매칭을 넘어서 그 의미를 파악하여 관련된 결과를 반환하는 검색 방식입니다.\\n예시: 사용자가 "태양계 행성"이라고 검색하면, "목성", "화성" 등과 같이 관련된 행성에 대한 정보를 반환합니다.\\n연관키워드: 자연어 처리, 검색 알고리즘, 데이터 마이닝\\n\\nEmbedding'

👉 split_text()는 텍스트를 문자열 리스트로 반환하므로, 전처리 후 Embedding에 직접 사용할 수 있습니다.



마무리

오늘은 텍스트 분할(Text Splitting)의 필요성과

LangChain의 기본 도구 CharacterTextSplitter를 살펴보았습니다.

  • 문서를 작은 청크로 나누어야 정확하고 효율적인 검색 가능
  • chunk_size와 chunk_overlap 설정이 핵심 → 맥락 보존 vs 효율성 사이의 균형
  • create_documents()와 split_text() 두 가지 방식 제공

📌 문서 로딩에 이어 분할까지 익혔으니, 이제 임베딩 → 벡터스토어 → 검색으로 이어지는 RAG 파이프라인의 뼈대를 점점 완성해가고 있습니다.

👉 내일(33일차)에는 RecursiveCharacterTextSplitter를 활용해 더 정교한 문서 분할 방법을 알아보겠습니다.

 

읽어주셔서 감사합니다.

728x90
반응형

+ Recent posts