深度学习31注意力机制、注意力分数、使用注意力机制的seq2seq、自注意力

1.注意力机制

2. 注意力机制

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l
python 复制代码
# 生成数据集
# 训练集样本数量
n_train = 50
# 生成训练集特征x_train,范围为[0, 5),并进行排序
x_train, _ = torch.sort(torch.rand(n_train) * 5)
# 定义函数f,用于生成标签y
def f(x):
    return 2 * torch.sin(x) + x**0.8
# 生成训练集标签y_train,并加上服从正态分布的噪声
y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))
# 生成测试集特征x_test,范围为[0, 5),步长为0.1
x_test = torch.arange(0, 5, 0.1)
# 生成测试集的真实标签y_truth
y_truth = f(x_test)
# 计算测试集样本数量
n_test = len(x_test)
n_test
复制代码
50
python 复制代码
# 绘制核回归结果的图像
def plot_kernel_reg(y_hat):
    # 绘制x_test和对应的真实标签y_truth以及预测标签y_hat的图像
    d2l.plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth','Pred'],
            xlim=[0,5], ylim=[-1,5])
    # 绘制训练集的散点图,用圆圈表示
    d2l.plt.plot(x_train, y_train, 'o', alpha=0.5)
# 将y_train的均值重复n_test次作为预测标签y_hat  
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
# 调用plot_kernel_reg函数,绘制核回归结果的图像
plot_kernel_reg(y_hat)
python 复制代码
# 非参数注意力汇聚
# 将测试集特征x_test重复n_train次并重新reshape为二维矩阵
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train))
# 计算注意力权重,通过对特征差值的平方取负并除以2,再进行softmax归一化
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)  
# 注意力权重与训练集标签y_train进行矩阵乘法得到预测标签y_hat
y_hat = torch.matmul(attention_weights, y_train)
# 调用plot_kernel_reg函数,绘制非参数注意力汇聚的核回归结果图像
plot_kernel_reg(y_hat)
python 复制代码
# 注意力权重
# 可视化注意力权重
d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
                xlabel='Sorted training inputs', ylabel='Sorted test inputs')   
python 复制代码
# 带参数注意力汇聚
# 假定两个张量的形状分别是(n,a,b)和(n,b,c),它们的批量矩阵乘法输出的形状为(n.a,c)  
# 创建形状为(2,1,4)的张量X,元素全为1
X = torch.ones((2,1,4))
# 创建形状为(2,4,6)的张量Y,元素全为1
Y = torch.ones((2,4,6))
# 执行批量矩阵乘法,并输出结果的形状
torch.bmm(X, Y).shape
复制代码
torch.Size([2, 1, 6])
python 复制代码
# 使用小批量矩阵乘法来计算小批量数据中的加权平均值
# 创建形状为(2,10)的权重张量,每个权重为0.1
weights = torch.ones((2,10)) * 0.1
# 创建形状为(2,10)的值张量,从0到19的连续数值
values = torch.arange(20.0).reshape((2,10))
# 执行小批量矩阵乘法,计算加权平均值
torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1))
复制代码
tensor([[[ 4.5000]],

        [[14.5000]]])
