12장. 멀티모달 RAG 와 데이터셋 자동 생성
실습 1 — 글로 그림 찾기
상품 그림 24장(8종류 × 3색)을 CLIP 으로 임베딩하고, "빨간 머그컵" 같은 한국어 글과 "a drawing of a red mug" 같은 영어 글로 찾아 몇 장을 맞히는지 견줍니다. 그림은 코드로 그린 단순한 그림이고, product_labels.csv 는 채점용 정답 표입니다. 두 CLIP 모델은 처음 실행할 때 1GB 남짓 내려받습니다.
1단계. 그림과 정답 표 읽기
import time
import numpy as np
import pandas as pd
from PIL import Image
labels = pd.read_csv("product_labels.csv") # 코드, 파일, 이름, 종류, 영어, 색
images = [Image.open(f).convert("RGB") for f in labels["파일"]]
print(len(images), "장 · 크기", images[0].size)
# 결과: 종류마다 어떤 색이 있는지
print(labels.groupby("종류")["색"].apply(list).to_dict())24 장 · 크기 (256, 256)
{'머그컵': ['노랑', '빨강', '파랑'], '모자': ['노랑', '파랑', '빨강'], '배낭': ['빨강', '노랑', '파랑'], '스탠드 조명': ['빨강', '파랑', '노랑'], '우산': ['빨강', '파랑', '노랑'], '운동화': ['빨강', '파랑', '노랑'], '티셔츠': ['빨강', '노랑', '파랑'], '화분': ['노랑', '빨강', '파랑']}2단계. 그림을 임베딩하기
from sentence_transformers import SentenceTransformer
# 그림과 영어 글을 같은 공간에 놓는 CLIP. GPU 는 뒤의 Ollama 에 비워 두려고 CPU 로 부른다
img_model = SentenceTransformer("clip-ViT-B-32", device="cpu")
# 한국어 등 50여 언어의 글을 위 CLIP 과 같은 공간으로 보내는 글 인코더
ko_model = SentenceTransformer("sentence-transformers/clip-ViT-B-32-multilingual-v1", device="cpu")
t = time.time()
I = img_model.encode(images, normalize_embeddings=True) # 그림 24장 → (24, 512)
print("그림 임베딩:", I.shape, f"· {time.time() - t:.1f}초")그림 임베딩: (24, 512) · 1.8초3단계. 한국어와 영어로 찾아 채점하기
ADJ = {"빨강": "빨간", "파랑": "파란", "노랑": "노란"}
EN = {"빨강": "red", "파랑": "blue", "노랑": "yellow"}
# 그림마다 그 그림을 가리키는 질문 하나씩: 한국어 24개, 영어 24개
ko_q = [f"{ADJ[c]} {k}" for k, c in zip(labels["종류"], labels["색"])]
en_q = [f"a drawing of a {EN[c]} {e}" for e, c in zip(labels["영어"], labels["색"])]
def score(T):
"""질문 임베딩 T 마다 가장 가까운 그림을 골라, 딱 맞힌 몫과 종류, 색을 맞힌 몫을 잰다"""
best = (T @ I.T).argmax(axis=1)
kind, color = labels["종류"].values, labels["색"].values
return {"딱 맞음": np.mean(best == np.arange(len(labels))), "종류 맞음": np.mean(kind[best] == kind),
"색 맞음": np.mean(color[best] == color)}, best
ko_score, ko_best = score(ko_model.encode(ko_q, normalize_embeddings=True))
en_score, en_best = score(img_model.encode(en_q, normalize_embeddings=True)) # 영어 글은 CLIP 자신의 글 인코더로
print(pd.DataFrame([ko_score, en_score], index=["한국어(다국어 모델)", "영어(CLIP)"]).round(3)) 딱 맞음 종류 맞음 색 맞음
한국어(다국어 모델) 0.5 0.5 1.0
영어(CLIP) 1.0 1.0 1.04단계. 한국어 질문이 틀린 것 보기
wrong = [(q, f"{labels.loc[b, '색']} {labels.loc[b, '종류']}") for q, b, i in zip(ko_q, ko_best, range(len(labels))) if b != i]
print("틀린 수:", len(wrong))
for q, got in wrong[:8]:
print(f" {q} → {got}")틀린 수: 12
빨간 머그컵 → 빨강 화분
파란 운동화 → 파랑 모자
노란 운동화 → 노랑 티셔츠
빨간 배낭 → 빨강 모자
노란 배낭 → 노랑 모자
파란 배낭 → 파랑 모자
빨간 스탠드 조명 → 빨강 모자
파란 스탠드 조명 → 파랑 모자이 실습이 뜻하는 것
이 실습은 말이 서툰 점원에게 말로만 물건을 찾아 오게 하는 시험과 같습니다. 진열대의 물건 24개와 정답 목록을 꺼내 놓고(1단계), 점원이 물건마다 생김새를 기억해 두게 합니다(2단계). 그다음 "빨간 머그컵"처럼 한국어로, "a drawing of a red mug"처럼 영어로 하나씩 말해 맞는 물건을 집어 오는지 채점하고(3단계), 한국어로 말했을 때 잘못 집어 온 물건을 살펴봅니다(4단계). 여기서 점원이 CLIP(Contrastive Language-Image Pre-training, 글과 그림을 짝지어 미리 학습한 모델), 생김새 기억이 그림 임베딩, 한국어를 점원이 아는 말로 옮겨 주는 통역이 다국어 글 모델입니다. 비유와 다른 점은, 점원은 말을 듣고 물건을 보지만 모델은 글과 그림을 모두 숫자 512개로 바꿔 가장 가까운 것을 고를 뿐이라는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 읽기 | 24 장 · 크기 (256, 256) | 상품 그림 24장을 읽었고, 한 장은 가로세로 256칸입니다. 종류 8가지마다 빨강, 파랑, 노랑 세 색이 있습니다 |
| 2. 임베딩 | (24, 512) · 1.8초 | 그림 한 장이 숫자 512개짜리 좌표 하나가 되었습니다. 이 PC 의 CPU(Central Processing Unit, 중앙 처리 장치)로 24장에 1.8초 걸렸습니다 |
| 3. 한국어 | 딱 맞음 0.5, 종류 맞음 0.5, 색 맞음 1.0 | 한국어 질문 24개 가운데 12개만 정확한 그림을 골랐습니다. 색은 모두 맞혔고 종류를 절반 틀렸습니다 |
| 3. 영어 | 셋 모두 1.0 | 영어 질문은 24개 모두 정확한 그림을 골랐습니다 |
| 4. 틀린 것 | 틀린 수: 12, 빨간 배낭 → 빨강 모자 등 | 틀린 12개는 색은 맞고 종류만 틀렸습니다. 출력된 8개 가운데 6개가 "모자" 그림으로 몰렸습니다 |
한 줄로 줄이면, 같은 CLIP 그림 공간이라도 영어로 물으면 다 맞고 한국어로 물으면 절반만 맞는다는 것을 보인 실습입니다. 한국어의 약점은 색이 아니라 "배낭", "스탠드 조명" 같은 종류 이름입니다. 실습 2에서는 그림끼리, 이름표 글로 견주고, 실습 3에서는 그림을 한국어 설명 글로 바꾸는 길을 해 봅니다. 코드로 그린 단순한 그림 24장의 결과라서, 실제 상품 사진에서도 영어가 다 맞는다는 뜻은 아닙니다.
코드 설명
SentenceTransformer("clip-ViT-B-32")의encode()는 글 목록뿐 아니라 PIL 그림 목록도 받아, 그림 24장을 길이 512 의 임베딩으로 바꿉니다. 이 PC 의 CPU 로 2초가 안 걸렸습니다. 한국어 글은 같은 공간으로 학습한 다국어 모델로 임베딩합니다.- 영어 질문은 24장을 모두 맞혔고(1.0), 한국어 질문은 딱 맞힌 것이 절반(0.5)이었습니다. 한국어도 색은 모두 맞혔지만(1.0) 종류를 절반만 맞혀서 생긴 차이입니다.
- 4단계를 보면 한국어 질문의 틀린 12개는 모두 색은 맞고 종류가 틀렸습니다. "빨간 배낭", "파란 스탠드 조명"처럼 여러 종류가 "모자" 그림으로 몰렸습니다.
- 다국어 모델은 영어 CLIP 의 글 인코더를 흉내 내도록 따로 학습한 것이라, "배낭", "스탠드 조명" 같은 낱말은 영어만큼 정확하게 같은 자리로 보내지 못했습니다. 한국어 서비스라면 질문을 영어로 바꾸거나, 실습 3처럼 그림을 한국어 설명 글로 바꾸는 길을 견줘 봅니다. 이 그림은 단순한 그림이라 실제 사진에서는 영어도 1.0 이 나오지 않을 수 있습니다.