본문 바로가기
Dev 개발할레나

[챗봇/벡터디비] Weaviate란? 벡터 디비 설정, 저장, 쿼리 테스트하기

by LENA-cfg 2024. 8. 28.

벡터 데이터베이스란?

대량의 고차원 데이터 저장 및 조회를 위해 벡터 임베딩 및 쿼리 벡터 유사도 비교 기반 신속하게 인덱싱하는 데이터 베이스

 

어떻게 쓰일까?

벡터 데이터베이스는 다차원 벡터 데이터를 처리하기때문에 대규모 머신러닝 어플리케이션 특히 추천 시스템이나 의미 검색 및 이상 감지에서 사용된다.

 

1. 추천시스템: 아이템 이미지를 벡터로 표현하고 벡터간 코사인 유사도 같은 유사성 계산을 하고 가장 관련도 높은 추천을 도와줌

2. 의미 검색: 사용자의 질의를 벡터로 표현하고 질의 벡터와 의미가 유사한지 유사성 계산해서 관련도 높은 문서를 찾아서 수준 높은 검색 기능을 제공

3. 이상 감지: 정상적인 소리, 주파수 벡터와 정상적인 패턴에서 벗어난 벡터 데이터를 비교함으로써 정상 패턴에서 벗어난 데이터를 감지

고차원 벡터, 고차원 텐서란?

벡터의 차원, 텐서의 차원 별 파이썬 표현

 

벡터는 리스트의 요소가 늘어나면서 차원이 증가한다.

텐서는 []가 늘어나면서 차원이 증가한다.

n차원 벡터는 1차원 텐서이므로 차원을 말할때 혼동되면 안된다.

 

 

(질문) 그럼 고차원 텐서도 벡터 데이터베이스에 저장이 될까?

기본적으로 벡터 데이터베이스는 n차원의 벡터들을 저장하고 관리하는데 최적화 되어있다. 벡터 표현인 단일 리스트 형태가 데이터 베이스에서 효율적으로 처리되기때문에 다차원 배열 텐서는 1차원 벡터로 변환하거나, 각 차원을 별도의 벡터로 분리하는 방법을 사용해야한다.

 

벡터 데이터베이스는 어떻게 동작할까?

 

해당 이미지는 자연어 처리 기준이며 컨텐트는 자연어로 가정하겠다. content 즉, 자연어를 embedding model에 넣는다.

embedding model은 openai의 text-embedding-ada-002모델을 사용한다고 했을때 위의 이미지처럼 1536차원 즉 1536개 원소를 가지는 리스트 형태인 출력 벡터를 얻는다. 그리고 이 출력 벡터를 벡터 디비에 저장을 한다.

 

그럼 실제 어플리케이션에서 사용할때는 사용자의 질문 즉, Query가 있을 것이다. 이 쿼리도 동일한 임베딩 모델에 넣고 벡터를 추출한 다음 출력 벡터를 이미 저장된 벡터에서 가장 유사한 벡터를 추출해야한다.

 

이때, 사용하는게 HNSW라는 인덱싱 방법을 사용한다. 

HNSW는 Hierarchical Naviagable Small World Graphs이며 가장 가까운 이웃을 근사적으로 검색하는데 사용되는 최신 알고리즘이다. 내가 1536차원의 벡터가 100개 있다고 하면 HNSW 알고리즘은 각 벡터들의 거리계산을 어떤 것으로 할지(l2, cosine similarity등)정하고 100개에 대해 거리를 계산하고 인덱스(벡터를 노드, 벡터들간에 유사성은 엣지)를 만든다. 이렇게 그래프 기반으로 만들었다면 그래프가 계층적 구조를 만들 수 있을 것이다. 그게 레이어의 개념이다. HNSW는 여러 레이어로 구성된 그래프 구조를 사용하고 상위일수록 데이터가 적고, 하위 일수록 데이터가 더 많이 포함된다.

 

왜 그래프 기반으로 탐색하냐면 고차원 벡터 공간에서 유사 벡터를 찾을때 모든 벡터를 하나씩 비교하는 건 비효율적이고 그래프를 사용하면 유사한 벡터들끼리 연결해주어 빠르게 검색할 수 있다. 그리고 근사 근접 검색(ANN)을 통해 완벽한 근접 이웃을 찾으면서 빠르게 유사한 벡터를 근사적으로 찾아내 검색 속도를 크게 향상 시킬 수 있다.

 

