Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 3 讲习题集 · 深度计算机视觉

四组难度递进:A 概念判断 → B 推导证明 → C 编程实现 → D 开放设计。答案默认折叠,点击展开。 建议用法:先闭卷做 A 组,再对照答案;B 组手写推导(不许看答案);C 组上机并跑通验收标准(验收标准本身就是判分线);D 组写成半页方案。 所有题目的答案都能在 3.1–3.10 正文中定位;与 综合自测卷 不重复(个别共用同一组数字的题已在题面注明)。

A 组 · 概念判断(8 题)

A1. 「卷积网络天生具有平移不变性,所以把物体挪几个像素不影响分类结果。」这句话错在哪?请给出两条正确表述,并指明「不变」是哪一步换来的。 (依据:3.1、例 3.3.1)

答案

错在把等变说成了不变。 卷积给的是平移等变:输入平移 Δ\Delta,输出特征图跟着平移 Δ\Delta(命题 3.1.1)。分类需要的平移不变要靠后续的池化 / 全局池化 / 大感受野逐步换来(3.3.1)。

正确表述两条:① 卷积具有平移等变——输入平移、输出特征图跟着平移;② 分类任务需要的平移不变由池化 / 全局平均池化 / 下采样等步骤换取,且只是局部的、有限的(平移几像素可能不变,平移半个感受野通常会变,受 padding 与下采样影响)。例 3.3.1 还说明局部 2×22\times2 池化提供的不变性很弱且不连续。

A2. 「ViT 完全没有归纳偏置,所以只要数据够多就一定强过 CNN。」请分别指出这句话的前半句与后半句的问题。 (依据:3.5、3.5)

答案

前半句错:ViT 的归纳偏置比 CNN 弱,而不是没有。patchify 本身假设每个 token 是一个连续方块(局部性),线性投影等价于一个无重叠的 P×PP\times P 卷积,patch 内像素的相对排布由投影权重学到(定义 3.5.1)。

后半句错:这是条件性结论,而且「强过」必须有控制变量支撑。ViT-Large 只用 ImageNet-1k 时比 ViT-Base 更差,要到大得多的数据上才展现出规模收益;DeiT 证明差距很大程度来自训练配方(不蒸馏已达 81.8%81.8\%);ConvNeXt 用逐步控制变量实验说明,把配方与微观设计对齐后纯卷积可与同规模 Transformer 持平(ConvNeXt-T 82.1%82.1\% 对 Swin-T 81.3%81.3\%)。所以选型依据是数据量、延迟、部署工具链与是否需要多尺度输出,而不是「谁更新」(3.5.5)。

A3. 判断并说明理由:「ResNet 之所以能训到几百层,是因为残差连接让梯度变得更大。」 (依据:定理 3.4.1 与其后的两个补充观察)

答案

不准确,只说对了现象的一半。 残差的价值有两件互补的事:① 让恒等映射变便宜——如果某几层的最优行为是「什么都不做」,普通网络要拼出恒等映射(很难),残差块只需把 FF 学成 ≈0\approx0,因为 y=F(x)+xy=F(x)+x 这条通路已经免费提供(优化视角);② 让梯度有一条不经过任何变换的路——∂y/∂x=I+∂F/∂x\partial y/\partial x=I+\partial F/\partial x,展开式 ∏(I+Ji)=I+∑Ji+⋯\prod(I+J_i)=I+\sum J_i+\cdots 中唯一不含 JJ 的项是 II。

对「更大」这个说法的两点纠正:① II 是矩阵意义下的直通,一个具体参数收到的梯度是否真的大,还取决于后续层的组合方式与总深度——残差缓解梯度消失,不等于「梯度不会消失」;② 展开式里的高阶交叉项在深层会累积,这是残差网络并非「无条件可训」的原因之一,实践中通常还要配合归一化与合理初始化。所以准确表述是「让深网络在优化上变得可达」,而不是「让梯度变大」。

A4. 「BatchNorm 的作用是把数据标准化,所以把它放在网络的任何位置、任何 batch size 下都能提升效果。」请用三个具体事实反驳。 (依据:定义 3.3.3、3.3.3、例 3.3.4)

答案

三个事实:

  1. 它依赖批统计,与 batch size 强耦合:统计量在每个通道上、跨该批全部样本的 B×H×WB\times H\times W 个元素计算(定义 3.3.3)。检测 / 分割常以 B=2B=2 训练,统计噪声大、训练不稳,因此这些框架几乎默认换 GroupNorm(定义 3.3.4)。
  2. 训练与推理必须用两套统计量:训练用当前批的 (μc,σc2)(\mu_c,\sigma_c^2),推理用训练期累积的运行统计量(命题 (3.8) 与动量更新式)。若推理也按当前批算,同一条数据的输出会随它与谁同批而变——部署不可接受,且单样本时方差退化为 00。此外领域偏移时运行统计量本身成为误差来源。
  3. 「哪里都加」会改变层的语义:把 BN 放在残差的加法之后会污染那条恒等通路(pre-activation 的动机,定义 3.4.4);而紧跟 BN 的卷积层偏置是冗余的(例 3.3.3(b)),但 BN 自己的 β\beta 却必需。另外在逐像素回归类任务(去噪 / 超分)上,BN 曾被观察到反而有害(3.3.3 延伸阅读)。

A5. 「池化层的主要作用是提供平移不变性。」请给出一个反例数字,并说出池化长期存在的三条更实际的理由。 (依据:例 3.3.1)

答案

反例(数字来自正文):对 X=(1201043010220113)X=\begin{pmatrix}1&2&0&1\\0&4&3&0\\1&0&2&2\\0&1&1&3\end{pmatrix} 做 2×22\times2 最大池化得 (4313)\begin{pmatrix}4&3\\1&3\end{pmatrix};把 XX 向右循环平移一格后,输出变化很小(本例中窗口内最大值的位置恰好未改变取值)——但这是「窗口内没有第二个同样强激活」这一条件下才成立的;一旦两个激活强度接近,池化输出会随平移在两者之间跳变(不连续、不可导)。所以这个不变性很弱且有条件。

更实际的三条理由:① 缩小特征图,省后续算力与显存;② 放大后续层的感受野(配合 命题 3.2.4 的递推);③ 提供少许可学的非线性(取最大是一种非线性)。另外 命题 3.3.1 说明最大池化与单调激活可交换,所以「Conv → ReLU → MaxPool」与「Conv → MaxPool → ReLU」数值等价——但这与「提供不变性」是两件事。

A6. 「检测任务只要总体准确率高就可以上线。」请用 10610^6 个候选位置、1010 个真实目标算一次,并说明应该看哪些指标。 (依据:例 3.7.1(c))

答案

把全部位置判为背景时,准确率 1−101,000,000=99.999%,1-\frac{10}{1{,}000{,}000}=99.999\% , 但 recall =0=0(1010 个真实目标全部漏掉),precision 无意义。这就是极端类不平衡下准确率几乎无信息量的算术根源。

应该看的指标:precision / recall / F1(或 AP);并且必须分别报告漏检与误检,因为两者的业务代价通常不同(3.10.3 的评估清单)。若要报 AP,必须说明口径:VOC 11 点还是 COCO 101 点 ++ 十个 IoU 阈值,跨不跨类别(方法 3.7.1)。

A7. 「LRN 与 BatchNorm 都是归一化,所以 AlexNet 用 LRN、ResNet 用 BN 只是命名不同。」判断并说明理由;再顺带指出 AlexNet 里 95%95% 参数在哪里、这个事实导致了什么后续设计。 (依据:3.4.1 的六项创新表与 例 3.4.1)

答案

