当Bert遇到未登录词?也许你应该这样做!

DayNightStudy

共 6067字,需浏览 13分钟

 ·

2022-09-29 14:04


作者简介




作者:杨夕

推荐系统 百面百搭地址:

https://github.com/km1994/RES-Interview-Notes

NLP 百面百搭地址:

https://github.com/km1994/NLP-Interview-Notes

个人笔记:

https://github.com/km1994/nlp_paper_study


关注公众号 【关于NLP那些你不知道的事】 加入 【NLP && 推荐学习群】一起学习!!!

NLP && 推荐学习群【人数满了,加微信 blqkm601】



一、动机

  • 中文预训练BERT 对于 英文单词覆盖不全问题。由于  中文预训练BERT 主要针对中文,所以词表中英文单词比较少,但是一般英文单词如果简单的直接使用tokenize函数,往往在一些序列预测问题上存在一些对齐问题,或者很多不存在的单词或符号没办法处理而直接使用 unk 替换了,某些英文单词或符号失去了单词的预训练效果;
  • 专业领域(如医疗、金融)用词或中文偏僻词问题。NLP经常会用到预训练的语言模型,小到word2vector,大到bert。现在bert之类的基本都用char级别来训练,然而由于 Bert 等预训练模型都是采用开放域的语料进行预训练,所以对词汇覆盖的更多是日常用词,专业用词则覆盖不了,这时候该怎么处理?
    ...
print(tokenizer.tokenize('骨骺'))
>>>
['骨', '[UNK]']
  • 英文预训练BERT(bert-base-uncased 和 bert-base-cased)词语被自动拆成词根词缀问题。英文预训练BERT 以词为单位。社会生活中总是会有新词产生,而且在专业领域(如医疗、金融)有一些不常用的词语是 英文预训练BERT 没有涵盖到的,可能词语会被自动拆成词根词缀;
    ...
print(tokenizer.tokenize('COVID'))
>>>
['co', '##vi', '##d']

二、处理方法

2.1 直接在 BERT 词表 vocab.txt 中替换 [unused]

  1. 找到pytorch版本的 bert-base-cased 的文件夹中的vocab.txt文件;
  2. 最前面的100行都是[unused]([PAD]除外),直接用需要添加的词替换进去;

注:这里我们 加入 骺 和 COVID 两个新词

  1. 查询 运行 之前实例,效果如下:
    ...
print(tokenizer.tokenize('骨骺'))
>>>
['骨', '骺']
    ...
print(tokenizer.tokenize('COVID'))
>>>
['covid']

2.2 通过重构词汇矩阵来增加新词

  1. 如果 觉得 修改 vocab.txt文件 会干扰到 其他项目,那么通过重构BERT初始权重矩阵的方式将他们加入词表;
    new_tokens = ['COVID', '骨骺']
num_added_toks = tokenizer.add_tokens(new_tokens)
print(tokenizer.tokenize('骨骺'))
print(tokenizer.tokenize('COVID'))
>>>
['骨骺']
['covid']

注:num_added_toks返回一个数,表示加入的新词数量,在这里是2

  1. resize_token_embeddings输入的参数是tokenizer的新长度
    model.resize_token_embeddings(len(tokenizer))
  1. 添加后的词汇,通过model.resize_token_embeddings方法,随机初始化了一个权重。
    tokenizer.save_pretrained("Pretrained_LMs/bert-base-cased")

2.3 添加特殊占位符号 add_special_tokens

  1. 动机

前面两个是往 词典中 添加未登录词,那么如何往分词器tokenizer中添加新的特殊占位符呢?

  1. 方法介绍

add_special_tokens :往分词器tokenizer中添加新的特殊占位符

    tokenizer.add_special_tokens({'additional_special_tokens':["<e>"]})

注:往additional_special_tokens这一类tokens中添加特殊占位符

  1. 测试
    ...
text = " I love <e> ! "
# 对于一个句子,首尾分别加[CLS]和[SEP]。
text = "[CLS] " + text + " [SEP]"
# 然后进行分词
print("普通 分词效果:")
tokenized_text1 = tokenizer.tokenize(text)
print(tokenized_text1)
indexed_tokens1 = tokenizer.convert_tokens_to_ids(tokenized_text1)
# 分词结束后获取BERT模型需要的tensor
segments_ids1 = [1] * len(tokenized_text1)
tokens_tensor1 = torch.tensor([indexed_tokens1]) # 将list转为tensor
segments_tensors1 = torch.tensor([segments_ids1])
# 获取所有词向量的embedding
word_vectors1 = bertmodel(tokens_tensor1, segments_tensors1)[0]
# 获取句子的embedding
sentenc_vector1 = bertmodel(tokens_tensor1, segments_tensors1)[1]
tokenizer.add_special_tokens({'additional_special_tokens':["<e>"]})
print("添加特殊占位符 后 的分词效果:")
print(tokenizer.additional_special_tokens) # 查看此类特殊token有哪些
print(tokenizer.additional_special_tokens_ids) # 查看其id
tokenized_text1 = tokenizer.tokenize(text)
print(tokenized_text1)
>>>
普通 分词效果:
['[CLS]', 'i', 'love', '<', 'e', '>', '!', '[SEP]']

添加特殊占位符 后 的分词效果:
['<e>']
[21128]
['[CLS]', 'i', 'love', '<e>', '!', '[SEP]']

三、方法对比

  • 方法一:
    • 优点:如果存在大量领域内专业词汇,而且已经整理成词表,可以利用该方法批量添加;
    • 缺点:因为 方法1 存在 未登录词数量限制(eg:cased模型只有99个空位,uncased模型有999个空位),所以当 未登录词 太多时,将不适用;
  • 方法二:
    • 优点:不存在 方法1 的 未登录词数量限制 问题;
  • 方法三:
    • 优点:对于一些 占位符(eg:),方法一和方法二可能都无法生效,因为 <, e, >和均存在于 vocab.txt,但前三者的优先级高于,而 add_special_tokens会起效,却会使得词汇表大小增大,从而需另外调整模型size。但是,如果同时在词汇表vocab.txt中替换[unused],同时 add_special_tokens,则新增词会起效,同时词汇表大小不变。

参考

  1. NLP | How to add a domain-specific vocabulary (new tokens) to a subword tokenizer already trained like BERT WordPiece | by Pierre Guillou | Medium
  2. 如何向BERT词汇表中添加token,新增特殊占位符
  3. 在BERT模型中添加自己的词汇(pytorch版)

所有文章

五谷杂粮

NLP百面百搭

NLP论文学习篇


推荐系统 百面百搭

torch 学习篇

Rasa 对话系统


知识图谱入门


转载记录



浏览 264
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报