PyTorch初涉:AlexNet网络剖析

2周前发布 gsjqwyl
11 0 0

初探PyTorch:AlexNet网络深度解析

AlexNet背景

AlexNet是于2012年由Alex Krizhevsky、Ilya Sutskever以及Geoffrey Hinton共同提出的深度卷积神经网络架构。它在ImageNet大规模视觉识别挑战赛(ILSVRC)中取得了突破性成果,将top – 5错误率从原本的26%大幅降低至15.3%,这一成就标志着深度学习在计算机视觉领域开启了全新的发展阶段。

AlexNet的成功主要归功于以下几个创新要点:
– 采用ReLU(修正线性单元)作为激活函数,有效解决了传统Sigmoid/Tanh激活函数在深层网络中出现的梯度消失问题。
– 运用Dropout技术来减少全连接层的过拟合情况。
– 使用重叠的最大池化来替代传统的平均池化,从而增强了特征的不变性。
– 首次在卷积神经网络中成功应用GPU加速训练,使得大规模深层网络的训练成为可能。

AlexNet的诞生开启了深度学习在计算机视觉领域的新纪元,为后续众多卷积神经网络架构(如VGG、ResNet等)的发展奠定了坚实基础。

AlexNet架构

AlexNet原始架构包含8个可学习的层,其中有5个卷积层和3个全连接层。具体架构详情如下:
1. 输入层:接收224×224×3的RGB图像(在FashionMNIST数据集中会调整为227×227×1的灰度图像)。
2. 卷积层1:使用96个11×11的卷积核,步长为4,搭配ReLU激活函数。
3. 最大池化层1:采用3×3的池化窗口,步长设为2。
4. 卷积层2:包含256个5×5的卷积核,填充值为2,使用ReLU激活函数。
5. 最大池化层2:同样是3×3的池化窗口,步长为2。
6. 卷积层3:有384个3×3的卷积核,填充值为1,搭配ReLU激活函数。
7. 卷积层4:也是384个3×3的卷积核,填充值为1,使用ReLU激活函数。
8. 卷积层5:为256个3×3的卷积核,填充值为1,搭配ReLU激活函数。
9. 最大池化层3:是3×3的池化窗口,步长为2。
10. 全连接层1:有4096个神经元,使用ReLU激活函数,且Dropout概率设为0.5。
11. 全连接层2:有4096个神经元,使用ReLU激活函数,Dropout概率为0.5。
12. 全连接层3(输出层):有1000个神经元(在FashionMNIST数据集中调整为10个)。

参数计算详解

下面详细计算AlexNet各层的参数数量:
1. 卷积层1
– 输入:227×227×1
– 96个11×11的卷积核
– 参数数量 = (11×11×1 + 1偏置)×96 = 11712
2. 卷积层2
– 输入:经过池化后尺寸为27×27×96
– 256个5×5的卷积核
– 参数数量 = (5×5×96 + 1)×256 = 614656
3. 卷积层3
– 输入:13×13×256
– 384个3×3的卷积核
– 参数数量 = (3×3×256 + 1)×384 = 885120
4. 卷积层4
– 输入:13×13×384
– 384个3×3的卷积核
– 参数数量 = (3×3×384 + 1)×384 = 1327488
5. 卷积层5
– 输入:13×13×384
– 256个3×3的卷积核
– 参数数量 = (3×3×384 + 1)×256 = 884992
6. 全连接层1
– 输入:6×6×256 = 9216
– 输出:4096
– 参数数量 = (9216 + 1)×4096 = 37752832
7. 全连接层2
– 输入:4096
– 输出:4096
– 参数数量 = (4096 + 1)×4096 = 16781312
8. 全连接层3
– 输入:4096
– 输出:10(FashionMNIST)
– 参数数量 = (4096 + 1)×10 = 40970

原始AlexNet的总参数数量约为6000万,在FashionMNIST数据集中约为5800万。

代码实现解析

模型实现代码(model.py)
import os
import sys

sys.path.append(os.getcwd())

import torch  # 导入PyTorch主库
from torch import nn  # 从torch中导入神经网络模块
from torchsummary import summary  # 导入torchsummary用于模型结构总结
import torch.nn.functional as F  # 导入PyTorch的函数式API,常用于激活函数、dropout等


