动手学深度学习(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层# 创建一个足够长的Pself.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)  # 多头注意力层1self.addnorm1 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化1self.attention2 = MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层2self.addnorm2 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化2self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)  # 基于位置的前馈网络self.addnorm3 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化3def 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  # 当前没有存储的值时,使用输入Xelse:key_values = torch.cat((state[2][self.i], X), axis=1)  # 连接存储的值和输入Xstate[2][self.i] = key_values  # 更新存储的值if self.training:batch_size, num_steps, _ = X.shapedec_valid_lens = torch.arange(1, num_steps + 1, device=X.device).repeat(batch_size, 1)  # 训练阶段生成有效长度else:dec_valid_lens = None  # 预测阶段有效长度为NoneX2 = 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) # 残差连接和层规范化2return 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_weightsreturn self.dense(X), state  # 返回输出和状态@propertydef 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层# 创建一个足够长的Pself.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)  # 多头注意力层1self.addnorm1 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化1self.attention2 = MultiHeadAttention(key_size, query_size, value_size, num_hiddens, num_heads, dropout)  # 多头注意力层2self.addnorm2 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化2self.ffn = PositionWiseFFN(ffn_num_input, ffn_num_hiddens, num_hiddens)  # 基于位置的前馈网络self.addnorm3 = AddNorm(norm_shape, dropout)  # 残差连接和层规范化3def 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  # 当前没有存储的值时,使用输入Xelse:key_values = torch.cat((state[2][self.i], X), axis=1)  # 连接存储的值和输入Xstate[2][self.i] = key_values  # 更新存储的值if self.training:batch_size, num_steps, _ = X.shapedec_valid_lens = torch.arange(1, num_steps + 1, device=X.device).repeat(batch_size, 1)  # 训练阶段生成有效长度else:dec_valid_lens = None  # 预测阶段有效长度为NoneX2 = 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) # 残差连接和层规范化2return 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_weightsreturn self.dense(X), state@propertydef 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.mzph.cn/pingmian/45861.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

备份及恢复Sonarqube服务数据

基础数据&#xff1a; 源数据机ip&#xff1a;192.*.53 测试机ip&#xff1a;192.*.65 Sonarqube访问地址&#xff1a;http://192.*.65:9000/ 账户名&#xff1a;admin 密码&#xff1a;123456 数据库postgres&#xff1a; 版本&#xff1a;PostgreSQL 15.3 一、数据备份…

AI写歌词,为你的音乐之旅添彩

在追求音乐梦想的道路上&#xff0c;每一个音符都承载着我们的情感与故事。而精彩的歌词&#xff0c;更是让这些音符焕发出耀眼光芒的关键。如今&#xff0c;AI 写词的出现&#xff0c;为我们的音乐之旅增添了绚丽的色彩。 “妙笔生词智能写歌词软件&#xff08;veve522&#…

JAVA自定义注释

interface 声明 package test; public interface InProgress { } InProgress public void calculateInterest(float amount, float rate) { } 带成员 public interface TODO {String value(); } InProgress //只有成员变量名有value时&#xff0c;值有给value赋值时可以这…

Spring Cloud Eureka

引入&#xff1a;远程调用时&#xff0c;url是写死的 String url "http://127.0.0.1:9090/product/" orderInfo.getProductId(); 解决思路&#xff1a; 比如&#xff08;医院&#xff0c;学校等&#xff09;机构的电话号码发生变化&#xff0c;就需要通知各个使⽤…

Android14系统应用统一裁剪方案

Android14系统应用统一裁剪方案 背景 当前移除集成到系统里的应用,一般都是根据应用名,到各个mk文件里逐个在PRODUCT_PACKAGES中删除;这种方法,耗时而且不易管理集成到系统里的应用;需要有一个统一管理删除不需要应用的方案。 方案 参考PRODUCT_PACKAGES变量,添加PRO…

游戏的无边框模式是什么?有啥用?

现在很多游戏的显示设置中&#xff0c;都有个比较特殊的选项“无边框”。小伙伴们如果尝试过&#xff0c;就会发现这个效果和全屏几乎一毛一样&#xff0c;于是就很欢快地用了起来&#xff0c;不过大家也许会发现&#xff0c;怎么和全屏比起来&#xff0c;似乎有点不够爽快&…

uniapp编译成h5后接口请求参数变成[object object]

问题&#xff1a;uniapp编译成h5后接口请求参数变成[object object] 但是运行在开发者工具上没有一点问题 排查&#xff1a; 1&#xff1a;请求参数&#xff1a;看是否是在请求前就已经变成了[object object]了 结果&#xff1a; 一切正常 2&#xff1a;请求头&#xff1a;看…

AST反混淆实战:提升JavaScript代码的可读性与调试便利性

博客标题&#xff1a;AST反混淆&#xff1a;提升JavaScript代码的可读性与调试便利性 引言 JavaScript代码混淆是一种常见的保护源码的方法&#xff0c;但这也给代码的维护和调试带来了不小的挑战。抽象语法树&#xff08;AST&#xff09;提供了一种结构化的方式来分析和转换…

平安好车主:“保”你车平安,“养”出好生活~

“小朋友 你是否有很多问号,为什么......”从出生到长大,不论我们身居何处,年岁几何,妈妈似乎总有嘱咐不完的话。小时候,总不能理解妈妈的话,只想摆脱唠叨,期盼快快长大。 如今,我们羽翼渐丰,已能驾驭人生,肩负起家庭的重任,但妈妈的话却依然从未落下。不过,此刻的我们,不仅能…

Gitea 仓库事件触发Jenkins远程构建

文章目录 引言I Gitea 仓库事件触发Jenkins远程构建1.1 Jenkins配置1.2 Gitea 配置引言 应用场景:项目部署 I Gitea 仓库事件触发Jenkins远程构建 Gitea支持用于仓库事件的Webhooks 1.1 Jenkins配置 高版本Jenkins需要关闭跨域限制和开启匿名用户访问 在Jenkins启动前加入…

STM32入门开发操作记录(一)——新建工程

目录 一、课程准备1. 课程资料2. 配件清单3. 根目录 二、环境搭建三、新建工程1. 载入器件支持包2. 添加模块3. ST配置4. 外观设置5. 主函数文件 一、课程准备 1. 课程资料 本记录操作流程参考自b站视频BV1th411z7snSTM32入门教程-2023版 细致讲解 中文字幕&#xff0c;课程资…

柯桥韩语培训韩语学习力职场口语韩语中的职场黑话你知道几个?

生活中比较常用的&#xff0c;与职场生活有关的新造词有상사병, 직장살이, 무두절(無頭節)等。一起来看下他们的意思吧... 상사병 상사병是指因为上司多变不定的指示而火大的意思。 직장살이 직장살이用来比喻职场生活也需要看上司的脸色&#xff0c;就像在婆家看婆婆脸色一样…

gorm只查询某一些字段字段的方法Select, 和只查询某一字段方法 Pluck

gorm中默认是查询所有字段的&#xff0c; 如果我们只需要获取某些字段的值&#xff0c;可以通过使用 Select方法来指定要查询的字段来实现&#xff0c; 也可以通过定义一个需要字段的结构体来实现&#xff1b; 而如果我们只需要查询某一个字段的值就可以使用 Pluck方法来获取(这…

【刷题汇总 -- 删除公共字符、两个链表的第一个公共结点、mari和shiny】

C日常刷题积累 今日刷题汇总 - day0121、删除公共字符1.1、题目1.2、思路1.3、程序实现 -- 蛮力法1.4、程序实现 -- 哈希 2、两个链表的第一个公共结点2.1、题目2.2、思路2.3、程序实现 -- 对齐比对法2.4、程序实现 -- 公共端点路程法 3、mari和shiny3.1、题目3.2、思路3.3、程…

[python]基于yolov10+gradio目标检测演示系统设计

【设计介绍】 YOLOv10结合Gradio实现目标检测系统设计是一个结合了最新目标检测技术和快速部署框架的项目。下面将详细介绍这一系统的设计和实现过程。 一、YOLOv10介绍 YOLOv10是YOLO&#xff08;You Only Look Once&#xff09;系列的最新版本&#xff0c;由清华大学的研究…

vienna整流器的矢量分析

Vienna整流器使用六个二极管和六个IGBT&#xff08;或MOSFET&#xff09;组成&#xff0c;提供三个电平&#xff1a;正极电平&#xff08;P&#xff09;、中性点电平&#xff08;O&#xff09;和负极电平&#xff08;N&#xff09;。通过对功率管的控制&#xff0c;Vienna整流器…

Telegram Bot、小程序开发(一)基础入门

文章目录 一、Telegram Bot是什么&#xff1f;二、Telegram Bot应用场景三、机器人是如何工作的&#xff1f;架构getUpdates 和 webhookswebhooks要求自签名证书 四、如何创建和使用Telegram Bot&#xff1f;整体步骤和流程Bot 的申请过程将机器人添加到 Telegram 群组 一、Tel…

昇思25天打卡营第25天|基于MoblieNetv2的垃圾分类

一、简介&#xff1a; 本次实验主要介绍垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 实验目的&#xff1a; 了解熟悉垃圾分类应用代码的编写&#xff08;Python语言&#x…

卷积神经网络——LeNet——FashionMNIST

目录 一、文件结构二、model.py三、model_train.py四、model_test.py 一、文件结构 二、model.py import torch from torch import nn from torchsummary import summaryclass LeNet(nn.Module):def __init__(self):super(LeNet,self).__init__()self.c1 nn.Conv2d(in_channe…

Autosar Dcm配置-0x28服务ComControl-基于ETAS软件

文章目录 前言DcmDcmDsdDcmDspBswMBswMModeRequestPortBswMModeConditionBswMLogicalExpressionBswMActionBswMActionListBswMRule总结前言 0x28服务主要用来控制非诊断报文的通讯,一般在刷写预编程过程中,用来禁止APP的通信报文,可以减少总线负载率,提高刷写成功率。本文…