🤖 深度学习基础与 LeNet 实战

写给数字 IC 工程师的 AI 通识:从「神经元是什么」到亲手跑通 1998 年的 LeNet-5,最后落到一张「算子统计表」—— 让你带着 NPU 硬件设计的眼光去读每一层网络,直接衔接 08-07 体系结构与 08-11 RTL 实战。
AI 通识 LeNet-5 卷积 / 池化 / 全连接 反向传播 PyTorch 实战 NPU 算子视角
🎯 本页学习目标
1. 能说清机器学习 / 深度学习 / 大模型三者的关系,并从「算法-硬件协同」角度讲出 IC 工程师为什么要懂算法
2. 能默写卷积的输出尺寸、参数量、MAC 计算量三个公式,并对 LeNet 每一层算出具体数值
3. 能用链式法则手推两层网络的梯度,说清梯度消失的数学来源(sigmoid 导数 ≤ 0.25 连乘)
4. 能跑通约 60 行的 PyTorch 版 LeNet 训练 MNIST 全流程,并产出面向 NPU 的「逐层算子统计表」
建议用时:约 100 分钟(通识 30 分钟 + 推公式 30 分钟 + 跑代码 25 分钟 + 自测 15 分钟)

1 AI 通识:从机器学习到大模型,IC 工程师为什么要懂它?

1.1 AI 与大模型基础认知:一张同心圆看懂三层关系

是什么:三者的关系是一组同心圆 —— 人工智能(AI) ⊃ 机器学习(ML) ⊃ 深度学习(DL) ⊃ 大模型(LLM)。AI 是"让机器表现出智能"这个大目标(含规则专家系统、搜索等非学习类方法);机器学习是其中"不从规则编程、而从数据中自动归纳规律"的方法族;深度学习是机器学习中"用多层神经网络自动学习特征"的分支;大模型(如 GPT、LLaMA)则是深度学习中"参数量巨大(十亿级以上)、靠海量数据预训练 + Transformer 架构"的一类模型 —— 2017 年 Transformer 论文《Attention Is All You Need》之后,几乎所有大模型都长在同一个骨架上。LeNet(1998)位于最内圈的最里层起点:它就是深度学习最早的实用化成功之一。

为什么 IC 工程师必须懂:因为 NPU 设计的每一个关键决策都来自算法的数学形状,这叫算法-硬件协同设计 —— ①算子种类决定数据通路:卷积的滑窗复用催生了脉动阵列与 line buffer,池化催生了比较树,激活催生了查找表/分段线性电路(详见 08-07);②数值精度决定位宽与数据类型:算法侧发现 INT8 量化掉点可接受(08-08),硬件才敢用 8bit MAC 把能效翻 4 倍;③访存模式决定存储层级:权重被反复复用 → 权重常驻 SRAM,激活流式生产消费 → 行缓冲;④模型演进决定指令集寿命:如果算法明年后主流换成你的硬件跑不动的算子(比如动态形状的注意力),这颗芯片的商业生命就结束了。不懂算法的 NPU 工程师,只能被动实现别人给的 Spec;懂算法的,能参与决定 Spec。

怎么做(给自己建一张图):把"AI 大家族"记成下面这张表,每个概念都问一句"它长在哪一层、硬件上意味着什么":

概念一句话定义代表对硬件的关键含义
人工智能 AI让机器表现出类人智能的总目标专家系统、搜索、机器学习方法不固定,只有学习类方法能被 NPU 加速
机器学习 ML从数据中自动归纳规律,而非手工写规则线性回归、SVM、决策树、神经网络传统 ML 算子碎片化,通用 CPU 往往更划算
深度学习 DL多层神经网络逐层自动提取特征LeNet、AlexNet、ResNet算子高度规则(卷积/矩阵乘),专用加速的空间来源
大模型 LLM十亿级参数 + 预训练 + TransformerGPT、LLaMA、Qwen矩阵乘为主但访存密集,推理受带宽约束;端侧靠量化压缩

面试怎么问:①"讲讲机器学习、深度学习、大模型的关系?"(同心圆 + 各自的判断标准:是否从数据学习 / 是否多层神经网络 / 是否大规模预训练);②"你是硬件工程师,为什么要懂算法?"(答算子决定数据通路、精度决定位宽、访存决定存储层级三条 + 举一个 INT8 量化的例子);③"Transformer 和 CNN 对 NPU 设计各意味着什么?"(CNN 滑窗规则适合空间阵列;注意力是动态权重矩阵乘,QKV 访存大,KV Cache 是带宽瓶颈)。

易错点:①"深度学习 = AI"错,AI 里还有非学习的经典方法;②"大模型是新架构"错,大模型主要是 Transformer 的规模化,不是全新计算原理,底层仍是矩阵乘与非线性;③ IC 视角常犯的错是"把算法当黑盒只拿 netlist 式的算子列表" —— 不理解算法意图,做量化/稀疏/融合决策时会掉坑(如对 softmax 前的 logits 量化会直接掉点)。

1.2 机器学习三大范式:监督、无监督、强化学习

是什么:按"数据带什么标签、反馈是什么"划分的三种学习方式。监督学习(supervised):训练数据自带标准答案(标签),模型学"输入→输出"的映射 —— 图像分类(MNIST 每张图都标了 0~9)、回归(房价预测);无监督学习(unsupervised):数据没有标签,模型自己找结构 —— 聚类(用户分群)、降维(PCA)、自编码器;强化学习(reinforcement):没有标准答案,只有环境反馈的奖励信号,智能体通过"试错 + 延迟奖励"学策略 —— AlphaGo、机器人步态策略(本站 04-04 的 RL 训练就是它)。

为什么重要:三种范式决定了训练负载与部署负载的巨大差异,直接影响芯片定位 —— 监督学习的训练是"前向 + 反向 + 更新"三段,算力需求约为推理的 2~3 倍;推理只有前向,可以极致量化压缩。端侧 NPU 几乎只做推理,训练发生在数据中心;而强化学习因为要在"交互回路"里高频推理 + 训练,常需要"训推一体"的近端算力 —— 机器人本体上 RL 策略的部署就是纯推理(策略网络 forward 一次 = 一步动作)。

维度监督学习无监督学习强化学习
数据形态(输入, 标准答案) 成对只有输入,无标签状态 + 奖励信号(可能延迟)
学习信号预测与标签的损失(交叉熵/MSE)数据内在结构(距离/重构误差)累计奖励最大化
典型任务分类、回归、检测聚类、降维、异常检测围棋、机器人控制、自动驾驶决策
代表算法LeNet、ResNet、Transformer 微调K-means、PCA、自编码器Q-learning、PPO(04-04 步态训练用)
硬件负载特点训练 3 段循环,算力大任务碎片化,多为 CPU/传统算法训练环境仿真重,部署端只跑策略推理
机器人中的例子识别零件缺陷、手势分类点云分割、运动模式发现人形机器人学会走路、抓取
💡 记忆锚点:监督学习像"刷题对答案",无监督像"自己整理笔记找规律",强化学习像"学骑车 —— 没人告诉你每块肌肉怎么发力,摔了(负奖励)骑稳了(正奖励)自己悟"。LeNet 属于最典型的监督学习:6 万张带标签的手写数字,学"像素→类别"的映射。

面试怎么问:①"三大范式各举一个例子,说明学习信号的区别";②"为什么端侧 NPU 只做推理?"(部署负载只有前向;反向传播需要保存中间激活做梯度,内存翻数倍;训练精度要求高,INT8 训练掉点难控);③"机器人步态用强化学习训练后,部署到机载芯片上跑的是什么?"(只有策略网络的前向推理,观测→动作,通常需要量化到 INT8/FP16 满足实时性)。

易错点:①把"无监督"理解成"没有监督学习准" —— 无监督只是学习信号不同,不是降级版;②半监督/自监督(大模型预训练本质是自监督:用文本后半句预测前半句式地造标签)是混合形态,面试提一句加分;③强化学习的"奖励"不是标签,别混着说。

2 神经元与多层感知机:一切网络的积木

2.1 神经元:一次乘累加 + 一个非线性

是什么:人工神经元是对生物神经元的极度简化 —— 输入 x₁…xₙ 加权求和,加偏置 b,再过一个非线性激活函数 f 输出:

z=\sum_{i=1}^{n} w_i x_i + b = \boldsymbol{w}^{\top}\boldsymbol{x} + b,\qquad a=f(z)

权重 w 表示"这条输入有多重要",偏置 b 表示"这个神经元有多容易被激活"(相当于阈值的负数)。注意这个运算对硬件工程师应该一眼亲切:乘累加(MAC)+ 查表/比较,正是 DSP/NPU 里最基本的单元 —— 一个神经元 = 一个点积,一层网络 = 一次矩阵-向量乘(GEMV),一批样本 = 矩阵-矩阵乘(GEMM)。深度学习的全部计算,展开到最底层就是这两件事(08-07 页的 GEMM 视角)。

为什么需要非线性激活:如果不用激活函数(或用线性激活 f(z)=z),n 层网络叠起来等价于一层:

a^{(2)}=W^{(2)}\big(W^{(1)}\boldsymbol{x}+\boldsymbol{b}^{(1)}\big)+\boldsymbol{b}^{(2)}=\big(W^{(2)}W^{(1)}\big)\boldsymbol{x}+\big(W^{(2)}\boldsymbol{b}^{(1)}+\boldsymbol{b}^{(2)}\big)

线性变换的复合仍是线性变换 —— 叠 100 层的表达能力等于叠 1 层,连"异或(XOR)"这种最简单的非线性可分问题都学不会。非线性激活是把"线性代数"升级为"万能函数拟合器"的开关(理论上,带非线性的足够宽网络可逼近任意连续函数,即通用近似定理)。对 NPU 的启示:激活函数无处不在、每层必有,所以 NPU 必须内置高效的激活单元 —— ReLU 是一条 max 指令,sigmoid/tanh 常用分段线性近似或查找表实现。

怎么做(记住三个经典激活函数):

\text{Sigmoid:}\ \sigma(z)=\dfrac{1}{1+e^{-z}},\qquad \text{Tanh:}\ \tanh(z)=\dfrac{e^{z}-e^{-z}}{e^{z}+e^{-z}},\qquad \text{ReLU:}\ \max(0,z)
激活函数输出范围导数优点缺点硬件实现
Sigmoid(0, 1)σ(1−σ),最大 0.25可解释为概率,平滑梯度消失(导数≤0.25);含指数运算;输出非零中心查表/分段线性,指数电路贵
Tanh(−1, 1)1−tanh²,最大 1零中心,收敛快于 sigmoid仍有梯度消失;同样含指数查表/分段线性
ReLU[0, +∞)正半轴恒 1,其余 0不衰减梯度,计算极简,稀疏激活负半轴"死区":神经元可能永久不激活(dying ReLU)一个 max 比较器,NPU 最爱

LeNet 诞生时(1998)主流用的是 sigmoid/tanh;现代复现(含本页 PyTorch 实战)几乎都换成 ReLU —— 这也是 08-07 页讲"为什么 NPU 激活单元按 ReLU 优化、sigmoid 用近似器"的历史脉络。

面试怎么问:①"为什么需要激活函数?去掉会怎样?"(线性复合仍是线性,XOR 反例);②"ReLU 相比 sigmoid 好在哪?"(导数恒 1 不衰减、计算只是一个 max、稀疏激活带来计算/存储节省 + 死区缺点);③"从硬件角度比较 ReLU 与 sigmoid 的实现代价"。

易错点:①把"神经元"过度拟人化 —— 它只是一个点积加非线性,没有任何智能;②混淆激活函数与其导数(sigmoid 导数最大 0.25,不是输出最大 0.25);③LeNet 原论文用的是 S2/S4 下采样层里的 tanh 式 squashing,现代 PyTorch 版普遍改 ReLU,读论文和读代码时别打架。

2.2 多层感知机(MLP):从单神经元到"层"的组织

是什么:多层感知机 = 神经元按层组织:输入层 → 若干隐藏层 → 输出层,每层神经元与下一层全连接(dense/linear),信息单向从输入流到输出。数学上每层就是一次矩阵乘加激活:

\boldsymbol{a}^{(l)}=f\big(W^{(l)}\boldsymbol{a}^{(l-1)}+\boldsymbol{b}^{(l)}\big),\qquad l=1,2,\dots,L

为什么:单神经元只能画一条分界线;多层叠加后,第一层学简单特征、第二层在特征之上组合出更复杂的特征 —— "逐层组合"是深度学习所有结构(包括 LeNet)的共同思想。MLP 的矩阵表达直接对应硬件上的 GEMM 引擎,这也是它对 IC 工程师最友好的地方。

怎么做(数一个 MLP 的参数):一层全连接的权重矩阵是 W∈ℝ^{m×n}、偏置 b∈ℝ^m,参数量 = m×n+m;MAC 数(一个样本)= m×n。例如输入 784 维(MNIST 拉平)、隐藏 128、输出 10 的 MLP:参数 = 784×128+128 + 128×10+10 = 101,770,比 LeNet 还多 65%,但精度反而更低 —— 卷积的权值共享 + 局部连接能用更少参数换更高精度,这正是下一节的主角。

面试怎么问:①"MLP 每层的计算在硬件上对应什么操作?"(GEMV/GEMM + 激活);②"全连接层参数量怎么算?给一个 784-128-10 的 MLP 估算参数量"(10.2 万);③"为什么图像任务用 MLP 不划算?"(拉平丢失空间结构 + 全连接参数爆炸,没有平移不变性)。

易错点:①"输入层"通常不计入层数与参数(它没有权重),3 层 MLP 指两个隐藏层 + 输出层的写法都存在,面试先统一口径;②参数量与计算量别混:参数是存储(权重个数),计算量是 MAC 次数,MLP 里二者接近,卷积里计算量远大于参数量(复用);③不要以为"深度"必须很深 —— LeNet 只有 2 个卷积层 + 3 个全连接层,7 层网络(含输入输出)在 1998 年已经是"deep"。

3 CNN 三大算子:卷积、池化、全连接

3.1 卷积:滑窗、参数共享与三个必背公式

是什么:卷积层用一个可学习的小窗口(卷积核,如 5×5)在输入特征图上按步长滑动,每个位置做一次"窗口内对应元素相乘再求和",得到输出特征图的一个像素。三个关键机制:①局部连接 —— 每个输出只看输入的一小块(图像局部相关性);②权值共享 —— 同一个核扫遍全图(平移不变的特征检测器,参数量从"每像素一组权重"降到"每组核一组权重");③多通道堆叠 —— 输出图的数量 = 卷积核组数,每组核又能看到输入的全部通道,层层组合出边缘→纹理→部件→整体的层次特征。

为什么(NPU 视角):卷积把"图像知识"写成了极规则的访存与计算模式 —— 窗口固定、步进固定、内积固定长度。规则 = 可以流水线化、可以空间展开成阵列、可以把窗口数据在片上反复复用。08-07 页的脉动阵列、im2col+GEMM、line buffer,全部建立在这一节的三个公式上。

怎么做(必背三公式,考 NPU 岗必出):

\text{输出尺寸:}\quad H_{out}=\left\lfloor\dfrac{H+2P-K}{S}\right\rfloor+1
\text{参数量:}\quad \#W = K\times K\times C_{in}\times C_{out} + C_{out}\quad(\text{含每组一个偏置})
\text{计算量:}\quad \#MACs = H_{out}\times W_{out}\times C_{out}\times\big(K\times K\times C_{in}\big)\quad(\text{每个输出像素 = 一次 } K^2 C_{in}\text{ 点积})

其中 K 卷积核边长、P 填充、S 步长、C_in/C_out 输入/输出通道数。两个例子现场算一遍:①LeNet C1:K=5,P=0,S=1,C_in=1,C_out=6,H=32 → H_out=28;参数 = 5×5×1×6+6 = 156;MAC = 28×28×6×25 = 117,600。②ResNet 的 3×3 卷积一层:K=3,P=1,S=1 → 尺寸不变("same"卷积),这是现代网络保持分辨率的标准手法,而 LeNet 时代靠手工算尺寸。验证:K=3,P=1,S=1 时 (H+2−3)/1+1 = H ✓。

相关概念感受野(receptive field):输出某像素对应的输入区域大小。两层 3×3 堆叠感受野 5×5,但参数只有 2×9=18 < 25,且多了两次非线性 —— "小核堆深"胜过"大核堆浅",这是 VGG 之后的共识。感受野递推:

RF_{l}=RF_{l-1}+\big(K_{l}-1\big)\times\prod_{i=1}^{l-1}S_i

多通道怎么卷:一组核的形状是 K×K×C_in(立方体),在 C_in 个通道上同时滑动求和,压成 1 张输出图;C_out 组这样的核 → C_out 张输出图。硬件含义:卷积本质是"K²C_in 长度的点积",与 GEMM 的内层完全同构 —— 这就是 im2col(把滑窗展开成矩阵的一列)能把一切卷积变 GEMM 的原因,也是 08-07 页数据通路的起点。

输入特征图 5×5 滑动窗口 stride=1 卷积核 3×3(共享权重) w₁w₂w₃ w₄w₅w₆ w₇w₈w₉ 9 次乘 1 次累加 输出特征图 3×3 y y = Σ wᵢ·xᵢ (5−3)/1+1 = 3 → 9 个输出位置 × 每位置 9 次乘加 = 81 MAC
图① 卷积运算示意:3×3 卷积核在 5×5 输入上以 stride=1 滑动,每个停留位置做 9 次乘加产出 1 个输出像素;同一组权重 w₁…w₉ 扫遍全图(权值共享),输出尺寸由 (H+2P−K)/S+1 决定