HNSW 인덱싱을 사용하여 query와 가까운 벡터를 찾으면 해당 벡터를 리턴해주면 된다. 위의 이미지에서 Query result부분이 될 것 같다.(물론 실제 사용할때는 LLM 모델 통과시켜서 사람이 읽이 편하게 만들어줘야겠지요)


Weaviate란?

 

 

 

위비에이트는 클라우드 기반 오픈소스 벡터 데이터베이스이다. 텍스트, 이미지 등의 데이터를 검색 가능한 벡터 데이터베이스로 전환한다. 핵심 엔진은 10-nn 최근접 이웃 검색을 실행해 속도가 빠르고, 가져오는 시점에 데이터를 벡터화 할수 있고 내 벡터를 업로드 할 수 있어서 유연성이 좋다. 

 

Weaviate 사용방법

위비에이트를 사용해 배포할때는 위비에이트 자체 클라우드, 도커, 쿠버네티스, 임베디드 방식등 4가지 방법이 있다.

나는 위비에이트 클라우드를 사용해서 테스트 해봤다. 

 

1. 위비에이트 클라우드 클러스터 생성

위비에이트 클라우드에 회원가입하고 로그인해서 클러스터를 생성해준다.

 

2. 위비에이트 클라이언트 설치

pip install -U weaviate-client

 

3. 위비에이트 클라이언트 연결

import weaviate
import weaviate.classes as wvc
import json
import requests


# 위비에이트 클라이언트 연결
client = weaviate.connect_to_weaviate_cloud(
    cluster_url = weaviate_url,  # 클라우드에 클러스터 생성 시 REST Endpoint링크 카피
    auth_credentials=wvc.init.Auth.api_key(api_key=weaviate_admin),  # API Keys 카피
    headers={'X-OpenAI-Api-key': API_KEY  # openai 키 카피
    }
)

위의 빨간점이 weaviate_url이다.

wvc.init.Auth.api_key(api_key=weaviate_admin)에 들어갈 weaviate_admin이 APIKey에서 Admin 카피해서 넣어주면 된다.

 

4. 위비에이트 컬렉션 생성

questions = client.collections.create(
    name="seoul_public_programs",
    vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_openai(),
    generative_config=wvc.config.Configure.Generative.openai()
)

 

5. 위비에이트 컬렉션에 데이터 인설트

 

우선, 데이터를 인설트 하기전 나는 Q&A봇으로 정의했고 질문, 답, 카테고리로 데이터를 만들어야했다.

import pandas as pd
import json
data = pd.read_csv("./data/서울시_문화행사_공공서비스예약_정보.csv")

questions_and_answers = []

for ind in range(len(data)):
    item = data.iloc[ind]
    print(item)
    questions_and_answers.append({
        "question": f"{item['서비스명']}은 언제 열리나요?",
        "answer": item["최소시간"],
        "category": item["분류명"]
    })
    questions_and_answers.append({
        "question": f"{item['서비스명']}의 장소는 어디인가요?",
        "answer": item["장소명"],
        "category": item["분류명"]
    })
    questions_and_answers.append({
        "question": f"{item['서비스명']} 예약 링크는 무엇인가요?",
        "answer": item["서비스 URL"],
        "category": item["분류명"]
    })
    questions_and_answers.append({
        "question": f"{item['서비스명']}의 참가 대상은 누구인가요?",
        "answer": item["이용대상"],
        "category": item["분류명"]
    })
    questions_and_answers.append({
        "question": f"{item['서비스명']}의 유형은 무엇인가요?",
        "answer": item["분류명"],
        "category": item["분류명"]
    })

file_path = './data/question_data.json'
with open(file_path, 'w', encoding='utf-8') as json_file:
    json.dump(questions_and_answers, json_file, ensure_ascii=False, indent=4)

위의 형식과 같이 데이터를 만들었다. 위의 데이터를 가지고 answer, question, category 형식으로 리스트로 만들어서 seoul_public_programs라는 컬렉션에 .data.insert_many 메소드를 사용해 데이터를 추가해준다.

with open('./data/question_data.json', 'r', encoding='utf-8') as json_file:
    data = json.load(json_file)

question_objs = list()

