임베딩 모델은 의미적 유사성을 측정하기 위해 만들어졌지만, 그 측정값들은 많은 편향 요인들의 영향을 받습니다. 이 글에서는 텍스트 임베딩 모델에서 볼 수 있는 하나의 만연한 편향 소스를 살펴보겠습니다: 입력 크기입니다.
더 긴 텍스트의 임베딩은 실제 내용의 유사성과 관계없이 다른 텍스트 임베딩과 비교했을 때 일반적으로 더 높은 유사도 점수를 보입니다. 진정으로 유사한 텍스트들은 여전히 관련 없는 텍스트보다 더 높은 유사도 점수를 가지겠지만, 긴 텍스트는 편향을 도입합니다—단순히 길이 때문에 그들의 임베딩이 평균적으로 더 유사하게 나타나게 됩니다.
이는 실제적인 결과를 초래합니다. 이는 임베딩 모델이 그 자체로는 관련성을 잘 측정할 수 없다는 것을 의미합니다. 임베딩 기반 검색에서는 항상 최상의 매치가 있지만, 크기 편향은 최상의 매치나 그보다 낮은 매치가 실제로 관련이 있는지 판단하기 위해 유사도 점수를 사용할 수 없다는 것을 의미합니다. 예를 들어, 코사인 유사도가 0.75보다 높은 매치는 관련이 있다고 말할 수 없습니다. 완전히 관련이 없는데도 그 수준에서 매치되는 긴 문서가 쉽게 있을 수 있기 때문입니다.
우리는 몇 가지 간단한 예시를 통해 이를 보여주고 텍스트 임베딩 간의 코사인 유사도가 평가를 위한 일반적인 방법이 될 수 없다는 것을 보여드리겠습니다
tag크기 편향 시각화하기
크기 편향이 어떻게 나타나는지 보여주기 위해, 우리는 Jina AI의 최신 임베딩 모델 jina-embeddings-v3를 text-matching
태스크 옵션과 함께 사용할 것입니다. 우리는 또한 널리 사용되는 IR 데이터셋의 텍스트 문서들을 사용할 것입니다: CISI 데이터셋입니다. 이 데이터셋은 Kaggle에서 다운로드할 수 있습니다.

이 데이터셋은 IR 시스템 훈련에 사용되므로 쿼리와 이에 매칭되는 문서들을 모두 포함하고 있습니다. 우리는 CISI.ALL
파일에 있는 문서들만 사용할 것입니다. GitHub의 대체 소스에서 다음 명령어로 다운로드할 수 있습니다:
wget https://raw.githubusercontent.com/GianRomani/CISI-project-MLOps/refs/heads/main/CISI.ALL
CISI는 1,460개의 문서를 포함하고 있습니다. 텍스트의 크기와 크기 분포에 대한 기본 통계는 아래 표와 히스토그램에 요약되어 있습니다:
in Words | in Sentences | |
---|---|---|
Average document size | 119.2 | 4.34 |
Std. Deviation | 63.3 | 2.7 |
Max size | 550 | 38 |
Min size | 8 | 1 |