python 复制代码
# 带参数的注意力汇聚
class NWKernelRegression(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 创建形状为(1,)的参数张量w,用于调整注意力权重
        self.w = nn.Parameter(torch.rand((1,),requires_grad=True))  
        
    def forward(self, queries, keys, values):
        # 重复queries并调整形状,使其与keys具有相同的列数
        queries = queries.repeat_interleave(keys.shape[1]).reshape(-1,keys.shape[1])   
        # 计算注意力权重,通过调整参数w对注意力进行调节
        self.attention_weights = nn.functional.softmax(-((queries - keys) * self.w)**2/2,dim=1)     
        # 执行带参数的注意力汇聚,并返回最终结果的形状调整
        return torch.bmm(self.attention_weights.unsqueeze(1),values.unsqueeze(-1)).reshape(-1)   

"w"通常指的是权重矩阵,这个矩阵决定了在给定的上下文中,哪些部分应当被赋予更高的关注(即更大的权重)。当我们说"w越窄",我们通常是指权重矩阵的宽度(即列数)较小,这通常对应于较少的关注点或者说更集

python 复制代码
# 将训练数据集转换为键和值
# 将x_train在行维度上重复n_train次,形成一个矩阵X_tile
X_tile = x_train.repeat((n_train, 1))
# 将y_train在行维度上重复n_train次,形成一个矩阵Y_tile
Y_tile = y_train.repeat((n_train, 1))
# 通过掩码操作,从X_tile中排除对角线元素,得到键矩阵keys
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train,-1))  
# 通过掩码操作,从Y_tile中排除对角线元素,得到值矩阵values
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape(n_train, -1) 
python 复制代码
# 训练带参数的注意力汇聚模型
# 创建带参数的注意力汇聚模型
net = NWKernelRegression()
# 创建均方误差损失函数,用于计算损失
loss = nn.MSELoss(reduction='none')
# 创建随机梯度下降优化器,用于参数更新
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
# 创建动画绘制器,用于绘制损失曲线
animator = d2l.Animator(xlabel='epoch',ylabel='loss',xlim=[1,5])

# 遍历5次
for epoch in range(5):
    # 清零梯度
    trainer.zero_grad()
    # 计算损失
    l = loss(net(x_train, keys, values), y_train) / 2
    # 反向传播,计算梯度
    l.sum().backward()
    # 更新参数
    trainer.step()
    # 打印当前的损失
    print(f'epoch {epoch+1}, loss {float(l.sum()):.6f}')
    # 绘制损失曲线
    animator.add(epoch+1, float(l.sum()))
python 复制代码
# 预测结果绘制
# 将训练数据集的输入在行维度上重复n_test次,形成键矩阵keys
keys = x_train.repeat((n_test, 1))
# 将训练数据集的输出在行维度上重复n_test次,形成值矩阵values
values = y_train.repeat((n_test, 1))
# 使用训练好的模型进行预测,得到预测结果y_hat
y_hat = net(x_test, keys, values).unsqueeze(1).detach()
# 绘制预测结果
plot_kernel_reg(y_hat)

解释:现在是分段拟合,分的段特别多(这个多少是w控制的),所以不平滑

python 复制代码
# 曲线在注意力权重较大的区域变得更不平滑
d2l.show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),
                 xlabel='Sorted training inputs', ylabel='Sorted testing inputs')   

1.注意力分数

2. 注意力分数

python 复制代码
import math
import torch
from torch import nn
from d2l import torch as d2l
python 复制代码
# 遮蔽softmax操作
def masked_softmax(X, valid_lens):
    """通过在最后一个轴上遮蔽元素来执行softmax操作"""
    if valid_lens is None:
        # 如果valid_lens为空,则对X执行softmax操作
        return nn.functional.softmax(X, dim=-1)
    else:
        shape = X.shape
        if valid_lens.dim() == 1:
            # 将valid_lens扩展为与X的最后一个维度相同的形状
            valid_lens = torch.repeat_interleave(valid_lens, shape[1])
        else:
            # 将valid_lens重塑为一维向量
            valid_lens = valid_lens.reshape(-1)
        # 在X的最后一个维度上进行遮蔽操作
        X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens, value=-1e6)   
        # 对遮蔽后的X执行softmax操作,并将形状还原为原始形状
        return nn.functional.softmax(X.reshape(shape), dim=-1)

参考注意力分数矩阵的形状是n*m 相当于一行对应一个query所以是对每行softmax 一些列mask掉舍去没用的填充

python 复制代码
# 演示此函数是如何工作
# 调用masked_softmax函数,并传入参数
masked_softmax(torch.rand(2,2,4), torch.tensor([2,3]))
复制代码
tensor([[[0.3005, 0.6995, 0.0000, 0.0000],
         [0.4543, 0.5457, 0.0000, 0.0000]],

        [[0.3104, 0.2572, 0.4324, 0.0000],
         [0.2256, 0.3151, 0.4594, 0.0000]]])