面试怎么问:①"现场推卷积输出尺寸公式,并算 32×32 输入、5×5 核、stride 1、no pad 的输出"(28×28);②"卷积参数量和计算量为什么差这么多?"(权值共享:参数只有一组核,计算却要每个位置都做);③"im2col 是什么?为什么能把卷积变成 GEMM?"(把每个滑窗位置展开成矩阵一列,卷积核展开成行,内积即输出 —— 08-07 页展开);④"感受野怎么算?两层 3×3 与一层 5×5 谁好?"。

易错点:①输出尺寸公式忘了先加 2P 再减 K,或除 S 不取整;②参数量漏掉偏置 C_out,或多算"每个输出位置一份权重"(那是全连接的算法,卷积共享);③MAC 与 FLOP 混用:1 MAC = 1 乘 1 加 = 2 FLOPs,对比论文数据先统一口径;④多通道卷积的核是 K×K×C_in 三维,不是"每通道独立输出一张图再相加"的误解 —— 是"先逐通道乘加再跨通道累加",两种顺序等价但硬件遍历顺序不同。

3.2 池化:用"降分辨率"换"鲁棒性"

是什么:池化(subsampling/downsampling)在特征图上按 2×2 窗口(典型)取最大值(max pooling)平均值(average pooling),把特征图边长减半、像素数减为 1/4。LeNet 的 S2/S4 就是 2×2 平均池化(当年叫"下采样"),现代网络多用 max。池化没有任何可学习参数,也没有跨通道混合 —— 每个通道独立做。

为什么:降维省算力:下一层卷积的输出位置数按面积缩 4 倍,MAC 同步缩 4 倍;②平移不变性:目标平移一两个像素,max 池化大概率取到同一个值,分类结果稳定 —— 手写数字写歪一点也能认;③扩大感受野:分辨率减半后,后续同一个 5×5 核覆盖的"原始输入区域"翻倍。

\text{MaxPool}_{2\times2}:\ y=\max(x_{1},x_{2},x_{3},x_{4})\qquad\text{(窗口无重叠,}S=K=2\text{,尺寸 } H/2\times W/2\text{)}

怎么做(硬件视角):池化单元 = 4 输入比较树(max)或 4 输入加法树 + 移位(avg,除以 4 就是右移 2 位),无权重、无乘法器,是 NPU 里最便宜的功能单元,常与卷积输出级联成"卷积-池化融合流水线"(一次 SRAM 写入都没有,详见 08-07 的算子融合)。LeNet 的 S2 严格说是"2×2 平均 + 乘系数加偏置"(当年带 2 个可训练参数),现代实现直接 pooling 不带参数。

面试怎么问:①"max 与 average 池化怎么选?"(分类特征保留最显著响应用 max;LeNet 时代/量化友好的平滑场景用 avg,现代还有 stride 卷积替代池化的做法);②"池化层有参数吗?有梯度吗?"(无参数;有梯度 —— max 把梯度路由给最大值那个输入,avg 平均分配);③"为什么说池化带来平移不变性?"。

易错点:①池化没有权重,数参数量时 S2/S4 贡献为 0(现代写法);②池化不跨通道 —— C_out 不变,变的是 H×W;③stride=2 的卷积也能降维,现代网络常"卷积代池化",别把池化当成唯一下采样手段。

3.3 全连接层:与卷积的等价关系

是什么:全连接层(FC/Linear/dense)把输入向量 x∈ℝⁿ 变换为输出向量 y∈ℝᵐ:

y=W\boldsymbol{x}+\boldsymbol{b},\qquad W\in\mathbb{R}^{m\times n},\quad \#W=m\times n+m,\quad \#MACs=m\times n\ (\text{每样本})

它"看见全部输入",最适合做"把前面提好的特征综合起来做最终判决" —— LeNet 的 C5、F6、输出层就是三级全连接,把 120 维特征逐级压到 84 维再到 10 类。

为什么 FC 与卷积"是一家人":FC 是全窗口的卷积:当卷积核尺寸 K 等于输入特征图尺寸时,输出 1×1×C_out,数学上与 FC 完全等价 —— LeNet 的 C5 层在论文里写作卷积(5×5 卷到 5×5 图上出 1×1),在现代框架里常直接写成 Linear,两者参数量分毫不差(5×5×16×120+120 = 16×5×5×120+120 = 48,120);②反过来,FC 可当全局卷积用:1×1 卷积 = 在通道维做 FC,是现代网络跨通道信息混合的标准手法。硬件视角:FC 层权重矩阵稠密、无复用(每个权重只用一次,权重访存 = MAC 数),数据复用率远低于卷积层 —— 大模型推理的带宽瓶颈就出在 FC/矩阵乘的权重搬运上(08-07/08-08 展开)。

对比项卷积层全连接层
连接方式局部窗口 + 权值共享全连接,每个输入都连
输入结构保留 H×W×C 三维空间结构须先 Flatten 拉平成向量
权重复用一个核复用于所有位置(复用 H_out×W_out 次)无复用,每权重用一次
参数量例子(LeNet C5)5×5×16×120+120 = 48,12016×5×5 → 120 等价 FC:48,120(完全相同)
对平移的敏感度平移不变(池化加强)敏感,特征位置变了权重对不上
硬件形态滑窗 + 点积阵列(可脉动化)纯 GEMM(矩阵乘引擎直接吃)

面试怎么问:①"全连接层和卷积层是什么关系?"(K=输入尺寸的卷积 ≡ FC;1×1 卷积 = 通道维 FC);②"为什么 FC 层是访存瓶颈?"(权重无复用,权重带宽 ≈ MAC 吞吐);③"Flatten 丢掉了什么?"(空间排布信息,只留数值 —— 换来的是与位置无关的综合能力)。

易错点:①Conv2d(16,120,5) 作用在 5×5 特征图上输出 120×1×1,与 Linear(400,120) 等价但权重排布顺序不同(通道优先 vs 拉平优先),迁移 pretrained 权重时踩过坑的人都懂;②FC 参数量巨大:LeNet 61,706 个参数里全连接侧 C5+F6+OUT 占 96%(48,120+10,164+850 = 59,134),这解释了为什么后来 NiN/GAP(全局平均池化)要消灭大 FC 层;③别把"全连接"写成"全边接"或与"fully convolutional(全卷积网络)"混淆 —— 后者恰是"把 FC 换成卷积"的网络。

4 LeNet 结构逐层拆解:尺寸、参数量、计算量全算清

4.1 结构总览与逐层数据:把 32×32 算到 10

是什么:LeNet-5(Yann LeCun 等,1998,《Gradient-Based Learning Applied to Document Recognition》)是第一个大规模商用的卷积神经网络,用于银行 ATM/支票手写数字识别。结构 8 段(含输入输出):

\text{INPUT}\ 32\times32 \rightarrow C1\ 6@28\times28 \rightarrow S2\ 6@14\times14 \rightarrow C3\ 16@10\times10 \rightarrow S4\ 16@5\times5 \rightarrow C5\ 120 \rightarrow F6\ 84 \rightarrow \text{OUT}\ 10
32×32 输入 1ch 5×5v C1 6@28×28 卷积 156参 2×2 S2 6@14×14 池化 无参 5×5v C3 16@10×10 卷积 2416参 2×2 S4 16@5×5 池化 无参 5×5v C5 120 FC 48120参 F6 84 FC 10164参 OUT 10 850参 C1:6 张特征图(只画 3 张示意) C3:16 张(原始论文为不完全连接) 参数量分布(总 61,706): C1 156 C5 48,120(78%) F6+OUT 计算量分布(总 0.42 MMAC): C1 28% C3 58% 读图要点:参数大头在 FC 端(C5 78%),计算大头在卷积端(C3 58%)——存储/计算错位 = 08-07 设计输入
图② LeNet-5 结构图:蓝色 = 卷积(带可学习参数)、橙色 = 池化(无参数)、绿色 = 输出层;下方两条比例条分别是参数量与计算量的层间分布 —— 参数与计算不同头,是 NPU 设计必须分开算的两本账

逐层算账(本页核心表格,请对照图②手工验算一遍):按第 3 节公式逐层代入,全部数值可复算。

类型(K/S/P)输出尺寸参数量MAC 计算量算账过程
INPUT输入 1 通道1×32×3200MNIST 28×28 补边到 32×32
C1卷积 K=5,S=1,P=06×28×28156117,6005×5×1×6+6;(28×28)×6×25
S2池化 2×2(S=2)6×14×140(现代)≈0(比较/加法)每 2×2 取一值,6 通道独立
C3卷积 K=5,S=1,P=016×10×102,416240,0005×5×6×16+16;(10×10)×16×150
S4池化 2×2(S=2)16×5×50(现代)≈0同 S2
C5卷积(≈FC)K=5120×1×148,12048,0005×5×16×120+120;1×1×120×400
F6全连接 120→848410,16410,080120×84+84;120×84
OUT全连接 84→101085084084×10+10;84×10
合计7 层可学习结构61,706416,520(≈0.42 MMAC)乘加各计 1 次;FLOPs 口径 ×2 ≈ 0.83 M
💡 算账口诀:参数量看"核":K×K×C_in×C_out+C_out;计算量看"图":输出图上每个像素做一次 K²C_in 点积。LeNet 三条铁律记牢 —— ①卷积层"计算多参数少"、全连接层"参数多计算少";②池化层零参数;③全模型 0.42 MMAC/样本,这正是 08-07 页"8×8 阵列几千拍跑完 LeNet"周期估算的输入。

