Transformer

一、机器翻译数据集预处理:

1.文本预处理意义:

通过文本预处理建立字典可以巧妙地将回归问题转化为分类问题来处理。

2.文本预处理过程:

  • 1.读入文本,对英语和德语文本分别处理:
    英语 德语
    Go. Va !
    Hi. Salut !
  • 2.token之间添加空格:
    go . va !
    hi . salut !
  • 3.按空格拆分句子中的token词:
    ([[‘go’, ‘.’],
    [‘hi’, ‘.’]],
    [[‘va’, ‘!’],
    [‘salut’, ‘!’]])
  • 4.将token转成字典序:
    ([[‘2’, ‘1’],
    [‘3’, ‘1’]],
    [[‘2’, ‘1’],
    [‘3’, ‘1’]])
  • 5.序列样本都有一个固定的长度3,截断或填充文本序列(每个句子):
    ([[‘2’, ‘1’, ‘0’],
    [‘3’, ‘1’, ‘0’]],
    [[‘2’, ‘1’, ‘0’],
    [‘3’, ‘1’, ‘0’]])
  • 6.每个文本序列添加句首句末标识
    ([[‘-1’,‘2’, ‘1’, ‘0’,‘-2’],
    [‘-1’,‘3’, ‘1’, ‘0’,‘-2’]],
    [[‘-1’,‘2’, ‘1’, ‘0’,‘-2’],
    [‘-1’,‘3’, ‘1’, ‘0’,‘-2’]])
  • 7.将每个样本序列(每个句子)转成batch,这里两个序列组成一个batch:
    tensor([[-1,2,1,0,-2],
    [-1,3,1,0,-2]], dtype=torch.int32)
    tensor([[-1,2,1,0,-2],
    [-1,3,1,0,-2]], dtype=torch.int32)
  • 8.将batch中的每个token使用独热编码转换成在字典上的概率一维向量:

3.代码:

import os
import torch
from d2l import torch as d2l

d2l.DATA_HUB['fra-eng'] = (d2l.DATA_URL + 'fra-eng.zip',
                           '94646ad1522d915e7b0f9296181140edcf86a4f5')

# 载入“英语-法语”数据集
def read_data_nmt():
    data_dir = d2l.download_extract('fra-eng')
    with open(os.path.join(data_dir, 'fra.txt'), 'r', encoding='utf-8') as f:
        return f.read()
raw_text = read_data_nmt()


# 预处理“英语-法语”数据集
def preprocess_nmt(text):
    # 标点符号和文本之间没有空格的加个空格
    def no_space(char, prev_char):
        return char in set(',.!?') and prev_char != ' '
    # utf-8处理
    text = text.replace('\u202f', ' ').replace('\xa0', ' ').lower()
    out = [
        ' ' + char if i > 0 and no_space(char, text[i - 1]) else char
        for i, char in enumerate(text)]
    return ''.join(out)
text = preprocess_nmt(raw_text)

# 词元化“英语-法语”数据数据集
def tokenize_nmt(text, num_examples=None):
    source, target = [], []
    # 按空格将文本拆分
    for i, line in enumerate(text.split('\n')):
        if num_examples and i > num_examples:
            break
        parts = line.split('\t')
        if len(parts) == 2:
            # 英语句子拆分
            source.append(parts[0].split(' '))
            # 法语句子拆分
            target.append(parts[1].split(' '))
    return source, target
source, target = tokenize_nmt(text)

# 将文本做成字典并转成字典序。对于长度小于等于2的句子,直接不要了;添加三个特殊token:<pad>填充,<bos>句首,<eos>句末
src_vocab = d2l.Vocab(source, min_freq=2,
                      reserved_tokens=['<pad>', '<bos>', '<eos>'])

# 截断或填充文本序列,将序列样本(每个句子)固定长度
def truncate_pad(line, num_steps, padding_token):
    # 句子长度大于num_steps截断
    if len(line) > num_steps:
        return line[:num_steps]
    # 句子长度小于num_steps使用<pad>填充
    return line + [padding_token] * (num_steps - len(line))
truncate_pad(src_vocab[source[0]], 10, src_vocab['<pad>'])

# 将机器翻译的文本序列转换成小批量,并添加句首句末特殊token
def build_array_nmt(lines, vocab, num_steps):
    lines = [vocab[l] for l in lines]
    lines = [l + [vocab['<eos>']] for l in lines]
    array = torch.tensor([
        truncate_pad(l, num_steps, vocab['<pad>']) for l in lines])
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
    return array, valid_len

