深度卷积神经网络 (AlexNet)

探索深度学习史上的里程碑网络AlexNet,理解其创新的网络架构设计和关键技术突破

发布于 2025-08-09 更新于 2025-08-091,852 字 5 分钟
深度卷积神经网络 (AlexNet)的文章封面
展开文章目录
  1. 1. AlexNet 的历史意义
  2. 1.1 现代CNN网络发展概览
  3. 2. AlexNet 网络架构
  4. 2.1 架构特点对比
  5. 2.2 PyTorch 实现
  6. 2.3 网络结构分析
  7. 3. 模型训练与评估
  8. 3.1 训练工具集成
  9. 3.2 训练示例
  10. 3.3 训练结果分析
  11. 参考资料

1. AlexNet 的历史意义

深度学习领域的观点认为,深度学习模型应该能自动学习到数据的特征,而不依赖于传统机器学习中的手工特征提取。这些特征由多个神经网络层共同学习到:

AlexNet第一层学到的特征抽取器

1.1 现代CNN网络发展概览

CNN网络架构的演进历程

本章节将介绍多个现代卷积神经网络和启发式概念,以培养深度学习领域的”直觉”:

  • AlexNet:首个击败传统计算机视觉模型的大型神经网络
  • VGG:使用重复的神经网络块构建更深的网络
  • NiN:重复使用卷积层,并用 1×1 卷积层替代全连接层
  • GoogLeNet:通过不同窗口大小的卷积层和最大池化层并行连结抽取信息
  • ResNet:通过残差块构建跨层的数据通道
  • DenseNet:以计算成本换取更好效果的稠密连接架构

以上网络架构都从 ImageNet 竞赛中脱颖而出。


2. AlexNet 网络架构

AlexNet网络架构图

AlexNet 的设计理念和网络架构与 LeNet 类似,但也有许多重要的不同:

2.1 架构特点对比

AlexNet特点

更深的网络结构、更大的通道数

  • AlexNet 由 5 个卷积层、2 个全连接隐藏层和 1 个全连接输出层组成
  • ImageNet 图像像素分辨率是 Fashion-MNIST 数据集的 10 倍多
  • 需要更大的 (11×11) 卷积窗口捕获目标特征

双数据流设计

  • 连接最后一个卷积层的全连接层共有 4096 个输出
  • 当时由于 GPU 显存的限制,需要采用双数据流的方式
  • 每个 GPU 只能计算一半的参数
  • 现在的 GPU 显存充裕,很少需要跨 GPU 分解模型

ReLU 激活函数

使用非饱和激活函数 ReLU 而不是 Sigmoid:

  • ReLU 函数的计算更简单,不需要复杂的求幂运算
  • ReLU 在正区间的梯度总是 1,避免梯度消失/爆炸问题
  • 使模型即使没有很好地初始化,也能有效地完成训练

多重正则化手段

  • 在权重衰减的基础上使用 Dropout 技术控制全连接层的模型复杂度
  • 通过翻转、裁切和变色对数据集进行图像的数据增强
  • 更大的样本量进一步减少了过拟合的问题

AlexNet详细架构图

2.2 PyTorch 实现

AlexNet 的网络架构用 PyTorch 框架实现如下:

import torch
from torch import nn, Tensor

