网络中的网络 (NiN)
探索Network In Network架构的创新设计,理解1×1卷积和全局平均池化的重要作用

展开文章目录
1. NiN 网络架构创新
/1735274228527-7677d622-95fd-4551-8625-0d81128792a6.png)
1.1 MlpConv 层的设计
NiN创新点
MlpConv 层替代传统卷积层
默认的卷积层只做加权求和,即使接入了非线性的激活函数,卷积层本身依然是线性的。NiN 用改进后的 MlpConv 层替代传统卷积层,为局部感受野每个高度和宽度位置(像素位置)对应的通道应用多层感知机(或多个使用了激活函数的 1×1 卷积层),提升特征捕捉能力。
/1735276390504-8e652f26-ab91-4cd1-bb0b-acdfd972c560.png)
全局平均池化 (GAP) 替代全连接层
在 NiN 的分类阶段,用全局平均池化层 (Global Average Pooling, GAP) 替代全连接层,使各个像素位置的值在通道范围内被均值化为标量,消除深度(通道)信息,保留通道的全局响应。这样,既减少了参数量、降低了过拟合的风险,又提升了模型的可解释性(但引入的非线性仍然增加了计算复杂度)。
/1735284796834-b24da2d2-331a-4ea1-bb24-876a31b04112.png)
2. NiN 块的实现
2.1 NiN 块的构成
2.2 PyTorch 实现
NiN 块NiNBlock的 PyTorch 实现如下:
from torch import Tensor, nn
class NiNBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, strides, padding):
super().__init__()
self.block = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()
)
def forward(self, x) -> Tensor:
return self.block(x)
3. NiN 网络架构设计
3.1 整体网络设计
3.2 完整网络实现
复用 NiN 块,NiN 网络的 PyTorch 实现如下(继续使用 Fashion-MNIST 数据集):
from torch import Tensor, nn
class NiNBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, strides, padding):
super().__init__()
self.block = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()
)
def forward(self, x) -> Tensor:
return self.block(x)
class NiN(nn.Module):
def __init__(self, num_classes: int):
super(NiN, self).__init__()
self.extractor = nn.Sequential(
NiNBlock(in_channels=1, out_channels=96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
NiNBlock(in_channels=96, out_channels=256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
NiNBlock(in_channels=256, out_channels=384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5)
)
self.classifier = nn.Sequential(
NiNBlock(in_channels=384, out_channels=num_classes, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d(output_size=1), # 等价为:nn.AvgPool2d(kernel_size=5, stride=5, padding=0)
nn.Flatten()
)
self._initialize_weights()
def _initialize_weights(self):
"""
默认使用的 Kaiming 均匀分布初始化,似乎难以训练模型
使用 Kaiming 正态分布初始化
"""
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, nonlinearity='relu')
def forward(self, x: Tensor) -> Tensor:
x = self.extractor(x)
x = self.classifier(x)
return x
3.3 网络结构分析
使用torchinfo库的summary函数执行更便利的输出维度测试:
from torchinfo import summary
model = NiN(num_classes=10)
summary(model, input_size=(1, 1, 224, 224))
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
NiN [1, 10] --
├─Sequential: 1-1 [1, 384, 5, 5] --
│ └─NiNBlock: 2-1 [1, 96, 54, 54] --
│ │ └─Sequential: 3-1 [1, 96, 54, 54] 30,336
│ └─MaxPool2d: 2-2 [1, 96, 26, 26] --
│ └─NiNBlock: 2-3 [1, 256, 26, 26] --
│ │ └─Sequential: 3-2 [1, 256, 26, 26] 746,240
│ └─MaxPool2d: 2-4 [1, 256, 12, 12] --
│ └─NiNBlock: 2-5 [1, 384, 12, 12] --
│ │ └─Sequential: 3-3 [1, 384, 12, 12] 1,180,800
│ └─MaxPool2d: 2-6 [1, 384, 5, 5] --
│ └─Dropout: 2-7 [1, 384, 5, 5] --
├─Sequential: 1-2 [1, 10] --
│ └─NiNBlock: 2-8 [1, 10, 5, 5] --
│ │ └─Sequential: 3-4 [1, 10, 5, 5] 34,790
│ └─AdaptiveAvgPool2d: 2-9 [1, 10, 1, 1] --
│ └─Flatten: 2-10 [1, 10] --
==========================================================================================
Total params: 1,992,166
Trainable params: 1,992,166
Non-trainable params: 0
Total mult-adds (Units.MEGABYTES): 763.82
==========================================================================================
Input size (MB): 0.20
Forward/backward pass size (MB): 12.20
Params size (MB): 7.97
Estimated Total Size (MB): 20.37
==========================================================================================
4. 模型训练与评估
4.1 训练配置与实现
继续使用training_tools.py中的工具训练评估模型:
import torch
from torch import Tensor, nn, optim
from training_tools import fashionMNIST_loader, Trainer
class NiNBlock(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, strides, padding):
super().__init__()
self.block = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()
)
def forward(self, x) -> Tensor:
return self.block(x)
class NiN(nn.Module):
def __init__(self, num_classes: int):
super(NiN, self).__init__()
self.extractor = nn.Sequential(
NiNBlock(in_channels=1, out_channels=96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
NiNBlock(in_channels=96, out_channels=256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
NiNBlock(in_channels=256, out_channels=384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5)
)
self.classifier = nn.Sequential(
NiNBlock(in_channels=384, out_channels=num_classes, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d(output_size=1), # 等价为:nn.AvgPool2d(kernel_size=5, stride=5, padding=0)
nn.Flatten()
)
self._initialize_weights()
def _initialize_weights(self):
"""
默认使用的 Kaiming 均匀分布初始化,似乎难以训练模型
使用 Kaiming 正态分布初始化
"""
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, nonlinearity='relu')
def forward(self, x: Tensor) -> Tensor:
x = self.extractor(x)
x = self.classifier(x)
return x
if __name__ == '__main__':
BATCH_SIZE = 128
EPOCHS_NUM = 30
LEARNING_RATE = 0.01
model = NiN(num_classes=10)
train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=224)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
trainer.train(EPOCHS_NUM)
4.2 训练结果与分析
查看完整训练过程
第 001/30 轮,训练损失:2.0495,训练精度:28.43%,测试损失:1.5681,测试精度:51.51%
第 002/30 轮,训练损失:0.9821,训练精度:66.64%,测试损失:0.6777,测试精度:75.06%
第 003/30 轮,训练损失:0.6354,训练精度:77.04%,测试损失:0.5442,测试精度:79.88%
第 004/30 轮,训练损失:0.5416,训练精度:80.46%,测试损失:0.5270,测试精度:80.00%
第 005/30 轮,训练损失:0.4881,训练精度:82.34%,测试损失:0.4588,测试精度:83.32%
第 006/30 轮,训练损失:0.4484,训练精度:83.86%,测试损失:0.4341,测试精度:84.49%
第 007/30 轮,训练损失:0.4199,训练精度:84.65%,测试损失:0.4069,测试精度:85.37%
第 008/30 轮,训练损失:0.3984,训练精度:85.55%,测试损失:0.4134,测试精度:85.44%
第 009/30 轮,训练损失:0.3778,训练精度:86.39%,测试损失:0.3887,测试精度:85.82%
第 010/30 轮,训练损失:0.3632,训练精度:86.78%,测试损失:0.4775,测试精度:83.04%
第 011/30 轮,训练损失:0.3501,训练精度:87.31%,测试损失:0.3427,测试精度:87.49%
第 012/30 轮,训练损失:0.3375,训练精度:87.63%,测试损失:0.4035,测试精度:85.31%
第 013/30 轮,训练损失:0.3277,训练精度:88.02%,测试损失:0.3529,测试精度:87.10%
第 014/30 轮,训练损失:0.3199,训练精度:88.21%,测试损失:0.3193,测试精度:88.24%
第 015/30 轮,训练损失:0.3096,训练精度:88.72%,测试损失:0.3444,测试精度:87.37%
第 016/30 轮,训练损失:0.3040,训练精度:88.95%,测试损失:0.3439,测试精度:87.36%
第 017/30 轮,训练损失:0.2953,训练精度:89.18%,测试损失:0.3265,测试精度:88.04%
第 018/30 轮,训练损失:0.2890,训练精度:89.62%,测试损失:0.3234,测试精度:88.43%
第 019/30 轮,训练损失:0.2835,训练精度:89.58%,测试损失:0.3689,测试精度:87.20%
第 020/30 轮,训练损失:0.2752,训练精度:89.96%,测试损失:0.3417,测试精度:87.38%
第 021/30 轮,训练损失:0.2711,训练精度:90.17%,测试损失:0.2927,测试精度:89.33%
第 022/30 轮,训练损失:0.2652,训练精度:90.30%,测试损失:0.3334,测试精度:87.89%
第 023/30 轮,训练损失:0.2601,训练精度:90.47%,测试损失:0.2848,测试精度:89.71%
第 024/30 轮,训练损失:0.2559,训练精度:90.63%,测试损失:0.2947,测试精度:89.62%
第 025/30 轮,训练损失:0.2492,训练精度:90.91%,测试损失:0.2871,测试精度:89.46%
第 026/30 轮,训练损失:0.2470,训练精度:91.07%,测试损失:0.3058,测试精度:89.04%
第 027/30 轮,训练损失:0.2440,训练精度:91.13%,测试损失:0.2831,测试精度:89.56%
第 028/30 轮,训练损失:0.2384,训练精度:91.28%,测试损失:0.2850,测试精度:89.24%
第 029/30 轮,训练损失:0.2354,训练精度:91.32%,测试损失:0.2835,测试精度:89.74%
第 030/30 轮,训练损失:0.2309,训练精度:91.53%,测试损失:0.2628,测试精度:90.59%/1735303510438-704a3523-c62e-4011-8326-8aaee0e79de3.png)
参考资料
如果这篇记录对你有帮助,可以留下一句回应。

DISCUSSION
讨论与回应
欢迎补充细节、指出问题,或分享与这篇文章有关的经验。
昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。