EmbeddingGemma 2로 사진·메모 검색을 만들 때 벡터는 몇 차원이면 되나
사진·메모 벡터는 잘라서 저장한다
사진과 글을 모두 256차원으로 잘라 저장하면 용량은 3분의 1로 줄고 적중률은 2~3점만 내려갑니다.
표의 사진 찾기는 사진 1,000장을 영어 설명 문장으로, 글 찾기는 한국어 위키백과 문단 960개를 질문으로 찾을 때 1등 결과가 정답인 비율입니다.
| 저장 차원 | 사진 찾기 | 글 찾기 | 고르는 경우 |
|---|---|---|---|
| 768 | 86.4% | 79.5% | 용량이 넉넉할 때 |
| 512 | 86.1% | 78.8% | 768과 거의 같다 |
| 256 | 84.1% | 76.2% | 기본값 |
| 128 | 75.3% | 69.3% | 후보만 추릴 때 |
from sentence_transformers import SentenceTransformermodel = SentenceTransformer("google/embeddinggemma-2", config_kwargs={"audio_config": None})photos = model.encode([{"image": "IMG_0412.jpg"}, {"image": "IMG_0413.jpg"}], truncate_dim=256, normalize_embeddings=True)memos = model.encode(["title: 여름휴가 | text: 해운대에서 콩이랑 아침 산책"], truncate_dim=256, normalize_embeddings=True)query = model.encode("바닷가에서 뛰는 강아지", prompt_name="SearchQuery", truncate_dim=256, normalize_embeddings=True)print(model.similarity(query, photos), model.similarity(query, memos))truncate_dim=256은 앞쪽 256개만 남기고, normalize_embeddings=True는 잘라서 짧아진 벡터의 길이를 다시 1로 맞춥니다. 질의도 같은 256차원이어야 비교됩니다.
- 메모는
title: 제목 | text: 본문꼴로 넣습니다. - 질의에는
prompt_name="SearchQuery"를 붙입니다. - 사진에는 아무것도 붙이지 않습니다.
audio_config를 None으로 주면 음성을 읽는 인코더를 빼고 440M 파라미터만 올립니다.
글만 넣거나 음성까지 넣는다면
인코더는 쓰는 것만 올립니다. 빼고 올려도 벡터는 같은 공간에 놓여서, 나중에 인코더를 더해도 이미 만든 벡터를 다시 만들 필요가 없습니다.
| 넣는 것 | config_kwargs | 크기 |
|---|---|---|
| 글만 | {"vision_config": None, "audio_config": None} | 270M |
| 글·사진 | {"audio_config": None} | 440M |
| 글·음성 | {"vision_config": None} | 570M |
| 전부 | {} | 740M |
Ollama로 띄운다면 embeddinggemma-2:270m은 글만, embeddinggemma-2:440m은 글과 사진을 받습니다.
사진과 메모가 한 목록에 섞인다
EmbeddingGemma 2는 사진과 글을 숫자 768개짜리 벡터로 바꿔 같은 공간에 놓는 임베딩 모델입니다. 같은 공간에서는 사진 벡터와 글 벡터의 거리를 그대로 견줄 수 있습니다.
사진용 모델과 글용 모델을 따로 쓰면 점수의 잣대가 달라 두 결과를 한 목록으로 섞지 못합니다. 사진 1,000장과 메모 다섯 개를 256차원으로 넣고 "바닷가에서 뛰는 강아지"로 찾으면, 해변의 개 사진들 사이에 해운대 산책 메모가 2위로 끼어듭니다.
사진 옆 영어 문장은 데이터셋에 딸린 설명이고, 순위는 사진 자체의 벡터로 매깁니다.
DigUp은 이 방식으로 사진·PDF 쪽·영상 장면·녹음 30초마다 벡터를 하나씩 SQLite 파일 하나에 넣고, 질의 하나로 영상 장면과 PDF 쪽을 한 순위에 올립니다.
앞쪽 숫자에 정보가 몰려 있다
EmbeddingGemma 2는 앞쪽 숫자에 정보가 몰려 있어 256차원까지 잘라도 순위가 버팁니다. Matryoshka는 중요한 정보가 앞쪽 숫자에 먼저 담기도록 가르치는 학습 방식이라, 뒤를 잘라 내도 가까운 것끼리는 여전히 가깝습니다. 한국어 설명으로 사진 찾기는 사진 1,000장 가운데 100장에 한국어 설명을 붙여 찾게 한 값입니다.
한국어 설명으로 찾아도 256차원에서 1등 적중률이 83%입니다.
128차원에서도 10등 안에 정답이 드는 비율은 사진·글 모두 90%를 넘어서, 128차원은 후보 열 개를 추린 뒤 768차원으로 다시 순위를 매길 때만 씁니다.
저장 크기는 숫자 하나를 float32(4바이트)로 저장할 때입니다.
이미 768차원으로 쌓아 둔 벡터를 줄인다면
다시 임베딩하지 않고 앞쪽 256개를 잘라 길이를 1로 맞추면 됩니다.
small = stored[:, :256]small = small / np.linalg.norm(small, axis=1, keepdims=True)길이를 맞추지 않으면 오류 없이 순위만 나빠집니다. 128차원으로 자른 벡터를 그대로 내적(dot)으로 비교하면 사진 1등 적중률이 75.3%에서 64.1%로 떨어집니다. SentenceTransformer("google/embeddinggemma-2", truncate_dim=256)처럼 모델을 만들 때 잘라도 길이가 1보다 짧게(0.7 안팎) 남으니 normalize_embeddings=True를 함께 줍니다. 질의만 768차원으로 남으면 mat1 and mat2 shapes cannot be multiplied 오류가 납니다.