目录

1 迁移学习

2 BERT

2.1 定义

2.2 架构

2.3 创新

2.4 代码

2.5 微调

3 优化算法

3.1 梯度下降(Gradient Descent)

3.2 随机梯度下降SGD

3.3 冲量法Momentum

3.4 Adam


BERT、优化算法必学。

1 迁移学习

(1)使用预训练好的模型抽取词、句子的特征(如word2vec或语言模型);

(2)不更新预训练好的模型;

(3)需要构建新的网络来抓去新任务需要的信息(word2vec忽略了时序信息,语言模型只看了一个方向)。

2 BERT

2.1 定义

基于微调的NLP模型;

预训练的模型抽取了足够多的信息;

新任务只需要增加一个简单的输出层。

2.2 架构

只有编码器的Transformer;

两个版本,Base:#blocks=12,hidden size=768,#heads=12,#parameters=110M

                  Large:#blocks=24,hidden size=1024,#heads=16,#parameters=340M

2.3 创新

(1)输入

Transformer每个样本是一个句子对,BERT通过分隔符拼起来,ID做区分,位置编码机器学不人工设计。

(2)带掩码的语言模型

Transformer的编码器是双向,标准语言模型要求单向;

带掩码的语言模型每次随机将一些词元换成mask;

因微调时不出现mask,80%概率将选中的次元变成mask、10%概率换成一个随机次元、10%概率保持原有词元。

(3)下一句子预测

预测一个句子对中两个句子是不是相邻,训练样本中50%概率选中相邻句子对、50%概率选择随机句子对,将对应输出放到全连接层预测。

2.4 代码
import torch
from torch import nn
from d2l import torch as d2l

(1)输入表示

#@save
def get_tokens_and_segments(tokens_a, tokens_b=None):
    """获取输入序列的词元及其片段索引"""
    tokens = ['<cls>'] + tokens_a + ['<sep>']
    # 0和1分别标记片段A和B
    segments = [0] * (len(tokens_a) + 2)
    if tokens_b is not None:
        tokens += tokens_b + ['<sep>']
        segments += [1] * (len(tokens_b) + 1)
    return tokens, segments

#@save
class BERTEncoder(nn.Module):
    """BERT编码器"""
    def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
                 ffn_num_hiddens, num_heads, num_layers, dropout,
                 max_len=1000, key_size=768, query_size=768, value_size=768,
                 **kwargs):
        super(BERTEncoder, self).__init__(**kwargs)
        self.token_embedding = nn.Embedding(vocab_size, num_hiddens)
        self.segment_embedding = nn.Embedding(2, num_hiddens)
        self.blks = nn.Sequential()
        for i in range(num_layers):
            self.blks.add_module(f"{i}", d2l.EncoderBlock(
                key_size, query_size, value_size, num_hiddens, norm_shape,
                ffn_num_input, ffn_num_hiddens, num_heads, dropout, True))
        # 在BERT中,位置嵌入是可学习的,因此我们创建一个足够长的位置嵌入参数
        self.pos_embedding = nn.Parameter(torch.randn(1, max_len,
                                                      num_hiddens))

    def forward(self, tokens, segments, valid_lens):
        # 在以下代码段中,X的形状保持不变:(批量大小,最大序列长度,num_hiddens)
        X = self.token_embedding(tokens) + self.segment_embedding(segments)
        X = X + self.pos_embedding.data[:, :X.shape[1], :]
        for blk in self.blks:
            X = blk(X, valid_lens)
        return X

vocab_size, num_hiddens, ffn_num_hiddens, num_heads = 10000, 768, 1024, 4
norm_shape, ffn_num_input, num_layers, dropout = [768], 768, 2, 0.2
encoder = BERTEncoder(vocab_size, num_hiddens, norm_shape, ffn_num_input,
                      ffn_num_hiddens, num_heads, num_layers, dropout)

tokens = torch.randint(0, vocab_size, (2, 8))
segments = torch.tensor([[0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 1, 1, 1, 1, 1]])
encoded_X = encoder(tokens, segments, None)
encoded_X.shape

(3)预训练

#@save
class MaskLM(nn.Module):
    """BERT的掩蔽语言模型任务"""
    def __init__(self, vocab_size, num_hiddens, num_inputs=768, **kwargs):
        super(MaskLM, self).__init__(**kwargs)
        self.mlp = nn.Sequential(nn.Linear(num_inputs, num_hiddens),
                                 nn.ReLU(),
                                 nn.LayerNorm(num_hiddens),
                                 nn.Linear(num_hiddens, vocab_size))

    def forward(self, X, pred_positions):
        num_pred_positions = pred_positions.shape[1]
        pred_positions = pred_positions.reshape(-1)
        batch_size = X.shape[0]
        batch_idx = torch.arange(0, batch_size)
        # 假设batch_size=2,num_pred_positions=3
        # 那么batch_idx是np.array([0,0,0,1,1,1])
        batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)
        masked_X = X[batch_idx, pred_positions]
        masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
        mlm_Y_hat = self.mlp(masked_X)
        return mlm_Y_hat

mlm = MaskLM(vocab_size, num_hiddens)
mlm_positions = torch.tensor([[1, 5, 2], [6, 1, 5]])
mlm_Y_hat = mlm(encoded_X, mlm_positions)
mlm_Y_hat.shape

mlm_Y = torch.tensor([[7, 8, 9], [10, 20, 30]])
loss = nn.CrossEntropyLoss(reduction='none')
mlm_l = loss(mlm_Y_hat.reshape((-1, vocab_size)), mlm_Y.reshape(-1))
mlm_l.shape

(4)下一句预测

#@save
class NextSentencePred(nn.Module):
    """BERT的下一句预测任务"""
    def __init__(self, num_inputs, **kwargs):
        super(NextSentencePred, self).__init__(**kwargs)
        self.output = nn.Linear(num_inputs, 2)

    def forward(self, X):
        # X的形状:(batchsize,num_hiddens)
        return self.output(X)

encoded_X = torch.flatten(encoded_X, start_dim=1)
# NSP的输入形状:(batchsize,num_hiddens)
nsp = NextSentencePred(encoded_X.shape[-1])
nsp_Y_hat = nsp(encoded_X)
nsp_Y_hat.shape

nsp_y = torch.tensor([0, 1])
nsp_l = loss(nsp_Y_hat, nsp_y)
nsp_l.shape
2.5 微调

(1)选择任务类型

  句子分类、命名实体识别、问题回答等。

(2)构建输入格式

(3)在BERT顶部加一个小的任务头

(4)反向传播 + 更新所有参数

3 优化算法

3.1 梯度下降(Gradient Descent)

每次要用全部数据,太慢。

3.2 随机梯度下降SGD

快,收敛慢、震荡大、容易掉到局部最优。

3.3 冲量法Momentum

3.4 Adam

AdamW是Adam的改进版,对权重衰减进行了更正确的实现。大模型(如Transformer、BERT、GPT)几乎都用AdamW。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