常用网络层与容器
本教程共 60 篇 · 第 10 篇 · 更新于 2026-08-17 · 约 3 分钟阅读
本节目标:认识
Linear、Conv2d、池化、Dropout等常用层,学会用Sequential和ModuleList快速拼网络。
全连接层:nn.Linear
nn.Linear(in_features, out_features) 只做一件事:对输入做线性变换,y = xWᵀ + b。输入几维、输出几维,构造时定死。
import torch
import torch.nn as nn
fc = nn.Linear(4, 3)
x = torch.randn(2, 4) # 2 个样本,每个 4 维
print(fc(x).shape) # torch.Size([2, 3])
注意输入只要求最后一维等于 4,前面有几维都行。形状 (2, 5, 4) 的张量照样能过,输出 (2, 5, 3)。它天生兼容批处理,这就是「全连接」的含义:每个输出节点和所有输入节点都相连。
输入维数写错是新手第一大类报错。好消息是报错信息会直接告诉你期望的形状,比如 mat1 and mat2 shapes cannot be multiplied,对着改就行。
卷积层:nn.Conv2d
图像任务几乎都从 nn.Conv2d 开始。最常用的四个参数:
in_channels:输入通道数。彩色图是 3,灰度图是 1。out_channels:输出通道数,也就是这层学多少个卷积核(kernel)。kernel_size:卷积核大小,写 3 表示 3×3。stride:核每次滑动几步,默认 1。想快速缩小特征图就设 2。
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3)
img = torch.randn(8, 3, 32, 32) # 8 张 32×32 彩色图
print(conv(img).shape) # torch.Size([8, 16, 30, 30])
输出变成了 30×30。卷积核扫过图像时会「吃掉」边缘,3×3 的核每边各少 1。想保持尺寸不变,加 padding=1 在外圈补零:
conv = nn.Conv2d(3, 16, 3, padding=1)
print(conv(torch.randn(8, 3, 32, 32)).shape) # torch.Size([8, 16, 32, 32])
卷积到底怎么提取特征,第 20 章细讲。这里先记住「怎么用」:通道数、核大小、padding,三个参数来回调,输出尺寸变化用打印验证。
池化层与 Dropout
nn.MaxPool2d(2) 在每个 2×2 小区域里取最大值,宽高各减半。它顺手做了两件事:缩小数据量,让网络对位置变化不那么敏感。
nn.Dropout(p) 训练时以概率 p 随机把神经元的输出清零,逼网络别过度依赖某个神经元,是防过拟合的常用手段。概率一般取 0.2 到 0.5,太大网络学不动,太小没效果。
pool = nn.MaxPool2d(2)
x = torch.randn(4, 16, 32, 32)
print(pool(x).shape) # torch.Size([4, 16, 16, 16])
Note
Dropout只在训练时随机清零,评估时自动失效。切换状态靠model.train()和model.eval(),第 16 章训练循环会细说。
还有 nn.Flatten(),把多维特征图压平成二维,是卷积层和全连接层之间的「转换头」。它等价于 x.view(x.size(0), -1),但写进模型结构里更清晰。
池化家族还有一位重要成员:nn.AdaptiveAvgPool2d((1, 1)),自适应平均池化。它不关心输入多大,保证输出你指定的尺寸。用它可以把任意大小的特征图压成 1×1,后面接全连接层时再也不用手算 32 * 7 * 7 这种数。CNN 里它常用来替代「展平 + 手算尺寸」的组合。
输出层常用激活
隐藏层里的 nn.ReLU 你见过了。输出层要按任务挑激活:
- 二分类:
nn.Sigmoid(),输出压到 0 到 1,当概率看。 - 多分类:
nn.Softmax(dim=1),整行输出变成概率分布,加起来等于 1。 - 回归:不加激活,直接输出数值。
用 nn.CrossEntropyLoss 时注意一个坑:这个损失函数内部已经包含 Softmax 了,模型输出层别再加 nn.Softmax,加了反而多算一遍,效果打折。这是新手很常见的「画蛇添足」错误。
容器:Sequential 与 ModuleList
网络层一多,逐行写 forward 就啰嗦。nn.Sequential 把层按顺序装起来,数据自动一层层穿过:
model = nn.Sequential(
nn.Linear(10, 32),
nn.ReLU(),
nn.Linear(32, 2),
)
print(model(torch.randn(5, 10)).shape) # torch.Size([5, 2])
注意 Sequential 里的顺序就是执行顺序。它适合「一条直线」的网络:输入进第一层,输出给第二层,不绕弯。需要跳转结构(比如残差连接)时,就得回到继承 nn.Module 自己写 forward。Sequential 只是省键盘,不是万能。
nn.ModuleList 是另一种容器:像 Python 列表一样装子模块,但它不会自动依次执行,得在 forward 里自己调用:
class Net(nn.Module):
def __init__(self):
super().__init__()
self.layers = nn.ModuleList([nn.Linear(10, 10) for _ in range(3)])
def forward(self, x):
for layer in self.layers:
x = torch.relu(layer(x))
return x
ModuleList 适合层数不固定、要用循环或条件语句控制的场景。它的兄弟 nn.ModuleDict 用字典存子模块,适合按名字取用。
Warning别用普通 Python 列表
self.layers = [nn.Linear(...)]装层!普通列表里的层不会被底座登记,参数会「消失」——loss 不降、parameters()里找不到。这就是ModuleList存在的理由。
拼一个 CNN
把上面这些层拼起来,就是一个经典的小卷积网络:
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 16, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2), # 28×28 → 14×14
nn.Conv2d(16, 32, 3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2), # 14×14 → 7×7
)
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(32 * 7 * 7, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, 10),
)
def forward(self, x):
x = self.features(x)
x = self.classifier(x)
return x
model = SimpleCNN()
print(model(torch.randn(2, 1, 28, 28)).shape) # torch.Size([2, 10])
结构分两段:features 管特征提取,classifier 管分类。中间那个 32 * 7 * 7 是两次池化后的特征图大小,这个数算错就会报形状不匹配。第 33 章讲调试时,这会是常见案例。
顺便说一句:torchvision.models 里那些预训练模型(resnet18、mobilenet 之类),本质上都是 nn.Module 的子类。你现在会看它们的结构了,以后用起来就不会陌生。
小结
nn.Linear全连接、nn.Conv2d卷积、nn.MaxPool2d池化、nn.Dropout防过拟合,nn.Flatten负责压平,nn.AdaptiveAvgPool2d输出尺寸免手算。- 输出层激活按任务选:二分类 Sigmoid,多分类 Softmax,回归不加。
nn.Sequential适合直线网络;要跳转就用继承 + 自己写 forward。- 层放进普通列表会丢参数,务必用
nn.ModuleList。
下一章进入数据环节:模型要吃数据,数据从哪来、怎么喂,交给 Dataset 和 DataLoader。