第1章 训练自己的Tokenizer,构建专属词表(非必须,按需进行)

前言

在自然语言处理领域,训练分词器和训练大模型是两个基本而关键的过程。它们相互依赖,共同影响着语言模型的性能和应用效果。
训练分词器是将连续文本分割成有意义单元的过程,它是数据预处理的一个重要步骤。一个精确的分词器不仅能够提高数据的质量,还能帮助模型更准确地理解句子结构和语义。这对于后续的大模型训练至关重要,因为即使是小的分词错误也可能在大规模模型中被放大,影响最终的应用效果。训练大模型需要大量的数据和计算资源,它从分词器得到的数据中学习语言的深层次结构和模式。大模型的训练过程可以揭示分词器的重要性:一个优秀的分词器能够显著提升模型的性能,尤其是在处理复杂文本或新领域数据时。

一、从零训练Tokenizer

想要自己训练一个自己专属的Tokenizer有多种方法,使用sentencepiece工具是一种非常便捷、高效的方法。

import sentencepiece as spm
from sentencepiece import sentencepiece_model_pb2 as sp_model_pb2
import json

model_name = "starry_tokenizer"
model_type = "bpe"
vocab_size = 20000

# 从语料中训练自己的tokenizer
# byte_fallback=True,这样在解码时如果遇到了词表以外的词,会用UTF-8 编码将这些字符转换成字节表示,可以避免 OOV(out of vocabulary)问题
# character_coverage 模型覆盖的字符数量,对于字符集丰富的语言(如日语或中文)推荐默认值为 0.9995,对于其他字符集较小的语言推荐默认值为 1.0。
# input 训练语料;model_prefix 模型输出路径;vocab_size训练后词表的大小;character_coverage 模型中覆盖的字符数;
# model_type 模型类型:unigram,bpe,char,word;byte_fallback在解码时如果遇到了词表以外的词,会用UTF-8 编码将这些字符转换成字节表示
spm.SentencePieceTrainer.Train(input=path, vocab_size=vocab_size, model_type=model_type, model_prefix=model_name,
                                byte_fallback=True, character_coverage=0.9995)

二、现有Tokenizer的基础上加上自己的词表

如果我们觉得重新训练一个自己的Tokenizer很麻烦,没有必要性, 但是又想加入一些特殊词汇在词表中。这时候我们可以使用sentencepiece进行词表扩充。

# 使用spm.SentencePieceProcessor()来读取tokenizer模型
sp = spm.SentencePieceProcessor(model_file='starry_tokenizer.model')
# 输出去Tokenizer模型的词表大小
print(sp.vocab_size())

old_model = sp_model_pb2.ModelProto()
old_model.PaeseFromString(sp.serialized_model_proto())
existed_pieces = set(p.piece for p in old_model.pieces)
for piece in pieces:
    if piece not in existed_pieces:
        existed_pieces.add(piece)

        new_piece = sp_model_pb2.ModelProto().SentencePiece()
        new_piece.piece = piece
        new_piece.score = 0
        old_model.pieces.append(new_piece)

# 保存Tokenizer模型
with open('new-starry-tokenizer.model', 'wb') as f:
    f.write(old_model.SerializeToString())

print(len(old_model.pieces))

三、合并两个分词器

对于现有的两个分词器,如果我们合并起来取并集的词表,那么我们可以用下面的代码:

# 合并两个分词器
def expand_vocab():
    model_file = "wiki_model.model"
    jp_model_file = "jp_koa_model.model"
    # 加载两个预训练的SentencePiece模型文件:wiki_model.model和jp_koa_model.model
    # 使用spm.SentencePieceProcessor类分别创建两个分词器对象:chinese_sp和jp_sp。
    chinese_sp = spm.SentencePieceProcessor(model_file=model_file)
    jp_sp = spm.SentencePieceProcessor(model_file=jp_model_file)
    print(chinese_sp.vocab_size())
    # 将chinese_sp的序列化模型协议(serialized model protocol)解析为sp_model_pb2.ModelProto对象:chinese_spm
    chinese_spm = sp_model_pb2.ModelProto()
    chinese_spm.ParseFromString(chinese_sp.serialized_model_proto())
    # 将jp_sp的序列化模型协议解析为sp_model_pb2.ModelProto对象:jp_spm。
    jp_spm = sp_model_pb2.ModelProto()
    jp_spm.ParseFromString(jp_sp.serialized_model_proto())
    # 创建一个集合chinese_sp_set,包含chinese_spm中的所有词汇片段(piece)。
    chinese_sp_set = set(p.piece for p in chinese_spm.pieces)
    # 遍历jp_spm中的每个词汇片段,如果该片段不在chinese_sp_set中,则将其添加到chinese_sp_set和chinese_spm.pieces中,并设置其得分为0。
    for p in jp_spm.pieces:
        piece = p.piece
        if piece not in chinese_sp_set:
            chinese_sp_set.add(piece)
            new_piece = sp_model_pb2.ModelProto().SentencePiece()
            new_piece.piece = piece
            new_piece.score = 0
            chinese_spm.pieces.append(new_piece)
    # 打印扩展后的词汇表大小。
    print(f"after expand vocab={len(chinese_spm.pieces)}") # 20253
    # save model
    with open(model_file, 'wb') as file:
        file.write(chinese_spm.SerializeToString())

注意:无论用什么方法扩充词表后,需要同时对语言模型的 token embedding 进行 resize,否则会出现维度不一致错误。
更具体的,如果使用 transformers model,可以使用 resize_token_embeddings 方法进行 resize,详情见resize_token_embeddings官方文档
以下是一个简单用Transformers加载bert模型后进行resize的示例代码。

model = BertForMaskedLM.from_pretrained(model_dir)
model.resize_token_embeddings(len(tokenizer))
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值