python 复制代码
masked_softmax(torch.rand(2,2,4), torch.tensor([[1,3],[2,4]]))
复制代码
tensor([[[1.0000, 0.0000, 0.0000, 0.0000],
         [0.3032, 0.3868, 0.3100, 0.0000]],

        [[0.3930, 0.6070, 0.0000, 0.0000],
         [0.2379, 0.2396, 0.2583, 0.2642]]])
python 复制代码
# 加性注意力
class AdditiveAttention(nn.Module):
    """加性注意力"""
    def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
        super(AdditiveAttention, self).__init__(**kwargs)
        # 用于转换键的线性变换
        self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
        # 用于转换查询的线性变换
        self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
        # 用于生成注意力分数的线性变换
        self.w_v = nn.Linear(num_hiddens, 1, bias=False)
        # Dropout层,用于随机丢弃一部分注意力权重
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, queries, keys, values, valid_lens):
        # 将查询和键进行线性变换
        queries, keys = self.W_q(queries), self.W_k(keys)
        # 执行加性操作,将查询和键相加
        features = queries.unsqueeze(2) + keys.unsqueeze(1)
        # 使用双曲正切函数激活加性操作的结果
        features = torch.tanh(features)
        # 使用线性变换生成注意力分数,并将最后一维的维度压缩掉
        scores = self.w_v(features).squeeze(-1)
        # 使用遮蔽softmax计算注意力权重
        self.attention_weights = masked_softmax(scores, valid_lens)
        # 根据注意力权重对values进行加权求和
        return torch.bmm(self.dropout(self.attention_weights), values)

在使用注意力机制的时候,可以设置bias=False来省略偏置参数。因为注意力机制会根据查询和键之间的相似度来分配权重,而偏置参数可能会干扰这个过程。

python 复制代码
# 演示上面的AdditiveAttention类
# 创建查询和键张量
queries, keys = torch.normal(0, 1, (2,1,20)), torch.ones((2,10,2))
# 创建值张量
values = torch.arange(40, dtype=torch.float32).reshape(1,10,4).repeat(2,1,1)  
# 创建有效长度张量
valid_lens = torch.tensor([2,6])
# 创建加性注意力对象
attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8, dropout=0.1)  
# 设置为评估模式,不使用dropout
attention.eval()
# 调用加性注意力对象的forward方法
attention(queries, keys, values, valid_lens)
复制代码
tensor([[[ 2.0000,  3.0000,  4.0000,  5.0000]],

        [[10.0000, 11.0000, 12.0000, 13.0000]]], grad_fn=<BmmBackward0>)
python 复制代码
# 注意力权重
# 调用d2l.show_heatmaps函数,显示注意力权重的热图
d2l.show_heatmaps(attention.attention_weights.reshape((1,1,2,10)),
                 xlabel='Keys', ylabel='Queries')
python 复制代码
# 缩放点积注意力
class DotProductAttention(nn.Module):
    """缩放点积注意力"""
    def __init__(self, dropout, **kwargs):
        super(DotProductAttention, self).__init__(**kwargs)
        # Dropout层,用于随机丢弃一部分注意力权重
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, queries, keys, values, valid_lens=None):
        # 获取查询向量的维度d
        d = queries.shape[-1]
        # 计算点积注意力得分,并进行缩放
        scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d)
        # 使用遮蔽softmax计算注意力权重
        self.attention_weights = masked_softmax(scores, valid_lens)
        # 根据注意力权重对values进行加权求和
        return torch.bmm(self.dropout(self.attention_weights), values)
python 复制代码
# 演示上述的DotProductAttention类
# 创建查询张量
queries = torch.normal(0,1,(2,1,2))
# 创建缩放点积注意力对象
attention = DotProductAttention(dropout=0.5)
# 设置为评估模式,不使用dropout
attention.eval()
# 调用缩放点积注意力对象的forward方法
attention(queries, keys, values, valid_lens)
复制代码
tensor([[[ 2.0000,  3.0000,  4.0000,  5.0000]],

        [[10.0000, 11.0000, 12.0000, 13.0000]]])