错。两者机制不同,且 LRN 不是 AlexNet 成功的关键。 LRN(局部响应归一化)只在相邻通道之间做局部的响应抑制,是当时受生物视觉启发的做法,已被 BatchNorm 取代(正文明确写「LRN 本身不是关键」)。BN 则是跨批、逐通道的标准化并带可学习仿射(定义 3.3.3)。

AlexNet 的 95%95\% 参数在全连接层(卷积层仅占 4.9%4.9\%:卷积合计约 3.0×1063.0\times10^6,全连接合计约 5.86×1075.86\times10^7,总计 61,640,93661{,}640{,}936)。原因是把 256×6×6=9216256\times6\times6=9216 维特征直接展平接 40964096 维全连接,一层就是 3.775×1073.775\times10^7 个参数。这个事实催生了两条后续设计:① 全局平均池化 ++ 小分类头(3.3.1);② 全卷积网络(把全连接换成卷积用于分割,3.7.4)。VGG16 把这个缺陷放大到 89%89\% 参数在分类器(例 3.4.2)。

A8. 「对抗样本是因为模型过拟合,多训一会儿就没了;而且反正人看不出差别,现实世界不可能发生。」请逐句纠正。 (依据:定义 3.9.3、3.9.3 的误解框)

答案

逐句纠正:

  1. 不是过拟合。对抗样本在训练集上也存在,而且能跨模型迁移(在一个模型上生成的扰动可能骗过结构相近的另一个模型);把成因归给过拟合会导向「多训一会儿就好」的错误结论。
  2. 「人看不出异常」不代表安全。模型的决策边界与人的语义边界不是同一条:ReLU 网络是高维分段线性函数,边界在部分方向上离数据点极近,而人眼对高频内容不敏感、浅层卷积却对高频模式敏感。所以靠肉眼审查输出保证安全是不可靠的。
  3. 物理世界可行,但需要精心构造。打印图片、贴纸、眼镜等条件下的实现已被证明可行,但光照、视角、拍摄距离等条件比数字域苛刻得多——准确表述是「可行但需精心构造」,既不是「随手就能攻击任何系统」,也不是「不可能」。
  4. 附带纠正:鲁棒性永远相对于攻击模型与预算(ϵ\epsilon、范数、步数),任何「某某模型已经鲁棒」的表述都必须附攻击设置,否则没有意义。

B 组 · 推导与证明(6 题)

B1. 推导输出尺寸公式 (3.2) 的取整来源,并据此手算五个配置: (a) Win=32, k=3, s=1, p=0W_{in}=32,\ k=3,\ s=1,\ p=0;(b) Win=32, k=3, s=1, p=1W_{in}=32,\ k=3,\ s=1,\ p=1;(c) Win=32, k=3, s=2, p=1W_{in}=32,\ k=3,\ s=2,\ p=1;(d) Win=32, k=3, s=1, p=2, d=2W_{in}=32,\ k=3,\ s=1,\ p=2,\ d=2;(e) Win=224, k=7, s=2, p=3W_{in}=224,\ k=7,\ s=2,\ p=3。 要求:涉及形状的先写形状,不许写「略」。 (依据:命题 3.2.2 与其 proof、例 3.2.1)

提示与答案

推导:窗口左边界只能取整数。设最后一个合法窗口的左边界为 jj,其右边界 j+k−1j+k-1 必须落在填充后的输入范围内(长度 Win+2pW_{in}+2p),故 j≤Win+2p−kj\le W_{in}+2p-k。jj 从 00 出发每次加 ss,可取个数为 Wout=⌊Win+2p−keffs⌋+1,keff=k+(k−1)(d−1).W_{out}=\left\lfloor\frac{W_{in}+2p-k_{\text{eff}}}{s}\right\rfloor+1,\qquad k_{\text{eff}}=k+(k-1)(d-1). 向下取整的来源:边界处剩余像素不足 ss 个、放不下完整窗口时该位置被丢弃;框架(如 PyTorch Conv2d)不会补齐不完整位置,Win+2p−k<0W_{in}+2p-k&lt;0 时直接报错。

手算(一维,二维逐轴独立):

  • (a) ⌊(32+0−3)/1⌋+1=30\lfloor(32+0-3)/1\rfloor+1=30 —— valid 约定,边界丢两圈信息。
  • (b) ⌊(32+2−3)/1⌋+1=32\lfloor(32+2-3)/1\rfloor+1=32 —— same 约定,尺寸不变。
  • (c) ⌊(32+2−3)/2⌋+1=⌊31/2⌋+1=16\lfloor(32+2-3)/2\rfloor+1=\lfloor31/2\rfloor+1=16 —— 精确减半,骨干降采样常用。
  • (d) keff=3+(3−1)(2−1)=5k_{\text{eff}}=3+(3-1)(2-1)=5,⌊(32+4−5)/1⌋+1=32\lfloor(32+4-5)/1\rfloor+1=32 —— 空洞卷积保尺寸扩视野,参数仍是 9 个而视野变 5×55\times5。
  • (e) ⌊(224+6−7)/2⌋+1=112\lfloor(224+6-7)/2\rfloor+1=112 —— 这就是 ResNet 首层输出(再接 3×3,s=2,p=13\times3,s=2,p=1 最大池化得 5656)。

三条随手可得的规律:s=1s=1 且 p=(k−1)/2p=(k-1)/2(kk 奇)→ 尺寸不变;s=2s=2 → 大致减半;d>1d&gt;1 → 核撑大而参数不变。

B2. ResNet-50 的瓶颈块在 56×56×25656\times56\times256 上做三次卷积:1×11\times1(256→64256\to64)、3×33\times3(64→6464\to64,p=1p=1)、1×11\times1(64→25664\to256),全部 s=1s=1。 (a) 分别写出三步的参数量与 MACs;(b) 求整块参数量与 MACs;(c) 与「单层 3×33\times3、256→256256\to256」比较,给出参数与算力的比值;(d) 用一句话说明这个设计把省下的预算换成了什么。 (依据:例 3.2.2、定义 3.4.4)

提示与答案

公式:Params =CoutCink2+Cout=C_{out}C_{in}k^2+C_{out};MACs =HoutWoutCoutCink2=H_{out}W_{out}C_{out}C_{in}k^2。

(a)

  • ① 1×11\times1,256→64256\to64:Params =64⋅256+64=16,448=64\cdot256+64=16{,}448;MACs =56⋅56⋅64⋅256=5.14×107=56\cdot56\cdot64\cdot256=5.14\times10^{7}。
  • ② 3×33\times3,64→6464\to64:Params =64⋅64⋅9+64=36,928=64\cdot64\cdot9+64=36{,}928;MACs =56⋅56⋅64⋅64⋅9=1.156×108=56\cdot56\cdot64\cdot64\cdot9=1.156\times10^{8}。
  • ③ 1×11\times1,64→25664\to256:Params =256⋅64+256=16,640=256\cdot64+256=16{,}640;MACs =56⋅56⋅256⋅64=5.14×107=56\cdot56\cdot256\cdot64=5.14\times10^{7}。

(b) Params =16,448+36,928+16,640=69,016≈6.9×104=16{,}448+36{,}928+16{,}640=69{,}016\approx6.9\times10^{4};MACs ≈2.18×108\approx2.18\times10^{8}。

(c) 单层 3×33\times3、256→256256\to256:Params =256⋅256⋅9+256=590,080=256\cdot256\cdot9+256=590{,}080;MACs =56⋅56⋅256⋅256⋅9=1.85×109=56\cdot56\cdot256\cdot256\cdot9=1.85\times10^{9}。比值:参数 ≈1/8.6\approx1/8.6、算力 ≈1/8.5\approx1/8.5。

