PostgreSQL码农集散地

微调大模型时 vocab.json 和 tokenizer.json 有什么用? 如何扩充中文词汇表?

参考文档点击文末阅读原文打开; 推荐《最好的PostgreSQL学习镜像》;


微调大模型时 vocab.json 和 tokenizer.json 有什么用? 如何扩充中文词汇表?

微调大模型时 vocab.json 和 tokenizer.json 有什么用?

在Hugging Face的Transformers库中,vocab.json 和 tokenizer.json 文件都与模型的分词器(Tokenizer)有关,但它们的功能和作用有所不同。

  1. vocab.json:

    例如,如果你有一个词汇表包含['hello', 'world'],vocab.json可能会是:

    {  
    "hello": 1,  
    "world": 2
    }  
  • 这个文件通常用于存储词汇表(vocabulary),也就是模型在训练过程中学到的所有词或子词(subwords)的集合。
  • 对于基于词汇的分词器(比如BERT的WordPiece,GPT的Byte Pair Encoding),vocab.json会包含词汇与其对应的ID之间的映射。
  • vocab.json的内容是一个字典,将词或子词映射到一个唯一的整数ID,这些ID在模型训练时作为输入。
  • tokenizer.json:

    例如,tokenizer.json可能会包含如下信息:

    {  
    "type": "BPE",  
    "model": ["hello", "world"],  
    "special_tokens": {  
    "unk_token": "[UNK]",  
    "pad_token": "[PAD]"
      },  
    "vocab_size": 5000
    }  
    • 这个文件则包含了分词器的配置,包括分词器的类型、词汇表的路径、特定的编码方式(如BPE编码的合并规则),以及可能的其他配置项(比如特殊符号的映射等)。
    • tokenizer.json是一个更为全面的文件,存储了分词器的所有相关信息,除了词汇表之外,还包括如何处理标记(tokens)的规则、如何分解词语、如何处理多语言输入等。
    • 通常,它是一个更复杂的JSON结构,包含有关分词器配置的详细信息。

    主要区别:

    • vocab.json:主要存储词汇到ID的映射,用于词汇表本身。
    • tokenizer.json:存储整个分词器的配置信息,包括词汇表、编码方式等。

    在实际使用中,如果你加载预训练模型的分词器(比如BERT或GPT),通常tokenizer.json会包含更多的信息,而vocab.json则是词汇表的一部分。


    为什么有tokenizer.json还需要vocab.json?

    tokenizer.json 和 vocab.json 可能会同时存在的原因是它们的功能和存储的信息不同,且有些模型的实现方式需要这两者的配合。具体来说,以下是这两者的作用以及它们为何都需要存在的原因:

    1. vocab.json 主要用于词汇表映射

    • vocab.json 存储的是分词器的词汇表,即一个词或子词(subword)到数字ID的映射。这对于分词器的基本工作至关重要,尤其是在像 BERT 或 GPT 这样的模型中,分词器需要快速查找每个词(或子词)的ID以便输入模型进行处理。
    • 对于基于子词(subword)的方法(如BPE,WordPiece等),vocab.json是一个简单的映射结构,它只是记录了词汇表中每个单元(词或子词)的ID。

    例如:

    {  
    "hello": 1,  
    "world": 2
    }  

    在这个例子中,hello 被映射到ID 1,world 被映射到ID 2。

    2. tokenizer.json 主要用于存储分词器的完整配置

    • tokenizer.json 存储的是关于分词器的完整配置,包括但不限于:
      • 分词策略(如BPE、WordPiece等)
      • 特殊符号(例如[CLS]、[SEP]、[PAD]等)
      • 是否有其他的预处理步骤(如大小写转换)
      • 子词的合并规则(对于BPE或SentencePiece这类方法)
    • 它不仅包含词汇表信息,还包括如何使用词汇表的信息,例如:如何拆分单词、如何合并子词、特殊标记的处理方式等等。因此,tokenizer.json 是一个更复杂的配置文件,能够定义分词器的行为。

    例如:

    {  
    "type": "BPE",  
    "special_tokens": {  
    "unk_token": "[UNK]",  
    "pad_token": "[PAD]"
      },  
    "vocab_size": 5000
    }  

    3. 为什么同时需要两个文件?

    • 分工不同:vocab.json 只关心词汇和其对应的 ID,而 tokenizer.json 负责存储分词器的操作细节。虽然 tokenizer.json 可能包含一些词汇表的信息,但它并不直接映射每个词到 ID。vocab.json 承担了这个任务。
    • 灵活性:有些模型使用的分词器可能会更复杂或具有特定的实现方式(例如,使用BPE的子词分割规则)。tokenizer.json 记录了这些实现细节,而 vocab.json 只需要关注词汇到ID的映射。
    • 兼容性:不同的预训练模型可能有不同的分词器实现和需求。某些模型可能只需要 vocab.json 来加载词汇表,而另一些模型则需要完整的分词器配置(即 tokenizer.json)。因此,两个文件可以为不同的使用场景提供支持。

    总结

    • vocab.json 存储的是分词器的词汇表(即词或子词与ID的映射)。
    • tokenizer.json 包含完整的分词器配置信息,包括词汇表、编码方式、特殊符号处理等详细信息。

    虽然有些情况下 tokenizer.json 可能包含部分词汇表信息,但通常它们各自有不同的功能,并且一起使用以确保分词器能够正确工作。


    对于没有训练过中文的模型, 在使用中文语料微调模型时, 是否需要扩充vocab.json和tokenizer.json? 如果需要扩充, 那么如何扩充?

    在使用没有训练过中文的预训练模型(例如基于英语的大型语言模型)进行中文语料微调时,确实可能需要扩充 vocab.json 和 tokenizer.json,特别是当模型未包含足够的中文词汇时。这是因为中文和英语在词汇、字符集、语法结构上有很大的不同。下面是需要扩充的原因及扩充的方式。

    为什么需要扩充 vocab.json 和 tokenizer.json?

    1. 词汇缺失:

    • 许多基于英语的模型(如BERT、GPT-2等)在训练时仅包含英语词汇,因此对于中文、日文等其他语言的词汇支持较差。如果模型的词汇表中没有中文的词汇或子词,分词器将无法有效地处理中文文本。
  • 子词划分:

    • 基于字节对编码(BPE)或WordPiece等分词技术的模型,在训练时可能会将不常见的词拆解成更小的子词(subword)。因此,当你用中文语料微调时,很多中文词汇可能会被拆分为多个子词(如果这些子词没有出现在词汇表中),这可能会导致性能下降。
  • 字符集差异:

    • 英语和中文的字符集不同,中文有大量的汉字,而这些汉字在模型的词汇表中可能不存在。因此,必须扩展词汇表,以便能处理中文字符。

    如何扩充 vocab.json 和 tokenizer.json?

    1. **扩充 vocab.json**:

    • 扩充 vocab.json 主要是将中文词汇或子词添加到现有的词汇表中。常见的做法是使用分词器(如BPE或WordPiece)从中文语料中生成新的词汇表。

    具体步骤:

    • 准备中文语料:你需要准备大量的中文文本,作为新的训练语料(可以是新闻、小说、维基百科等)。
    • 训练分词器:使用适合的工具(如 SentencePiece、tokenizers 库等)从中文语料中训练一个新的子词分词器,并生成一个新的词汇表。
      • 如果使用 SentencePiece,可以通过以下命令训练:
        spm_train --input=chinese_corpus.txt --model_type=bpe --model_prefix=chinese_model --vocab_size=8000  
      • 这将生成一个新的 vocab.json 文件,包含中文子词的映射。

    2. **扩充 tokenizer.json**:

    • tokenizer.json 存储分词器的配置信息,包括分词器的类型、特殊符号等。扩充时,需要确保更新这个文件中的配置,以支持新的词汇表和中文分词策略。

    具体步骤:

    • 在训练新的分词器时,通常会生成一个 tokenizer.json 文件。这个文件包含了分词器的配置信息,包括词汇表、子词合并规则、特殊符号等。
    • 更新特殊符号:你需要确保中文相关的特殊符号(如 [CLS]、[SEP]、[UNK] 等)被正确配置,或者你可以根据需要自定义特殊符号。
    • 如果使用 tokenizers 库来创建分词器,生成 tokenizer.json 文件的代码示例如下:
      from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers, processors  

      tokenizer = Tokenizer(models.BPE())  
      tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()  
      tokenizer.decoder = decoders.BPE()  

      trainer = trainers.BpeTrainer(vocab_size=8000, special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]"])  
      tokenizer.train(["chinese_corpus.txt"], trainer)  
      tokenizer.save("tokenizer.json")  

    3. 微调模型时的注意事项:

    • 微调时加载新分词器:微调模型时,加载的新分词器(包括更新后的 vocab.json 和 tokenizer.json)需要与模型的原始结构兼容。Hugging Face Transformers 提供了 AutoTokenizer 来自动加载和处理这些文件。

      例如:

      from transformers import AutoTokenizer, AutoModelForSequenceClassification  

      # 加载微调后的分词器  
      tokenizer = AutoTokenizer.from_pretrained("path_to_your_tokenizer")  
      model = AutoModelForSequenceClassification.from_pretrained("path_to_your_model")  

      # 使用新分词器进行文本处理  
      inputs = tokenizer("你好,世界!", return_tensors="pt")  

    4. 使用现成的中文模型:

    • 如果你不希望从头开始训练分词器,可以选择使用已有的中文预训练模型(如 bert-base-chinese 或 hfl/chinese-roberta-wwm-ext 等)。这些模型已经训练了适合中文的分词器,不需要重新扩充词汇表。

    总结:

    • 需要扩充:当原始的预训练模型未训练过中文,且其词汇表不包含中文字符或子词时,确实需要扩充 vocab.json 和 tokenizer.json。
    • 扩充方式:通过重新训练一个适合中文的分词器,生成新的 vocab.json 和 tokenizer.json。你可以使用 SentencePiece、tokenizers 等工具从中文语料中训练新的分词器,并更新配置文件。
    • 注意:确保在微调过程中正确加载更新后的分词器,同时检查新的词汇表是否适配中文处理需求。

    如果你有更具体的需求或想法,也可以进一步探讨如何实施。


    扩充时, 如何避免打乱已有vocab.json文件内已有词汇的映射ID? 

    在扩充现有的 vocab.json 时,为了避免打乱已经存在的词汇的 ID 映射,关键是要确保在添加新的词汇或子词时,新的词汇不会覆盖或改变原有词汇的 ID 映射。可以通过以下几个步骤来实现这一目标:

    扩充 vocab.json 时避免打乱已有映射的步骤:

    1. 保持原有词汇的顺序和 ID

    • 首先,加载并保存原始的 vocab.json 文件,并确保将其中的词汇和 ID 映射关系保留下来。
    • 对于新词汇,你可以将它们添加到现有词汇的末尾,确保原有的词汇 ID 保持不变。

    2. 加载原始的 vocab.json

    • 读取现有的 vocab.json 文件,确保了解其中的词汇和 ID 映射。
    import json  

    with open('path_to_original_vocab.json', 'r', encoding='utf-8') as f:  
        vocab = json.load(f)  

    # 打印现有词汇和ID映射  
    print(list(vocab.items())[:10])  # 查看前10个词和它们的ID  

    3. 生成新的 vocab.json

    • 在原始词汇表的基础上,使用你自己的中文语料生成新的词汇。需要确保新的词汇不与现有的词汇冲突。最简单的做法是将新词汇追加到原词汇表的末尾。

    • 避免冲突:通过检查新词汇是否已经在现有的词汇表中,如果已经存在,就跳过;如果不存在,就添加到新词汇表中,并为其分配新的 ID。

    new_vocab = {"new_word_1": len(vocab) + 1, "new_word_2": len(vocab) + 2}  # 假设这是新生成的词汇  
    vocab.update(new_vocab)  # 添加新词汇到现有词汇表  
    • 生成新的 vocab.json 文件:
    with open('new_vocab.json', 'w', encoding='utf-8') as f:  
        json.dump(vocab, f, ensure_ascii=False, indent=4)  

    4. 生成新的分词器配置(tokenizer.json)

    • 更新完词汇表后,你还需要根据新的 vocab.json 重新生成 tokenizer.json 配置文件。这个文件记录了分词器的具体信息,例如分词规则、特殊符号等。

    • 使用更新后的 vocab.json 来训练或更新分词器,确保新的分词器配置不会破坏原有的功能:

    from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers, processors  

    # 加载原始词汇表  
    tokenizer = Tokenizer(models.BPE())  
    tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()  
    tokenizer.decoder = decoders.BPE()  

    # 使用更新后的词汇表和特殊符号进行训练  
    trainer = trainers.BpeTrainer(  
        vocab_size=8000,  # 新的词汇表大小  
        special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]"]  
    )  

    tokenizer.train_from_file("path_to_updated_corpus.txt", trainer)  

    # 保存新的 tokenizer 配置文件  
    tokenizer.save("path_to_new_tokenizer.json")  

    5. 验证词汇表和映射是否正确

    • 在扩充词汇表后,重要的一步是验证现有词汇的 ID 映射是否保持不变。可以通过以下代码检查:
    # 检查某个原词汇是否映射正确  
    original_word = "hello"# 这是原始 vocab.json 中的词  
    original_id = vocab.get(original_word)  
    print(f"Original ID for '{original_word}':", original_id)  

    # 确保新词汇没有影响原有词汇的 ID  
    new_word = "new_word_1"
    new_id = vocab.get(new_word)  
    print(f"New ID for '{new_word}':", new_id)  

    6. 加载并使用新的分词器

    • 最后,在微调时使用新的词汇表和分词器配置。你可以通过 Hugging Face Transformers 的 AutoTokenizer 来加载新创建的 vocab.json 和 tokenizer.json 文件:
    from transformers import AutoTokenizer  

    tokenizer = AutoTokenizer.from_pretrained('path_to_new_tokenizer')  
    model = AutoModelForSequenceClassification.from_pretrained('path_to_your_model')  

    # 使用新的分词器进行文本处理  
    inputs = tokenizer("你好,世界!", return_tensors="pt")  

    总结:

    1. 扩充时保持原ID不变:扩充时,将新词汇添加到现有词汇表的末尾,确保原始词汇的 ID 映射不会发生变化。
    2. 避免词汇冲突:在添加新词汇时,检查是否已有该词汇,避免重复ID。
    3. 更新分词器配置:根据扩充后的词汇表更新 tokenizer.json 配置文件,并确保新的分词器仍然可以正确地处理现有和新加入的词汇。
    4. 验证正确性:在扩充后,检查原始词汇的 ID 映射是否保持不变,并确保新词汇的 ID 正确分配。

    通过这种方法,你可以在扩充词汇表时,避免影响已有词汇的 ID 映射,从而确保模型可以平稳地处理新的中文语料并进行微调。

    文末彩蛋:国产数据库周边生态

    当然一款数据库要流行起来, 除了自己要强大, 还离不开生态. 用好周边生态工具, 管理水平战胜90%老司机!!! 下面简单介绍一下国产数据库周边生态.

    1、管控软件

    鸣嵩(前阿里云数据库总经理 / 研究员)等大佬们创业创办的云猿生, 核心产品是KubeBlocks. 他们的理念是让管理数据库和搭积木一样简单, 如果你要管理很多套并且种类(OLTP\OLAP\NoSQL\KV\TS\MQ等)很多的数据库产品, 推荐首选.

    • https://github.com/apecloud/kubeblocks

    PG中文社区核心委员唐成老师的公司乘数开源的Clup, 专用管理PostgreSQL和PolarDB的集群管理软件, 如果你要管理很多套数据库, 推荐选择. 并且Clup还提供了企业版、自研的连接池、分布式存储、一体机、备份平台等, 是企业用户推荐之选.

    • https://www.csudata.com/

    若航老司机开源的pigsty, 集成了300多个PG插件的PG集群和PolarDB集群管理软件, 如果你要管理很多套PG或PolarDB数据库, 且对插件有特别多的需求, 推荐选择.

    • https://pigsty.cc/zh/

    2、审计监控诊断优化

    翟总(曾经是我背后的男人)到海信聚好看后研发的 DBdoctor, 采用ebpf技术, 在对数据库几乎没有影响的情况下实时监控数据库和服务器的各项指标, 发现和诊断问题根因非常方便.

    • https://www.dbdoctor.cn/

    天舟老哥的核心产品Bytebase 是位于您和数据库之间的中间件。它是数据库 DevOps 的 GitLab/GitHub,专为开发人员、DBA 和平台工程师打造。

    • https://bytebase.cc/docs/introduction/what-is-bytebase/

    PawSQL, SQL优化和诊断产品.  

    D-Smart, Oracle老前辈白老大出品, 专注企业级市场, 将业界顶级DBA经验的产品化作品, 产品功能包括数据库监控、诊断、优化等.

    • https://www.modb.pro/db/567140

    3、国产数据库IDE

    IDE是开发者的必备工具,例如社区有pgAdmin, 国产IDE则可以看看老程序猿达刚老师的DeskUI:

    • https://www.deskui.com

    4、数据同步&迁移&备份恢复

    NineData, 老领导出去创业做的产品, 产品涵盖了数据同步、迁移、备份、比对、devops、chatDBA等.

    • https://www.ninedata.cloud/home

    DSG, 非常老牌的数据库同步迁移企业级产品, 支持各种数据库的异构和同构迁移, 用他们的话说, 没有dsg搞不定的迁移, 比goldengate还牛.

    • https://www.dsgdata.com/

    公开课

    如果你对PolarDB学习感兴趣可以阅读这个公开课系列:

    除了PolarDB还非常值得关注的几款PG栈国产数据库:

    • HaloDB(基于PG兼容PostgreSQL、Oracle、MySQL. http://www.halodbtech.com/ )、
    • IvorySQL(基于开源PG兼容PG、Oracle. https://www.ivorysql.org/zh-cn/ )、
    • ProtonBase(云原生分布式数仓. https://protonbase.com/ )、
    • 成都文武数据库(https://ww-it.cn)

    参考文档点击阅读原文获得


    感谢关注我的github (https://github.com/digoal/blog) 及视频号:

    Image