語義相似性是嵌入模型設計用來衡量的目標,但這些測量會受到許多偏差因素的影響。在本文中,我們將探討文本嵌入模型中一個普遍存在的偏差來源:輸入文本的長度。
較長文本的嵌入向量在與其他文本嵌入比較時,通常會顯示出較高的相似度分數,無論實際內容的相似程度如何。雖然真正相似的文本仍會有較高的相似度分數,但較長的文本會引入偏差—僅僅因為其長度而使其嵌入看起來平均更相似。
這會產生實際影響。這意味著嵌入模型本身並不能很好地衡量相關性。在基於嵌入的搜索中,總會有最佳匹配,但由於長度偏差,你無法使用相似度分數來判斷最佳匹配或任何次佳匹配是否真正相關。你不能說,例如,任何 cosine 相似度高於 0.75 的匹配都是相關的,因為可能會有一個長文檔在完全不相關的情況下也達到這個匹配程度。
我們將通過一些簡單的例子來演示這一點,並向您展示為什麼文本嵌入之間的 cosine 相似度不能作為評估的通用方法
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 個文檔。下面的表格和直方圖總結了文本大小及其大小分佈的基本統計資料:
| 以字詞計 | 以句子計 | |
|---|---|---|
| 平均文檔大小 | 119.2 | 4.34 |
| 標準差 | 63.3 | 2.7 |
| 最大大小 | 550 | 38 |
| 最小大小 | 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 萬個 token 的嵌入,這對於本文來說已經足夠了。
將您的密鑰放入變數中:
api_key = "<Your Key>"
現在,使用 jina-embeddings-v3 的 text-matching 任務生成嵌入。此代碼以 10 個為一批處理 docs 中的文本。
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,))),
現在,我們計算所有嵌入對之間的 cosine 相似度。首先,讓我們使用 numpy 定義 cosine 函數 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 個的 cosine 相似度:
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 個值的列表。它們的分佈應該近似於同一語言和語域中「隨機」文檔之間的 cosine 相似度。下面的表格和直方圖總結了結果。
| 文本數量 | 1,460 |
|---|---|
| cosine 數量 | 2,130,140 |
| 平均值 | 0.343 |
| 標準差 | 0.116 |

這些文檔,即使彼此不相關,通常也有遠高於零的 cosine 相似度。我們可能會傾向於設定一個閾值 0.459(平均值加上 1 個標準差),或者將其四捨五入到 0.5,並說任何 cosine 小於該值的文檔對一定基本上是不相關的。
但讓我們在較小的文本上做相同的實驗。我們將使用 nltk 庫將每個文檔分解成句子:
import nltk
sentences = []
for doc in docs:
sentences.extend(nltk.sent_tokenize(doc))
這產生了 6,331 個句子,平均長度為 27.5 個單詞,標準差為 16.6。在下面的直方圖中,句子的大小分佈以紅色顯示,完整文檔的大小分佈以藍色顯示,方便您比較。

我們將使用相同的模型和方法來獲取每個句子的嵌入:
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
然後計算每個句子的 embedding 與其他句子之間的餘弦相似度:
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 |
下圖中的綠線是句子與文件之間餘弦值的分布。我們可以看到這個分布恰好落在文件間餘弦值和句子間餘弦值之間,表明大小效應同時涉及被比較的兩個文本的大小。

讓我們通過將每十個文件連接在一起進行另一個測試,創建 146 個更大的文件並測量它們的餘弦值。結果總結如下:
| Number of texts | 146 documents |
|---|---|
| Number of cosines | 21,170 |
| Average | 0.658 |
| Std. Deviation | 0.09 |

