학습 자료

글자 대신 뜻을 잰다 — 문장을 숫자로 바꾼다


Article

앞에서 글 264편을 9,005조각으로 잘랐다. 이제 여기서 답을 찾아야 한다.

전체 여섯 편 중 지금
  1. ①② 웹에서 글을 가져온다끝남
  2. ③④ 토막으로 자른다끝남
  3. ⑤ 문장을 숫자로 바꾼다지금 여기
  4. ⑥ 264편을 검색한다다음

글자를 맞춰보는 방식이 안 되는 이유

질문: 돈 돌려받고 싶다
  • 주문 후 7일 이내에 환불 신청이 가능하다. 겹치는 글자 1개
  • 배송은 결제일 기준 2~3일 걸린다. 겹치는 글자 1개
  • 회원 등급은 매월 1일에 갱신된다. 겹치는 글자 1개
  • 포인트는 적립일로부터 1년간 유효하다. 겹치는 글자 1개

전부 1개다. 답이 첫 줄에 있는데 넷을 구분할 방법이 없다. 뜻을 봐야 하는데 컴퓨터는 숫자만 안다. 그래서 뜻을 숫자로 바꾼다.

용어 세 개
벡터
줄지어 늘어놓은 숫자 묶음
[0.08, -0.04, ...]
임베딩
문장을 벡터로 바꾸는 일 (또는 그 결과)
뜻을 좌표로
차원
그 묶음에 든 숫자의 개수
지도는 2, 우리 모델은 384

지도에서 서울 (37.5, 127.0) 과 인천 (37.4, 126.7) 의 숫자를 빼보면 어디가 가까운지 나온다. 원리는 같고, 뜻은 축이 많아서 384개를 쓸 뿐이다.

1. 도구를 깔고 모델을 받는다

.venv\Scripts\activate                      # (.venv) 가 붙었는지 확인하고
python -m pip install sentence-transformers

계산 엔진(torch)이 딸려 와서 1~2GB 를 받는다. 10분 넘게 걸려도 멈춘 게 아니다. 그래픽카드는 없어도 된다.

11_load_model.py
from sentence_transformers import SentenceTransformer

# 모델 이름은 "만든사람/모델이름" 형식이다.
# 처음 실행할 때만 받고, 두 번째부터는 디스크에서 읽는다.
model = SentenceTransformer("intfloat/multilingual-e5-small")

print("차원:", len(model.encode("아무 문장이나")))
print("한 번에 읽는 최대 길이:", model.max_seq_length)
터미널
  • model.safetensors: 100%|██████████| 471M/471M [00:41<00:00, 11.4MB/s]
  • 차원: 384
  • 한 번에 읽는 최대 길이: 512

이 모델은 문장 하나를 숫자 384개로 표현한다. 512 를 넘으면 뒤가 조용히 잘리는데, 우리 조각은 최대 200자라 한참 남는다.

같이 뜨는 경고 끄기

터미널
  • Warning: You are sending unauthenticated requests to the HF Hub.
  • Please set a HF_TOKEN to enable higher rate limits and faster downloads.

에러가 아니다. 실행할 때마다 모델 저장소에 「최신 맞나」를 확인하러 가는데 그 요청이 익명이라 뜬다. 같은 곳에서 여러 명이 동시에 하면 429 로 차단당하므로, 이미 받았으면 아예 못 가게 막는다.

# 윈도우 파워셸 — 이 터미널에만 적용된다
$env:HF_HUB_OFFLINE = "1"
python 11_load_model.py
# 코드에 넣을 때는 반드시 import 보다 위에
import os
os.environ["HF_HUB_OFFLINE"] = "1"

from sentence_transformers import SentenceTransformer
실제로 재보면
경고모델 로드
그냥 실행뜬다12.5초
HF_HUB_OFFLINE=1없다7.3초
확인하러 가는 왕복이 없어져서 5초가 빨라진다.

2. 문장이 숫자 384개가 된다

12_first_vector.py
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("intfloat/multilingual-e5-small")

# encode 가 "문장 -> 벡터" 변환을 한다
v = model.encode("환불은 어떻게 하나요")

print(v.shape)
print(v[:6])            # 앞 6개만. 384개를 다 찍으면 화면이 넘친다

# 목록으로 넣으면 여러 문장을 한 번에 처리한다. 반복문보다 빠르다.
vs = model.encode([
    "환불은 어떻게 하나요",
    "결제를 취소하고 싶어요",
    "오늘 점심 뭐 먹지",
])
print(vs.shape)
터미널
  • (384,)
  • [ 0.08457815 -0.04076722 -0.08963361 -0.05305987 0.03292692 0.00353625]
  • (3, 384)

.shape 는 「몇 줄 몇 칸인가」다. (384,) 는 묶음 하나, (3, 384) 는 그런 묶음이 3개다.

