python #3 pgvector 유사 리뷰 검색 #
#2025-08-20
1. 목적 #
고객 리뷰 문장을 벡터로 임베딩하고 PostgreSQL의 pgvector 기능을 활용하여 비슷한 리뷰를 검색하는 기능을 구현
#
2. 코드 #
import torch
import transformers
import sentence_transformers
import sklearn
import numpy
import scipy
print(f"torch: {torch.__version__}")
print(f"transformers: {transformers.__version__}")
print(f"sentence-transformers: {sentence_transformers.__version__}")
print(f"scikit-learn: {sklearn.__version__}")
print(f"numpy: {numpy.__version__}")
print(f"scipy: {scipy.__version__}")
from dotenv import load_dotenv
import os
load_dotenv() # 같은 폴더에 있는 .env 로드
torch: 2.2.2
transformers: 4.25.1
sentence-transformers: 2.2.2
scikit-learn: 1.3.2
numpy: 1.24.4
scipy: 1.10.1
skala conda 환경을 만들었었는데 pgvector 돌리기용으로 지피티가 추천해준 패키지 조합이 있어서 그냥 force로 저렇게 깔아줬다.
from sentence_transformers import SentenceTransformer
import numpy as np
# 1단계: 문장 임베딩
model = SentenceTransformer('paraphrase-MiniLM-L6-v2')
reviews = [
"배송이 빠르고 제품도 좋아요.",
"품질이 기대 이상입니다!",
"생각보다 배송이 오래 걸렸어요.",
"배송은 느렸지만 포장은 안전했어요.",
"아주 만족스러운 제품입니다."
]
embeddings = model.encode(reviews)
import psycopg2
from pgvector.psycopg2 import register_vector
# 2단계: PostgreSQL 테이블 생성
conn = psycopg2.connect(
host="localhost",
port=5432,
database="postgres",
user="postgres",
password=os.getenv("PG_PASSWORD"), # 환경변수에서 불러옴
)
register_vector(conn) # 벡터 변환 활성화
cur = conn.cursor()
# DB 초기화 (기존 테이블 삭제 후 재생성)
dim = model.get_sentence_embedding_dimension()
cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
cur.execute("DROP TABLE IF EXISTS review_vectors;") # 테이블 완전 삭제
cur.execute(f"""
CREATE TABLE review_vectors (
id SERIAL PRIMARY KEY,
review TEXT,
embedding vector({dim})
);
""")
conn.commit()
# 3단계: 벡터 저장
for review, emb in zip(reviews, embeddings):
cur.execute(
"INSERT INTO review_vectors (review, embedding) VALUES (%s, %s)",
(review, np.array(emb, dtype=np.float32))
)
conn.commit()
# 4단계: 유사도 검색
query = "배송이 느렸어요"
query_vec = model.encode([query])[0].astype(np.float32)
print("\n유사도 검색 결과:")
cur.execute(
"""
SELECT review, embedding <=> %s AS cosine_distance
FROM review_vectors
ORDER BY embedding <=> %s
LIMIT 3;
""",
(query_vec, query_vec)
)
for review, dist in cur.fetchall():
print(f"코사인거리: {dist:.4f} | 리뷰: {review}")
유사도 검색 결과:
코사인거리: 0.0783 | 리뷰: 배송이 빠르고 제품도 좋아요.
코사인거리: 0.0990 | 리뷰: 배송은 느렸지만 포장은 안전했어요.
코사인거리: 0.1253 | 리뷰: 생각보다 배송이 오래 걸렸어요.
# 5. 마무리
cur.close()
conn.close()
#
3. 생각 #
PostgreSQL 테이블 생성 단계에서 나는 python으로 그냥 쏴줬는데 pgadmin 왔다갔다하면서 연동 느낌을 주는게 목적인가? 싶어서 남들 코드로 확인만 해보기.
- pgadmin을 들어가서 postgresql에 테이블 생성
-- 리뷰 테이블 생성
CREATE TABLE review_vectors (
id SERIAL PRIMARY KEY,
review TEXT,
embedding VECTOR(384) -- 384차원 임베딩 벡터
);
# 벡터 DB에 저장
conn = psycopg2.connect(
dbname="*",
user="*",
password="*",
host="localhost",
port="5432"
)
cur = conn.cursor()
# 각 리뷰와 임베딩을 DB에 저장
for review, embedding in zip(reviews, embeddings):
emb_list = embedding.tolist()
cur.execute(
"INSERT INTO review_vectors (review, embedding) VALUES (%s, %s)",
(review, emb_list)
)
요게 정석인듯. python으로 review를 embedding이라는 벡터로 만들고 -> SQL 쿼리문 작성하고 -> python으로 연결해서 python으로 리뷰 임베딩을 작성하고 -> reviews, embeddings를 db에 저장.
#
내코드는?
conn = psycopg2.connect( # DB 연결 객체 conn 생성
host="localhost",
port=5432,
database="postgres",
user="postgres",
password=os.getenv("PG_PASSWORD"), # 환경변수에서 불러옴
)
DB연결을 먼저하고
register_vector(conn)
cur = conn.cursor() # cursor 객체 cur 생성 (SQL을 실행하고 결과를 가져오는 역할)
dim = model.get_sentence_embedding_dimension()
cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
cur.execute("DROP TABLE IF EXISTS review_vectors;") # 테이블 완전 삭제
cur.execute(f"""
CREATE TABLE review_vectors (
id SERIAL PRIMARY KEY,
review TEXT,
embedding vector({dim})
);
""")
conn.commit()
테이블 생성을 해줌.
# 3단계: 벡터 저장
for review, emb in zip(reviews, embeddings):
cur.execute(
"INSERT INTO review_vectors (review, embedding) VALUES (%s, %s)",
(review, np.array(emb, dtype=np.float32))
)
conn.commit()
여기는 똑같다.