for i, d in enumerate(data):
    question_objs.append({
        "answer": d["answer"],
        "question": d["question"],
        "category": d["category"],
    })

questions = client.collections.get("seoul_public_programs")
questions.data.insert_many(question_objs)

 

6. 위비에이트 쿼리 테스트

questions = client.collections.get("seoul_public_programs")
response = questions.query.near_text(
        query=user_input,
        limit=2
    ) 
response.objects[0].properties['answer']

쿼리 테스트1
실제 인설트 데이터1
쿼리 테스트2
실제 인설트 데이터2

질문이 완전히 동일하지 않아도 잘 가져오는 것을 확인 할 수 있었다.

 

7. 쿼리 결과를 LLM에 통과시켜보자 

def db_user_query_mapping(user_input):
    client = weaviate.connect_to_weaviate_cloud(
        cluster_url = weaviate_url, 
        auth_credentials=wvc.init.Auth.api_key(api_key=weaviate_admin), 
        headers={'X-OpenAI-Api-key': API_KEY  
        }
    )

    questions = client.collections.get("seoul_public_programs")
    response = questions.query.near_text(
            query=user_input,
            limit=2
        ) 
    return response.objects[0].properties['answer']


def create_prompt(query, result):
    system_role = f"""You are an artificial intelligence language model named "Public Program Assistant" that specializes in summarizing and answering questions about Seoul's public programs.
    Your task is to take a given document and return a very detailed summary in the query language without generating new information.
    Please ensure that your response is solely based on the provided document and that it is accurate.
    {str(result)}
    Responses should be provided in Korean. Ensure that the answer is accurate and strictly based on the document.
    """
    user_content = f"""User question: "{str(query)}". """

    messages = [
        {"role": "system", "content": system_role},
        {"role": "user", "content": user_content}
    ]

    return messages

def generate_response(messages):
    result = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=messages,
        temperature=0.4,
        max_tokens=500)
    return result.choices[0].message.content


user_input = input()
answer = db_user_query_mapping(user_input)
llm_response = create_prompt(user_input, answer)
chatbot_response = generate_response(llm_response)
print(chatbot_response)

 

사람이 이해할 수 있게 쿼리와 정답을 생성 모델에 통과시켜 답변을 만들어주었다.


개선할 점

1. 데이터 질문과 답변을 다양하게 구성할 것 => 사람들이 어떤 질문을 할지 모르고 새로운 질문에 대한 답변 정확도가 떨어진다.

 

2. 데이터를 정확도가 높아지게 구성할 것 => 현재 데이터는 괄호나 시간등이 질문에 들어가있어서 몇몇 질문에 정확도가 떨어지기때문에 question에 대해 질문이 단순하고 명확해질 필요가 있다.


출처

https://weaviate.io/developers/weaviate/quickstart

 

Quickstart Tutorial | Weaviate

Overview

weaviate.io

 

https://velog.io/@m4a1carbin4/%EB%B2%A1%ED%84%B0-DB-%EA%B8%B0%EB%A1%9D-Weaviate-%ED%83%90%EB%B0%A9%EA%B8%B0

 

벡터 DB 기록 : Weaviate 탐방기 1

벡터가 이리 쓰일 줄이야...말 그대로 벡터를 저장하는 DB이다.(정말 달리 설명할 방도가 없다 그냥 DB가 다루는 데이터가 벡터값을 중심으로 관리한다는것 정도.)그러니 이걸 쓰면 어디에 좋은지

velog.io

 

https://meetcody.ai/ko/blog/2024%EB%85%84%EC%97%90-%EC%8B%9C%EB%8F%84%ED%95%B4-%EB%B3%BC-%EB%A7%8C%ED%95%9C-%EC%83%81%EC%9C%84-5%EA%B0%80%EC%A7%80-%EB%B2%A1%ED%84%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4/

 

2024년에 시도해 볼 만한 상위 5가지 벡터 데이터베이스

벡터 데이터베이스는 머신 러닝과 같은 영역을 포괄하는 광범위하고 복잡한 데이터 세트를 다루는 애플리케이션을 처리합니다. 상위권을 확인하세요!

meetcody.ai

https://github.com/nmslib/hnswlib

 

GitHub - nmslib/hnswlib: Header-only C++/python library for fast approximate nearest neighbors

Header-only C++/python library for fast approximate nearest neighbors - nmslib/hnswlib

github.com