RAG2026-5일9차시 · 임베딩 이해8 / 19← 이전목차다음 →MetaContents

9차시. 임베딩 이해

5. 실습 ② — 유사도 직접 재보기

출처: 원본 PDF 09차시_임베딩이해.pdf 10쪽 · 쪽 그림 10쪽

오늘 실습의 핵심입니다. 단어가 안 겹쳐도 점수가 높게 나오는지 확인합시다.

📝 src/similarity.py

Python
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️⃣ 실습 ② — 유사도 직접 재보기」과 이어집니다.

Python
# ============================================================
# 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으로 확실히 낮습니다.
관련 있는 것과 없는 것이 확실히 갈립니다.

← 이전 절9차시 · 4. 실습 ① — 벡터를 직접 눈으로 보기다음 절 →9차시 · 6. 실습 ③ — 내 기준선 찾기