面试怎么问:①"背一遍 LeNet 结构,并说每层输出尺寸"(按本节公式现场推,别硬背数字);②"LeNet 参数量与计算量各多少、分布在哪?"(6.17 万 / 0.42 MMAC;参数 96% 在全连接侧,计算 86% 集中在 C1+C3 两个卷积层);③"为什么 LeNet 计算量这么小还重要?"(确立了卷积-池化-FC 范式与权值共享思想,后续 30 年 CNN 都是这个骨架的放大版)。

易错点:①把 61,706 说成 60K 时不加口径 —— 原论文含 RBF 输出层与 S 层可训练系数,数字略有出入,报数先报口径;②C5 在论文里是卷积层(5×5 核卷完 5×5 图),写成 FC 虽等价但要知道来龙去脉;③MAC 与 FLOPs 差 2 倍,不同论文口径不同。

4.2 三个"为什么":32×32、C3 不完全连接、与现代简化版

①为什么输入是 32×32 而 MNIST 是 28×28?LeCun 的两个考虑:一是让感受野"呼吸" —— 5 层卷积/池化后,最外层像素对应的感受野才能覆盖整个数字,关键笔画(如 7 的折角)不会贴边丢失;二是早期字符集是 32×32 规格化输入,与预处理流水线一致。28×28 输入补 2 圈零边(P=2)即成 32×32。②为什么 C3 是 16 张图而不是 6×16=96 组核全连接?原论文用一张手工配置的连接表:每张 C3 特征图只连 S4 前身 S2 的部分通道(如 3 个或 4 个),16 张图对应 60 组核。动机是打破对称性(不同特征图看不同输入组合)+ 削减参数。现代复现一律改成完全连接(16 组核各看全部 6 通道),参数量从 1,516 变 2,416,精度几乎不变 —— 你在 PyTorch 里写的 Conv2d(6,16,5) 就是简化版。③原始与现代版的差异清单:

项目原始 LeNet-5(1998)现代复现/PyTorch 版(本页实战)
激活函数缩放 tanh(squashing,幅值受限)ReLU
池化2×2 平均 + 可训练系数/偏置MaxPool2d/AvgPool2d,无参数
C3 连接连接表,不完全连接(60 组核)完全连接 Conv2d(6,16,5)
输出层84→10 RBF 欧氏距离(模板匹配)Linear(84,10)+ softmax/交叉熵
输入32×32,白底黑字,值域[−0.1,1.175]28×28 输入 + padding=5 亦可,常用 padding=2 直接 28 起步
损失MSE(对 RBF 输出)交叉熵 CrossEntropyLoss
参数量约 60K(含 RBF/S 层系数)61,706(上表现代口径)

面试怎么问:①"为什么 LeNet 输入要 padding 到 32×32?"(感受野覆盖 + 历史预处理规格);②"原始 C3 的不完全连接有什么好处?"(破对称、省参数、特征多样性 —— 类似后来 Group Convolution 的思想雏形);③"你会怎么把 1998 年的 LeNet 改成 2020 年代风格?"(ReLU、max pool、BN、softmax 交叉熵、Adam —— 本页实战代码即答案)。

易错点:①论文、教材、框架三方的"LeNet"细节都不同(d2l 用 28×28 输入 + sigmoid + avg pool),谈结构先声明版本;②C3 连接表数字(60 组核)常被忽略,被问"为什么 16 张图恰好是 16"时答不上;③原始输出层是 RBF 不是 FC,损失是 MSE —— 别把现代写法安到 1998 年头上。

5 数据与学习机制:MNIST、前向传播与损失函数

5.1 MNIST 数据集:深度学习的"Hello World"

是什么:MNIST 是 LeCun 等人 1998 年整理的手写数字数据集,60,000 张训练图 + 10,000 张测试图,每张 28×28 单通道灰度图,标签 0~9 共 10 类。源自美国人口普查局与高中学生的真实手写样本,是 LeNet 论文的基准任务。文件为 IDX 格式(大端二进制:先魔数/维度头,后逐像素 uint8),四个文件:train-images-idx3-ubyte、train-labels-idx1-ubyte、t10k-images-idx3-ubyte、t10k-labels-idx1-ubyte。

怎么做(加载与归一化):现代工程几乎不手解 IDX,用 torchvision 一行加载,download=True 时自动从镜像拉取(torchvision 官方文档见参考来源;原始页面 yann.lecun.com/exdb/mnist/ 目前已停止维护,访问仅剩目录列表,不再作为下载入口)。关键一步是归一化:原始像素 0~255 → ToTensor 缩到 [0,1] → 再按训练集经验均值 0.1307、标准差 0.3081 标准化到零附近:

from torchvision import transforms, datasets
tf = transforms.Compose([
    transforms.ToTensor(),                          # 像素 0~255 → [0,1],形状 (1,28,28)
    transforms.Normalize((0.1307,), (0.3081,)),     # (x−0.1307)/0.3081,零均值单位方差
])
train_set = datasets.MNIST("./data", train=True,  download=True, transform=tf)
test_set  = datasets.MNIST("./data", train=False, download=True, transform=tf)

为什么必须归一化:①不同量纲的输入让损失面呈狭长椭圆,梯度下降在陡方向震荡、缓方向爬行,归一化后损失面更圆,收敛快且稳;②输入零均值 → 第一层权重的梯度分布均衡,配合合适的初始化,各层激活值不会逐层放大/消失(硬件视角:定点/量化部署时,输入范围已知且集中,正是 INT8 量化的前提,详见 08-08)。

面试怎么问:①"MNIST 规模、尺寸、类别数?"(60k/10k、28×28 灰度、10 类,秒答);②"为什么要减均值除方差?"(零均值单位方差 → 损失面圆、收敛稳、利于量化);③"LeNet 为什么还要把 28×28 补成 32×32?"(见 4.2)。

易错点:①把 Normalize 的均值方差写成 (0.5,0.5) 不算错但不精确 —— 0.1307/0.3081 是 MNIST 实测统计值;②混淆"测试集"与"验证集":调参应该用从训练集切出的验证集,MNIST 测试集只许最后汇报,反复"偷看"测试集调参 = 数据泄漏;③IDX 是大端字节序,自己写解析器时用 >iiii 之类的格式串,直接小端读会得到天文数字。

5.2 前向传播:数据在 LeNet 里的一次完整旅行

是什么:前向传播 = 把一张图从输入层推到输出层,逐层做"乘累加 + 激活"。用矩阵/张量语言重述 LeNet(现代版,输入 28×28、C1 用 padding=2 保持尺寸):

\begin{aligned} X&\in\mathbb{R}^{1\times28\times28} &&\xrightarrow{\ \mathrm{Conv}_{6@5\times5}+\mathrm{ReLU}\ }\ && A_1\in\mathbb{R}^{6\times28\times28} \xrightarrow{\ \mathrm{MaxPool}_{2\times2}\ } && A_2\in\mathbb{R}^{6\times14\times14}\\ &\xrightarrow{\ \mathrm{Conv}_{16@5\times5}+\mathrm{ReLU}\ } && A_3\in\mathbb{R}^{16\times10\times10} \xrightarrow{\ \mathrm{MaxPool}_{2\times2}\ } && A_4\in\mathbb{R}^{16\times5\times5} \xrightarrow{\ \mathrm{Flatten}\ } \boldsymbol{v}\in\mathbb{R}^{400}\\ &\xrightarrow{\ \mathrm{FC}_{400\to120}+\mathrm{ReLU}\ } \boldsymbol{h}_1\in\mathbb{R}^{120} \xrightarrow{\ \mathrm{FC}_{120\to84}+\mathrm{ReLU}\ } \boldsymbol{h}_2\in\mathbb{R}^{84} \xrightarrow{\ \mathrm{FC}_{84\to10}\ } \boldsymbol{z}\in\mathbb{R}^{10} \xrightarrow{\ \mathrm{softmax}\ } \boldsymbol{p} \end{aligned}

其中 softmax 把 10 个原始得分(logits)变成概率分布:

p_k=\dfrac{e^{z_k}}{\sum_{j=1}^{10}e^{z_j}},\qquad \sum_k p_k=1

怎么做(逐层用形状说话):调试任何网络的第一个手段就是把每层输出 shape 打出来(本页实战代码里的注释就是 shape 流水账)。每一步的"数学形状→硬件动作"对应关系:Conv = 滑窗点积(乘累加阵列),Pool = 比较树,Flatten = 纯重排(不计算,只改寻址 —— NPU 里就是 DMA/地址生成器的事),FC = 矩阵向量乘。一张 28×28 的图走完全程约 0.42 MMAC,人脑级芯片(NPU 仿真器)毫秒内完成。

