NOAI 2024 复赛题解|第2题:层数越多,分数越低?
NOAI复赛 计算机视觉
需要真题、资料,请拉到文末添加艾斯老师微信。
题目回顾
从 CIFAR10 里选了 5,000 张真实拍照图像,再用扩散模型生成了 5,000 张"虚假"图像,合在一起当训练集。测试集各 1,000 张。每张图 3×32×32。
任务:用 PyTorch 搭一个 CNN,区分真实图片和虚假图片。真实 Label=0,虚假 Label=1。
约束条件
• 至少 2 个卷积层(nn.Conv2d)+ 2 个池化层(nn.MaxPool2d)
• 最多 2 个线性层(nn.Linear)
• 不得使用 nn.Sequential() 嵌套
• 模型类名按题目要求命名
评分公式
• 复杂度分 = 1 / (卷积层数 + 线性层数 + 1)
• 最终得分 = (复杂度分 + Accuracy) × 3/4
先看评分公式
这道题和第1题最大的区别:不是准确率越高越好,而是"用最少的层达到最高准确率"。
算几个例子:
| 卷积层 | 线性层 | 复杂度分 | 准确率 | 最终得分 |
| 2 | 1 | 0.25 | 0.90 | 0.86 |
| 2 | 2 | 0.20 | 0.92 | 0.84 |
| 4 | 2 | 0.14 | 0.95 | 0.82 |
| 2 | 1 | 0.25 | 0.95 | 0.90 |
注意第三行:4 个卷积层 + 准确率 95%,居然不如 2 个卷积层 + 准确率 90%。
层数的代价非常高。多加一层卷积,准确率至少要提升好几个百分点才能回本。最优策略:2 个卷积层 + 1 个线性层,把准确率拉到尽可能高。
思路一:最简 CNN
用最少层数满足要求:2 个 Conv2d + 2 个 MaxPool2d + 1 个 Linear。
class CNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(32 * 8 * 8, 1) def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool1(x) x = F.relu(self.conv2(x)) x = self.pool2(x) x = x.view(x.size(0), -1) x = self.fc1(x) return x
复杂度分 = 1/(2+1+1) = 0.25,这是满足约束条件下的最高复杂度分。
32×32 经过两次 2×2 池化变成 8×8,32 个通道,展平后 32×8×8 = 2048 维输入线性层。如果 accuracy 到 0.90,最终得分就是 (0.25+0.90)×0.75 = 0.86。
思路二:在最简结构上提升准确率
层数不能加,只能在现有结构里做文章。
通道数是免费的
评分公式只数层数,不管通道数。把通道从 16→32 改成 64→128,复杂度分不变,但特征提取能力变强。
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.fc1 = nn.Linear(128 * 8 * 8, 1)
代价是训练变慢、参数变多(可能过拟合),但没有评分惩罚。
BatchNorm 也是免费的
nn.BatchNorm2d 不是卷积层也不是线性层,不会被计入复杂度分。但它能稳定训练、加速收敛。免费提升,没有理由不用 ✅
数据增强
训练集只有 10,000 张。加上随机翻转、随机裁剪,可以有效减少过拟合:
transform_train = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5)) ])
注意:测试时不要加随机增强,只做 Normalize。
卷积核大小可以调
3×3 是标准选择,但 5×5 感受野更大,能捕捉更大范围的纹理差异。扩散模型生成的图片在纹理细节上和真实图片有系统性差异,大卷积核可能更容易抓到。这类判断来自对生成模型和图像特征的了解,平时多读论文、多做实验,比赛时就能快速做出选择。
思路三:理解数据本身
扩散模型生成的图片和真实图片,差别在哪?
CIFAR10 分辨率很低(32×32),人眼很难分辨真假。但对 CNN 来说,扩散模型生成的图片在高频纹理上和真实照片有系统性差异——生成图片的像素过渡往往更平滑,缺少真实照片中传感器噪声带来的细粒度纹理。
这意味着:
• 不需要识别"图片内容是什么",只需要捕捉纹理层面的差异
• 模型不需要很深——浅层卷积本来就擅长提取纹理特征
• 这也是为什么 2 层卷积就能达到不错的准确率
如果理解到这一层,可以做一个额外尝试:对输入图片做高通滤波(比如减去均值模糊后的版本),把高频信息放大再喂给 CNN。不增加网络层数,但可能帮助模型更快学到真假差异。
容易踩的坑
坑 1疯狂加层反而降分
直觉"模型越大越准",堆了 4、5 个卷积层。准确率可能确实高了一点,但复杂度分被拉低,总分反而不如简单模型。做这道题之前,先把评分公式的表格自己算一遍 🧮
坑 2线性层的输入维度算错
输入: 3 × 32 × 32 Conv1 → Pool1: channels × 16 × 16 Conv2 → Pool2: channels × 8 × 8 展平: channels × 8 × 8
如果 padding 没设对或多做了一次池化,尺寸就不是 8×8。调试方法:forward 里加一行 print(x.shape),跑一个 batch 确认每层输出形状。
坑 3注意模型类名
评分系统会按指定类名加载你的模型。类名不对就找不到,判 0 分。提交前仔细看题目要求的类名。
坑 4测试时忘了 model.eval()
不调用 model.eval(),BatchNorm 会继续用当前 batch 的统计量,Dropout 也不会关闭,测试结果不稳定。
区分度在哪
• 有没有认真算过评分公式。算过的人会选 2 conv + 1 linear,没算过的人会堆层数
• 有没有意识到通道数、BatchNorm 是免费的。同样的层数,16 通道裸 CNN 和 128 通道 + BatchNorm + 数据增强,准确率可以差出好几个百分点
• 有没有理解真假图片的差异在纹理而不在内容。理解这一点的人会在预处理上做文章,而不是一味加深网络