(d) 换成了深度:ResNet-50 用 m=C/4m=C/4 的瓶颈把「同宽度堆叠」压到约 1/8.51/8.5 的预算,从而在同代预算下把总深度从十几层量级推到五十层量级,同时每块内多出两层非线性(3.4.5)。

注意易错点:1×11\times1 卷积也有偏置,漏掉 +64+64 / +256+256 会以为公式算错(例 3.2.2 的核对)。

B3. 从 xl+1=xl+Fl(xl)x_{l+1}=x_l+F_l(x_l) 出发推导残差网络的梯度结构 (3.10): (a) 写出 ∂xi+1/∂xi\partial x_{i+1}/\partial x_i 与 ∂xL/∂xl\partial x_L/\partial x_l;(b) 展开连乘并指出哪一项是「直通项」;(c) 设某一层残差分支的雅可比满足 ∥∂Fi/∂xi∥=0.1 I\lVert\partial F_i/\partial x_i\rVert=0.1,I 且各层相同,比较「有残差」与「无残差(梯度为纯连乘 ∏Ji\prod J_i)」两种情形下传回浅层的梯度量级(取 L−l=10L-l=10);(d) 说明 II 为什么不会被训练改掉,并指出这个论证不能推出什么。 (依据:定理 3.4.1 与其 proof)

提示与答案

(a) 由 xi+1=xi+Fi(xi)x_{i+1}=x_i+F_i(x_i), ∂xi+1∂xi=I+∂Fi∂xi⟹∂xL∂xl=∏i=lL−1(I+∂Fi∂xi),\frac{\partial x_{i+1}}{\partial x_i}=I+\frac{\partial F_i}{\partial x_i}\quad\Longrightarrow\quad \frac{\partial x_L}{\partial x_l}=\prod_{i=l}^{L-1}\Bigl(I+\frac{\partial F_i}{\partial x_i}\Bigr), 再由 ∂L/∂xl=(∂L/∂xL)(∂xL/∂xl)\partial\mathcal L/\partial x_l=(\partial\mathcal L/\partial x_L)(\partial x_L/\partial x_l) 即得 (3.10)。

(b) 记 Ji=∂Fi/∂xiJ_i=\partial F_i/\partial x_i,则 ∏i=lL−1(I+Ji)=I+∑iJi+∑i<jJiJj+⋯+Jl⋯JL−1.\prod_{i=l}^{L-1}(I+J_i)=I+\sum_i J_i+\sum_{i&lt;j}J_iJ_j+\cdots+J_l\cdots J_{L-1}. 展开式里每一项都含至少一个 JJ,唯一不含 JJ 的项是 II——它就是直通项(它给出 ∂L/∂xL⋅I=∂L/∂xL\partial\mathcal L/\partial x_L\cdot I=\partial\mathcal L/\partial x_L,即从深层原样带回的一份梯度)。

(c) 取 n=L−l=10n=L-l=10、∥J∥=0.1\lVert J\rVert=0.1:

  • 无残差:∥∏Ji∥=0.110=10−10\lVert\prod J_i\rVert=0.1^{10}=10^{-10} —— 已经彻底消失。
  • 有残差:∥(I+J)10∥\lVert(I+J)^{10}\rVert(近似按标量 (1+0.1)10=2.59(1+0.1)^{10}=2.59) 与 ∥I∥=1\lVert I\rVert=1 同量级,不会降到零。

(注意这里是量级估计:矩阵情形下非交换性会使确切的范数依赖结构,但「有一项恒为 II」这一点不依赖结构。)

(d) II 不依赖于任何权重,所以它不可能被梯度下降「学掉」;类比是给梯度修了一条无需维护的高架桥。

不能推出什么:① 不能说「梯度一定很大」——II 是矩阵意义的直通,具体参数收到的梯度大小还取决于后续层的组合与总深度;② 不能忽略展开式里的高阶交叉项在深层的累积,这正是残差网络并非「无条件可训」的原因之一,实践中还需归一化与合理初始化;③ 不能推出「更深一定更好」——原文里千层网络能训起来,但精度并不比百层更好。

B4. BN 的三条代数性质:(a) 证明 BN 对「整通道加常数」不变;(b) 证明紧跟 BN 的卷积层偏置是冗余的(训练与推理两种情形都要说);(c) 说明 BN 的额外计算量为何在 FLOPs 上可忽略、却在内存带宽上代价明显。 (依据:例 3.3.3)

提示与答案

(a) 设 xi′=xi+cx&#x27;_i=x_i+c(ii 遍历该通道全部元素)。则 μ′=μ+c\mu&#x27;=\mu+c,且 xi′−μ′=xi−μx&#x27;_i-\mu&#x27;=x_i-\mu,故 σ′2=σ2\sigma&#x27;^2=\sigma^2,于是 x^i′=xi′−μ′σ′2+ϵ=xi−μσ2+ϵ=x^i.\hat x&#x27;_i=\frac{x&#x27;_i-\mu&#x27;}{\sqrt{\sigma&#x27;^2+\epsilon}}=\frac{x_i-\mu}{\sqrt{\sigma^2+\epsilon}}=\hat x_i. (推理时用运行统计量同理:训练与推理的常数一致则抵消,不一致会引入系统性偏移——这正是领域偏移下 BN 成为误差源的原因。)

(b) 设卷积输出 z=W∗x+bconvz=W*x+b_{\text{conv}}。训练时 BN 算 (z−μbatch(z))/σ(z)(z-\mu_{\text{batch}}(z))/\sigma(z),而 μbatch(z)=μbatch(W∗x)+bconv,\mu_{\text{batch}}(z)=\mu_{\text{batch}}(W*x)+b_{\text{conv}}, 故分子中 bconvb_{\text{conv}} 被完全减掉;既然结果与 bconvb_{\text{conv}} 无关,该参数永远冗余(其作用可由 BN 的 β\beta 承担)。推理时运行统计量 μrun\mu^{\text{run}} 同样吸收了等量偏移,抵消关系不变。工程含义:紧跟 BN 的卷积可以设 bias=False,省参数也省一次加法。

(c) BN 在每个激活值上做:一次减法 ++ 一次乘以预计算好的 γ/σ2+ϵ\gamma/\sqrt{\sigma^2+\epsilon},约 22 次逐元素运算。对照一层 3×33\times3、Cin=Cout=64C_{in}=C_{out}=64 卷积:每个输出元素要 Cink2=576C_{in}k^2=576 次 MAC。故 BN 相对卷积约 0.3%0.3\%,FLOPs 上可忽略。但它需要两次全张量归约与读写(求均值、求方差),访存量与激活张量同阶——所以「BN 拖慢小模型推理」的观感来自内存带宽而非 FLOPs(例 3.3.3(c))。

B5. 写出 InfoNCE 损失 (3.11),并推导它对相似度的梯度: (a) 记 qk=exp⁡(sik)/Zq_k=\exp(s_{ik})/Z,证明 ∂log⁡qk/∂sik=1−qk\partial\log q_k/\partial s_{ik}=1-q_k 以及 l≠kl\ne k 时 ∂log⁡qk/∂sil=−ql\partial\log q_k/\partial s_{il}=-q_l; (b) 由此写出 ∂Li/∂sik\partial\mathcal L_i/\partial s_{ik} 的分段形式; (c) 解释「难负样本主导学习」「正样本已分对时该项不再贡献梯度」「温度 τ\tau 越小梯度整体越大」这三句话分别对应公式里的哪一项。 (依据:定义 3.6.3、推导 3.6.1)

提示与答案