面试怎么问:①"给一张 28×28 图,说出 LeNet 每层输出 shape"(背本节链条);②"Flatten 层做了什么计算?"(不做计算,只重排 —— 面试陷阱题);③"softmax 为什么要有指数归一化?"(把任意实数得分变合法概率分布,且保持可导、放大最大者)。

易错点:①logits 与概率不分:交叉熵损失接收的是 logits(框架内部帮你 softmax),重复 softmax 会掉点;②softmax 的数值稳定性:z 很大时 e^z 溢出,工程上先减 max(z)(框架已内置,自己实现要记得);③Flatten 的顺序(通道优先 CHW 拉平)与权重文件的排布必须一致,否则加载权重后精度乱掉。

5.3 损失函数:给"错误"定价 — MSE 与交叉熵

是什么:损失函数把"预测 p 与标签 y 的差距"变成一个标量,训练的全部目标就是让它变小。两个最常用者:

\text{MSE:}\quad L=\dfrac{1}{2}\sum_{k}\big(p_k-y_k\big)^2\qquad\qquad \text{交叉熵:}\quad L=-\sum_{k}y_k\log p_k=-\log p_{\text{正确类}}

(标签 one-hot 时交叉熵只剩正确类那一项 —— 你给正确类的概率越低,罚得越重,对数把"差得多"放大成"罚得多得多"。)

为什么分类用交叉熵:关键在梯度形状。对 softmax 输出 z 求导,两种损失天差地别:

\text{softmax+交叉熵:}\quad \dfrac{\partial L}{\partial z_k}=p_k-y_k\qquad\qquad \text{MSE+sigmoid/softmax:}\quad \dfrac{\partial L}{\partial z}\propto(p-y)\cdot\sigma'(z)

交叉熵的梯度就是"预测减标签",干净且永不饱和 —— 错得越离谱,|p−y| 越大,学习信号越强;而 MSE 多乘一个激活函数导数因子,输出饱和(对/错得很夸张)时 σ′→0,"错得越狠反而学得越慢"。加上交叉熵源自极大似然估计(最小化 KL 散度),统计意义上就是"最合理解",所以分类默认它。MSE 则是回归(预测连续值)的默认选择。

对比项MSE(均方误差)交叉熵(CrossEntropy)
公式½Σ(p−y)²−Σ y·log p
任务回归(角度、力矩、坐标)分类(类别、词表)
配输出层恒等输出softmax + logits
错误大时梯度线性增长,但 sigmoid 下被 σ′ 掐死梯度 = p−y,永不饱和
概率解释高斯噪声假设极大似然 / KL 散度
LeNet 用法原始论文(RBF 输出)现代复现(本页实战)

面试怎么问:①"为什么分类不用 MSE?"(梯度饱和公式 + 极大似然解释,两层都说才算完整);②"交叉熵在 one-hot 下化简成什么?"(−log p_correct);③"回归任务什么时候用 L1 而不是 L2?"(离群点鲁棒性,L1 梯度恒定不炸)。

易错点:①框架里 CrossEntropyLoss = LogSoftmax + NLLLoss,吃 logits;自己写了 softmax 再喂给它 = softmax 两次,精度下降;②交叉熵的 p 要夹在 (0,1) 开区间,log(0) = −∞,工程上 clip/加 ε;③别把"交叉熵小 = 准确率高"直接划等号 —— 校准与精度是两件事。

6 反向传播与优化算法:网络是怎么「学会」的

6.1 反向传播:链式法则的工程化

是什么:训练要回答"每个权重该往哪边、挪多大"。反向传播(backpropagation)就是两步:①前向算出损失 L;②反向从 L 出发,沿计算图把误差信号用链式法则逐层乘回去,得到每个参数的梯度 ∂L/∂W。它不是新算法,只是"把链式法则按拓扑序组织,复用中间结果"的动态规划 —— PyTorch 的 loss.backward() 一行,底层就是这套机制(自动微分)。

怎么做(手推两层网络,面试白板题标准答案):设两层网络,样本 x,标签 y,激活函数 f:

\boldsymbol{z}_1=W_1\boldsymbol{x}+\boldsymbol{b}_1,\quad \boldsymbol{a}_1=f(\boldsymbol{z}_1),\quad \boldsymbol{z}_2=W_2\boldsymbol{a}_1+\boldsymbol{b}_2,\quad \hat{y}=f(\boldsymbol{z}_2),\quad L=\dfrac{1}{2}\big(\hat{y}-y\big)^2

从输出往回乘,每一步只用"上游梯度 × 本地导数":

\dfrac{\partial L}{\partial \hat{y}}=\hat{y}-y\ \Rightarrow\ \dfrac{\partial L}{\partial \boldsymbol{z}_2}=(\hat{y}-y)\odot f'(\boldsymbol{z}_2)\ \Rightarrow\ \dfrac{\partial L}{\partial W_2}=\dfrac{\partial L}{\partial \boldsymbol{z}_2}\,\boldsymbol{a}_1^{\top} \ \Rightarrow\ \dfrac{\partial L}{\partial \boldsymbol{a}_1}=W_2^{\top}\dfrac{\partial L}{\partial \boldsymbol{z}_2} \ \Rightarrow\ \dfrac{\partial L}{\partial \boldsymbol{z}_1}=\dfrac{\partial L}{\partial \boldsymbol{a}_1}\odot f'(\boldsymbol{z}_1) \ \Rightarrow\ \dfrac{\partial L}{\partial W_1}=\dfrac{\partial L}{\partial \boldsymbol{z}_1}\,\boldsymbol{x}^{\top}

四个记忆点:①对权重的梯度 = "到达该权重的误差信号" × "该权重前向时的输入"(∂L/∂W₂ 用 a₁,∂L/∂W₁ 用 x);②误差跨层回传要过 Wᵀ(反向走前向的转置路);③激活处逐元素乘本地导数 f′;④所有中间梯度都只需算一次(复用),这就是反传比数值微分快几个数量级的原因。

前向:算出预测与损失 x z₁=W₁x+b₁ 层1 仿射 a₁=f(z₁) 激活 z₂=W₂a₁+b₂ 层2 仿射 ŷ=f(z₂) 激活 L 反向:梯度沿链式法则逐层乘回 ∂L/∂L=1 ∂L/∂ŷ = ŷ−y 损失项 ∂L/∂z₂ × f′(z₂) 0.25 ∂L/∂a₁=W₂ᵀδ₂ 转置回传 ∂L/∂z₁ × f′(z₁) 打折 ∂L/∂W₁ = δ₁xᵀ 真正要更新的权重梯度 梯度消失的账:sigmoid 下每过一层梯度最多乘 0.25,十层连乘 ≤ 0.25¹⁰ ≈ 10⁻⁶ 靠输入层 ≈ 10⁻⁶ 中间层 ≈ 10⁻⁴ 靠输出层 ≈ 10⁻¹·∇L(还能学) 结果:靠近输入的层几乎不更新(前几层权重"睡着"),ReLU(正半轴 f′=1)正是为斩断这条衰减链而生
图③ 反向传播链路:蓝线为前向计算流,红虚线为反向梯度流;每个节点只做"上游梯度 × 本地导数"这一件事 —— 链式法则把全局问题拆成局部规则的乘法,这也是 NPU/训练芯片需要支持反向算子(Wᵀ 卷积、比较器 argmax 路由)的根本原因

为什么(与硬件的关系):训练 = 前向 + 反向 + 更新,反向多出三个硬件需求:①前向算子的镜像算子(卷积反传是 W 旋转 180° 的卷积、池化反传要记住 max 位置);②中间激活必须暂存(反传要用前向的 a₁、x,显存/缓冲翻倍 —— 08-07 存储层级的训练口径);③更新阶段是读改写(θ ← θ − η·g,读写带宽型负载)。端侧 NPU 只做推理,恰恰因为可以砍掉这一整套。

面试怎么问:①"白板推两层网络 ∂L/∂W₁"(默写上面六连乘);②"反向传播和链式法则什么关系?"(链式法则是数学,反传是按计算图拓扑序调度链式法则并复用中间量的实现);③"为什么训练显存比推理大?"(要缓存中间激活供反传用,粗估为前向的 2~3 倍);④"梯度消失/爆炸的原因各是什么?"(消失:激活导数<1 连乘;爆炸:权重初值大或梯度>1 连乘,clip/BN/残差应对)。

易错点:①∂L/∂a₁ 那步忘了转置 W₂ᵀ(反向矩阵要转置);②把"激活函数导数"与"权重梯度"混为一谈;③softmax+交叉熵组合的 f′ 因子已被吸收成 p−y,此时再额外乘 σ′ 就重复扣减(经典实现 bug);④梯度消失不是 sigmoid 输出小,而是导数小于 1 连乘 —— 口误会被面试官抓住。

6.2 优化算法:SGD、动量与 Adam,学习率是第一超参

