动手学深度学习(Pytorch版)代码实践 -注意力机制-Transformer

68Transformer

在这里插入图片描述

1. PositionWiseFFN

基于位置的前馈网络

  • 原理:这是一个应用于每个位置的前馈神经网络。它使用相同的多层感知机(MLP)对序列中的每个位置独立进行变换。
  • 作用:对输入序列的每个位置独立地进行非线性变换,增强模型的表达能力。
  • 组成
    • 两个线性层
    • 一个ReLU激活函数
class PositionWiseFFN(nn.Module):
    """基于位置的前馈网络"""
    # 基于位置的前馈网络对序列中的所有位置的表示进行变换时使用的是同一个多层感知机(MLP)
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)  # 第一个全连接层
        self.relu = nn.ReLU()  # ReLU激活函数
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)  # 第二个全连接层

    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))  # 前向传播
2. AddNorm

残差连接和层规范化

  • 原理:将残差连接(输入直接加到输出)和层规范化结合在一起。层规范化在归一化每个输入样本后,通过一个可训练的缩放和平移参数来调整输出。
  • 作用:通过残差连接解决深度神经网络训练中的梯度消失问题,并通过层规范化稳定网络训练。
  • 组成
    • 一个Dropout层
    • 一个LayerNorm层
class AddNorm(nn.Module):
    """残差连接后进行层规范化"""
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)  # Dropout层
        self.ln = nn.LayerNorm(normalized_shape)  # LayerNorm层

    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)  # 残差连接后进行层规范化
3. PositionalEncoding

位置编码

  • 原理:位置编码为每个输入位置添加一个固定的向量,使模型能够感知序列中元素的位置。这些位置编码向量使用正弦和余弦函数生成。
  • 作用:在不改变模型结构的情况下,为Transformer提供序列的位置信息,使其能够处理顺序数据。
  • 组成
    • 一个Dropout层
    • 一个存储位置编码的张量
      在这里插入图片描述
class PositionalEncoding(nn.Module):
    """位置编码"""
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)  # Dropout层
        # 创建一个足够长的P
        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)  # 偶数位置使用sin函数
        self.P[:, :, 1::2] = torch.cos(X)  # 奇数位置使用cos函数

    def forward(self, X):
        X = X + self.P[:, :X.shape[1], :].to(X.device)  # 添加位置编码
        return self.dropout(X)  # 应用Dropout
4. MultiHeadAttention

多头注意力

  • 原理:多头注意力机制将输入拆分成多个头,每个头独立地计算注意力,然后将这些头的输出拼接并进行一次线性变换。每个头有自己的查询、键和值的线性变换。
  • 作用:通过并行计算多个注意力机制,增强模型的表示能力和捕捉不同特征的能力。
  • 组成
    • 多个线性层
    • 一个点积注意力层
      在这里插入图片描述
class MultiHeadAttention(nn.Module):
    """多头注意力"""
    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  # 注意力头数
        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):
        # queries,keys,values的形状:
        # (batch_size,查询或者“键-值”对的个数,num_hiddens)
        # valid_lens 的形状:
        # (batch_size,)或(batch_size,查询的个数)
        # 经过变换后,输出的queries,keys,values 的形状:
        # (batch_size*num_heads,查询或者“键-值”对的个数,
        # num_hiddens/num_heads)
        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:
            # 在轴0,将第一项(标量或者矢量)复制num_heads次,
            # 然后如此复制第二项,然后诸如此类。
            valid_lens = torch.repeat_interleave(
                valid_lens, repeats=self.num_heads, dim=0)

        # output的形状:(batch_size*num_heads,查询的个数,
        # num_hiddens/num_heads)
        output = self.attention(queries, keys, values, valid_lens)

        # output_concat的形状:(batch_size,查询的个数,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)

def transpose_qkv(X, num_heads):
    """为了多注意力头的并行计算而变换形状"""
    # 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,
    # num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)

    # 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)

    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])

def transpose_output(X, num_heads):
    """逆转transpose_qkv函数的操作"""
    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)
5. EncoderBlock

