动手学深度学习(task1)注意力机制(更新中)

Posted 山顶夕景

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动手学深度学习(task1)注意力机制(更新中)相关的知识,希望对你有一定的参考价值。

note

  • 注意力机制

文章目录

零、基础回顾

0.0 不同人员的学习定位

  • AI相关从业人员(产品经理等):掌握What,知道名词,能干什么
  • 数据科学家、工程师:掌握What、How,手要快,能出活
  • 研究员、学生:掌握What、How、Why,除了知道有什么和怎么做,还要知道为什么,思考背后的原因,做出新的突破

0.1 AI地图


y轴表示可以达到的层次:由底部向上依次是

感知:了解是什么,比如能够可以看到物体,如面前的一块屏幕

推理:基于感知到的现象,想象或推测未来会发生什么

知识:根据看到的数据或者现象,形成自己的知识

规划:根据学习到的知识,做出长远的规划

  • NLP:停留在【感知层面】,如机器翻译;NLP从【符号学】的方法,到【概率模型】,到现在的【机器学习|深度学习】。
  • CV:在感知层面上,对图片做一些推理;图片里都是像素,很难用nlp的那种符号学解释,所以一般用【概率模型】和【机器学习|深度学习】。
  • 深度学习:机器学习的一种,包括CV、NLP、强化学习等。

0.2 深度学习的应用

  • 物体检测和分割:图片内容、物体是啥、物体位置;物体分割指每个像素属于什么,属于飞机还是人等;
  • 样式迁移:原图片+迁移风格=风格迁移后的图片
  • 文生图:如diffusion model
  • 文字生成:如ChatGPT
  • 广告点击:
    • 步骤:
      • 触发:用户输入关键词,机器先找到一些相关的广告
      • 点击率预估: 利用机器学习的模型预测用户对广告的点击率
      • 排序:利用 点击率 x 竞价 的结果进行排序呈现广告,排名高的在前面呈现
    • 模型的预测:数据 (待预测广告) → 特征提取 → 模型 → 点击率预测
    • 训练数据 (过去广告展现和用户点击) → 特征(X)和用户点击(Y) → 喂给模型训练

0.3 答疑

Q1:领域专家是什么意思?

举个例子,比如我要做农业上的物体识别,我种了一棵树,想要看今年的收成怎么样,我有很多很多土地,用人去一个个查看很费力,于是我用一个无人机,将农作物的情况拍下来,假设得到了树的一些图片,而数据科学家不知道农作物什么样的情况是好,什么样是坏,于是领域专家进行解释,比如多少叶子算是好,什么样不好。同时数据科学家将领域专家的问题翻译成机器学习能做的任务。所以可以认为领域专家提需求的人甲方,而数据科学家乙方

Q2:符号学可以和机器学习融合起来吗?

确实是可以的。目前来说,符号学深度学习有一些新的进展,以前说符号学就是做一些符号上的推理,目前深度学习如图神经网络,可以做一些比较复杂的推理。

Q3:说自然语言处理仅仅停留在感知层面似乎不太合适?因为语言的理解和产出不仅仅是感知,也涉及到语言知识和世界知识,也涉及到规划,比如机器规划下一步要做什么。

语言当然是一个很复杂的过程,我只是想说,自然语言处理我们做得还很一般,虽然能做一些感知以外的东西,但是我感觉是说,不如深度学习特别机器学习,在图片上的应用做得好一些。当然AI地图上也只是一个大致的分类

Q4:如何寻找自己领域的paper的经验吗?

因为大家如果现在去读paper的话,可能每天都有一百篇paper出来,你怎么样去找到你想要的paper,总不能天天看朋友圈推文,这样只能知道别人读过的paper,不会有自己独特的见解

Q5:以无人驾驶为例,误判率在不断下降,但误判的影响还是很严重的,有可能从已有的判断case(样例)得到修正,从而完全避免这样的错误吗?

无人驾驶中,任何一次出现的错误,都可能带来毁灭性的灾难。大家可能看到,特斯拉今天撞了,明天又撞了。所以说,无人驾驶对于错误率确实是非常注重的。

机器学习在学术界现在有很多关于uncertainty或者robustness的研究,就是说模型在数据偏移或者极端情况下会不会给出很不好的答案,我们不会特别深入去讲这个事情,但是无人驾驶这一块确实会通过大量的技术,比如说把不同的模型融合在一起,不是仅仅train一个模型,用多个模型来做投票。汽车有很多雷达、摄像头,它会通过不同的传感器来进行模型的融合,从而降低误差。

因为涉及到评价无人驾驶的特别技术,但在竞赛中我们会给大家看到如何通过融合多个模型提升精度的做法。

