news 2026/4/27 10:25:34

PyTorch实战:手把手教你用DenseNet-121在CIFAR-10上跑出第一个结果(附完整训练脚本)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch实战:手把手教你用DenseNet-121在CIFAR-10上跑出第一个结果(附完整训练脚本)

PyTorch实战:手把手教你用DenseNet-121在CIFAR-10上跑出第一个结果(附完整训练脚本)

当你第一次接触深度学习时,可能会被各种复杂的网络结构吓到。DenseNet作为近年来备受关注的卷积神经网络架构,以其独特的密集连接机制在ImageNet等大型数据集上表现出色。但今天,我们不谈理论,直接从实战出发——用PyTorch在CIFAR-10数据集上训练一个DenseNet-121模型,让你在30分钟内看到第一个训练结果。

1. 环境准备与数据加载

在开始之前,确保你的Python环境已经安装了PyTorch和torchvision。如果你使用GPU训练,还需要安装对应版本的CUDA。下面是我们需要的核心库:

import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader from torchsummary import summary

CIFAR-10数据集包含60,000张32x32的彩色图像,分为10个类别,每个类别6,000张。PyTorch的torchvision已经内置了这个数据集,我们可以很方便地下载和使用:

# 数据预处理 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) # 加载数据集 trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) trainloader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test) testloader = DataLoader(testset, batch_size=100, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

提示:数据增强是提升模型泛化能力的重要手段。我们在训练集上使用了随机裁剪和水平翻转,而测试集只进行了标准化处理。

2. DenseNet-121模型实现

DenseNet的核心思想是密集连接(Dense Connectivity),即每一层都接收前面所有层的输出作为输入。这种结构促进了特征重用,缓解了梯度消失问题。下面是DenseNet-121的关键组件实现:

class _DenseLayer(nn.Module): def __init__(self, num_input_features, growth_rate, bn_size, drop_rate): super(_DenseLayer, self).__init__() self.add_module('norm1', nn.BatchNorm2d(num_input_features)), self.add_module('relu1', nn.ReLU(inplace=True)), self.add_module('conv1', nn.Conv2d(num_input_features, bn_size * growth_rate, kernel_size=1, stride=1, bias=False)), self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate)), self.add_module('relu2', nn.ReLU(inplace=True)), self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate, kernel_size=3, stride=1, padding=1, bias=False)), self.drop_rate = drop_rate def forward(self, x): new_features = super(_DenseLayer, self).forward(x) if self.drop_rate > 0: new_features = F.dropout(new_features, p=self.drop_rate, training=self.training) return torch.cat([x, new_features], 1) class _Transition(nn.Sequential): def __init__(self, num_input_features, num_output_features): super(_Transition, self).__init__() self.add_module('norm', nn.BatchNorm2d(num_input_features)) self.add_module('relu', nn.ReLU(inplace=True)) self.add_module('conv', nn.Conv2d(num_input_features, num_output_features, kernel_size=1, stride=1, bias=False)) self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))

完整的DenseNet-121模型构建如下:

class DenseNet(nn.Module): def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64, bn_size=4, drop_rate=0, num_classes=10): super(DenseNet, self).__init__() # 初始卷积层 self.features = nn.Sequential( nn.Conv2d(3, num_init_features, kernel_size=3, stride=1, padding=1, bias=False), nn.BatchNorm2d(num_init_features), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2) ) # 构建DenseBlock num_features = num_init_features for i, num_layers in enumerate(block_config): block = _DenseBlock(num_layers=num_layers, num_input_features=num_features, bn_size=bn_size, growth_rate=growth_rate, drop_rate=drop_rate) self.features.add_module('denseblock%d' % (i + 1), block) num_features = num_features + num_layers * growth_rate if i != len(block_config) - 1: trans = _Transition(num_input_features=num_features, num_output_features=num_features // 2) self.features.add_module('transition%d' % (i + 1), trans) num_features = num_features // 2 # 最终分类层 self.features.add_module('norm5', nn.BatchNorm2d(num_features)) self.classifier = nn.Linear(num_features, num_classes) # 参数初始化 for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.constant_(m.bias, 0) def forward(self, x): features = self.features(x) out = F.relu(features, inplace=True) out = F.avg_pool2d(out, kernel_size=8, stride=1).view(features.size(0), -1) out = self.classifier(out) return out

