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

9차시. 임베딩 이해

6. 실습 ③ — 내 기준선 찾기

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

"몇 점부터 관련 있다고 볼까?" 이 기준을 정하는 실습입니다. 24차시에서 검색 실패를 판정할때 이 값을 씁니다.

📝 src/find_threshold.py

출력
# 내 문서 주제에 맞춰 두 그룹을 만든다
BASE = "환불은 상품 수령 후 7일 이내에 신청할 수 있습니다"

RELATED = [      # 관련 있다고 봐야 할 질문들
    "반품하고 싶어요",
    "돈 돌려받을 수 있나요?",
    "구매 취소 기간이 어떻게 되나요?",
    "물건 안 마음에 들면 어떡하죠?",
    "환불 신청 기한 알려주세요",
]
UNRELATED = [    # 관련 없다고 봐야 할 질문들
    "회사 주차장은 어디인가요?",
    "채용 공고 보고 싶어요",
    "오늘 날씨 어때요?",
    "대표이사가 누구인가요?",
    "직원 복지 제도 알려주세요",
]

# ① 관련 있는 질문들의 점수를 하나씩 구해서 리스트에 담기
rel = []
for q in RELATED:
    rel.append(sim(BASE, q))

# ② 관련 없는 질문들도 똑같이
unrel = []
for q in UNRELATED:
    unrel.append(sim(BASE, q))

print("■ 관련 있는 질문")
for i in range(len(RELATED)):
    print(f"   {rel[i]:.3f}  {RELATED[i]}")
print(f"   → 최저 {min(rel):.3f}")

print("\n■ 관련 없는 질문")
for i in range(len(UNRELATED)):
    print(f"   {unrel[i]:.3f}  {UNRELATED[i]}")
print(f"   → 최고 {max(unrel):.3f}")

print(f"\n▶ 권장 임계값: {(min(rel) + max(unrel)) / 2:.2f}")

실행 결과 예시
────────────────────────────────
■ 관련 있는 질문
   0.712  반품하고 싶어요
   0.688  돈 돌려받을 수 있나요?
   0.751  구매 취소 기간이 어떻게 되나요?
   0.634  물건 안 마음에 들면 어떡하죠?
   0.823  환불 신청 기한 알려주세요
   → 최저 0.634

■ 관련 없는 질문
   0.284  회사 주차장은 어디인가요?
   0.196  채용 공고 보고 싶어요
   0.152  오늘 날씨 어때요?
   0.311  대표이사가 누구인가요?
   0.267  직원 복지 제도 알려주세요
   → 최고 0.311

👉 권장 임계값: 0.47

💡 이 값을 어디에 쓰나요?
"유사도가 0.47 미만이면 관련 문서가 없다고 판단한다"
→ 이렇게 정해두면 엉뚱한 문서를 근거로
억지 답변을 만드는 일을 막을 수 있습니다.
config.py에 MIN_SCORE = 0.47 처럼 적어두세요.
24차시에서 그대로 씁니다.

실습 코드 · 09/find_threshold.py

실습 코드: 실습/src/09/find_threshold.py · 위의 원본 PDF 9차시 12~13쪽 「6️⃣ 실습 ③ — 내 기준선 찾기」과 이어집니다.

Python
# ============================================================
# 9차시 | PPT: 09차시_임베딩이해.pptx
# 역할: 환불 문장과 관련 질문, 무관 질문의 유사도를 비교해 기준값 후보를 계산
# 입력: BASE 문장, RELATED 5개, UNRELATED 5개
# 출력: 질문별 유사도, 관련 최솟값과 무관 최댓값의 중간값
# 흐름:
#   BASE와 각 질문 -> sim() -> 점수 리스트
#     -> min(관련), max(무관)의 평균 -> print
# ============================================================
# embedding / vector: 문장을 숫자 목록으로 바꾼 값과 그 목록
# 임계값: 이 점수 이상이면 관련 있다고 볼 후보. 관련 점수와 무관 점수 사이에 둡니다.

from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
import numpy as np

# .env의 API 키를 읽고, 문서를 임베딩할 때와 같은 모델을 씁니다.
load_dotenv()

emb = OpenAIEmbeddings(model="text-embedding-3-small")

