12장. 멀티모달 RAG 와 데이터셋 자동 생성
실습 2 — 그림으로 찾고 이름 붙이기
그림끼리 견줘 비슷한 그림을 찾고, 이름표 글과 견줘 그림에 종류와 색을 붙여 봅니다(제로샷 분류). 실습 1과 같은 준비로 시작합니다.
1단계. 실습 1과 같은 준비
import numpy as np
import pandas as pd
from PIL import Image
from sentence_transformers import SentenceTransformer
labels = pd.read_csv("product_labels.csv")
images = [Image.open(f).convert("RGB") for f in labels["파일"]]
img_model = SentenceTransformer("clip-ViT-B-32", device="cpu")
ko_model = SentenceTransformer("sentence-transformers/clip-ViT-B-32-multilingual-v1", device="cpu")
I = img_model.encode(images, normalize_embeddings=True)
print(I.shape)(24, 512)2단계. 그림으로 비슷한 그림 찾기
S = I @ I.T # 그림끼리의 코사인 유사도 (24, 24)
np.fill_diagonal(S, -1) # 자기 자신은 빼고 찾는다
name = lambda k: f"{labels.loc[k, '색']} {labels.loc[k, '종류']}"
for i in [0, 9, 21]:
near = np.argsort(-S[i])[:3]
print(name(i), "→", [f"{name(k)} {S[i, k]:.2f}" for k in near])
# 가장 가까운 그림이 같은 종류인 몫과 같은 색인 몫
top = S.argmax(axis=1)
print("같은 종류:", round(np.mean(labels["종류"].values[top] == labels["종류"].values), 3),
"· 같은 색:", round(np.mean(labels["색"].values[top] == labels["색"].values), 3))노랑 머그컵 → ['빨강 머그컵 0.97', '파랑 머그컵 0.96', '노랑 화분 0.84']
빨강 배낭 → ['노랑 배낭 0.98', '파랑 배낭 0.97', '빨강 머그컵 0.81']
노랑 화분 → ['파랑 화분 0.97', '빨강 화분 0.97', '노랑 머그컵 0.84']
같은 종류: 1.0 · 같은 색: 0.03단계. 이름표 글로 종류와 색 붙이기
kinds = labels.drop_duplicates("종류")[["종류", "영어"]].values.tolist() # [[머그컵, mug], ...]
colors = [["빨강", "red", "빨간색"], ["파랑", "blue", "파란색"], ["노랑", "yellow", "노란색"]]
def zero_shot(prompts, model):
"""이름표 글들의 임베딩과 그림 임베딩을 견줘 그림마다 가장 가까운 이름표의 번호를 고른다"""
T = model.encode(prompts, normalize_embeddings=True)
return (I @ T.T).argmax(axis=1)
rows = []
for lang, model, kind_p, color_p in [
("영어", img_model, [f"a drawing of a {e}" for _, e in kinds], [f"a drawing of a {e} object" for _, e, _ in colors]),
("한국어", ko_model, [f"{k} 그림" for k, _ in kinds], [f"{ko} 물건 그림" for _, _, ko in colors])]:
k_hat, c_hat = zero_shot(kind_p, model), zero_shot(color_p, model)
rows.append({"이름표": lang,
"종류 정확도": np.mean([kinds[j][0] for j in k_hat] == labels["종류"].values),
"색 정확도": np.mean([colors[j][0] for j in c_hat] == labels["색"].values)})
print(pd.DataFrame(rows).round(3).to_string(index=False))이름표 종류 정확도 색 정확도
영어 1.0 1.0
한국어 0.5 1.0이 실습이 뜻하는 것
이 실습은 사진첩을 정리하는 일과 같습니다. 실습 1처럼 사진마다 생김새를 기억해 두고(1단계), 사진 한 장마다 가장 닮은 사진 셋을 옆에 놓아 봅니다(2단계). 그다음 "머그컵 그림", "빨간색 물건 그림" 같은 이름표를 몇 장 써 두고, 사진마다 가장 잘 어울리는 이름표를 붙입니다(3단계). 여기서 닮은 사진 찾기가 그림끼리의 유사도 검색, 이름표 붙이기가 제로샷 분류(zero-shot, 미리 보여 준 예가 0개)입니다. 비유와 다른 점은, 사람은 이름표를 붙이며 익숙해지지만 모델은 이 실습에서 아무것도 새로 배우지 않는다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 준비 | (24, 512) | 실습 1과 같이 그림 24장이 좌표 24개가 되었습니다 |
| 2. 닮은 그림 | 노랑 머그컵 → ['빨강 머그컵 0.97', '파랑 머그컵 0.96', '노랑 화분 0.84'] | 노란 머그컵과 가장 닮은 것은 다른 색 머그컵 둘이고, 같은 노랑인 화분은 셋째(0.84)입니다. 1에 가까울수록 닮았습니다 |
| 2. 몫 | 같은 종류: 1.0 · 같은 색: 0.0 | 24장 모두 가장 닮은 그림이 같은 종류의 다른 색이었습니다. 이 그림들에서 CLIP 은 색보다 모양을 크게 봅니다 |
| 3. 영어 이름표 | 종류 정확도 1.0, 색 정확도 1.0 | 학습 없이 이름표 글만으로 24장의 종류와 색을 모두 맞혔습니다 |
| 3. 한국어 이름표 | 종류 정확도 0.5, 색 정확도 1.0 | 색은 모두 맞혔지만 종류는 절반입니다. 실습 1과 같은 약점입니다 |
한 줄로 줄이면, 그림끼리 찾으면 모양이 같은 상품이 먼저 나오고, 이름표 글만 있으면 학습 없이도 그림을 분류할 수 있다는 실습입니다. 다만 한국어 이름표는 이번에도 종류를 절반만 맞혔습니다. 그래서 실습 3에서는 비전 모델로 그림을 한국어 설명 글로 바꿔 봅니다. "같은 색 0.0"은 CLIP 이 색을 못 본다는 뜻이 아닙니다. 3단계에서 색 이름표는 모두 맞혔으니, 이 그림들에서는 모양 차이가 색 차이보다 크게 잡혔다는 뜻입니다.
코드 설명
- 2단계에서 그림마다 가장 가까운 그림은 24장 모두 같은 종류의 다른 색이었습니다(같은 종류 1.0, 같은 색 0.0). 노란 머그컵에는 빨간 머그컵(0.97)과 파란 머그컵(0.96)이 가까웠습니다. 이 그림들에서 CLIP 은 색보다 모양을 더 크게 본 것입니다.
- 3단계의 제로샷 분류는 학습 없이 이름표 글 8개(종류)와 3개(색)만으로, 영어 이름표일 때 종류와 색을 모두 맞혔습니다(1.0).
zero_shot()은 그림 임베딩과 이름표 임베딩의 내적이 가장 큰 이름표를 고릅니다. - 한국어 이름표("머그컵 그림")는 색은 모두 맞혔지만 종류는 0.5 였습니다. 실습 1과 같은 결과로, 이 다국어 모델에서는 한국어 종류 이름이 약하다는 것을 다시 보여 줍니다.
fill_diagonal(S, -1)은 자기 자신과의 유사도(1)를 -1 로 덮어, 가장 가까운 그림으로 자기 자신이 뽑히지 않게 합니다.