预训练BERT学习笔记_Storm*Rage的博客

Transformer的Attention

Decoder的结构与Encoder相比多了一个Encoder-Decoder Attention,两个Attention分别用于计算输入和输出的权值:

Self-Attention:当前翻译和已经翻译的前文之间的关系;
Encoder-Decnoder Attention:当前翻译和编码的特征向量之间的关系。
  Encoder和Decoder的结构下图所示:

2.4 Encoder-Decoder Attention
  在解码器中,Transformer block比编码器中多了个encoder-cecoder attention。在encoder-decoder attention中,Q QQ来自与解码器的上一个输出,K KK和V VV则来自于编码器的输出。

  由于在机器翻译中,解码过程是一个顺序操作的过程,也就是当解码第 k kk个特征向量时,只能看到第 k − 1 k-1k−1及其之前的解码结果,论这种情况下的multi-head attention被视为masked multi-head attention。

  BERT主要分为三层,embedding层、encoder层、prediction层。

3.2 Encoder层
  Encoder层则和Transformer encoder基本相同,主要完成两个自监督任务,即MLM和NSP。

3.2.1 Masked Language Model
  Masked Language Model(MLM)是指在训练的时候随机从输入语料上mask掉一些单词,然后通过的上下文预测该单词,该任务像完形填空。
  在BERT的实验中,15%的WordPiece Token会被随机Mask掉。在训练模型时,一个句子会被多次传递到模型中用于参数学习,但是并不是每次都mask掉这些单词,而是在确定要mask掉的单词之后,80%的时候会直接替换为[mask],10%的时候将其替换为其它任意单词,10%的时候会保留原始Token。
  因为如果句子中的某个Token 100%都会被mask掉,那么在fine-tuning的时候模型就会有一些没有见过的单词。加入随机Token的原因是因为Transformer要保持对每个输入token的分布式表征,否则模型就会记住这个[mask]与原单词相同。而因为一个单词被随机替换掉的概率只有15 % × 10 % = 1.5 % 15\% \times 10\% =1.5\%15%×10%=1.5%,负面影响可以忽略不计的。另外由于每次只预测15%的单词,因此模型收敛的比较慢。

3.2.2 Next Sentence Prediction
  Next Sentence Prediction(NSP)的任务是判断句子B是否是句子A的下文。如果是的话输出’IsNext’,否则输出’NotNext’。训练数据的生成方式是从平行语料中随机抽取的连续两句话,其中50%保留抽取的两句话,它们符合IsNext关系,另外50%的第二句话随机从语料中提取,符合NotNext。这个关系保存在[CLS]符号中。

3.3 prediction层
  Prediction层采用线性全连接并做softmax归一化。在不同的下游任务使用中,可以把bert理解为一个特征抽取encoder,根据下游任务灵活使用。BERT应用的四个场景:

语句对分类,如语句相似度任务,语句蕴含判断等
单语句分类,如情感分类
QA任务,如阅读理解,将question和document构建为语句对,输出start和end的位置即可
序列标注,如NER,从每个位置得到类别即可。
  对于NSP任务来说,其条件概率表示为P = s o f t m a x ( C W T ) P=softmax(CW^T)P=softmax(CW 
T
 ),其中C CC是BERT输出中的[CLS]符号,W WW是可学习的权值矩阵。对于其它任务来说,可以根据BERT的输出信息作出对应的预测,在BERT的基础上再添加一个输出层便可以完成对特定任务的微调。


  其中Tok表示不同的Token,E EE表示嵌入向量,T i T_iT 
i

 表示第i ii个Token在经过BERT处理之后得到的特征向量。

  微调的任务包括:

1.基于句子对的分类
MNLI:给定一个前提(Premise),根据这个前提去推断假设(Hypothesis)与前提的关系。该任务的关系分为三种,蕴含关系(Entailment)、矛盾关系(Contradiction)以及中立关系(Neutral)。
QQP:基于Quora,判断 Quora 上的两个问题句是否表示的是一样的意思。
QNLI:用于判断文本是否包含问题的答案,类似于我们做阅读理解定位问题所在的段落。
STS-B:预测两个句子的相似性,包括5个级别。
MRPC:判断两个句子是否是等价的。
RTE:类似于MNLI,但是只是对蕴含关系的二分类判断。
SWAG:从四个句子中选择为可能为前句下文的那个。
2.基于单个句子的分类
SST-2:电影评价的情感分析。
CoLA:句子语义判断,是否是可接受的(Acceptable)。
3.问答任务
SQuAD v1.1:给定一个句子(通常是一个问题)和一段描述文本,输出这个问题的答案,类似于做阅读理解的简答题。
4.命名实体识别
CoNLL-2003 NER:判断一个句子中的单词是不是Person,Organization,Location,Miscellaneous或者other(无命名实体)。
四、源码分析
  分析的源码为基于PyTorch的HuggingFace Transformer。源代码地址:https://github.com/huggingface/transformers。入口类为BertModel。