python 复制代码
# 均匀的注意力权重
# 调用d2l.show_heatmaps函数,显示注意力权重的热图
d2l.show_heatmaps(attention.attention_weights.reshape((1,1,2,10)),
                 xlabel='Keys', ylabel='Queries')

1. 使用注意力机制的seq2seq

2. 使用注意力机制的seq2seq

python 复制代码
import torch
from torch import nn
from d2l import torch as d2l
import os
python 复制代码
# 带有注意力机制的解码器基本接口
class AttentionDecoder(d2l.Decoder):
    """带有注意力机制的解码器基本接口"""
    def __init__(self, **kwargs):
        super(AttentionDecoder, self).__init__(**kwargs)
        
    @property
    def attention_weight(self):
        raise NotImplementedError
python 复制代码
# 实现带有Bahdanau注意力的循环神经网络解码器
class Seq2SeqAttentionDecoder(AttentionDecoder):
    def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, dropout=0, **kwargs):
        # 调用父类AttentionDecoder的构造函数进行初始化
        super(Seq2SeqAttentionDecoder, self).__init__(**kwargs)
        # 创建一个加性注意力机制的实例,用于计算注意力权重
        self.attention = d2l.AdditiveAttention(num_hiddens, num_hiddens, num_hiddens, dropout)  
        # 创建一个嵌入层,用于将输入的整数序列进行嵌入表示
        self.embedding = nn.Embedding(vocab_size, embed_size)
        # 创建一个GRU层,用于实现循环神经网络的计算
        self.rnn = nn.GRU(embed_size + num_hiddens, num_hiddens, num_layers, dropout=dropout)  
        # 创建一个线性层,将隐藏状态映射到词汇表大小的输出
        self.dense = nn.Linear(num_hiddens, vocab_size)
        
    def init_state(self, enc_outputs, enc_valid_lens, *args):
        # 将编码器的输出解包为outputs和hidden_state
        outputs, hidden_state = enc_outputs
        # 对outputs进行维度变换,将batch维和时间步维交换,保持与解码器输入的一致性
        # 返回初始化的解码器隐藏状态
        return (outputs.permute(1, 0, 2), hidden_state, enc_valid_lens)
    
    def forward(self, X, state):
        # 解析输入的状态信息,包括编码器的输出、隐藏状态和有效长度
        enc_outputs, hidden_state, enc_valid_lens = state
        # 对输入序列进行嵌入表示,并进行维度变换,将batch维和时间步维交换
        X = self.embedding(X).permute(1, 0, 2)
        # 初始化输出列表和注意力权重列表
        outputs, self._attention_weights = [], []
        for x in X:
            # 获取当前时间步的查询向量,将隐藏状态的最后一个时间步的特征进行维度扩展
            query = torch.unsqueeze(hidden_state[-1], dim=1)
            # 计算注意力上下文向量,通过注意力机制对编码器的输出进行加权求和
            context = self.attention(query, enc_outputs, enc_outputs, enc_valid_lens)  
            # 将注意力上下文向量与当前时间步的输入进行拼接,用于输入到循环神经网络
            x = torch.cat((context, torch.unsqueeze(x, dim=1)), dim=-1)
            # 执行循环神经网络的前向计算,得到输出和更新后的隐藏状态
            out, hidden_state = self.rnn(x.permute(1,0,2),hidden_state)
            # 将输出和注意力权重添加到对应的列表中
            outputs.append(out)
            self._attention_weights.append(self.attention.attention_weights)
        # 将输出列表中的结果进行拼接,并通过线性层进行映射
        outputs = self.dense(torch.cat(outputs, dim=0))
        # 对输出进行维度变换,将batch维和时间步维交换,并返回更新后的状态信息
        return outputs.permute(1, 0, 2), [enc_outputs, hidden_state, enc_valid_lens]  
    
    @property
    def attention_weights(self):
        # 返回注意力权重
        return self._attention_weights
