背景
- 在使用Bert进行文本分析的过程中,我们需要用BERT自带的分词器(Tokenizer)来对文本序列进行分词(Tokenize),其基本原理是基于BERT自带的词典文件
vocab.txt
进行词汇匹配,从而得到文本序列的分词结果。
使用Tokenizer分词
- 然而,词典的规模毕竟有限,当遇到
vocab.txt
中不存在的字符时,分词器会将该字符拆分,或者干脆标记为[UNK]
,也就是未知词汇。在通常情况下这样处理是没有问题的,但是,如果我们需要进行一些特殊操作的时候,将未登录词识别为[UNK]
就不满足我们的需求了。 - 例如,在做关系抽取时,我们需要将实体的前后打上实体类型的标签
<LOC> London <LOC> is a...
,然后输入编码器。如果我们依然使用默认的分词器,那么显然没有<LOC>
这个字符,就会基于word piece
进行拆分,这样我们就不能用<LOC>
来标记地点实体了,将会严重影响关系抽取的效果。
特殊标记被拆分
- 再比如,对于一些不在
vocab.txt
中的emoji, 分词器会将其识别为[UNK]
。一般情况下没有问题,但是假如我们需要对社交媒体发言中的Emoji进行分析,就不能忽视不同Emoji之间的区别,这时将他们都识别为[UNK]
是不合理的。
Emoji未识别
因此,我们需要探索一种方法,让BERT的分词器可以识别不在词典vocab.txt
中的词汇,避免将其识别为[UNK]
。
方案1. 替换vocab.txt ★★☆☆☆
经过观察我们发现,vocab.txt
的前面几行是[unusedx]
。顾名思义,这种字符是没有用的,只是用来占位置的,他们在Bert Embedding层的权重是随机初始化的。那么,我们将[unused]
替换为词典中没有的特殊字符,就可以让分词器识别出自定义的特殊字符了,这些特殊字符的初始权重也沿用了对应位置[unused]
符号的权重,即也是随机初始化的。
根据这篇博客的内容Bert预留[unused*],直接将我们需要增加的token替换vocab.txt中原来的[unused]的对应行即可。如图所示,我们将[unused]
替换为原本词典中不存在的几个表情符和特殊汉字。

- 替换后的分词情况
我们发现,替换后,已经可以识别出某些emoji。但是,对于两个emoji连续出现的情况,还是无能为力。例如,下图两个🙏的emoji,就识别为[UNK]
。说明这种方法是有一定缺陷的。
该表词表
2. 增加special_token ★★☆☆☆
在某论坛上出现这样一个提问,Special tokens not tokenized properly #12168,How to use my own additional vocabulary dictionary? #396和我们的诉求比较一致,下面给了一些讨论,大家有兴趣可以看看。总之结论跟huggingface官方论坛给出的关于add_token的方法比较类似,即用special token的方法,下面直接贴出:
# Let's see how to increase the vocabulary of Bert model and tokenizer
tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")
num_added_toks = tokenizer.add_tokens(["new_tok1", "my_new-tok2"])
print("We have added", num_added_toks, "tokens")
# Notice: resize_token_embeddings expect to receive the full size of the new vocabulary, i.e., the length of the tokenizer.
model.resize_token_embeddings(len(tokenizer))
感兴趣的可以运行一下,这里直接说结论,
- ✔这种方法可以识别出句子中的特殊token
- ✖这种方法会增加词表大小
- ✖因为改了词表大小,所以需要重置模型权重,如上面代码最后一行所示。
由于重置模型权重不能保证原单词的权重中不变,新增单词的权重随机初始化,因此这种方法实际操作中不推荐。
3. 推荐方法★★★★★
最好的方法当然是两者的结合
- 在vocab.txt中用新增字符替换掉对应行的[unused*]
- 在tokenizer中增加special token,实现方法如下图所示:

这样就可以实现如下诉求:
- 不改变词表大小
- 可以识别出特殊token
- 不用resize模型的预训练权重,之前的token权重可以保留,新增的token权重随机初始化。
参考资料
- Bert预留[unused*],链接:https://zhuanlan.zhihu.com/p/378774885
- huggingface的add_token函数,链接:https://huggingface.co/docs/transformers/internal/tokenization_utils#transformers.SpecialTokensMixin.add_tokens
网友评论