다양한 벡터 임베딩 비교
이 글은 원래 The New Stack에 게시되었으며 허가를 받아 여기에 재게시되었습니다.
서로 다른 신경망이 생성한 벡터 임베딩은 어떻게 다르며, Jupyter Notebook에서 이를 어떻게 평가할 수 있을까요?
대규모 언어 모델(LLM)이 주목받고 있으며, 우리는 ChatGPT와 같은 언어 애플리케이션의 새로운 패러다임을 마주하고 있습니다. 벡터 데이터베이스는 스택의 핵심 부분이 될 것입니다. 따라서 벡터와 그것이 왜 그렇게 중요한지 이해하는 것이 중요합니다.
이 프로젝트는 모델 간 벡터 임베딩의 차이를 보여주고, 하나의 Jupyter Notebook에서 여러 벡터 데이터 컬렉션을 사용하는 방법을 보여줍니다. 이 글에서는 벡터 임베딩이 무엇인지, 왜 중요한지, 그리고 Jupyter Notebook에서 서로 다른 벡터 임베딩을 어떻게 비교하는지 다룹니다.
벡터 임베딩이란 무엇이며 왜 중요한가요?
벡터 임베딩은 어디에서 나오나요?
가장 간단히 말하면, 벡터 임베딩은 데이터의 수치적 표현입니다. 주로 비정형 데이터를 표현하는 데 사용됩니다. 비정형 데이터란 이미지, 비디오, 오디오, 텍스트, 분자 이미지 및 형식적 구조가 없는 기타 종류의 데이터입니다. 벡터 임베딩은 입력 데이터를 사전 학습된 신경망에 통과시키고 마지막에서 두 번째 층의 출력을 취해 생성됩니다.
신경망은 서로 다른 아키텍처를 가지고 있으며 서로 다른 데이터 세트로 학습되므로, 각 모델의 벡터 임베딩은 고유합니다. 그렇기 때문에 비정형 데이터와 벡터 임베딩을 다루는 것은 어렵습니다. 나중에 서로 다른 데이터 세트로 파인튜닝된 동일한 기반의 모델들이 어떻게 서로 다른 벡터 임베딩을 산출할 수 있는지 살펴보겠습니다.
신경망의 차이는 또한 다양한 형태의 비정형 데이터를 처리하고 그 임베딩을 생성하기 위해 서로 다른 모델을 사용해야 함을 의미합니다. 예를 들어, 이미지에 대한 임베딩을 생성하는 데 문장 트랜스포머 모델을 사용할 수는 없습니다. 반대로, 문장에 대한 임베딩을 생성하는 데 이미지 모델인 ResNet50을 사용하고 싶지는 않을 것입니다. 따라서 데이터 유형에 적합한 모델을 찾는 것이 중요합니다.
벡터 임베딩은 어떻게 비교하나요?
다음으로, 이를 어떻게 비교하는지 살펴보겠습니다. 이 섹션에서는 Hugging Face의 MiniLM을 기반으로 한 세 가지 서로 다른 다국어 모델을 비교합니다. 벡터를 비교하는 방법은 많습니다. 이 예시에서는 L2 거리 메트릭과 inverted file index를 벡터 인덱스로 사용합니다.
이 프로젝트를 위해 저는 Taylor Swift의 최근 앨범 발매에서 영감을 받아 Jupyter Notebook 안에 바로 정의한 일부 데이터를 개발했습니다. 제 예시를 사용해도 되고, 직접 문장을 만들어도 됩니다. 데이터가 준비되면 서로 다른 임베딩을 얻고, 두 세트의 임베딩을 Milvus와 같은 벡터 데이터베이스에 저장합니다. 세 번째 모델의 임베딩을 사용해 쿼리하여 비교를 수행합니다.
우리는 검색 결과가 다른지, 그리고 검색 결과가 서로 얼마나 벗어나 있는지 확인하려고 합니다. 프로덕션 환경에서는 보고자 하는 결과를 알고, 그런 다음 반환된 결과와 대조해 확인해야 합니다.
서로 다른 모델의 벡터 임베딩 비교
우리가 비교하는 세 가지 모델은 Sentence Transformers의 MiniLM을 사용하는 기본 다국어 패러프레이즈 모델, 의도 감지를 위해 파인튜닝된 버전, 그리고 무엇을 위해 튜닝되었는지에 대한 세부 정보 없이 Sprylab이 파인튜닝한 모델입니다. 제 노트북에서 실행할 수 있는 호환 가능한 모델 세 가지를 찾는 것이 이 프로젝트에서 가장 어려운 부분 중 하나였습니다.
벡터 임베딩을 비교하려면 길이/차원이 같은 벡터가 필요합니다. 이 예제에서는 MiniLM Sentence Transformer 모델에 따라 384차원 벡터를 사용하고 있습니다. 이것이 각 데이터 조각에 대해 384개의 “특징”이나 “범주”를 만든다는 뜻은 아니며, 데이터를 384차원 공간에서 추상적으로 표현한다는 뜻입니다. 예를 들어, 어떤 차원도 품사, 문장의 단어 수, 어떤 것이 고유명사인지 여부 또는 그런 개념적인 것을 나타내지는 않습니다.
벡터 임베딩 비교 데이터
우리는 sentence transformer 모델을 사용하고 있으며, 이는 데이터가 문장 형태여야 한다는 뜻입니다. 비교할 문장을 최소 50개 이상 준비하는 것을 권장합니다. 예제 노트북에는 51개가 포함되어 있습니다. 또한 어느 정도 유사성이 있는 데이터를 사용하는 것을 권장합니다. 이 예제에서는 Taylor Swift의 네 곡 “Speak Now,” “Starlight,” “Sparks Fly,” “Haunted.”의 가사를 사용했습니다.
제가 이 곡들을 선택한 이유는 많은 가사가 완전한 문장을 이루고 있어 가사 형식에서 문장 형식으로 바꾸기 쉽기 때문입니다. 또한 가설도 테스트해 보고 싶었습니다. 처음 세 곡은 대체로 사랑 노래인 반면, 마지막 곡인 “Haunted,”는 이별 노래에 더 가깝습니다. 그래서 저는 “Haunted”가 처음 세 곡에 대한 반환 결과에 서로의 곡보다 덜 나타날 가능성이 높다고 가정합니다.
Jupyter Notebook에서 벡터 임베딩 비교하기
코드로 들어가 보겠습니다. Milvus의 경량 버전인 Milvus Lite를 사용하면 Jupyter Notebook에서 벡터 임베딩을 직접 비교할 수 있습니다. 이 예제 코드에서는 ! pip install pymilvus milvus sentence-transformers가 필요합니다. 이 프로젝트에는 몇 가지 import만 필요하며, time 라이브러리는 선택 사항입니다. import를 실행한 후 Milvus에서 default_server를 시작하고 연결을 설정합니다.
from sentence_transformers import SentenceTransformer
from milvus import default_server
from pymilvus import connections, utility, FieldSchema, CollectionSchema, DataType, Collection
from time import time
default_server.start()
connections.connect(host="127.0.0.1", port=default_server.listen_port)
Hugging Face에서 sentence transformer 모델 가져오기
1단계는 벡터 임베딩 모델을 가져오는 것입니다. 이 예제에서는 paraphrase multilingual MiniLM 시리즈를 사용합니다. 첫 번째는 표준 버전입니다. 다음 두 가지는 서로 다르게 파인튜닝된 버전입니다. 이 모델 선택은 파인튜닝이 벡터를 눈에 띄게 어떻게 변화시킬 수 있는지 보여주는 명확한 예를 제공합니다.
v12 = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
ft3_v12 = SentenceTransformer("Sprylab/paraphrase-multilingual-MiniLM-L12-v2-fine-tuned-3")
ft5_v12 = SentenceTransformer("hroth/psais-paraphrase-multilingual-MiniLM-L12-v2-5shot")
데이터 세트를 벡터 데이터베이스의 두 컬렉션에 로드하기
아래에는 Taylor Swift의 “Speak Now” 앨범에서 가져온 51개의 문장을 나열했습니다. 이 코드 스니펫을 자유롭게 복사하여 붙여넣으세요. 또는 읽어 보셔도 좋습니다. 그녀는 훌륭한 작사가입니다.
# Speak Now (Taylor's Version)에서 영감을 받음
# 가사 출처: Speak Now, Starlight, Sparks Fly, Haunted
sentences = [
"나는 하얀 베일의 행사에 무례하게 끼어들어서는 안 되는 그런 여자가 아니지만, 너는 잘못된 여자와 결혼해서는 안 되는 그런 남자가 아니야",
"나는 몰래 들어가서 네 친구들과 그녀의 콧대 높은 작은 가족들이 모두 파스텔색 옷을 입고 있는 걸 보고, 그녀는 페이스트리 모양의 드레스를 입은 채 방 안쪽 어딘가에서 신부 들러리에게 소리치고 있어",
"이건 분명 네가 생각했던 모습이 아닐 거야.",
"나는 내가 서서 '예스라고 말하지 마, 지금 도망쳐 내가 교회 밖 뒷문에서 널 만날게.'라고 말하는 백일몽 속에 빠져들어.",
"기다리지 마, 또는 단 하나의 서약도 말하지 마 넌 내 말을 끝까지 들어야 해 그리고 그들이 말했어, '지금 말하라'.",
"다정한 몸짓들이 오간다.",
"그리고 오르간이 장송 행진곡처럼 들리는 노래를 연주하기 시작해.",
"그리고 나는 커튼 속에 숨어 있어, 네 사랑스러운 예비 신부가 나를 초대하지 않은 것 같아.",
"그녀는 미인 대회 여왕처럼 통로를 떠내려오듯 걸어와.",
"하지만 난 네가 그게 나였으면 한다는 걸 알아 네가 그게 나였으면 한다는 걸, 그렇지?",
"나는 목사가 '지금 말하거나 영원히 침묵하라'고 말하는 걸 들어",
"침묵이 있고, 내 마지막 기회가 있어.",
"나는 떨리는 손으로 일어서고, 모든 시선이 내게 향해",
"방 안의 모두가 경악한 표정이지만 난 오직 너만 바라보고 있어.",
"그리고 너는 '지금 도망치자'라고 말할 거야 내가 턱시도를 벗고 뒷문으로 나가면 널 만날게",
"자기야, 난 내 서약을 말하지 않았어 네가 곁에 있어서 정말 다행이야 그들이 '지금 말하라'고 했을 때",
"나는 말했어, '오 세상에, 정말 멋진 선율이야'",
"그건 최고의 밤이었고, 우리가 어떻게 움직였는지 결코 잊지 못할 거야.",
"온 장소가 한껏 차려입었고 우리는 춤추고 있었어, 별빛으로 만들어진 것처럼 춤추고 있었어",
"나는 45년 여름 보드워크에서 바비를 만났어",
"어느 날 밤 늦게 창문 밖에서 나를 데려갔어 우리는 열일곱이었고 미쳐서 거침없이, 거침없이 달리고 있었어.",
"우리가 들어갔을 때 그가 무슨 노래를 틀고 있었는지 기억나지 않아.",
"우리가 공작부인과 왕자인 척하며 요트 클럽 파티에 몰래 들어갔던 그 밤.",
"그가 말했어, '너 좀 봐, 바꿀 수 없는 것들에 대해 너무 많이 걱정하고 있잖아 계속 그런 식으로 생각하면 평생 블루스를 부르며 살게 될 거야'",
"그는 바다에 물수제비를 뜨려고 하며 내게 말했어 '별빛이 보이지 않니, 별빛 말이야 불가능한 것들을 꿈꾸지 않니'",
"우, 우 그는 말도 안 되는 소리를 해 우, 우 나와 춤을 춰 우, 우 우리는 결혼할 수도 있어 아이 열 명을 낳고 그들에게 꿈꾸는 법을 가르칠 수도 있어",
"네가 움직이는 방식은 완전한 폭풍우 같아.",
"그리고 나는 카드로 만든 집이야",
"넌 나를 도망치게 만들어야 할 만큼 무모한 사람이지만, 어쩐지 난 멀리 못 갈 걸 알아",
"그리고 너는 내 앞에 서 있었어, 손이 닿을 만큼 가까이",
"내가 무슨 생각을 하고 있는지 네가 볼 수 없기를 바랄 만큼 가까이",
"지금 모든 걸 내려놔",
"쏟아지는 비 속에서 나를 만나",
"보도 위에서 내게 키스해",
"고통을 없애 줘",
"왜냐하면 네가 웃을 때마다 불꽃이 튀는 게 보여",
"불빛이 어두워질 때 그 초록 눈으로 나를 사로잡아, 자기야",
"네가 곁에 없을 때도 나를 사로잡을 무언가를 줘",
"내 마음은 네가 나쁜 생각이라는 걸 나에게 상기시키는 걸 잊어",
"네가 한 번 나를 만지면 정말 대단해 넌 내가 네가 상상했던 것보다 훨씬 더 괜찮다는 걸 알게 돼",
"나는 세상 나머지 사람들에게는 경계하지만 너와 함께라면, 소용없다는 걸 알아"
"그리고 나는 인내심 있게 기다릴 수 있지만 정말 네가 그래 줬으면 해"
"나는 손가락으로 네 머리카락을 쓸어 넘기고 불빛들이 요동치는 걸 지켜봐",
"계속 네 눈을 내게 고정해 줘 그건 딱 잘못된 만큼이라 옳게 느껴져",
"그리고 나를 계단 위로 이끌어 줘 부드럽고 느리게 속삭여 주지 않을래, 나는 너에게 사로잡혔어, 자기야 불꽃놀이처럼",
"너와 나는 아슬아슬한 선 위를 걸어왔어 나는 줄곧 알고 있었지만, 그 선이 끊어지는 걸 보게 될 줄은 몰랐어",
"어두워지고 있고 모든 게 너무 조용해 그리고 이제 아무것도 믿을 수 없어 그리고 그게 전부 큰 실수인 것처럼 너에게 밀려오고 있어",
"오, 나는 숨을 참고 있어 다시는 널 잃지 않을 거야",
"무언가가 네 눈을 차갑게 만들었어",
"제발, 제발, 나를 이렇게 떠나지 마 네가 어떤 사람인지 다 안다고 생각했어",
"무언가가 끔찍하게 잘못됐어 너는 내가 원했던 전부야",
"네가 없을 때마다 숨을 쉴 수 없어 이제 되돌릴 수 없어, 나는 haunted",
"난 그냥 알아 넌 떠난 게 아니야, 떠날 수 없어, 아니",
]
먼저 데이터를 벡터 데이터베이스에 넣기 위한 스키마를 정의해야 합니다. 이 예제에서는 두 컬렉션 모두 동일한 스키마를 사용하므로 하나만 만들면 됩니다. 다만 두 개의 컬렉션을 생성해야 한다는 점을 잊지 마세요.
컬렉션이 준비되면 모든 문장을 해당 모델의 임베딩으로 인코딩하고 벡터 인덱스 매개변수를 정의합니다. 거리 메트릭으로는 L2를 사용하고, 네 개의 중심점을 가진 inverted file index를 사용합니다. 결국 엔트리는 51개뿐이니까요. 그런 다음 데이터를 형식에 맞게 정리하고 Milvus에 로드합니다.
# object should be inserted in the format of (title, date, location, speech embedding)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="sentence", dtype=DataType.VARCHAR, max_length=500),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIMENSION),
]
schema = CollectionSchema(fields=fields)
collection_v12 = Collection(name=COLLECTION_V12, schema=schema)
collection_v12_ft5 = Collection(name=COLLECTION_V12_Q, schema=schema)
v12_embeds = {}
v12_q_embeds = {}
for sentence in sentences:
v12_embeds[sentence] = v12.encode(sentence)
v12_q_embeds[sentence] = ft5_v12.encode(sentence)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 4},
}
collection_v12.create_index(field_name="embedding", index_params=index_params)
collection_v12.load()
collection_v12_ft5.create_index(field_name="embedding", index_params=index_params)
collection_v12_ft5.load()
for sentence in sentences:
v12_insert = [
{
"sentence": sentence,
"embedding": v12_embeds[sentence]
}
]
ft_insert = [
{
"sentence": sentence,
"embedding": v12_q_embeds[sentence]
}
]
collection_v12.insert(v12_insert)
collection_v12_ft5.insert(ft_insert)
collection_v12.flush()
collection_v12_ft5.flush()
임베딩을 비교하기 위해 벡터 저장소 쿼리하기
다음으로 비교할 차례입니다. 이 예제에서는 처음 두 문장을 사용합니다. 세 번째 모델을 사용해 이 문장들의 벡터 임베딩을 생성합니다.
search_embeds = {}
search_data = []
for sentence in sentences[0:2]:
vector_embedding = ft3_v12.encode(sentence)
search_embeds[sentence] = vector_embedding
search_data.append(vector_embedding)
이제 Milvus에 쿼리합니다. 여기서는 시간도 함께 추적하고 있습니다. 제가 얻은 검색 시간은 아래에 표시되어 있습니다. 검색 매개변수 아래에 동일한 metric type을 전달해야 합니다.
start1 = time()
res_v12 = collection_v12.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time1 = time() - start1
print(f"Time for first search: {time1}")
start2 = time()
res_v12_ft5 = collection_v12_ft5.search(
data=search_data, # Embeded search value
anns_field="embedding", # Search across embeddings
param={"metric_type": "L2",
"params": {"nprobe": 2}},
limit = 3, # Limit to top_k results per search
output_fields=["sentence"])
time2 = time() - start2
print(f"Time for second search: {time2}")
이제 결과를 살펴보고 비교해 보겠습니다. 원본 모델과 Sprylab이 fine-tuning한 모델의 결과가 놀라울 정도로 비슷하다는 것을 볼 수 있습니다. 유일한 차이는 반환된 첫 번째 결과가 문장 자체라는 점입니다. 이는 이 두 벡터 공간에서 두 번째와 세 번째 결과가 서로에 비해, 두 개의 예시 검색 문장과 더 유사하다는 것을 말해 줍니다.
for i, hits in enumerate(res_v12):
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
다음으로, 두 개의 파인튜닝된 모델을 비교해 보겠습니다. 이 결과를 통해 “I’m on my guard for the rest of the world …“로 시작하는 문장이 두 비교 모두에서 나타나기 때문에 우리의 검색 문장들과 의미적으로 유사하다는 것을 알 수 있습니다. 여기서 흥미로운 두 가지 점은: 1) 첫 번째 쿼리와 다른 결과, 그리고 2) 두 번째 쿼리 문장은 첫 번째의 상위 세 개에 나타나지 않지만, 그 반대는 참이라는 것입니다.
for hit in hits:
print(f"Query sentence: {sentences[i]}")
print(f"Nearest Neighbor Number {i}: {hit.entity.get('sentence')} ---- {hit.distance}\n")
요약
이 튜토리얼에서는 벡터 임베딩과 이를 비교하는 방법에 대해 배웠고, 사용자 지정 데이터로 직접 몇 가지 비교를 수행했습니다. 보너스로, 두 개의 서로 다른 컬렉션을 동시에 작업하는 방법의 예도 보여드립니다. 이것이 서로 다른 잠재 벡터 공간을 쿼리할 수 있는 방법입니다.
모델과 그 모델의 몇 가지 파인튜닝된 버전 간의 차이를 보여드렸습니다. 또한 하나의 결과가 두 임베딩 공간 모두에서 나타나는 것도 확인했습니다. 여러 벡터 표현에서 쿼리 결과가 나타난다는 것은 해당 쿼리가 다양한 방식으로 의미적으로 유사해야 한다는 뜻입니다. 다음 단계로 이미지 모델, 서로 다른 차원의 언어 모델 또는 여러분의 데이터로 이를 시도해 보세요.
계속 읽기

How to Improve Retrieval Quality for Japanese Text with Sudachi, Milvus/Zilliz, and AWS Bedrock
Learn how Sudachi normalization and Milvus/Zilliz hybrid search improve Japanese RAG accuracy with BM25 + vector fusion, AWS Bedrock embeddings, and practical code examples.

Smarter Autoscaling in Zilliz Cloud: Always Optimized for Every Workload
With the latest upgrade, Zilliz Cloud introduces smarter autoscaling—a fully automated, more streamlined, elastic resource management system.

Introducing Zilliz MCP Server: Natural Language Access to Your Vector Database
Developers can easily manage and query vector databases with natural language via Zilliz MCP Server in AI-native environments.



