Hacker News 벡터 검색 데이터셋

Hacker News 벡터 검색 데이터셋 (Hacker News vector search dataset)

Hacker News의 게시물 2874만 개와 그 벡터 임베딩을 담은 데이터셋입니다. 대규모 벡터 검색 애플리케이션의 설계·용량·성능을 살펴보고, 유사도 검색(semantic search)과 생성형 AI 요약 앱까지 만들어 볼 수 있어요.

출처: 문서

본문

소개 (Introduction)

Hacker News 데이터셋은 2874만 개의 게시물과 그 벡터 임베딩을 포함합니다. 임베딩은 SentenceTransformers 모델 all-MiniLM-L6-v2로 생성되었으며, 각 임베딩 벡터의 차원은 384입니다.

이 데이터셋은 사용자가 만든 텍스트 데이터 위에 구축된 대규모 실제 벡터 검색 애플리케이션의 설계, 용량, 성능 측면을 살펴보는 데 사용할 수 있습니다.

데이터셋 상세 (Dataset details)

벡터 임베딩을 포함한 전체 데이터셋은 ClickHouse가 S3 버킷에 단일 Parquet 파일로 제공합니다.

먼저 문서를 참고해 용량 산정(sizing) 작업을 수행해 이 데이터셋의 저장소 및 메모리 요구사항을 추정할 것을 권장합니다.

단계 (Steps)

  1. 테이블 생성

hackernews 테이블을 만들어 게시물, 임베딩 및 관련 속성을 저장하세요:

CREATE TABLE hackernews
(
    `id` Int32,
    `doc_id` Int32,
    `text` String,
    `vector` Array(Float32),
    `node_info` Tuple(
        start Nullable(UInt64),
        end Nullable(UInt64)),
    `metadata` String,
    `type` Enum8('story' = 1, 'comment' = 2, 'poll' = 3, 'pollopt' = 4, 'job' = 5),
    `by` LowCardinality(String),
    `time` DateTime,
    `title` String,
    `post_score` Int32,
    `dead` UInt8,
    `deleted` UInt8,
    `length` UInt32
)
ENGINE = MergeTree
ORDER BY id;

id는 단순히 증가하는 정수입니다. 추가 속성들은 문서에서 설명하는 사후 필터링/사전 필터링과 결합한 벡터 유사도 검색을 이해하는 데 프레디킷으로 사용할 수 있습니다.

  1. 데이터 로드

Parquet 파일에서 데이터셋을 로드하려면 다음 SQL 문을 실행하세요:

INSERT INTO hackernews SELECT * FROM s3('https://clickhouse-datasets.s3.amazonaws.com/hackernews-miniLM/hackernews_part_1_of_1.parquet', NOSIGN);

테이블에 2874만 행을 삽입하는 데는 몇 분이 걸릴 수 있습니다.

  1. 벡터 유사도 인덱스 구축

다음 SQL을 실행해 hackernews 테이블의 vector 컬럼에 벡터 유사도 인덱스를 정의하고 구축하세요:

ALTER TABLE hackernews ADD INDEX vector_index vector TYPE vector_similarity('hnsw', 'cosineDistance', 384, 'bf16', 64, 512);

ALTER TABLE hackernews MATERIALIZE INDEX vector_index SETTINGS mutations_sync = 2;

인덱스 생성과 검색의 매개변수 및 성능 고려사항은 문서에 설명되어 있습니다. 위 문은 HNSW 하이퍼파라미터 Mef_construction에 각각 64와 512 값을 사용합니다. 인덱스 빌드 시간과 선택한 값에 따른 검색 결과 품질을 평가해 이 매개변수들의 최적 값을 신중하게 선택해야 합니다. 인덱스 구축과 저장은 사용 가능한 CPU 코어 수와 저장 대역폭에 따라 전체 2874만 데이터셋에서 몇 분~한 시간이 걸릴 수 있습니다.

  1. ANN 검색 수행

벡터 유사도 인덱스가 구축되면 벡터 검색 쿼리가 자동으로 인덱스를 사용합니다: Query

SELECT id, title, text
FROM hackernews
ORDER BY cosineDistance( vector, <search vector>)
LIMIT 10

벡터 인덱스를 메모리에 처음 로드하는 데 몇 초~몇 분이 걸릴 수 있습니다.

  1. 검색 쿼리용 임베딩 생성

Sentence Transformers는 문장과 단락의 의미를 포착하는 로컬에서 사용하기 쉬운 임베딩 모델을 제공합니다. 이 HackerNews 데이터셋의 데이터셋은 all-MiniLM-L6-v2 모델로 생성된 벡터 임베딩을 포함합니다. 아래 예제 Python 스크립트는 sentence_transformers Python 패키지를 사용해 프로그래밍 방식으로 임베딩 벡터를 생성하는 방법을 보여줍니다. 생성된 검색 임베딩 벡터는 SELECT 쿼리의 cosineDistance() 함수에 인자로 전달됩니다.

from sentence_transformers import SentenceTransformer
import sys

import clickhouse_connect

print("Initializing...")

model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

chclient = clickhouse_connect.get_client() # ClickHouse credentials here

while True:
    # Take the search query from user
    print("Enter a search query :")
    input_query = sys.stdin.readline();
    texts = [input_query]

    # Run the model and obtain search vector
    print("Generating the embedding for ", input_query);
    embeddings = model.encode(texts)

    print("Querying ClickHouse...")
    params = {'v1':list(embeddings[0]), 'v2':20}
    result = chclient.query("SELECT id, title, text FROM hackernews ORDER BY cosineDistance(vector, %(v1)s) LIMIT %(v2)s", parameters=params)
    print("Results :")
    for row in result.result_rows:
        print(row[0], row[2][:100])
        print("---------")