4.1 入口和总体架构
  使用bert进行下游任务fine-tune时,通常先构造一个BertModel,然后由它从输入语句中提取特征,得到输出。

class BertModel(BertPreTrainedModel):

    def __init__(self, config):
        super().__init__(config)
        self.config = config

        self.embeddings = BertEmbeddings(config) # embedding向量输入层
        self.encoder = BertEncoder(config) # encoder编码层
        self.pooler = BertPooler(config) # pooler输出层,CLS输出

        self.init_weights()

    # 获取embedding层的word_embedding,
    def get_input_embeddings(self):
        return self.embeddings.word_embeddings

    # 利用别的数据来初始化word_embeddings
    def set_input_embeddings(self, value):
        self.embeddings.word_embeddings = value
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  构造方法主要有以下三点:

读取配置config,包括vocab_size、num_attention_heads、num_hidden_layers等参数,一般在配置文件bert_config.json中
构造embedding、encoder、pooler三个对象,分别对应embedding层、encoder层、prediction层。
利用pretrain model初始化weights,进行多头剪枝prune_heads等。
  从输入语句提取特征,并得到输出,代码如下

def forward(
    self,
    input_ids=None,
    attention_mask=None,
    token_type_ids=None,
    position_ids=None,
    head_mask=None,
    inputs_embeds=None,
    encoder_hidden_states=None,
    encoder_attention_mask=None,
    output_attentions=None,
    output_hidden_states=None,
    return_dict=None,
    ):
    ··· # 预处理
    # embedding层,包括word_embedding, position_embedding, token_type_embedding
    embedding_output = self.embeddings(
        input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids, inputs_embeds=inputs_embeds
    )
    # encoder层,得到每个位置的编码、所有中间隐层、所有中间attention分布
    encoder_outputs = self.encoder(
        embedding_output,
        attention_mask=extended_attention_mask,
        head_mask=head_mask,
        encoder_hidden_states=encoder_hidden_states,
        encoder_attention_mask=encoder_extended_attention_mask,
        output_attentions=output_attentions,
        output_hidden_states=output_hidden_states,
        return_dict=return_dict,
    )
    sequence_output = encoder_outputs[0]
    # CLS位置编码向量
    pooled_output = self.pooler(sequence_output)

    if not return_dict:
        # 返回每个位置编码、CLS位置编码、所有中间隐层、所有中间attention分布等。
        return (sequence_output, pooled_output) + encoder_outputs[1:]

    return BaseModelOutputWithPooling(
        last_hidden_state=sequence_output,
        pooler_output=pooled_output,
        hidden_states=encoder_outputs.hidden_states,
        attentions=encoder_outputs.attentions,
    )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  在上述从输入语句中抽取特征的过程中,有以下行为:

embedding层,对input_ids、position_ids、token_type_ids进行embedding
encoder层,embedding后的结果,经过多层Transformer encoder,得到输出。每一层encoder结构基本相同,均包括multi-head self-attention和feed-forward,并经过layer-norm和残差连接
pooler层,对CLS位置进行线性全连接,将它作为整个sequence的输出。
  最终返回4个结果:

