9차시. 임베딩 이해
5. 실습 ② — 유사도 직접 재보기
출처: 원본 PDF 09차시_임베딩이해.pdf 10쪽 · 쪽 그림 10쪽
오늘 실습의 핵심입니다. 단어가 안 겹쳐도 점수가 높게 나오는지 확인합시다.
📝 src/similarity.py
import numpy as np
from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
load_dotenv()
emb = OpenAIEmbeddings(model="text-embedding-3-small")
def sim(a: str, b: str) -> float:
# 두 문장을 좌표(벡터)로 바꾼다
v1 = np.array(emb.embed_query(a))
v2 = np.array(emb.embed_query(b))
# 코사인 유사도 = 내적 ÷ (길이 × 길이)
dot_product = np.dot(v1, v2)
length1 = np.linalg.norm(v1)
length2 = np.linalg.norm(v2)
return float(dot_product / (length1 * length2))
PAIRS = [
("환불 규정", "반품 절차"), # 뜻은 같고 글자는 다름
("비밀번호 변경", "패스워드 재설정"), # 완전한 동의어
("제품이 고장났어요", "기기 불량 신고"), # 표현이 다름
("환불 규정", "배송 안내"), # 약하게 관련
("환불 규정", "오늘 점심 메뉴"), # 완전 무관
]
for a, b in PAIRS:
print(f"{sim(a, b):.3f} {a:<18} ↔ {b}")실행 결과 예시 (코드를 읽고 만든 예상 화면입니다. 실제 실행 결과, 특히 모델의 답은 다를 수 있습니다)
0.782 환불 규정 ↔ 반품 절차
0.891 비밀번호 변경 ↔ 패스워드 재설정
0.734 제품이 고장났어요 ↔ 기기 불량 신고
0.521 환불 규정 ↔ 배송 안내
0.203 환불 규정 ↔ 오늘 점심 메뉴실습 코드 · 09/similarity.py
실습 코드: 실습/src/09/similarity.py · 위의 원본 PDF 9차시 10쪽 「5️⃣ 실습 ② — 유사도 직접 재보기」과 이어집니다.
# ============================================================
# 9차시 | PPT: 09차시_임베딩이해.pptx
# 역할: 두 문장의 embedding이 얼마나 같은 방향인지 코사인 유사도로 계산
# 입력: PAIRS에 있는 문장 쌍 5개
# 출력: 각 쌍의 유사도 (소수 4자리)
# 흐름:
# 문장 둘 -> embed_query -> vector
# -> 내적 / (길이1 * 길이2) -> print
# ============================================================
# embedding: 문장을 숫자 목록으로 바꾼 값
# vector: 그 숫자 목록. 방향이 비슷할수록 아래 유사도가 1에 가깝습니다.
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
# numpy는 숫자 목록끼리의 계산(내적, 길이)을 한 줄로 해 줍니다.
import numpy as np
load_dotenv()
emb = OpenAIEmbeddings(model="text-embedding-3-small")
def sim(sentence1, sentence2):
# 각 문장을 벡터로 바꿉니다. 호출마다 네트워크 요청이 납니다.
vector1 = emb.embed_query(sentence1)
vector2 = emb.embed_query(sentence2)
# 파이썬 리스트를 numpy 배열로 바꿔야 np.dot 같은 계산을 쓸 수 있습니다.
vector1 = np.array(vector1)
vector2 = np.array(vector2)
# 코사인 유사도: 1에 가까울수록 방향이 비슷합니다.
dot_product = np.dot(vector1, vector2)
length1 = np.linalg.norm(vector1)
length2 = np.linalg.norm(vector2)
similarity = dot_product / (length1 * length2)
# numpy 숫자를 보통 파이썬 float로 바꿔 돌려줍니다.
return float(similarity)
# 위의 세 쌍은 글자가 거의 안 겹치지만 뜻이 같고, 아래 두 쌍은 뜻이 멀어집니다.
PAIRS = [
("환불 규정", "반품 절차"),
("비밀번호 변경", "패스워드 재설정"),
("제품이 고장났어요", "기기 불량 신고"),
("환불 규정", "배송 안내"),
("환불 규정", "오늘 점심 메뉴"),
]
# 튜플 하나를 두 변수로 나눠 받습니다. 쌍마다 embed_query가 두 번 불립니다.
for sentence1, sentence2 in PAIRS:
score = sim(sentence1, sentence2)
print(f"'{sentence1}'와 '{sentence2}'의 유사도: {score:.4f}")실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/09
python similarity.py
예시 출력 (API 키 필요, 실행하지 않음)
'환불 규정'와 '반품 절차'의 유사도: (유사도 점수 미기록)
'비밀번호 변경'와 '패스워드 재설정'의 유사도: (유사도 점수 미기록)
'제품이 고장났어요'와 '기기 불량 신고'의 유사도: (유사도 점수 미기록)
'환불 규정'와 '배송 안내'의 유사도: (유사도 점수 미기록)
'환불 규정'와 '오늘 점심 메뉴'의 유사도: (유사도 점수 미기록)코드 해설
두 문장의 embedding이 얼마나 같은 방향인지 코사인 유사도로 계산입니다. 입력은 PAIRS에 있는 문장 쌍 5개이고, 출력은 각 쌍의 유사도 (소수 4자리)입니다.
주석에 적힌 흐름입니다.
문장 둘 -> embed_query -> vector
-> 내적 / (길이1 * 길이2) -> print코드 앞에서 고정해 두는 말입니다.
- embedding: 문장을 숫자 목록으로 바꾼 값
- vector: 그 숫자 목록. 방향이 비슷할수록 아래 유사도가 1에 가깝습니다.
함수와 클래스로 나누어 읽습니다.
sim: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
5-1) 실행 결과
0.782 환불 규정 ↔ 반품 절차
0.891 비밀번호 변경 ↔ 패스워드 재설정
0.734 제품이 고장났어요 ↔ 기기 불량 신고
0.521 환불 규정 ↔ 배송 안내
0.203 환불 규정 ↔ 오늘 점심 메뉴🎯 여기서 확인할 것
"환불 규정" ↔ "반품 절차" = 0.782
글자가 단 하나도 겹치지 않습니다.
Ctrl+F로는 절대 못 찾습니다.
그런데 0.782라는 높은 점수가 나옵니다.
이것이 의미 검색의 힘입니다.
반면 "점심 메뉴"는 0.203으로 확실히 낮습니다.
관련 있는 것과 없는 것이 확실히 갈립니다.