순서
KoNLPyTextSplitter를 이용한 텍스트 분리
OpenAI Embedding모델을 이용한 텍스트 임베딩 생성
Embedding한 데이터를 Faiss로 local에 저장해서 재사용하기
Faiss에서 질문과 관련된 내용을 similarity_score_threshold로 가져오기
LCEL로 간단하게 질문-답변 chain만들기
Streamlit으로 UI만들기
KoNLPyTextSplitter를 이용한 텍스트 분리
Langchain Docs를 살펴보면 한국어에 대한 내용이 종종 나옵니다. 번역을 영어에서 한국어로 바꾸는 예시가 나오거나 KoNLPY를 이용해 한국어 구문분석을 하는 등의 내용이 포함되어 있습니다.
때마침 Text Splitter에 대해 알아보던 중이어서 KoNLPY를 사용해보기로 했습니다.
KoNLPy란?
바쁜 현대인을 위한 한줄 요약 :
KoNLPy는 파이썬 기반의 한국어 자연어 처리를 위한 오픈 소스 라이브러리로, 다양한 형태소 분석기를 지원합니다.
KonlpyTextSplitter란?
바쁜 현대인을 위한 한줄 요약:
LangChain의 KoNLPyTextSplitter는 한국어의 복잡한 형태소 구조를 이해하고 상세하게 분석할 수 있는 반면, 다른 TextSplitter는 일반적으로 단순 공백 또는 구두점 기반 분리에 초점을 맞춘다는 점에서 차별화됩니다.
KoNLPyTextSplitter를 조금 더 설명하자면 일반적인 TextSplitter는 영어에 특화되어 있어, 공백과 구두점을 기준으로 문장이나 단어를 나누게 되면 그 의미를 이해하기 어려워집니다. 따라서 한국어에 특화된 형태를 이해하고 나눠야합니다.
그래서 KoNLPy를 이용해서 한국어 문장에 더 맞게 분석해서 나눠주는 것이 KoNLPyTextSplitter입니다.
사용 예시
# 1단계. KoNLPY를 이용한 텍스트 분리
from langchain.text_splitter import KonlpyTextSplitter
with open("대한민국 헌법.txt") as f:
korean_document = f.read()
text_splitter = KonlpyTextSplitter(chunk_size=1000, chunk_overlap=100)
texts = text_splitter.split_text(korean_document)
설명
KoNLPyTextSplitter를 langchain.text_splitter에서 import합니다.
한글로 된 텍스트파일을 읽어와 korean_document: str으로 가져옵니다.
KoNLPyTextSplitter객제를 생성합니다.
KoNLPyTextSplitter 클래스는 TextSplitter를 상속받아 만들어져 있기 때문에 생성시에 다음과 같은 인자를 받아 생성할 수 있습니다.
chunk_size: 반환되는 청크(텍스트 블록)의 최대 크기를 지정합니다. 이는 분리할 때 각 청크의 문자 수를 제한합니다.
chunk_overlap: 청크 간의 중복되는 문자 수를 지정합니다. 이는 연속적인 청크들 사이에 얼마나 많은 중복을 허용할지 결정합니다.
length_function: 주어진 청크의 길이를 측정하는 함수입니다. 기본적으로 Python의 내장 함수인 len()을 사용하여 문자열의 길이를 측정합니다.
keep_separator: 청크를 분리할 때 구분자를 청크에 포함시킬지 여부를 결정합니다. 예를 들어, True로 설정하면 분리된 청크에 구분자가 포함됩니다.
add_start_index: True로 설정하면, 각 청크의 메타데이터에 청크의 시작 인덱스를 포함시킵니다. 이는 분리된 텍스트 청크가 원본 텍스트 내에서 시작하는 위치를 알고 싶을 때 유용합니다.
strip_whitespace: True로 설정하면, 각 문서의 시작과 끝에서 공백을 제거합니다. 이는 청크의 앞뒤에 불필요한 공백이 없도록 하여 처리의 정확성을 높입니다.
split_text로 분할된 텍스트를 리스트로 저장합니다.
OpenAI Embedding모델을 이용한 텍스트 임베딩 생성
Embedding이란? → 지난 게시물 확인하기 [embedding과 Vector Stores를 얕게 알아보자]
바쁜 현대인을 위한 한줄 요약:
임베딩은 텍스트나 이미지와 같은 비정형 데이터를 컴퓨터가 이해할 수 있는 숫자 벡터 형태로 변환하는 것입니다.
Embedding한 데이터를 Faiss로 local에 저장해서 재사용하기
Faiss란?
바쁜 현대인을 위한 한줄 요약:
Faiss는 대규모 벡터 집합에 대한 효율적인 유사성 검색과 클러스터링을 위해 Facebook AI Research에 의해 개발된 라이브러리입니다.
Langchain에서 지 원해주는 VectorDB 중에서 가장 가벼우면서 성능은 조금 아쉬운 모델입니다. 하지만 우리가 연습으로 활용하기에는 적당하기 때문에 선택했습니다.
사용 예시
# 2단계 OpenAIEmbeddings를 이용한 텍스트 임베딩 생성 및 Local에 저장
from langchain_community.vectorstores.faiss import FAISS
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
db = FAISS.from_texts(texts, embeddings)
db.save_local("faiss_db")
# 3단계 local에 저장된 FAISS를 불러와서 새로운 문장에 대한 유사도 검색
new_db = FAISS.load_local("faiss_db", embeddings)
설명
OpenAIEmbeddings 객체를 생성합니다. 이 객체는 입력받은 문자열 리스트를 OpenAI의 임베딩 모델에 요청해서 float 리스트로 바꿔주는 embed_documents를 포함하고 있습니다. 우리는 이 과정을 직접하지 않고 객체를 생성해서 FAISS객체에게 사용하도록 전달해줍니다.
FAISS객체의 .from_texts(texts, embeddinds)로 문자열 리스트를 float 리스트로 변환한 후 FAISS 에 저장합니다.
FAISS 객체에는 from_texts이외에도 from_document, from_embeddings도 있습니다.
from_texts :
입력된 텍스트 문서는 내부적으로 주어진 임베딩 모델을 사용하여 임베딩되며, 이 임베딩들은 FAISS 데이터베이스를 초기화하는 데 사용됩니다.
문서를 직접 임베딩하고 싶거나 텍스트 데이터로부터 시작하려는 경우에 적합한 방법입니다.from_document :
이 함수는 Document 객체의 리스트를 입력으로 받으며, 각 Document 객체는 페이지 콘텐츠(page_content)와 메타데이터(metadata)를 포함합니다.
from_texts 함수를 내부적으로 호출하여 Document 객체에서 추출된 텍스트를 임베딩하고, 이를 바탕으로 FAISS 데이터베이스를 초기화합니다.
이 방식은 문서 데이터가 이미 구조화된 형식(Document 객체)으로 준비되어 있고, 각 문서의 메타데이터도 함께 인덱싱하고자 할 때 유용합니다.from_embeddings :
이 함수는 이미 임베딩된 문서들의 튜플(문서의 텍스트와 해당 텍스트의 임베딩을 나타내는 리스트)을 입력으로 받습니다.
사용자가 이미 문서를 임베딩한 경우에 유용하며, 임베딩 과정을 건너뛰고 직접 FAISS 데이터베이스를 초기화할 수 있습니다.
이 방식은 임베딩 프로세스를 외부에서 관리하고 있을 때 빠르게 데이터베이스를 구성하고 싶을 때 사용됩니다.
save_local()함수로 데이터를 로컬에 저장합니다. save_local의 저장할 경로와 폴더명을 인자로 사용합니다. (예시 : “./local/ekwak/faiss_db”)
load_local()함수로 로컬에 저장된 데이터를 가져옵니다. 저장된 경로와 폴더명, DB에서 사용할 임베딩 객체를 인자로 사용합니다.
Faiss에서 질문과 관련된 내용을 similarity_score_threshold로 가져오기
Vector store-backed retriever란?
바쁜 현대인을 위한 한줄 요약:
Vector store-backed retriever는 백터DB 안의 내용을 검색하기 쉽도록 백터스토어 클래스를 래핑한 도구입니다.
Langchain에서 지원하는 Vector store-backed retriever는 3가지가 있습니다.
Maximum marginal relevance retrieval
최대 한계 관련성(Maximum Marginal Relevance, MMR) 검색은 검색 결과로 나오는 문서들이 단지 관련성이 높은 것뿐만 아니라 서로 다른 내용을 담고 있도록 하여, 사용자에게 더 풍부하고 다양한 정보를 제공하는 방법입니다. 이를 통해, 모든 검색 결과가 거의 비슷한 내용을 반복하는 대신, 다양한 관점이나 정보를 얻을 수 있게 됩니다.
MMR은 각 문서에 대해 두 가지를 동시에 평가합니다: 문서가 검색 쿼리와 얼마나 관련이 있는지(관련성), 그리고 이미 선택된 문서들과 비교했을 때 얼마나 새로운 정보를 제공하는지(다양성). 이렇게 함으로써, MMR은 관련성이 높으면서도 서로 다른 내용을 가진 문서들을 검색 결과로 제공합니다.
MMR에서는 λ(람다)라는 값을 조절하여 관련성과 다양성 사이의 균형을 맞춥니다. λ가 높으면 관련성을 더 중시하고, 낮으면 다양성을 더 중시합니다. 이 방식을 사용하면, 사용자는 주제에 대해 깊이 있게 탐색할 수 있는 동시에, 다른 시각이나 정보도 함께 탐색할 수 있게 됩니다.
Similarity score threshold retrieval
유사성 점수 임계값 검색(Similarity Score Threshold Retrieval)은 검색 시스템에서 특정 임계값 이상의 유사성 점수를 가진 문서만을 반환하는 방식입니다. 이 방법은 검색 결과의 질을 높이기 위해 사용됩니다. 각 문서는 검색 쿼리와의 유사성을 점수로 계산받으며, 이 점수가 설정된 임계값보다 높은 문서만 검색 결과로 나타납니다.
Specifying top k
상위 k 지정(Specifying top k)은 검색 시스템에서 사용자가 정의한 숫자 'k'에 해당하는 상위 결과만을 반환하는 방법입니다. 이는 사용자가 받고 싶어하는 검색 결과의 양을 직접 제어할 수 있게 해줍니다. 예를 들어, 사용자가 'k'를 10으로 설정하면, 검색 쿼리와 가장 관련성이 높은 상위 10개의 문서만을 검색 결과로 받게 됩니다.
이 예제에서는 가장 정답에 가까운 값만 필요했기 때문에 Similarity score threshold retrieval를 사용했습니다.
사용 예 시
retriever = new_db.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.8}) # 0.8 이상의 유사도를 가진 문장을 검색
LCEL로 간단하게 질문-답변 chain만들기 & Streamlit으로 UI만들기
LCEL이란?
바쁜 현대인을 위한 한줄 요약 :
LangChain Expression Language(LCEL)은 코드 변경 없이 프로토타입에서 생산까지 다양한 복잡성의 체인을 쉽게 조합하고 실행할 수 있는 선언적 프로그래밍 언어입니다.
LCEL은 Langchain이 원하는 가장 궁극적인 목표라고 생각합니다. 최대한 쉽고 단순한 방식으로 각각의 Chain단계를 묶어서 사용하기 위해 고안되었다고 설명합니다.
좀더 알아보고 싶다면? [LCEL 시리즈] invoke, batch, stream | 지피터스 GPTers
사용 예시
# 인터페이스 만들기
import streamlit as st
# 앱 제목 설정
st.title('대한민국 헌법 검색기')
# 사용자 입력 받기
user_input = st.text_input('내용을 입력하세요:', '')
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(api_key=key, temperature=0)
template = """Answer the question based only on the following context, which can include text and tables:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
result = chain.invoke(user_input)
# 입력 받은 내용 출력
if user_input:
st.write('입력한 내용:', result)
설명
Chain 생성
사용할 프롬프트 템플릿을 작성합니다.
우리는 프롬프트에 질문{question}과 질문을 Faiss에 검색해 출력된 데이터{context}를 모두 LLM에 주고 해당 내용을 바탕으로 답변하게 했습니다.LLM의 출력 결과를 StrOutputParser로 출력 결과만 가져옵니다.
Streamlit으로 user_input받기
st.text_input('내용을 입력하세요:', '')
chain.invoke(user_input)으로 user_input으로 chain동작시키기
result = chain.invoke(user_input)
결과 출력하기
if user_input:
st.write('입력한 내용:', result)
결과 영상
화면 기록 2024-02-12 오후 5.26.40.mp4결론
이번 사례에서는 Langchain에서 텍스트 파일을 임베딩하고 백터스토어에 데이터를 입/출력하는 방법을 대한민국 헌법 검색기를 만들며 알아봤습니다. KonlpyTextSplitter처럼 평소 사용하지 않는 TextSplitter를 사용해보았는데 사실 다른 TextSplitter와 성능상에 큰 차이점을 느끼기엔 부족한 사례였습니다. 하지만 Vector store-backed retriever에 대해 더 자세히 알고 쓸수 있게된 점이 큰 수확이었습니다. 다음 사례에는 백터스토어와 Agent를 연결해 여러 문서에 검색하는 Agent를 만들어보겠습니다. 긴글 읽어주셔서 감사합니다.
#9기랭체인