编码器块

  • 原理:编码器块是Transformer的基础单元,包含多头注意力机制和前馈神经网络。每个子层之后都加了残差连接和层规范化。
  • 作用:捕捉输入序列中的特征,通过堆叠多个编码器块来增强模型的表示能力。
  • 组成
    • 一个多头注意力层
    • 两个残差连接和层规范化层
    • 一个前馈神经网络
class EncoderBlock(nn.Module):
    """Transformer编码器块"""
    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 = 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):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))  # 多头注意力后进行残差连接和层规范化
        return self.addnorm2(Y, self.ffn(Y))  # 前馈网络后进行残差连接和层规范化
6. TransformerEncoder

Transformer编码器

  • 原理:Transformer编码器由多个编码器块堆叠而成。每个编码器块都由多头注意力机制和前馈神经网络组成。
  • 作用:将输入序列编码为一组隐藏状态,这些隐藏状态包含了输入序列的上下文信息。
  • 组成
    • 一个嵌入层
    • 一个位置编码层
    • 多个编码器块
class TransformerEncoder(d2l.Encoder):
    """Transformer编码器"""
    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  # 隐藏单元数
        self.embedding = nn.Embedding(vocab_size, num_hiddens)  # 嵌入层
        self.pos_encoding = PositionalEncoding(num_hiddens, dropout)  # 位置编码层
        self.blks = nn.Sequential()  # 编码器块的容器
        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):
        # 因为位置编码值在-1和1之间,
        # 因此嵌入值乘以嵌入维度的平方根进行缩放,
        # 然后再与位置编码相加。
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))  # 嵌入层和位置编码
        self.attention_weights = [None] * len(self.blks)  # 存储注意力权重
        for i, blk in enumerate(self.blks):
            X = blk(X, valid_lens)  # 通过每个编码器块
            self.attention_weights[i] = blk.attention.attention.attention_weights  # 存储每个块的注意力权重
        return X  # 返回编码结果
7. DecoderBlock

解码器块

  • 原理:解码器块是Transformer解码器的基础单元,包含解码器自注意力、编码器-解码器注意力和前馈神经网络。每个子层之后都加了残差连接和层规范化。

  • 作用:在解码时通过注意力机制获取编码器的上下文信息,并生成序列输出。

  • 组成

    • 两个多头注意力层(自注意力和编码器-解码器注意力)
    • 三个残差连接和层规范化层
    • 一个前馈神经网络
class DecoderBlock(nn.Module):
    """解码器中第i个块"""
    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 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层1
        self.addnorm1 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化1
        self.attention2 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层2
        self.addnorm2 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化2
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)  # 基于位置的前馈网络
        self.addnorm3 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化3

    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]  # 编码器的输出和有效长度
        # 训练阶段,输出序列的所有词元都在同一时间处理,
        # 因此state[2][self.i]初始化为None。
        # 预测阶段,输出序列是通过词元一个接着一个解码的,
        # 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示
        if state[2][self.i] is None:
            key_values = X  # 当前没有存储的值时,使用输入X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)  # 连接存储的值和输入X
        state[2][self.i] = key_values  # 更新存储的值
        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)  # 训练阶段生成有效长度
        else:
            dec_valid_lens = None  # 预测阶段有效长度为None

        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)  # 自注意力
        Y = self.addnorm1(X, X2) # 残差连接和层规范化1
        # 编码器-解码器注意力。
        # enc_outputs的开头:(batch_size,num_steps,num_hiddens)
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens) # 编码器-解码器注意力
        Z = self.addnorm2(Y, Y2) # 残差连接和层规范化2
        return self.addnorm3(Z, self.ffn(Z)), state # 前馈网络后进行残差连接和层规范化3

8. TransformerDecoder

Transformer解码器

  • 原理:Transformer解码器由多个解码器块堆叠而成。每个解码器块都包含自注意力机制、编码器-解码器注意力和前馈神经网络。
  • 作用:生成目标序列的输出,使用编码器的上下文信息和先前生成的序列信息。
  • 组成
    • 一个嵌入层
    • 一个位置编码层
    • 多个解码器块
    • 一个线性层用于生成输出
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 = PositionalEncoding(num_hiddens, dropout)  # 位置编码层,为嵌入向量添加位置信息
        self.blks = nn.Sequential()  # 使用nn.Sequential容器来包含多个解码器块
        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):
        # 初始化解码器的状态,返回一个包含编码器输出、有效长度和初始化为None的解码器块状态的列表
        return [enc_outputs, enc_valid_lens, [None] * self.num_layers]

    def forward(self, X, state):
        # 前向传播,参数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)]  # 初始化存储注意力权重的列表
        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  # 返回注意力权重
