Background overlay
3128 字
16 分钟
09-经典网络模型
2026-05-15
更新中...

一、 AlexNet#

import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(
# 这里使用一个11*11的更大窗口来捕捉对象。
# 同时,步幅为4,以减少输出的高度和宽度。
# 另外,输出通道的数目远大于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使用三个连续的卷积层和较小的卷积窗口。
# 除了最后的卷积层,输出通道的数量进一步增加。
# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
nn.Linear(6400, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10))

二、VGG#

1. vgg块#

601

import torch
from torch import nn
from d2l import torch as d2l
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)

2.vgg网络#

def vgg(conv_arch):
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# 全连接层部分
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
net = vgg(conv_arch)

三、GoogLeNet#

1. Inception块#

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Inception(nn.Module):
# c1--c4是每条路径的输出通道数
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
# 线路1,单1x1卷积层
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 线路2,1x1卷积层后接3x3卷积层
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 线路3,1x1卷积层后接5x5卷积层
self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 线路4,3x3最大汇聚层后接1x1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# 在通道维度上连结输出
return torch.cat((p1, p2, p3, p4), dim=1)

2. GoogLeNet 模型#

601

b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))

四、 ResNet#

1. 残差块#

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Residual(nn.Module): #@save
def __init__(self, input_channels, num_channels,
use_1x1conv=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels,
kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(num_channels, num_channels,
kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
Y += X
return F.relu(Y)

2.残差网络#

五、注意力机制#

还是不理解QKV对应没有注意力机制下的哪些输入?参数?卷积核?还是训练数据?还是指什么

如果经过的QKV完全一样那么和直接连接下一层有什么区别?如何选区Wq,Wk,Wv或者最后如何对数据应用这个变化以实现注意力的效果?

这个问题直击自注意力机制(Self-Attention)的灵魂。很多初学者都会困惑:既然 Q、K、V 都是同一个输入 XX 变来的,那折腾这一大圈,跟直接把 XX 喂给下一层(比如一个全连接层或卷积层)到底有什么本质区别?

我们分两个层面来彻底解开这个疑惑。

一、 如果经过的 QKV 都一样,和直接连接下一层有什么区别?#

假设我们正在处理一段自动驾驶场景中的数据,输入数据 XX 是一个序列,包含了主车、行人 A、车辆 B 在过去几秒的状态特征。

1. 直接连接下一层(例如全连接层 MLP)的做法:

如果直接把 XX 连到下一层,公式是 Y=XWY = XW

这就好比让主车、行人 A、车辆 B 各自拿着一个通用的公式,独立地去升级自己的特征。主车只看主车的数据,行人 A 只看行人 A 的数据。它们之间是没有信息交流的(除非你用很大感受野的卷积或循环网络硬把它们揉在一起)。

2. 注意力机制的做法(引入 Q、K、V):

虽然 Q、K、V 的源头都是 XX,但通过注意力计算,它打破了 Token 之间的孤岛

主车生成的 QQ 会去和行人 A 生成的 KK 计算点积。这就意味着,主车在更新自己的特征时,主动“看”了一眼行人 A,并根据行人 A 的重要程度(注意力权重),把行人 A 的部分特征(VV)融合到了自己身上。

3. 为什么不能干脆省掉权重,让 Q = K = V = X?

如果我们不乘任何权重矩阵,直接拿原始特征 XX 自己跟自己算内积(即 XXTX X^T),会发生什么?

此时,两个实体之间的“注意力打分”,完全取决于它们物理特征的相似度。比如,两辆同向行驶、速度相同的车,内积肯定最大。但实际上,一辆正在直行的主车,最需要关注的往往不是和它同向行驶的车,而是垂直方向突然冲出来的行人

如果不引入权重矩阵,模型就永远被困在“物理相似度”里。

二、 如何选取 Wq,Wk,WvW_q, W_k, W_v?如何对数据应用这个变化?#

这三个矩阵 Wq,Wk,WvW_q, W_k, W_v 不是由人类手动选取的,也不是预先设定好的规则。它们和神经网络里其他的参数一样,在模型一开始被随机初始化,然后在模型训练的过程中,通过计算损失函数(比如轨迹预测的偏差)并使用反向传播算法,自动学习和更新出来的。

模型通过大量的训练数据,自己摸索出了一套规律:把 XX 乘上特定的 WqW_qWkW_k,就能把特征映射到一个“意图匹配空间”。在这个空间里,“准备直行的车(Q)”和“横穿的行人(K)”的内积会变得极其巨大,从而触发警报(分配高注意力权重)。

为了让你清楚数据在代码层面是如何一步步发生变化的,这里是它最底层的数学执行流

第一步:特征映射(生成 Q, K, V)

输入数据 XX(假设形状是 [序列长度, 特征维度])分别并行地乘以三个不同的权重矩阵:

Q=XWqQ = XW_q

K=XWkK = XW_k

V=XWvV = XW_v

此时,原始数据 XX 就拥有了三种不同的“身份”。

第二步:计算注意力原始得分(打分)

拿所有的查询去和所有的键计算点积相似度:

Score=QKT\text{Score} = QK^T

这个算出来的矩阵,就是一个 N×NN \times N 的打分表,记录了场景中每一个实体对其他所有实体的关注度得分。

第三步:缩放与概率归一化(Softmax)

因为随着特征维度的增大,点积的结果会爆炸,导致梯度消失,所以需要除以维度的一个缩放因子 dk\sqrt{d_k}。然后通过 Softmax 将得分转化为加和为 1 的概率权重:

A=Softmax(QKTdk)A = \text{Softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)

这就是最终的注意力权重矩阵(Attention Map)。如果主车对行人 A 的权重是 0.9,对车辆 B 的权重是 0.1,就说明此时主车 90% 的精力都在防备行人。

第四步:加权信息提取(输出)

最后,拿这个权重矩阵去乘以对应的值 VV

Y=AVY = AV

得到的 YY 就是经过注意力机制洗礼后的全新特征。此时,YY 中的主车特征,已经不再仅仅是它自己的历史轨迹了,而是“包含了行人 A 潜在危险信息的综合特征”。这个 YY 就会被直接送入下一层(比如前馈神经网络)进行最终的预测。

目前常用的注意力机制有哪些?有哪些异同?

目前常用的注意力机制主要分为三大流派:Transformer 家族(主宰大模型与多模态)CNN 家族(主宰传统视觉特征提取),以及为了解决极长序列算力瓶颈而诞生的高效底层变体

我们可以把注意力机制想象成不同风格的“找重点策略”。以下是主流机制的通俗拆解与异同对比:

1. Transformer 家族:基于 QKV 的序列玩家#

这是目前大语言模型(如 ChatGPT)和前沿视觉模型(如 Sora)的最底层基石。

  • 自注意力机制 (Self-Attention)

    • 通俗表达: 内部消化。句子里的每一个词,都去观察同一个句子里的其他词,看看谁和自己关系最紧密。

    • 技术差异: Q、K、V 全部来自同一个输入序列。它打破了传统 RNN 只能顺序计算的限制,实现了全局视野和并行计算。

  • 交叉注意力 (Cross-Attention)

    • 通俗表达: 跨界寻找。拿着问题(Query)去另一本书(Key 和 Value 的来源)里找答案。

    • 技术差异: Q 来自输入 A(例如文本提示),而 K 和 V 来自输入 B(例如图像特征)。这是多模态模型能够将不同模态“对齐”并产生互动的核心组件。

  • 多头注意力 (Multi-Head Attention)

    • 通俗表达: 多个评审团从不同角度打分。第一组评审专门看语法,第二组看情感,第三组看逻辑。

    • 技术差异: 将原本的 Q、K、V 拆分成多个“头”,让它们并行执行独立的注意力计算,最后再拼接起来。这极大地提升了模型捕捉多维度复杂特征的能力。

2. CNN 家族:基于特征图的视觉玩家#

这类机制通常不使用繁琐的 QKV 点积,而是通过对卷积出来的空间或通道特征图进行权重缩放来起作用。

  • 通道注意力 (Channel Attention, 如 SE-Net)

    • 通俗表达: 解决“看什么”的问题。在成百上千个特征通道里,给“边缘特征”或“颜色特征”所在的通道打分。

    • 技术差异: 通过全局池化和全连接层,学习出一个针对通道的一维权重向量,直接乘回原本的特征图上。

  • 空间注意力 (Spatial Attention)

    • 通俗表达: 解决“看哪里”的问题。在一张图里,把目标物体的区域点亮,把无用的背景变暗。

    • 技术差异: 在通道维度上进行池化压缩,学习出一个二维的空间权重掩码(Mask)。

  • 混合注意力 (如 CBAM)

    • 通俗表达: 既挑重点特征,又看关键区域。综合了上述两者的优势,是许多经典目标检测算法的轻量级增强标配。

3. 高效注意力:算力与显存的优化者#

标准自注意力机制最大的痛点是 O(N2)O(N^2) 的计算和内存复杂度:当输入序列的长度 NN 翻倍时,计算量会变成 4 倍。为了让模型能处理几十万字的长文本,诞生了以下变体:

  • 稀疏注意力 (Sparse Attention, 如 Longformer / BigBird)

    • 通俗表达: 不再要求每个人都互相认识,只和自己周围的邻居,或者几个固定的“村长”(全局点)交流。

    • 技术差异: 强行把稠密的注意力计算矩阵变稀疏,忽略掉大部分远距离的无效点积。这属于算法层面的近似优化

  • FlashAttention (现代大模型标配)

    • 通俗表达: 不改变最终的答案,但彻底优化了显卡内部的“搬砖路线”,让数据跑得极快。

    • 技术差异: 它是一种硬件感知 (Hardware-aware) 的底层优化。通过分块计算 (Tiling) 和重计算技巧,大幅减少了 GPU SRAM 与高带宽内存 (HBM) 之间缓慢的数据读写操作。它的计算结果与标准注意力完全等价(没有精度损失),但速度飞升且极度节省显存。

09-经典网络模型
https://icemeow.top/blog/posts/graduate/pytorch-8/
作者
ICEMeow
发布于
2026-05-15
许可协议
CC BY-NC-SA 4.0