Python에서 문서들을 읽고 이들의 임베딩을 얻어보겠습니다. 아래 코드는 CISI.ALL
파일이 로컬 디렉토리에 있다고 가정합니다:
with open("CISI.ALL", "r", encoding="utf-8") as inp:
cisi_raw = inp.readlines()
docs = []
current_doc = ""
in_text = False
for line in cisi_raw:
if line.startswith("."):
in_text = False
if current_doc:
docs.append(current_doc.strip())
current_doc = ""
if line.startswith(".W"):
in_text = True
else:
if in_text:
current_doc += line
이렇게 하면 docs
리스트에 1,460개의 문서가 채워집니다. 다음과 같이 확인할 수 있습니다:
print(docs[0])
The present study is a history of the DEWEY Decimal
Classification. The first edition of the DDC was published
in 1876, the eighteenth edition in 1971, and future editions
will continue to appear as needed. In spite of the DDC's
long and healthy life, however, its full story has never
been told. There have been biographies of Dewey
that briefly describe his system, but this is the first
attempt to provide a detailed history of the work that
more than any other has spurred the growth of
librarianship in this country and abroad.
이제 jina-embeddings-v3를 사용하여 각 텍스트의 임베딩을 구성할 것입니다. 이를 위해서는 Jina AI 웹사이트의 API 키가 필요합니다. 최대 100만 토큰의 임베딩까지 무료로 사용할 수 있는 키를 받을 수 있으며, 이는 이 글을 위해 충분합니다.
키를 변수에 넣으세요:
api_key = "<Your Key>"
이제 jina-embeddings-v3로 text-matching
태스크를 사용하여 임베딩을 생성합니다. 이 코드는 docs
의 텍스트를 10개씩 배치로 처리합니다.
import requests
import json
from numpy import array
embeddings = []
url = "https://api.jina.ai/v1/embeddings"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + api_key
}
i = 0
while i < len(docs):
print(f"Got {len(embeddings)}...")
data = {
"model": "jina-embeddings-v3",
"task": "text-matching",
"late_chunking": False,
"dimensions": 1024,
"embedding_type": "float",
"input": docs[i:i+10]
}
response = requests.post(url, headers=headers, data=json.dumps(data))
for emb in response.json()['data']:
embeddings.append(array(emb['embedding']))
i += 10
각 텍스트에 대해 embeddings
리스트에 1024차원의 임베딩이 있을 것입니다. 다음과 같이 확인할 수 있습니다:
print(embeddings[0])
array([ 0.0352382 , -0.00594871, 0.03808545, ..., -0.01147173,
-0.01710563, 0.01109511], shape=(1024,))),
이제 모든 임베딩 쌍 간의 코사인을 계산합니다. 먼저, numpy
를 사용하여 코사인 함수 cos_sim
을 정의합니다:
from numpy import dot
from numpy.linalg import norm
def cos_sim(a, b):
return float((a @ b.T) / (norm(a)*norm(b)))
그런 다음, 1,460개의 임베딩 각각을 다른 1,459개와 비교하여 코사인을 계산합니다:
all_cosines = []
for i, emb1 in enumerate(embeddings):
for j, emb2 in enumerate(embeddings):
if i != j:
all_cosines.append(cos_sim(emb1, emb2))
결과는 2,130,140개의 값 리스트입니다. 이들의 분포는 동일한 언어와 레지스터의 "무작위" 문서들 간의 코사인에 근사해야 합니다. 아래 표와 히스토그램이 결과를 요약합니다.
Number of texts | 1,460 |
---|---|
Number of cosines | 2,130,140 |
Average | 0.343 |
Std. Deviation | 0.116 |

이 문서들은 서로 관련이 없음에도 불구하고 일반적으로 0보다 훨씬 높은 코사인 값을 가집니다. 우리는 0.459(평균 + 1 표준편차)의 임계값을 설정하거나, 혹은 0.5로 반올림하여 그보다 낮은 코사인 값을 가진 문서 쌍은 대체로 관련이 없다고 말하고 싶을 수 있습니다.
하지만 더 작은 텍스트에 대해 같은 실험을 해보겠습니다. nltk
라이브러리를 사용하여 각 문서를 문장으로 나눕니다:
import nltk
sentences = []
for doc in docs:
sentences.extend(nltk.sent_tokenize(doc))
이는 평균 길이 27.5 단어와 표준편차 16.6의 6,331개 문장을 생성합니다. 아래 히스토그램에서 문장의 크기 분포는 빨간색으로, 전체 문서는 파란색으로 표시되어 비교할 수 있습니다.

동일한 모델과 방법을 사용하여 각 문장의 임베딩을 얻을 것입니다:
sentence_embeddings = []
i = 0
while i < len(sentences):
print(f"Got {len(sentence_embeddings)}...")
data = {
"model": "jina-embeddings-v3",
"task": "text-matching",
"late_chunking": False,
"dimensions": 1024,
"embedding_type": "float",
"input": sentences[i:i+10]
}
response = requests.post(url, headers=headers, data=json.dumps(data))
for emb in response.json()['data']:
sentence_embeddings.append(array(emb['embedding']))
i += 10
그리고 각 문장의 임베딩과 다른 문장의 임베딩 사이의 코사인을 구합니다:
sent_cosines = []
for i, emb1 in enumerate(sentence_embeddings):
for j, emb2 in enumerate(sentence_embeddings):
if i != j:
sent_cosines.append(cos_sim(emb1, emb2))
결과적으로 더 많은 코사인 값이 나왔습니다: 40,075,230개로, 아래 표에 요약되어 있습니다:
Number of sentences | 6,331 |
---|---|
Number of cosines | 40,075,230 |
Average | 0.254 |
Std. Deviation | 0.116 |
문장 대 문장의 코사인은 전체 문서 대 문서의 코사인보다 평균적으로 상당히 낮습니다. 아래 히스토그램은 그들의 분포를 비교하고 있으며, 문장 쌍들이 문서 쌍들과 거의 동일한 분포를 형성하지만 왼쪽으로 이동된 것을 쉽게 볼 수 있습니다.