注意:原始DenseNet是为ImageNet设计的(输入尺寸224x224),我们针对CIFAR-10的32x32输入调整了初始卷积层和池化层的参数。

3. 模型训练与验证

有了模型和数据,接下来就是训练过程。我们使用交叉熵损失函数和SGD优化器,并加入学习率调度:

def train_model(): device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") net = DenseNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4) scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[150, 225], gamma=0.1) for epoch in range(300): # 总共训练300个epoch net.train() running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data[0].to(device), data[1].to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: # 每100个batch打印一次 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 scheduler.step() # 每个epoch结束后在测试集上验证 test_acc = evaluate(net, testloader, device) print('Epoch %d test accuracy: %.2f%%' % (epoch + 1, 100 * test_acc)) print('Finished Training') return net def evaluate(model, dataloader, device): model.eval() correct = 0 total = 0 with torch.no_grad(): for data in dataloader: images, labels = data[0].to(device), data[1].to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return correct / total

4. 训练技巧与性能优化

要让DenseNet在CIFAR-10上达到最佳性能,有几个关键技巧需要注意:

  1. 学习率调整策略

    • 初始学习率设为0.1
    • 在第150和225个epoch时将学习率乘以0.1
    • 使用momentum=0.9和weight decay=1e-4
  2. 数据增强

    • 随机裁剪(padding=4)
    • 随机水平翻转
    • 标准化处理(使用CIFAR-10的均值和方差)
  3. 模型调整

    • 将原始DenseNet的第一个7x7卷积(stride=2)改为3x3卷积(stride=1)
    • 去掉第一个最大池化层或使用较小的核尺寸
    • 最终平均池化层调整为适应CIFAR-10的8x8

下表展示了不同配置下的模型性能对比:

配置项默认值调整值准确率影响
初始卷积7x7, stride=23x3, stride=1+3.2%
第一池化MaxPool 3x3无或2x2+1.5%
学习率衰减固定多步衰减+2.8%
数据增强裁剪+翻转+4.1%
# 优化后的模型初始化 def DenseNet121(): return DenseNet( growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64, bn_size=4, drop_rate=0, num_classes=10 )

5. 结果可视化与分析

训练完成后,我们可以可视化训练过程中的损失和准确率变化:

import matplotlib.pyplot as plt def plot_training(history): plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(history['train_loss'], label='Train Loss') plt.title('Training Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.subplot(1, 2, 2) plt.plot(history['test_acc'], label='Test Accuracy') plt.title('Test Accuracy') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.tight_layout() plt.show()

典型训练过程中,你会看到:

  • 训练损失在前50个epoch快速下降
  • 测试准确率在100个epoch后增长放缓
  • 学习率调整时(150和225epoch)会有明显的准确率提升

在单个NVIDIA V100 GPU上,完整训练过程大约需要2-3小时,最终测试准确率可以达到约94.5%,这与原论文在CIFAR-10上的结果相当。

6. 完整代码整合

以下是完整的训练脚本,包含了模型定义、数据加载、训练循环和评估:

import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import torch.nn.functional as F # 数据加载 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) trainloader = DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test) testloader = DataLoader(testset, batch_size=100, shuffle=False, num_workers=2) # DenseNet实现 class _DenseLayer(nn.Module): def __init__(self, num_input_features, growth_rate, bn_size, drop_rate): super(_DenseLayer, self).__init__() self.add_module('norm1', nn.BatchNorm2d(num_input_features)), self.add_module('relu1', nn.ReLU(inplace=True)), self.add_module('conv1', nn.Conv2d(num_input_features, bn_size * growth_rate, kernel_size=1, stride=1, bias=False)), self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate)), self.add_module('relu2', nn.ReLU(inplace=True)), self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate, kernel_size=3, stride=1, padding=1, bias=False)), self.drop_rate = drop_rate def forward(self, x): new_features = super(_DenseLayer, self).forward(x) if self.drop_rate > 0: new_features = F.dropout(new_features, p=self.drop_rate, training=self.training) return torch.cat([x, new_features], 1) class DenseNet(nn.Module): def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), num_init_features=64, bn_size=4, drop_rate=0, num_classes=10): super(DenseNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, num_init_features, kernel_size=3, stride=1, padding=1, bias=False), nn.BatchNorm2d(num_init_features), nn.ReLU(inplace=True), ) num_features = num_init_features for i, num_layers in enumerate(block_config): block = nn.Sequential() for j in range(num_layers): layer = _DenseLayer(num_features + j * growth_rate, growth_rate, bn_size, drop_rate) block.add_module('denselayer%d' % (j + 1), layer) self.features.add_module('denseblock%d' % (i + 1), block) num_features = num_features + num_layers * growth_rate if i != len(block_config) - 1: trans = nn.Sequential( nn.BatchNorm2d(num_features), nn.ReLU(inplace=True), nn.Conv2d(num_features, num_features // 2, kernel_size=1, stride=1, bias=False), nn.AvgPool2d(kernel_size=2, stride=2), ) self.features.add_module('transition%d' % (i + 1), trans) num_features = num_features // 2 self.features.add_module('norm5', nn.BatchNorm2d(num_features)) self.classifier = nn.Linear(num_features, num_classes) for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.constant_(m.bias, 0) def forward(self, x): features = self.features(x) out = F.relu(features, inplace=True) out = F.avg_pool2d(out, kernel_size=8, stride=1).view(features.size(0), -1) out = self.classifier(out) return out # 训练与评估 def train(): device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") net = DenseNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4) scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[150, 225], gamma=0.1) for epoch in range(300): net.train() running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data[0].to(device), data[1].to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100)) running_loss = 0.0 scheduler.step() test_acc = evaluate(net, testloader, device) print('Epoch %d test accuracy: %.2f%%' % (epoch + 1, 100 * test_acc)) print('Finished Training') def evaluate(model, dataloader, device): model.eval() correct = 0 total = 0 with torch.no_grad(): for data in dataloader: images, labels = data[0].to(device), data[1].to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return correct / total if __name__ == '__main__': train()

将上述代码保存为densenet_cifar10.py,直接运行即可开始训练。如果你想要快速验证模型效果,可以减少训练epoch数(如50个epoch),虽然准确率会低一些,但能在较短时间内看到模型学习效果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 10:21:08

修饰抗体定制如何解码蛋白质翻译后修饰?

一、什么是蛋白质翻译后修饰,为何至关重要?蛋白质翻译后修饰(PTM)是指蛋白质在合成后经历的化学修饰过程,几乎参与细胞所有生命活动的调控。人体内约50%-90%的蛋白质都会发生翻译后修饰,这一机制使蛋白质类…

作者头像 李华
网站建设 2026/4/27 10:21:08

TMSpeech:5分钟实现Windows本地实时语音转文字的终极方案

TMSpeech:5分钟实现Windows本地实时语音转文字的终极方案 【免费下载链接】TMSpeech 腾讯会议摸鱼工具 项目地址: https://gitcode.com/gh_mirrors/tm/TMSpeech 你是否经常在会议中手忙脚乱地记录要点?是否担心云端语音识别服务泄露你的隐私数据&…

作者头像 李华
网站建设 2026/4/27 10:21:00

DevDocs用户支持体系:问题解答与技术支持终极指南

DevDocs用户支持体系:问题解答与技术支持终极指南 【免费下载链接】devdocs API Documentation Browser 项目地址: https://gitcode.com/GitHub_Trending/de/devdocs DevDocs作为一款强大的API Documentation Browser,为开发者提供了便捷的文档浏…

作者头像 李华
网站建设 2026/4/27 10:17:25

Awoo Installer:Switch游戏安装的终极免费解决方案

Awoo Installer:Switch游戏安装的终极免费解决方案 【免费下载链接】Awoo-Installer A No-Bullshit NSP, NSZ, XCI, and XCZ Installer for Nintendo Switch 项目地址: https://gitcode.com/gh_mirrors/aw/Awoo-Installer 想要在Nintendo Switch上快速安装游…

作者头像 李华
网站建设 2026/4/27 10:17:22

终极指南:如何使用FakeLocation保护Android位置隐私

终极指南:如何使用FakeLocation保护Android位置隐私 【免费下载链接】FakeLocation Xposed module to mock locations per app. 项目地址: https://gitcode.com/gh_mirrors/fak/FakeLocation 在当今数字化时代,位置模拟工具已成为Android隐私保护…

作者头像 李华
网站建设 2026/4/27 10:16:02

终极Vim备份策略:保护代码数据的完整指南

终极Vim备份策略:保护代码数据的完整指南 【免费下载链接】vim The official Vim repository 项目地址: https://gitcode.com/gh_mirrors/vi/vim Vim作为一款功能强大的文本编辑器,被广泛应用于代码编写和文本处理。然而,许多用户忽视…

作者头像 李华