python 复制代码
# 测试Bahdanau注意力解码器
# 创建编码器和解码器的实例
encoder = d2l.Seq2SeqEncoder(vocab_size = 10, embed_size=8, num_hiddens=16, num_layers=2)  
encoder.eval()
decoder = Seq2SeqAttentionDecoder(vocab_size=10, embed_size=8, num_hiddens=16,num_layers=2)  
decoder.eval()
# 创建输入序列X
X = torch.zeros((4,7), dtype=torch.long)
# 初始化解码器的状态
state = decoder.init_state(encoder(X), None)
# 执行解码器的前向计算
output, state = decoder(X, state)
# 输出结果的形状以及状态信息的长度和形状
output.shape, len(state), state[0].shape, len(state[1]), state[1][0].shape
复制代码
(torch.Size([4, 7, 10]), 3, torch.Size([4, 7, 16]), 2, torch.Size([4, 16]))
python 复制代码
def read_data_nmt():
    """载入 "英语-法语" 数据集 """
    # 下载并解压数据集
    data_dir = d2l.download_extract('fra-eng')
    # 打开数据文件,使用utf-8编码读取文件内容
    with open(os.path.join(data_dir, 'fra.txt'), 'r', encoding='utf-8') as f:
        # 返回读取的文件内容
        return f.read()
python 复制代码
def preprocess_nmt(text):
    """预处理 "英语-法语" 数据集"""
    def no_space(char, prev_char):
        # 检查是否需要在字符之前添加空格
        return char in set(',.!?') and prev_char != ''
    # 替换特殊字符为空格并转换为小写
    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)
python 复制代码
def tokenize_nmt(text, num_examples=None):
    """词元化 "英语-法语" 数据数据集 """
    # 初始化源语言和目标语言的列表
    source, target = [], []
    # 对每行文本进行遍历
    for i, line in enumerate(text.split('\n')):
        # 如果指定了num_examples,并且超过了指定数量,则退出循环
        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
python 复制代码
def truncate_pad(line, num_steps, padding_token):
    """截断或填充文本序列"""
    # 如果文本序列的长度超过了指定的步数
    if len(line) > num_steps:
        # 截断文本序列,保留前num_steps个元素
        return line[:num_steps]
    # 在文本序列末尾填充padding_token,使其长度达到num_steps
    return line + [padding_token] * (num_steps - len(line))
python 复制代码
def build_array_nmt(lines, vocab, num_steps):
    """将机器翻译的文本序列转换成小批量"""
    # 将文本序列中的每个词根据词汇表转换为对应的索引
    lines = [vocab[l] for l in lines]
    # 在每个文本序列末尾添加<eos>表示句子结束的索引
    lines = [l + [vocab['<eos>']] for l in lines]
    # 将每个文本序列截断或填充为固定长度,并转换为tensor形式
    array = torch.tensor([ truncate_pad(l, num_steps, vocab['<pad>']) for l in lines ])
    # 计算有效长度,即非填充部分的长度
    valid_len = (array != vocab['<pad>']).type(torch.int32).sum(1)
    # 返回转换后的tensor形式的文本序列和有效长度
    return array, valid_len
python 复制代码
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
python 复制代码
# 训练
# 设置嵌入大小、隐藏层大小、层数和丢弃率
embed_size, num_hiddens, num_layers, dropout = 32, 32, 2, 0.1
# 设置批量大小和序列长度
batch_size, num_steps = 64, 10
# 设置学习率、训练轮数和设备(GPU或CPU)
lr, num_epochs, device = 0.005, 250, d2l.try_gpu()