# 整合上述函数,返回翻译数据集的dataloader和字典
def load_data_nmt(batch_size, num_steps, num_examples=600):
    text = preprocess_nmt(read_data_nmt())
    source, target = tokenize_nmt(text, num_examples)
    src_vocab = d2l.Vocab(source, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    tgt_vocab = d2l.Vocab(target, min_freq=2,
                          reserved_tokens=['<pad>', '<bos>', '<eos>'])
    src_array, src_valid_len = build_array_nmt(source, src_vocab, num_steps)
    tgt_array, tgt_valid_len = build_array_nmt(target, tgt_vocab, num_steps)
    data_arrays = (src_array, src_valid_len, tgt_array, tgt_valid_len)
    data_iter = d2l.load_array(data_arrays, batch_size)
    return data_iter, src_vocab, tgt_vocab

二、自注意力机制:

注意力机制讲解

1.定义:

在这里插入图片描述
通过自注意力机制可以获取每个token在样本中与其他所有token之间的相关性信息,能够更好地捕获每个token在样本中的上下文序列信息。

其中一个token(q)和另一个token(kv的相关性信息计算结果为一个值,该token和序列中其余所有token的相关性组成一个长为d的一维向量。

三、位置编码:

1.定义:

在这里插入图片描述
由于自注意力机制只关注了token之间的相关性,并没有关注token的位置信息,所以引入位置编码来提取序列中各个token的位置信息。

2.每个token如何计算位置编码:

在这里插入图片描述
通过在token(1×d)不同的特征维度上加入一些信息使得模型能够根据这些细微的信息来分辨样本中不同token的位置信息。

3.相对位置信息:

在这里插入图片描述
sin cos 进行位置编码可以方便的获取每个token相对于样本的相对位置,它有助于模型理解token之间的相对位置关系,但并不直接提供每个token相对于整个样本的绝对位置。

4.代码:

class PositionalEncoding(nn.Module):
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)
        # 位置编码初始化batch_size为1,有max_len个token,每个token有num_hiddens个特征
        self.P = torch.zeros((1, max_len, num_hiddens))
        # 位置编码公式
        X = torch.arange(max_len, dtype=torch.float32).reshape(
            -1, 1) / torch.pow(
                10000,
                torch.arange(0, num_hiddens, 2, dtype=torch.float32) /
                num_hiddens)
        self.P[:, :, 0::2] = torch.sin(X)
        self.P[:, :, 1::2] = torch.cos(X)

    def forward(self, X):
    	# 序列的数据信息和位置信息相加作为自注意力机制的输入
        X = X + self.P[:, :X.shape[1], :].to(X.device)
        return self.dropout(X)

四、嵌入层Embedding:

1.定义:

Embedding类似于的热编码one_hot,将token数值转换成一维向量表示。
具体来说,embedding会维护一个m×n的矩阵,矩阵是一个超参数在训练阶段不断更新,对于每个token,例如vocab中对应的字典序为3,则embedding会将其映射到其维护的矩阵的第3行,转换成一个1×n的矩阵。
在这里插入图片描述

五、Transformer:

1.Transformer架构:

在这里插入图片描述

2.多头注意力机制:

2.1文字定义:

在这里插入图片描述
多头注意力机制就是多个自注意力机制块的堆叠,输入的一个序列样本tokens=Queries=Keys=Values,其中每个token=qi=ki=vi。每个自注意力块都有各自的三个全连接层,不同自注意力块有不同的权重参数用来对输入序列tokens提取不同特征,最后不同注意力块中Attention的输入不同,因此可以从不同角度下捕获样本中token之间的相关性

  • 为什么多个自注意力机制块能从不同角度提取特征?
    答:因为自注意力机制块中的Attention输入不同。
  • 为什么每个头的Attention输入不同?
    答:因为每个注意力机制块在输入Attention前都使用各自的全连接层对序列tokens进行不同角度的特征提取,所以每个注意力机制块的Attention输入不同。
  • 全连接层为什么能对输入序列tokens从不同角度提取特征?
    答:因为每个自注意力机制块的全连接层有个各自的权重参数(Wq1,Wk1,Wv1),(Wq2,Wk2,Wv2)…所以对相同的输入序列tokens不同的自注意力机制块可以将序列转换成不同的输出提供给各自的Attention使用。

2.2数学定义:

在这里插入图片描述

2.3图像解释:

在这里插入图片描述

2.4代码:

import math
import torch
from d2l.torch import MultiHeadAttention
from torch import nn
from d2l import torch as d2l

class MultiHeadAttention(nn.Module):
    # 输入token的qkv特征向量维度key_size, query_size, value_size
    # num_hiddens是多头注意力机制前和前馈网络中的全连接层隐藏层大小
    # num_head是头数,决定了attention前全连接层个数
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        # 使用DotProductAttention注意力机制,好处是DotProductAttention中没有可学习参数
        self.attention = d2l.DotProductAttention(dropout)
        # 每个全连接层的输入输出一维向量的维度(特征维)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)

    def forward(self, queries, keys, values, valid_lens):
        # 对于输入计算每个头的QKV输出,这里通过维度转换巧妙地使用一个全连接层就能完成多个头的Q输出
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)

        if valid_lens is not None:
            valid_lens = torch.repeat_interleave(valid_lens,
                                                 repeats=self.num_heads,
                                                 dim=0)
        # 使用DotProductAttention计算每个头的注意力    
        output = self.attention(queries, keys, values, valid_lens)
        # 恢复维度,得到每个头的注意力
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)