숫자 하나하나에는 뜻이 없다. 384개가 모여서 만드는 방향이 뜻이다.

3. 글자가 안 겹쳐도 찾는다

두 벡터가 얼마나 비슷한지는 코사인 유사도로 잰다. 두 화살표가 같은 쪽을 가리키는가를 −1 에서 1 사이 숫자로 나타낸 것이다. 1 에 가까우면 같은 방향, 0 근처면 상관없음이다.

13_similarity.py
from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer("intfloat/multilingual-e5-small")

question = "돈 돌려받고 싶다"
docs = [
    "주문 후 7일 이내에 환불 신청이 가능하다.",
    "배송은 결제일 기준 2~3일 걸린다.",
    "회원 등급은 매월 1일에 갱신된다.",
    "포인트는 적립일로부터 1년간 유효하다.",
]

# 결과가 [[a, b, c, d]] 표 모양이라 [0] 으로 첫 줄만 꺼낸다
scores = util.cos_sim(model.encode(question), model.encode(docs))[0]

# 마이너스를 붙이면 큰 것부터 정렬된다
for i in sorted(range(len(docs)), key=lambda i: -float(scores[i])):
    same = set(question.replace(" ", "")) & set(docs[i].replace(" ", ""))
    print(f"{float(scores[i]):.3f}  겹치는 글자 {len(same)}개  {docs[i]}")
터미널
  • 0.844 겹치는 글자 1개 주문 후 7일 이내에 환불 신청이 가능하다.
  • 0.831 겹치는 글자 1개 포인트는 적립일로부터 1년간 유효하다.
  • 0.827 겹치는 글자 1개 배송은 결제일 기준 2~3일 걸린다.
  • 0.799 겹치는 글자 1개 회원 등급은 매월 1일에 갱신된다.

환불 문서가 1위로 올라왔다. 겹치는 글자는 여전히 넷 다 1개다.

4. 점수는 순위로만 쓴다

1위가 0.844, 꼴찌가 0.799 다. 차이가 0.045 밖에 안 난다. 두 문장만 떼어놓고 재면 이렇게 된다.

둘씩만 재봤을 때
  • 0.888 돈 돌려받고 싶다 / 환불 신청이 가능하다
  • 0.892 돈 돌려받고 싶다 / 배송비가 무료다
  • 0.863 돈 돌려받고 싶다 / 오늘 점심 뭐 먹지

무관한 쪽이 더 높게 나왔다. 점수대가 통째로 높은 쪽에 뭉쳐 있고, 그 범위는 모델마다 다르다. 「0.8 넘으면 비슷하다」 같은 기준은 없다. 후보끼리 줄 세울 때만 쓴다.

5. 모델 고르기

대표적인 모델들 (설정 파일에서 직접 확인)
모델차원층최대 토큰받는 용량
multilingual-e5-small38412512약 470MB
multilingual-e5-base76812514약 1.1GB
multilingual-e5-large102424514약 2.2GB
BAAI/bge-m31024248194약 2.2GB
ko-sroberta-multitask76812514약 440MB
차원 = 숫자 몇 개로 표현하나, 층 = 계산을 몇 겹으로 하나, 최대 토큰 = 한 번에 읽는 길이.

고르는 순서는 ① 돌릴 수 있나 ② 한국어를 학습했나(multilingual · m3) ③ 검색용(retrieval)으로 학습됐나 ④ 재본다. 우리는 노트북 CPU 로 돌려야 해서 small 을 골랐다.

모델을 바꾸면 벡터를 전부 다시 만들어야 한다. 차원이 같아도 A 모델 벡터와 B 모델 벡터는 비교할 수 없다.

이번 편에 나온 것

정리
쓴 것하는 일
pip install sentence-transformers문장 → 벡터 도구를 깐다 (1~2GB)
SentenceTransformer("이름")모델을 부른다. 없으면 받는다
model.encode("문장")숫자 384개짜리 벡터 하나
model.encode([...])목록을 통째로. 반복문보다 빠르다
.shape몇 줄 몇 칸인가
util.cos_sim(a, b)두 벡터의 방향이 얼마나 같나 (−1 ~ 1)
model.max_seq_length이 길이를 넘으면 조용히 잘린다
HF_HUB_OFFLINE=1모델 확인 요청을 끈다
sorted(..., key=lambda i: -x)마이너스를 붙여 큰 것부터 정렬
set(a) & set(b)두 모음의 공통 부분

다음 편에서 9,005조각에 통째로 걸어본다.

Share
  • 파이썬
  • AI
  • RAG
  • 임베딩
  • 벡터
글자 대신 뜻을 잰다 — 문장을 숫자로 바꾼다 — 디코드랩(DCODELAB)