NLP学习—实现一个BERT(简记)
本文主要内容是介绍了BERT的代码结构、具体实现与原理,对BERT的一些简记
内容包括以下几个部分:
- BERT Tokenization 分词模型(BertTokenizer)
- BERT Model 本体模型(BertModel)
- BertEmbeddings
- BertEncoder
- BertLayer
- BertAttention
- BertIntermediate
- BertOutput
- BertLayer
- BertPooler
本文内容的结构图如下:
具体的内容如下
1. BERT Tokenization 分词模型(BertTokenizer)
1)Tokenization分词-BertTokenizer:基于BasicTokenizer和WordPieceTokenizer的分词器
a)BasicTokenizer负责处理的第一步——按标点、空格等分割句子,并处理是否统一小写,以
及清理非法字符。
(1)对于中文字符,通过预处理(加空格)来按字分割;
(2)同时可以通过never_split指定对某些词不进行分割;
(3)这一步是可选的(默认执行)。
b)WordPieceTokenizer在词的基础上,进一步将词分解为子词(subword)。
(1)subword 介于 char 和 word 之间,既在一定程度保留了词的含义,又能够照顾到英
文中单复数、时态导致的词表爆炸和未登录词的 OOV(Out-Of-Vocabulary)问题,将词根
与时态词缀等分割出来,从而减小词表,也降低了训练难度;
(2)例如,tokenizer 这个词就可以拆解为“token”和“##izer”两部分,注意后面一个词的
“##”表示接在前一个词后面。
2. BERT Model 本体模型(BertModel)
组成:
1. embeddings,即BertEmbeddings类的实体,根据单词符号获取对应的向量表示;
2. encoder,即BertEncoder类的实体;
3. pooler,即BertPooler类的实体,这一部分是可选的。
补充: 剪枝是一个复杂的操作,需要将保留的注意力头部分的 Wq、Kq、Vq
和拼接后全连接部分的权重拷贝到一个新的较小的权重矩阵(注意先禁止 grad
再拷贝),并实时记录被剪掉的头以防下标出错。具体参考BertAttention部分
的prune_heads方法.**
2.1 BertEmbeddings
组成:包含三个部分求和得到:
1) word_embeddings,上文中 subword 对应的嵌入。
2) token_type_embeddings,用于表示当前词所在的句子,辅助区别句子与
padding、句子对间的差异。
3) position_embeddings,句子中每个词的位置嵌入,用于区别词的顺序。和
transformer 论文中的设计不同,这一块是训练出来的,而不是通过 Sinusoidal
函数计算得到的固定嵌入。一般认为这种实现不利于拓展性(难以直接迁移到更长
的句子中)。
->>> 三个 embedding 不带权重相加,并通过一层 LayerNorm+dropout 后输出,其大小
为(batch_size, sequence_length, hidden_size)。
2.2 BertEncoder
组成:包含多层 BertLayer,这一块本身没有特别需要说明的地方,不过有一个细节值
得参考:利用 gradient checkpointing 技术以降低训练时的显存占用。
- BertLayer
- BertAttention
- BertSelfAttention
- BertSelfOutput
- BertIntermediate
- BertOutput
2.3 BertPooler
这一层只是简单地取出了句子的第一个token,即[CLS]对应的向量,然后过一个全连接层
和一个激活函数后输出:(这一部分是可选的,因为pooling有很多不同的操作)