def transpose_qkv(X, num_heads):
    # 将X(batch_size,num_steps,num_hiddens)转换成四维(batch_size,num_steps,num_heads,num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)
    # 转换各个维度的位置(batch_size,num_heads,num_steps,num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)
    # 转换成三维(batch_size×num_heads,num_steps,num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])

def transpose_output(X, num_heads):
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0, 2, 1, 3)
    return X.reshape(X.shape[0], X.shape[1], -1)


# 全连接层神经元个数,多头注意力机制头数
num_hiddens, num_heads = 100, 5
# key_size, query_size, value_size, num_hiddens ,num_heads,每个token的qkv长度都是num_hiddens一维特征向量
attention = MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens, num_hiddens, num_heads, dropout=0)
attention.eval()
# 一个batch有2个句子样本,每个样本有4个token,其中第一个样本的有效token数为3,第二个样本有效token数为2
# 在Encoder中,num_queries=num_kvs因为是同一个句子间的token进行计算。在Decoder第二个Attention中,num_queries!=num_kvs,因为kv是Encoder输出,q是Decoder输入,在英译德中表示待翻译德语的token和英语句子中的token进行计算
batch_size, num_queries, num_kvs, valid_lens = 2, 4, 4, torch.tensor([3, 2])
# 一个batch有2个句子样本,每个样本有4个token,每个token都是一个长度100的一维向量,表示各个字典序上的概率
X = torch.ones((batch_size, num_queries, num_hiddens))
Y = torch.ones((batch_size, num_kvs, num_hiddens))
# 输入维度(2, 4, 100),输出维度torch.Size([2, 4, 100])
attention(X, Y, Y, valid_lens).shape

3.带掩码的多头注意力机制:

3.1定义:

在这里插入图片描述
因为在训练过程中,预测的真实值标签是一次性输入给解码层的,例如预测序列“oh hello my world”时,训练时一次性奖序列传给解码层了,但是解码层是一个时间步预测一个单词,在预测“my”时解码层肯定不能使用序列后文"my world"这两个token真实值标签进行注意力计算只能使用前文已经与测过的真实值标签“oh hello”参与注意力计算

因此提出了掩码机制来控制多头注意力每个时间步进行注意力计算时仅能使用已经预测过的真实值标签,而掩藏后文的真实值标签不参与注意力分数计算。

3.2多头和掩码多头在输入上的区别******:

最大的区别就是掩码机制,上面已经说过了。

但是这里还要注意另一个区别(虽然上面也有体现):多头注意力机制每次都是一次性输入序列中的所有token进行相关性计算,但是带掩码的多头注意力机制时每次仅输入前k个已经预测过的那k个token进行相关性计算。

4.FNN前馈网络:

4.1定义:

在这里插入图片描述

4.2代码:

class PositionWiseFFN(nn.Module):
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        # 输入一维向量维度ffn_num_input=24,输出一维向量维度ffn_num_hiddens=48
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)
        self.relu = nn.ReLU()
        # 输入一维向量维度ffn_num_hiddens=48,输出一维向量维度ffn_num_outputs=24
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)

    def forward(self, X):
        # X表示样本数2,每个样本的token数3,每个token的一维特征向量长度为24,输入维度(2,3,24)
        # dense函数默认输入为二维(样本数,特征数),对于非二维输入,默认将所有高维都作为样本维
        # 先对X执行全连接层dense1,将(2×3,24)作为输入,输出(6,48)
        # 后执行全连接层dense2,将(6,48)作为输入,输出(6,24)
        return self.dense2(self.relu(self.dense1(X)))
    

