在實施 AI 流程時,理解 AI 模型存在著許多障礙,其中一些是相當大的障礙。但很多人首先遇到的障礙是理解我們在談論 tokens 時的含義。

在選擇 AI 語言模型時,最重要的實用參數之一是其上下文視窗的大小 — 即最大輸入文本大小 — 這是以 tokens 為單位計算的,而不是以字、字元或其他自動識別的單位計算。
此外,嵌入服務通常是按"每個 token"計費的,這意味著要理解你的帳單,tokens 是很重要的。
如果你不清楚 token 是什麼,這可能會令人感到非常困惑。

但在現代 AI 的所有令人困惑的方面中,tokens 可能是最不複雜的。本文將嘗試闡明什麼是分詞(tokenization),它做什麼,以及為什麼我們要這樣做。
tag內容摘要
對於那些想要或需要快速了解從 Jina Embeddings 購買多少 tokens 或估算需要購買多少的人來說,以下統計數據就是你所需要的。
tag每個英語單詞的 Tokens
在本文後面描述的實證測試中,使用 Jina Embeddings 英語專用模型時,各種英語文本轉換成 tokens 的比率大約比單詞多 10%。這個結果相當穩定。
Jina Embeddings v2 模型的上下文視窗為 8192 個 tokens。這意味著如果你傳入一個超過 7,400 個單詞的英語文本,很可能會被截斷。
tag每個中文字元的 Tokens
對於中文來說,結果更加變化多端。根據文本類型的不同,每個漢字的 tokens 比率從 0.6 到 0.75 不等。當給中文版 Jina Embeddings v2 輸入英文文本時,產生的 tokens 數量與英語版 Jina Embeddings v2 大致相同:大約比單詞數量多 10%。
tag每個德語單詞的 Tokens
德語的單詞到 tokens 的比率比英語更加變化多端,但比中文少。根據文本類型的不同,平均比單詞多 20% 到 30% 的 tokens。當給德英文版 Jina Embeddings v2 輸入英文文本時,使用的 tokens 比英語專用和中英文模型略多:比單詞多 12% 到 15% 的 tokens。
tag注意事項
這些是簡單的計算,但對於大多數自然語言文本和大多數用戶來說應該大致正確。最終,我們只能保證 tokens 的數量永遠不會超過你文本中的字元數加二。實際上它總是會比這少得多,但我們不能預先承諾任何具體的數量。
這些是基於統計學上簡單計算的估計。我們不保證任何特定請求會使用多少 tokens。
如果你只是需要關於購買多少 Jina Embeddings tokens 的建議,可以在這裡停止閱讀。其他公司的嵌入模型可能與 Jina 模型的單詞到 token 和漢字到 token 的比率不同,但總體上差異不會太大。
如果你想了解原因,本文的其餘部分將深入探討語言模型的分詞。
tag單詞、Tokens、數字
分詞在自然語言處理中的應用歷史要早於現代 AI 模型的存在。
說電腦中的一切都只是數字可能有點老生常談,但這基本上是事實。然而,語言本質上不只是一堆數字。它可能是由聲波組成的語音,或是紙上的標記,甚至是印刷文本的圖像或手語視頻。但是,當我們談論使用電腦處理自然語言時,我們通常指的是由字元序列組成的文本:字母(a、b、c 等)、數字(0、1、2…)、標點符號和空格,使用不同的語言和文本編碼。
電腦工程師稱這些為"字串"。
AI 語言模型以數字序列作為輸入。所以,你可能寫下這句話:
What is today's weather in Berlin?
但是,經過分詞後,AI 模型得到的輸入是:
[101, 2054, 2003, 2651, 1005, 1055, 4633, 1999, 4068, 1029, 102]
分詞是將輸入字串轉換為 AI 模型可以理解的特定數字序列的過程。
當你通過按 token 收費的網路 API 使用 AI 模型時,每個請求都會被轉換成像上面那樣的數字序列。請求中的 tokens 數量就是該數字序列的長度。因此,要求英語版 Jina Embeddings v2 為 "What is today's weather in Berlin?" 生成嵌入將花費你 11 個 tokens,因為它在將該句子傳遞給 AI 模型之前將其轉換為了 11 個數字的序列。
基於 Transformer 架構的 AI 模型具有固定大小的上下文視窗,其大小以 tokens 為單位計量。有時這被稱為"輸入視窗"、"上下文大小"或"序列長度"(特別是在 Hugging Face MTEB 排行榜上)。這意味著模型一次可以看到的最大文本大小。
所以,如果你想使用嵌入模型,這就是允許的最大輸入大小。
Jina Embeddings v2 模型都有 8,192 tokens 的上下文視窗。其他模型會有不同(通常更小)的上下文視窗。這意味著無論你輸入多少文本,與該 Jina Embeddings 模型相關聯的分詞器必須將其轉換為不超過 8,192 個 tokens。
tag將語言映射到數字
解釋 tokens 邏輯最簡單的方式是:
對於自然語言模型來說,token 所代表的字串部分可以是一個詞、詞的一部分或標點符號。空格在分詞器輸出中通常不會有任何明確的表示。
分詞是自然語言處理中一組稱為文本分段技術的一部分,執行分詞的模組很邏輯地被稱為分詞器。
為了展示分詞是如何工作的,我們將使用最小的英語版 Jina Embeddings v2 模型:jina-embeddings-v2-small-en
。Jina Embeddings 的其他英語專用模型 — jina-embeddings-v2-base-en — 使用相同的分詞器,所以沒必要下載我們在本文中不會使用的額外 AI 模型。
首先,在你的 Python 環境或筆記本中安裝 transformers
模組。使用-U
標誌來確保升級到最新版本,因為此模型無法與某些舊版本相容:
pip install -U transformers
然後,使用 AutoModel.from_pretrained
下載 jina-embeddings-v2-small-en
:
from transformers import AutoModel
model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-small-en', trust_remote_code=True)
要標記化字串,請使用模型的 tokenizer
成員物件的 encode
方法:
model.tokenizer.encode("What is today's weather in Berlin?")
結果是一個數字列表:
[101, 2054, 2003, 2651, 1005, 1055, 4633, 1999, 4068, 1029, 102]
要將這些數字轉換回字串形式,使用 tokenizer
物件的 convert_ids_to_tokens
方法:
model.tokenizer.convert_ids_to_tokens([101, 2054, 2003, 2651, 1005, 1055, 4633, 1999, 4068, 1029, 102])
結果是一個字串列表:
['[CLS]', 'what', 'is', 'today', "'", 's', 'weather', 'in',
'berlin', '?', '[SEP]']
請注意,模型的分詞器具有以下特點:
- 在開頭添加
[CLS]
,在結尾添加[SEP]
。這是出於技術原因所必需的,意味著**每個嵌入請求都會多消耗兩個標記**,超出文本本身所需的標記數量。 - 將標點符號從單詞中分開,把「Berlin?」分成:
berlin
和?
,把「today's」分成today
、'
和s
。 - 將所有內容轉為小寫。並非所有模型都這樣做,但這在使用英語時有助於訓練。在大小寫具有不同含義的語言中,這可能就不那麼有幫助。
不同程式中的不同字數計算演算法可能會對這個句子的字數有不同統計。OpenOffice 將其計為六個字。Unicode 文本分段演算法(Unicode Standard Annex #29)計為七個字。其他軟體可能會得出不同的數字,這取決於它們如何處理標點符號和像「's」這樣的附著語。
這個模型的分詞器為這六或七個字產生了九個標記,再加上每個請求所需的兩個額外標記。
現在,讓我們試試一個不如柏林常見的地名:
token_ids = model.tokenizer.encode("I live in Kinshasa.")
tokens = model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
結果:
['[CLS]', 'i', 'live', 'in', 'kin', '##sha', '##sa', '.', '[SEP]']
「Kinshasa」這個名字被分成三個標記:kin
、##sha
和 ##sa
。##
表示這個標記不是單詞的開頭。
如果我們給分詞器一些完全陌生的內容,標記數量相對於字數的比例會更高:
token_ids = model.tokenizer.encode("Klaatu barada nikto")
tokens = model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
['[CLS]', 'k', '##la', '##at', '##u', 'bar', '##ada', 'nik', '##to', '[SEP]']
三個字被分成八個標記,再加上 [CLS]
和 [SEP]
標記。
德語的分詞情況類似。使用 Jina Embeddings v2 for German 模型,我們可以用與英語模型相同的方式來標記化「What is today's weather in Berlin?」的德語翻譯。
german_model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-base-de', trust_remote_code=True)
token_ids = german_model.tokenizer.encode("Wie wird das Wetter heute in Berlin?")
tokens = german_model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
結果:
['<s>', 'Wie', 'wird', 'das', 'Wetter', 'heute', 'in', 'Berlin', '?', '</s>']
這個分詞器與英語的有些不同,它使用 <s>
和 </s>
替代了 [CLS]
和 [SEP]
,但功能相同。此外,文本沒有進行大小寫標準化——大小寫保持原樣——因為在德語中,大小寫的意義與英語不同。
(為了簡化這個演示,我移除了一個表示單詞開頭的特殊字符。)
現在,讓我們試試一個來自報紙文章的更複雜句子:
Ein Großteil der milliardenschweren Bauern-Subventionen bleibt liegen – zu genervt sind die Landwirte von bürokratischen Gängelungen und Regelwahn.
sentence = """
Ein Großteil der milliardenschweren Bauern-Subventionen
bleibt liegen – zu genervt sind die Landwirte von
bürokratischen Gängelungen und Regelwahn.
"""
token_ids = german_model.tokenizer.encode(sentence)
tokens = german_model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
標記化結果:
['<s>', 'Ein', 'Großteil', 'der', 'mill', 'iarden', 'schwer',
'en', 'Bauern', '-', 'Sub', 'ventionen', 'bleibt', 'liegen',
'–', 'zu', 'gen', 'ervt', 'sind', 'die', 'Landwirte', 'von',
'büro', 'krat', 'ischen', 'Gän', 'gel', 'ungen', 'und', 'Regel',
'wahn', '.', '</s>']
在這裡,你可以看到許多德語單詞被分成更小的片段,而且不一定按照德語語法允許的方式分割。結果是,在字數計數器中只算作一個字的長德語單詞,在 Jina 的 AI 模型中可能會被分成任意數量的標記。
讓我們用中文做同樣的事,將「What is today's weather in Berlin?」翻譯成:
柏林今天的天气怎么样?
chinese_model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-base-zh', trust_remote_code=True)
token_ids = chinese_model.tokenizer.encode("柏林今天的天气怎么样?")
tokens = chinese_model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
標記化結果:
['<s>', '柏林', '今天的', '天气', '怎么样', '?', '</s>']
在中文中,書面文本通常沒有詞間空格,但 Jina Embeddings 分詞器經常將多個中文字符組合在一起:
Token string | Pinyin | Meaning |
---|---|---|
柏林 | Bólín | Berlin |
今天的 | jīntiān de | today's |
天气 | tiānqì | weather |
怎么样 | zěnmeyàng | how |
讓我們使用一個來自香港報紙的更複雜句子:
sentence = """
新規定執行首日,記者在下班高峰前的下午5時來到廣州地鐵3號線,
從繁忙的珠江新城站啟程,向機場北方向出發。
"""
token_ids = chinese_model.tokenizer.encode(sentence)
tokens = chinese_model.tokenizer.convert_ids_to_tokens(token_ids)
print(tokens)
(翻譯:"在新規定實施的第一天,本記者在下午 5 點放工高峰期抵達廣州地鐵 3 號線,從珠江新城站出發,向機場北方向前進。")
結果:
['<s>', '新', '規定', '執行', '首', '日', ',', '記者', '在下', '班',
'高峰', '前的', '下午', '5', '時', '來到', '廣州', '地', '鐵', '3',
'號', '線', ',', '從', '繁忙', '的', '珠江', '新城', '站', '啟',
'程', ',', '向', '機場', '北', '方向', '出發', '。', '</s>']
這些 token 並不對應任何特定的中文詞典。例如,"啟程" 通常會被視為一個單詞,但在這裡被分成兩個字元。同樣地,"在下班" 通常會被認為是兩個詞,其分界點應該在 "在" 和 "下班" 之間,而不是像 tokenizer 所做的那樣在 "在下" 和 "班" 之間分開。
在所有三種語言中,tokenizer 斷詞的位置與人類讀者認知的邏輯斷句位置並不直接相關。
這並非 Jina Embeddings 模型的特殊功能。這種分詞方法在 AI 模型開發中幾乎是通用的。雖然不同的 AI 模型可能沒有完全相同的 tokenizer,但在目前的發展階段,它們實際上都會使用具有這種行為特徵的 tokenizer。
下一節將討論 tokenization 所使用的具體算法及其背後的邏輯。
tag為什麼我們要進行分詞?為什麼要用這種方式?
AI 語言模型接受代表文本序列的數字序列作為輸入,但在運行底層神經網路和創建嵌入之前還需要進行一些處理。當面對代表小文本序列的數字列表時,模型會在其內部字典中查找每個數字,這個字典為每個數字儲存了一個獨特的向量。然後它將這些向量組合起來,作為神經網路的輸入。
這意味著 tokenizer 必須能夠將任何我們給它的輸入文本轉換成出現在模型的 token 向量字典中的 token。如果我們使用傳統詞典中的 token,當我們第一次遇到拼寫錯誤、罕見的專有名詞或外語單詞時,整個模型就會停止運作。它無法處理那個輸入。
在自然語言處理中,這被稱為詞彙表外(OOV)問題,它在所有文本類型和所有語言中都普遍存在。解決 OOV 問題有幾種策略:
- 忽略它。用 "未知" token 替代所有不在詞典中的內容。
- 繞過它。不使用將文本序列映射到向量的詞典,而是使用將單個字符映射到向量的詞典。英語大多數時候只使用 26 個字母,所以這比任何詞典都要小得多,且對 OOV 問題更具魯棒性。
- 在文本中尋找頻繁出現的子序列,將它們放入詞典,剩下的內容使用字符(單字母 token)。
第一種策略意味著會失去很多重要信息。如果數據採取的形式不在詞典中,模型甚至無法從所見的數據中學習。即使是最大的詞典中也有許多普通文本中的內容未被收錄。
第二種策略是可行的,研究人員也對此進行過探究。然而,這意味著模型必須接受更多輸入並學習更多內容。這需要更大的模型和更多的訓練數據,而結果從未被證明比第三種策略更好。
AI 語言模型幾乎都以某種形式實現第三種策略。大多數使用 Wordpiece 算法 [Schuster 和 Nakajima 2012] 的某種變體或稱為 字節對編碼(BPE)的類似技術。[Gage 1994,Senrich 等人 2016] 這些算法是與語言無關的。這意味著它們對所有書面語言的工作方式都相同,除了可能字符列表外不需要其他知識。它們是為像 Google 的 BERT 這樣的多語言模型設計的,這些模型可以接受從互聯網抓取的任何輸入——數百種語言和電腦程序等非人類語言的文本——因此可以在不進行複雜語言學處理的情況下進行訓練。
一些研究表明,使用更具語言特異性和語言感知的 tokenizer 可以帶來顯著改進。[Rust 等人 2021] 但以這種方式構建 tokenizer 需要時間、金錢和專業知識。實施像 BPE 或 Wordpiece 這樣的通用策略要便宜和容易得多。
然而,結果是,除非將文本運行通過 tokenizer 然後計算輸出的 token 數量,否則無法知道特定文本代表多少個 token。因為文本最小可能的子序列是一個字母,所以你可以確定 token 數量不會大於字符數(減去空格)再加二。
要獲得一個好的估計,我們需要向 tokenizer 輸入大量文本,並根據經驗計算我們獲得的 token 數量與輸入的單詞或字符數量的平均比較。在下一節中,我們將對目前可用的所有 Jina Embeddings v2 模型進行一些不太系統的經驗測量。
tagtoken 輸出大小的經驗估計
對於英語和德語,我使用 Unicode 文本分割算法(Unicode 標準附件 #29)來獲取文本的詞數。當你雙擊某個內容時,這個算法被廣泛用於選擇文本片段。這是最接近通用客觀詞計數器的工具。
我在 Python 中安裝了實現這個文本分割器的 polyglot 庫:
pip install -U polyglot
要獲取文本的詞數,你可以使用這樣的程式碼片段:
from polyglot.text import Text
txt = "What is today's weather in Berlin?"
print(len(Text(txt).words))
結果應該是 7
。
要獲取 token 數量,將文本片段傳遞給各種 Jina Embeddings 模型的 tokenizer,如下所述,每次我都從返回的 token 數量中減去二。
tag英語
(jina-embeddings-v2-small-en
和 jina-embeddings-v2-base-en)
為了計算平均值,我從 Wortschatz Leipzig 下載了兩個英語文本語料庫,這是萊比錫大學主辦的多種語言和配置的免費可下載語料庫集合:
- 2020 年的一百萬句英語新聞數據語料庫(
eng_news_2020_1M
) - 2016 年的一百萬句 英語維基百科 數據語料庫(
eng_wikipedia_2016_1M
)
這兩個都可以在他們的英語下載頁面找到。
為了增加多樣性,我還從 Project Gutenberg 下載了 Hapgood 翻譯的維克多·雨果的《悲慘世界》,以及1611 年翻譯的英皇欽定本聖經。
對於所有四個文本,我使用 polyglot
中實現的 Unicode 分割器計算詞數,然後計算 jina-embeddings-v2-small-en
產生的 token,每次分詞請求減去兩個 token。結果如下:
文本 | 詞數 (Unicode 分割器) | Token 數 (Jina Embeddings v2 英語版) | Token 與詞的比率 (取至小數點後三位) |
---|---|---|---|
eng_news_2020_1M | 22,825,712 | 25,270,581 | 1.107 |
eng_wikipedia_2016_1M | 24,243,607 | 26,813,877 | 1.106 |
les_miserables_en | 688,911 | 764,121 | 1.109 |
kjv_bible | 1,007,651 | 1,099,335 | 1.091 |
使用精確數字並不表示這是一個精確的結果。具有如此不同體裁的文件會產生比字數多 9% 到 11% 的字符數,這表明您可能會得到比字數多約 10% 的字符數(按 Unicode 分詞器計算)。文字處理器通常不會計算標點符號,而 Unicode 分詞器會計算,所以您不能期望辦公軟體的字數計算與此相符。
tag德語
(jina-embeddings-v2-base-de)
對於德語,我從 Wortschatz Leipzig 的德語頁面下載了三個語料庫:
deu_mixed-typical_2011_1M
— 來自 2011 年不同體裁文本的均衡混合的一百萬個句子。deu_newscrawl-public_2019_1M
— 來自 2019 年的一百萬個新聞文本句子。deu_wikipedia_2021_1M
— 2021 年從德語維基百科中提取的一百萬個句子。
為了增加多樣性,我還從 德語文本檔案庫下載了卡爾・馬克思的《資本論》三卷。
然後我按照與英語相同的程序進行:
文本 | 字數 (Unicode 分詞器) | 字符數 (Jina Embeddings v2 用於德語和英語) | 字符數與字數的比率 (保留 3 位小數) |
---|---|---|---|
deu_mixed-typical_2011_1M | 7,924,024 | 9,772,652 | 1.234 |
deu_newscrawl-public_2019_1M | 17,949,120 | 21,711,555 | 1.210 |
deu_wikipedia_2021_1M | 17,999,482 | 22,654,901 | 1.259 |
marx_kapital | 784,336 | 1,011,377 | 1.289 |
這些結果的差異比僅英語模型更大,但仍表明德語文本平均會產生比字數多 20% 到 30% 的字符數。
使用德英雙語分詞器時,英語文本產生的字符數比僅英語分詞器多:
文本 | 字數 (Unicode 分詞器) | 字符數 (Jina Embeddings v2 用於德語和英語) | 字符數與字數的比率 (保留 3 位小數) |
---|---|---|---|
eng_news_2020_1M | 24243607 | 27758535 | 1.145 |
eng_wikipedia_2016_1M | 22825712 | 25566921 | 1.120 |
使用雙語德英分詞器對英語文本進行編碼時,應該預期需要比僅英語分詞器多 12% 到 15% 的字符數。
tag中文
(jina-embeddings-v2-base-zh)
中文通常不使用空格書寫,在 20 世紀之前也沒有傳統的「詞」的概念。因此,中文文本的大小通常以字數來衡量。所以,我沒有使用 Unicode 分詞器,而是通過移除所有空格然後直接獲取字符長度來測量中文文本的長度。
我從 Wortschatz Leipzig 的中文語料庫頁面下載了三個語料庫:
zho_wikipedia_2018_1M
— 2018 年從中文維基百科提取的一百萬個句子。zho_news_2007-2009_1M
— 2007 年至 2009 年收集的一百萬個中文新聞來源句子。zho-trad_newscrawl_2011_1M
— 來自專門使用繁體字的新聞來源的一百萬個句子。
此外,為了增加多樣性,我還使用了魯迅在 1920 年代初期創作的中篇小說《阿 Q 正傳》。我從 古騰堡計劃下載了繁體版本。
文本 | 字數 | 字符數 (Jina Embeddings v2 用於中文和英語) | 字符數與字數的比率 (保留 3 位小數) |
---|---|---|---|
zho_wikipedia_2018_1M | 45,116,182 | 29,193,028 | 0.647 |
zho_news_2007-2009_1M | 44,295,314 | 28,108,090 | 0.635 |
zho-trad_newscrawl_2011_1M | 54,585,819 | 40,290,982 | 0.738 |
Ah_Q | 41,268 | 25,346 | 0.614 |
這種字符與標記之比的差異出人意料,尤其是繁體字語料庫中的異常值值得進一步研究。儘管如此,我們可以得出結論:對於中文來說,你所需要的 token 數量會比文本中的字符數量要少。根據你的內容,可以預期需要減少 25% 到 40%。
在 Jina Embeddings v2 中英雙語版本中的英文文本,其 token 數量與純英文模型產生的數量大致相同:
Text | Word count (Unicode Segmenter) | Token count (Jina Embeddings v2 for Chinese and English) | Ratio of tokens to words (to 3 decimal places) |
---|---|---|---|
eng_news_2020_1M | 24,243,607 | 26,890,176 | 1.109 |
eng_wikipedia_2016_1M | 22,825,712 | 25,060,352 | 1.097 |
tag認真對待 Token
Token 是 AI 語言模型的重要骨架,這個領域的研究仍在持續進行中。
AI 模型在一個革命性的發現是:它們對於雜訊數據具有很強的抗性。即使特定模型使用的並非最佳的分詞策略,只要網絡夠大、資料充足且訓練得當,它就能從不完美的輸入中學會做正確的事情。
因此,相比其他領域,在改進分詞方面投入的精力要少得多,但這種情況可能會改變。
作為通過 Jina Embeddings 這樣的 API 購買嵌入向量的用戶,你無法精確知道特定任務需要多少 token,可能需要自己進行一些測試才能得到確切的數字。但是這裡提供的估算值—英文約為詞數的 110%、德文約為詞數的 125%、中文約為字符數的 70%—應該足以用於基本預算規劃。