是什么:有了梯度,沿负梯度方向更新权重,迭代逼近损失最小点 —— 这就是梯度下降。SGD 用小批量(mini-batch)估计梯度直接走;动量(Momentum)给更新加"惯性":历史速度的指数加权平均,坑洼(梯度噪声)被抹平,平坦方向越走越快;Adam 同时维护梯度的一阶矩 m(方向)与二阶矩 v(幅度)的自适应估计,给每个参数自动配步长,默认超参即可用,是首选起点。

\text{SGD:}\quad \theta_{t+1}=\theta_t-\eta\, g_t \qquad \text{Momentum:}\quad v_{t+1}=\beta v_t+g_t,\ \ \theta_{t+1}=\theta_t-\eta\, v_{t+1}
\text{Adam:}\quad m_t=\beta_1 m_{t-1}+(1-\beta_1)g_t,\quad v_t=\beta_2 v_{t-1}+(1-\beta_2)g_t^2,\quad \theta_{t+1}=\theta_t-\eta\,\dfrac{\hat{m}_t}{\sqrt{\hat{v}_t}+\epsilon}

(g_t 为小批量梯度;Adam 的 m̂/v̂ 是偏差修正项,β₁=0.9、β₂=0.999、ε=10⁻⁸、η 默认 10⁻³。)

优化器更新特点优点缺点典型学习率硬件视角
SGD沿当前小批量梯度直走实现最简,泛化常更好对学习率敏感,沟壑地形震荡0.01~0.1(d2l LeNet 用 0.9)状态零开销,只需读写 θ
SGD+动量梯度指数加权平均后走抗噪声、加速谷底方向仍需调 η 与 β0.01,β=0.9多存一份速度 v(同 θ 大小)
Adam一阶/二阶矩自适应步长鲁棒、收敛快、默认参数能用显存 ×3,泛化有时逊于 SGD10⁻³训练芯片要存 m/v,更新阶段访存重

怎么做(学习率诊断法):学习率 η 是最重要的一个旋钮,看损失曲线就能诊断:

症状(训练损失曲线)诊断处方
缓慢下降,几十 epoch 还没起色η 太小×3~×10 上调,或换 Adam
震荡剧烈甚至 NaN,损失飙升η 太大÷10 下调;或加梯度裁剪
先快后平台,再不动η 不随进度衰减阶梯衰减(stepLR)或余弦退火
训练损失降、验证损失回头过拟合(非 η 问题)转入第 7 节正则化手段

batch_size 的隐影响:大 batch 梯度估计稳但每 epoch 步数少,常配合放大 η(线性缩放法则);小 batch 噪声大,反而有一定正则效果。LeNet 在 MNIST 上 batch=64~256、Adam 1e-3,几个 epoch 就能到 98%+ —— 对硬件工程师的意义:batch 决定了单次前向的并行度,NPU 的 PE 阵列利用率、SRAM 分块(tiling)都按 batch×通道×尺寸来规划(08-07 的 N/M/T 参数)。

面试怎么问:①"SGD、动量、Adam 的区别,各自什么时候用?"(简→惯性→自适应;调参预算少/新任务用 Adam,刷泛化上限用 SGD+动量+调 η);②"Adam 为什么显存 ×3?"(m、v 两份状态 + 梯度);③"学习率怎么定?"(说诊断法 + log 尺度扫描,别只背"1e-3")。

易错点:①把动量公式两种常见变形(v=βv−ηg 或 v=βv+g 后乘 η)当成对错之分 —— 等价,系数约定不同;②Adam 的 β₂ 接近 1 时前期 v̂ 被偏差修正救回来,自己实现漏掉 m̂/v̂ 会前几步步长异常;③η 与 batch_size 联动调,只动一个看结论容易误判;④"损失没降"先查数据与标签、再查学习率,新手 90% 的锅是标签错/归一化错,不是优化器。

7 过拟合与正则化:让网络只会「背题」变成会「做题」

7.1 过拟合:诊断信号与四大正则化手段

是什么:模型把训练集"背"下来了(包括噪声),但没学到规律 —— 表现为训练损失持续下降、验证/测试损失先降后升,两条曲线分叉形成"U 形剪刀差"。与之相对:欠拟合是训练损失本身降不下去(模型太小/训练太久没到位/特征太弱)。LeNet 只有 6.17 万参数、MNIST 有 6 万样本,基本不会过拟合;但把 LeNet 放大 10 倍或把样本砍到 1 千,剪刀差立刻出现 —— 参数/样本比是直观预警指标。

为什么(从硬件视角理解):过拟合的根源是"模型容量 >> 数据信息量",网络把冗余容量用来记噪声。正则化的全部手段,本质都是主动限制有效容量增加数据信息量。这个"容量-数据匹配"思想对 NPU 同样成立:芯片算力固定,越大的模型越要靠压缩/量化/稀疏砍掉冗余容量才能塞进去 —— 08-08 的量化在算法侧做的正是同方向的事。

怎么做(四大手段,按工程使用频率排序):

手段机制怎么用(代码)适用/代价
数据增强免费扩数据:旋转/平移/翻转/裁剪,给模型"变着花样看同一张图"transforms.RandomRotation(10) 等,只在训练集上加首选,零参数代价;要符合任务先验(数字 6/9 别随便转 180°)
Dropout训练时以概率 p 随机置零神经元,迫使网络不依赖任何单一路径;推理时关闭(框架自动缩放)nn.Dropout(0.5) 加在 FC 层之间FC 层标配;卷积层用少;训练/推理行为不一致(对量化部署有影响)
L2 正则(权重衰减)损失加 λ‖W‖²:大权重被惩罚,解偏向平滑小权重optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)一行参数;偏置/BN 参数通常不加
早停(early stopping)监控验证损失,连续 N 个 epoch 不再改善就停,回取最优点手写计数器或 EarlyStopping 回调零成本;需要可靠的验证集
💡 直觉记忆:Dropout 像"随机抽考"逼你全面复习,L2 像"写论文限字数逼你说重点",数据增强像"换不同字体出题",早停像"见好就收"。四招在 LeNet 实战中几乎用不上(MNIST 太简单),但放大模型/换真任务后全是必修。

面试怎么问:①"怎么判断过拟合?列出至少四种缓解手段"(训练/验证曲线分叉 + 上表四招);②"Dropout 训练和推理为什么行为不同?"(训练随机置零+期望缩放,推理用全网络;框架 eval() 自动切换);③"L2 正则为什么又叫权重衰减?"(更新式 θ ← θ(1−ηλ)−η∇L,每步先乘一个小于 1 的系数);④"为什么 Dropout 层对量化/部署工程师是麻烦?"(推理时要折叠缩放系数、随机性不可综合,部署前通常做等价吸收)。

易错点:①把"训练精度 100%"当好事 —— 先看验证集再庆祝;②数据增强用错先验(水平翻转对 MNIST 会把 2 变成 5 的镜像);③Dropout 放在输入层或卷积层乱用,掉点反而怪正则化无效;④权重衰减与 L2 在 Adam 里并非严格等价(AdamW 才是正确解耦)—— 深究者加分。

8 PyTorch 实战:约 60 行跑通 LeNet 训练 MNIST

是什么:下面四段代码拼起来是一个完整可运行的训练脚本(约 60 行):定义 LeNet → 加载 MNIST → 配置损失/优化器 → 训练并每个 epoch 汇报测试精度。本机有 GPU 用 GPU,没有也能跑(CPU 约 1~2 分钟/5 epoch)。运行前 pip install torch torchvision

8.1 逐段讲解:四段代码,每一段对应前面一节理论

第 ① 段 · 模型定义(对应第 2~4 节):把图②的每一层翻译成 nn 模块,注释即 shape 流水账。

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

class LeNet(nn.Module):
    """现代简化版 LeNet:ReLU + MaxPool,输入 1x28x28(C1 用 padding=2 保持 28)"""
    def __init__(self, num_classes=10):
        super().__init__()
        self.feat = nn.Sequential(                      # 卷积编码器
            nn.Conv2d(1, 6, kernel_size=5, padding=2),  # -> 6 x 28 x 28
            nn.ReLU(),                                  # 逐元素 max(0,x)
            nn.MaxPool2d(2),                            # -> 6 x 14 x 14
            nn.Conv2d(6, 16, kernel_size=5),            # -> 16 x 10 x 10
            nn.ReLU(),
            nn.MaxPool2d(2),                            # -> 16 x 5 x 5
        )
        self.cls = nn.Sequential(                       # 分类器
            nn.Flatten(),                               # -> 400(纯重排,不算)
            nn.Linear(16*5*5, 120), nn.ReLU(),          # C5 -> 120
            nn.Linear(120, 84),     nn.ReLU(),          # F6 -> 84
            nn.Linear(84, num_classes),                 # -> 10(logits)
        )
    def forward(self, x):
        return self.cls(self.feat(x))

读法:Conv2d(1,6,5) 的三个数字就是 C_in、C_out、K —— 用第 3 节公式口算参数量验证:5×5×1×6+6=156 ✓。输出层不给 softmax,因为下一段的 CrossEntropyLoss 内部已含 LogSoftmax(见 5.3 易错点)。