9. 整体架构
  • 原理:Transformer模型由编码器和解码器两部分组成,编码器负责将输入序列编码为上下文表示,解码器根据这些表示生成输出序列。
  • 作用:通过编码器-解码器结构,Transformer能够高效处理序列到序列的任务,如机器翻译、文本摘要等。
10.完整实现
import math
import pandas as pd
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt

#@save
class PositionalEncoding(nn.Module):
    """位置编码"""
    def __init__(self, num_hiddens, dropout, max_len=1000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(dropout)  # Dropout层
        # 创建一个足够长的P
        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)  # 偶数位置使用sin函数
        self.P[:, :, 1::2] = torch.cos(X)  # 奇数位置使用cos函数

    def forward(self, X):
        X = X + self.P[:, :X.shape[1], :].to(X.device)  # 添加位置编码
        return self.dropout(X)  # 应用Dropout


# 残差连接和层规范化
# 加法和规范化(add&norm)组件
# ln = nn.LayerNorm(3) # 3表示最后一个维度的大小是3
# bn = nn.BatchNorm1d(3) # 3表示输入数据有3个通道。
# X = torch.tensor([[1, 2, 3], [2, 3, 4]], dtype=torch.float32)
# 在训练模式下计算X的均值和方差
# print('layer norm:', ln(X), '\nbatch norm:', bn(X))
"""
Layer Normalization对每一行进行归一化处理
layer norm: tensor([[-1.2247,  0.0000,  1.2247],
        [-1.2247,  0.0000,  1.2247]], grad_fn=<NativeLayerNormBackward>)
Batch Normalization对每一列(通道)进行归一化处理
batch norm: tensor([[-1.0000, -1.0000, -1.0000],
        [ 1.0000,  1.0000,  1.0000]], grad_fn=<NativeBatchNormBackward>)
"""

# 残差连接和层规范化来实现AddNorm类。
# 暂退法也被作为正则化方法使用。
class AddNorm(nn.Module):
    """残差连接后进行层规范化"""
    def __init__(self, normalized_shape, dropout, **kwargs):
        super(AddNorm, self).__init__(**kwargs)
        self.dropout = nn.Dropout(dropout)  # Dropout层
        self.ln = nn.LayerNorm(normalized_shape)  # LayerNorm层

    def forward(self, X, Y):
        return self.ln(self.dropout(Y) + X)  # 残差连接后进行层规范化
    
# 残差连接要求两个输入的形状相同,以便加法操作后输出张量的形状相同。
# add_norm = AddNorm([3, 4], 0.5)
# add_norm.eval()
# print(add_norm(torch.ones((2, 3, 4)), torch.ones((2, 3, 4))).shape)
# torch.Size([2, 3, 4])



# 多头注意力
#@save
class MultiHeadAttention(nn.Module):
    """多头注意力"""
    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  # 注意力头数
        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):
        # queries,keys,values的形状:
        # (batch_size,查询或者“键-值”对的个数,num_hiddens)
        # valid_lens 的形状:
        # (batch_size,)或(batch_size,查询的个数)
        # 经过变换后,输出的queries,keys,values 的形状:
        # (batch_size*num_heads,查询或者“键-值”对的个数,
        # num_hiddens/num_heads)
        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:
            # 在轴0,将第一项(标量或者矢量)复制num_heads次,
            # 然后如此复制第二项,然后诸如此类。
            valid_lens = torch.repeat_interleave(
                valid_lens, repeats=self.num_heads, dim=0)

        # output的形状:(batch_size*num_heads,查询的个数,
        # num_hiddens/num_heads)
        output = self.attention(queries, keys, values, valid_lens)

        # output_concat的形状:(batch_size,查询的个数,num_hiddens)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)

#@save
def transpose_qkv(X, num_heads):
    """为了多注意力头的并行计算而变换形状"""
    # 输入X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,
    # num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)

    # 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)

    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])