sequence_output:每个位置的编码输出,每个位置对应一个向量
pooled_output: CLS位置编码输出,经过了一层Linear和activation。一般用CLS来代表整个语句
hidden_states:所有中间层的隐层,这个需要在config中打开,才会保存下来
attentions: 所有中间层的attention分布,这个也需要在config中打开,才会保存。
4.2 Embedding层
class BertEmbeddings(nn.Module):

    def __init__(self, config):
        super().__init__()
        self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
        self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
        self.token_type_embeddings = nn.Embedding(config.type_vocab_size, config.hidden_size)

        # layerNorm归一化和dropout。layerNorm对归一化做一个线性连接,故有训练参数
        self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)

        self.register_buffer("position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)))

    def forward(self, input_ids=None, token_type_ids=None, position_ids=None, inputs_embeds=None):
        # 获取input_shape, [batch, seq_length]
        if input_ids is not None:
            input_shape = input_ids.size()
        else:
            input_shape = inputs_embeds.size()[:-1]

        seq_length = input_shape[1]
        # position_ids默认按照字的顺利进行编码,不足补0
        if position_ids is None:
            position_ids = self.position_ids[:, :seq_length]
        # token_type_ids默认全0
        if token_type_ids is None:
            token_type_ids = torch.zeros(input_shape, dtype=torch.long, device=self.position_ids.device)
        # 通过embedding_lookup查表,将ids向量化
        if inputs_embeds is None:
            inputs_embeds = self.word_embeddings(input_ids)
        position_embeddings = self.position_embeddings(position_ids)
        token_type_embeddings = self.token_type_embeddings(token_type_ids)
        # 最终embedding为三者直接相加,权值包含在embedding本身训练参数中
        embeddings = inputs_embeds + position_embeddings + token_type_embeddings
        # 归一化和dropout后,得到最终输入向量
        embeddings = self.LayerNorm(embeddings)
        embeddings = self.dropout(embeddings)
        return embeddings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  Embedding层的具体行为包括:

从三个embedding表中,通过id查找到对应向量。三个embedding表为word_embeddings,position_embeddings,token_type_embeddings。均是在train阶段训练得到。
三个embedding向量直接相加,得到总embedding。
对总embedding进行归一化和dropout。
4.3 Encoder层
class BertEncoder(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])

    def forward(
        self,
        hidden_states,
        attention_mask=None,
        head_mask=None,
        encoder_hidden_states=None,
        encoder_attention_mask=None,
        output_attentions=False,
        output_hidden_states=False,
        return_dict=False,
    ):
        all_hidden_states = () if output_hidden_states else None
        all_attentions = () if output_attentions else None
        # 遍历所有layer。bert中每个layer结构相同
        for i, layer_module in enumerate(self.layer):
            # 保存每层hidden_state
            if output_hidden_states:
                all_hidden_states = all_hidden_states + (hidden_states,)

            if getattr(self.config, "gradient_checkpointing", False):

                def create_custom_forward(module):
                    def custom_forward(*inputs):
                        return module(*inputs, output_attentions)

                    return custom_forward
                # 执行每层self-attention和feed-forward计算。得到隐层输出
                layer_outputs = torch.utils.checkpoint.checkpoint(
                    create_custom_forward(layer_module),
                    hidden_states,
                    attention_mask,
                    head_mask[i],
                    encoder_hidden_states,
                    encoder_attention_mask,
                )
            else:
                layer_outputs = layer_module(
                    hidden_states,
                    attention_mask,
                    head_mask[i],
                    encoder_hidden_states,
                    encoder_attention_mask,
                    output_attentions,
                )
            hidden_states = layer_outputs[0]
            # 保存每层attention分布,默认不保存
            if output_attentions:
                all_attentions = all_attentions + (layer_outputs[1],)
        # 保存最后一层
        if output_hidden_states:
            all_hidden_states = all_hidden_states + (hidden_states,)

        if not return_dict:
            return tuple(v for v in [hidden_states, all_hidden_states, all_attentions] if v is not None)
        return BaseModelOutput(
            last_hidden_state=hidden_states, hidden_states=all_hidden_states, attentions=all_attentions
        )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  Encoder由多个结构相同的子层BertLayer组成,遍历所有的子层,执行每层的self-attention和feed-forward计算,并保存每层的hidden_state和attention分布。

4.3.1 BertLayer子层
class BertLayer(nn.Module):
    def __init__(self, config):
        super().__init__()
        # multi-head self attention层
        self.attention = BertAttention(config)
        self.is_decoder = config.is_decoder
        if self.is_decoder:
            # 对于decoder,cross-attention和self-attention共用一个函数
            self.crossattention = BertAttention(config)
        # 两层feed-forward全连接,然后残差并layerNorm输出
        self.intermediate = BertIntermediate(config)
        self.output = BertOutput(config)

    def forward(
        self,
        hidden_states,
        attention_mask=None,
        head_mask=None,
        encoder_hidden_states=None,
        encoder_attention_mask=None,
        output_attentions=False,
    ):
        # self-attention:attention_mask 和 head_mask
        self_attention_outputs = self.attention(
            hidden_states, attention_mask, head_mask, output_attentions=output_attentions,
        )
        # hidden state隐层输出
        attention_output = self_attention_outputs[0]
        # attention分布
        outputs = self_attention_outputs[1:]  # add self attentions if we output attention weights
        # decoder在self-attention结束后进行soft-attention
        if self.is_decoder and encoder_hidden_states is not None:
            cross_attention_outputs = self.crossattention(
                attention_output,
                attention_mask,
                head_mask,
                encoder_hidden_states,
                encoder_attention_mask,
                output_attentions,
            )
            attention_output = cross_attention_outputs[0]
            outputs = outputs + cross_attention_outputs[1:]  # add cross attentions if we output attention weights
        # feed-forward和layerNorm归一化
        intermediate_output = self.intermediate(attention_output)
        layer_output = self.output(intermediate_output, attention_output)
        # 输出hidden_state隐层和attention分布
        outputs = (layer_output,) + outputs
        return outputs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  BertLayer实现的功能包括:

