
简介面向机器学习初学者与Kaggle参赛者这份资源以CIFAR-10图像分类赛题为切入点演示如何用PyTorch完成从数据读取、模型搭建到训练预测与提交结果的完整流程。压缩包共1017个文件约2.34MB其中1006张PNG图片构成精简版训练/测试图像4个Python脚本与2个PyTorch notebook分别承担数据预处理、模型定义和训练逻辑3个CSV文件包含标签与提交结果另有2个pyc缓存文件便于复现。已有180人学习浏览适合希望通过实战代码快速建立深度学习图像分类项目认知的读者。借助notebook的分步讲解和可运行脚本使用者能对照理解卷积网络在真实比赛中的调参思路、预测结果整理及Kaggle提交格式并基于示例图片和CSV快速跑通一个最小可用方案。1. 别急着写模型先弄懂 CIFAR-10 到底在考什么我第一次打 Kaggle 的 CIFAR-10 比赛时犯过一个特别典型的错误花了一整天把 ResNet 代码调通、跑出 baseline然后志得意满地点了 Submit最后看到的准确率数字让我整个人都清醒了。说实话这个比赛看起来非常简单但真正想拿到一个体面的分数远不是把数据集丢进 CNN 里训练这么简单。先把这个比赛的基础信息说透。CIFAR-10 是一个 60 张 32x32 彩色图片组成的数据集共分 10 个类别飞机、汽车、鸟、猫、鹿、狗、青蛙、马、轮船、卡车。其中训练集 50000 张测试集 10000 张。Kaggle 上的比赛版本通常会把官方测试集留出一部分作为 Public Leaderboard公开榜另一部分作为 Private Leaderboard私有榜你最终的名次由私有榜决定。这里要特别留个心眼很多人看到 32x32 就觉得这图片这么小随便搞搞不就 90% 正确率了吗 但放大看你会发现CIFAR-10 的图片分辨率极低一只猫可能就只有一小坨像素人眼都容易认错模型更是如此。所以这个比赛本质上考的不是你会不会搭 CNN而是数据策略、正则化手段、训练技巧和模型集成这几项能不能有效组合。再讲讲评价指标。CIFAR-10 用的是 Accuracy准确率也就是预测正确的图片占比。别看它简单在类别分布均衡的数据集里Accuracy 是最直观也最容易被刷的指标。纯随机猜是 10% 准确率线性模型大概能到 40%一个正经的 ResNet 轻松到 90% 以上但要再往上走每 1% 的差距都需要成倍的算力和调参功夫。我还想强调一点参加 Kaggle 比赛先读明白数据说明远比先写代码重要。CIFAR-10 看起来人畜无害但它的数据分布、类别平衡、是否包含数据噪声都会直接影响你的方案选择。比如你必须确认测试集是否经过了与训练集相同的数据预处理否则你本地验证准确率高提交上去反而下降这种事在 Kaggle 上并不罕见。2. 环境准备与数据加载这里卡住了 80% 的新手2.1 用 Anaconda 创建一个干净的环境很多初学者一上来就在全局环境里装 PyTorch等到要装第二个项目依赖时版本冲突能让你怀疑人生。我强烈建议用 Anaconda 或 Miniconda 为这个比赛单独建一个环境。做法很简单conda create -n cifar10 python3.10 conda activate cifar10 conda install pytorch torchvision torchaudio pytorch-cuda12.1 -c pytorch -c nvidia注意pytorch-cuda12.1这个参数取决于你的显卡驱动支持的 CUDA 版本。你可以先跑nvidia-smi看右上角的 CUDA Version那个是驱动支持的最高 CUDA 版本只要这个数字大于或等于你要安装的 CUDA 版本就可以。简单说不是 CUDA 装得越高越好而是要匹配你的驱动。如果你没有独立显卡或者显卡显存不够小于 6GB 会比较吃紧也别急着放弃。CPU 也能跑通整个流程只是训练速度会慢上很多。环境变量设置CUDA_VISIBLE_DEVICES或者直接不装 CUDA 版本PyTorch 会自动退回 CPU 模式。2.2 理解 torchvision 内置数据集与 DataLoader 的协作逻辑很多人上来就下载 Kaggle 上的 CIFAR-10 数据文件然后自己写解析代码这一步没必要。torchvision 已经内置了 CIFAR-10 数据集你只需要告诉它我要下载到哪里、要不要训练集就行from torchvision import datasets, transforms train_dataset datasets.CIFAR10( root./data, trainTrue, downloadTrue, transformtrain_transform ) test_dataset datasets.CIFAR10( root./data, trainFalse, downloadTrue, transformtest_transform )如果网络不好下载失败手动去下载cifar-10-python.tar.gz放到./data下对应目录也能解决问题。DataLoader 则是负责把 Dataset 里的数据一批一批地取出来送给模型训练核心参数有这么几个train_loader DataLoader( train_dataset, batch_size128, shuffleTrue, num_workers4, pin_memoryTrue )batch_size每次喂给模型多少张图通常取决于显存大小128 或 256 都算常见。shuffle训练集必须设为 True把打乱顺序的样本交给模型防止模型学到顺序相关的偏差。num_workers用几个子进程去做数据加载和预处理Windows 上建议设为 0 或 2Linux 上可以往上调但过高反而会拖慢速度。pin_memory设为 True 后主机内存的数据搬到 GPU 显存时会走更快的通道对训练吞吐有实际收益。batch_size的选择其实对模型收敛有直接影响。过大时收敛慢、容易收敛到 sharp minimum泛化差的尖底过小则梯度噪声大、训练不稳定。CIFAR-10 场景下128 是一个很均衡的起步值我实测下来效果不错。2.3 用数据集划分验证训练效果我从不直接用官方测试集来评估模型因为提交次数有限每天都有提交上限一旦提交多了就会被排行榜惩罚俗称刷榜反而影响最终排名。所以我都会先从训练集里划一部分出来当验证集from torch.utils.data import random_split train_data, val_data random_split( train_dataset, [45000, 5000], generatortorch.Generator().manual_seed(42) )分成 45000 训练、5000 验证的好处是训练过程中可以实时监控验证准确率据此判断模型有没有过拟合、学习率策略要不要调整。最后再拿真正留出的官方测试集提交到 Kaggle每一分提交都花在刀刃上。3. 数据增广别嫌麻烦这是 90% 与 95% 的分水岭3.1 不再裸奔的预处理 PipelineCIFAR-10 只有 50000 张训练图片如果直接喂给模型哪怕是 ResNet 这种效果不错的架构也很容易过拟合——训练集准确率蹭蹭往上涨验证集准确率却早早封顶。数据增广Data Augmentation是解决这个问题最直接、最便宜的手段。我测试过很多组增广配置最终长期使用的一套是train_transform transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.Transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize( mean(0.4914, 0.4822, 0.4465), std(0.2470, 0.2435, 0.2616) ) ]) test_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize( mean(0.4914, 0.4822, 0.4465), std(0.2470, 0.2435, 0.2616) ) ])逐个解释一下RandomCrop(32, padding4)先把图片四周补上 4 个像素的 0默认模式再做一次随机裁剪回 32x32。等于每次训练看到的图片都发生了小幅位移模型被迫学得更稳健。RandomHorizontalFlip以 50% 概率水平翻转。CIFAR-10 中大多数类别翻转后语义不变比如卡车翻转后还是卡车这是算力性价比极高的一种增广。ColorJitter给图片亮度、对比度加一点随机扰动提高模型对光照变化的鲁棒性。注意幅度不要太大否则会让图片失真、反而损害性能。Normalize把像素值从 [0,1] 区间转换为以 0 为中心、方差为 1 的分布。这个操作必须做否则网络训练会明显变慢甚至在深层网络中梯度爆炸。验证集和测试集只做标准化绝对不做随机增广这是为了保证评估指标稳定。一个小细节验证/测试增广中的mean/std值是全官方 CIFAR-10 数据集的统计值不是自己拍脑袋编的直接用上面这组数字即可。3.2 CutOut、MixUp、CutMix 到底该不该用基础的 RandomCrop Flip 能帮你稳定站上 90% 以上的准确率但想突破 95%就得考虑更高阶的增广或训练策略了。这里我说三个常见的以及我自己的使用感受。CutOut也叫 RandomErasing随机擦除图片中的一块小区域强迫模型不要只依赖局部信息。它对 CIFAR-10 提升效果不错而且实现很简单PyTorch 里torchvision.transforms.RandomErasing可以直接用。对 32x32 的输入尺寸RandomErasing(p0.7, scale(0.01, 0.1))是个可靠的配置。MixUp按比例把两张训练图混合同时把标签也按同样的比例混合。做法是每次随机取两个样本和一个混合系数lambda输出变成mixed_x lambda * x1 (1 - lambda) * x2 mixed_y lambda * y1 (1 - lambda) * y2MixUp 在 CIFAR-10 上稳定有提升但直观感受是训练收敛变慢需要配合更长的 epoch 才能看到好处。它和 CutOut 不是互斥关系有人两者都用也有人只选其一属于预算充足就上预算有限优先保住基础增广的类型。CutMix把一块区域从一个样本里切下来贴到另一个样本上标签按面积比例混合。它在很多 Kaggle 图像比赛里是冠军常客技巧比 MixUp 更适合 CIFAR-10 这种形状信息重要的场景。我的建议是初学阶段先把基础增广做好保证模型不快速过拟合当你能稳定跑到 93%、94% 以后再引入 MixUp 或 CutMix 这一类高阶方案。一步到位反而容易让问题变复杂排错难度上升收益却说不准。4. 模型与训练从 ResNet18 起步的进阶路线4.1 为什么 ResNet18 是性价比极高的第一选择CIFAR-10 的图只有 32x32用不着那种为 ImageNet 千分类设计的超深网络很多强模型在这类任务上反而吃亏。ResNet18 大概几百万参数单张图处理很快训练时间可控效果已经能轻松超过 90%所以我先建议用它跑通全流程。torchvision 里提供了 CIFAR-10 专用的 ResNet 变体注意不要直接引通用的torchvision.models.resnet18因为通用版本第一层是 7x7 步长 2 的卷积和最大池化对 32x32 的输入并不合适。最省事的做法是把第一层改成kernel_size3, stride1, padding1去掉第一层后的池化。也可以直接导入现成的变体from torchvision.models import resnet18 model resnet18(num_classes10)不过更靠谱的是手动调整输入层这是 CIFAR-10 任务的关键细节import torch.nn as nn model.conv1 nn.Conv2d(3, 64, kernel_size3, stride1, padding1, biasFalse) model.maxpool nn.Identity()改完以后模型对 32x32 输入的适应度会明显提升且参数量几乎不变。4.2 关于优化器不要无脑选 Adam这是个老生常谈的话题但我还是要强调CIFAR-10 这类任务里SGD Momentum CosineAnnealing 往往比 Adam 训练效果更稳定最终准确率也更高。并不是说 Adam 不行而是对于图像分类这类计算机视觉任务SGD 在调整得当的情况下更容易收敛到平坦的极小值泛化性能更好。我常用的训练配置优化器SGDmomentum0.9初始学习率0.1配合 batch_size128 的场景权重衰减5e-4学习率策略CosineAnnealingLR从 0.1 逐步降到接近 0Epoch50 左右如果你坚持用 Adam建议把学习率降到0.001并配合 weight decay。但根据我个人的经验同样 50 个 epochSGD 路线在 CIFAR-10 上通常能比 Adam 高出 0.5 到 1 个百分点的准确率。4.3 训练循环模板把 checkpoint 和日志做好初学阶段最容易踩的坑就是训练到一半程序崩了结果所有进度全部归零。所以我建议从一开始就建立 checkpoint 机制每训练完一个 epoch 就保存一次模型权重和优化器状态torch.save({ epoch: epoch, model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_acc: best_acc, }, checkpoint.pth)恢复训练时checkpoint torch.load(checkpoint.pth) model.load_state_dict(checkpoint[model_state_dict]) optimizer.load_state_dict(checkpoint[optimizer_state_dict]) start_epoch checkpoint[epoch] 1另外把每个 epoch 的训练 loss、训练准确率、验证准确率记录到一个日志文件或者直接用tensorboard可视化。刚开始觉得麻烦等到调参时你会发现这些历史数据是判断哪一步改动起了作用的重要依据。我在实际训练中还养成了一个习惯不仅保存最优准确率的 checkpoint还会保存最后一个 epoch 的权重。因为有些集成策略会用到最后一轮模型或者你权重衰减策略在最后几个 epoch 带来了某种特殊状态。5. 预测与集成别人提分的隐藏手牌5.1 TTATest Time Augmentation几乎零成本的提分方案TTA 的原理很简单预测时不再对图片只做一次前向传播而是对同一张图的多个增广版本各做一次预测最后把预测概率平均起来作为最终输出。以水平翻转为例一张测试图可以先正常预测一次再水平翻转后预测一次两条概率向量取平均最终预测结果通常会更稳定。model.eval() with torch.no_grad(): logits1 model(x) logits2 model(torch.flip(x, dims[3])) probs (torch.softmax(logits1, dim1) torch.softmax(logits2, dim1)) / 2 pred probs.argmax(dim1)就这么简单的一行增强通常能带来 0.2% 到 0.5% 的准确率提升。CIFAR-10 里有几个类别边界模糊TTA 正好能平抑这种偶然性而且是零成本额外训练属于纯收益操作。5.2 模型集成单模 94%三模平均可能 95%单个模型到了 94% 左右想再往上突破非常吃力。但把几个不同随机种子训练出来的模型做预测平均往往能有意外之喜。最简单的做法是 K 折交叉验证。把训练集分成 5 折每次用其中 4 折训练、1 折验证训练出 5 个模型。预测时5 个模型分别对测试集预测把 5 组概率取平均final_probs np.mean(all_pred_probs, axis0) predictions np.argmax(final_probs, axis1)这种集成的收益来源一是在于不同模型看到了不同的训练数据二是在于不同随机种子带来的模型差异。只要模型之间有一定差异平均后准确率大概率比最好的单个模型还高。你也可以在此基础上做加权集成用验证集上每个模型的准确率作为依据给高准确率的模型分配更高权重。实际操作中简单平均通常已经能拿到大部分收益加权平均略复杂一点但有时能再挤一点分数出来。我的经验是差距在 0.5% 以内的模型简单平均就够了如果模型水平参差不齐再考虑加权。这里额外提醒一句Kaggle 在比赛后期会切换到你没见过的 Private 数据上评估集成策略在这种未知数据上通常依然稳定因为我们只是做了概率平均并没有过分贴合公开榜。这一点对最终名次的影响非常关键。6. 实战中的排错与技巧这些坑我都踩过6.1 训练 Loss 不降应该先排查什么如果你发现 Loss 一直降不下去准确率徘徊在 10% 左右基本等于随机水平。此时先别急着怀疑模型结构按以下顺序排查检查数据预处理是否正常把经过 transform 的 batch 保存成图片看一眼确认没有出现全黑、全白或者严重失真的情况。检查标签与图片是否一一对应CIFAR-10 的悲剧在于有些图片人眼都很难辨认如果你误把标签顺序搞错训练再久都白搭。检查学习率是否太大/太小学习率太大会导致 Loss 震荡甚至发散太小则收敛极慢。SGD 用 0.1 起步比较稳。检查网络最后输出层数量是否为 10很多人复制 ImageNet 代码时忘了改最后分类头导致输出维度不匹配报错还好如果恰好输出 1000 类然后你只取了前 10 个输出那坑就大了。6.2 验证集准确率停滞在 90% 以下大多不是模型架构问题如果你的验证准确率卡在 80%~90% 之间多半不是模型不够强而是数据策略出问题了。首先检查训练集和验证集之间是否存在分布差异其次检查是否忘了做数据增广最后再检查 Normalize 的均值和方差是不是用错了一旦标准化错误模型的训练效率和最终上限都会受很大影响。6.3 显存不够怎么办如果你在训练时报出 CUDA out of memory先不要急着换显卡。有几个立刻见效的救急手段减小 batch size比如从 128 降到 64 或 32同时按比例调低学习率lr base_lr * batch_size / 128。使用torch.cuda.amp混合精度训练显存占用能降低近一半而且训练速度还可能变快。关闭pin_memoryTrue有时能省下一部分瓶颈资源但影响不是很大。减少num_workers数据缓存对显存的影响较小但能降低整体系统开销。6.4 训练中途断掉的续训问题我在一次比赛里训练了 40 个 epoch结果机器意外重启因为没有保存 checkpoint 的坏习惯所有进度全部丢失。复盘之后学乖了现在每次至少保证每个 epoch 结束时保存一次模型并且把best_acc单独记录训练日志和权重存在同一个目录下。还有一个小技巧写代码时在训练循环外预留--resume参数这样即使某次比赛中途断线也能在恢复到 checkpoint 后立刻从断点继续训练不浪费任何一小时的算力。6.5 别忽略代码的可复现性Kaggle 比赛里你可能一次跑出 93.5%另一次只跑出 92.8%模型结构完全相同差别只来自随机性。为了让自己调参时不被随机性迷惑我建议固定所有能固定的随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False注意固定随机种子后训练速度可能略有下降但对判断这次改动到底有没有用至关重要。实际比赛中 GPU 和硬件不同也会影响结果所以在本地调试调参时固定种子最终训练时可以不固定种子让模型探索更多可能性。写在最后如果你想低成本参加一次 KaggleCIFAR-10 是不错的起点我个人觉得CIFAR-10 这个比赛就像开了作弊器一样把数据量、任务复杂度、算力需求都压到了刚好的水位你不需要上万张图片的下载能力不需要动辄几周的预训练时间也不需要花一大笔钱买显卡就能把完整的机器学习项目闭环——数据处理、模型搭建、训练调参、验证评估、预测提交——从头到尾跑一遍。很多人在入坑机器学习时总想着等我准备好再参加比赛但 Kaggle 的真相是你永远不可能完全准备好。打一次完整比赛遇到报错、踩坑、调参失手、提交被拒这些混乱时刻恰恰是成长最快的时候。CIFAR-10 就是这么一块低门槛试金石先把这套流程走通之后你想去打更高阶的 ImageNet 分类、目标检测至少不会在工程层面被卡住了。如果你有 GPU我建议直接把 batch size 拉满配 50 个 epoch 的 SGD CosineAnnealing再叠一个简单的 TTA感受一下从 90% 冲刺 95% 的过程。如果你没有 GPU用 CPU 跑一个小的 ResNet18 加上基础增广也能在几小时里拿到 80% 以上的结果用来理解整个流程绰绰有余。动起手来跑通一个结果比看十篇教程都管用。本文还有配套的精品资源点击获取