使用场景
在Transfomer模型输入的文本中常常会额外使用一些特殊[token]来表示一些特殊含义,比如希望对LLM通过设计prompt提升下游任务效果。
最开始在Bert预训练文本中就约定俗成用[CLS]表示句子开头、[SEP]表示隔开两个句子的符号、[UNK]表示未登录词、[PAD]表示该位置补充0。同时将特殊token加入模型词汇表vocab后可以防止tokenzier在切词时将该[token]切开,在文本编码时保留该[token]为一个整体。
常见使用场景在Bert表征文本时,你需要再使用一些特殊的[token]加入输入的文本中,能起到额外补充一些信息的作用。当然效果好的前提是需要足够多数据让模型finetune。如果数据量少,不如采用词表中已有的特殊[token]去做训练。
实现方法
常见实现方式有以下:
- 1.直接修改词表vocab.txt,具体通过替换其中[unuesd]的token。
- 2.通过tokenizer.add_tokens() 添加,再使用model.resize_token_embeddings() 随机初始化权重。
- 3.通过tokenizer.add_special_tokens() 添加,再使用model.resize_token_embeddings() 随机初始化权重。
这里面方法1,经实验已经无效,无法通过直接修改vocab.txt实现添加新的自定义token,方法2和3的效果等价。
model.resize_token_embeddings()
的作用是将模型的embedding size重新进行变换。
new_num_tokens大于原先的max word的话,会在末尾pad新随机初始化的vector ; 小于原先的max word的话,会把末尾的word embedding vector删掉。而且这个操作只是pad 新的token或cut已有的token,其他位置token的预训练词向量不会被重新初始化。因此可以实现仅对新增加的自定义[token]进行随机初始化。
实践方法
from transformers import BertConfig
from transformers import BertTokenizer
from transformers import BertModel
model_name = "hfl/chinese-roberta-wwm-ext/"
model_dir = "models/pretrain_models/" + model_name
config = BertConfig.from_pretrained(model_dir)
model = BertForMaskedLM.from_pretrained(model_dir)
tokenizer = BertTokenizer.from_pretrained(model_dir)
add_tokens = ["[desc]", "[title]"]
tokenizer.add_tokens(add_tokens) #返回一个数,表示加入的新词数量(2)
special_tokens_dict = {'additional_special_tokens': add_token}. # add_special_tokens 传入是一个key为additional_special_tokens的dict
tokenizer.add_special_tokens(special_tokens_dict) # 效果与add_tokens等价
model.resize_token_embeddings(len(tokenizer)) #关键步骤,重新调整所有的向量数量,即vocab size
tokenizer.save_pretrained(model_dir) #将当前tokenizer保存本地,实现保留增加[token]。
执行完tokenizer.save_pretrained(model_dir)
后,你可以在原保存model目录下,查看vocab.txt中[unused]被替换掉,同时如果之前没有就会生成added_tokens.json,special_tokens_map文件。