在实施 AI 流程的过程中,理解 AI 模型存在许多障碍,其中一些是相当大的障碍。但很多人首先遇到的障碍是理解我们在谈论 tokens 时究竟是什么意思。

在选择 AI 语言模型时,最重要的实用参数之一是其上下文窗口的大小——即最大输入文本大小——它是以 token 为单位的,而不是以词、字符或任何其他自动可识别的单位。
此外,嵌入服务通常是按"每个 token"计费的,这意味着要理解你的账单,tokens 很重要。
如果你不清楚什么是 token,这可能会让人非常困惑。

但在现代 AI 的所有令人困惑的方面中,tokens 可能是最不复杂的。本文将试图解释什么是 tokenization,它做什么,以及为什么我们要这样做。
tag简要总结
对于那些想要或需要快速了解从 Jina Embeddings 购买多少 tokens,或估算需要购买多少的人来说,以下统计数据是您需要了解的。
tag每个英语单词的 Tokens
在本文后面描述的经验测试中,使用 Jina Embeddings 英语专用模型时,各种英语文本转换为 tokens 的比率大约比单词数多 10%。这个结果相当稳定。
Jina Embeddings v2 模型的上下文窗口为 8192 个 tokens。这意味着如果您传递给 Jina 模型一个超过 7,400 个单词的英语文本,很可能会被截断。
tag每个中文字符的 Tokens
对于中文,结果更加变化多样。根据文本类型的不同,每个汉字的 tokens 比率从 0.6 到 0.75 不等。输入给 Jina Embeddings v2 中文版的英语文本产生的 tokens 数量与 Jina Embeddings v2 英语版大致相同:比单词数多大约 10%。
tag每个德语单词的 Tokens
德语的单词到 token 的比率比英语更多变,但比中文少。根据文本类型的不同,平均比单词多 20% 到 30% 的 tokens。将英语文本输入到 Jina Embeddings v2 德英双语版时,使用的 tokens 比仅英语和中英模型多一些:比单词多 12% 到 15% 的 tokens。
tag注意事项
这些是简单的计算,但对于大多数自然语言文本和大多数用户来说应该是近似正确的。最终,我们只能承诺 tokens 的数量永远不会超过文本中的字符数加二。实际上它通常会比这少得多,但我们不能事先承诺任何具体的数量。
这些是基于统计学上简单计算的估计。我们不能保证任何特定请求会使用多少 tokens。
如果你只需要有关购买多少 Jina Embeddings tokens 的建议,可以在这里停止阅读。其他公司的嵌入模型可能与 Jina 模型的单词到 token 和汉字到 token 的比率不同,但总体上不会有太大差异。
如果你想了解原因,本文的其余部分将深入探讨语言模型的 tokenization。
tag单词、Tokens、数字
Tokenization 在现代 AI 模型存在之前就已经是自然语言处理的一部分了。
说计算机中的一切都只是数字可能有点陈词滥调,但这基本上是事实。然而,语言本质上并不只是一堆数字。它可能是由声波组成的语音,或是纸上的标记,甚至是印刷文本的图像或手语视频。但大多数情况下,当我们谈论使用计算机处理自然语言时,我们指的是由字符序列组成的文本:字母(a、b、c 等)、数字(0、1、2...)、标点符号和空格,使用不同的语言和文本编码。
计算机工程师称这些为"字符串"。
AI 语言模型接受数字序列作为输入。所以,你可能写了这样一个句子:
What is today's weather in Berlin?
但经过 tokenization 后,AI 模型得到的输入是:
[101, 2054, 2003, 2651, 1005, 1055, 4633, 1999, 4068, 1029, 102]
Tokenization 是将输入字符串转换为 AI 模型可以理解的特定数字序列的过程。
当你通过按 token 收费的 web API 使用 AI 模型时,每个请求都会被转换成像上面那样的数字序列。请求中的 token 数量就是该数字序列的长度。因此,请求 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 代表的字符串部分可以是一个词、词的一部分或标点符号。空格通常在分词器输出中没有明确的表示。
Tokenization 是自然语言处理中称为文本分割的一组技术的一部分,执行 tokenization 的模块很符合逻辑地被称为分词器。
为了展示 tokenization 是如何工作的,我们将使用 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>']
这些标记并不映射到任何具体的中文词典。例如,"啟程" — qǐchéng(启程、出发)通常会被归类为一个词,但这里被分成了两个独立的字符。同样,"在下班"通常被认为是两个词,其中"在" — zài(在、当)和"下班" — xiàbān(下班、下班时间)是分开的,而不是像分词器这样把它分成"在下"和"班"。
在这三种语言中,分词器切分文本的位置与人类读者会划分的逻辑位置并不直接相关。
这并不是 Jina Embeddings 模型的特定特征。这种分词方法在 AI 模型开发中几乎是普遍存在的。虽然两个不同的 AI 模型可能没有完全相同的分词器,但在当前的开发状态下,它们实际上都会使用具有这种行为特征的分词器。
下一部分将讨论分词使用的具体算法及其背后的逻辑。
tag为什么要分词?为什么要用这种方式?
AI 语言模型接收代表文本序列的数字序列作为输入,但在运行底层神经网络和创建嵌入之前还会发生一些其他处理。当面对表示小文本序列的数字列表时,模型会在其内部字典中查找每个数字,该字典为每个数字存储唯一的向量。然后将它们组合起来,作为神经网络的输入。
这意味着分词器必须能够将我们提供的任何输入文本转换为出现在模型的标记向量字典中的标记。如果我们使用传统词典中的标记,那么当我们遇到第一个拼写错误或罕见的专有名词或外来词时,整个模型就会停止。它将无法处理该输入。
在自然语言处理中,这被称为词汇表外(OOV)问题,它在所有文本类型和所有语言中都普遍存在。解决 OOV 问题有几种策略:
- 忽略它。用"未知"标记替换字典中没有的所有内容。
- 绕过它。不使用将文本序列映射到向量的字典,而是使用将单个字符映射到向量的字典。英语大多数时候只使用 26 个字母,所以这必须比任何词典都更小,而且对 OOV 问题的抵抗力更强。
- 在文本中找到频繁出现的子序列,将它们放入字典,并对剩余部分使用字符(单字母标记)。
第一种策略意味着大量重要信息会丢失。如果数据采用的形式不在字典中,模型甚至无法学习它所见到的数据。普通文本中的很多内容即使在最大的字典中也找不到。
第二种策略是可能的,研究人员也对此进行了研究。但是,这意味着模型必须接受更多的输入,必须学习更多的内容。这意味着需要更大的模型和更多的训练数据,而结果从未被证明比第三种策略更好。
AI 语言模型几乎都以某种形式实现第三种策略。大多数使用某种变体的 Wordpiece 算法 [Schuster and Nakajima 2012] 或类似的技术,称为 字节对编码(BPE)。[Gage 1994, Senrich et al. 2016] 这些算法是与语言无关的。这意味着它们对所有书面语言的工作方式都相同,除了可能的字符的完整列表之外不需要任何知识。它们是为像 Google 的 BERT 这样的多语言模型设计的,这些模型可以接受从互联网抓取的任何输入 — 数百种语言和计算机程序等非人类语言的文本 — 这样它们就可以在不进行复杂语言学处理的情况下进行训练。
一些研究表明,使用更具语言特异性和语言感知的分词器可以带来显著的改进。[Rust et al. 2021] 但是以这种方式构建分词器需要时间、金钱和专业知识。实施像 BPE 或 Wordpiece 这样的通用策略要便宜和容易得多。
然而,因此,除了通过分词器运行文本然后计算输出的标记数量之外,没有其他方法可以知道特定文本代表多少标记。因为文本的最小可能子序列是一个字母,所以你可以确定标记的数量不会大于字符数(减去空格)加二。
要获得良好的估计,我们需要向分词器输入大量文本,并根据经验计算我们获得的标记数量与输入的单词或字符数量的平均比较。在下一节中,我们将对目前可用的所有 Jina Embeddings v2 模型进行一些不太系统的经验测量。
tag标记输出大小的经验估计
对于英语和德语,我使用了 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
。
要获得标记计数,将文本片段传递给各种 Jina Embeddings 模型的分词器,如下所述,每次我都从返回的标记数量中减去二。
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
生成的标记数,每次分词请求减去两个标记。结果如下:
文本 | 词数 (Unicode 分段器) | 标记数 (Jina Embeddings v2 英语版) | 标记与词的比率 (保留 3 位小数) |
---|---|---|---|
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% 的词元,这表明根据 Unicode 分词器的测量,你可能会得到比单词多大约 10% 的词元。文字处理软件通常不计算标点符号,而 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
— 来自专门使用繁体字的新闻来源的一百万个句子。
此外,为了增加多样性,我还使用了鲁迅在 20 世纪 20 年代初写的中篇小说《阿 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 |
这种标记数量与字符数量比率的差异出乎意料,特别是繁体字语料库的异常值值得进一步研究。尽管如此,我们可以得出结论,对于中文,您需要的标记数量会少于文本中的字符数量。根据您的内容,预计可以减少 25% 到 40%。
在 Jina Embeddings v2 中文和英文版本中的英文文本产生的标记数量与纯英文模型大致相同:
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认真对待标记
标记是 AI 语言模型的重要支撑框架,这一领域的研究仍在持续进行。
AI 模型革命性的一个表现是它们对嘈杂数据的强大鲁棒性。即使特定模型没有使用最优的分词策略,只要网络足够大,有足够的数据,并经过充分训练,它就可以从不完美的输入中学会正确的处理方法。
因此,相比其他领域,在改进分词方面投入的精力要少得多,但这种情况可能会改变。
作为通过Jina Embeddings 这样的 API 购买嵌入的用户,您无法准确知道特定任务需要多少标记,可能需要自己进行一些测试才能得到准确的数字。不过,这里提供的估算——英语约为词数的 110%,德语约为词数的 125%,中文约为字符数的 70%——应该足以满足基本预算需求。