一、可视化注意力权重

1.1 查询、键和值

自主性的与非自主性的注意力提示解释了人类的注意力的方式,下面来看看如何通过这两种注意力提示,用神经网络来设计注意力机制的框架,

首先,考虑一个相对简单的状况,即只使用非自主性提示。要想将选择偏向于感官输入,则可以简单地使用参数化的全连接层,甚至是非参数化的最大汇聚层或平均汇聚层。

因此:

  • “是否包含自主性提示”将注意力机制与全连接层或汇聚层区别开来。
    • 查询query:在注意力机制的背景下,自主性提示。给定任何查询,注意力机制通过注意力汇聚(attention pooling)将选择引导至感官输入(sensory inputs,例如中间特征表示)。
    • 在注意力机制中,这些感官输入被称为(value)。每个值都与一个(key)配对,这可以想象为感官输入的非自主提示。如下图所示,可以通过设计注意力汇聚的方式,便于给定的查询(自主性提示)与键(非自主性提示)进行匹配,这将引导得出最匹配的值(感官输入)。

这个框架下的模型将成为本章的中心。然而,注意力机制的设计有许多替代方案。例如可以设计一个不可微的注意力模型,该模型可以使用强化学习方法(Mnih et al., 2014)进行训练。

1.2 注意力的可视化

平均汇聚层可以被视为输入的加权平均值,其中各输入的权重是一样的。实际上,注意力汇聚得到的是加权平均的总和值,其中权重是在给定的查询和不同的键之间计算得出的。

import torch
import matplotlib.pyplot as plt
from matplotlib_inline import backend_inline
# from d2l import torch as d2l

# metrices: shape, [要显示的行数,要显示的列数,查询的数目, 键的数目]
# 可视化注意力权重
#@save
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
                  cmap='Reds'):
    """显示矩阵热图"""
    backend_inline.set_matplotlib_formats('svg') # format
    # d2l.use_svg_display()
    num_rows, num_cols = matrices.shape[0], matrices.shape[1]
    # fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,
    #                              sharex=True, sharey=True, squeeze=False)
    fig, axes = 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);

# 当查询和键相同时,注意力权重为1,否则为0
attention_weights = torch.eye(10).reshape((1, 1, 10, 10))
# 显示注意力权重
show_heatmaps(attention_weights, xlabel='Keys', ylabel='Queries')

上面的栗子,仅当查询和键相同时,注意力权重为1,否则为0。后面也经常用show_heatmaps函数来显示注意力权重。

1.3 小结和练习

【小结】

  • 受试者使用非自主性和自主性提示有选择性地引导注意力。前者基于突出性,后者则依赖于意识。
  • 注意力机制与全连接层或者汇聚层的区别源于增加的自主提示。
  • 由于包含了自主性提示,注意力机制与全连接的层或汇聚层不同。
  • 注意力机制通过注意力汇聚使选择偏向于值(感官输入),其中包含查询(自主性提示)和键(非自主性提示)。键和值是成对的。
  • 可视化查询和键之间的注意力权重是可行的。
  • 查询(自主提示)和键(非自主提示)之间的交互形成了注意力汇聚;注意力汇聚有选择地聚合了值(感官输入)以生成最终的输出。1964年提出的Nadaraya-Watson核回归模型是一个简单但完整的例子,可以用于演示具有注意力机制的机器学习。

【练习】
(1)在机器翻译中通过解码序列词元时,其自主性提示可能是什么?非自主性提示和感官输入又是什么?

(2)随机生成一个10 X 10矩阵并使用softmax运算来确保每行都是有效的概率分布,然后可视化输出注意力权重。

import torch
import torch.nn.functional as F
matrix = torch.randn(10, 10)
# 确保每行是有效的概率分布
softmax_matrix = F.softmax(matrix, dim = 1)
# 可视化注意力权重
show_heatmaps(softmax_matrix.unsqueeze(0).unsqueeze(0), "Keys", "Queries")

二、注意力汇聚:Nadaraya-Watson 核回归

  • Nadaraya-Watson核回归是具有注意力机制的机器学习范例。
  • Nadaraya-Watson核回归的注意力汇聚是对训练数据中输出的加权平均。从注意力的角度来看,分配给每个值的注意力权重取决于将值所对应的键和查询作为输入的函数。
  • 注意力汇聚可以分为非参数型和带参数型。

2.1 平均汇聚

回归问题:给定的成对的“输入-输出”数据集 ( x 1 , y 1 ) , … , ( x n , y n ) \\(x_1, y_1), \\ldots, (x_n, y_n)\\ (x1,y1),,(xn,yn),如何学习 f f f来预测任意新输入 x x x的输出 y ^ = f ( x ) \\haty = f(x) y^=f(x)