ffn = PositionWiseFFN(24, 48, 24)
ffn.eval()
# 输入(2, 3, 24),输出torch.Size([2, 3, 24])
ffn(torch.ones((2, 3, 24))).shape    

5.层归一化:

归一化可以对batch中的数据进行缩放,用于加快模型的收敛速度。

批量归一化讲解

5.1定义:

在这里插入图片描述

5.2代码:

# 层归一化对每个样本的特征维进行归一化
ln = nn.LayerNorm(normalized_shape=[2])
# 输入维度(2, 3, 2)
X = torch.tensor([
    [[1, 2], [3, 4], [5, 6]],
    [[7, 8], [9, 10], [11, 12]]
], dtype=torch.float32)
# LayerNorm会分别对每个样本的 2 个特征(即每列)进行归一化即1,3,5进行归一化,2,4,6进行归一化
ln(X)

6.残差连接:

残差神经网络讲解

6.1定义:

残差连接将每层的输出定义为之前该层的输入+该层的输出,相比于传统的网络加入了快速通道,这样可以保证不管残差块堆叠多少层,都不会降低模型的精度。
在这里插入图片描述

7.编码层:

7.1编码层输出:

在这里插入图片描述

7.2编码层Block代码:

class EncoderBlock(nn.Module):
    # 输入token的qkv特征向量维度key_size, query_size, value_size
    # num_hiddens是多头注意力机制前和前馈网络中的全连接层隐藏层大小
    # norm_shape是层归一化应用于哪个维度
    # ffn_num_input, ffn_num_hiddens是前馈网络输入输出的特征维([batch_size,num_steps,features]第三维features)的维度
    # num_head是头数,决定了attention前全连接层个数
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, use_bias=False, **kwargs):
        super(EncoderBlock, self).__init__(**kwargs)
        # 多头注意力机制
        self.attention = d2l.MultiHeadAttention(key_size, query_size,
                                                value_size, num_hiddens,
                                                num_heads, dropout, use_bias)
        # 层归一化
        self.addnorm1 = AddNorm(norm_shape, dropout)
        # 前馈网络
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
                                   num_hiddens)
        # 层归一化
        self.addnorm2 = AddNorm(norm_shape, dropout)

    def forward(self, X, valid_lens):
        # 输入X(batch_size,num_steps,features)
        # 首先将X作为QKV执行多头注意力机制,第二步执行层归一化,第三步执行前馈网络,第四步执行层归一化
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))
        return self.addnorm2(Y, self.ffn(Y))

7.3编码层代码:

class TransformerEncoder(d2l.Encoder):
    # num_layers表示堆叠多少个Encoder Block
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, use_bias=False, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        # 嵌入层,将输入(batch_size, seq_len, vocab_size)转换为(batch_size, seq_len, num_hiddens),即每个token由都热编码转换为稠密向量的表示
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        # 位置编码,对每个样本的token(长为num_hiddens的一维向量)加入位置信息
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        # 堆叠num_layers个编码层块
        for i in range(num_layers):
            self.blks.add_module(
                "block" + str(i),
                EncoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, use_bias))

    def forward(self, X, valid_lens, *args):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        # 用来存储每层Block的输出
        self.attention_weights = [None] * len(self.blks)
        # 执行num_layers个编码层块
        for i, blk in enumerate(self.blks):
            # 每层的输入为上一层的输出
            X = blk(X, valid_lens)
            # 将每层的输出存到attention_weights中
            self.attention_weights[
                i] = blk.attention.attention.attention_weights
        return X
# vocab_size为200,num_layers为2    
encoder = TransformerEncoder(200, 24, 24, 24, 24, [3, 24], 24, 48, 8, 2,
                             0.5)
encoder.eval()
# 输入维度(2, 3)输出维度torch.Size([2, 3, 24])
encoder(torch.ones((2,3), dtype=torch.long), valid_lens).shape    

8.解码层输入:

8.1解码层输入:

在这里插入图片描述

8.2解码层Block代码:

class DecoderBlock(nn.Module):
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 norm_shape, ffn_num_input, ffn_num_hiddens, num_heads,
                 dropout, i, **kwargs):
        super(DecoderBlock, self).__init__(**kwargs)
        self.i = i
        self.attention1 = d2l.MultiHeadAttention(key_size, query_size,
                                                 value_size, num_hiddens,
                                                 num_heads, dropout)
        self.addnorm1 = AddNorm(norm_shape, dropout)
        self.attention2 = d2l.MultiHeadAttention(key_size, query_size,
                                                 value_size, num_hiddens,
                                                 num_heads, dropout)
        self.addnorm2 = AddNorm(norm_shape, dropout)
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens,
                                   num_hiddens)
        self.addnorm3 = AddNorm(norm_shape, dropout)

    def forward(self, X, state):
        # state长度为3,记录了[enc_outputs,enc_valid_lens,dec_ht],其中dec_ht记录了前t-1个时间步decoder的输出
        enc_outputs, enc_valid_lens = state[0], state[1]
        # 使用前t-1个时间步的输出作为第一层Attention的kv,训练时直接使用前t-1个时间步的真实值X
        if state[2][self.i] is None:
            key_values = X
        # 使用前t-1个时间步的输出作为第一层Attention的kv,预测时使用前t-1个时间步的预测值state[2]
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)
        state[2][self.i] = key_values
        # 训练过程由于输入的是所有token真实值,所以需要掩码后面的token不参与注意力机制计算
        if self.training:
            batch_size, num_steps, _ = X.shape
            dec_valid_lens = torch.arange(1, num_steps + 1,
                                          device=X.device).repeat(
                                              batch_size, 1)
        # 预测过程由于输入的是一个token,所以不需要掩码
        else:
            dec_valid_lens = None
        # 首先计算当前输入token与上文的kv的注意力,由dec_valid_lens在训练阶段进行掩码,预测阶段没有意义
        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)
        Y = self.addnorm1(X, X2)
        # 然后计算当前输入token与Encoder输出的kv的注意力
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens)
        Z = self.addnorm2(Y, Y2)
        return self.addnorm3(Z, self.ffn(Z)), state
    
    
decoder_blk = DecoderBlock(24, 24, 24, 24, [3, 24], 24, 48, 8, 0.5, 0)
decoder_blk.eval()
# decoder输入
X = torch.ones((2, 3, 24))
# state初值
valid_lens = torch.tensor([3, 2])
encoder_blk = EncoderBlock(24, 24, 24, 24, [3, 24], 24, 48, 8, 0.5)
state = [encoder_blk(X, valid_lens), valid_lens, [None]]
# 输入维度(2, 3, 24),输出维度torch.Size([2, 3, 24])
decoder_blk(X, state)[0].shape    

8.3解码层代码:

class TransformerDecoder(d2l.AttentionDecoder):
    def __init__(self, vocab_size, key_size, query_size, value_size,
                 num_hiddens, norm_shape, ffn_num_input, ffn_num_hiddens,
                 num_heads, num_layers, dropout, **kwargs):
        super(TransformerDecoder, self).__init__(**kwargs)
        self.num_hiddens = num_hiddens
        self.num_layers = num_layers
        self.embedding = nn.Embedding(vocab_size, num_hiddens)
        self.pos_encoding = d2l.PositionalEncoding(num_hiddens, dropout)
        self.blks = nn.Sequential()
        # 堆叠num_layers层
        for i in range(num_layers):
            self.blks.add_module(
                "block" + str(i),
                DecoderBlock(key_size, query_size, value_size, num_hiddens,
                             norm_shape, ffn_num_input, ffn_num_hiddens,
                             num_heads, dropout, i))
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def init_state(self, enc_outputs, enc_valid_lens, *args):
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

    def forward(self, X, state):
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))
        self._attention_weights = [[None] * len(self.blks) for _ in range(2)]
        # 堆叠num_layers层,每层输入X, state并输出X, state座位下一层输入
        for i, blk in enumerate(self.blks):
            X, state = blk(X, state)
            self._attention_weights[0][
                i] = blk.attention1.attention.attention_weights
            self._attention_weights[1][
                i] = blk.attention2.attention.attention_weights
        return self.dense(X), state

    @property
    def attention_weights(self):
        return self._attention_weights

六、训练过程举例:******