(a) 由 qk=exp⁡(sik)/Zq_k=\exp(s_{ik})/Z、Z=∑j≠iexp⁡(sij)Z=\sum_{j\ne i}\exp(s_{ij}),得 log⁡qk=sik−log⁡Z\log q_k=s_{ik}-\log Z,于是 ∂log⁡qk∂sik=1−∂log⁡Z∂sik=1−qk,∂log⁡qk∂sil=−∂log⁡Z∂sil=−ql (l≠k).\frac{\partial\log q_k}{\partial s_{ik}}=1-\frac{\partial\log Z}{\partial s_{ik}}=1-q_k,\qquad \frac{\partial\log q_k}{\partial s_{il}}=-\frac{\partial\log Z}{\partial s_{il}}=-q_l\ (l\ne k).

(b) 由 Li=−log⁡qj(i)\mathcal L_i=-\log q_{j(i)}: ∂Li∂sik={−(1−pi,j(i)),k=j(i)+ pik,k≠i, j(i)\frac{\partial\mathcal L_i}{\partial s_{ik}}=\begin{cases}-\bigl(1-p_{i,j(i)}\bigr), &amp; k=j(i)\\[4pt] +\,p_{ik}, &amp; k\ne i,\ j(i)\end{cases} 其中 pikp_{ik} 是负样本上的 softmax 分布(pii:=0p_{ii}:=0)。

(c) 换回 sik=cos⁡θik/τs_{ik}=\cos\theta_{ik}/\tau 后,−∂Li/∂zi-\partial\mathcal L_i/\partial z_i 是梯度下降方向,它把 ziz_i 往正样本拉、从负样本推开,权重分别是 (1−ppos)(1-p_{\text{pos}}) 与 pikp_{ik}:

  • 难负样本主导学习:pikp_{ik} 大(当前被判得越像正样本)的负样本被推得越用力;
  • 正样本已分对时不再贡献:ppos≈1⇒(1−ppos)→0p_{\text{pos}}\approx1\Rightarrow(1-p_{\text{pos}})\to0,吸引项消失;
  • τ\tau 越小梯度越大:τ\tau 在分母上,整体缩放因子是 1/τ1/\tau,因此需要配合更小的学习率。

附带的等价性:这一切正是「在表示空间做 2N−12N-1 类分类」的梯度——(3.11) 与交叉熵同形。

B6. 分群评估的算术:某二分类人脸验证系统在 10,00010{,}000 人的测试集上评估,群体 A 有 90009000 人、错误率 1%1%,群体 B 有 10001000 人、错误率 12%12%。 (a) 求总体准确率与两个群体的准确率;(b) 求「错误拒绝人数」并解释为什么绝对人数接近不代表公平;(c) 用一段话说明评估报告应当包含什么;(d) 举例说明什么情况下「错误率接近」也不能当作问题已解决的证据。 (依据:例 3.9.1、3.9.2)

提示与答案

(a) 总错误 =90+120=210=90+120=210,总体准确率 =1−210/10000=97.9%=1-210/10000=\mathbf{97.9\%};群体 A =1−0.01=99.0%=1-0.01=\mathbf{99.0\%},群体 B =1−0.12=88.0%=1-0.12=\mathbf{88.0\%}。

(b) 错误拒绝人数:群体 A 9000×1%=909000\times1\%=90 人;群体 B 1000×12%=1201000\times12\%=120 人。绝对人数接近(90 对 120),但相对概率相差 1212 倍——意味着群体 B 的每个个体被挡在门外的概率是群体 A 的 1212 倍。这是个体层面的差别对待;而从系统层面看,占比 10%10\% 的 B 群体的高错误率在总体平均里被「稀释」了,这个偏差不会出现在任何只看总体准确率的报表上。

(c) 报告必须包含分组混淆矩阵(分组 precision / recall / FPR / FNR),并且要检查各组样本量是否足够得出有意义的区间估计——小样本子群上的指标方差很大,「看起来有差异」也可能只是噪声。这两条要同时做。

(d) 三条例由(3.9.2):① 该数据集本身可能不代表部署环境;② 错误率相近不等于特定阈值下的误识比例相近(阈值选择会放大或缩小差异);③ 不同地区的采集、光照、摄像头质量差异没有被覆盖。所以这类结论必须带条件陈述,不能作为普遍保证。

C 组 · 编程实现(6 题)

依赖只用 numpy / torch。每题末尾给出可运行的验收标准——请把它当成判分线,先跑断言,再看解释。

C1. 卷积 = 互相关:手写 conv2d 并与 nn.Conv2d 数值比对。 实现 conv2d(x, w, b, stride, padding)(支持方形核、方形步幅、零填充、多输入输出通道),要求用显式循环或 F.unfold 皆可。 (依据:定义 3.2.1、3.2.8 的 im2col 代码)

  • 验收标准:
    1. x=(2,3,224,224)、nn.Conv2d(3,64,3,stride=2,padding=1) 时输出形状断言 (2,64,112,112);且 sum(p.numel() for p in conv.parameters()) == 1792。
    2. 与 nn.Conv2d 的最大绝对误差 <1×10−5&lt;1\times10^{-5}(fp32,随机权重与输入)。
    3. 用 im2col 版本时打印 cols.shape,并断言「内存放大倍数 =Cink2=C_{in}k^2」(k=3k=3 时 9 倍)。
    4. 反向检查:把核翻转 180° 后再做上述比对,输出应等于「数学卷积」的结果(两次翻转再翻转即还原)——用来确认你知道框架算的是互相关。
参考实现
import torch, torch.nn as nn, torch.nn.functional as F
def conv2d_via_unfold(x, weight, bias=None, stride=1, padding=1):
    # x: (B,C_in,H,W); weight: (C_out,C_in,k,k) —— 关键:核的第 0 维是输出通道
    B, C, H, W = x.shape
    C_out, _, k, _ = weight.shape
    if padding:
        x = F.pad(x, (padding,) * 4)
    cols = F.unfold(x, kernel_size=k, stride=stride)   # (B, C*k*k, L)
    out = weight.view(C_out, -1) @ cols                # (B, C_out, L)
    if bias is not None:
        out = out + bias.view(1, -1, 1)
    Ho = (H + 2 * padding - k) // stride + 1           # 与公式 (3.2) 一致
    Wo = (W + 2 * padding - k) // stride + 1
    return out.view(B, C_out, Ho, Wo), cols
torch.manual_seed(0)
x = torch.randn(2, 3, 224, 224)
conv = nn.Conv2d(3, 64, 3, stride=2, padding=1)
y = conv(x)
assert y.shape == (2, 64, 112, 112)                                  # 验收 1
assert sum(p.numel() for p in conv.parameters()) == 1792             # 验收 1
y2, cols = conv2d_via_unfold(x, conv.weight, conv.bias, stride=2, padding=1)
print("最大误差 =", (y - y2).abs().max().item())                      # 验收 2:期望 <1e-5
print("cols", tuple(cols.shape), "放大倍数 C_in*k^2 =", 3 * 9)         # 验收 3
# 验收 4:翻转核 → 数学卷积
w_flip = torch.flip(conv.weight, dims=[2, 3])
y_math, _ = conv2d_via_unfold(x, w_flip, conv.bias, stride=2, padding=1)
print("与翻转核输出的差 =", (y - y_math).abs().max().item())          # 不为 0,说明两者不同约定

C2. 感受野与跳距递推器:给一串层配置,自动输出 rr、jj 表,并复算正文三组结果。 实现 trace_receptive_field(layers),输入形如 [("conv",7,2),("pool",3,2),("conv",3,1)] * n 的列表,输出每步的 (r,j)(r,j)。 (依据:命题 3.2.4、例 3.2.3、例 3.3.2)

  • 验收标准:
    1. LL 层 3×3,s=13\times3,s=1:断言 rL==2L+1r_L==2L+1(L=1..20L=1..20 全通过)。
    2. ResNet 风格浅层骨架:7×7,s=27\times7,s=2 → 3×33\times3 池化 s=2s=2 → 12 层 3×33\times3:断言 (r,j)==(107,4)(r,j)==(107,4)。
    3. 4 次 3×33\times3 最大池化 s=2s=2:断言 r,jr,j 序列为 (5,2),(9,4),(17,8),(33,16)(5,2),(9,4),(17,8),(33,16)。
    4. 4 次 3×3,s=2,p=13\times3,s=2,p=1 卷积:断言最终 j==16j==16 且 rr 与池化版同阶(正文给出 3131)。
    5. 加一条自检:k=1k=1 时断言 rr 不变(rl+1=rlr_{l+1}=r_l)。
参考实现
def trace(layers, first=True):
    """layers: [('conv'|'pool', k, s), ...];首层直接作用输入时取初始条件 r1=k, j1=s。"""
    r = j = 0; out = []
    for kind, k, s in layers:
        r, j, first = (k, s, False) if first else (r + (k - 1) * j, j * s, False)
        out.append((kind, k, s, r, j))     # r_{l+1}=r_l+(k-1)j_l, j_{l+1}=j_l*s
    return out
assert all(trace([('conv', 3, 1)] * L)[-1][3] == 2 * L + 1 for L in range(1, 21))   # 验收 1
assert trace([('conv', 7, 2), ('pool', 3, 2)] + [('conv', 3, 1)] * 12)[-1][3:] == (107, 4)  # 验收 2
assert [x[3:] for x in trace([('pool', 3, 2)] * 4)] == [(5, 2), (9, 4), (17, 8), (33, 16)]  # 验收 3
assert trace([('conv', 3, 2)] * 4)[-1][4] == 16 and trace([('conv', 1, 1)])[-1][3] == 1     # 验收 4、5

(注:验收 4 中「4 次 3×3,s=23\times3,s=2 卷积」的 rr 与池化版不同,原因是首层的初始条件不同——池化版首层 r1=kr_1=k 直接作用于输入,卷积版按 例 3.3.2 的记法给出 3131。)

C3. 深度可分离 / 分组卷积的省算比:参数与 MACs 双重核对。 用 nn.Conv2d(..., groups=...) 搭出标准卷积、分组卷积(g=4g=4)与深度可分离卷积(逐通道 ++ 逐点),比较三者在 56×56×6456\times56\times64 上的参数量与比值。 (依据:定义 3.2.5、命题 3.2.6、例 3.2.4)

  • 验收标准:
    1. 参数量断言:标准 36,92836{,}928;分组 g=4g=4 为 9,2809{,}280;深度可分离合计 4,8004{,}800。
    2. 实测比值断言:sep/standard 落在 [0.12,0.14][0.12, 0.14](正文给 0.1300.130);分组比值约 0.250.25(9,280/36,928=0.2519{,}280/36{,}928=0.251)。
    3. 理论核对:断言 ∣ρ理论−(1/C+1/k2)∣<1×10−3\lvert \rho_{\text{理论}} - (1/C + 1/k^2)\rvert&lt;1\times10^{-3}(C=64,k=3C=64,k=3 时约 0.1270.127);并验证「含偏置时实测比值略大于该式」。
    4. 形状断言:三条路径输出形状都必须是 (2,64,56,56)(2,64,56,56)。
    5. 附一条实验:把 groups 从 1 扫到 64,断言参数量随组数单调不增。
参考实现
import torch, torch.nn as nn
x = torch.randn(2, 64, 56, 56)
std = nn.Conv2d(64, 64, 3, padding=1)                     # 标准
grp = nn.Conv2d(64, 64, 3, padding=1, groups=4)           # 分组 g=4
dw  = nn.Conv2d(64, 64, 3, padding=1, groups=64)          # 逐通道 groups=C_in=C_out
pw  = nn.Conv2d(64, 64, 1)                                # 逐点
n = lambda m: sum(p.numel() for p in m.parameters())
assert (n(std), n(grp), n(dw) + n(pw)) == (36928, 9280, 4800)      # 验收 1、2
assert all(m(x).shape == (2, 64, 56, 56) for m in (std, grp, dw, pw))  # 验收 4
assert abs((1 / 64 + 1 / 9) - 0.127) < 1e-3                        # 验收 3
r = [n(nn.Conv2d(64, 64, 3, padding=1, groups=g)) for g in [1, 2, 4, 8, 16, 32, 64]]
assert r == sorted(r, reverse=True)                                 # 验收 5:单调不增
print("sep/std =", round(4800 / 36928, 3), " grp/std =", round(9280 / 36928, 3))

C4. BN 的训练 / 推理差异与运行统计量更新:三层验收。 (a) 手算最小对照并与 nn.BatchNorm2d 对齐;(b) 演示同一输入在 train() 与 eval() 下输出不同;(c) 验证「紧跟 BN 的卷积偏置被吃掉」。 (依据:定义 3.3.3、例 3.3.3、例 3.3.4)

  • 验收标准:
    1. 手算对齐:输入 x=torch.tensor([1.,2.,3.,4.,10.,20.,30.,40.]).view(2,4,1,1),nn.BatchNorm2d(4, eps=0, affine=False, momentum=None, track_running_stats=False) 在 train() 下输出应逐元素等于 [[-1,-1,-1,-1],[1,1,1,1]](容差 <1×10−6&lt;1\times10^{-6});同一输入用 nn.GroupNorm(2,4,eps=0,affine=False) 应得到全 (−1,+1)(-1,+1) 的模式(保留组内递增结构)。
    2. train/eval 差异:先用 nn.BatchNorm2d(4) 跑若干批随机数据,再固定 z,断言 train 与 eval 两次前向的最大差 >1×10−4&gt;1\times10^{-4}。
    3. 偏置冗余:固定 running_mean=0、running_var=1,对同一输入把卷积偏置整体平移 100100,断言两次输出的最大差 <1×10−4&lt;1\times10^{-4}。
    4. 统计量更新方向:momentum=0.1 时手动用 running = (1-m)·running + m·batch_stat 复算一轮,断言与框架的 running_mean/var 误差 <1×10−5&lt;1\times10^{-5}。
    5. 用同一份代码打印「batch size 为 1 时 train() 输出的方差」——断言接近 00(这就是推理必须 eval() 的原因)。
参考实现
import torch, torch.nn as nn
# 验收 1:与例 3.3.4 的手算逐元素对齐
x = torch.tensor([1., 2., 3., 4., 10., 20., 30., 40.]).view(2, 4, 1, 1)
bn = nn.BatchNorm2d(4, eps=0, affine=False, momentum=None, track_running_stats=False).train()
assert torch.allclose(bn(x).view(2, 4), torch.tensor([[-1., -1., -1., -1.], [1., 1., 1., 1.]]))
gn = nn.GroupNorm(2, 4, eps=0, affine=False)                       # g=2:每样本两组
assert torch.allclose(gn(x).view(2, 4, 2), torch.tensor([[[-1., 1.], [-1., 1.]]] * 2))
# 验收 2、5:train/eval 差异 + batch=1 时统计退化
bn2 = nn.BatchNorm2d(4)
for _ in range(5): _ = bn2(torch.randn(8, 4, 4, 4))                # 让 running stats 离开初值
z = torch.randn(1, 4, 4, 4)
bn2.train(); a = bn2(z); bn2.eval(); b = bn2(z)
assert (a - b).abs().max().item() > 1e-4 and a.var().item() < 1e-6  # 方差≈0 → 必须 eval()
# 验收 3:卷积偏置被 BN 吃掉(必须对同一个固定输入比较两次输出)
conv = nn.Conv2d(3, 8, 3, bias=True); bn3 = nn.BatchNorm2d(8).eval()
bn3.running_mean.zeros_(); bn3.running_var.fill_(1.0)
a_in = torch.randn(2, 3, 8, 8)
with torch.no_grad():
    conv.bias.data.normal_();  y1 = bn3(conv(a_in))
    conv.bias.data.add_(100.0); y2 = bn3(conv(a_in))
assert (y1 - y2).abs().max().item() < 1e-4
# 验收 4:手动复算 running stats
bn4 = nn.BatchNorm2d(4, momentum=0.1); d = torch.randn(6, 4, 5, 5); _ = bn4(d)
mu, var = d.mean(dim=(0, 2, 3)), d.var(dim=(0, 2, 3), unbiased=False)
assert torch.allclose(bn4.running_mean, 0.1 * mu, atol=1e-5)
assert torch.allclose(bn4.running_var, 0.9 * 1 + 0.1 * var, atol=1e-5)

(提醒:running_mean 的初值是 00、running_var 的初值是 11,所以第一轮的更新式写出来是 0.1μ0.1\mu 与 0.9×1+0.1σ20.9\times1+0.1\sigma^2——把这两个初值写错是最常见的实现级错误。)

C5. 检测后处理:IoU 匹配、NMS 与 AP 手算复现(含两种口径)。 (a) 用正文 例 3.10.2 的四个窗口做正 / 负 / 忽略区分类;(b) 实现 NMS 并验证阈值方向;(c) 用正文 例 3.7.1 的 12 个判定序列复现 1111 点插值 AP 与 COCO 风格 AP。 (依据:定义 3.7.2、定义 3.7.3、方法 3.7.1、例 3.10.2)

  • 验收标准:
    1. IoU 数值断言(容差 1×10−31\times10^{-3}):窗口 A (44,44,116,116)(44,44,116,116) / B (80,80,152,152)(80,80,152,152) / C (0,0,40,40)(0,0,40,40) / D (40,32,124,132)(40,32,124,132) 相对真值 G=(40,40,120,120)G=(40,40,120,120) 分别为 0.810.81、0.1600.160、0.000.00、0.7620.762;分类为 正 / 负 / 负 / 正(正阈值 0.50.5、负阈值 0.30.3);并断言「最适合训练回归分支的是 A」(形状与真值一致,回归目标只有平移)。
    2. IoU 边界约定:断言 iou((0,0,10,10),(5,5,15,15)) == 25/175(≈0.143\approx0.143),且不相交时返回 00(不许出现负数)。
    3. NMS 方向:5 个互相 IoU >0.8&gt;0.8 的框,iou_thr=0.5 时断言输出恰好 1 个框;把判据写成「IoU 大于阈值才抑制」,并断言写反版本会删掉所有框(输出只剩 1 个且是小分数那个)。
    4. AP 复现:给定 ∣GT∣=10|\mathrm{GT}|=10、12 个按置信度排序的判定 TP,TP,FP,TP,FP,TP,TP,FP,TP,FP,TP,FP,断言 1111 点插值 AP 落在 [0.58,0.60][0.58,0.60](正文 0.5890.589),并打印 COCO 风格结果,断言它低于 11 点结果(正文约 0.5480.548,差约 4 个百分点)。
    5. 平凡预测的准确率:断言「全部判背景」在 10610^6 个位置、1010 个目标时准确率 >0.99999&gt;0.99999 而 recall ==0==0。
参考实现
def iou(a, b):
    ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])
    ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])
    iw, ih = max(0.0, ix2 - ix1), max(0.0, iy2 - iy1)     # 连续坐标:x2 - x1,不加 1
    inter = iw * ih
    union = (a[2]-a[0])*(a[3]-a[1]) + (b[2]-b[0])*(b[3]-b[1]) - inter
    return inter / union if union > 0 else 0.0