【准备数据集】根据下面的非线性函数生成一个人工数据集,其中加入的噪声项为 ϵ \\epsilon ϵ y i = 2 sin ⁡ ( x i ) + x i 0.8 + ϵ , y_i = 2\\sin(x_i) + x_i^0.8 + \\epsilon, yi=2sin(xi)+xi0.8+ϵ,

  • ϵ \\epsilon ϵ服从均值为 0 0 0和标准差为 0.5 0.5 0.5的正态分布
  • 下面生成了 50 50 50个训练样本和 50 50 50个测试样本。为了更好地可视化之后的注意力模式,需要将训练样本进行排序。
# 导库,绘图格式设置
import torch
import torch.nn as nn
from d2l import torch as d2l
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)

# 和上面和下面的可视化函数结果等价
def use_svg_display():
    """Use the svg format to display a plot in Jupyter.

    Defined in :numref:`sec_calculus`"""
    backend_inline.set_matplotlib_formats('svg')

def set_figsize(figsize=(3.5, 2.5)):
    """Set the figure size for matplotlib.

    Defined in :numref:`sec_calculus`"""
    use_svg_display()
    d2l.plt.rcParams['figure.figsize'] = figsize

def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
    """Set the axes for matplotlib.

    Defined in :numref:`sec_calculus`"""
    axes.set_xlabel(xlabel)
    axes.set_ylabel(ylabel)
    axes.set_xscale(xscale)
    axes.set_yscale(yscale)
    axes.set_xlim(xlim)
    axes.set_ylim(ylim)
    if legend:
        axes.legend(legend)
    axes.grid()

def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,
         ylim=None, xscale='linear', yscale='linear',
         fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):
    """Plot data points.

    Defined in :numref:`sec_calculus`"""
    if legend is None:
        legend = []

    set_figsize(figsize)
    axes = axes if axes else d2l.plt.gca()

    # Return True if `X` (tensor or list) has 1 axis
    def has_one_axis(X):
        return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)
                and not hasattr(X[0], "__len__"))

    if has_one_axis(X):
        X = [X]
    if Y is None:
        X, Y = [[]] * len(X), X
    elif has_one_axis(Y):
        Y = [Y]
    if len(X) != len(Y):
        X = X * len(Y)
    axes.cla()
    for x, y, fmt in zip(X, Y, fmts):
        if len(x):
            axes.plot(x, y, fmt)
        else:
            axes.plot(y, fmt)
    set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)

def plot_kernel_reg(y_hat):
    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);

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

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)  # 测试样本数

# 2. 平均汇聚
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)

上面使用最简单的评估器——平均汇聚求所有训练样本输出值的平均值,显然没啥用,相差有点大。

2.2 非参数注意力汇聚

显然,平均汇聚忽略了输入 x i x_i xi
于是Nadaraya :cite:Nadaraya.1964和Watson :cite:Watson.1964提出了一个更好的想法,根据输入的位置对输出 y i y_i yi进行加权:
f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i , f(x) = \\sum_i=1^n \\fracK(x - x_i)\\sum_j=1^n K(x - x_j) y_i, f(x)=i=1nj=1nK(xxj)K(xxi)yi,

其中 K K K(kernel)。上面公式所描述的估计器被称为Nadaraya-Watson核回归(Nadaraya-Watson kernel regression)。