#@save
def transpose_output(X, num_heads):
    """逆转transpose_qkv函数的操作"""
    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)


class PositionWiseFFN(nn.Module): #@save
    """基于位置的前馈网络"""
    # 基于位置的前馈网络对序列中的所有位置的表示进行变换时使用的是同一个多层感知机(MLP)
    def __init__(self, ffn_num_input, ffn_num_hiddens, ffn_num_outputs,
                 **kwargs):
        super(PositionWiseFFN, self).__init__(**kwargs)
        self.dense1 = nn.Linear(ffn_num_input, ffn_num_hiddens)  # 第一个全连接层
        self.relu = nn.ReLU()  # ReLU激活函数
        self.dense2 = nn.Linear(ffn_num_hiddens, ffn_num_outputs)  # 第二个全连接层

    def forward(self, X):
        return self.dense2(self.relu(self.dense1(X)))  # 前向传播


# 编码器
# EncoderBlock类包含两个子层:多头自注意力和基于位置的前馈网络,
# 这两个子层都使用了残差连接和紧随的层规范化。
#@save
class EncoderBlock(nn.Module):
    """Transformer编码器块"""
    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 = 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):
        Y = self.addnorm1(X, self.attention(X, X, X, valid_lens))  # 多头注意力后进行残差连接和层规范化
        return self.addnorm2(Y, self.ffn(Y))  # 前馈网络后进行残差连接和层规范化

# Transformer编码器中的任何层都不会改变其输入的形状
# X = torch.ones((2, 100, 24))
# valid_lens = torch.tensor([3, 2])
# encoder_blk = EncoderBlock(24, 24, 24, 24, [100, 24], 24, 48, 8, 0.5)
# encoder_blk.eval()
# encoder_blk(X, valid_lens).shape
# torch.Size([2, 100, 24])

# 实现的Transformer编码器的代码中,堆叠了num_layers个EncoderBlock类的实例
#@save
class TransformerEncoder(d2l.Encoder):
    """Transformer编码器"""
    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  # 隐藏单元数
        self.embedding = nn.Embedding(vocab_size, num_hiddens)  # 嵌入层
        self.pos_encoding = PositionalEncoding(num_hiddens, dropout)  # 位置编码层
        self.blks = nn.Sequential()  # 编码器块的容器
        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):
        # 因为位置编码值在-1和1之间,
        # 因此嵌入值乘以嵌入维度的平方根进行缩放,
        # 然后再与位置编码相加。
        X = self.pos_encoding(self.embedding(X) * math.sqrt(self.num_hiddens))  # 嵌入层和位置编码
        self.attention_weights = [None] * len(self.blks)  # 存储注意力权重
        for i, blk in enumerate(self.blks):
            X = blk(X, valid_lens)  # 通过每个编码器块
            self.attention_weights[i] = blk.attention.attention.attention_weights  # 存储每个块的注意力权重
        return X  # 返回编码结果
    
# encoder = TransformerEncoder(
#     200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 1, 0.5)
# print(encoder)
"""
TransformerEncoder(
  (embedding): Embedding(200, 24)
  (pos_encoding): PositionalEncoding(
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (blks): Sequential(
    (block0): EncoderBlock(
      (attention): MultiHeadAttention(
        (attention): DotProductAttention(
          (dropout): Dropout(p=0.5, inplace=False)
        )
        (W_q): Linear(in_features=24, out_features=24, bias=False)
        (W_k): Linear(in_features=24, out_features=24, bias=False)
        (W_v): Linear(in_features=24, out_features=24, bias=False)
        (W_o): Linear(in_features=24, out_features=24, bias=False)
      )
      (addnorm1): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      (ffn): PositionWiseFFN(
        (dense1): Linear(in_features=24, out_features=48, bias=True)
        (relu): ReLU()
        (dense2): Linear(in_features=48, out_features=24, bias=True)
      )
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
    )
  )
)
"""