class AlexNet(nn.Module):  # 定义AlexNet模型,继承自nn.Module
    def __init__(self):  # 构造函数,初始化网络结构
        super(AlexNet, self).__init__()  # 调用父类的构造函数
        self.ReLU = nn.ReLU()  # 定义ReLU激活函数,后续多次复用
        self.conv1 = nn.Conv2d(
            in_channels=1, out_channels=96, stride=4, kernel_size=11
        )  # 第一层卷积,输入通道1,输出通道96,步幅4,卷积核11x11
        self.pool1 = nn.MaxPool2d(kernel_size=3, stride=2)  # 第一层池化,3x3窗口,步幅2

        self.conv2 = nn.Conv2d(
            in_channels=96, out_channels=256, stride=1, kernel_size=5, padding=2
        )  # 第二层卷积,输入96通道,输出256通道,5x5卷积核,padding=2
        self.pool2 = nn.MaxPool2d(kernel_size=3, stride=2)  # 第二层池化,3x3窗口,步幅2

        self.conv3 = nn.Conv2d(
            in_channels=256, out_channels=384, stride=1, kernel_size=3, padding=1
        )  # 第三层卷积,输入256通道,输出384通道,3x3卷积核,padding=1
        self.conv4 = nn.Conv2d(
            in_channels=384, out_channels=384, stride=1, kernel_size=3, padding=1
        )  # 第四层卷积,输入384通道,输出384通道,3x3卷积核,padding=1
        self.conv5 = nn.Conv2d(
            in_channels=384, out_channels=256, stride=1, kernel_size=3, padding=1
        )  # 第五层卷积,输入384通道,输出256通道,3x3卷积核,padding=1

        self.pool3 = nn.MaxPool2d(kernel_size=3, stride=2)  # 第三层池化,3x3窗口,步幅2
        self.flatten = nn.Flatten()  # 展平层,将多维输入展平成一维

        self.fc1 = nn.Linear(
            in_features=256 * 6 * 6, out_features=4096
        )  # 第一个全连接层,输入256 * 6 * 6,输出4096
        self.fc2 = nn.Linear(
            in_features=4096, out_features=4096
        )  # 第二个全连接层,输入4096,输出4096
        self.fc3 = nn.Linear(
            in_features=4096, out_features=10
        )  # 第三个全连接层,输入4096,输出10(假设10分类)

    def forward(self, x):  # 定义前向传播过程
        x = self.conv1(x)  # 输入通过第一层卷积
        x = self.ReLU(x)  # 激活
        x = self.pool1(x)  # 池化

        x = self.conv2(x)  # 第二层卷积
        x = self.ReLU(x)  # 激活
        x = self.pool2(x)  # 池化

        x = self.conv3(x)  # 第三层卷积
        x = self.ReLU(x)  # 激活
        x = self.conv4(x)  # 第四层卷积
        x = self.ReLU(x)  # 激活
        x = self.conv5(x)  # 第五层卷积
        x = self.ReLU(x)  # 激活
        x = self.pool3(x)  # 池化

        x = self.flatten(x)  # 展平为一维向量

        x = self.fc1(x)  # 第一个全连接层
        x = self.ReLU(x)  # 激活
        x = F.dropout(x, p=0.5)  # dropout防止过拟合,丢弃概率0.5

        x = self.fc2(x)  # 第二个全连接层
        x = self.ReLU(x)  # 激活
        x = F.dropout(x, p=0.5)  # dropout防止过拟合,丢弃概率0.5

        x = self.fc3(x)  # 第三个全连接层,输出最终结果
        return x  # 返回输出


if __name__ == "__main__":  # 如果作为主程序运行
    model = AlexNet()  # 实例化AlexNet模型
    print(model)  # 打印模型结构
    summary(
        model, input_size=(1, 227, 227), device="cpu"
    )  # 打印模型摘要,输入尺寸为(1, 227, 227),单通道
训练代码(train.py)