# similarity.py의 sim과 같은 함수입니다. 코사인 유사도를 돌려줍니다.
def sim(sentence1, sentence2):
    vector1 = emb.embed_query(sentence1)
    vector2 = emb.embed_query(sentence2)

    vector1 = np.array(vector1)
    vector2 = np.array(vector2)

    # 내적 ÷ (길이 × 길이)
    dot_product = np.dot(vector1, vector2)
    length1 = np.linalg.norm(vector1)
    length2 = np.linalg.norm(vector2)
    similarity = dot_product / (length1 * length2)
    return float(similarity)

# 기준이 되는 문서 문장입니다. 내 문서로 실습할 때는 이 줄과 아래 두 목록을 바꿉니다.
BASE = "환불은 상품 수령 후 7일 이내에 신청할 수 있습니다"

# 관련 있다고 봐야 할 질문들
RELATED = [
    "반품하고 싶어요",
    "돈 돌려받을 수 있나요?",
    "구매 취소 기간이 어떻게 되나요?",
    "물건 안 마음에 들면 어떡하죠?",
    "환불 신청 기한 알려주세요",
]

# 관련 없다고 봐야 할 질문들
UNRELATED = [
    "회사 주차장은 어디인가요?",
    "채용 공고 보고 싶어요",
    "오늘 날씨 어때요?",
    "대표이사가 누구인가요?",
    "직원 복지 제도 알려주세요",
]

# ① 관련 있는 질문들의 점수를 하나씩 구해 리스트에 담습니다.
related_scores = []

for question in RELATED:
    score = sim(BASE, question)
    related_scores.append(score)

# ② 관련 없는 질문들도 똑같이 합니다.
unrelated_scores = []

for question in UNRELATED:
    score = sim(BASE, question)
    unrelated_scores.append(score)

# 점수 리스트와 질문 리스트는 순서가 같으므로 같은 번호 i로 짝지어 찍습니다.
for i in range(len(RELATED)):
    print(f"   {related_scores[i]:.3f}  {RELATED[i]}")
# 빈 줄 하나로 관련 그룹과 무관 그룹을 나눠 보이게 합니다.
print()
for i in range(len(UNRELATED)):
    print(f"   {unrelated_scores[i]:.3f}  {UNRELATED[i]}")

# 임계값 후보 계산
# 관련 질문 중 가장 낮은 점수와, 무관 질문 중 가장 높은 점수의 중간입니다.
min_related = min(related_scores)
max_unrelated = max(unrelated_scores)

# 두 그룹 사이 한가운데를 기준으로 삼습니다. 관련 점수가 무관 점수보다 높아야 의미가 있습니다.
threshold = (min_related + max_unrelated) / 2

# 이 값을 config.py에 MIN_SCORE로 적어 두면 24차시에서 그대로 씁니다.
print(f"\n 임계값 후보 :  {threshold : .2f}")

실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/09
      python find_threshold.py
예시 출력 (API 키 필요, 실행하지 않음)
   (유사도 미기록)  반품하고 싶어요
   (유사도 미기록)  돈 돌려받을 수 있나요?
   (유사도 미기록)  구매 취소 기간이 어떻게 되나요?
   (유사도 미기록)  물건 안 마음에 들면 어떡하죠?
   (유사도 미기록)  환불 신청 기한 알려주세요

   (유사도 미기록)  회사 주차장은 어디인가요?
   (유사도 미기록)  채용 공고 보고 싶어요
   (유사도 미기록)  오늘 날씨 어때요?
   (유사도 미기록)  대표이사가 누구인가요?
   (유사도 미기록)  직원 복지 제도 알려주세요

 임계값 후보 :  (관련/무관 점수가 없어 계산값을 적지 않음)

코드 해설

환불 문장과 관련 질문, 무관 질문의 유사도를 비교해 기준값 후보를 계산입니다. 입력은 BASE 문장, RELATED 5개, UNRELATED 5개이고, 출력은 질문별 유사도, 관련 최솟값과 무관 최댓값의 중간값입니다.

주석에 적힌 흐름입니다.

출력
BASE와 각 질문 -> sim() -> 점수 리스트
-> min(관련), max(무관)의 평균 -> print

코드 앞에서 고정해 두는 말입니다.

  • embedding / vector: 문장을 숫자 목록으로 바꾼 값과 그 목록
  • 임계값: 이 점수 이상이면 관련 있다고 볼 후보. 관련 점수와 무관 점수 사이에 둡니다.

함수와 클래스로 나누어 읽습니다.

  • sim: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.

파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.

← 이전 절9차시 · 5. 실습 ② — 유사도 직접 재보기다음 절 →9차시 · 7. 임베딩 모델 고르기