# 解码器
"""
Transformer解码器也是由多个相同的层组成。
在DecoderBlock类中实现的每个层包含了三个子层:解码器自注意力、“编码器-解码器”注意力和基于位置的前馈网络。
这些子层也都被残差连接和紧随的层规范化围绕。
"""
class DecoderBlock(nn.Module):
    """解码器中第i个块"""
    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 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层1
        self.addnorm1 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化1
        self.attention2 = MultiHeadAttention(
            key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层2
        self.addnorm2 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化2
        self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)  # 基于位置的前馈网络
        self.addnorm3 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化3

    def forward(self, X, state):
        enc_outputs, enc_valid_lens = state[0], state[1]  # 编码器的输出和有效长度
        # 训练阶段,输出序列的所有词元都在同一时间处理,
        # 因此state[2][self.i]初始化为None。
        # 预测阶段,输出序列是通过词元一个接着一个解码的,
        # 因此state[2][self.i]包含着直到当前时间步第i个块解码的输出表示
        if state[2][self.i] is None:
            key_values = X  # 当前没有存储的值时,使用输入X
        else:
            key_values = torch.cat((state[2][self.i], X), axis=1)  # 连接存储的值和输入X
        state[2][self.i] = key_values  # 更新存储的值
        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)  # 训练阶段生成有效长度
        else:
            dec_valid_lens = None  # 预测阶段有效长度为None

        X2 = self.attention1(X, key_values, key_values, dec_valid_lens)  # 自注意力
        Y = self.addnorm1(X, X2) # 残差连接和层规范化1
        # 编码器-解码器注意力。
        # enc_outputs的开头:(batch_size,num_steps,num_hiddens)
        Y2 = self.attention2(Y, enc_outputs, enc_outputs, enc_valid_lens) # 编码器-解码器注意力
        Z = self.addnorm2(Y, Y2) # 残差连接和层规范化2
        return self.addnorm3(Z, self.ffn(Z)), state # 前馈网络后进行残差连接和层规范化3


# 构建了由num_layers个DecoderBlock实例组成的完整的Transformer解码器
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 = PositionalEncoding(num_hiddens, dropout)  # 位置编码层
        self.blks = nn.Sequential()  # 解码器块的容器
        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)]  # 存储注意力权重
        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 # 返回注意力权重
    
# decoder = TransformerDecoder(
#     200, 24, 24, 24, 24, [100, 24], 24, 48, 8, 1, 0.5)
# print(decoder)
"""
TransformerDecoder(
  (embedding): Embedding(200, 24)
  (pos_encoding): PositionalEncoding(
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (blks): Sequential(
    (block0): DecoderBlock(
      (attention1): MultiHeadAttention(
        (attention): DotProductAttention(
          (dropout): Dropout(p=0.5, inplace=False)
        )
        (W_q): Linear(in_features=24, out_features=24, bias=False)
        (W_k): Linear(in_features=24, out_features=24, bias=False)
        (W_v): Linear(in_features=24, out_features=24, bias=False)
        (W_o): Linear(in_features=24, out_features=24, bias=False)
      )
      (addnorm1): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      (attention2): MultiHeadAttention(
        (attention): DotProductAttention(
          (dropout): Dropout(p=0.5, inplace=False)
        )
        (W_q): Linear(in_features=24, out_features=24, bias=False)
        (W_k): Linear(in_features=24, out_features=24, bias=False)
        (W_v): Linear(in_features=24, out_features=24, bias=False)
        (W_o): Linear(in_features=24, out_features=24, bias=False)
      )
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      (ffn): PositionWiseFFN(
        (dense1): Linear(in_features=24, out_features=48, bias=True)
        (relu): ReLU()
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      (ffn): PositionWiseFFN(
        (dense1): Linear(in_features=24, out_features=48, bias=True)
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      (addnorm2): AddNorm(
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
      (addnorm2): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
      )
      (ffn): PositionWiseFFN(
      (ffn): PositionWiseFFN(
        (dense1): Linear(in_features=24, out_features=48, bias=True)
        (dense1): Linear(in_features=24, out_features=48, bias=True)
        (relu): ReLU()
        (dense2): Linear(in_features=48, out_features=24, bias=True)
      )
      (addnorm3): AddNorm(
        (dropout): Dropout(p=0.5, inplace=False)
        (ln): LayerNorm((100, 24), eps=1e-05, elementwise_affine=True)
      )
    )
  )
  (dense): Linear(in_features=24, out_features=200, bias=True)
)
"""


