零基础入门AI:1 token ~= 3/4 words?3分钟理解大语言模型分词

ChatGPT API 按 token 数量收费,那么1个token究竟是多少?

按Openai 的估算,1 token ~= 3/4 words,100个token大约是75个单词。

输入的文本是如何被分解为 token 的呢?在大语言模型处理文本时,分词(Tokenization)是最基础又相对独立的一个环节,在整个流程中非常重要,但又经常容易被忽略。今天我们就一起来梳理分词(Tokenization)相关的知识。

简介

什么是分词(Tokenization)?

分词(Tokenization)是为了将自然语言转换为计算机可以理解的数值形式。分词的过程是将文本分解为更小单元的过程,这些更小的单元通常被称为 token。这些token可以是单词、子词或者字符,具体取决于所选用的分词方法。这些token将会进一步被转换为数字ID,再转换为向量,从而成为计算机可以理解的输入形式。

分词粒度

分词(Tokenization)有三种主要的粒度:

  • 词级别(Word-Based Tokenization):这是最自然的语言单元,对于像英文这样的语言,单词之间天然存在空格分隔,因此切分相对容易。但会造成词表过大,也一定会存在超出词汇表的词(OOV),而且还不能学到词根或词缀的关系。
  • 字符级别(Character-Based Tokenization):这是最细粒度的tokenize方法,将文本切分为单个字符。这种方法在处理某些语言或任务时可能很有用,尤其是当文本中包含很多未知词或拼写错误时。然而,字符级别的tokenize会丢失一些词或短语级别的语义信息。
  • 子词级别(Subword-Based Tokenization):这种方法介于词和字符之间,旨在解决词级别tokenize可能遇到的问题,如超出词汇表的词(OOV)问题和词表过大问题。子词方法可以将一个词进一步切分为更小的有意义的单元,如词缀或词根。这也是目前最主流的分词粒度。

Subword 分词方法