class AlexNet(nn.Module):
    def __init__(self, num_classes=10):
        super(AlexNet, self).__init__()
        
        # 特征提取部分 (卷积层)
        self.features = nn.Sequential(
            # 第一层卷积 + ReLU + 池化
            nn.Conv2d(in_channels=1, out_channels=96, kernel_size=11, stride=4, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            
            # 第二层卷积 + ReLU + 池化
            nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            
            # 第三层卷积 + ReLU
            nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            
            # 第四层卷积 + ReLU
            nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            
            # 第五层卷积 + ReLU + 池化
            nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        
        # 分类器部分 (全连接层)
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(in_features=6400, out_features=4096),
            nn.ReLU(inplace=True),
            
            nn.Dropout(p=0.5),
            nn.Linear(in_features=4096, out_features=4096),
            nn.ReLU(inplace=True),
            
            nn.Linear(in_features=4096, out_features=num_classes)
        )

    def forward(self, x: Tensor) -> Tensor:
        x = self.features(x)
        x = torch.flatten(x, 1)  # 展平操作
        x = self.classifier(x)
        return x

2.3 网络结构分析

让我们测试各层输出的维度变化:

def test_alexnet_architecture():
    """测试AlexNet各层的输出维度"""
    # 创建模型和测试数据
    model = AlexNet(num_classes=10)
    test_input = torch.randn(1, 1, 224, 224)
    
    print(f"输入形状: {test_input.shape}")
    print("\n=== 特征提取部分 ===")
    
    x = test_input
    for i, layer in enumerate(model.features):
        x = layer(x)
        print(f"{type(layer).__name__} 输出形状: {tuple(x.shape)}")
    
    # 展平操作
    x = torch.flatten(x, 1)
    print(f"\n展平后形状: {x.shape}")
    
    print("\n=== 分类器部分 ===")
    for i, layer in enumerate(model.classifier):
        x = layer(x)
        print(f"{type(layer).__name__} 输出形状: {tuple(x.shape)}")

# 运行测试
test_alexnet_architecture()
查看输出结果
输入形状: torch.Size([1, 1, 224, 224])

=== 特征提取部分 ===
Conv2d 输出形状: (1, 96, 54, 54)
ReLU 输出形状: (1, 96, 54, 54)
MaxPool2d 输出形状: (1, 96, 26, 26)
Conv2d 输出形状: (1, 256, 26, 26)
ReLU 输出形状: (1, 256, 26, 26)
MaxPool2d 输出形状: (1, 256, 12, 12)
Conv2d 输出形状: (1, 384, 12, 12)
ReLU 输出形状: (1, 384, 12, 12)
Conv2d 输出形状: (1, 384, 12, 12)
ReLU 输出形状: (1, 384, 12, 12)
Conv2d 输出形状: (1, 256, 12, 12)
ReLU 输出形状: (1, 256, 12, 12)
MaxPool2d 输出形状: (1, 256, 5, 5)

展平后形状: torch.Size([1, 6400])

=== 分类器部分 ===
Dropout 输出形状: (1, 6400)
Linear 输出形状: (1, 4096)
ReLU 输出形状: (1, 4096)
Dropout 输出形状: (1, 4096)
Linear 输出形状: (1, 4096)
ReLU 输出形状: (1, 4096)
Linear 输出形状: (1, 10)

3. 模型训练与评估

3.1 训练工具集成

为了便于复用,我们将数据加载函数和训练器类组织到 training_tools.py 中:

查看完整的训练工具代码
import os
from typing import Literal, Tuple
import torch
import torchvision
from torch import Tensor, device, nn, optim
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

def fashionMNIST_loader(batch_size, *, resize=None) -> Tuple[DataLoader, DataLoader]:
    """Fashion-MNIST数据加载器"""
    transform_list = [
        transforms.ToTensor(),
        transforms.Normalize((0.5,), (0.5,))
    ]
    if resize:
        transform_list.insert(0, transforms.Resize(size=resize))
    
    transform_pipe = transforms.Compose(transform_list)
    
    train_dataset = torchvision.datasets.FashionMNIST(
        root='~/DataSets', train=True, transform=transform_pipe, download=True)
    test_dataset = torchvision.datasets.FashionMNIST(
        root='~/DataSets', train=False, transform=transform_pipe, download=True)
    
    train_loader = DataLoader(train_dataset, batch_size, shuffle=True, num_workers=4)
    test_loader = DataLoader(test_dataset, batch_size, shuffle=False, num_workers=4)
    
    return train_loader, test_loader

class Trainer:
    """通用训练器类"""
    def __init__(self, model, train_loader, test_loader, criterion, optimizer, device):
        self.model = model.to(device)
        self.train_loader = train_loader
        self.test_loader = test_loader
        self.criterion = criterion
        self.optimizer = optimizer
        self.device = device
        self.writer = SummaryWriter()

    def _run_epoch(self, data_loader, mode='train'):
        is_train = mode == 'train'
        self.model.train() if is_train else self.model.eval()
        
        total_loss = correct_count = total_count = 0
        
        with torch.set_grad_enabled(is_train):
            for features, labels in data_loader:
                features = features.to(self.device)
                labels = labels.to(self.device)
                
                outputs = self.model(features)
                loss = self.criterion(outputs, labels)
                
                if is_train:
                    self.optimizer.zero_grad()
                    loss.backward()
                    self.optimizer.step()
                
                total_loss += loss.item()
                _, predicted = outputs.max(1)
                total_count += labels.size(0)
                correct_count += predicted.eq(labels).sum().item()
        
        accuracy = 100. * correct_count / total_count
        avg_loss = total_loss / len(data_loader)
        return accuracy, avg_loss

    def train(self, epochs):
        for epoch in range(epochs):
            train_acc, train_loss = self._run_epoch(self.train_loader, 'train')
            test_acc, test_loss = self._run_epoch(self.test_loader, 'eval')
            
            self.writer.add_scalar('Loss/Train', train_loss, epoch)
            self.writer.add_scalar('Loss/Test', test_loss, epoch)
            self.writer.add_scalar('Accuracy/Train', train_acc, epoch)
            self.writer.add_scalar('Accuracy/Test', test_acc, epoch)
            
            print(f'Epoch [{epoch+1:3d}/{epochs}] '
                  f'Train Loss: {train_loss:.4f}, Train Acc: {train_acc:5.2f}%, '
                  f'Test Loss: {test_loss:.4f}, Test Acc: {test_acc:5.2f}%')
    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.writer.close()

3.2 训练示例

现在可以很方便地训练、评估 AlexNet 模型:

import torch
from torch import nn, optim
from training_tools import fashionMNIST_loader, Trainer

def train_alexnet():
    """训练AlexNet模型"""
    # 超参数设置
    BATCH_SIZE = 128
    EPOCHS = 10  # 为了演示,减少训练轮数
    LEARNING_RATE = 0.001
    
    # 创建模型和数据加载器
    model = AlexNet(num_classes=10)
    train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE, resize=224)
    
    # 损失函数和优化器
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    print(f"使用设备: {device}")
    print(f"模型参数数量: {sum(p.numel() for p in model.parameters()):,}")
    
    # 开始训练
    with Trainer(model, train_loader, test_loader, criterion, optimizer, device) as trainer:
        trainer.train(EPOCHS)

