ChatGPT AI에 소설을 학습시킨 다음 질문하기

반응형

이 글에서는 LangChain을 활용하여 구텐베르크 프로젝트에서 제공하는 윌리엄 셰익스피어의 “로미오와 줄리엣” 소설을 ChatGPT에 학습시키고, 이를 활용하여 채팅하는 방법을 설명합니다.

필요한 Python 패키지 설치하기

LangChain에서 ChatGPT 학습을 위해 필요한 파이썬 패키지를 설치합니다.

pip install langchain==0.0.142
pip install openai==0.27.4
pip install tiktoken==0.3.3
pip install chromadb==0.3.21
  • langchain: 자연어 처리 및 기계 번역에 사용됩니다.
  • openai: OpenAI API와 상호작용하는 데 사용됩니다.
  • tiktoken: 텍스트 토큰화에 사용됩니다.
  • chromadb: 벡터 데이터베이스를 이용하는 데 사용됩니다.

필요한 Python 패키지 임포트하기

필요한 Python 패키지를 임포트합니다.

import os
import platform

import openai
import chromadb
import langchain
import tiktoken

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chat_models import ChatOpenAI
from langchain.chains import ChatVectorDBChain
from langchain.document_loaders import GutenbergLoader

OpenAI API 키를 환경변수에 등록하기

터미널을 열고 환경 변수에 OPENAI_API_KEY를 등록합니다.

export OPENAI_API_KEY="YOUR_API_KEY"

주피터 노트북에서 작업을 수행한다면, 다음과 같이 OPENAI_API_KEY를 등록합니다.

os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"

구텐베르크에서 소설 가져오기

구텐베르크에서 “로미오와 줄리엣” 소설을 가져오기 위해서는 GutenbergLoader라는 구텐베르크 문서 로더를 활용할 수 있습니다. 아래의 코드를 통해 해당 소설을 로드합니다.

loader = GutenbergLoader('https://www.gutenberg.org/cache/epub/1513/pg1513.txt')
data = loader.load()

해당 소설은 178,596개의 단어를 가지고 있습니다.

그리고 OpenAI의 tiktoken 패키지를 사용하면 토큰 수를 추정해 볼 수 있습니다.
먼저 num_tokens_from_string 함수를 작성합니다.

def num_tokens_from_string(string: str, encoding_name: str) -> int:  
"""Returns the number of tokens in a text string."""
encoding = tiktoken.get_encoding(encoding_name)
num_tokens = len(encoding.encode(string))
return num_tokens

num_tokens_from_string 함수를 활용하여 계산한 결과, 약 53,945개의 토큰이 예상되었습니다.

텍스트를 청크(chunk) 단위로 분할하기

사용하려는 text-embedding-ada-002 임베딩 모델의 최대 토큰 한도는 2,048개 입니다. 따라서, 입력 가능한 텍스트는 토큰 수가 이 한도를 초과하지 않아야 합니다. 이를 위해서 우리는 ‘text_splitter’ 유틸리티를 사용하여 텍스트를 청크(chunk) 단위로 분할해야 합니다. 여기서는 RecursiveCharacterTextSplitter를 사용합니다. 이 텍스트 분할기는 의미적으로 가장 연관성이 높은 텍스트 조각으로 보이는 모든 단락을 가능한 한 길게 유지하려고 하는 효과가 있습니다. 따라서 일반적인 텍스트 분할에 RecursiveCharacterTextSplitter 사용을 권장합니다.

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
doc = text_splitter.split_documents(data)

위 코드를 실행하면, 1000 토큰 청크 단위로 분할된 183개의 문서가 생성됩니다.

텍스트를 임베딩으로 변환하여 DB에 저장하기

임베딩 스토어로 Chroma를 사용합니다. Chroma는 기본적으로 인메모리 데이터베이스를 사용합니다. 그러나 Chroma는 데이터베이스 파일을 디스크에 저장하는 옵션도 제공합니다. 이 옵션을 사용하면 임베딩 비용을 지불하지 않고도 데이터베이스를 재사용할 수 있습니다.

먼저 Chroma가 데이터베이스 파일을 저장할 디렉토리를 지정해야 합니다.

persist_directory="/content/drive/My Drive/Colab Notebooks/chroma/romeo"

그리고 텍스트를 벡터 임베딩으로 변환하여 벡터 데이터베이스에 저장합니다.

embeddings = OpenAIEmbeddings()  
vectordb = Chroma.from_documents(docs, embeddings, persist_directory=persist_directory)
vectordb.persist()

OpenAIEmbeddings는 기본적으로 openai의 text-embedding-ada-002 모델을 사용합니다. 임베딩 작업이 완료되면 183개의 문서에 대한 임베딩이 Chroma 데이터베이스에 저장됩니다.

아래 이미지는 실제 OpenAI API에 요청된 토큰 수입니다. 53,020 토큰이 사용되었습니다.

Chroma와 LangChain을 사용하여 체인 구성하기