常见的 subword 分词方法大致可分为以下两类:

  • 基于规则的分词方法:比如python中的NLTK、spaCy等等,通常是依赖于预定义的规则和模式来识别分词的边界。

    python 复制代码
    >>> from nltk.tokenize import word_tokenize
    >>> 
    >>> text = "Don't you love eating apples?"
    >>> word_tokens = word_tokenize(text)
    >>> print(word_tokens)
    ['Do', "n't", 'you', 'love', 'eating', 'apples', '?']
    >>> 
  • 基于预训练模型的分词方法:比如python中的transformer,可以加载不同的预训练模型,这些模型已经在大量文本上进行了训练,学习了单词的上下文表示。

    python 复制代码
    >>> from transformers import AutoTokenizer
    >>> 
    >>> tokenizer = AutoTokenizer.from_pretrained("lmsys/vicuna-7b-v1.5")
    >>> 
    >>> text = "Don't you love eating apples?"
    >>> tokenizer.tokenize(text)
    ['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
    >>> 

    在目前主流的大语言预训练模型中,常见的 subword 分词算法有三种:

    • BPE(Byte Pair Encoding):BPE是一种基于频率的子词分词算法,它从字符级别的分词开始,通过不断合并常见的相邻字符来生成新的子词。这个过程持续进行,直到达到预定的词表大小。
    • WordPiece:WordPiece也是通过合并子词来构建词表,但WordPiece不是基于频率,而是基于能够最大化语言模型概率的相邻子词对。
    • Unigram:Unigram采用一种减量式的分词方法,首先构建一个非常大的初始词表,然后根据评估规则,不断从词表中移除子词,直到满足预定的词表大小。

下文将以开源大模型中的小羊驼(Vicuna)模型为例,进一步分析在大语言模型中的分词细节。关于小羊驼(Vicuna)的更多细节介绍,请参见 零基础入门AI:两步快速本地部署ChatGPT的平替 - 小羊驼 Vicuna

Transformers AutoTokenizer

分词 tokenize

python 复制代码
>>> from transformers import AutoTokenizer
>>> 
>>> tokenizer = AutoTokenizer.from_pretrained("lmsys/vicuna-7b-v1.5")
>>> 
>>> text = "Don't you love eating apples?"
>>> tokenizer.tokenize(text)
['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
>>> 
>>> text = "He is eating an apple."
>>> tokenizer.tokenize(text)
['▁He', '▁is', '▁e', 'ating', '▁an', '▁apple', '.']
>>> 

编码 encode

可以使用 tokenizer.encode 直接将文本转换为词表中的token ID列表(带cls 和sep,带truncation,带padding):

python 复制代码
>>> text = "Don't you love eating apples?"
>>> tokenizer.encode(text)
[1, 3872, 29915, 29873, 366, 5360, 321, 1218, 623, 793, 29973]
>>> 

也可以分两步,tokenize和convert_tokens_to_ids,将文本转换为词表中的token ID列表(不带cls 和 sep):

python 复制代码
>>> text = "Don't you love eating apples?"
>>> tokens = tokenizer.tokenize(text)
>>> print(tokens)
['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
>>> ids = tokenizer.convert_tokens_to_ids(tokens)
>>> print(ids)
[3872, 29915, 29873, 366, 5360, 321, 1218, 623, 793, 29973]
>>> 

解码 decode

大语言模型在完成推理后,输出的是词表中的token ID,可以使用 decode 将其解码成文本。

python 复制代码
>>> text = "Don't you love eating apples?"
>>> tokens = tokenizer.tokenize(text)
>>> print(tokens)
['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
>>> ids = tokenizer.convert_tokens_to_ids(tokens)
>>> print(ids)
[3872, 29915, 29873, 366, 5360, 321, 1218, 623, 793, 29973]
>>> decoded_text = tokenizer.decode(ids)
>>> print(decoded_text)
Don't you love eating apples?
>>> 

类似的,也可以用 convert_ids_to_tokens 和 convert_tokens_to_string 两步来实现

python 复制代码
>>> text = "Don't you love eating apples?"
>>> tokens = tokenizer.tokenize(text)
>>> print(tokens)
['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
>>> ids = tokenizer.convert_tokens_to_ids(tokens)
>>> print(ids)
[3872, 29915, 29873, 366, 5360, 321, 1218, 623, 793, 29973]
>>> output_tokens = tokenizer.convert_ids_to_tokens(ids)
>>> print(output_tokens)
['▁Don', "'", 't', '▁you', '▁love', '▁e', 'ating', '▁app', 'les', '?']
>>> decoded_text = tokenizer.convert_tokens_to_string(output_tokens)
>>> print(decoded_text)
Don't you love eating apples?
>>> 

词表是啥样?

python 复制代码
>>> from transformers import AutoTokenizer
>>> 
>>> tokenizer = AutoTokenizer.from_pretrained("lmsys/vicuna-7b-v1.5")
>>> list(tokenizer.vocab.items())[:5]
[('▁програм', 20796), ('prüng', 21563), ('▁Level', 21597), ('adu', 13467), ('▁prob', 2070)]

在前文的"编码 encode"步骤中,可以看到 ▁Don 被编码为 3872,t被编码为29873,现在可以在词表中验证一下:

python 复制代码
>>> tokenizer.vocab['▁Don']
3872
>>> tokenizer.vocab['t']
29873
>>> 

词表有多大?

python 复制代码
>>> len(tokenizer.vocab)
32000
>>> 

小羊驼(Vicuna)

config.json

小羊驼(Vicuna)的 config.json 如下

json 复制代码
{
  "_name_or_path": "vicuna-7b-v1.5",
  "architectures": [
    "LlamaForCausalLM"
  ],
  "bos_token_id": 1,
  "eos_token_id": 2,
  "hidden_act": "silu",
  "hidden_size": 4096,
  "initializer_range": 0.02,
  "intermediate_size": 11008,
  "max_position_embeddings": 4096,
  "model_type": "llama",
  "num_attention_heads": 32,
  "num_hidden_layers": 32,
  "num_key_value_heads": 32,
  "pad_token_id": 0,
  "pretraining_tp": 1,
  "rms_norm_eps": 1e-05,
  "rope_scaling": null,
  "tie_word_embeddings": false,
  "torch_dtype": "float16",
  "transformers_version": "4.31.0",
  "use_cache": true,
  "vocab_size": 32000
}

从中也可以印证词汇表的大小是32000。

tokenizer_config.json

小羊驼(Vicuna)的 tokenizer_config.json如下

tokenizer_config.json 复制代码
{
  "add_bos_token": true,
  "add_eos_token": false,
  "bos_token": {
    "__type": "AddedToken",
    "content": "<s>",
    "lstrip": false,
    "normalized": false,
    "rstrip": false,
    "single_word": false
  },
  "clean_up_tokenization_spaces": false,
  "eos_token": {
    "__type": "AddedToken",
    "content": "</s>",
    "lstrip": false,
    "normalized": false,
    "rstrip": false,
    "single_word": false
  },
  "legacy": false,
  "model_max_length": 4096,
  "pad_token": null,
  "padding_side": "right",
  "sp_model_kwargs": {},
  "tokenizer_class": "LlamaTokenizer",
  "unk_token": {
    "__type": "AddedToken",
    "content": "<unk>",
    "lstrip": false,
    "normalized": false,
    "rstrip": false,
    "single_word": false
  }
}

从以上"vicuna-7b-v1.5/tokenizer_config.json"中可以看到,最大支持4096个token,分词类使用的是LlamaTokenizer,开始符定义为"<s>",结束符定义为"</s>",超出词汇表的定义为"<unk>"。

在前文的"编码 encode"中使用 tokenizer.encode 时,会添加开始符,就是在最前端添加"<s>",验证如下:

python 复制代码
>>> text = "Don't you love eating apples?"
>>> tokenizer.encode(text)
[1, 3872, 29915, 29873, 366, 5360, 321, 1218, 623, 793, 29973]
>>> 
>>> tokenizer.vocab['<s>']
1
>>> tokenizer.vocab['</s>']
2
>>> tokenizer.vocab['<unk>']
0

To learn

AI 大神 Karpathy 在github上线了最小化的BPE版本算法:minbpe,火速围观

相关推荐
AI完全体6 分钟前
【AI知识点】偏差-方差权衡(Bias-Variance Tradeoff)
人工智能·深度学习·神经网络·机器学习·过拟合·模型复杂度·偏差-方差
GZ_TOGOGO18 分钟前
【2024最新】华为HCIE认证考试流程
大数据·人工智能·网络协议·网络安全·华为
sp_fyf_202418 分钟前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
新缸中之脑20 分钟前
Ollama 运行视觉语言模型LLaVA
人工智能·语言模型·自然语言处理
胡耀超1 小时前
知识图谱入门——3:工具分类与对比(知识建模工具:Protégé、 知识抽取工具:DeepDive、知识存储工具:Neo4j)
人工智能·知识图谱
陈苏同学1 小时前
4. 将pycharm本地项目同步到(Linux)服务器上——深度学习·科研实践·从0到1
linux·服务器·ide·人工智能·python·深度学习·pycharm
吾名招财1 小时前
yolov5-7.0模型DNN加载函数及参数详解(重要)
c++·人工智能·yolo·dnn
鼠鼠龙年发大财2 小时前
【鼠鼠学AI代码合集#7】概率
人工智能
龙的爹23332 小时前
论文 | Model-tuning Via Prompts Makes NLP Models Adversarially Robust
人工智能·gpt·深度学习·语言模型·自然语言处理·prompt
工业机器视觉设计和实现2 小时前
cnn突破四(生成卷积核与固定核对比)
人工智能·深度学习·cnn