在大航哥的建议下,阅读了Transformers的Bart源代码:modeling_bart.py。
这里记录一下阅读时的思路和笔记,主要是从软件工程的视角梳理了一下源代码中几个类之间的关系,方便后续的理解、使用和修改。至于每个类中一些中间结果的shape,则可以自己在调试的时候跟进去看。
在这里我会以自顶向下的方式来分析:
- 首先对整个modeling_bart.py 文件中类的关系进行分析和介绍
- 然后介绍父类的作用:PretrainedBartModel、PreTrainedModel
- 接下来以 BartForConditionalGeneration 为例子进行介绍,modeling_bart.py 文件中的三个功能类都使用到了 BartModel
- 因此,最后分析 BartModel
以及构成它的相关组件(咕咕咕):BartEncoder, BartDecoder,EncoderLayer 和 DecoderLayer
1. 类的关系总览
modeling_bart.py 文件中,类之间的关系如下图所示:
- PretrainedBartModel 是 BartModel,BartForConditionalGeneration,BartForSequenceClassification 和 BartForQuestionAnswering的父类
- BartForConditionalGeneration,BartForSequenceClassification 和 BartForQuestionAnswering 在实现功能的过程中都使用了BartModel
- BartModel 就是 BART 的 backbone network。BART 本质上还是一个 Encoder Decoder 的架构,因此,在 BartModel 中由 BartEncoder 和 BartDecoder组成
- BartEncoder 和 BartDecoder中,分别由多个 EncoderLayer 和 DecoderLayer构成
2. PretrainedBartModel 和 PreTrainedModel
在具体地介绍 BartModel 之前,先来看看他的父类 PretrainedBartModel。
PretrainedBartModel 继承自 PreTrainedModel 类。
PreTrainedModel 类提供了诸如:from_pretrained(),init_weights() 等方法。所以PretrainedBartModel 也拥有PreTrainedModel 类的方法啦,比较常用的就是from_pretrained():
我们先从一个具体的功能类 BartForConditionalGeneration 来看。BartForConditionalGeneration 可用于摘要生成,也可以做 mask filling 的任务(需要使用bart-large)。
3.1 __init()__ 函数从代码中可以看出,BartForConditionalGeneration 在初始化时,创建了 BartModel 的对象,并赋值给了self.model。
因此,BartModel 主干网络是我们后面需要重点分析的部分。
然后我们直接来看 forward() 函数,参数可以分为 encoder 的部分和 decoder 的部分。
这里有个 past_key_values 变量,我们留到后面再讲,先把整体的流程摸清楚。
具体的参数应该传入什么,可以参考github 上的issue:Can I training a bart model from scratch by transformers?
在 forward() 的函数体中,首先,把参数传入到 self.model,也就是 BartModel 的对象中,这里其实是调用了 BartModel 的forward() 方法。得到输出结果 outputs,outputs 包含了 encoder 和 decoder 在 encode 和 decode 过程中的一些中间结果,可以从 return 语句看出,包括了 decoder_hidden_states,decoder_attentions, cross_attentions, encoder_last_hidden_state等信息。
然后将输出结果 outputs 中的 decoder hidden state:outputs[0] 传入到全连接层中,得到 lm_logits。lm_logits 可以在 mask filling 时判断 [mask] 处应该填入什么单词。
接下来,如果 labels 不为空的话,则通过 CrossEntropyLoss() 计算 masked_lm_loss。
ok,接下来就是 BartModel 这个重头戏了。
4.1 __init()__ 函数首先,创建了一个embedding函数,赋值给了self.shared,用于在后面的 BartEncoder 和 BartDecoder 中提取embedding。
然后创建了 BartEncoder 和 BartDecoder 的对象,分别用于 encode 和 decode 。因此,我们后面需要进一步分析 BartEncoder 和 BartDecoder 。
最后通过init_weights() 函数初始化权重。
4.2 forward()函数首先通过 encoder 得到 encoder_outputs:
- 当 encoder_outputs 为None时,会调用 BartModel 中的 self.encoder 进行编码(self.encoder 是 BartEncoder 的对象);
- 当给定了 encoder_outputs 时,则直接将其封装到 BaseModelOutput 中,并实例化给 encoder_outputs 。猜测这个写法是方便用户使用不同的 encoder?
然后将 decoder 的相关参数以及 encoder_outputs[0] (encoder 的 last_hidden_state)传入到 self.decoder 中,得到 decoder_outputs。
最终将 decoder_outputs 的内容封装到 Seq2SeqModelOutput 并返回该类的对象。
至此,我们已经得到了 BartForConditionalGeneration 中的 outputs。
并且,可以看到,outputs[0] 就是 decoder_outputs.last_hidden_state。
后面还可以再继续深入了解BartEncoder等内容,溜了…
参考资料
- BART源码剖析(transformers 4.9.0)