# 训练
# 英语-法语”机器翻译数据集上训练Transformer模型
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: 目标语言(法语)的词汇表对象。
train_iter, src_vocab, tgt_vocab = d2l.load_data_nmt(batch_size, num_steps)  # 加载数据

"""
key_size, query_size, value_size: 注意力机制中键、查询和值的维度。
num_hiddens: 隐藏单元数,表示词嵌入的维度。
norm_shape: 层规范化的形状。
ffn_num_input, ffn_num_hiddens: 前馈神经网络的输入和隐藏层的大小。
num_heads: 多头注意力机制的头数。
num_layers: 解码器层数,即解码器块的数量。
dropout: Dropout层的丢弃概率。
"""
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)  # 训练模型
plt.show()  # 显示损失曲线
# loss 0.029, 9302.9 tokens/sec on cuda:0

# Transformer模型将一些英语句子翻译成法语,并且计算它们的BLEU分数。
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}')
"""
go . => va !,  bleu 1.000
i lost . => j'ai perdu .,  bleu 1.000
he's calm . => il est calme .,  bleu 1.000
i'm home . => je suis chez moi .,  bleu 1.000
"""

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/796679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Open-TeleVision——通过VR沉浸式感受人形机器人视野:兼备远程控制和深度感知能力

前言 7.3日&#xff0c;我司七月在线(集AI大模型职教、应用开发、机器人解决方案为一体的科技公司)的「大模型机器人(具身智能)线下营」群里的一学员发了《Open-TeleVision: Teleoperation with Immersive Active Visual Feedback》这篇论文的链接&#xff0c;我当时快速看了一…

【网络文明】关注网络安全

在这个数字化时代&#xff0c;互联网已成为我们生活中不可或缺的一部分&#xff0c;它极大地便利了我们的学习、工作、娱乐乃至日常生活。然而&#xff0c;随着网络空间的日益扩大&#xff0c;网络安全问题也日益凸显&#xff0c;成为了一个不可忽视的全球性挑战。认识到网络安…

C双指针滑动窗口算法

这也许是双指针技巧的最⾼境界了&#xff0c;如果掌握了此算法&#xff0c;可以解决⼀⼤类⼦字符串匹配的问题 原理 1、我们在字符串 S 中使⽤双指针中的左右指针技巧&#xff0c;初始化 left right 0&#xff0c;把索引闭区间 [left, right] 称为⼀个「窗⼝」。 2、我们先…

开发个人Ollama-Chat--6 OpenUI

开发个人Ollama-Chat–6 OpenUI Open-webui Open WebUI 是一种可扩展、功能丰富且用户友好的自托管 WebUI&#xff0c;旨在完全离线运行。它支持各种 LLM 运行器&#xff0c;包括 Ollama 和 OpenAI 兼容的 API。 功能 由于总所周知的原由&#xff0c;OpenAI 的接口需要密钥才…

总结单例模式的写法

一、单例模式的概念 1.1 单例模式的概念 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。就是当前进程确保一个类全局只有一个实例。 1.2 单例模式的优…

首次跑通Arduino IDE + ESP8266

感触 网络&#xff0c;网络&#xff0c;还是网络。 中文开发者往往具备更多的网络知识和能力。 文档&#xff0c;文档&#xff0c;更是文档。 在计算机领域&#xff0c;遇到的问题&#xff0c;99.999%前人已经解决过。

Lesson 50 He likes ... But he doesn‘t like ...

Lesson 50 He likes … But he doesn’t like … 词汇 tomato n. 西红柿 复数&#xff1a;tomatoes 同义词&#xff1a;ketch-up 番茄酱     dead horse 例句&#xff1a;冰箱里有很多西红柿。    There are some tomatoes in the fridge. potato n. 土豆 复数&#…

【公益案例展】华为云X《无尽攀登》——攀登不停,向上而行

‍ 华为云公益案例 本项目案例由华为云投递并参与数据猿与上海大数据联盟联合推出的 #榜样的力量# 《2024中国数据智能产业最具社会责任感企业》榜单/奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 夏伯渝&#xff0c;中国无腿登珠峰第一人&#xff0c;一生43年…

