2026/6/1 13:37:12
网站建设
项目流程
做网站和谷歌推广一共多少钱,室内装修公司招聘信息,单页网站开发实例下载,百度快照是什么意思?从零实现ResNet18#xff1a;理论云端实践全指南
引言
ResNet18是深度学习领域最经典的卷积神经网络之一#xff0c;由微软研究院在2015年提出。你可能在论文中见过它的结构图#xff0c;甚至能随手画出它的残差连接示意图。但当你想在自己的电脑上运行一个ResNet18模型时…从零实现ResNet18理论云端实践全指南引言ResNet18是深度学习领域最经典的卷积神经网络之一由微软研究院在2015年提出。你可能在论文中见过它的结构图甚至能随手画出它的残差连接示意图。但当你想在自己的电脑上运行一个ResNet18模型时却发现显卡内存不足、训练速度慢如蜗牛——这是很多AI爱好者都会遇到的困境。本文将带你从理论到实践完整掌握ResNet18。不同于纯理论讲解我们会用搭积木的方式理解残差连接的核心思想手把手教你用PyTorch从零实现ResNet18在云端GPU环境快速部署和训练模型用实际案例展示如何用ResNet18完成图像分类任务即使你只有Python基础跟着本文操作也能在1小时内完成从代码编写到模型训练的全流程。我们使用的云端GPU环境可以免费申请完全不用担心本地配置问题。1. ResNet18原理解析1.1 为什么需要残差网络在ResNet出现之前深度学习面临一个尴尬问题网络层数越深训练效果反而越差。这就像让小学生直接学微积分知识跨度太大反而适得其反。ResNet的解决方案很巧妙——如果深层网络难以学习新特征那就让它先学会保持现状。具体做法是引入残差连接如图1所示让网络可以跳过某些层的计算。这样即使新增的层没有学到有用特征至少不会让效果变差。1.2 网络结构拆解ResNet18的结构可以看作是由多个基础模块堆叠而成初始卷积层7x7大卷积核快速下采样4个阶段Stage每个阶段包含多个残差块残差块核心组件分为BasicBlock用于ResNet18/34和Bottleneck用于更深网络以ResNet18为例其结构参数为[2, 2, 2, 2] # 四个阶段分别有2个残差块每个残差块的计算过程可以用伪代码表示output conv2(conv1(x)) x # 残差连接就是简单的加法2. 环境准备与云端部署2.1 为什么需要GPU环境训练ResNet18这样的卷积神经网络GPU几乎是必需品。以CIFAR-10数据集为例CPU训练1个epoch需要约15分钟入门级GPU如T4仅需1分钟高端GPU如A100只需20秒我们推荐使用CSDN星图平台的PyTorch镜像已预装CUDA和常用深度学习库。2.2 快速创建GPU实例登录CSDN星图平台选择PyTorch 1.12 CUDA 11.3镜像申请T4或A100规格的GPU实例等待1-2分钟完成环境初始化创建成功后通过Web Terminal或SSH连接实例。首次使用建议运行以下命令检查环境nvidia-smi # 查看GPU状态 python -c import torch; print(torch.cuda.is_available()) # 检查PyTorch能否使用GPU3. 从零实现ResNet183.1 项目结构准备新建项目目录并安装必要依赖mkdir resnet18-implementation cd resnet18-implementation pip install torch torchvision matplotlib3.2 编写ResNet18核心代码创建model.py文件实现BasicBlock和ResNet18import torch import torch.nn as nn class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride1): super().__init__() self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, stride1, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) # 残差连接可能需要下采样 self.shortcut nn.Sequential() if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(out_channels) ) def forward(self, x): out torch.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out self.shortcut(x) # 关键残差连接 return torch.relu(out) class ResNet18(nn.Module): def __init__(self, num_classes10): super().__init__() self.in_channels 64 # 初始卷积层 self.conv1 nn.Conv2d(3, 64, kernel_size7, stride2, padding3, biasFalse) self.bn1 nn.BatchNorm2d(64) self.maxpool nn.MaxPool2d(kernel_size3, stride2, padding1) # 四个阶段 self.layer1 self._make_layer(64, 2, stride1) self.layer2 self._make_layer(128, 2, stride2) self.layer3 self._make_layer(256, 2, stride2) self.layer4 self._make_layer(512, 2, stride2) # 分类头 self.avgpool nn.AdaptiveAvgPool2d((1, 1)) self.fc nn.Linear(512, num_classes) def _make_layer(self, out_channels, num_blocks, stride): layers [] # 第一个block可能需要下采样 layers.append(BasicBlock(self.in_channels, out_channels, stride)) self.in_channels out_channels # 后续block保持通道数不变 for _ in range(1, num_blocks): layers.append(BasicBlock(out_channels, out_channels, stride1)) return nn.Sequential(*layers) def forward(self, x): x torch.relu(self.bn1(self.conv1(x))) x self.maxpool(x) x self.layer1(x) x self.layer2(x) x self.layer3(x) x self.layer4(x) x self.avgpool(x) x torch.flatten(x, 1) x self.fc(x) return x3.3 验证模型结构编写测试代码检查模型from model import ResNet18 import torch model ResNet18() dummy_input torch.randn(1, 3, 224, 224) # 模拟224x224的RGB输入 output model(dummy_input) print(f输入尺寸: {dummy_input.shape}) print(f输出尺寸: {output.shape}) # 应为[1, 10] print(f参数量: {sum(p.numel() for p in model.parameters()) / 1e6:.2f}M)正常输出应类似输入尺寸: torch.Size([1, 3, 224, 224]) 输出尺寸: torch.Size([1, 10]) 参数量: 11.18M4. 训练与评估实战4.1 准备CIFAR-10数据集创建train.py文件添加数据加载代码import torchvision import torchvision.transforms as transforms # 数据增强和归一化 transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), 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, trainTrue, downloadTrue, transformtransform_train) trainloader torch.utils.data.DataLoader( trainset, batch_size128, shuffleTrue, num_workers2) testset torchvision.datasets.CIFAR10( root./data, trainFalse, downloadTrue, transformtransform_test) testloader torch.utils.data.DataLoader( testset, batch_size100, shuffleFalse, num_workers2) classes (plane, car, bird, cat, deer, dog, frog, horse, ship, truck)4.2 训练循环实现继续在train.py中添加训练代码import torch.optim as optim from model import ResNet18 import torch.nn as nn import torch device cuda if torch.cuda.is_available() else cpu model ResNet18().to(device) criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.1, momentum0.9, weight_decay5e-4) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max200) def train(epoch): model.train() train_loss 0 correct 0 total 0 for batch_idx, (inputs, targets) in enumerate(trainloader): inputs, targets inputs.to(device), targets.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() train_loss loss.item() _, predicted outputs.max(1) total targets.size(0) correct predicted.eq(targets).sum().item() if batch_idx % 100 0: print(fEpoch: {epoch} | Batch: {batch_idx}/{len(trainloader)} f| Loss: {loss.item():.3f} | Acc: {100.*correct/total:.1f}%) def test(epoch): model.eval() test_loss 0 correct 0 total 0 with torch.no_grad(): for batch_idx, (inputs, targets) in enumerate(testloader): inputs, targets inputs.to(device), targets.to(device) outputs model(inputs) loss criterion(outputs, targets) test_loss loss.item() _, predicted outputs.max(1) total targets.size(0) correct predicted.eq(targets).sum().item() print(fTest Epoch: {epoch} | Loss: {test_loss/len(testloader):.3f} f| Acc: {100.*correct/total:.1f}%) for epoch in range(1, 201): train(epoch) test(epoch) scheduler.step()4.3 关键参数解析学习率初始设为0.1配合余弦退火调度器批量大小128适合大多数GPU显存数据增强随机裁剪水平翻转防止过拟合优化器带动量的SGD比Adam更适合ResNet5. 常见问题与优化技巧5.1 训练不收敛怎么办如果训练初期loss不下降可以尝试检查数据归一化参数是否正确暂时去掉数据增强确认基础流程正常使用更小的学习率如0.01测试5.2 显存不足的解决方案遇到CUDA out of memory错误时减小batch size如从128降到64使用梯度累积accum_steps 2 # 每2个batch更新一次参数 optimizer.zero_grad() for i, (inputs, targets) in enumerate(trainloader): outputs model(inputs) loss criterion(outputs, targets) / accum_steps loss.backward() if (i1) % accum_steps 0: optimizer.step() optimizer.zero_grad()5.3 提升模型准确率想要突破90%准确率可以尝试增加训练轮数200 epoch使用标签平滑Label Smoothing添加MixUp或CutMix数据增强尝试更大的输入尺寸如从32x32调整到224x224总结通过本文的实践你应该已经掌握了残差连接的本质让网络可以学习恒等映射解决梯度消失问题ResNet18完整实现从BasicBlock到完整网络结构的搭建技巧云端训练最佳实践如何利用GPU资源高效训练模型调参优化方法论学习率调度、数据增强等关键参数的设置逻辑建议你现在就动手尝试修改网络深度观察对性能的影响在自定义数据集上微调ResNet18尝试将模型部署为推理服务获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。