通常情况下,增加预训练模型大小会带来效果的提升;然而,当模型大小达到一定的程度之后,就很难再进行了,因为受到了GPU内存和训练时间的限制。为了减小模型参数和模型训练时间,ALBERT提出了两种解决方法。ALBERT也是采用和Bert一样的Transformer的Encoder[1]结构,激活函数也是GLUE[2]。相比于Bert,ALBERT主要改进之处在于以下几点:Embedding因式分解(Factorized embedding parameterization)、层间参数共享(Cross-layer parameter sharing)、句子间关联损失(Inter-sentence coherence loss)。
我们用H表示隐藏层大小,E表示embedding维度,V表示词汇表的大小。
Bert base模型的Encoder输出大小(H)和embedding维度(E)都是768,然而,ALBERT认为词级别的embedding是没有上下文依赖的表述;而隐藏层的输出值,不仅包括了词本身的意思,还包含了上下文信息。理论上来说隐藏层包含的信息更多一些,因此应该让H >> E。所以ALBERT的embedding维度是小于encoder输出维度的。在NLP任务中,通常词典都很大,embedding矩阵的大小是E×V,如果和Bert一样让H = E,那么embedding矩阵的参数量会很大,并且在反向传播过程中,更新的内容也比较稀疏。
结合上述所说的两点,ALBERT采用了一种因式分解的方法来降低参数量。首先把one-hot向量映射到一个低纬度的空间,大小为E,然后再映射到一个高纬度的空间(H);从而把参数量从O(V×H)降低到了O(V×E + E×H),当E << H时,参数量就减少得很明显了。
Transformer中共享参数的方案有多种,比如,只共享全连接层,只共享Attention[1]层。ALBERT结合了上述两种方案,对全连接层和Attention都进行参数共享,也就是共享encoder所有的参数。同量级下的Transformer采用该共享方案后实际效果有所下降,但是参数量减少了很多,训练速度也提升了很多。
ALBERT减小了模型参数,提升了训练速度,且每一层的输出的embedding相比于BERT来说震荡幅度更小一些。可见参数共享其实是有稳定网络参数的作用的。
Bert的NSP任务实际上是一个二分类,训练数据的正样本是通过采样同一个文档中的两个连续的句子,而负样本是通过采用两个不同文档的句子。该任务主要是希望能提高下游任务的效果,例如自然语言推理任务。但是后续的研究发现该任务效果并不好,主要原因是其任务过于简单。NSP其实包含两个子任务,主题预测与关系一致性预测,但是主题预测相比于关系一致性预测简单太多了,并且在MLM任务中其实也有类似的效果。
这里解释一下为什么包含了主题预测,因为正样本是在同一个文档中选取的,负样本是在不同的文档选取的,假如我们有2个文档,一个是娱乐相关的,一个是新中国成立70周年相关的,那么负样本选择的内容就是不同的主题,而正样都在娱乐文档中选择的话预测出来的主题就是娱乐,在新中国成立70周年的文档中选择的话就是后者这个主题了。
在ALBERT中,为了只保留一致性任务去除主题识别的影响,提出了一个新的任务Sentence-Order Prediction(SOP),SOP的正样本和NSP的获取方式是一样的,负样本把正样本的顺序反转即可。由于SOP是在同一个文档中选的,其只关注句子的顺序并没有主题方面的影响。并且SOP能解决NSP的任务,但是NSP并不能解决SOP的任务,该任务的添加让最终的结果提升了一个点。
注:上述内容参考博客以及原始论文ALBERT[3]。
transformers是一个很强大的预训练模型库,实现了几乎所有开源的预训练模型的调用接口【主页】。albert_xlarge_zh使用指南
"""Convert ALBERT checkpoint."""
import argparse
import logging
import torch
from transformers import AlbertConfig, AlbertForPreTraining, load_tf_weights_in_albert
logging.basicConfig(level=logging.INFO)
def convert_tf_checkpoint_to_pytorch(tf_checkpoint_path, albert_config_file, pytorch_dump_path):
# Initialise PyTorch model
config = AlbertConfig.from_json_file(albert_config_file)
print("Building PyTorch model from configuration: {}".format(str(config)))
model = AlbertForPreTraining(config)
# Load weights from tf checkpoint
load_tf_weights_in_albert(model, config, tf_checkpoint_path)
# Save pytorch-model
print("Save PyTorch model to {}".format(pytorch_dump_path))
torch.save(model.state_dict(), pytorch_dump_path)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
# Required parameters
parser.add_argument(
"--tf_checkpoint_path", default=None, type=str, required=True, help="Path to the TensorFlow checkpoint path."
)
parser.add_argument(
"--albert_config_file",
default=None,
type=str,
required=True,
help="The config json file corresponding to the pre-trained ALBERT model. \n"
"This specifies the model architecture.",
)
parser.add_argument(
"--pytorch_dump_path", default=None, type=str, required=True, help="Path to the output PyTorch model."
)
args = parser.parse_args()
convert_tf_checkpoint_to_pytorch(args.tf_checkpoint_path, args.albert_config_file, args.pytorch_dump_path)
'''
python convert_albert_tf_checkpoint_to_pytorch.py \
--tf_checkpoint_path=./albert_xlarge/model.ckpt-best \
--albert_config_file=./albert_xlarge/albert_config.json \
--pytorch_dump_path=./albert_xlarge/pytorch_model.bin
'''
from transformers import BertTokenizer, AlbertModel
tokenizer = BertTokenizer.from_pretrained("./albert_xlarge")
bert_model = AlbertModel.from_pretrained("./albert_xlarge")
"""下游微调任务"""
[1] Attention Is All You Need
[2] Gaussian Error Linear Units (GELUs)
[3] ALBERT: A Lite BERT for Self-supervised Learning of Language Representations