# 载入翻译数据集,并获取数据迭代器和词汇表
train_iter, src_vocab, tgt_vocab = load_data_nmt(batch_size, num_steps)  
# 创建源语言的编码器实例
encoder = d2l.Seq2SeqEncoder(len(src_vocab), embed_size, num_hiddens, num_layers, dropout)  
# 创建目标语言的带有注意力机制的解码器实例
decoder = Seq2SeqAttentionDecoder(len(tgt_vocab), embed_size, num_hiddens, num_layers, dropout)   
# 创建编码-解码模型实例
net = d2l.EncoderDecoder(encoder, decoder)
# 训练序列到序列模型
d2l.train_seq2seq(net, train_iter, lr, num_epochs, tgt_vocab, device)
复制代码
loss 0.018, 7276.1 tokens/sec on cuda:0
python 复制代码
# 将几个英语句子翻译成汉语
# 英语句子列表
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):
    # 使用训练好的模型net对英语句子进行翻译,并获取注意力权重序列
    translation, dec_attention_weight_seq = d2l.predict_seq2seq(net, eng, src_vocab,
                                                               tgt_vocab, num_steps, device, True)   
    # 打印英语句子、翻译结果和BLEU分数
    print(f' {eng} => {translation}, ',
         f'bleu {d2l.bleu(translation, fra, k=2):.3f}')
复制代码
 go => va  !,  bleu 0.000
 i lost . => j'ai perdu .,  bleu 0.492
 he's calm . => il est malade .,  bleu 0.658
 i'm home . => je suis chez moi .,  bleu 1.000
python 复制代码
# 将注意力权重序列进行拼接,并调整形状
attention_weights = torch.cat([step[0][0][0] for step in dec_attention_weight_seq], 0).reshape((1, 1, -1, num_steps))     
python 复制代码
# 可视化注意力权重
# 显示注意力权重的热图,仅显示与输入英语句子对应的位置
d2l.show_heatmaps(attention_weights[:,:,:,:len(engs[-1].split()) + 1].cpu(),
                 xlabel = 'key positions', ylabel = 'Query posistions')

1. 自注意力

2. 自注意力

python 复制代码
import math
import torch
from torch import nn
from d2l import torch as d2l
python 复制代码
# 设置隐藏单元数量和头的数量
num_hiddens, num_heads = 100, 5
# 创建多头注意力实例
# 输入参数为隐藏单元数量、查询维度、键维度、值维度、头的数量和dropout率
attention = d2l.MultiHeadAttention(num_hiddens, num_hiddens, num_hiddens,
                                  num_hiddens, num_heads, 0.5)
# 将多头注意力设置为评估模式,不进行训练
attention.eval()
复制代码
MultiHeadAttention(
  (attention): DotProductAttention(
    (dropout): Dropout(p=0.5, inplace=False)
  )
  (W_q): Linear(in_features=100, out_features=100, bias=False)
  (W_k): Linear(in_features=100, out_features=100, bias=False)
  (W_v): Linear(in_features=100, out_features=100, bias=False)
  (W_o): Linear(in_features=100, out_features=100, bias=False)
)
python 复制代码
# 设置批量大小、查询数和有效长度
batch_size, num_queries, valid_lens = 2, 4, torch.tensor([3, 2])
# 创建形状为(batch_size, num_queries, num_hiddens)的输入张量X,初始化为全1
X = torch.ones((batch_size, num_queries, num_hiddens))
# 对输入张量X应用多头注意力机制,并获取输出的形状
attention(X, X, X, valid_lens).shape
复制代码
torch.Size([2, 4, 100])
python 复制代码
# 位置编码
class PositionalEncoding(nn.Module):
    # 初始化函数,接收隐藏单元数量、dropout率和最大序列长度作为输入
    def __init__(self, num_hiddens, dropout, max_len=1000):
        # 调用父类的初始化函数
        super(PositionalEncoding, self).__init__()
        # 创建一个dropout层,用于随机丢弃输入的元素
        self.dropout = nn.Dropout(dropout)
        # 创建一个形状为(1, max_len, num_hiddens)的位置编码张量P,初始化为全0
        self.P = torch.zeros((1, max_len, num_hiddens))
        # 生成位置编码矩阵X,其中每一行表示一个位置的编码,编码方式采用sin和cos函数
        # 编码公式:X[i, j] = sin(i / 10000^(2j / num_hiddens)) 或 cos(i / 10000^(2j / 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)
        # 将位置编码矩阵中的偶数维度的元素替换为sin函数的结果
        self.P[:,:,0::2] = torch.sin(X)
        # 将位置编码矩阵中的奇数维度的元素替换为cos函数的结果
        self.P[:,:,1::2] = torch.cos(X)
        
    # 前向传播函数,接收输入张量X作为输入
    def forward(self, X):
        # 将位置编码张量P与输入张量X相加,并将结果移动到与X相同的设备上
        X = X + self.P[:, :X.shape[1], :].to(X.device)
        # 对相加后的结果应用dropout,并返回结果
        return self.dropout(X)