LangChain를 구성하기 위해 OpenAI GPT-3 모델(gpt-3.5-turbo)을 사용합니다. return_source_documents=True 옵션을 사용하여 응답에 Chroma의 검색 결과가 포함되어 있는지 확인합니다.

model = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
chain = ChatVectorDBChain.from_llm(model, vectordb, return_source_documents=True)

ChatOpenAI 클래스를 사용하여 OpenAI GPT-3 모델을 로드합니다. 그리고langchain 패키지의 ChatVectorDBChain를 사용하여 Chroma와 LangChain을 연결합니다.

“로미오와 줄리엣” 소설 관련하여 질문하기

질문 #1: 로미오와 줄리엣이 함께 하룻밤을 보낸 적이 있나요?

query = "로미오와 줄리엣은 함께 밤을 보냈나요?"
result = chain({"question": query, "chat_history": []})

응답이 너무 짧아서 만족스럽지 못했습니다.

벡터 검색 결과를 한 번 살펴보겠습니다. 다음 내용을 살펴보면 로미오와 줄리엣이 밤에 만나는 장면을 검색한 것을 확인할 수 있습니다.

이전 질문을 영어로 바꾸어 질문했더니 더 긴 답변을 얻을 수 있었습니다.

로미오와 줄리엣이 정원이 내려다보이는 열린 갤러리의 Scene V에서 볼 수 있듯이 함께 하룻밤을 보냈음을 암시합니다. 줄리엣은 로미오에게 떠날 것인지 묻고, 로미오는 떠나야 살 수 있고, 아니면 남아서 죽어야 한다고 대답합니다. 줄리엣은 바깥의 빛은 햇빛이 아니라 태양이 만토바로 가는 로미오의 횃불이 되기 위해 내뿜는 유성이라며 로미오를 설득하려 합니다. 로미오와 줄리엣의 대화를 보면 두 사람이 하룻밤을 함께 보냈으며, 아침에 로미오가 떠날 것에 대해 이야기하고 있음을 알 수 있습니다. 또한 Scene VI에서 로렌스 수사는 로미오에게 "이 폭력적인 즐거움에는 폭력적인 결말이 있다"며 "적당히 사랑해야 한다"고 말하며 사랑의 위험성에 대해 경고합니다. 이 경고는 로미오와 줄리엣이 열정적이고 격렬한 관계를 맺어 잠재적으로 부정적인 결과를 초래할 수 있음을 암시합니다. 전반적으로 로미오와 줄리엣이 하룻밤을 함께 보냈다고 명시적으로 언급하지는 않지만, 대화의 맥락과 함축적인 의미로 볼 때 둘이 하룻밤을 함께 보냈음을 암시합니다.

질문 #2: 로잘린은 누구인가요?

로잘린은 윌리엄 셰익스피어의 희곡 "로미오와 줄리엣"에 등장하는 인물입니다. 로잘린은 로미오가 줄리엣을 만나기 전, 극 초반에 로미오가 열광하는 여인입니다. 1막 1장 1절에서 로미오는 "로잘린을 위해 그대의 얕은 뺨을 씻었다!"고 묘사되고, 2막 3장에서는 로렌스 수사가 로잘린을 "사랑하지 않고 점만 찍었다"고 로미오를 꾸짖는 장면이 나옵니다. 로미오 자신도 로잘린을 사랑한다는 사실을 인정하지만, "사랑에 양념을 치는 것은 맛이 없다"며 로잘린이 자신의 감정에 보답하지 않는다는 사실도 인정합니다. 로잘린은 로미오를 거부하는 차갑고 다가갈 수 없는 여성으로 묘사됩니다. 1막 4장에서 메르쿠티오는 로잘린을 로미오를 미치게 하는 "창백하고 강인한 여인"으로 묘사합니다. 로미오 자신도 "내 사랑보다 더 아름다운 이여! 만물을 보는 태양이여 세상이 시작된 이래로 그녀의 짝을 본 적이 없네"라고 말하며 로잘린에 대한 짝사랑을 한탄합니다. 전반적으로 로잘린은 줄리엣에 대한 로미오의 사랑의 강렬함과 진정성을 강조하는 역할을 합니다. 로잘린에 대한 로미오의 사랑은 피상적이고 짝사랑인 반면, 줄리엣에 대한 로미오의 사랑은 열정적이고 모든 것을 바치는 사랑입니다.

마치며

LangChain과 ChatGPT를 활용하여 소설을 학습하고, 이를 토대로 나만의 대화형 챗봇을 손쉽게 구축할 수 있었다는 사실에 정말 놀랐습니다. 이러한 애플리케이션은 책을 읽은 후 기억이 나지 않거나 책 내용과 관련하여 토론하고 싶은 경우 매우 유용할 것으로 생각됩니다. 이러한 방식의 서비스는 앞으로 점차 보급되어, 책과 문학을 좋아하는 사람들에게는 더욱 더 쉽게 접근 가능한 방식이 될 것입니다.

참고

반응형