分词器tokenizers

总览#

为了让语言变为模型能够理解的形式(tokens),每个字词必须映射为独一无二的序号,这时需要使用分词器 tokenizer 对语言进行转换。例如对于 “are you ok”,gemma 模型的 tokenizer 会将之转换为一个 List:[2, 895, 692, 4634]

顺便一提,第一个序号 2 是开始标记 <bos>

本文是学习 chat 模型微调留下的学习笔记,只记录了自认为必要的知识点。写得很乱,可能以后会整理一下。

使用 transformers 库的 Tokenizers#

HuggingFace 家的 Tokenizers 提供了当下最常用的 tokenizer 实现。

为了方便学习,先实例化一个 tokenizers。

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("google/gemma-2b-it")

gemma 的使用需要 token,为了方便可以换为 facebook/blenderbot-400M-distill 之类的模型。本文是以 gemma 为例。

现在,就可以通过 tokenizer.encode("are you ok") 看转换效果了。

Tokenizer 与 PreTrainedTokenizer#

上面使用 AutoTokenizer.from_pretrained("google/gemma-2b-it") 获得的是 GemmaTokenizerFast 类变量,这个类继承于 transformers.PreTrainedTokenizerFast

可以通过 Tokenizer 对象实例化一个 PreTrainedTokenizerFast 对象。

from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)

通过 wrapped_tokenizer.save_pretrained("path") 可以将 tokenizer 的整体状态保存为三个文件:tokenizer_config.json、special_tokens_map.json 和 tokenizer.json。若要从文件加载,就使用 PreTrainedTokenizerFast.from_pretrained("path") 实例化。

tokenizers.Tokenizer 类#

tokenizers 库中 Tokenizer 类能够涵盖转换 tokens 的四步骤。

要将字符串转换为 tokens,需要经过以下四步骤:

Normalization,让字符串更 “干净”,会进行像是删空格和统一大小写这样的操作。

若词表大小足够大,这一步就不需要太多处理了。对于 gemma,这一步仅将空格变为下划线。

Pre-Tokenization,将字符串分割为单词。对于英语,就是根据空格进行分词。

若 Model 步骤不使用 WordLevel 方法(现在一般都不用 WordLevel 了),则 Pre-Tokenization 步骤不是必须的。

Model,通过字典将字符映射为序号。是 Tokenizer 的核心。

Post-Processing,添加 CLS SEP 等标记,便于让模型知晓何字符串时开始何时结束。

Tokenizer 对象是 GemmaTokenizerFast 的一部分。对于本文一开始生成的 tokenizer,可以通过 tokenizer._tokenizer 获得其包含的 Tokenizer 对象。

Model 步骤#

Model 步骤是 Tokenizer 的核心,是 Tokenizer 的唯一必需过程。

Model 通过字典将字符映射为序号。至于如何配合字典将字符串分段,有着不同的算法:

  • WordLevel,最为经典的分段方法,直接将单词进行字典映射。这会导致像是 happy happier happiest 一类词被映射到完全不同的 ID,显著增加字典所需大小
  • BPE(Byte Pair Encoding),整个单词被拆分为字节,并根据实际将常见的字节组合构成词典条目。如此,便可像搭积木一般构造单词,所需字典词汇表更小
  • WordPiece,与 BPE 算法很像,目标都是希望像搭积木一般构造单词。但 WordPiece 不是先拆分为字节,而是先分为长句,若字典中不含该条目再进一步拆分
  • Unigram,其输出的子词分段能够带概率。比起 BPE,Unigram 将概率纳入考虑,选择可能性最大的那一个分词方法

WordLevel 现在一般不会用了。gemma 使用的是 BPE。

本节参考:

对话模板#

chat 模型本质上是下一词预测(next-token prediction)模型,但它遵循了一个对话模板。具体来说,差不多就是输入字符串 问: 1+1等于几?答: 然后让模型补全剩下的字符。

写成 json,这段对话应当长成这样:

[
    {
        "role": "user",
        "content": "1+1等于几?",
    },
    {
        "role": "model",
        "content": "2",
    },
]

能够通过 tokenizer 的对话模板将 json 转换为模型输入。

对话模板本质是一段以字符串形式存储的脚本,可以通过 tokenizer.chat_template 得到。gemma 的对话模板的脚本框架是这样:

{{ bos_token }}

{% for message in messages %}
    {% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}
        {{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}
    {% endif %}
        {% if (message['role'] == 'assistant') %}
            {% set role = 'model' %}{% else %}{% set role = message['role'] %}
        {% endif %}
    {{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}
{% endfor %}

{% if add_generation_prompt %}
    {{'<start_of_turn>model\n'}}
{% endif %}

tokenizer.chat_template 返回的脚本被压成了一行,需要手动分段才能得到上面这样有较好可读性的形式。

脚本很易读。大致是先放一个 bos_token,然后遍历每段对话进行处理。最后选择是否添加 <start_of_turn>model\n 来引导模型生成回答(而不是续写问题)。

调用 tokenizer.apply_chat_template() 时,可以传入 add_generation_prompt=True 使得该脚本最后的判断为真。

准本好 json 对话 chat,就可以使用 tokenizer.apply_chat_template(chat, tokenize=False) 看一下模板生成效果。

tokenizer.apply_chat_template(chat, tokenize=False)
# <bos><start_of_turn>user\n1+1等于几?<end_of_turn>\n<start_of_turn>model\n2<end_of_turn>\n

参考来源#

作者:chirp

出处:https://www.cnblogs.com/chirp/p/18148151

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   倒地  阅读(544)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示