一、前言
AlexNet(2012),是深度学习发展历程中的一个里程碑,开启了深度学习的狂潮。同时,也是如今学习深度学习绕不开的经典模型。
本文旨在通过还原AlexNet的网络结构,在学习模型的同时,强化自己的参数计算能力。
二、实现步骤
2.1环境配置
import torch.nn as nn
import torchvision.models as models
import torch
2.2与官方AlexNet结构对照
alexnet = models.alexnet()
# 打印官方AlexNet结构,用于对照自定义的
print(alexnet)
2.3自定义AlexNet类
# 自定义MyAlextNet类
# 定义自定义的 AlexNet 类,必须继承nn.Module
class MyAlexNet(nn.Module):
# 定义类的初始化方法
def __init__(self):
# 调用父类nn.Module的初始化方法,必须写,否则无法继承nn.Module的核心功能
super(MyAlexNet, self).__init__()
self.relu = nn.ReLU()
# 实例化 Dropout 层(丢弃概率 0.5)
# AlexNet 的全连接层后通常加 Dropout 防止过拟合)
# Dropout 会随机让 50% 的神经元输出置 0,避免模型过度依赖某部分神经元
self.drop = nn.Dropout(0.5)
# ❤定义第一个卷积层
# 批量大小,C,H,W
# ①(4, 3, 224, 224) ------------→ (4, 64, 55, 55)
# ①输入通道数(厚度)②输出通道数(卷积核数量) ③卷积核大小 ④步数 ⑤padding
self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=11, stride=4, padding=2)
# ❤定义一个最大池化层 MaxPool
# ②(4, 64, 55, 55) ------------→ (4, 64, 27, 27)
# kernel_size=3 : 池化核大小 3×3 stride=2:池化步长 2
self.pool1 = nn.MaxPool2d(3, stride=2)
# ❤定义第二个卷积层
# ③(4, 64, 27, 27) ------------→ (4, 192, 27, 27)
self.conv2 = nn.Conv2d(in_channels=64, out_channels=192, kernel_size=5, stride=1, padding=2)
# ❤定义第二个最大池化层
# ④(4, 192, 27, 27) ------------→ (4, 192, 13, 13)
self.pool2 = nn.MaxPool2d(3, stride=2)
# ❤定义第三个卷积层
# ⑤(4, 192, 13, 13) ------------→ (4, 384, 13, 13)
self.conv3 = nn.Conv2d(192, 384, 3, 1, 1)
# ❤定义第四个卷积层
# ⑤(4, 384, 13, 13) ------------→ (4, 256, 13, 13)
self.conv4 = nn.Conv2d(384, 256, 3, 1, 1)
# ❤定义第五个卷积层
# ⑤(4, 256, 13, 13) ------------→ (4, 256, 13, 13)
self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
# ❤定义第三个最大池化层
# ⑤(4, 256, 13, 13) ------------→ (4, 256, 6, 6)
self.pool3 = nn.MaxPool2d(3, stride=2)
# 定义自适应平均池化层,
# 核心参数output_size=6:无论输入特征图尺寸是多少,输出固定为 6×6
# ⑥ (4, 256, 6, 6) ------------→ (4, 256, 6, 6)
self.adapool = nn.AdaptiveAvgPool2d(output_size=6) # 行18
# 第一个全连接层
self.fc1 = nn.Linear(9216, 4096)
# 第二个全连接层
self.fc2 = nn.Linear(4096, 4096)
# 第三个全连接层
self.fc3 = nn.Linear(4096, 1000)
# 前项传播
def forward(self, x):
# 第一个卷积
# (4,3,224,224) → (4,64,55,55) → (4,64,27,27)
x = self.conv1(x)
x = self.relu(x)
x = self.pool1(x)
# 第二个卷积
# (4,64,27,27) → (4,192,27,27) → (4,192,13,13)
x = self.conv2(x)
x = self.relu(x)
x = self.pool2(x)
# 第三个卷积
# (4,192,13,13) → (4,384,13,13)
x = self.conv3(x)
x = self.relu(x)
print(x.size())
# 第四个卷积
# (4,384,13,13) → (4,256,13,13)
x = self.conv4(x)
x = self.relu(x)
print(x.size())
# 第五个卷积
# (4,256,13,13) → (4,256,6,6)
x = self.conv5(x)
x = self.relu(x)
x = self.pool3(x)
print(x.size())
# 自适应平均池化,本项目中形状无变化
x = self.adapool(x)
# 展平特征图,二维转为一维
# x.size()[0]:批量大小(这里是 4),保留第一维
# -1:自动计算剩余维度的总长度(256×6×6=9216)
x = x.view(x.size()[0], -1)
# 三个全连接+drop操作(减少参数量,防止过拟合)
# (4,9216) → (4,4096)
x = self.fc1(x)
x = self.relu(x)
x = self.drop(x)
# (4,4096) → (4,4096)
x = self.fc2(x)
x = self.relu(x)
x = self.drop(x)
# (4,4096) → (4,1000)
x = self.fc3(x)
x = self.relu(x)
return x
2.4模型参数统计
定义一个辅助函数,在写代码的过程中,可以用于验证所计算的参数,以conv1为例
# 统计模型总参数和可训练参数
def get_parameter_number(model):
# 总参数数
total_num = sum(p.numel() for p in model.parameters())
# 可训练参数数
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
# 初始化自定义模型
myalexnet = MyAlexNet()
# 统计conv1层参数(权重64×3×11×11=23232 + 偏置64 = 23296)
print("Conv1层参数统计:", get_parameter_number(myalexnet.conv1))
2.5输入,验证模型
# 模拟输入:(批量大小, 通道数, 高, 宽) = (4, 3, 224, 224)
img = torch.zeros((4, 3, 224, 224))
out = myalexnet(img)
# 打印输出维度(4, 1000),对应4个样本的1000类预测
print("模型输出维度:", out.size())
三、总结
本代码通过5个卷积层和3个全连接,配合relu激活函数、最大池化以及Dropout 来简单地模拟实现AlexNet的核心结构,用于理解卷积过程计算参数量。