multi-head self-attention, 支持attention_mask和head_mask
如果作decoder的话,self-attention结束后进行一层cross-attention,将encoder信息和decoder信息产生交互
feed-forward全连接和layerNorm归一化。
4.3.2 BertAttention注意力计算
class BertAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.self = BertSelfAttention(config)
        self.output = BertSelfOutput(config)
        self.pruned_heads = set()

    def prune_heads(self, heads):
        # 对每层多头进行裁剪,直接对权重矩阵剪枝
        if len(heads) == 0:
            return
        heads, index = find_pruneable_heads_and_indices(
            heads, self.self.num_attention_heads, self.self.attention_head_size, self.pruned_heads
        )

        # q,k,v和全连接上,加入mask
        self.self.query = prune_linear_layer(self.self.query, index)
        self.self.key = prune_linear_layer(self.self.key, index)
        self.self.value = prune_linear_layer(self.self.value, index)
        self.output.dense = prune_linear_layer(self.output.dense, index, dim=1)

        self.self.num_attention_heads = self.self.num_attention_heads - len(heads)
        self.self.all_head_size = self.self.attention_head_size * self.self.num_attention_heads
        self.pruned_heads = self.pruned_heads.union(heads)

    def forward(
        self,
        hidden_states,
        attention_mask=None,
        head_mask=None,
        encoder_hidden_states=None,
        encoder_attention_mask=None,
        output_attentions=False,
    ):
        # self-attention计算
        self_outputs = self.self(
            hidden_states, attention_mask, head_mask, encoder_hidden_states, encoder_attention_mask, output_attentions,
        )
        # 残差连接和归一化
        attention_output = self.output(self_outputs[0], hidden_states)
        # 输出归一化后隐层,和attention概率分布
        outputs = (attention_output,) + self_outputs[1:]  
        return outputs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  BertAttention主要包括:self-attention计算和归一化残差连接。

4.3.3 BertIntermediate全连接
class BertIntermediate(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
        if isinstance(config.hidden_act, str):
            self.intermediate_act_fn = ACT2FN[config.hidden_act]
        else:
            self.intermediate_act_fn = config.hidden_act

    def forward(self, hidden_states):
        # 全连接,[hidden_size, intermediate_size]
        hidden_states = self.dense(hidden_states)
        # 非线性激活,如glue,relu。bert默认使用glue
        hidden_states = self.intermediate_act_fn(hidden_states)
        return hidden_states
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  该模块主要进行全连接和非线性激活。

4.3.4 BertOutput输出
class BertOutput(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.intermediate_size, config.hidden_size)
        self.LayerNorm = BertLayerNorm(config.hidden_size, eps=config.layer_norm_eps)
        self.dropout = nn.Dropout(config.hidden_dropout_prob)

    def forward(self, hidden_states, input_tensor):
        # 全连接, [intermediate_size, hidden_size]
        hidden_states = self.dense(hidden_states)
        # dropout
        hidden_states = self.dropout(hidden_states)
        # 归一化和残差连接
        hidden_states = self.LayerNorm(hidden_states + input_tensor)
        return hidden_states
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  输出层也比较简单经过一层全连接、dropout、layerNorm归一化和残差连接,得到输出隐层。

4.4 Pooler层输出
class BertPooler(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.dense = nn.Linear(config.hidden_size, config.hidden_size)
        self.activation = nn.Tanh()

    def forward(self, hidden_states):
        # CLS位置输出
        first_token_tensor = hidden_states[:, 0]
        # 全连接 + tanh激活 
        pooled_output = self.dense(first_token_tensor)
        pooled_output = self.activation(pooled_output)
        return pooled_output

  Pooler层对CLS位置向量,进行全连接和tanh激活,从而得到输出向量。CLS位置向量一般用来代表整个sequence。
 


 

https://blog.youkuaiyun.com/weixin_43886056/article/details/107960402

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值