d2l注意力机制

d2l的注意力机制部分,这一部分由于基础薄弱,理解困难,对于代码中的每一步操作,都做了详细注释

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from d2l import torch as d2l
from matplotlib_inline import backend_inline
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
cmap='Reds'):
    
    """显⽰矩阵热图"""
    backend_inline.set_matplotlib_formats('svg')
    # 获取矩阵行列 在刚才那个四维数组里 这里的row和col就是(1,1)
    num_rows, num_cols = matrices.shape[0], matrices.shape[1]

    # 创建一个plt.subplot对象,包含行列,share表示共享x,y轴
    fig, axes = plt.subplots(num_rows, num_cols, figsize=figsize,sharex=True, sharey=True, squeeze=False)
    # axes为(1,1)
    for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
        for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
            # ax 是一个代表子图(Axes 对象)的变量
            # imshow 函数接受一个矩阵(二维数组)作为输入
            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);
attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
print(attention_weights)
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')
tensor([[[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]]]])

这里是在生成样本

与其说是训练数据,倒不如说是待拟合的曲线

n_train = 50 # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5) # 排序后的训练样本
print(x_train)  # 由小到大排序了 由小到大的随机数 50个 0-5
print(_)
tensor([0.0242, 0.0304, 0.0714, 0.2212, 0.3445, 0.5505, 0.6742, 0.6828, 0.7654,
        0.8208, 1.0969, 1.2353, 1.3248, 1.3310, 1.4640, 1.6394, 1.6605, 1.8268,
        1.9079, 1.9425, 2.1019, 2.1221, 2.2390, 2.2930, 2.5946, 2.6120, 2.7567,
        2.9374, 2.9783, 3.0069, 3.0853, 3.0946, 3.1073, 3.1225, 3.1675, 3.2284,
        3.2396, 3.5591, 3.6327, 3.7487, 3.9388, 4.0640, 4.0876, 4.1110, 4.2617,
        4.3653, 4.6591, 4.6686, 4.8065, 4.8699])
tensor([34,  2, 22, 14,  3, 16, 13, 15, 36, 17, 18, 30, 19, 35, 32, 12, 27, 44,
        45, 40, 37, 21, 47,  5, 26, 39, 43, 10, 48, 20,  1, 38, 49,  9, 41,  0,
        46, 23,  6, 25, 29, 11,  7,  8, 24, 31, 33,  4, 42, 28])
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) # 测试样本 或者说是拟合的曲线x轴

y_truth = f(x_test) # 测试样本的真实输出 应该拟合出的结果
n_test = len(x_test) # 测试样本数
n_test
50
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])
    plt.plot(x_train, y_train, 'o', alpha=0.5);

平均汇聚

y_hat = torch.repeat_interleave(y_train.mean(), n_test)
print(y_hat)
plot_kernel_reg(y_hat)
tensor([2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657,
        2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657,
        2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657,
        2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657,
        2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657, 2.3657,
        2.3657, 2.3657, 2.3657, 2.3657, 2.3657])

非参数注意力汇聚

# X_repeat的形状:(n_test,n_train),

# 每⼀⾏都包含着相同的测试输⼊(例如:同样的查询)
X_repeat = x_test.repeat_interleave(n_train)  # [1,2]就变成[1,1,2,2]
print(X_repeat.shape)
X_repeat=X_repeat.reshape((-1, n_train))
print(X_repeat.shape)
print(X_repeat[1],X_repeat[2])


# x_train包含着键。 attention_weights的形状: (n_test,n_train),
# 每⼀⾏都包含着要在给定的每个查询的值(y_train)之间分配的注意⼒权重
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)
# dim=1怎么理解

# y_hat的每个元素都是值的加权平均值,其中的权重是注意⼒权重
y_hat = torch.matmul(attention_weights, y_train)
# 对于attention_weights的某一行,都是一个经过softmax归一化的权重
# 代表第一个点(q)与每一个x_train的相似度(权重),权重乘以y_train,就是想得到的加权y_test
plot_kernel_reg(y_hat)
torch.Size([2500])
torch.Size([50, 50])
tensor([0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000,
        0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000,
        0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000,
        0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000,
        0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000, 0.1000,
        0.1000, 0.1000, 0.1000, 0.1000, 0.1000]) tensor([0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000,
        0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000,
        0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000,
        0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000,
        0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000, 0.2000,
        0.2000, 0.2000, 0.2000, 0.2000, 0.2000])
d2l.show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),xlabel='Sorted training inputs',ylabel='Sorted testing inputs')

带参数注意力汇聚

X = torch.ones((2, 1, 4))
Y = torch.ones((2, 4, 6))
torch.bmm(X, Y).shape
torch.Size([2, 1, 6])
weights = torch.ones((2, 10)) * 0.1
print(weights.unsqueeze(1).shape)
values = torch.arange(20.0).reshape((2, 10))
print(values.unsqueeze(-1).shape)
print(torch.bmm(weights.unsqueeze(1), values.unsqueeze(-1)).shape)
torch.Size([2, 1, 10])
torch.Size([2, 10, 1])
torch.Size([2, 1, 1])
class NWKernelRegression(nn.Module):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.w = nn.Parameter(torch.rand((1,), requires_grad=True))

    def forward(self, queries, keys, values):
        # queries和attention_weights的形状为(查询个数,“键-值”对个数)
        queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))
                                                                    
        self.attention_weights = nn.functional.softmax(-((queries - keys) * self.w)**2 / 2, dim=1)

        # values的形状为(查询个数,“键-值”对个数)
        return torch.bmm(self.attention_weights.unsqueeze(1),values.unsqueeze(-1)).reshape(-1)

生成k-v keys其实就是横轴的坐标

# X_tile的形状:(n_train, n_train),每⼀⾏都包含着相同的训练输⼊
X_tile = x_train.repeat((n_train, 1))
print(x_train.shape,X_tile.shape)
# Y_tile的形状:(n_train, n_train),每⼀⾏都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
print(y_train.shape,Y_tile.shape)
# keys的形状:('n_train', 'n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
print(keys.shape)
# values的形状:('n_train', 'n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
print(values.shape)

torch.Size([50]) torch.Size([50, 50])
torch.Size([50]) torch.Size([50, 50])
torch.Size([50, 49])
torch.Size([50, 49])
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])
for epoch in range(5):
    trainer.zero_grad()
    l = loss(net(x_train, keys, values), y_train)
    l.sum().backward()
    trainer.step()
    print(f'epoch {epoch + 1}, loss {float(l.sum()):.6f}')
    animator.add(epoch + 1, float(l.sum()))

# keys的形状:(n_test, n_train),每⼀⾏包含着相同的训练输⼊(例如,相同的键)
keys = x_train.repeat((n_test, 1))
# value的形状:(n_test, n_train)
values = y_train.repeat((n_test, 1))
y_hat = net(x_test, keys, values).unsqueeze(1).detach()
plot_kernel_reg(y_hat)

d2l.show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),xlabel='Sorted training inputs',ylabel='Sorted testing inputs')


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

熊熊想读研究生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值