以德译英的训练过程为例:
1.输入batch:(batch_size,num_steps)和vocab_size
2.分别对每个样本使用Embedding将每个token转换为稠密向量表示,输出维度(batch_size,num_steps,features)
3.对输入进行位置编码加入位置信息,输出维度(batch_size,num_steps,features)
4.对于输入batch,在每个头上分别使用全连接层获得输入的不同表示。每个头的输入为(batch_size,num_steps,features/num_heads)。
5.输入=Q=K=V,使用注意力机制将每个token作为q计算与其他token的相关性,每个头输出维度(batch_size,num_steps,features/num_heads),num_heads个头的输出concat成(batch_size,num_steps,features)
6.层归一化对batch进行归一化操作,输出维度不变。
7.前馈网络将样本的特征维先升维后降维,最终输出维度(batch_size,num_steps,features)
8.层归一化对batch进行归一化操作,输出维度不变。
9.堆叠num_layers个Encoder,重复3~8,其中每层的输出作为下一层输入,并将每层输出记录到attention_weights中。
10.Encoder最后一层将attention_weights传给Decoder,维度四维(num_layers,batch_size,num_steps,features)
11.Decoder输入batch为Encoder输入batch对应的翻译,也是首先进行嵌入层和位置编码,输出维度(batch_size,num_steps,features)
12.每个时间步,使用掩码将未使用的token进行隐藏,不参与注意力计算,具体通过dec_valid_lens来记录当前时间步序列可用的token数,将不可见的token在batch中设置为0。
13.使用掩码注意力机制,将当前待处理的token作为q,其余dec_valid_lens个token作为kv计算q与kv的注意力(这里其实所有token都会算注意力,但是我们只关注待处理token的注意力),输出维度(batch_size,num_steps,features)。
14.执行层归一化,输入输出维度不变。
15.将(batch_size,num_steps,features)中待处理的token取出作为q(这里其实所有token都会算注意力,但是我们只关注待处理token的注意力),attention_weights中相应时间步的特征矩阵作为KV,维度为(batch_size,num_steps,features),使用注意力机制计算当前德语token q与英语句子中所有token的相关性,输出维度(batch_size,num_steps,features)。
16.执行层归一化,前馈网络,层归一化,得到输出维度(batch_size,num_steps,features),仅包含了t个时间步的预测值,num_steps-t个时间步上值为0(掩码)。作为下一层decoder输入。
17.重复12~16的操作,依次计算每个预测值,最终输出维度(batch_size,num_steps,features)中包含了所有num_steps个token的预测。
18.经过一个全连接层将feature映射到vocab上的概率,得到最终预测值。
19.计算损失。
20.反向传播计算梯度。
21.梯度下降算法更新参数值。
22.进行下一个batch训练。

七、预测过程举例:

预测过程类似训练过程,只是Decoder会有所不同:
1.训练时一次输入的是整个预测序列的真实值,而预测时一次只输入一个token,为了保持输入维度与训练过程一致,我们就需要使用填充特殊符号或0的方式将输入token(batch_size,1,features)补齐成(batch_size,num_steps,features)的维度,然后进行预测。
2.每个时间步预测逐步填充输入,第t个时间步预测时(batch_size,num_steps,features)中可用信息维度为(batch_size,t,features)。

八、训练预测代码:

# 训练
num_hiddens, num_layers, dropout, batch_size, num_steps = 32, 2, 0.1, 64, 10
lr, num_epochs, device = 0.005, 200, d2l.try_gpu()
ffn_num_input, ffn_num_hiddens, num_heads = 32, 64, 4
key_size, query_size, value_size = 32, 32, 32
norm_shape = [32]

train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)

encoder = TransformerEncoder(len(src_vocab), key_size, query_size, value_size,
                             num_hiddens, norm_shape, ffn_num_input,
                             ffn_num_hiddens, num_heads, num_layers, dropout)
decoder = TransformerDecoder(len(tgt_vocab), key_size, query_size, value_size,
                             num_hiddens, norm_shape, ffn_num_input,
                             ffn_num_hiddens, num_heads, num_layers, dropout)
net = d2l.EncoderDecoder(encoder, decoder)
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)

# 预测
engs = ['go .', "i lost .", 'he\'s calm .', 'i\'m home .']
fras = ['va !', 'j\'ai perdu .', 'il est calme .', 'je suis chez moi .']
for eng, fra in zip(engs, fras):
    translation, dec_attention_weight_seq = d2l.predict_seq2seq(
        net, eng, src_vocab, tgt_vocab, num_steps, device, True)
    print(f'{eng} => {translation}, ',
          f'bleu {d2l.bleu(translation, fra, k=2):.3f}')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姓蔡小朋友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值