이 크기 의존성이 견고한지 테스트하기 위해, 문장과 문서 사이의 모든 코사인을 구해서 히스토그램에 추가해 보겠습니다. 그 정보는 아래 표에 요약되어 있습니다:
Number of texts | 6,331 sentences & 1,460 documents |
---|---|
Number of cosines | 9,243,260 |
Average | 0.276 |
Std. Deviation | 0.119 |
아래 녹색 선은 문장 대 문서 코사인의 분포입니다. 이 분포가 문서 대 문서 코사인과 문장 대 문장 코사인 사이에 깔끔하게 맞아들어가는 것을 볼 수 있으며, 이는 크기 효과가 비교되는 두 텍스트의 더 크고 작은 것 모두와 관련이 있음을 보여줍니다.

문서들을 10개씩 그룹으로 연결하여 146개의 훨씬 더 큰 문서를 만들고 그들의 코사인을 측정하는 또 다른 테스트를 해보겠습니다. 결과는 아래에 요약되어 있습니다:
Number of texts | 146 documents |
---|---|
Number of cosines | 21,170 |
Average | 0.658 |
Std. Deviation | 0.09 |

이는 다른 분포들보다 훨씬 오른쪽에 있습니다. 0.5의 코사인 임계값은 이러한 문서들이 거의 모두 서로 관련이 있다고 말해줄 것입니다. 이 크기의 관련 없는 문서들을 제외하려면, 임계값을 훨씬 더 높게, 아마도 0.9까지 설정해야 할 것이며, 이는 의심할 여지 없이 더 작은 문서들 간의 좋은 매치를 제외할 것입니다.
이는 문서 크기를 어떻게든 고려하지 않고서는 최소 코사인 임계값을 매치가 얼마나 좋은지 추정하는 데 전혀 사용할 수 없다는 것을 보여줍니다.
tag크기 편향의 원인은 무엇인가?
임베딩의 크기 편향은 긴 컨텍스트 모델의 위치 편향과는 다릅니다. 이는 아키텍처에 의해 발생하지 않습니다. 본질적으로 크기에 관한 것도 아닙니다. 예를 들어, 같은 문서를 계속 반복해서 연결하여 더 긴 문서를 만들었다면, 크기 편향을 보이지 않았을 것입니다.
문제는 긴 텍스트가 더 많은 것을 말한다는 점입니다. 주제와 목적에 의해 제한되더라도, 더 많은 단어를 쓰는 근본적인 목적은 더 많은 것을 말하기 위해서입니다.
더 긴 텍스트는, 적어도 사람들이 일반적으로 만드는 종류의 텍스트는, 자연스럽게 더 많은 의미 공간에 "퍼지는" 임베딩을 생성할 것입니다. 텍스트가 더 많은 것을 말한다면, 그 임베딩은 텍스트의 주제와 독립적으로 다른 벡터들과 평균적으로 더 작은 각도를 가질 것입니다.
tag관련성 측정
이 포스트의 교훈은 의미론적 벡터들 간의 코사인을 단독으로 사용하여 무언가가 좋은 매치인지 판단할 수 없다는 것입니다. 단지 사용 가능한 것들 중에서 가장 좋은 매치라는 것만 알 수 있습니다. 최상의 매치의 유용성과 타당성을 확인하기 위해서는 코사인을 계산하는 것 외에 다른 작업을 해야 합니다.
정규화를 시도해볼 수 있습니다. 크기 편향을 경험적으로 측정할 수 있다면, 이를 상쇄하는 것이 가능할 수 있습니다. 하지만 이 접근 방식은 매우 견고하지 않을 수 있습니다. 한 데이터셋에서 작동하는 것이 다른 데이터셋에서는 작동하지 않을 수 있습니다.
jina-embeddings-v3에서 제공하는 비대칭 쿼리-문서 인코딩은 임베딩 모델의 크기 편향을 줄이지만 완전히 제거하지는 않습니다. 비대칭 인코딩의 목적은 문서를 덜 "퍼지게" 인코딩하고 쿼리를 더 "퍼지게" 인코딩하는 것입니다.
아래 히스토그램의 빨간 선은 jina-embeddings-v3를 사용한 비대칭 인코딩으로 문서 대 문서 코사인의 분포를 나타냅니다 - 각 문서는 retrieval.query
와 retrieval.passage
플래그를 사용하여 인코딩되며, 모든 문서 쿼리 임베딩은 같은 문서가 아닌 모든 문서 패시지 임베딩과 비교됩니다. 평균 코사인은 0.200이고 표준편차는 0.124입니다.
이러한 코사인들은 위에서 같은 문서들에 대해 text-matching
플래그를 사용하여 찾은 것들보다 상당히 작으며, 아래 히스토그램에서 보여줍니다.