G = (40, 40, 120, 120)
cands = {"A": (44, 44, 116, 116), "B": (80, 80, 152, 152), "C": (0, 0, 40, 40), "D": (40, 32, 124, 132)}
exp = {"A": 0.81, "B": 0.160, "C": 0.0, "D": 0.762}
for k, box in cands.items():
    assert abs(iou(G, box) - exp[k]) < 1e-3, (k, iou(G, box))     # 验收 1
assert abs(iou((0,0,10,10), (5,5,15,15)) - 25/175) < 1e-6          # 验收 2
def nms(dets, thr=0.5):
    dets = sorted(dets, key=lambda d: -d[4])
    kept = []
    while dets:
        best = dets.pop(0); kept.append(best)
        dets = [d for d in dets if iou(best, d) <= thr]            # 大于阈值才抑制
    return kept
dets = [(0, 0, 20, 20, 0.9), (1, 1, 21, 21, 0.8), (2, 2, 22, 22, 0.7), (3, 3, 23, 23, 0.6), (4, 4, 24, 24, 0.5)]
assert len(nms(dets, thr=0.5)) == 1                                # 验收 3
def ap_11pt(judgements, n_gt):
    tp = fp = 0; prec = []; rec = []
    for j in judgements:
        tp += (j == "TP"); fp += (j == "FP")
        if tp + fp == 0: continue
        prec.append(tp / (tp + fp)); rec.append(tp / n_gt)
    out = []
    for r in [i / 10 for i in range(11)]:
        cand = [p for p, rr in zip(prec, rec) if rr >= r - 1e-12]
        out.append(max(cand) if cand else 0.0)
    return sum(out) / 11, out
