- 自2017年横空出世,transformer⼀直都普遍存在于现代的深度学习应用中,例 如语言、视觉、语音和强化学习领域。
- 学习Transformer不能急躁,需要打好基础。
目录
(2)手写笔记(非参数的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核回归
步骤:
- 定义需要使用“有参数的Nadaraya_Watson核回归”模型训练的函数f(x)
- 初步定义训练数据:Q,K,V
- 对训练数据进行预处理,并构建模型结构
- 选取损失函数MSELoss,优化器SGD,并开始训练
- 绘制迭代-损失结果图、预测结果图和注意力权重热力图
代码:(含注释)
"""
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曲线图
预测结果和真实结果对比图
注意力权重热力图
>>>如有疑问,欢迎评论区一起探讨