python 复制代码
# 行代表标记在序列中的位置,列代表位置编码的不同维度
# 设置位置编码的维度和序列的长度
encoding_dim, num_steps = 32, 60
# 创建位置编码器实例,传入位置编码的维度和dropout率
pos_encoding = PositionalEncoding(encoding_dim, 0)
# 将位置编码器设置为评估模式,不进行训练
pos_encoding.eval()
# 应用位置编码器到全0张量上,得到位置编码后的张量X
X = pos_encoding(torch.zeros((1, num_steps, encoding_dim)))
# 获取位置编码器中的位置编码张量P,截取与X相同长度的部分
P = pos_encoding.P[:, :X.shape[1], :]
# 绘制位置编码张量P中特定维度的子集
d2l.plot(torch.arange(num_steps), P[0, :, 6:10].T, xlabel='Row (position)',
        figsize=(6, 2.5), legend=["Col %d" % d for d in torch.arange(6, 10)])   
python 复制代码
# 循环遍历范围为0到7的数字
for i in range(8):
    # 打印当前数字的二进制表示,使用字符串格式化进行对齐和补零
    print(f'{i} in binary is {i:>03b}')
复制代码
0 in binary is 000
1 in binary is 001
2 in binary is 010
3 in binary is 011
4 in binary is 100
5 in binary is 101
6 in binary is 110
7 in binary is 111
python 复制代码
# 在编码维度上降低频率
# 从位置编码张量P中获取第一个样本的编码部分,并添加两个维度
P = P[0, :, :].unsqueeze(0).unsqueeze(0)
# 显示热力图,以编码维度为x轴,位置为y轴
d2l.show_heatmaps(P, xlabel='Column (encoding dimension)',
                 ylabel='Row (position)', figsize=(3.5, 4), cmap='Blues')
相关推荐
ZGIAI1 小时前
销售团队最缺的不是另一个 AI,而是有人把跟进这件事一直做下去
人工智能·架构
隔窗听雨眠2 小时前
MCP会成为Agentic AI的标准吗?技术演进、生态博弈与标准之路的深度分析
人工智能
2601_967659882 小时前
2026年多个网页快速提取重点、自动汇总、对比并整理成表格的AI工具清单
人工智能
IT古董2 小时前
AI资讯日报|2026年9月5日:GPT-6 Astra全量推送却遭“翻车“,奥特曼紧急致歉一天12条大新闻:OpenAI翻车、英伟达收购、最狠AI法案出台
人工智能
chen_zn952 小时前
《WAM 系列》Zero-WAM | 人类视频上下文学习 | 未来片段预测 | 零样本跨任务泛化
人工智能·具身智能·vla
airank2 小时前
2026年GEO服务商选型指南:系统梳理服务商分类框架、主流服务商能力横评、企业级选型六大维度及避坑要点,帮助企业在AI搜索时代做出科学决策。
大数据·人工智能·数据分析·aigc
Delite8022 小时前
摆脱实验室束缚:便携式卡尔费休微量水分检测技术与现场应用解析
大数据·网络·人工智能
Jialu.2 小时前
模型压缩实战:BERT 量化从 390MB 到 146MB 的实践
人工智能·深度学习·bert
袋鼠云数栈2 小时前
实时湖仓如何真正做到“数据够新”?
大数据·数据库·人工智能·数据治理