하지만 비대칭 인코딩이 크기 편향을 제거하지는 못했습니다. 아래 히스토그램은 비대칭 인코딩을 사용한 전체 문서와 문장의 코사인을 비교합니다.

문장 코사인의 평균은 0.124로, 비대칭 인코딩을 사용할 때 평균 문장 코사인과 평균 문서 코사인의 차이는 0.076입니다. 대칭 인코딩의 평균 차이는 0.089입니다. 크기 편향의 변화는 미미합니다.
비대칭 인코딩이 정보 검색을 위한 임베딩을 개선하지만, 매치의 관련성을 측정하는 데는 더 나아지지 않았습니다.
tag미래의 가능성
리랭커 접근 방식, 예를 들어 jina-reranker-v2-base-multilingual와 jina-reranker-m0는 쿼리-문서 매치를 점수화하는 대안적인 방법이며, 우리는 이미 이것이 쿼리 정확도를 향상시킨다는 것을 알고 있습니다. 리랭커 점수는 정규화되지 않아서 객관적인 유사도 측정으로도 작동하지 않습니다. 하지만 이들은 다르게 계산되며, 리랭커 점수를 관련성의 좋은 추정치로 만드는 방식으로 정규화하는 것이 가능할 수 있습니다.
다른 대안은 강력한 추론 능력을 가진 대규모 언어 모델을 사용하여 후보가 쿼리에 좋은 매치인지 직접 평가하는 것입니다. 단순하게는, 작업에 특화된 대규모 언어 모델에게 "1부터 10까지의 척도에서, 이 문서가 이 쿼리에 좋은 매치인가요?"라고 물어볼 수 있습니다. 기존 모델들이 이 작업에 잘 맞지 않을 수 있지만, 집중적인 훈련과 더 정교한 프롬프팅 기술은 유망합니다.
모델이 관련성을 측정하는 것이 불가능한 것은 아니지만, 임베딩 모델과는 다른 패러다임이 필요합니다.
tag모델을 그것이 잘하는 것에 사용하라
위에서 문서화한 크기 편향 효과는 임베딩 모델의 근본적인 한계 중 하나를 보여줍니다: 그들은 사물을 비교하는 데는 뛰어나지만 절대적 관련성을 측정하는 데는 신뢰할 수 없습니다. 이 한계는 설계의 결함이 아닙니다—그것은 이러한 모델들이 작동하는 방식의 본질적인 특성입니다.
그렇다면 이것이 여러분에게 의미하는 바는 무엇일까요?
첫째, 코사인 임계값에 대해 회의적이어야 합니다. 그것들은 그저 작동하지 않습니다. 코사인 유사도 측정은 유혹적으로 객관적으로 보이는 부동소수점 숫자를 출력합니다. 하지만 무언가가 숫자를 출력한다고 해서 그것이 객관적으로 무언가를 측정하고 있다는 의미는 아닙니다.
둘째, 하이브리드 솔루션을 고려하세요. 임베딩은 큰 항목 집합을 유망한 후보로 효율적으로 좁힐 수 있으며, 그 후에 리랭커나 LLM, 또는 심지어 인간 평가자와 같은 더 정교한(그리고 계산적으로 집약적인) 기술을 적용하여 실제 관련성을 결정할 수 있습니다.
셋째, 시스템을 설계할 때 능력보다는 작업 측면에서 생각하세요. 벤치마크에서 객관적으로 가장 스마트하고 높은 점수를 받는 모델이라도 그것을 구매한 목적을 수행할 수 없다면 돈 낭비입니다.
우리 모델의 한계를 이해하는 것은 비관적인 것이 아닙니다 – 이는 응용에서의 더 넓은 원칙을 반영합니다: 모델이 잘하는 것과 그렇지 않은 것을 이해하는 것은 신뢰할 수 있고 효과적인 시스템을 구축하는 데 중요합니다. 나사를 조이는 데 망치를 사용하지 않는 것처럼, 임베딩 모델을 그들이 처리할 수 없는 작업에 사용해서는 안 됩니다. 여러분의 도구가 잘하는 것을 존중하세요.