> For the complete documentation index, see [llms.txt](https://kerasnoone.gitbook.io/garnet/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kerasnoone.gitbook.io/garnet/shen-jing-wang-luo/yu-xun-lian-mo-xing/yu-xun-lian-guo-cheng-de-mask-ji-zhi.md).

# 预训练过程的Mask机制

## 静态Mask

原始的BERT模型使用的是**静态Mask**机制. 随机遮盖或替换一句话里面任意token, 随机把一句话中15%的token替换成以下内容:

* 80%的概率被替换成`[MASK]`
* 10%的概率被替换成随机的另外一个token
* 10%的概率不做任何操作

### 被选中的15%的词中仅Mask掉80%词的目的是?

虽然选定的15%的token中, 只有80%会被真正的Mask, 即`15% * 80% = 12%`的token被Mask, 但在预训练过程中, 这15%被选中的token都要去做**MLM**任务, 即都要通过预测来进行学习.

这个预训练Trick的目的是缓解**Exposure Bias**, 即预训练和Fine-tune两阶段不一致的情况. 预训练时, 要预测的单词被Mask住, 此时得到的向量是完全基于其上下文的语义向量; 但是在Fine-tune阶段, 当前词是可见的, 且当前位置得到的语义向量, 是包含这个token的. 两个阶段的情况出现了偏差.

因此, 留着80%的词\[MASK], 10%的词保留, 剩下10%的词随机替换, 是在告诉模型, 输入的词有可能是错误的, 不要太相信它. 相当于加入噪音, 在训练数据有噪音的情况下, 缓解两节点的bias.

参考:

* [BERT预训练任务中被选中的15%的词中仅80%的词是真正被Masked掉的，目的是什么？](https://www.zhihu.com/question/326704953/answer/698621923)

### 实际实现

实际操作时, 在生成预训练数据的时候, **提前对数据进行mask**. 也就是说从一开始随机选择了这15%的Tokens, 之后的N个epoch里都不再改变了. 为了充分利用数据, BERT定义了`dupe_factor`参数, 这样可以将训练数据复制`dupe_factor`份, 同一条数据就可以有`dupe_factor`种不同的mask方法. 但每条样本mask的结果依然是固定的这`dupe_factor`种.

## 动态Mask

这种方法是RoBERTa中使用的Mask方法. 动态Mask, 是在每一次将训练example喂给模型的时候, 才进行随机mask. 相比于静态Mask, 动态mask相当于间接的增加了训练数据, 有助于提高模型性能.

### 实际实现

RoBERTa一开始把预训练的数据复制10份, 每一份都随机选择15%的Tokens进行Masking, 即同样的一句话有10种不同的mask方式. 也可以不复制数据, 真正在喂给模型之前, 实时地随机生成当前样本Mask的策略.

## Whole-Word Masking

这是在中文BERT预训练模型中常用的预训练Mask机制. 例如我们常用的哈工大开源的[Chinese-BERT-wwm](https://github.com/ymcui/Chinese-BERT-wwm/)模型中, 就使用了这种机制.

原始的BERT模型预训练时, **会随机选取整句中的最小输入单元作为token来进行遮盖**. BERT中使用了**Byte Pair Encoding**(BPE)技术, 对原来的每个英文单词继续拆解得到了**subword**(子词), 例如`superman`被拆解成为`super`和`man`两个token. 那么在预训练的过程中, 就会出现`super`没有被Mask但`man`被Mask的情况, 一个完整的单词被割裂开.

谷歌后面又提出了BERT WWM模型, 即原来在预训练时遮盖子词, 现在遮盖整个词. `superman`还是会被拆解为`super`和`man`两个token, 但如果其中一个要被Mask, 就要将两个token同时Mask掉.

对于中文模型, 原始的谷歌开源的BERT中的中文, 是使用每个字作为token, 没有考虑到中文中的分词(CWS), 也会出现割裂的情况. 使用WWM的策略, 就可以**对全词进行Mask**:

| 说明       | 样例                                                                            |
| -------- | ----------------------------------------------------------------------------- |
| 原始文本     | 使用语言模型来预测下一个词的probability。                                                    |
| 分词文本     | 使用 语言 模型 来 预测 下 一个 词 的 probability 。                                          |
| 原始Mask输入 | 使 用 语 言 \[MASK] 型 来 \[MASK] 测 下 一 个 词 的 pro \[MASK] ##lity 。                  |
| 全词Mask输入 | 使 用 语 言 \[MASK] \[MASK] 来 \[MASK] \[MASK] 下 一 个 词 的 \[MASK] \[MASK] \[MASK] 。 |

与英文一样, 中文还是被拆解成单个字作为token, 只是在预训练过程中, 还需要借助中文分词工具, 在遮盖时对全词进行Mask. 使用的是[哈工大LTP](http://ltp.ai/)工具.

## ERNIE (Baidu)

ERNIE在Mask的时候, 加入了外部的知识, 从而使得模型可以获得更可靠的语言表示. 这些知识的学习是在训练中隐性地学习, 而不是直接将外部知识的embedding加入到模型结构中. 这里的外部知识指的是各种**实体**, **短语**等内容.

与原始的BERT的Mask进行对比:

![](https://1942165044-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI7KRyeBH5dlW-CkUtn%2Fsync%2F3da35e8a38074365950651606fcdbc00795da29d.png?generation=1621902410275991\&alt=media)

ERNIE使用了三种Mask机制:

* **basic-level masking**: 就是原始token级别的mask, 与原始BERT的mask方式是一样的, 按字进行
* **entity-level masking**: 识别出样本中的实体, 如同WWM一样, 对实体全词进行Mask
* **phrase-level masking**: 对短语全词进行Mask

这三种Mask是分三个阶段使用的.

* 在第一个阶段, 用的是basic-level masking, 采用基本层级的masking, 即随机mask掉中文中的一个字
* 第二阶段使用phrase-level masking, mask掉句子中一部分词组, 然后让模型预测这些词组; 在这个阶段, 词组的信息就被encoding到word embedding中了
* 第三个阶段使用entity-level masking, 将句子中的实体, 如人名, 机构名, 商品名等mask掉; 模型在训练完成后, 也就学习到了这些实体的信息

![](https://1942165044-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI7KRyeBH5dlW-CkUtn%2Fsync%2Fda68b7391c891d271addbddea0b2bad5fc591a14.png?generation=1621902410785849\&alt=media)

### 实际实现

预训练过程中使用的每条样本, 由5部分组成:

* **token\_ids**: 输入句子对的token表示
* **sentence\_type\_ids**: 0或者1表示token属于哪一个句子
* **position\_ids**: 绝对位置编码
* **seg\_labels**: 表示分词边界信息. 0表示词首, 1表示非词首, -1为占位符
* **next\_sentence\_label**: 表示该句子对是否存在上下句的关系, 0为无1为有

在源码中, 多阶段分步mask机制实际上是对Batch内的每个样本, 以某个概率随机决定样本句子是mask\_word还是mask\_char模式:

* **mask\_char**: 即mask单字, 跟原始BERT一致
* **mask\_word**: 利用分词边界信息进行phrase-level masking

对单字的mask操作简单, 对词的mask, 需要使用到**分词边界**的信息. 在对词进行mask时, 以词为单位判断是否进行mask, 因此只需要根据分词边界, 用每个词的第一个字作为代表, 随机决定是否应该被mask. 如果要mask则遮盖整个词, 否则整个词都不要被mask.

### 参考资料

* [详解百度ERNIE进化史及典型应用场景](https://zhuanlan.zhihu.com/p/137532642)
* [ERNIE的掩码机制代码解析](https://blog.csdn.net/weixin_41089007/article/details/103657080)

## 参考资料

* [bert家族中的mask机制](https://zhuanlan.zhihu.com/p/360982134)
* [人生如戏，全靠演技；Bert 善变，全靠 MASK](https://zhuanlan.zhihu.com/p/248939893)