“`python
import os # 导入os模块,用于与操作系统交互
import sys # 导入sys模块,用于操作Python运行时环境

sys.path.append(os.getcwd()) # 将当前工作目录添加到sys.path,方便模块导入

import time # 导入time模块,用于计时
from torchvision.datasets import FashionMNIST # 导入FashionMNIST数据集
from torchvision import transforms # 导入transforms用于数据预处理
from torch.utils.data import (
DataLoader, # 导入DataLoader用于批量加载数据
random_split, # 导入random_split用于划分数据集
)
import numpy as np # 导入numpy用于数值计算
import matplotlib.pyplot as plt # 导入matplotlib用于绘图
import torch # 导入PyTorch主库
from torch import nn, optim # 导入神经网络模块和优化器
import copy # 导入copy模块用于深拷贝
import pandas as pd # 导入pandas用于数据处理

from AlexNet_model.model import AlexNet # 从自定义模块导入AlexNet模型

def train_val_date_load(): # 定义函数用于加载训练集和验证集
train_dataset = FashionMNIST(
root=”./data”, # 数据存储路径
train=True, # 加载训练集
download=True, # 如果数据不存在则下载
transform=transforms.Compose(
[
transforms.Resize(size=227), # 将图片缩放到227×227
transforms.ToTensor(), # 转换为Tensor
]
),
)

train_date, val_data = random_split(
    train_dataset,
    [
        int(len(train_dataset) * 0.8),  # 80%作为训练集
        len(train_dataset) - int(len(train_dataset) * 0.8),  # 剩余20%作为验证集
    ],
)

train_loader = DataLoader(
    dataset=train_date,
    batch_size=32,
    shuffle=True,
    num_workers=1,  # 训练集加载器,批量32,打乱顺序
)

val_loader = DataLoader(
    dataset=val_data,
    batch_size=32,
    shuffle=True,
    num_workers=1,  # 验证集加载器,批量32,打乱顺序
)

return train_loader, val_loader  # 返回训练集和验证集加载器

def train_model_process(model, train_loader, val_loader, epochs=10): # 定义训练过程函数
device = “cuda” if torch.cuda.is_available() else “cpu” # 判断是否有GPU可用
optimizer = optim.Adam(model.parameters(), lr=0.001) # 使用Adam优化器,学习率0.001
criterion = nn.CrossEntropyLoss() # 定义交叉熵损失函数
model.to(device) # 将模型移动到指定设备

best_model_wts = copy.deepcopy(model.state_dict())  # 保存最佳模型参数
best_acc = 0.0  # 初始化最佳准确率
train_loss_all = []  # 记录每轮训练损失
val_loss_all = []  # 记录每轮验证损失
train_acc_all = []  # 记录每轮训练准确率
val_acc_all = []  # 记录每轮验证准确率

since = time.time()  # 记录训练开始时间

for epoch in range(epochs):  # 遍历每个训练轮次
    print(f"Epoch {epoch + 1}/{epochs}")  # 打印当前轮次信息

    train_loss = 0.0  # 当前轮训练损失
    train_correct = 0  # 当前轮训练正确样本数

    val_loss = 0.0  # 当前轮验证损失
    val_correct = 0  # 当前轮验证正确样本数

    train_num = 0  # 当前轮训练样本总数
    val_num = 0  # 当前轮验证样本总数

    for step, (images, labels) in enumerate(train_loader):  # 遍历训练集
        images = images.to(device)  # 将图片移动到设备
        labels = labels.to(device)  # 将标签移动到设备

        model.train()  # 设置模型为训练模式

        outputs = model(images)  # 前向传播,获取输出

        pre_lab = torch.argmax(outputs, dim=1)  # 获取预测标签

        loss = criterion(outputs, labels)  # 计算损失

        optimizer.zero_grad()  # 梯度清零
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

        train_loss += loss.item() * images.size(0)  # 累加损失
        train_correct += torch.sum(pre_lab == labels.data)  # 累加正确预测数
        train_num += labels.size(0)  # 累加样本数

    for step, (images, labels) in enumerate(val_loader):  # 遍历验证集
        images = images.to(device)  # 将图片移动到设备
        labels = labels.to(device)  # 将标签移动到设备
        model.eval()  # 设置模型为评估模式

        with torch.no_grad():  # 关闭梯度计算
            outputs = model(images)  # 前向传播
            pre_lab = torch.argmax(outputs, dim=1)  # 获取预测标签
            loss = criterion(outputs, labels)  # 计算损失

            val_loss += loss.item() * images.size(0)  # 累加损失
            val_correct += torch.sum(pre_lab == labels.data)  # 累加正确预测数
            val_num += labels.size(0)  # 累加样本数

    train_loss_all.append(train_loss / train_num)  # 记录本轮平均训练损失
    val_loss_all.append(val_loss / val_num)  # 记录本轮平均验证损失
    train_acc = train_correct.double() / train_num  # 计算本轮训练准确率
    val_acc = val_correct.double() / val_num  # 计算本轮验证准确率
    train_acc_all.append(train_acc.item())  # 记录训练准确率
    val_acc_all.append(val_acc.item())  # 记录验证准确率
    print(
        f"Train Loss: {train_loss / train_num:.4f}, Train Acc: {train_acc:.4f}, "
        f"Val Loss: {val_loss / val_num:.4f}, Val Acc: {val_acc:.4f}"
    )  # 打印本轮损失和准确率
    if val_acc_all[-1] > best_acc:  # 如果本轮验证准确率更高
        best_acc = val_acc_all[-1]  # 更新最佳准确率
        best_model_wts = copy
© 版权声明

相关文章

暂无评论

暂无评论...