karpathy/lecun1989-repro

GitHub: karpathy/lecun1989-repro

使用 PyTorch 从零复现 LeCun 1989 年反向传播手写数字识别论文中的早期卷积神经网络架构。

Stars: 776 | Forks: 90

# lecun1989-repro ![teaser](https://static.pigsec.cn/wp-content/uploads/repos/cas/68/68fe0de5c7115607ae904a0aa26aa69a42a84ad79591e274b88f0f62038aa250.png) 这份代码尝试复现 Yann LeCun 等人 1989 年的论文:[Backpropagation Applied to Handwritten Zip Code Recognition](http://yann.lecun.com/exdb/publis/pdf/lecun-89e.pdf)。据我所知,这是最早使用反向传播训练的神经网络在实际生活中的应用(距今已有 33 年)。 #### 运行 由于我们没有论文中使用的确切数据集,我们采用 MNIST 并从中随机挑选样本,生成一个近似的数据集。该数据集仅包含 7291 个训练数字和 2007 个测试数字,且大小仅为 16x16 像素(标准 MNIST 为 28x28)。 ``` $ python prepro.py ``` 现在我们可以尝试复现这篇论文了。原始网络训练了 3 天,但我(Apple Silicon M1)的 MacBook Air 在 33 年后只需大约 90 秒就能搞定。(非模拟的 arm64,但仅使用 CPU,我不认为 PyTorch 和 Apple M1 现在已经是最好的朋友了,但无论如何依然有大约 3000 倍的加速)。既然我们已经运行了 prepro(预处理),我们现在可以运行 repro(复现)了!(哈哈): ``` $ python repro.py ``` 运行后会打印(在第 23 次,即最后一轮 pass): ``` eval: split train. loss 4.073383e-03. error 0.62%. misses: 45 eval: split test . loss 2.838382e-02. error 4.09%. misses: 82 ``` 这与论文报告的数字接近,但并不完全相同。为了完全吻合论文的结果,我们期望得到以下输出: ``` eval: split train. loss 2.5e-3. error 0.14%. misses: 10 eval: split test . loss 1.8e-2. error 5.00%. misses: 102 ``` 我估计这种差异主要来源于训练数据集本身。我们仅仅是使用 33 年后的今天所拥有的数据(MNIST)模拟了原始数据集。论文中还有许多其他细节没有详细说明,因此我也不得不进行一些猜测(见下文注释)。例如,H1 和 H2 层之间的具体稀疏连接结构并未描述,论文只是说输入是“根据一种此处不予讨论的方案选择的”。另外,论文使用了一种“使用 Hessian 的正对角近似的特殊版本 Newton 算法”,但在这个实现中我只使用了简单的 SGD,因为它明显更简单,而且根据论文的说法,“并不认为该算法能极大提升学习速度”。无论如何,我们得到的数字在数量级上是相似的…… #### 注释 我对论文记录的笔记如下: - 7291 个数字用于训练 - 2007 个数字用于测试 - 每张图像是 16x16 像素的灰度图(非二值图) - 图像被缩放到 [-1, 1] 范围 - 网络有三个隐藏层 H1 H2 H3 - H1 是 5x5、步幅为 2 的卷积,具有 12 个平面。固定 padding 为 -1。 - 并非“标准”做法:神经元之间不共享偏置!(包括在同一个特征平面内) - H1 有 768 个单元 (8\*8\*12),19,968 个连接 (768\*26),1,068 个参数 (768 个偏置 + 25\*12 个权重) - 并非“标准”做法:H2 的所有单元都从 5x5、步幅为 2 的卷积中提取输入,但每个单元只连接到 12 个平面中不同的 8 个 - H2 包含 192 个单元 (4\*4\*12),38,592 个连接 (192 个单元 * 201 条输入线),2,592 个参数 (12 * 200 个权重 + 192 个偏置) - H3 有 30 个与 H2 全连接的单元。因此有 5790 个连接 (30 * 192 + 30) - 输出层有 10 个与 H3 全连接的单元。因此有 310 个权重 (30 * 10 + 10) - 总计:1256 个单元,64,660 个连接,9760 个参数 - 所有单元均使用 tanh 激活(包括输出单元!) - 输出层的权重被设定在准线性范围内 - 损失函数:均方误差 (MSE) - 权重初始化:在 U[-2.4/F, 2.4/F] 范围内的随机值,其中 F 为 fan-in。“倾向于使总输入保持在 sigmoid 的工作范围内” - 训练 - 样本以固定的顺序输入 - 每次只对单个样本进行 SGD - 使用 Hessian 正对角近似的特殊版本 Newton 算法 - 训练共进行了 23 轮全量数据遍历 (pass),每轮结束后测量训练和测试误差。总共展示 167,693 次 (23 * 7291) - 最终误差:训练集 2.5e-3,测试集 1.8e-2 - 误分类率:训练集上 0.14%(10 个错误),测试集上 5.0%(102 个错误)。 - 计算: - 在 SUN-4/260 工作站上运行 - 数字信号协处理器: - 256 KB 局部内存 - fp32 下峰值性能为 12.5M MAC/s(即 25MFLOPS) - 训练耗时 3 天 - 吞吐量为 10-12 个数字/秒,“主要受限于归一化步骤” - 对于已归一化的数字,吞吐量为 30 个数字/秒 - “我们已经成功地将反向传播学习应用于一个大型、现实世界的任务中” **未解决的问题:** - 论文中没有描述 H2 到 H1 的 12 -> 8 连接……我将假设它采用了一种合理的块状结构连接 - 不清楚到底什么是“MSE 损失”。是否为了简化梯度计算而加入了 1/2 的缩放因子?我将假设没有。 - 学习率是多少?我将通过手动扫描来确定最佳学习率。 - 是否使用了学习率衰减?文中未提及,我假设没有。 - 是否使用了权重衰减?未提及,假设没有。 - PDF 中是否存在一个排版错误,即在权重初始化中 fan-in 应该有一个平方根?该 PDF 的格式有点乱。我假设是有的。 - 论文并未说明,但具体的目标值是什么?假设对于正负样本它们分别是 +1/-1,因为输出单元也使用了 tanh…… 关于权重初始化难题的另一点说明。例如,“Kaiming 初始化”是: ``` a = gain * sqrt(3 / fan_in) ~U(-a, a) ``` 对于 tanh 神经元,推荐的 gain 为 5/3。因此,我们得到的 `a = sqrt(3) * 5 / 3 * sqrt(1 / fan_in) = 2.89 * sqrt(1 / fan_in)`,这与论文中的做法非常接近(gain 为 2.4)。因此,如果最初的研究确实使用了平方根,只是 PDF 格式错了,那么(现代的)Kaiming 初始化和最初使用的初始化是非常接近的。 #### 待办事项 - 利用 33 年时间积累的知识对网络进行现代化改造。 - 加入我那粗糙的超参数扫描代码,以便潜在地调整学习率
标签:Apex, PyTorch, 人工智能, 凭据扫描, 图像分类, 机器学习, 深度学习, 用户模式Hook绕过, 神经网络, 计算机视觉, 论文复现, 逆向工具