注意力机制之——Window Attention

和全局注意力的区别

Window Attention简单理解:crop再计算注意力

窗口注意力不是把图片真的裁成多张图再分别送网络,而是在特征图上按固定窗口分组,只在每个窗口内部计算 self-attention

|---------------------------------------------------|-----------------------------------------------------------|
| 普通全局自注意力 | 窗口注意力 |
| 如果特征图大小是 H x W,token 数是 N = H*W,注意力复杂度大约是N^2 | 把特征图划分成很多个小窗口,比如 8x8 每个窗口内部的 64 个 token 互相看 不同窗口之间暂时不直接交互 |
| 假设特征图是 64x64,复杂度约 4096^2 | 如果窗口是 8x8,分成64个窗口,一共复杂度64x 64^2 |
| | |

全局注意力(Global Attention)的计算复杂度与图像分辨率呈平方级 关系,这在处理高分辨率图像时会导致显存爆炸和计算极其缓慢。窗口注意力通过将图像切分成不重叠的局部窗口,将全局计算转化为了局部的固定大小计算,从而将复杂度降为了线性关系

所以可以简单理解,窗口注意力和全局注意力的区别主要就是分成窗口,分别计算权重之后再合在一起,降低了计算量。

不同窗口间权重共享

所有窗口共用同一套 Q、K、V 的权重矩阵。模型并不会为每一个独立的窗口单独学习一套投影参数。

除了 Q/K/V 权重,窗口注意力通常还会引入相对位置偏置(Relative Position Bias)。由于每个窗口的尺寸是固定的(例如都是 7×77×7 ),因此所有窗口也共享同一套相对位置偏置表(Bias Table)。这使得模型能够学习到窗口内部固定的空间结构关系。

因为所有窗口使用的是相同的权重,且各个窗口之间的计算是完全独立的,所以这种设计非常适合硬件加速。在实际代码实现中,模型会将输入特征图均匀划分为多个窗口,然后利用 GPU 的并行计算能力,一次性对所有窗口执行相同的矩阵乘法操作,从而极大地降低了参数量和计算开销。

和卷积的区别

不同窗口直接权重共享是为什么,这不是就和卷积一样了吗

窗口注意力参数共享指的是Q/K/V 投影参数在所有窗口共享,真正的attention weight 是动态的,是和图像内容有关的:

  • 所有窗口共享 Q/K/V projection 和输出 projection;每个窗口根据自己的特征内容计算出独立的 attention weights。
  • 如果里面是一条横笔画,某个像素可能更关注同一行的像素;如果里面是竖笔画,它可能更关注同一列;
  • 相比之下卷积得到的权重只和位置偏移有关,上方像素用 w_up
  • 这种**内容自适应(Content-aware)**的特性,是注意力机制超越传统卷积的关键
  • 虽然窗口大小是固定的,但有效感受野是动态的。如果窗口内的某个像素和另一个像素相关性极高,它们之间的"连接权重"就会非常大;如果不相关,权重就会趋近于 0。这意味着模型可以"跳过"无关的背景,直接在窗口内建立跨越几个像素的长距离依赖。

局部平移等变性

局部平移等变性对CV是很重要的,如果输入整体向右平移一格,那么输出也会整体向右平移,分类网络通常通过 pooling/global average pooling 获得一定平移不变性(猫在图片任何位置都应该判断为猫),SR、检测、分割更需要平移等变性。

卷积因为"权重共享 + 局部邻域+ stride默认是1"天然具备局部平移等变性;而窗口注意力,虽然共享 QKV,也有类似性质,但即便是全局attention的局部平移等变性也不如卷积,全局 attention 的权重是内容动态生成的。

位置编码也对局部平移等变性有影响:

如果没有位置编码,attention 对 token 的排列本身几乎不敏感;它知道内容相似,不知道空间相邻。

如果加了绝对位置编码,又会让不同位置的同样内容变得不完全一样,造成平移等变性更差;相对位置编码 / relative position bias稍好一点,它不太关心窗口在整张图哪里,只关心窗口内部两点的相对偏移,因此更适合图像任务,也更接近卷积的局部归纳偏置。