但受此启发,我们可以上图中的注意力机制框架的角度重写 :eqref:eq_nadaraya-watson,成为一个更加通用的注意力汇聚(attention pooling

动手学深度学习(五十)——多头注意力机制

文章目录

1. 为什么用多头注意力机制

  • 所谓自注意力机制就是通过某种运算来直接计算得到句子在编码过程中每个位置上的注意力权重;然后再以权重和的形式来计算得到整个句子的隐含向量表示。

  • 自注意力机制的缺陷就是:模型在对当前位置的信息进行编码时,会过度的将注意力集中于自身的位置, 因此作者提出了通过多头注意力机制来解决这一问题

2. 什么是多头注意力机制

  在实践中,当给定相同的查询、键和值的集合时,我们希望模型可以基于相同的注意力机制学习到不同的行为,然后将不同的行为作为知识组合起来,例如捕获序列内各种范围的依赖关系(例如,短距离依赖和长距离依赖)。因此,允许注意力机制组合使用查询、键和值的不同的 子空间表示(representation subspaces)可能是有益的。

  为此,与使用单独的一个注意力池化不同,我们可以独立学习得到 h h h 组不同的 线性投影(linear projections)来变换查询、键和值。然后,这 h h h 组变换后的查询、键和值将并行地进行注意力池化。最后,将这 h h h 个注意力池化的输出拼接在一起,并且通过另一个可以学习的线性投影进行变换,以产生最终输出。这种设计被称为 多头注意力,其中 h h h 个注意力池化输出中的每一个输出都被称作一个 Vaswani.Shazeer.Parmar.ea.2017。下图展示了使用全连接层来实现可以学习的线性变换的多头注意力。

3. 多头注意力机制模型和理论计算

  在实现多头注意力之前,让我们用数学语言将这个模型形式化地描述出来。给定查询 q ∈ R d q \\mathbfq \\in \\mathbbR^d_q qRdq、键 k ∈ R d k \\mathbfk \\in \\mathbbR^d_k kRdk 和值 v ∈ R d v \\mathbfv \\in \\mathbbR^d_v vRdv,每个注意力头 h i \\mathbfh_i hi ( i = 1 , … , h i = 1, \\ldots, h i=1,,h) 的计算方法为

h i = f ( W i ( q ) q , W i ( k ) k , W i ( v ) v ) ∈ R p v , \\mathbfh_i = f(\\mathbf W_i^(q)\\mathbf q, \\mathbf W_i^(k)\\mathbf k,\\mathbf W_i^(v)\\mathbf v) \\in \\mathbb R^p_v, hi=f(Wi(q)q,Wi(k)k,Wi(v)v)Rpv,

其中,可学习的参数包括 W i ( q ) ∈ R p q × d q \\mathbf W_i^(q)\\in\\mathbb R^p_q\\times d_q Wi(q)Rpq×dq W i ( k ) ∈ R p k × d k \\mathbf W_i^(k)\\in\\mathbb R^p_k\\times d_k Wi(k)Rpk×dk W i ( v ) ∈ R p v × d v \\mathbf W_i^(v)\\in\\mathbb R^p_v\\times d_v Wi(v)Rpv×dv ,以及代表注意力池化的函数 f f f 可以是可加性注意力和缩放的“点-积”注意力。多头注意力的输出需要经过另一个线性转换,它对应着 h h h 个头拼接后的结果,因此其可学习参数是 W o ∈ R p o × h p v \\mathbf W_o\\in\\mathbb R^p_o\\times h p_v WoRpo×hpv

W o [ h 1 ⋮ h h ] ∈ R p o . \\mathbf W_o \\beginbmatrix\\mathbf h_1\\\\\\vdots\\\\\\mathbf h_h\\endbmatrix \\in \\mathbbR^p_o. Woh1hhRpo.

基于这种设计,每个头都可能会关注输入的不同部分。可以表示比简单加权平均值更复杂的函数。

有掩码的多头注意力:

  • 解码器对序列中一个元素输出时,不应该考虑该元素之后的元素
  • 通过掩码进行实现:在计算 x i x_i xi输出时,假装当前序列长度为i

微观下的多头Attention可以表示为:

4. 动手实现多头注意力机制层

  在实现过程中,我们选择了缩放的“点-积”注意力作为每一个注意力头。为了避免计算成本和参数数量的显著增长,我们设置了 p q = p k = p v = p o / h p_q = p_k = p_v = p_o / h pq=pk=pv=po/h。值得注意的是,如果我们将查询、键和值的线性变换的输出数量设置为 p q h = p k h = p v h = p o p_q h = p_k h = p_v h = p_o pqh=pkh=pvh=po,则可以并行计算 h h h 头。在下面的实现中, p o p_o po 是通过参数num_hiddens 指定的。

import math
import torch
from torch import nn
from d2l import torch as d2l
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)

    # `output` 的形状: (`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)

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) # 将输入映射为(batch_size,query_size/k-v size,num_hidden)大小的输出
        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`, or `values` 的形状:
            # (`batch_size`, 查询或者“键-值”对的个数, `num_hiddens`)
        # `valid_lens` 的形状:
            # (`batch_size`,) or (`batch_size`, 查询的个数)
        # 经过变换后,输出的 `queries`, `keys`, or `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:
            valid_lens = torch.repeat_interleave(valid_lens,
                                                repeats=self.num_heads,
                                                dim以上是关于动手学深度学习(task1)注意力机制(更新中)的主要内容,如果未能解决你的问题,请参考以下文章

注意力机制 attention 注意力分数 动手学深度学习v2

注意力机制 attention 注意力分数 动手学深度学习v2

Attention使用注意力机制的seq2seq 动手学深度学习v2

Attention使用注意力机制的seq2seq 动手学深度学习v2

自注意力 self attention Transformer 多头注意力代码 Transformer 代码 动手学深度学习v2

《动手学深度学习》PyTorch: 数据操作