這個分布遠遠在其他分布的右側。0.5 的餘弦閾值會告訴我們幾乎所有這些文件都是相關的。要排除這種大小的不相關文件,我們需要將閾值設置得更高,可能要高達 0.9,這無疑會排除較小文件之間的良好匹配。
這表明我們完全不能使用最小餘弦閾值來評估匹配的好壞,至少在不考慮文件大小的情況下不行。
tag是什麼導致了大小偏差?
Embedding 中的大小偏差與長上下文模型中的位置偏差不同。它不是由架構造成的。它本質上也不是關於大小的。例如,如果我們通過不斷重複同一文件來創建更長的文件,就不會出現大小偏差。
問題在於長文本說更多的事情。即使它們受到主題和目的的限制,寫更多的字的本質就是要說更多的內容。
較長的文本,至少是人們通常創建的那種,自然會產生在語義空間中更"分散"的 embedding。如果一個文本說更多的事情,無論文本的主題是什麼,其 embedding 與其他向量的平均角度都會更小。
tag衡量相關性
這篇文章的教訓是,你不能單純使用語義向量之間的餘弦值來判斷某個內容是否是好的匹配,只能判斷它是可用選項中最好的匹配。除了計算餘弦值之外,你還必須做其他事情來檢查最佳匹配的實用性和有效性。
你可以嘗試歸一化。如果你能經驗地測量大小偏差,可能可以抵消它。然而,這種方法可能不太穩健。適用於一個數據集的方法可能不適用於另一個數據集。
jina-embeddings-v3 提供的非對稱查詢-文件編碼減少了 embedding 模型中的大小偏差,但並沒有消除它。非對稱編碼的目的是讓文件的編碼不那麼"分散",而讓查詢的編碼更分散。
下面直方圖中的紅線是使用 jina-embeddings-v3 的非對稱編碼的文件間餘弦值分布 – 每個文件都使用 retrieval.query 和 retrieval.passage 標誌進行編碼,每個文件查詢 embedding 都與不是來自同一文件的每個文件段落 embedding 進行比較。平均餘弦值為 0.200,標準差為 0.124。
這些餘弦值比我們上面使用 text-matching 標誌找到的相同文件的餘弦值小得多,如下面的直方圖所示。

然而,非對稱編碼並沒有消除大小偏差。下面的直方圖比較了使用非對稱編碼的完整文件和句子的餘弦值。

句子餘弦值的平均值為 0.124,所以使用非對稱編碼時,句子平均餘弦值和文件平均餘弦值之間的差異為 0.076。對稱編碼的平均值差異為 0.089。大小偏差的變化並不顯著。
雖然非對稱編碼改進了信息檢索的 embedding,但在衡量匹配的相關性方面並沒有更好。
tag未來的可能性
重排序器方法,例如 jina-reranker-v2-base-multilingual 和 jina-reranker-m0,是對查詢-文件匹配進行評分的另一種方式,我們已經知道它改進了查詢精度。重排序器分數沒有歸一化,所以它們也不能作為客觀的相似度度量。然而,它們的計算方式不同,可能可以通過某種方式將重排序器分數歸一化,使其成為良好的相關性估計器。
另一種選擇是使用大型語言模型,最好是具有強大推理能力的模型,直接評估候選項是否與查詢匹配良好。簡單來說,我們可以問一個特定任務的大型語言模型:"在 1 到 10 的尺度上,這個文件與這個查詢的匹配程度如何?"現有的模型可能不太適合這個任務,但針對性的訓練和更複雜的提示技術是有希望的。
模型測量相關性並非不可能,但它需要與 embedding 模型不同的範式。
tag讓模型做它擅長的事
我們上面記錄的大小偏差效應顯示了 embedding 模型的一個基本限制:它們在比較事物方面表現出色,但在測量絕對相關性方面不可靠。這個限制不是設計缺陷—它是這些模型工作方式的固有特徵。
那麼這對你意味著什麼?
首先,對餘弦閾值持懷疑態度。它們就是不起作用。餘弦相似度度量產生的浮點數看起來很客觀。但是僅僅因為某個東西輸出數字並不意味著它在客觀地測量某些東西。
第二,考慮混合解決方案。Embedding 可以有效地將大量項目縮小到有希望的候選項,之後你可以應用更複雜(且計算密集)的技術,如重排序器或 LLM,甚至人工評估員來確定實際相關性。
第三,在設計系統時,要從任務而不是能力的角度思考。在基準測試中得分最高、客觀上最聰明的模型,如果不能完成你購買它的工作,仍然是浪費錢。
理解我們模型的限制並不是悲觀的態度 – 它反映了應用中的一個更廣泛的原則:理解你的模型擅長什麼,不擅長什麼,對於構建可靠和有效的系統至關重要。就像我們不會用錘子擰螺絲一樣,我們也不應該讓 embedding 模型處理它們無法處理的任務。尊重你的工具所擅長的領域。