而在普通窗口注意力中,通常是 non-overlap window,同一个内容在窗口中的相对位置很可能是不同的,所以窗口注意力平移等变性还要更弱一些。

移位窗口(Shifted Window)

为了加强窗口之间的交流,让相邻窗口之间能够交换信息,扩大有效感受野,同时也提升一些平移等变性,一般有下面的操作:

  • shifted window attention:对 feature 做 cyclic shift,再划分窗口,比如 Swin Transformer;
  • 大核卷积:让窗口外的信息通过卷积传过去;
  • overlapping window:窗口之间有重叠;
  • 多层堆叠:靠层数逐渐扩大感受野。

|---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| | |
| 常规窗口(W-MSA) :就是你说的,把图像切成规则的块,分别计算注意力。这一步负责提取局部特征,极大地降低了计算量。 | 移位窗口(SW-MSA) :在下一个网络层中,将切分的网格整体平移(Shift)几个像素。这样一来,原本在上一层属于不同窗口的像素,在这一层就会被划分到同一个新窗口中。这一步巧妙地实现了跨窗口的信息交互,让模型在保持低计算量的同时,依然能逐渐获得全局的感受野。 |
| | |

代码

参考ESC/esc/archs/esc_real_arch.py at main · dslisleedh/ESC · GitHub

通过1x1卷积把通道数triple,得到qkv:

复制代码
self.to_qkv = nn.Conv2d(dim, dim * 3, 1, 1, 0)

接下来就是使用feat_to_win函数,对feature分块:

复制代码
def feat_to_win(x: Tensor, window_size: Sequence[int], heads: int):
    return rearrange(
        x,
        "b (qkv heads c) (h wh) (w ww) -> qkv (b h w) heads (wh ww) c",
        heads=heads,
        wh=window_size[0],
        ww=window_size[1],
        qkv=3,
    )

qkv = feat_to_win(qkv, self.window_size, self.num_heads)
q, k, v = qkv[0], qkv[1], qkv[2]  # (B*nwin, heads, N, head_dim)

rearrange 是 einops 库中非常强大的张量重塑函数。它通过模式字符串直观地描述了张量维度的拆分与重组。

  • 输入模式 : "b (qkv heads c) (h wh) (w ww)",对应b,c,h,w
    • (qkv heads c)表示将通道维度拆分为 qkvheadsc
    • (h wh)表示将高度维度拆分为 hwh
    • (w ww)表示将宽度维度拆分为 www
  • 输出模式 : "qkv (b h w) heads (wh ww) c"
    • qkv: 放在最前面,方便后续分别处理 Q、K、V。
    • (b h w) : 将 Batch、高度窗口数、宽度窗口数合并为一个维度。这表示将所有的窗口"展平(Flatten)"了。表示多少个窗口(Batch * 窗口数)
    • heads: 多头注意力的头数。
    • (wh ww) : 将窗口的高度和宽度展平。这表示将每个子窗口的大小,子窗口内的二维像素"展平"成了一维的 Token 序列,因为自注意力机制是在一维序列上计算的。
    • c: 每个 Token 的特征维度。
    • 虽然输出变成了5维,但是qkv分别都是4维

很多视觉 Transformer / SR 模型里,除了分类不用关心平移,会使用绝对位置编码,其他Restoemer任务确实更常用相对位置编码/相对位置偏置,原因正是你说的:它是在"让 attention 知道空间关系"和"尽量保留平移等变性/局部一致性"之间做折中。

假设窗口大小是:

复制代码
Wh x Ww

窗口内任意两个 token 的相对位移范围是正负(W-1):

复制代码
Δh: -(Wh - 1) ~ +(Wh - 1)
Δw: -(Ww - 1) ~ +(Ww - 1)

所以一共有:

复制代码
(2*Wh - 1) * (2*Ww - 1)

每个 attention head 都有一张自己的 bias 表,所以形状是:

复制代码
[num_heads, num_relative_positions]=[num_heads,(2*Wh - 1) * (2*Ww - 1)]

会被初始化为随机数:

复制代码
self.relative_position_bias = nn.Parameter(
            torch.randn(
                num_heads, (2 * window_size[0] - 1) * (2 * window_size[1] - 1)
            ).to(torch.float32)
            * 0.001
        )

使用的时候直接查表:

复制代码
bias = self.relative_position_bias[self.rpe_idxs[:, 0], self.rpe_idxs[:, 1]]

得到的bias在reshape之后和qkv一起送入计算attention:

复制代码
bias = self.relative_position_bias[self.rpe_idxs[:, 0], self.rpe_idxs[:, 1]]
bias = bias.reshape(
                1, self.num_heads,
                self.window_size[0]*self.window_size[1],
                self.window_size[0]*self.window_size[1]
            )
out = self.attn_func(q, k, v, bias)

attention有几种:

复制代码
if attn_type == 'Naive':
        attn_func = attention
elif attn_type == 'SDPA' or attn_type == 'FlashBias':
        attn_func = F.scaled_dot_product_attention
elif attn_type == 'Flex':
        attn_func = torch.compile(flex_attention, dynamic=True)
else:
        raise NotImplementedError(f'Attention type {attn_type} is not supported.')

|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | |
| Naive | def attention(q: Tensor, k: Tensor, v: Tensor, bias: Tensor) -> Tensor: score = q @ k.transpose(-2, -1) / q.shape-1 ** 0.5 score = score + bias score = F.softmax(score, dim=-1) return score @ v |
| SDPA | F.scaled_dot_product_attention (通常简称为 SDPA )是 PyTorch 2.0 及更高版本中引入的一个官方核心 API不需要手动编写复杂的 CUDA 代码,只需调用这个函数,PyTorch 就会在后台自动评估当前硬件,并为你选择最快、最省显存的底层实现算法1。目前它支持三种主要的后端实现: Flash Attention:专为长序列设计的 IO 感知算法,大幅减少显存读写,速度极快且显存占用极低。 Memory-Efficient Attention (xformers):另一种内存高效算法,兼容性更广。 Math (C++ 实现):传统的 PyTorch C++ 数学实现,作为兜底方案。 |
| flex_attention | torch.compile(flex_attention, dynamic=True) 通过 torch.compile,FlexAttention 能够自动将用户定义的 Python 逻辑"降级"并融合为一个高度优化的 Triton 内核。它不仅支持自动反向传播,还能利用掩码中的稀疏性跳过无效计算,在保持极高灵活性的同时,达到了与手写底层内核相媲美的性能 |

然后再由window返回整图特征(也是使用rearrange实现):

复制代码
out = win_to_feat(out, self.window_size, h_div, w_div)
out = self.to_out(out.to(dtype)[:, :, :h, :w])    # self.to_out = nn.Conv2d(dim, dim, 1, 1, 0)
相关推荐
lkforce2 小时前
Transformer架构下的详细计算流程模拟,精确到数字(单层单头)
人工智能·深度学习·ai·transformer
爱吃程序猿的喵1 天前
LingBot-Map 复现与原理剖析:基于 Geometric Context Transformer 的流式 3D 重建
人工智能·python·深度学习·计算机视觉·3d·transformer
weixin_468466855 天前
从Transformer到ViT与Swin详解
人工智能·深度学习·transformer·computer vision·vit·卷积·swin
tyler_download5 天前
揉扁搓圆transformer架构:反向传播算法详解
深度学习·算法·transformer
Together_CZ7 天前
GKDT: General Keypoint Detection Transformer——通用关键点检测 Transformer
人工智能·transformer·通用关键点检测·gkdt·keypoint·detection
Yunzenn7 天前
强化学习1-Liu2026_GFlowRL_精读笔记
人工智能·笔记·深度学习·机器学习·transformer·集成学习·vllm
LDZKKJ8 天前
国产开源 MoE 三强横评:Qwen3 235B / Kimi K2 / DeepSeek V3.1 深度技术解读
语言模型·chatgpt·transformer
汤姆小白9 天前
08-应用部署
人工智能·python·机器学习·numpy·transformer
一只空白格9 天前
Transformer架构面试题目
人工智能·深度学习·transformer