if __name__ == '__main__':
    train_alexnet()

3.3 训练结果分析

查看训练过程详细输出
使用设备: cuda
模型参数数量: 61,100,840

Epoch [  1/10] Train Loss: 2.2980, Train Acc: 18.95%, Test Loss: 2.2814, Test Acc: 30.81%
Epoch [  2/10] Train Loss: 1.3385, Train Acc: 51.70%, Test Loss: 0.7708, Test Acc: 71.34%
Epoch [  3/10] Train Loss: 0.6948, Train Acc: 73.89%, Test Loss: 0.6682, Test Acc: 75.69%
Epoch [  4/10] Train Loss: 0.6026, Train Acc: 77.29%, Test Loss: 0.5620, Test Acc: 78.50%
Epoch [  5/10] Train Loss: 0.5429, Train Acc: 79.51%, Test Loss: 0.5525, Test Acc: 78.86%
Epoch [  6/10] Train Loss: 0.5013, Train Acc: 81.22%, Test Loss: 0.4786, Test Acc: 82.20%
Epoch [  7/10] Train Loss: 0.4650, Train Acc: 82.46%, Test Loss: 0.4660, Test Acc: 82.68%
Epoch [  8/10] Train Loss: 0.4402, Train Acc: 83.53%, Test Loss: 0.4500, Test Acc: 83.57%
Epoch [  9/10] Train Loss: 0.4170, Train Acc: 84.50%, Test Loss: 0.4054, Test Acc: 84.86%
Epoch [ 10/10] Train Loss: 0.3973, Train Acc: 85.22%, Test Loss: 0.3984, Test Acc: 85.24%

AlexNet训练过程损失和准确率曲线


参考资料

如果这篇记录对你有帮助,可以留下一句回应。

前往留言

DISCUSSION

讨论与回应

欢迎补充细节、指出问题,或分享与这篇文章有关的经验。

昵称与邮箱为必填项,邮箱仅用于头像和回复通知,不会公开。网址可以留空。

正在准备留言区…

演示赞赏界面

谢谢你愿意支持长期写作。

第一阶段不会发起支付或收集任何信息。接入真实赞赏渠道后,这里会展示清楚的金额、渠道和完成状态。

输入关键词开始搜索 · 按 Esc 关闭
打开完整搜索页 →