第 28 天 · 从零手写卷积网络,算清每一个维度
一句话
CS231n 是斯坦福 2015 年开设的深度学习视觉课,由李飞飞和 Karpathy 首批主讲。它从 k 近邻和线性分类器讲起,一直讲到卷积网络、训练技巧和经典架构,并要求学生用 numpy 手写前向传播和反向传播。
论文讲了什么
CS231n 不是论文,而是一门课程,包括讲义、课件、录像和 3 次编程作业。课程目标是让学生能从零实现、调试和训练卷积神经网络,而不只是会调用框架。本日的核心阅读材料是公开讲义 cs231n.github.io。
讲义分为 3 个模块:
- 神经网络基础:图像分类任务和数据驱动方法,k 近邻,线性分类器(SVM 损失和 softmax 损失),随机梯度下降,用计算图做反向传播。
- 训练神经网络:激活函数、权重初始化、批归一化、正则化与 dropout、梯度检查、学习率调整、超参数搜索。
- 卷积网络:卷积层、池化层和全连接层,层尺寸的设计规律,经典架构案例,迁移学习,可视化网络学到的特征。
卷积层一节最值得精读。它解释了两个关键设计:
- 局部连接:每个神经元只连接输入的一小块区域。
- 参数共享:同一个滤波器在所有空间位置复用。
讲义用 AlexNet 第一层算了一笔账。输入是 227×227×3,用 96 个 11×11 的滤波器,步长 4,输出 55×55×96,共 290,400 个神经元。每个神经元有 363 个权重和 1 个偏置。如果不共享参数,总参数超过 1 亿。共享之后,只剩 34,944 个参数。
输出尺寸由一个公式决定:(W − F + 2P) / S + 1。W 是输入宽度,F 是滤波器大小,P 是填充,S 是步长。结果不是整数时,这组超参数就不合法。
讲义还介绍了 im2col 技巧:把每个感受野拉成矩阵的一列,卷积就变成一次大矩阵乘法,可以调用高度优化的 BLAS 库。代价是内存,因为重叠区域的像素被复制了多次。
架构案例部分逐层计算了 VGGNet:总共约 1.38 亿参数,一张图前向传播约需 93 MB 内存。大部分内存花在前几层卷积,大部分参数集中在第一个全连接层,约 1.03 亿个。
3 次作业把这些知识落到代码:
- 作业 1:实现 k 近邻、SVM、softmax 分类器和两层神经网络。
- 作业 2:实现全连接网络、批归一化、dropout 和卷积网络。
- 作业 3:用循环网络生成图像描述,并做网络可视化。
关键概念
- 卷积层(Convolutional layer)
- 用一组小滤波器在输入上滑动,每个位置计算点积,得到特征图。局部连接和参数共享让它的参数量远少于全连接层。
- 感受野(Receptive field)
- 一个神经元在输入图像上能“看到”的区域。叠 3 层 3×3 卷积,感受野等于 1 层 7×7 卷积,但参数更少,非线性更多。
- 池化层(Pooling layer)
- 在小窗口内取最大值来缩小空间尺寸。最常用 2×2 窗口、步长 2,丢弃 75% 的激活值。池化层没有可学习参数。
- im2col
- 把每个感受野展开成矩阵的一列,卷积就变成矩阵乘法。它运算速度快,但重叠像素被复制多次,内存占用会成倍增加。
- 批归一化(Batch normalization)
- 对每层的激活按小批量做标准化,再学习一个缩放系数和一个偏移量。它让网络对初始化不敏感,并允许使用更大的学习率。
- 迁移学习(Transfer learning)
- 先在 ImageNet 这类大数据集上预训练,再把网络当作特征提取器,或在小数据集上微调。新数据越少、越接近原数据,需要重新训练的层越少。
历史位置:为什么值得读
2012 年 AlexNet(第 8 天)赢得 ImageNet 竞赛,深度学习在视觉领域爆发,但当时几乎没有系统的教材。CS231n 在 2015 年冬季学期首次开课,由李飞飞和她的博士生 Karpathy 主讲,2016 年 Justin Johnson 加入。课程录像和讲义在网上广泛传播,被全世界的自学者使用。
这门课的影响来自“手写一切”的教学方式。学生要用 numpy 写出每一层的前向传播和反向传播,再用数值梯度检查结果。Karpathy 的观点是:反向传播是一个会“泄漏”的抽象,不理解它,就无法调试梯度消失、ReLU 死亡这类问题。这种风格后来延续到他的 micrograd 项目和 “Neural Networks: Zero to Hero” 视频系列。
在清单里,本课程把第 8–12 天串成一个整体。AlexNet(第 8 天)和 ResNet(第 9–10 天)是讲义里的架构案例。扩张卷积(第 11 天)和 dropout(第 12 天)在讲义里都有对应小节。读完本课程,再回看这几篇论文,会更清楚它们各自改进了哪一环。
学前准备
- 熟悉 numpy 的广播、切片和矩阵乘法
- 懂偏导数和链式法则
- 先看第 8 天 AlexNet,知道卷积网络解决什么问题
- 知道训练集、验证集和测试集各自的用途
在原仓库怎么学
- README.md:看本日的内容安排和文件清单。
- setup.py:运行它,检查依赖是否可用。
- implementation.py:按顺序读。先读 conv_forward_naive,再读 im2col 和 conv_forward_im2col,然后读 pool_forward、relu、fc_forward、softmax 和 cross_entropy_loss。最后读 SimpleCNN 类和 count_conv_params。
- data/README.md:了解本日使用的 CIFAR-10 数据集。
- train_minimal.py:用 SGD 在 CIFAR-10 上训练 SimpleCNN。首次运行会自动下载数据集。
- visualization.py:画第一层滤波器、各层激活图和空间尺寸的逐层变化。
- notebook.ipynb:在笔记本里逐步运行上面的流程。
- exercises/:做 5 个练习,依次是嵌套循环版卷积前向、最大池化的前向和反向、计算各层输出尺寸、统计网络参数量、特征可视化。答案在 exercises/solutions/。
做完仓库练习后,建议去 cs231n.github.io 完成官方作业 2 的卷积网络部分。官方作业要求写反向传播并做梯度检查,覆盖面更完整。
今天再看
CS231n 的方法论仍然有效,部分技术内容已经过时。
- 仍然有效:手写前向和反向、梯度检查、先在小数据上过拟合、盯住损失曲线。这些调试习惯对训练 Transformer 同样适用。Karpathy 2019 年的博客《A Recipe for Training Neural Networks》把它们整理成了一份清单。
- 部分被取代:2020 年的 Vision Transformer(ViT)把图像切成 16×16 的小块当作 token,证明数据足够多时可以不用卷积。现在多模态大模型的图像编码器大多基于 Transformer。不过 2022 年的 ConvNeXt 显示,按 Transformer 的经验改造卷积网络,性能可以追平。
- 课程本身在演进:课程现名 “Deep Learning for Computer Vision”,2026 年春季仍在开课,内容已加入注意力机制和生成模型。
容易卡住的地方
- 维度对不上:卷积输入通常按 (N, C, H, W) 排列。每写一层,先在纸上用 (W − F + 2P) / S + 1 算出输出形状,再写代码,并用 assert 检查形状。
- 池化反向传播写错:最大池化的梯度只流向窗口内最大值所在的位置,其余位置为 0。前向时要记录最大值位置,反向时按这个掩码分配梯度。
- 用 naive 卷积训练:4 层嵌套循环的 Python 卷积很慢。只用它在小输入上验证正确性,训练时换成 im2col 版本,并比较两者输出是否一致。
- 参数量算错:每个卷积滤波器有 F × F × C_in 个权重,再加 1 个偏置。最常漏掉的是输入通道数 C_in 和偏置。用 AlexNet 第一层的 34,944 校验你的计数器。
延伸阅读
- 原论文:CS231n: Convolutional Neural Networks for Visual Recognition
- 原仓库第 28 天目录:28_CS231n/ —— 代码、笔记本、练习与答案(英文)
- CS231n 讲义:Convolutional Neural Networks —— 本日最该精读的一节讲义
- A Recipe for Training Neural Networks —— Karpathy 的训练调试实战清单
- An Image is Worth 16x16 Words(ViT) —— Transformer 进入视觉领域的转折点