第 ② 段 · 数据加载(对应 5.1 节):

tf = transforms.Compose([
    transforms.ToTensor(),                          # 0~255 -> [0,1]
    transforms.Normalize((0.1307,), (0.3081,)),     # MNIST 经验均值/方差
])
train_ds = datasets.MNIST("./data", train=True,  download=True, transform=tf)   # 60000 张
test_ds  = datasets.MNIST("./data", train=False, download=True, transform=tf)   # 10000 张
train_dl = DataLoader(train_ds, batch_size=64,  shuffle=True)    # 训练集要打乱
test_dl  = DataLoader(test_ds,  batch_size=512, shuffle=False)

第 ③ 段 · 训练配置(对应 5.3/6.2 节):

device = "cuda" if torch.cuda.is_available() else "cpu"
model  = LeNet().to(device)
lossf  = nn.CrossEntropyLoss()                       # 交叉熵,输入 logits
opt    = torch.optim.Adam(model.parameters(), lr=1e-3)   # 想对照 d2l 就换 SGD(lr=0.9)

第 ④ 段 · 训练 + 评估循环(对应 5.2/6.1 节):每个 batch 内部发生的事就是"前向 → 损失 → backward 反传 → step 更新",正是图③的循环体。

for epoch in range(5):
    model.train()                                    # 训练模式(本模型无 Dropout/BN,写上是好习惯)
    for x, y in train_dl:
        x, y = x.to(device), y.to(device)
        opt.zero_grad()                              # 1. 清上一批梯度(PyTorch 梯度默认累加!)
        out  = model(x)                              # 2. 前向:形状 (64,10)
        loss = lossf(out, y)                         # 3. 交叉熵标量
        loss.backward()                              # 4. 反向传播:链式法则自动求所有梯度
        opt.step()                                   # 5. 优化器按梯度更新权重
    model.eval()                                     # 评估模式
    correct = 0
    with torch.no_grad():                            # 推理不需要梯度,省显存提速
        for x, y in test_dl:
            pred = model(x.to(device)).argmax(1).cpu()   # 取得分最大的类
            correct += (pred == y).sum().item()
    print(f"epoch {epoch+1}/5  test acc = {correct/len(test_ds):.4f}")
# 预期输出:第 1 个 epoch 即 ~0.98,5 个 epoch 稳定在 0.988~0.992

为什么值得 IC 工程师亲手跑:跑完你就拥有了一张"活的"对照表 —— loss.backward() 对应反传硬件的多路梯度数据流,opt.step() 对应权重 SRAM 的读改写,no_grad() 的省显存对应推理芯片不用存激活。下一步把 model(x) 换成你自己的算子,或者用 torch.onnx.export 导出 ONNX 喂给 08-08 的量化工具链,这条链就是"算法→芯片"的真实工作流。

8.2 调参实践表:lr / batch_size / epoch 的推荐与诊断

超参推荐起点安全范围调大效果调小效果对硬件的含义
学习率 lr1e-3(Adam)1e-4~1e-2收敛快,过大发散/NaN更稳但慢,可能陷平台不进硬件,只进固件/驱动
batch_size6416~512梯度稳、吞吐高,泛化略降,显存↑噪声大正则强,利用率低决定 NPU 单次并行宽度与 SRAM 分块
epoch51~20训练损失更低,过拟合风险↑欠拟合决定总计算量 = 单次 MAC × 60k × epoch
激活/池化ReLU + MaxPool换 tanh/AvgPool 可复现 1998 味道(掉点)ReLU=max 电路,是 NPU 激活单元的最爱
症状最可能原因快速验证
acc 卡在 ~10%(瞎猜水平)标签错位/损失喂了 softmax 两遍/学习率爆炸打印前 8 个 (pred,label) 对;查 CrossEntropyLoss 输入
acc 卡在 ~11.2%数据全是一个类别(打乱失效/索引错)Counter(y_train) 看类别分布
loss=NaNlr 太大;或 Normalize 用错统计量产生除零lr ÷10 重跑;打印 x.mean()/x.std()
CPU 跑太慢batch 太小/开了不必要的多线程batch 提到 256;DataLoader 加 num_workers

面试怎么问:①"loss.backward() 前为什么要 zero_grad()?"(PyTorch 梯度累加语义,为梯度累积/大 batch 拆分留机制,不清零 = 把多批梯度加起来);②"torch.no_grad() 做了什么?"(不建计算图、不存中间量 —— 显存从训练口径降到推理口径);③"训练脚本的哪几行最消耗算力?各对应硬件什么单元?"(前向 conv 与 backward 卷积反传 —— MAC 阵列;step() —— 权重存储带宽)。

易错点:①忘 model.eval() —— 本模型没有 Dropout/BN 时恰好无害,换 ResNet 后测试精度随机抖动,极难排查;②忘 shuffle=True,若按类别顺序喂 batch,梯度估计系统性偏差;③把 numpy 张量直接喂模型(device 不匹配报错);④统计精度时忘了 item() 把 0 维张量转 Python 数,累加出一堆张量,最后除出来形状对不上。

9 面向 NPU 的算子统计视角:从模型到硬件的桥

9.1 换一副眼镜:从"网络"到"算子清单"

是什么:把 LeNet 不当作"识别数字的智能",而当作一张算子执行清单(op graph):卷积 ×2、池化 ×2、逐元素激活 ×4、矩阵乘 ×3、重排 ×1。硬件工程师要回答的问题立刻具体化 —— 每种算子需要什么计算单元?权重/激活各占多少存储?哪种算子吃算力、哪种吃带宽?把 4.1 的逐层表按算子类型聚合,得到下面这张 NPU 设计的直接输入表:

算子类型出现次数权重数(存储)MAC 计算计算/访存比需要什么硬件单元
标准卷积(Conv)22,572(156+2,416)357,600(86%)高(权重复用 H_out×W_out 次)→ 算力型MAC 阵列/乘法器树 + 加法树,line buffer 滑窗,权重常驻 SRAM
全连接/矩阵乘(FC)359,134(96% 总权重)58,920(14%)低(每权重用 1 次)→ 带宽型GEMM 引擎,大权重搬运带宽,无滑窗
池化(Pool)20≈0(比较/加法)纯访存4 输入比较树/加法树,可与卷积融合成流水
激活(ReLU)40≈0(max)纯访存1 个 max 比较器;sigmoid/tanh 需查表/分段线性
Flatten/重排100纯地址运算地址生成器/DMA,零计算单元
Softmax+损失10≈10⁻³ MAC(10 维指数+归一)控制流复杂常在 CPU/矢量单元做;端侧 NPU 常不含(推理输出 logits 即可)
合计13 个算子节点61,706416,520

怎么用(这张表直接喂给后续三章):读表找主要矛盾:86% 的计算在卷积 → PE 阵列按卷积设计(08-07 脉动阵列);96% 的权重在全连接 → 权重缓冲容量与带宽按 FC 压测;池化/激活零参数 → 融合进卷积流水,不落 SRAM;②估算存储:权重 INT8 ≈ 61.7 KB(一片小 SRAM 放得下),C1 输入 28×28×8bit ≈ 6.3 KB,激活峰值 6×28×28×8bit ≈ 37.6 KB —— 128 KB 片上存储即可全图驻留,这正是小 NPU 教学项目的可行边界;③算周期:0.42 MMAC ÷(64 MAC/拍 × 利用率 50%)≈ 13K 拍,@100 MHz ≈ 0.13 ms/张 —— 与 08-07 页的逐层映射表对得上;④进 RTL:把表首列当成 08-11 页控制模块的状态机状态名(Conv/Pool/FC/Done),算子清单就是 Spec 的雏形。

面试怎么问:①"拿到一个 ONNX 模型,你的第一份硬件分析报告包含什么?"(算子直方图、权重/激活存储分布、MAC 分布、计算-访存比分类 —— 即本表);②"为什么 FC 层是带宽问题而卷积层是算力问题?"(复用率决定计算访存比);③"哪些算子可以融合?融合省的是什么?"(卷积+激活+池化,省的是中间激活写回 SRAM/DRAM 的往返 —— 能耗大头在访存);④"Softmax 为什么常丢给 CPU?"(指数/除法不规则,占比极小,专用电路不划算)。

易错点:①只看 MAC 不看访存 —— LeNet 量级无所谓,放大到 ResNet/Transformer 后带宽是第一瓶颈;②权重数与 MAC 数分布"错位"忘了分账(存储按 96% FC 规划、算力按 86% 卷积规划);③把损失/softmax 算进端侧推理预算 —— 部署时输出 logits 给 CPU 就行;④忘了 dtype:61.7K 参数是个数,61.7 KB 才是存储,FP32 直接 ×4。

10 本节自测

