인공지능/Generative AI

[5탄] Langchain+GPT+텍스트 임베딩으로 상품에 대한 질의응답하기

유일리 2023. 11. 13. 21:00

지난 시간에 진행했던 selenium 웹 크롤링으로 무신사 특정 상품의 리뷰 데이터를 가지고 올 수 있었다.

이번에는 그 리뷰들을 기반으로 사용자가 상품에 대해 질의하면 응답하는 기능을 구현해 볼 것이다.

(온라인 가상 점원 프로젝트를 진행하고 있다는 사실을 까먹지말자..! 서비스에 대해 궁금하신 분은 1탄으로..)


https://www.musinsa.com/app/goods/836499

 

비바스튜디오(VIVASTUDIO) LOCATION CREWNECK [INDIGO BLUE] - 사이즈 & 후기 | 무신사

제품분류 : 상의 > 맨투맨/스웨트셔츠 브랜드 : 비바스튜디오(VIVASTUDIO) 제품번호 : KSVT20 제품 : LOCATION CREWNECK [INDIGO BLUE] - 46,400 원산지 : 대한민국

www.musinsa.com

지난 시간에 이 상품 리뷰를 가져왔기 때문에 오늘도 이 상품에 대한 질의응답을 구현해보겠다.

우선 오늘 구현을 시작하기 전에 처음으로 쓰는 '텍스트 임베딩' 기술에 대해 알아보자.

텍스트 임베딩이란?

 

텍스트 임베딩이란 사람이 말하는 자연어를 컴퓨터가 이해할 수 있도록 수치적 형태로 변환하는 과정을 말한다. 알다시피 컴퓨터는 기본적으로 0과 1의 이진 데이터를 사용하여 모든 작업을 수행하기 때문에, 텍스트를 효율적으로 이해하기 위해서는 숫자 형태로 변환하는 것이 필수적이다. 

 

텍스트를 숫자 데이터로 바꾸는 일반적인 방법은 청크 기반 임베딩이나 단어 임베딩을 사용한다. 

  • 청크 기반 임베딩 : 청크는 텍스트를 의미 있게 더 작은 단위(예: 문단, 문장)로 분리하는 것이다. 이러한 분할을 통해 텍스트의 관리와 처리를 용이하게 한다. 각 청크는 임베딩 과정을 거쳐 고차원의 실수 벡터로 변환된다. 
  • 단어 임베딩 : 토큰화는 텍스트를 단어나 구두점 등의 더 작은 단위(토큰)로 분리하는 것이다. 각 토큰은 단어 임베딩 과정을 거쳐 고차원의 실수 벡터로 변환된다.

즉, 청크 기반 임베딩은 문맥을 포함한 전체적인 텍스트의 의미를 분석하는데 적합하고, 단어 임베딩은 개별 단어의 의미를 포착하고 이를 기반으로 한 분석에 더 적합하다.

 

쇼핑몰의 상품 정보는 기본적으로 긴 텍스트이기 때문에 우리는 텍스트를 청크로 분할한 후 벡터로 변환하는 방법을 사용할 것이다. 

 

우선 시작하기 전에 필요한 라이브러리를 설치해준다.

pip install langchain
pip install openai
pip install tiktoken
pip install numpy
pip install faiss-gpu

 

필요한 라이브러리를 import해준다.

from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from get_reviews import GetReviews
import os

 

텍스트(리뷰 내용)를 청크로 분할한다.

def get_text_chunks(raw_text):
    text_splitter = CharacterTextSplitter(
        separator="\n",
        chunk_size=1000,
        chunk_overlap=0,
        length_function=len
    )
    chunks = text_splitter.split_text(raw_text)
    return chunks

 

이 함수는 주어진 raw_text를 여러개의 청크로 분할한다. 이때 raw_text는 해당 상품의 리뷰(유용한 순 리뷰와 낮은 평점 순 리뷰) 내용이다. 각 청크는 최대 1000자까지의 텍스트를 포함하고 분할된 청크들은 chunks 리스트에 저장되고 반환한다. 

 

