BERT详细介绍

BERT详细介绍

概述

代表模型BERT。BERT是2018年10月由Google AI研究院提出的一种预训练模型.

  • BERT的全称是 Bidirectional Encoder Representation from Transformers.
  • BERT在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩: 全部两个衡量指标上全面超越人类, 并且在11种不同NLP测试中创出SOTA表现. 包括将GLUE基准推高至80.4% (绝对改进7.6%), MultiNLI准确度达到86.7% (绝对改进5.6%). 成为NLP发展史上的里程碑式的模型成就.

架构

总体架构: 如下图所示, 最左边的就是BERT的架构图, 可以很清楚的看到BERT采用了Transformer Encoder block进行连接, 因为是一个典型的双向编码模型.

从上面的架构图中可以看到, 宏观上BERT分三个主要模块:

  • 最底层黄色标记的Embedding模块.
  • 中间层蓝色标记的Transformer模块.
  • 最上层绿色标记的预微调模块.

2.1 输入部分

$$E = E_{token} + E_{segment} + E_{position}$$
  1. Token Embeddings 是词嵌入张量, 第一个单词是CLS标志, 可以用于之后的分类任务.
  2. Segment Embeddings 是句子分段嵌入张量, 是为了服务后续的两个句子为输入的预训练任务.
  3. Position Embeddings 是位置编码张量, 此处注意和传统的Transformer不同, 不是三角函数计算的固定位置编码, 而是通过学习得出来的.

2.2 多头注意力

$$Attention(Q,K,V) = softmax\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$$$head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)$$$$MultiHead(Q,K,V) = Concat(head_1, ..., head_h)W^O$$

2.3 层归一化

层归一化,对输入数据缩放,有效的控制方差,提升训练的稳定性

$$LayerNorm(x) = \gamma \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta$$

2.4 前馈网络层

Feed Forward,线性层+激活层+线性层。激活函数使用GELU。

$$FFN(x) = GELU(xW_1 + b_1)W_2 + b_2$$

2.5 激活函数

在前馈网络(FFN)使用 GELU作为激活函数

GELU函数的精确定义为:

$GELU(x) = x \cdot \Phi(x)$

其中$\Phi(x)$是标准正态分布的累积分布函数(CDF),Bert实际上做了近似处理,计算效率更高。

$GELU \simeq 0.5x(1+\tanh(\frac 2 \pi(x+0.044715x^3))) $

注意RoBERTa / GPT-1/2 均采用该近似版本。

Bert为什么选择GELU而不是RELU

面试一句话总结:

BERT 使用的激活函数是 GELU,它是一种基于高斯分布的平滑激活函数,相比 ReLU 在负值区间处理更柔和,有助于提升模型表达能力和训练稳定性。


预训练任务

MaskedLM(完形填空)

NSP(是否下句子预测)

微调任务

4.1 单句分类任务

应用的任务包括

  • 意图识别
  • 新闻文本分类
  • 情感分类

计算原理

  1. 取[CLS]向量,维度为[B, H]
  2. 接入分类层,本质上做线形变换$W\in \mathbb R ^{H\times C}$
  3. 最终激活函数使用softmax/sigmoid,得到归一化概率
  4. 概率最大的作为分类标签

4.2 句子对分类任务

应用的任务包括

  • 句子对的相似度计算
  • 文本是否匹配

计算原理

  1. 取[CLS]向量,维度为[B, H]
  2. 接入分类层,本质上做线形变换$W\in \mathbb R ^{H\times C}$
  3. 最终激活函数使用softmax/sigmoid,得到归一化概率
  4. 概率最大的作为分类标签

4.3 序列标注任务

应用的任务包括

  • 实体识别NER、分词、词性标注

计算原理

  1. 取每个token向量,维度为[B, L, H]
  2. 接入分类层,本质上做线形变换$W\in \mathbb R ^{H\times C}$
  3. 最终激活函数使用softmax/sigmoid,得到归一化概率
  4. 针对token分类,概率最大的作为分类标签

4.4 基于原文的问答任务

基本不用了,不用看

源码分析

使用句子分类模型加载权重,查看模型的架构

from transformers import BertTokenizer, BertForSequenceClassification
model_path = "/Users/chan/projects/models/bert-base-chinese"
# 加载分词器
tokenizer = BertTokenizer.from_pretrained(model_path)
# 加载分类模型
model = BertForSequenceClassification.from_pretrained(model_path, num_labels=5)
print(model)
BertForSequenceClassification(
  (bert): BertModel(
    (embeddings): BertEmbeddings(
      (word_embeddings): Embedding(21128, 768, padding_idx=0) # 词嵌入向量
      (position_embeddings): Embedding(512, 768) # 位置编码向量
      (token_type_embeddings): Embedding(2, 768) # 句子归属向量
      (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (encoder): BertEncoder( # 编码器
      (layer): ModuleList(
        (0-11): 12 x BertLayer(
          (attention): BertAttention( # 注意力机制
            (self): BertSdpaSelfAttention( 
              (query): Linear(in_features=768, out_features=768, bias=True) # 查询
              (key): Linear(in_features=768, out_features=768, bias=True) # 键
              (value): Linear(in_features=768, out_features=768, bias=True) # 值
              (dropout): Dropout(p=0.1, inplace=False)
            )
            (output): BertSelfOutput( # 注意力机制输出
              (dense): Linear(in_features=768, out_features=768, bias=True) # 输出密集层
              (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
          )
          (intermediate): BertIntermediate( # FFN前馈层
            (dense): Linear(in_features=768, out_features=3072, bias=True) # 第一次线性变化
            (intermediate_act_fn): GELUActivation() # 激活函数
          )
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True) # 第二次线性变换
            (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
    )
    (pooler): BertPooler( # 池化层 取[CLS]的token输出
      (dense): Linear(in_features=768, out_features=768, bias=True)
      (activation): Tanh()
    )
  )
  (dropout): Dropout(p=0.1, inplace=False)
  (classifier): Linear(in_features=768, out_features=5, bias=True) # 线性分类层
)

分类任务

inputs = "hello world!"
inputs = tokenizer(inputs, return_tensors='pt')
output = model(**inputs) ## 前向传播
logits = output.logits # 未归一化的概率
# 取概率最大的标签,就是分类标签
logits.argmax()

MLM任务

from transformers import BertForMaskedLM

mlm = BertForMaskedLM.from_pretrained(model_path)
mlm_inputs = "[MASK]京是中国的首都"
mlm_inputs = tokenizer(mlm_inputs, return_tensors='pt')

output = mlm(**mlm_inputs)
logits = output.logits

index = 1  # [MASK] 位置在1索引
logits[:, index, :]

mlm_input_id = logits[:, index, :].argmax()

tokenizer.decode(mlm_input_id)
# [MASK] ===> 北

总结

本节内容主要讲解bert的原理,包括架构、预训练任务、微调任务、如何做分类任务,如何完成掩码词预测任务。重点需要掌握bert的架构以及微调分类任务。