1. 输入特征图为 32×32,用 5×5 卷积核、stride=1、不填充(padding=0)做卷积,输出特征图的尺寸是?
💡 代入输出尺寸公式 H_out = ⌊(H+2P−K)/S⌋+1 = ⌊(32+0−5)/1⌋+1 = 28。这正是 LeNet C1 层的实际情况:输入 32×32、5 个卷积核划过,每次只挪 1 格,水平/垂直方向各只有 28 个合法位置。
2. 多层网络如果去掉所有激活函数(或全部用线性激活),网络会退化成什么?
💡 线性变换的复合仍是线性变换:W₂(W₁x+b₁)+b₂ = (W₂W₁)x+(W₂b₁+b₂)。没有非线性激活,叠 100 层和叠 1 层的表达能力一样,异或这种最简单的非线性问题都学不了。激活函数是把「线性代数」变成「万能函数拟合器」的关键。
3. LeNet 的 C1 层(输入 1×32×32,6 个 5×5 卷积核,输出 6×28×28)一共有多少次 MAC(乘累加)计算?
💡 MACs = H_out×W_out×K×K×C_in×C_out = 28×28×5×5×1×6 = 117,600。注意区分:参数量只有 5×5×1×6+6 = 156 个(权值共享的威力),但每个输出像素都要做 25 次乘加,共 784 个输出位置 × 6 张输出图 —— NPU 设计里 MAC 数才是面积与功耗预算的依据,不是参数量。
4. 深层网络用 sigmoid 做激活时梯度消失的根本原因是什么?
💡 σ′(x)=σ(x)(1−σ(x)),最大值在 x=0 处仅 0.25。反向传播的梯度要沿层做链式法则连乘,n 层之后上界只有 0.25ⁿ,靠近输入的层梯度趋近 0,权重几乎不再更新。ReLU 正半轴导数恒为 1,连乘不衰减,是深度网络能训起来的关键改进之一。
5. 多分类任务的输出层搭配交叉熵损失而不是 MSE,最重要的工程原因是?
💡 softmax+交叉熵联合求导恰好得到 p−y 这种「预测减标签」的干净形式:错得越离谱梯度越大,学习信号强;而 MSE 配 sigmoid,当输出饱和(σ′→0)时梯度也趋近 0,初学阶段反而「错得越狠学得越慢」。工程上分类默认交叉熵,回归才用 MSE。
📌 本节小结: AI ⊃ 机器学习 ⊃ 深度学习 ⊃ 大模型,LeNet 是深度学习实用化的起点;它属于监督学习,和强化学习(机器人步态)、无监督(聚类)共同构成三大范式。一切计算落在"乘累加 + 非线性"上:神经元是点积加激活,激活函数(ReLU 导数恒 1)是网络能表达非线性的开关。卷积靠局部连接与权值共享,三个公式(输出尺寸/参数量/MAC)是 NPU 岗面试必背;池化零参数换鲁棒性;全连接与"核=整图的卷积"等价、是带宽瓶颈。LeNet 61,706 参数、0.42 MMAC/样本 —— 参数 96% 在全连接侧、计算 86% 在卷积侧,这本"两本账"直接决定 NPU 的存储与算力规划。训练 = 前向定价(交叉熵)+ 反向分账(链式法则,sigmoid 导数≤0.25 连乘致梯度消失)+ 优化器走步(Adam 起步,学习率第一超参);过拟合看双曲线剪刀差,四大正则化对症下药。最后把网络翻译成 13 个算子节点的执行清单,就是 08-07 硬件映射与 08-11 RTL 的 Spec 雏形。
🤔 思考题: 1. 把 LeNet 的两个 5×5 卷积换成 3×3(padding=1)并增加一层,参数量与 MAC 各怎么变?哪种结构对 8×8 PE 阵列更友好?
2. 若端侧 NPU 只有 64 KB 片上 SRAM,LeNet 用 INT8 部署时权重还能全驻留吗(61.7 KB)?激活怎么办?给出你的 tiling 方案。
3. 用 6.1 的手推结果解释:为什么训练芯片的反向数据通路里需要一个"转置/旋转 180°"的卷积数据组织?
4. 把本页算子统计表与 08-05 页你写过的 APB 从机接口对照:如果权重经 APB 逐字节写入需要 61,706 个周期,你能提出什么样的"批量写"扩展?

11 参考来源与延伸资源

本页知识点以 LeCun 1998 年 LeNet-5 原论文为事实基准,结构讲法参考《动手学深度学习》与 PyTorch 官方教程,视频直觉推荐 3Blue1Brown 与「跟李沐学AI」。以下链接均经核实可访问。

📗
⭐ 《动手学深度学习》中文版在线全文(李沐 等 · 免费开源教材)
面向中文读者的可运行深度学习教科书,PyTorch/TensorFlow/Paddle 多框架实现:第 3~5 章线性网络→MLP,第 6 章卷积,第 7 章现代 CNN,第 11 章优化算法 —— 本页理论线的"教科书本体",每节配可运行代码与练习。
官方免费⭐ 首选教材难度:入门
📘
⭐ 《动手学深度学习》6.6 卷积神经网络(LeNet)章节
与本页最对口的单节:LeNet 模型构造(逐层打印输出形状验证)、在 Fashion-MNIST 上训练(Xavier 初始化 + 交叉熵 + SGD),并附"换最大池化/换 ReLU"的改造练习 —— 本页实战代码的理论底稿。
官方免费⭐ 配套精读难度:入门
🎓
《动手学深度学习》官方课程大纲与课件(D2L Courses 中文版)
李沐团队的官方课程页:15 章视频课表、课件 PDF、作业与论坛入口,按"深度学习基础→卷积→注意力"排课 —— 想把本页知识点展开成一学期课程,用这份大纲即可。
官方课程难度:入门
🎬
⭐ B站「跟李沐学AI」官方空间(动手学深度学习系列课)
《动手学深度学习》PyTorch 版全程视频 + 论文精读系列(AlexNet/ResNet/Transformer)都在此号,配合上面教材逐节食用 —— 中文视频里讲 LeNet 与卷积基础最系统的课程资源。
B站官方课程号⭐ 强推难度:入门
🎬
⭐ 3Blue1Brown(B站官方双语):深度学习之神经网络的结构 Part 1
把"神经元 = 加权求和 + 偏置 + 激活"讲成动态可视化的神作:手写数字识别正是本页 MNIST 任务的动画版,10 分钟建立"层与神经元"的直觉,官方中文号播放量百万级。
B站官方双语⭐ 建立直觉难度:入门
🎬
⭐ 3Blue1Brown:深度学习之反向传播算法(上/下,官方双语)
上篇直观演示"误差如何一层层往回怪罪",下篇把链式法则逐项写出 —— 与本页 6.1 手推对照食用:先看动画接受画面,再用本页公式白板复推,双向锁死。
B站官方双语⭐ 配套图③难度:入门~中
📄
⭐ LeCun 1998 原论文 PDF:Gradient-Based Learning Applied to Document Recognition(LeNet-5 出处)
LeNet-5 的原典:第 2 节 CNN 四大思想(局部感受野/权值共享/下采样),第 2.2 节逐层规格与连接表(本页 4.2"为什么 C3 不完全连接"的出处),附 MNIST 基准对比 —— 备份镜像见 HAL:hal.science/hal-03926082/document。
原论文 PDF⭐ 一手出处难度:中
🗂️
⭐ torchvision 官方文档:datasets.MNIST(当前可用的标准 MNIST 获取入口)
train/test 参数、raw IDX 文件格式(train-images-idx3-ubyte 等)与 download 镜像机制的一手说明;原始 yann.lecun.com/exdb/mnist/ 页面已停维(仅剩目录),工程上请按本页 5.1 用 torchvision 加载。
PyTorch 官方⭐ 数据集入口难度:入门
📘
⭐ PyTorch 官方教程总入口(Learn the Basics / 60 Minute Blitz)
"Learn the Basics"从张量操作一路讲到训练完整网络,与本页 8.1 四段代码互补;进阶的 "What is torch.nn really?" 把 nn.Module/参数/优化器的机制拆到最底层 —— 写完 LeNet 后的正确下一步。
PyTorch 官方⭐ 必跑难度:入门
📝
⭐ 知乎高赞:经典卷积神经网络之 LeNet-5 网络模型
中文社区对 LeNet-5 逐层尺寸/参数/连接表的图文讲解,把 1998 年原论文的"卷积-下采样-非线性"范式讲得通俗,与本页 4.1 统计表互相验证(建议以论文口径为准核对数字)。
知乎专栏⭐ 中文精讲难度:入门
📝
CSDN:经典卷积神经网络 — LeNet-5 的详解(逐层解析 + 完整代码)
按 C1→S2→C3→S4→C5→F6→输出 逐层拆解(含"为什么输入 32×32"),并给出一套可直接跑的 Keras 实现 —— 想再对照一套 PyTorch 之外的框架实现时用。
CSDN 博客难度:入门
📄
IEEE Xplore:LeNet-5 论文正式出版页(Proceedings of the IEEE, 1998)
DOI 10.1109/5.726791 的官方收录页,含摘要、引用信息与机构下载入口 —— 写报告/论文引用 LeNet 时以该页 bibliographic 信息为准。
IEEE 官方难度:中