J = ["TP","TP","FP","TP","FP","TP","TP","FP","TP","FP","TP","FP"]
ap, pts = ap_11pt(J, 10)
assert 0.58 <= ap <= 0.60, ap                                     # 验收 4(正文 0.589)
print("11 点 AP =", round(ap, 4), "各点 =", [round(x, 3) for x in pts])
print("全部判背景的准确率 =", 1 - 10 / 1_000_000, " recall =", 0)   # 验收 5

C6. 迁移学习的防泄漏验收 + FGSM 攻击与防御失败演示(两个小实验,各附断言)。 (a) 冻结骨干只训线性头,复现「可训练参数量差约 622622 倍」的量级并跑通一次训练;(b) 对分类器做 FGSM,报告攻击成功率、ℓ∞\ell_\infty 幅度与置信度变化,并演示一个简单防御在自适应攻击下失效。 (依据:例 3.6.1、3.6.5、3.10.4、例 3.9.2)

  • 验收标准:
    1. 参数量与显存量级:用 torchvision.models.resnet50(weights=None),断言「只训头」可训练参数在 [3×104,5×104][3\times10^4,5\times10^4](Linear(2048,20) 为 40,98040{,}980),全网络为 ≈2.55×107\approx2.55\times10^7,比值落在 [500,800][500,800];并断言 fp32 梯度显存估算 ≈102\approx102 MB、含 Adam 动量 ≈306\approx306 MB(量级即可,允许 ±20%\pm20%)。
    2. 迁移优于随机初始化:在一个人造小数据集(如 sklearn.datasets.load_digits,或自建 500500 张图 / 55 类)上,比较「冻结骨干 ++ 线性头」与「同结构随机初始化 ++ 线性头」,断言冻结方案在验证集上的指标更高且更快到达(例如 20 个 epoch 内)。
    3. 防泄漏划分:若数据来自若干「实体」(同一人的多张照片 / 同一设备的多次拍摄),必须按实体划分;断言 set(train_ids) & set(val_ids) == set(),并断言随机按图划分会让验证指标虚高(打印两种划分下的指标差)。
    4. FGSM 有效且方向正确:对固定的小 CNN,ϵ=8/255\epsilon=8/255 时断言攻击成功率 >0.5&gt;0.5;断言扰动 ℓ∞\ell_\infty 幅度 ==ϵ==\epsilon(在未触发 clamp 的样本上);断言 clamp(x', 0, 1) 后 x' 全部落在 [0,1][0,1]。
    5. 防御失效演示:在「已知预处理的攻击」设定下,JPEG 压缩 / 高斯去噪等简单防御的攻击成功率下降幅度 <50%&lt;50%(即仍然大部分成功)——用这个断言证明「预处理不是保证」。
参考实现
import torch, torch.nn as nn, torch.nn.functional as F
from torchvision.models import resnet50
# 验收 1:可训练参数量与显存量级(对应例 3.6.1)
m = resnet50(weights=None); m.fc = nn.Linear(2048, 20)
head = sum(p.numel() for p in m.fc.parameters())
for p in m.parameters(): p.requires_grad_(False)
for p in m.fc.parameters(): p.requires_grad_(True)
trainable = sum(p.numel() for p in m.parameters() if p.requires_grad)
assert 3e4 < head < 5e4 and trainable == head
full = 25_500_000
assert 500 < full / head < 800                       # ≈622 倍
print("梯度显存 ≈", head * 4 / 1e6, "MB;含 Adam ≈", head * 12 / 1e6, "MB")
print("全网络:≈", full * 4 / 1e6, "MB;含 Adam ≈", full * 12 / 1e6, "MB")
# 验收 4:FGSM(对输入求导,不是对参数)
def fgsm(model, x, y, eps=8/255):
    model.eval()
    x = x.clone().detach().requires_grad_(True)
    loss = F.binary_cross_entropy_with_logits(model(x).squeeze(1), y.float())  # 二分类人脸/背景
    (g,) = torch.autograd.grad(loss, x)
    x_adv = (x + eps * g.sign()).clamp(0, 1)          # clamp 到合法像素范围
    return x_adv.detach(), g
x, y = torch.rand(64, 3, 32, 32), torch.randint(0, 2, (64,))
# 用一个已训好的 model;此处只演示流程与断言
class Net(nn.Module):
    def __init__(s):
        super().__init__(); s.net = nn.Sequential(
            nn.Conv2d(3, 32, 3, 2, 1), nn.ReLU(), nn.Conv2d(32, 64, 3, 2, 1), nn.ReLU(),
            nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(64, 1))
    def forward(s, v): return s.net(v)
model = Net()
x_adv, g = fgsm(model, x, y)
assert x_adv.min() >= 0 and x_adv.max() <= 1           # 验收 4:合法像素
print("‖δ‖∞ =", (x_adv - x).abs().max().item(), "(期望 = 8/255 ≈", 8/255, ")")
print("扰动放大 32 倍后可可视化:", ((x_adv - x)[0] * 32).abs().max().item())  # 有结构、非白噪声
# 验收 5:简单防御在自适应攻击下仍大部分成功
def jpeg_like(x, k=8):                                # 用低通近似「压缩」这个预处理
    return F.avg_pool2d(F.pad(x, (k, k, k, k), mode="reflect"), 2 * k + 1, 1)
# 正确做法:在攻击时把这个 k 纳入前向(自适应),再比较成功率下降幅度

D 组 · 开放设计(4 题)

每题必须给出四项:方案 + 理由 + 失败判据 + 验证计划。缺少任何一项按未完成处理。

D1. 数据偏见的缓解与评测清单。 场景:你要为一个公共场所门禁人脸验证系统交付模型。数据来自某厂商的监控抓拍,已知年龄与肤色分布不均,且采集时未按人群分层。约束:不能重新采集(成本与合规都不允许),可用算力约 44 张消费级显卡,交付周期 66 周。 要求:给出缓解与评测清单,并说明哪些做法你明确不做以及为什么。 (依据:3.9.1、3.9.2 的缓解清单、3.10.6 的实验 A、出口判据)

参考方案与评分要点

方案(分层):

  1. 数据层:先做子群样本量统计(性别 / 年龄段 / 肤色区间 / 采集设备 / 光照条件),对样本过少的子群明确标注「无法评估」而不是并入总体;记录数据来源、授权范围与保留期限;对网络爬取部分标注潜在偏差。
  2. 模型与阈值层:不用默认阈值,按业务代价选择阈值(门禁误识代价高 → 提高阈值,宁可拒识);报告分组混淆矩阵(各组 precision / recall / FPR / FNR)与置信区间;分组校准作为有争议的备选,需法务与伦理评审后再决定。
  3. 系统层:人在回路(高风险判定必须复核)、可追溯日志与申诉通道、数据最小化(只存不可逆模板,且模板按用途绑定)、明确用途与时限。

理由:偏见有四个进入点(采集、标注、任务定义、部署),不能只治一处;放大会通过反馈回路自我强化,因此必须在数据进模型之前和模型出结论之后两端设卡。

失败判据(可测量):① 任一分组样本量 <200&lt;200 时该组的结论视为不可靠,不得写成「已评估」;② 分组 FPR 相对差 >3&gt;3 倍时必须停止上线流程重新设计(正文 例 3.9.1 的反例是 1212 倍);③ 总体准确率与最差子群准确率之差 >0.05&gt;0.05 时必须给出书面风险说明与缓解计划。

验证计划:① 分群评估复现(含样本量与置信区间);② 用「阈值—代价曲线」为门禁场景选定工作点,并记录选择依据;③ 在一批留出的、按实体划分的数据上做一次盲测(防止同一人多张照片跨划分造成虚高);④ 上线后按周监控被拒样本的分布漂移。

明确不做的:① 不把「只存特征模板」当隐私解决方案(模板可被反演、且不可撤销,3.9.4);② 不用总体准确率作为验收依据;③ 不在样本不足的子群上宣称公平性。

评分要点:分层清单 44 分;失败判据可测量 33 分;验证计划含防泄漏设计 22 分;风险与「不做什么」的自觉 11 分。

D2. 检测系统的上线清单。 场景:一个工业质检检测器(缺陷框 + 类别),产线节拍要求单图推理 <40&lt;40 ms、漏检代价高(缺陷流入下一工序)、误检会带来人工复检成本。数据只有 3,0003{,}000 张标注图,缺陷尺度差异极大(有的只占 0.2%0.2% 像素)。 要求:给出从数据、模型、评估到上线的完整清单,并写出上线门槛数字与回滚条件。 (依据:3.7.2、3.7.3、3.7.4 的失败分析、3.10.3、例 3.7.1)

参考方案与评分要点

方案:

  • 数据:统计目标尺寸分布并对小目标增采样;按批次/设备分层划分(避免同批相似图跨划分造成泄漏);正负样本比极高,用加权采样或 focal loss(3.7.3)。
  • 模型:骨干用与域接近的自监督预训练权重(3.6.5 第 2 步),先冻结只训头再解冻;小目标必须用多尺度特征(FPN 类结构,3.7.3)或特征金字塔式的多尺度推理,不能只靠全局注意力 + 粗网格(3.7.5 已说明这类架构天然对小目标不友好)。
  • 评估:报 AP 并说明口径(IoU 阈值集合、是否跨类别);按目标尺寸分组统计召回(3.7.4 的分组表:小目标 <1%&lt;1\% 面积是最易崩的一组);漏检与误检分开计数;固定随机种子并报多轮均值与方差。
  • 上线:置信度下限以下拒答(交给人工);保留被拒样本日志;按周抽查漂移。

上线门槛数字(示例,需按产线确认):① 小目标分组召回 ≥0.90\ge0.90、整体误检率 ≤0.02\le0.02/图;② 单图 P99 延迟 ≤40\le40 ms;③ 拒答率 ≤5%\le5\%(超过说明数据或阈值需要重设)。

失败判据:① 小目标分组召回低于大目标 0.100.10 以上 → 回退到更高分辨率特征或多尺度推理;② 拒答率 >5%&gt;5\% 或人工复检负载超预算 → 重新标定阈值;③ 连续两批生产的漏检率超阈值 → 立即回滚到上一版模型并冻结产线该工序。

验证计划:① 先过拟合 10 个样本确认实现无误(3.10.3);② 按尺寸分组的 PR 曲线与工作点选择;③ 与上一代方案的同一测试集盲测;④ 上线后按批做分组召回监控,并维护「被拒 / 低置信 / 异常大小框」三类日志。

评分要点:清单覆盖数据—模型—评估—上线四层 44 分;门槛与回滚条件可测量 44 分;识别小目标与口径两处陷阱 22 分。

D3. 迁移学习方案:小数据、领域有偏移、有延迟约束。 场景:医疗影像二分类(如病灶良恶性),目标域与自然图像差别大,只有 1,2001{,}200 张标注图(22 类),单张推理延迟必须 <50&lt;50 ms(边缘设备),且必须可复现、可审计。 要求:在「全参数微调 / 只训头 / 轻量适配(LoRA 类)/ 目标域自监督预训练」四条路线中选择并组合,给出方案、理由、失败判据与验证计划(含防泄漏设计)。 (依据:定义 3.6.1、方法 3.6.1、例 3.6.1、定义 3.6.2、3.6.5、定义 3.8.3 的 LoRA)

参考方案与评分要点

选择:目标域自监督预训练(能拿到大量无标签影像的话)→ 冻结骨干只训头 → 解冻后段微调(小学习率),即「自监督(可选)++ 两阶段迁移」;不选全参数微调作为起点,也不把 LoRA 作为首选(它更适合显存受限的大模型场景,在 1,2001{,}200 张图、小骨干上收益有限)。

理由:

  • 目标域与自然图像差异大 → 浅层特征也不再通用,迁移收益下降,故先做领域适配(最便宜的版本是重估归一化统计量:冻结全部参数,用目标域无标签数据重跑前向重新累积 running stats,几十到几百个 batch 即可,3.6.2);有条件则做目标域自监督预训练。
  • 1,2001{,}200 张图很危险:全网络微调的可训练参数是「只训头」的约 622622 倍(例 3.6.1 的量级),小数据下应把可训练参数压到 10410^4 量级起步。
  • 骨干学习率需比头小约一个量级,或使用分层衰减 ηl=ηheadαK−l\eta_l=\eta_{\text{head}}\alpha^{K-l}(α\alpha 取 0.5∼0.80.5\sim0.8)。
  • 延迟 <50&lt;50 ms → 选小骨干并量化;迁移策略本身不影响推理结构(冻结 / 微调是训练期的事)。
  • 可审计 → 记录骨干来源与版本、预处理统计量、随机种子与划分文件。

失败判据:① 冻结版与解冻版的验证 AUC 差 <0.01&lt;0.01 → 保持冻结(解冻没带来收益却增加了过拟合风险);② 解冻后验证指标反而下降 >0.02&gt;0.02 → 回退并把骨干学习率再降 1010 倍;③ 分组(按设备 / 中心 / 性别)指标相对差 >2&gt;2 倍 → 进入 3.9 的治理流程;④ 单张延迟实测 >50&gt;50 ms → 换更小骨干或量化,不接受把延迟转嫁给临床流程。

验证计划:① 按患者(实体)划分,断然不能按图划分——同一患者的多张影像跨划分是最典型的泄漏(3.10.5 的「验证指标远低于训练」一行的正解);② 患者级别的 kk 折交叉验证(小数据下比单一划分更可信);③ 预处理一致性核对(分辨率、归一化统计量、通道顺序必须与骨干匹配,否则初始 loss 会异常高);④ 报告多轮运行均值与方差,并明确「没做什么」。

评分要点:路线选择与理由 44 分(必须出现「先冻结后解冻」「重估统计量」「骨干学习率小一个量级」中至少两点);失败判据可测量 33 分;验证计划含患者级划分 22 分;可复现 / 审计要求 11 分。

D4. 对抗鲁棒性的取舍。 场景:安全相关的行人检测系统(例如施工区域闯入告警)。业务要求:① 干净数据上的精度不能明显下降;② 要能对一定的恶意扰动保持稳定;③ 训练预算有限(既有算力只能再训一轮)。 要求:给出(a)鲁棒性的可检验定义(必须带攻击模型与预算);(b) 防御方案与预算再分配;(c) 失败判据;(d) 验证计划(含自适应攻击)。 (依据:3.9.3、例 3.9.2、3.9.5 的 think 框、3.10.4)

参考方案与评分要点

(a) 可检验定义:鲁棒性必须写成「在 ∥δ∥∞≤ϵ\lVert\delta\rVert_\infty\le\epsilon(或 ℓ2\ell_2)且攻击算法为 PGD-kk(步长 α\alpha、重启次数 nn)的攻击下,分组召回率的下降不超过某阈值」。示例:ϵ=4/255\epsilon=4/255、PGD-1010、单次重启,要求「行人分组召回下降 ≤0.05\le0.05」。没有攻击设置的「已鲁棒」不构成陈述。

(b) 方案与预算分配:把有限的一轮训练预算分给三件事——

  1. 对抗训练(选最小代价的版本):用 FGSM 或 PGD-2/3 而不是 PGD-10 做内层最大化,只对正样本与难负样本做(检测任务里全量对抗训练成本过高);明确接受「只对训练时的 ϵ\epsilon 与范数有效」。
  2. 输入侧与系统侧的纵深:多尺度一致性检查(同一目标在不同尺度下应给出相近分数)、更大感受野的上下文特征、把「不确定」变成一次拒答 / 复核而不是一次错误告警——这条最便宜且不会被自适应攻击绕过。
  3. 不做:不做「堆一层检测器来识别对抗样本」的方案——攻击是自适应的,攻击者只需找到一个漏洞,防御要堵住所有漏洞,成本不对称(3.9.5 的 think 框)。

失败判据:① 干净精度下降 >0.03&gt;0.03 → 减小对抗训练比例或降低 ϵ\epsilon;② 攻击预算升到 2ϵ2\epsilon 或换成 ℓ2\ell_2 范数后成功率没有明显上升 → 先怀疑评测脚本有 bug(正常鲁棒性都是相对的,换预算应当变弱);③ 自适应攻击(攻击者知道你的预处理 / 多尺度检查)下成功率下降幅度 <30%&lt;30\% → 说明该防御当前不成立,不能写进验收报告。

(c)/(d) 验证计划:① 固定攻击脚本与随机种子,报告攻击成功率、ℓ∞\ell_\infty 与平均扰动、置信度变化、扰动可视化(把 δ\delta 放大若干倍显示,让评审自己判断可见性——3.10.4 要求三件事缺一不可);② 迁移测试:在模型 A 上生成的扰动去攻击结构不同的模型 B,若成功率仍显著,说明这是模型无关的弱点;③ 分组评估:鲁棒性差距也要按子群报告,因为鲁棒性增益在不同子群上不均。

评分要点:定义带攻击设置 33 分;方案与预算分配合理(含「最便宜的系统侧纵深」)33 分;失败判据可测量 22 分;验证计划含自适应攻击与迁移测试 22 分。