Redis持久化RDB,AOF

目 录 CONFIG动态修改配置 慢查询 持久化 在上一篇主要对redis的了解入门&#xff0c;安装&#xff0c;以及基础配置&#xff0c;多实例的实现&#xff1a;redis的安装看我上一篇&#xff1a; Redis安装部署与使用,多实例 redis是挡在MySQL前面的&#xff0c;运行在内存…

Java基础-I/O流

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 字节流 定义 说明 InputStream与OutputStream示意图 说明 InputStream的常用方法 说明 OutputStrea…

虚函数__

10 文章目录 虚函数虚函数表override(不允许后续函数继承)虚析构纯虚函数 虚函数 虚函数表 override(不允许后续函数继承) 虚析构 纯虚函数

C++的deque(双端队列),priority_queue(优先级队列)

deque deque是一个容器,是双端队列,从功能上来讲,deque是一个vector和list的结合体 顺序表和链表 deque的结构和优缺点 开辟buff小数组,空间不够了,不扩容,而是开辟一个新的小数组 开辟中控数组(指针数组)指向buff小数组 将已存在的数组指针存在中控数组中间,可以使用下标访…

【ARM】CCI集成指导整理

目录 1.CCI集成流程 2.CCI功能集成指导 2.1CCI结构框图解释 Request concentrator Transaction tracker Read-data Network Write-data Network B-response Network 2.2 接口注意项 记录一下CCI500的ACE slave interface不支持的功能&#xff1a; 对于ACE-Lite slav…

Java项目:基于SSM框架实现的中小型企业财务管理系统【ssm+B/S架构+源码+数据库+答辩PPT+开题报告+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的中小型企业财务管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单…

【分库】分库的核心原则

目录 分库的核心原则 前言 分区透明性与一致性保证 弹性伸缩性与容错性设计 数据安全与访问控制机制 分库的核心原则 前言 在设计和实施分库策略时&#xff0c;遵循一系列核心原则是至关重要的&#xff0c;以确保系统不仅能够在当前规模下高效运行&#xff0c;还能够随着…

集成excel工具:自定义导入回调监听器、自定义类型转换器、web中的读

文章目录 I 封装导入导出1.1 定义工具类1.2 自定义读回调监听器: 回调业务层处理导入数据1.3 定义文件导入上下文1.4 定义回调协议II 自定义转换器2.1 自定义枚举转换器2.2 日期转换器2.3 时间、日期、月份之间的互转2.4 LongConverterIII web中的读3.1 使用默认回调监听器3.2…

算法 —— 高精度

目录 加法高精度 两个正整数相加 两个正小数相加 两正数相加 减法高精度 两个正整数相减 两个正小数相减 两正数相减 加减法总结 乘法高精度 两个正整数相乘 两个正小数相乘 乘法总结 加法高精度 题目来源洛谷&#xff1a;P1601 AB Problem&#xff08;高精&#x…

医疗器械FDA |FDA网络安全测试具体内容

医疗器械FDA网络安全测试的具体内容涵盖了多个方面&#xff0c;以确保医疗器械在网络环境中的安全性和合规性。以下是根据权威来源归纳的FDA网络安全测试的具体内容&#xff1a; 一、技术文件审查 网络安全计划&#xff1a;制造商需要提交网络安全计划&#xff0c;详细描述产…

循环结构(一)——for语句【互三互三】

文章目录 &#x1f341; 引言 &#x1f341; 一、语句格式 &#x1f341; 二、语句执行过程 &#x1f341; 三、语句格式举例 &#x1f341;四、例题 &#x1f449;【例1】 &#x1f680;示例代码: &#x1f449;【例2】 【方法1】 &#x1f680;示例代码: 【方法2】…

转盘输入法

简介 转盘输入法&#xff0c;给你的聊天加点新意。它不用常见的九宫格或全键盘&#xff0c;而是把字母摆在圆盘上&#xff0c;一滑一滑&#xff0c;字就出来了&#xff0c;新鲜又直接。 触摸屏版本 当触屏输入法启动时&#xff0c;与200X年流行的按键手机相比&#xff0c;两者…