위 Python 스크립트 실행과 유사도 검색 결과의 예는 아래와 같습니다(상위 20개 게시물 각각에서 100자만 출력):

Initializing...

Enter a search query :
Are OLAP cubes useful

Generating the embedding for  "Are OLAP cubes useful"

Querying ClickHouse...

Results :

27742647 smartmic:
slt2021: OLAP Cube is not dead, as long as you use some form of:<p>1. GROUP BY multiple variables...
27744260 georgewfraser:A data mart is a logical organization of data to help humans understand the schema...
27761434 mwexler:&quot;We model data according to rigorous frameworks like Kimball or Inmon...
28401230 chotmat:...OLAP database is just a copy, replica, or archive of data...
22198879 Merick:+1 for Apache Kylin, it&#x27;s a great project...
...

(위 검색 결과 예시는 문서 원문의 전체 20개 결과를 담고 있습니다.)

요약 데모 애플리케이션 (Summarization demo application)

위 예제는 ClickHouse를 사용한 유사도 검색(semantic search)과 문서 검색을 보여주었습니다. 이어서 매우 단순하지만 잠재력이 큰 생성형 AI 예제 애플리케이션을 소개합니다. 이 애플리케이션은 다음 단계를 수행합니다:

  1. 사용자로부터 *주제(topic)*를 입력받습니다
  2. SentenceTransformers와 모델 all-MiniLM-L6-v2를 사용해 주제에 대한 임베딩 벡터를 생성합니다
  3. hackernews 테이블에서 벡터 유사도 검색으로 관련성 높은 게시물/댓글을 검색합니다
  4. LangChain과 OpenAI gpt-3.5-turbo Chat API를 사용해 #3 단계에서 검색한 콘텐츠를 요약합니다. #3 단계에서 검색한 게시물/댓글은 Chat API에 컨텍스트로 전달되며 생성형 AI의 핵심 연결 고리입니다.

요약 애플리케이션 실행 예를 먼저 나열한 뒤, 요약 애플리케이션 코드를 보여줍니다. 애플리케이션을 실행하려면 환경 변수 OPENAI_API_KEY에 OpenAI API 키가 설정되어 있어야 합니다. OpenAI API 키는 https://platform.openai.com에 등록한 후 얻을 수 있습니다. 이 애플리케이션은 고객 감정 분석, 기술 지원 자동화, 사용자 대화 마이닝, 법률 문서, 의료 기록, 회의 기록, 재무제표 등 여러 엔터프라이즈 도메인에 적용 가능한 생성형 AI 사용 사례를 보여줍니다.

$ python3 summarize.py

Enter a search topic :
ClickHouse performance experiences

Generating the embedding for ---->  ClickHouse performance experiences

Querying ClickHouse to retrieve relevant articles...

Initializing chatgpt-3.5-turbo model...

Summarizing search results retrieved from ClickHouse...

Summary from chatgpt-3.5:
The discussion focuses on comparing ClickHouse with various databases like TimescaleDB, Apache Spark,
AWS Redshift, and QuestDB, highlighting ClickHouse's cost-efficient high performance and suitability
for analytical applications. ...

위 애플리케이션의 코드:

print("Initializing...")

import sys
import json
import time
from sentence_transformers import SentenceTransformer

import clickhouse_connect

from langchain.docstore.document import Document
from langchain.text_splitter import CharacterTextSplitter
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains.summarize import load_summarize_chain
import textwrap
import tiktoken

def num_tokens_from_string(string: str, encoding_name: str) -> int:
    encoding = tiktoken.encoding_for_model(encoding_name)
    num_tokens = len(encoding.encode(string))
    return num_tokens

model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

chclient = clickhouse_connect.get_client(compress=False) # ClickHouse credentials here

while True:
    # Take the search query from user
    print("Enter a search topic :")
    input_query = sys.stdin.readline();
    texts = [input_query]

    # Run the model and obtain search or reference vector
    print("Generating the embedding for ----> ", input_query);
    embeddings = model.encode(texts)

    print("Querying ClickHouse...")
    params = {'v1':list(embeddings[0]), 'v2':100}
    result = chclient.query("SELECT id,title,text FROM hackernews ORDER BY cosineDistance(vector, %(v1)s) LIMIT %(v2)s", parameters=params)

    # Just join all the search results
    doc_results = ""
    for row in result.result_rows:
        doc_results = doc_results + "\n" + row[2]

    print("Initializing chatgpt-3.5-turbo model")
    model_name = "gpt-3.5-turbo"

    text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
        model_name=model_name
    )

    texts = text_splitter.split_text(doc_results)

    docs = [Document(page_content=t) for t in texts]

    llm = ChatOpenAI(temperature=0, model_name=model_name)

    prompt_template = """
Write a concise summary of the following in not more than 10 sentences:

{text}

CONSCISE SUMMARY :
"""

    prompt = PromptTemplate(template=prompt_template, input_variables=["text"])

    num_tokens = num_tokens_from_string(doc_results, model_name)

    gpt_35_turbo_max_tokens = 4096
    verbose = False

    print("Summarizing search results retrieved from ClickHouse...")

    if num_tokens <= gpt_35_turbo_max_tokens:
        chain = load_summarize_chain(llm, chain_type="stuff", prompt=prompt, verbose=verbose)
    else:
        chain = load_summarize_chain(llm, chain_type="map_reduce", map_prompt=prompt, combine_prompt=prompt, verbose=verbose)

    summary = chain.run(docs)

    print(f"Summary from chatgpt-3.5: {summary}")

더 알아보기 (Learn more)