攻克 Transformer & 注意力机制的查询、键和值 & 有无参数的Nadaraya-Watson核回归

本文介绍了Transformer的概念,包括其自主性和非自主性提示在注意力机制中的对应,以及手写笔记中非参数和有参数Nadaraya-Watson核回归的应用。通过实例演示了如何在深度学习中实现参数化的Transformer模型,并展示了训练过程中的Loss曲线、预测结果和注意力权重分析。
摘要由CSDN通过智能技术生成
  • 自2017年横空出世,transformer⼀直都普遍存在于现代的深度学习应用中,例 如语言、视觉、语音和强化学习领域。
  • 学习Transformer不能急躁,需要打好基础。

目录

(1)概念解释

(2)手写笔记(非参数的Nadaraya-Watson核回归)

(3)带有参数的Nadaraya-Watson核回归


(1)概念解释

1. 自主性提示(意志线索):假设你想找到你的 Pen,此时是你的意志主动的请求大脑找到 Pen 

2. 非自主性提示(非意识线索):你的大脑开始工作,结合以往的经验(神经元权重)去找 Pen

3. 感官输入:物体通过你的眼睛传入你的大脑,直至你的大脑判断次物体是 Pen 为止;注意,此时你的键值对是匹配的

  • 在注意力机制的背景下,我们将自主性提示称为查询(query)
  • 给定任何查询,注意⼒机制通过注意力汇聚将选择引导⾄感官输⼊(例如中间特征表⽰)。
  • 在注意⼒机制中,这些感官输⼊被称为值(value)
  • 更通俗的解释,每个值都与⼀个键(key)配对,这可以想象为感官输⼊的自主性提示。
  • 如下图所⽰,我们可以设计注意⼒汇聚,以便给定的查询(自主性提示)可以与键(非自主性提示)进⾏匹配,这将引导得出最匹配的值(感官输⼊)。

 (2)手写笔记(非参数的Nadaraya-Watson核回归)

预测效果图

由观察可知“查询-键”对越接近,注意力汇聚的注意力权重就越高。 


⾮参数的Nadaraya-Watson核回归具有⼀致性(consistency)的优点:如果有⾜够的数据,此模型会收敛到 最优结果。尽管如此,我们还是可以轻松地将可学习的参数集成到注意⼒汇聚中

例如,与 (10.2.6)略有不同,在下⾯的查询x和键xi之间的距离乘以可学习参数w:


 (3)带有参数的Nadaraya-Watson核回归

步骤:

  1. 定义需要使用“有参数的Nadaraya_Watson核回归”模型训练的函数f(x)
  2. 初步定义训练数据:Q,K,V
  3. 对训练数据进行预处理,并构建模型结构
  4. 选取损失函数MSELoss,优化器SGD,并开始训练
  5. 绘制迭代-损失结果图、预测结果图和注意力权重热力图

代码:(含注释)

"""
1.定义需要使用“有参数的Nadaraya_Watson核回归”模型训练的函数f(x)
2.初步定义训练数据:Q,K,V
3.对训练数据进行预处理,并构建模型结构
4.选取损失函数MSELoss,优化器SGD,并开始训练
5.绘制迭代-损失结果图、预测结果图和注意力权重热力图
注意:
训练的数据和测试数据的对比:训练的查询和测试的查询不一样,键值对基本不变
"""
import matplotlib.pyplot as plt
import torch
from torch import nn
from d2l import torch as d2l
from tqdm import tqdm

n_train = 50  # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5)   # 排序后的训练样本
print('x_train:')
print(x_train)


# 定义需要使用Transformer训练的函数
def f(x):
    return 2 * torch.sin(x) + x**0.8


y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,))  # 训练样本的输出
x_test = torch.arange(0, 5, 0.1)  # 测试样本
y_truth = f(x_test)  # 测试样本的真实输出
n_test = len(x_test)  # 测试样本数

# ==================================处理训练阶段需要输入的Q,K,V==================================== #
# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = x_train.repeat((n_train, 1))  # 沿着x轴复制50次,沿y轴复制1次
print('x_train:', x_train.size())
print("X_tile:", X_tile.size())

# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
print('y_train:', y_train.size())
print("Y_tile:", Y_tile.size())

# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
print('删除主对角线上的元素:', X_tile[(1 - torch.eye(n_train)).type(torch.bool)].size())
print('keys:', keys.size())

# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
print('删除主对角线上的元素:', Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].size())
print('values:', values.size())


class NWKernelRegression(nn.Module):
    """带参数的 Nadaraya Watson 核回归"""
    def __init__(self):
        super().__init__()
        self.w = nn.Parameter(torch.rand((1,), requires_grad=True))
        print('参数:', self.w)

    def forward(self, queries, keys, values):
        """前向传导函数"""
        # queries 和 attention_weights 的形状为(查询个数,“键-值”对个数)
        queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))
        print('queries:', queries.size())
        softmax = nn.Softmax(dim=1)  # 定义 softmax 激活函数
        self.attention_weights = softmax(-((queries - keys) * self.w)**2 / 2)
        print("attention_weights:", self.attention_weights.size())

        # 矩阵相乘
        # values 的形状为(查询个数,“键-值”对个数)
        huiju = torch.bmm(self.attention_weights.unsqueeze(1), values.unsqueeze(-1)).reshape(-1)
        print('huiju:', huiju.size())

        return huiju


# ==================================损失函数随着迭代次数增加而减小==================================== #
net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')  # 定义均方误差损失函数

# 构建一个优化器(SGD、Adam)
# 这个对象能保存当前的参数状态并且基于计算梯度更新参数(self.w)
# 优化器就是需要根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值的作用
trainer = torch.optim.SGD(net.parameters(), lr=0.01)  # 随机梯度下降

# 画图:迭代次数与损失大小的关系图
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 50])

# # 训练
for epoch in tqdm(range(50)):
    trainer.zero_grad()  # 参数梯度初始化为0
    l = loss(net(x_train, keys, values), y_train)  # (huiju - y_train)**2
    l.sum().backward()  # 反向传播计算梯度
    trainer.step()  # 执行单个优化步骤,return the loss
    print("*"*20)
    print(f'epoch {epoch + 1}, loss {float(l.sum()):.6f}')  # 保留六位小数
    animator.add(epoch + 1, float(l.sum()))
plt.show()


# ==============================================预测结果图=============================================== #
def plot_kernel_reg(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)


# keys的形状:(n_test,n_train),每一行包含着相同的训练输入(例如,相同的键)
keys = x_train.repeat((n_test, 1))
# value的形状:(n_test,n_train)
values = y_train.repeat((n_test, 1))

# # 测试
# 输入查询、键和值,经过注意力汇聚,输出预测的值
# net:定义的是带有参数的带参数的 Nadaraya Watson 核回归模型
print("****************************测试数据信息******************************")
print("Q", x_test.size())  # torch.arange(0, 5, 0.1)
print("K", keys.size())  # 复制训练样本(键)50份,并沿着y轴拼接在一起
print("V", values.size())  # 复制训练样本的输出(值)50份,并沿着y轴拼接在一起
y_hat = net(x_test, keys, values).unsqueeze(1).detach()  # .detach(): 保持一部分网络参数不变,切断反向传播
plot_kernel_reg(y_hat)
plt.show()
print("********************************************************************")


# ==================================注意力权重热力图==================================== #
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5), cmap='Reds'):
    """查询与键之间的距离越小,注意力所占的权重越大"""
    d2l.use_svg_display()
    # matrices.size() == torch.Size([50, 50])
    num_rows, num_cols = matrices.shape[0], matrices.shape[1]
    # fig的尺寸: Figure(250x250)    Axes 数组
    fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize, sharex=True, sharey=True, squeeze=False)
    for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
        for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
            pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)
            if i == num_rows - 1:
                ax.set_xlabel(xlabel)
            if j == 0:
                ax.set_ylabel(ylabel)
            if titles:
                ax.set_title(titles[j])
    fig.colorbar(pcm, ax=axes, shrink=0.6)


print(net.attention_weights)
print(net.attention_weights.size())
# 循环的时候缩减维度,故此处需要扩展维度
show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),
              xlabel='Sorted training inputs',
              ylabel='Sorted testing inputs')
plt.show()

>>>output

 Epoch-Loss曲线图

 预测结果和真实结果对比图

 注意力权重热力图

>>>如有疑问,欢迎评论区一起探讨 

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Flying Bulldog

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

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

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

打赏作者

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

抵扣说明:

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

余额充值