분할된 텍스트 청크를 벡터로 변환한다.

def get_vectorstore(text_chunks):
    embeddings = OpenAIEmbeddings()
    vectorstore = FAISS.from_texts(texts=text_chunks, embedding=embeddings)
    return vectorstore

 

여기서 OpenAIEmbeddings는 텍스트를 벡터로 변환하는 임베딩 작업을 하고, FAISS.from_texts는 이 임베딩을 사용하여 텍스트들의 벡터 저장소를 생성한다. 이 저장소는 추후 정보 검색에 사용된다.

 

*두 기술의 차이점은? OpenAI 임베딩은 자연어 텍스트를 고차원의 수치적 벡터로 변환한다. 이 과정에서 텍스트의 의미적, 문맥적 정보가 벡터로 인코딩된다. FAISS는 대규모  벡터 데이터를 저장하고, 벡터 간의 유사성을 빠르게 계산하는데 효과적이다. 즉, OpenAI 임베딩으로 생성된 벡터를 사용하여 질문의 의미를 파악하고, FAISS를 통해 가장 관련성 높은 정보를 빠르게 찾아낼 수 있다.

 

*임베딩은 어떻게 진행될까? 우선 임베딩 레이어를 통해 정수 인코딩된 토큰을 대응하는 고차원의 실수 벡터로 변환한다. 이는 임베딩 행렬이라는 가중치 행렬을 사용한다. 임베딩 행렬은 초기에 무작위로 설정되고, 학습 과정에서 데이터에 맞게 조정된다. 변환된 임베딩 벡터들은 뉴럴 네트워크 모델의 입력으로 사용되어, 모델 학습 과정에서 가중치와 동시에 학습되고 최적화된다.

 

대화 검색 체인을 생성한다.

def get_conversation_chain(vectorstore):
    llm = ChatOpenAI()
    memory = ConversationBufferMemory(memory_key='chat_history', return_messages=True)
    conversation_chain = ConversationalRetrievalChain.from_llm(
        llm=llm,
        retriever=vectorstore.as_retriever(),
        memory=memory
    )
    return conversation_chain

 

ChatOpenAI()를 통해 언어 모델을 사용하고 memory로 대화 버퍼 메모리를 생성해서 대화 이력을 저장하고 대화의 흐름을 추적한다. 대화 검색 체인은 언어 모델과 메모리를 통합하고 벡터 저장소를 검색 엔진으로 사용해서 사용자의 질문과 관련 있는 정보를 검색한다. 

 

사용자 질문에 대한 응답을 반환한다.

def Ask(user_question, url):
    os.environ['OPENAI_API_KEY'] = apikey

    # get review data
    up_reviews, worst_reviews = GetReviews(url, 10)
    raw_text = str(up_reviews) + str(worst_reviews)

    # get the text chunks
    text_chunks = get_text_chunks(raw_text)

    # create vector store
    vectorstore = get_vectorstore(text_chunks)

    # create conversation chain
    conversation_chain = get_conversation_chain(vectorstore)

    response = conversation_chain({"question": user_question})['answer']
    return response

 

이전 시간에 만들었던 GetReviews 함수를 호출해서 상품에 대한 리뷰 데이터를 가져온다. 위의 단계들을 실행한 후 사용자 질문에 대한 응답을 받아 반환한다.

 

자 이제 상품에 대한 질문을 해보자.

user_question="어떻게 스타일링하면 좋을까?"
url = 'https://www.musinsa.com/app/goods/836499'
print(Ask(user_question, url))

 

모델이 사용자의 질문에 대한 리뷰 기반 응답을 잘 반환하는 것을 확인할 수 있다.


리뷰 이외에도 상품 정보를 다양하게 가져와서 질의응답을 구현할 수 있다. 지금은 어느정도 사용자의 질문을 잘 이해하고 응답을 잘 하고 있지만 조금 더 세밀하고 자연스러운 자연어 처리를 위해 유지보수할 예정이다. 또한, 응답 처리 속도 향상을 위한 캐싱 작업도 이뤄질 예정이다!