01 / 本课学习路线
本课学习路线
阅读与推演约 100 分钟,练习约 60 分钟,进阶练习另需约 15 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
三个计算共用一条思路:先把公式里的每个字母对应到题目的输入,再用一个能手算的小例子核对循环边界,最后按题目的并列规则和输出格式写代码。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 用一般式 ⌊(H+2P−k_eff)/S⌋+1 算输出尺寸,并说出 d=2 时 15 与 16 的差别在哪 | 第 04 节 | 自查第 2 条、练习 1 |
| 数出一层卷积的参数量,并说出卷积比全连接省参数的两个原因 | 第 04 节 | 自查第 3 条 |
| 写对 IoU 的交集宽高,处理相接、不相交和包含三种边界 | 第 05、06 节 | 自查第 4 条、练习 2 |
| 说清 NMS 里排序键和抑制规则各自的并列约定 | 第 05、06 节 | 自查第 5 条、练习 3 |
| 通过 AI013 与 AI014,输出前核对 4 位小数格式 | 第 06、07 节 | 自查第 1、6 条 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成模块 1 的「二维数组、坐标与边界处理」、模块 2 的「复合排序与并列规则」和模块 6 的「浮点精度与稳定 softmax」。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | (32 + 2 * 1 - 5) // 2 + 1 是多少?// 与 / 的区别是什么? | 数字与运算符 |
| 自测 2 | X = [[1, 2, 0], [0, 1, 3], [2, 1, 0]]:X[1][2] 是多少?X[-1][0] 会报错还是返回一个值? | 二维数组、坐标与边界处理 |
| 自测 3 | max(0, min(4, 6) - max(0, 2)) 和 max(0, min(2, 5) - max(0, 3)) 各是多少? | 常用内置函数 |
| 自测 4 | s = [0.8, 0.9, 0.8],sorted(range(3), key=lambda i: (-s[i], i)) 得到什么? | 复合排序与并列规则 |
| 自测 5 | f"{4 / 28:.4f}" 打出什么?f"{-0.0:.4f}" 呢? | 浮点精度与稳定 softmax |
展开先修自测答案
自测 1:29 // 2 + 1 = 14 + 1 = 15。// 是向下取整的整数除法,/ 得到浮点数 14.5 再加 1 是 15.5——尺寸公式必须用 //。这正是第 04 节表里 d=2 那一行。
自测 2:X[1][2] 是 3(第 1 行第 2 列,下标从 0 起)。X[-1][0] 不报错,返回 2——负下标从末尾往回数。卷积里越界坐标若不先判断,X[-1] 会取到最后一行,得到错误值而不是报错(第 08 节错误表第 2 行)。
自测 3:第一个是 max(0, 4 − 2) = 2,第二个是 max(0, 2 − 3) = max(0, −1) = 0。这就是 IoU 交集宽的写法:相交时得到正的宽,不相交时得到负数、被截成 0。
自测 4:[1, 0, 2]——先按 −s 升序(分数 0.9 最先),0.8 并列时再按下标升序。NMS 的处理顺序就是这个排序键。
自测 5:0.1429 和 -0.0000。IoU 非负,本课不会打出负零;但记住负零的存在,模块 8 · 第 2 课 的两位小数输出会再遇到它。
03 / 概念与术语
卷积核、步幅、填充、膨胀、有效核;交并比、抑制与保留顺序
三件事各有一组词。卷积的词全部落在输出尺寸公式的字母上;检测后处理的词落在 IoU 的分子分母和 NMS 的排序键上。
| 术语 | 含义 | 代码里的位置 |
|---|---|---|
卷积核(Ker)、核边长 K | K×K 的权重表;在输入上滑动,每停一格与窗口内的输入逐元素相乘再求和 | Ker[a][b],内两层循环走 a, b |
步幅(stride,S) | 窗口每次移动几格;S=2 时输出格 (i, j) 的窗口左上角在补零图的 (2i, 2j) | i * S + a - P |
填充(padding,P) | 在输入四周各补 P 圈 0;题目规定越界位置按 0 参与计算 | if 0 <= r < H and 0 <= c < W |
膨胀系数(dilation,d)与有效核 k_eff | 核元素之间隔 d−1 个格;有效覆盖边长 k_eff = d·(K−1)+1,d=1 时就是 K | conv_out 里的 k_eff |
| 输出尺寸 | ⌊(H + 2P − k_eff) / S⌋ + 1;AI013 保证整除,直接用整数除法 | conv_out |
| 互相关 | 深度学习里的「卷积」不翻转核,按位置直接对应相乘;翻转核是另一种运算 | X[r][c] * Ker[a][b](下标同向) |
| 交并比(IoU) | 交集面积 / 并集面积;并集 = 面积 A + 面积 B − 交集 | inter / union |
| 交集宽高截断 | 交集宽 = max(0, min(x2a, x2b) − max(x1a, x1b)),高同理;只相接时为 0 | max(0.0, ...) |
| 非极大值抑制(NMS) | 按分数从高到低处理;轮到的框保留,把与它 IoU 严格大于阈值 T 的未处理框压掉 | nms 主循环 |
| 排序键与并列 | (−分数, 下标):分数高的在前,同分下标小的在前 | key=lambda i: (-scores[i], i) |
| 保留顺序 | 输出按处理顺序排列的原始下标,不是按下标从小到大 | keep.append(i) |
两条约定贯穿三道题:第一,框的坐标是连续坐标,宽 = x2 − x1,不加 1(像素坐标才会加 1,本课的题目不用);第二,比较符号按题目原文——AI015 写的是「严格大于阈值 T」,等于阈值时不压掉。
04 / 尺寸与卷积
输出尺寸与卷积乘加
输出尺寸公式是本课所有代码的基础:输出尺寸决定循环边界,因此应先用示例验证尺寸公式。
| H | P | K | S | d | k_eff | 输出边长 |
|---|---|---|---|---|---|---|
| 4 | 0 | 3 | 1 | 1 | 3 | 2 |
| 4 | 1 | 3 | 1 | 1 | 3 | 4 |
| 224 | 1 | 3 | 2 | 1 | 3 | 112 |
| 32 | 1 | 3 | 2 | 2 | 5 | 15 |
最后一行用于验证膨胀系数 dilation 对有效卷积核尺寸的影响:漏算有效核(仍用 K=3)会得到 ⌊31/2⌋+1 = 16,正确答案是 ⌊29/2⌋+1 = 15。第二行是常见的「3×3 核配 1 圈填充」:输出和输入一样大。
一次完整的数值卷积:3×3 输入 × 2×2 核(步幅 1、无填充)
输入 X 核 K 1 2 0 1 0 0 1 3 0 -1 2 1 0 输出边长 = 3 − 2 + 1 = 2,所以结果是一个 2×2 的表
| 输出位置 | 窗口(输入的 2×2 子块) | 逐格乘加 | 结果 |
|---|---|---|---|
| out[0][0] | 1 2 / 0 1 | 1×1 + 2×0 + 0×0 + 1×(−1) | 0 |
| out[0][1] | 2 0 / 1 3 | 2×1 + 0×0 + 1×0 + 3×(−1) | −1 |
| out[1][0] | 0 1 / 2 1 | 0×1 + 1×0 + 2×0 + 1×(−1) | −1 |
| out[1][1] | 1 3 / 1 0 | 1×1 + 3×0 + 1×0 + 0×(−1) | 1 |
输出 = [[0, −1], [−1, 1]]。每一格都是「把核压在输入的一个 2×2 子块上,对应位置相乘再全部相加」;窗口左上角的坐标就是输出位置本身(步幅 1、无填充时输出格 out[i][j] 对应输入 X 的 (i, j) 起点)。核不翻转——深度学习里说的卷积按互相关计算。写下面的卷积函数(conv2d)之前先把这四行手算一遍,用来核对循环边界和下标对应关系。
输出尺寸函数(conv_out)与 AI013 版卷积函数(conv2d)模板
Python# conv_out 已写好;conv2d 是完整参考实现,建议先独立实现,再与下方参考实现核对
def conv_out(h, k, s, p, d=1):
k_eff = d * (k - 1) + 1 # d=1 时就是 k
return (h + 2 * p - k_eff) // s + 1
def conv2d(X, Ker, S, P):
# AI013 题目规则:越界位置按 0 参与计算,输出尺寸保证整除
H, W, K = len(X), len(X[0]), len(Ker)
OH, OW = (H + 2 * P - K) // S + 1, (W + 2 * P - K) // S + 1
out = [[0] * OW for _ in range(OH)]
for i in range(OH):
for j in range(OW):
s = 0
for a in range(K):
for b in range(K):
r, c = i * S + a - P, j * S + b - P
if 0 <= r < H and 0 <= c < W:
s += X[r][c] * Ker[a][b]
out[i][j] = s
return out外两层循环走输出格,内两层走核;AI013 保证 (H+2P−K) 可被 S 整除,因此可使用整数除法。深度学习惯称的「卷积」不翻转核(数学上是互相关),自己实现与在线测试都按不翻转处理。
参数量:卷积与全连接的对比
把 224×224 灰度图逐像素连到 128 个神经元,第一层就是 50,176×128+128 = 6,422,656 个参数;两层 3×3 卷积(3→16→32)合计只有 448+4,640 = 5,088 个。局部感受野和权重共享共同减少了卷积层的参数量。
张量布局与参数量:题目会说明张量布局是 NCHW(批次、通道、高、宽)还是 NHWC(批次、高、宽、通道),两种布局的循环嵌套顺序完全不同,先在纸上标出每个维度再写代码。参数量 C_in×K×K×C_out + C_out 与图多大无关:3→16 是 448,16→32 是 4,640。AI013 只有单通道、单张图,四层循环的顺序就是「输出行、输出列、核行、核列」。
补零为什么不用真建大矩阵:输出格 (i, j) 的窗口左上角在补零图的 (i·S, j·S),映射回原图是 r = i·S + a − P、c = j·S + b − P。r 或 c 落在 [0, H) 或 [0, W) 之外的位置,在补零图里就是 0,乘任何核元素都是 0——所以只要跳过这些位置,结果与真建一张 (H+2P)×(W+2P) 的大矩阵完全一样。第 06 节 AI013 示例 2 的九格表把每个越界项都标了出来。
补充学习(选学)感受野:两层 3×3 卷积可覆盖 5×5 区域约 4 分钟18 个权重与 25 个权重(不计偏置)的感受野对比,外加一次非线性
感受野是「输出的一格能看到多大一片输入」。一层 3×3 的输出格看 3×3;再叠一层 3×3,第二层的一格看的是第一层的 3×3 个格子,而它们各自又看了一圈,合起来正好 5×5。这一结论要求两层的步幅都为 1 且无膨胀;感受野相等只说明覆盖的输入范围相同,不表示两种网络等价。
参数量对比(单通道、不计偏置):两层 3×3 一共 9+9 = 18 个权重,一层 5×5 是 25 个。在上述条件下感受野相同,参数更少,两层之间还多一次非线性——经典卷积网络常堆叠多个 3×3 卷积层,以较少参数扩大感受野。
补充学习(选学)多通道与 1×1 卷积约 5 分钟逐通道乘加再相加;1×1 只在通道方向混合
多通道输入时,核也带同样多层:每个通道各做一次滑窗乘加,逐格相加再加偏置。输出想要 16 张特征图就配 16 组核——参数量公式 C_in×K×K×C_out + C_out 就是这么数出来的。
1×1 卷积的窗口只有一格:读取当前位置的全部输入通道做加权混合,不聚合空间邻居。把 256 通道压到 64 通道只要 256×1×1×64+64 = 16,448 个参数,比任何大核都省。
05 / IoU 与 NMS
交并比与非极大值抑制的规则
检测后处理包含两项核心计算:IoU 衡量重叠程度,NMS 根据得分和重叠程度选择保留框。交集宽高使用 max(0,·) 处理不相交情况;NMS 的处理顺序由得分和并列规则共同决定。
IoU 讲解例与三种边界
A=(0,0,4,4) 面积 16 B=(2,2,6,6) 面积 16 交集宽 = min(4,6)−max(0,2) = 2 高 = 2 交集 = 4 并集 = 16+16−4 = 28 IoU = 4/28 ≈ 0.1429(AI014 按 4 位小数打印) 相接:交集宽 = 0 → IoU = 0(AI014 明确「只共享一条边或一个点记 0」) 不相交:交集宽为负 → 使用 max(0,·) 截为 0 包含:小框在大框里 → IoU = 4/16 = 0.25
NMS 的完整流程:按分数从高到低排序(同分先处理输入下标更小的)→ 轮到的、尚未被去掉的框保留 → 把与它 IoU 严格大于阈值 T 的未处理框去掉 → 重复。AI015 有两处要求要逐字核对:比较符号是「严格大于」;输出是保留顺序下的原始下标。
交并比与非极大值抑制的参考实现
Pythondef iou(a, b):
# 框记作 (x1, y1, x2, y2) 连续坐标,宽 = x2 - x1
ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])
ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])
inter = max(0, ix2 - ix1) * max(0, iy2 - iy1) # 不相交时宽或高为负,截为 0
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
union = area_a + area_b - inter
return inter / union if union > 0 else 0.0
def nms(boxes, scores, thr):
# 排序键 (-分数, 下标):分数相同时先处理下标小的
order = sorted(range(len(boxes)), key=lambda i: (-scores[i], i))
keep = []
removed = set()
for i in order:
if i in removed:
continue
keep.append(i) # 轮到的框保留
for j in order:
if j != i and j not in removed and iou(boxes[i], boxes[j]) > thr:
removed.add(j) # 严格大于阈值才去掉
return keep
assert abs(iou((0, 0, 4, 4), (2, 2, 6, 6)) - 4 / 28) < 1e-9
assert iou((0, 0, 2, 2), (2, 0, 4, 2)) == 0.0 # 只共享一条边,记 0
assert iou((0, 0, 2, 2), (3, 3, 5, 5)) == 0.0 # 完全不相交
assert abs(iou((0, 0, 4, 4), (1, 1, 3, 3)) - 0.25) < 1e-9 # 包含:小框面积/大框面积
assert nms([(0, 0, 4, 4), (1, 0, 5, 4), (6, 6, 9, 9)], [0.9, 0.8, 0.7], 0.5) == [0, 2]
assert nms([(0, 0, 4, 4), (1, 0, 5, 4), (6, 6, 9, 9)], [0.9, 0.8, 0.7], 0.7) == [0, 1, 2]
assert nms([(0, 0, 2, 2), (0, 0, 2, 2)], [0.5, 0.5], 0.5) == [0]两个函数各有一处必须写对的地方:iou 的交集宽高一律先和 0 取较大值,否则不相交的两个框会算出正的「交集」;nms 的排序键是 (−分数, 下标),同分时先处理下标小的,并且只有 IoU 严格大于阈值才去掉。断言里的三种 IoU 边界(相接、不相交、包含)和两个阈值下不同的 NMS 结果,与上面的推演逐项对应。
卷积坐标、IoU 边界与 NMS 顺序检查
卷积应核对窗口左上角的坐标计算,IoU 应核对交集宽高为负时的处理,NMS 还需核对排序、阈值比较和并列规则。
为什么「被压掉的框不再影响其他框」是规则的一部分:NMS 的目标是每个目标只留一个代表框,代表框由分数最高者担任;已经被代表框压掉的框,本身不是代表,就没有资格再去压别的框。第 06 节 AI015 示例 2 是一条链——框 1 被框 0 压掉后,本来与框 1 重叠的框 2 得以保留。
06 / 题面示例逐步
AI013 两个示例、AI014 两个示例、AI015 两个示例
AI013「单通道卷积特征图」:第一行 H W K S P,接下来 H 行特征图、再 K 行卷积核;输出 OH 行、每行 OW 个整数。示例 1:3 3 2 1 0 / 1 2 3 / 4 5 6 / 7 8 9 / 1 0 / 0 -1 → -4 -4 / -4 -4。
| 输出位置 | 窗口(输入的 2×2 子块) | 逐格乘加 | 结果 |
|---|---|---|---|
| out[0][0] | 1 2 / 4 5 | 1×1 + 2×0 + 4×0 + 5×(−1) | −4 |
| out[0][1] | 2 3 / 5 6 | 2×1 + 3×0 + 5×0 + 6×(−1) | −4 |
| out[1][0] | 4 5 / 7 8 | 4×1 + 5×0 + 7×0 + 8×(−1) | −4 |
| out[1][1] | 5 6 / 8 9 | 5×1 + 6×0 + 8×0 + 9×(−1) | −4 |
这个核只保留「左上减右下」:每个窗口的左上与右下相差 4,所以四格全是 −4。它与第 04 节的核相同、输入不同——逐格核对两张表的结果,再用带填充、不同步幅的输入检查坐标映射和循环边界。
示例 2:3 3 3 1 1 / 1 2 3 / 4 5 6 / 7 8 9 / 0 1 0 / 1 1 1 / 0 1 0 → 7 11 11 / 17 25 23 / 19 29 23。K=3、P=1、S=1,输出尺寸 ⌊(3+2−3)/1⌋+1 = 3,和输入一样大。这个核是十字形,只有中心与上下左右五个位置的权重是 1,所以每个输出格就是「自己 + 四邻」之和,越界的邻居记 0。
| 输出位置 | 中心(原图坐标) | 参与相加的项 | 结果 |
|---|---|---|---|
| out[0][0] | X[0][0] = 1 | 上 (−1,0) 越界 0 + 左 (0,−1) 越界 0 + 1 + 右 2 + 下 4 | 7 |
| out[0][1] | X[0][1] = 2 | 上越界 0 + 左 1 + 2 + 右 3 + 下 5 | 11 |
| out[0][2] | X[0][2] = 3 | 上越界 0 + 左 2 + 3 + 右 (0,3) 越界 0 + 下 6 | 11 |
| out[1][0] | X[1][0] = 4 | 上 1 + 左 (1,−1) 越界 0 + 4 + 右 5 + 下 7 | 17 |
| out[1][1] | X[1][1] = 5 | 上 2 + 左 4 + 5 + 右 6 + 下 8 | 25 |
| out[1][2] | X[1][2] = 6 | 上 3 + 左 5 + 6 + 右 (1,3) 越界 0 + 下 9 | 23 |
| out[2][0] | X[2][0] = 7 | 上 4 + 左 (2,−1) 越界 0 + 7 + 右 8 + 下 (3,0) 越界 0 | 19 |
| out[2][1] | X[2][1] = 8 | 上 5 + 左 7 + 8 + 右 9 + 下 (3,1) 越界 0 | 29 |
| out[2][2] | X[2][2] = 9 | 上 6 + 左 8 + 9 + 右 (2,3) 越界 0 + 下 (3,2) 越界 0 | 23 |
括号里是映射回原图的坐标 (r, c) = (i·S + a − P, j·S + b − P)。四个角各有两项越界,四条边中点各有一项越界,正中央没有越界项。越界坐标里有负数——若不先判断范围,Python 的负下标会取到最后一行或最后一列(第 08 节错误表第 2 行给出了错误输出)。
AI014「IoU 矩阵」:第一行 N M,接下来 N 行预测框、再 M 行真值框,每行 x1 y1 x2 y2;输出 N 行、每行 M 个 IoU,四舍五入保留 4 位小数。示例 1:2 1 / 0 0 2 2 / 1 1 3 3 / 0 0 2 2 → 1.0000 / 0.1429;示例 2:1 1 / 0.5 0.5 2.5 1.5 / 1.0 0.0 3.0 2.0 → 0.3333。
| 预测框 → 真值框 | 交集宽 | 交集高 | 交集 | 面积 A + 面积 B | 并集 | IoU | 打印 |
|---|---|---|---|---|---|---|---|
| (0,0,2,2) → (0,0,2,2) | min(2,2)−max(0,0) = 2 | 2 | 4 | 4 + 4 | 4 | 4/4 = 1 | 1.0000 |
| (1,1,3,3) → (0,0,2,2) | min(3,2)−max(1,0) = 1 | 1 | 1 | 4 + 4 | 7 | 1/7 = 0.142857… | 0.1429 |
| (0.5,0.5,2.5,1.5) → (1.0,0.0,3.0,2.0) | min(2.5,3.0)−max(0.5,1.0) = 1.5 | min(1.5,2.0)−max(0.5,0.0) = 1.0 | 1.5 | 2.0 + 4.0 | 4.5 | 1.5/4.5 = 0.3333… | 0.3333 |
并集 = 面积 A + 面积 B − 交集,交集只能减一次。四舍五入用 int(v * 10000 + 0.5) 再拆成整数部分与 4 位小数(第 07 节 fmt4)。再自测相接(0.0000)、不相交(0.0000)、包含(1/16 = 0.0625)、同框(1.0000)和负坐标:例如 (−3,−3,−1,−1) 与 (−2,−2,0,0) 的交集为 1、并集为 7,应输出 0.1429。每组先算交集与并集,再核对程序输出。
AI015「非极大值抑制」:第一行 N T,接下来 N 行 x1 y1 x2 y2 与分数(score);输出保留框的原始下标(从 0 起),按保留顺序排列。示例 1:3 0.30 / 0 0 4 4 0.90 / 1 1 5 5 0.80 / 10 10 12 12 0.70 → 0 2。
| 轮到 | 状态 | 与未处理框的 IoU | 动作 | 保留列表 |
|---|---|---|---|---|
| 框 0(0.90) | 未被压掉 | 对框 1:交集 3×3 = 9,并集 16+16−9 = 23,IoU 9/23 = 0.3913 > 0.30;对框 2:交集宽 min(4,12)−max(0,10) < 0 → 0 | 保留框 0;压掉框 1;框 2 不动 | [0] |
| 框 1(0.80) | 已被压掉 | 不再比较 | 跳过 | [0] |
| 框 2(0.70) | 未被压掉 | 没有未处理的框 | 保留框 2 | [0, 2] |
输出 0 2。示例 2:3 0.20 / 0 0 4 2 0.90 / 2 0 6 2 0.80 / 4 0 8 2 0.70 → 也是 0 2,但原因不同,见下表。
| 轮到 | 状态 | 与未处理框的 IoU | 动作 | 保留列表 |
|---|---|---|---|---|
| 框 0(0.90) | 未被压掉 | 对框 1:交集 2×2 = 4,并集 8+8−4 = 12,IoU 0.3333 > 0.20;对框 2:交集宽 min(4,8)−max(0,4) = 0 → 0(只相接) | 保留框 0;压掉框 1;框 2 不动 | [0] |
| 框 1(0.80) | 已被压掉 | 不再比较——它与框 2 的 IoU 也是 0.3333,但它已经没有资格压别人 | 跳过 | [0] |
| 框 2(0.70) | 未被压掉 | 没有未处理的框 | 保留框 2 | [0, 2] |
若让被压掉的框 1 继续去压别人,框 2 会被压掉、输出只剩 0(第 08 节错误表)。再自测同分保留下标小(3 0.50 / 0 0 2 2 0.50 / 0 0 2 2 0.50 / 5 5 6 6 0.50 → 0 2)、阈值 1 时同一个框也压不掉(→ 0 1)、IoU 恰等于阈值不压((0,0,2,2) 与 (0,0,2,1) 的 IoU 恰为 2/4 = 0.5,T=0.5 → 0 1)三组边界,逐项核对保留的下标。
07 / 从三个函数到程序
参考实现与三份完整程序,每一步落在哪几行
先用断言分别验证卷积函数(conv2d)、交并比函数(iou)与非极大值抑制函数(nms),再各自加上读入和输出组成完整程序;AI014 与 AI015 共用同一个 iou。
| 步骤 | AI013 | AI014 | AI015 |
|---|---|---|---|
| 读入 | H W K S P,H 行特征图,K 行核 | N M,N 行预测框,M 行真值框 | N T,N 行 x1 y1 x2 y2 score |
| 核心计算 | conv_out 定尺寸;四层循环乘加,越界跳过 | 逐对调用 iou | 排序键 (−分数, 下标);逐个处理,> thr 才压掉 |
| 并列 / 边界 | 整除保证成立,用 // | 交集宽高 max(0.0, ·);并集为 0 记 0 | 同分下标小在前;等于阈值不压;被压掉的框跳过 |
| 输出 | OH 行、每行 OW 个整数 | N 行、每行 M 个 4 位小数 | 一行保留顺序的下标 |
展开参考实现 1:卷积函数 conv2d 与尺寸函数 conv_out(自带断言;先自己写完再对照)
conv2d 的参考实现(自带断言)
Pythondef conv_out(h, k, s, p, d=1):
# 有效核 k_eff = d·(k−1)+1;d=1 时就是 k
k_eff = d * (k - 1) + 1
return (h + 2 * p - k_eff) // s + 1
def conv2d(X, Ker, S, P):
# 越界位置按 0 参与计算:只判断坐标,不真的建补零大矩阵
H, W, K = len(X), len(X[0]), len(Ker)
OH, OW = conv_out(H, K, S, P), conv_out(W, K, S, P)
out = [[0] * OW for _ in range(OH)]
for i in range(OH):
for j in range(OW):
acc = 0
for a in range(K):
for b in range(K):
r, c = i * S + a - P, j * S + b - P
if 0 <= r < H and 0 <= c < W:
acc += X[r][c] * Ker[a][b]
out[i][j] = acc
return out
# 输出尺寸表的四行
assert conv_out(4, 3, 1, 0) == 2
assert conv_out(4, 3, 1, 1) == 4
assert conv_out(224, 3, 2, 1) == 112
assert conv_out(32, 3, 2, 1, d=2) == 15 # 漏算有效核会得 16
# 第 04 节的 3×3 输入 × 2×2 核
assert conv2d([[1, 2, 0], [0, 1, 3], [2, 1, 0]], [[1, 0], [0, -1]], 1, 0) == [[0, -1], [-1, 1]]
# AI013 题面示例 1 与示例 2
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1, 0], [0, -1]], 1, 0) == [[-4, -4], [-4, -4]]
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[0, 1, 0], [1, 1, 1], [0, 1, 0]], 1, 1) == [[7, 11, 11], [17, 25, 23], [19, 29, 23]]
# 步长 2:窗口跳着走,输出 2×2
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1]], 2, 0) == [[1, 3], [7, 9]]
# 练习 1:示例 1 加一圈补零
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1, 0], [0, -1]], 1, 1)[0] == [-1, -2, -3, 0]断言覆盖第 04 节尺寸表四行、3×3 × 2×2 的 [[0, −1], [−1, 1]]、AI013 两个示例、步幅 2 的跳格、以及练习 1 的第一行。
展开参考实现 2:交并比函数 iou 与非极大值抑制函数 nms(自带断言;与第 05 节相同)
iou 与 nms 的参考实现(自带断言)
Pythondef iou(a, b):
# 框记作 (x1, y1, x2, y2) 连续坐标,宽 = x2 - x1
ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])
ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])
inter = max(0, ix2 - ix1) * max(0, iy2 - iy1) # 不相交时宽或高为负,截为 0
area_a = (a[2] - a[0]) * (a[3] - a[1])
area_b = (b[2] - b[0]) * (b[3] - b[1])
union = area_a + area_b - inter
return inter / union if union > 0 else 0.0
def nms(boxes, scores, thr):
# 排序键 (-分数, 下标):分数相同时先处理下标小的
order = sorted(range(len(boxes)), key=lambda i: (-scores[i], i))
keep = []
removed = set()
for i in order:
if i in removed:
continue
keep.append(i) # 轮到的框保留
for j in order:
if j != i and j not in removed and iou(boxes[i], boxes[j]) > thr:
removed.add(j) # 严格大于阈值才去掉
return keep
assert abs(iou((0, 0, 4, 4), (2, 2, 6, 6)) - 4 / 28) < 1e-9
assert iou((0, 0, 2, 2), (2, 0, 4, 2)) == 0.0 # 只共享一条边,记 0
assert iou((0, 0, 2, 2), (3, 3, 5, 5)) == 0.0 # 完全不相交
assert abs(iou((0, 0, 4, 4), (1, 1, 3, 3)) - 0.25) < 1e-9 # 包含:小框面积/大框面积
assert nms([(0, 0, 4, 4), (1, 0, 5, 4), (6, 6, 9, 9)], [0.9, 0.8, 0.7], 0.5) == [0, 2]
assert nms([(0, 0, 4, 4), (1, 0, 5, 4), (6, 6, 9, 9)], [0.9, 0.8, 0.7], 0.7) == [0, 1, 2]
assert nms([(0, 0, 2, 2), (0, 0, 2, 2)], [0.5, 0.5], 0.5) == [0]与「代码自测」任务的断言对应:讲解例 4/28、相接、不相交、包含四种 IoU,以及阈值 0.5 与 0.7 下不同的 NMS 结果、同分保留下标小。
展开完整参考程序 1:AI013 单通道卷积特征图
完整程序:AI013(标准输入 → 标准输出)
Pythonimport sys
def conv_out(h, k, s, p, d=1):
# 输出边长:有效核 k_eff = d·(k−1)+1,再套 ⌊(h + 2p − k_eff) / s⌋ + 1
k_eff = d * (k - 1) + 1
return (h + 2 * p - k_eff) // s + 1
def conv2d(X, Ker, S, P):
# 单通道卷积:越界位置按 0 参与计算,不真的建补零大矩阵
H, W, K = len(X), len(X[0]), len(Ker)
OH, OW = conv_out(H, K, S, P), conv_out(W, K, S, P)
out = [[0] * OW for _ in range(OH)]
for i in range(OH):
for j in range(OW):
acc = 0
for a in range(K):
for b in range(K):
r, c = i * S + a - P, j * S + b - P # 映射回原图坐标
if 0 <= r < H and 0 <= c < W: # 越界 → 贡献 0
acc += X[r][c] * Ker[a][b]
out[i][j] = acc
return out
def main():
data = sys.stdin.read().split()
H, W, K, S, P = (int(v) for v in data[:5])
nums = [int(v) for v in data[5:]]
X = [nums[i * W:(i + 1) * W] for i in range(H)] # 先读 H 行特征图
Ker = [nums[H * W + a * K:H * W + (a + 1) * K] for a in range(K)] # 再读 K 行核
out = conv2d(X, Ker, S, P)
print("\n".join(" ".join(str(v) for v in row) for row in out))
main()适用于题目的全部数据范围(H、W 到 120,K 到 7)。读入顺序先 H 行特征图、再 K 行核;越界只判断坐标,不建补零矩阵。
展开完整参考程序 2:AI014 IoU 矩阵
完整程序:AI014(标准输入 → 标准输出)
Pythonimport sys
def iou(a, b):
# 框记作 (x1, y1, x2, y2) 连续坐标,宽 = x2 − x1
iw = max(0.0, min(a[2], b[2]) - max(a[0], b[0])) # 交集宽,不相交时为负 → 截为 0
ih = max(0.0, min(a[3], b[3]) - max(a[1], b[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
def fmt4(v):
# 四舍五入到 4 位小数(0.5 进位);IoU 非负,不会出现 -0.0000
n = int(v * 10000 + 0.5)
return f"{n // 10000}.{n % 10000:04d}"
def main():
data = sys.stdin.read().split()
n, m = int(data[0]), int(data[1])
vals = [float(v) for v in data[2:]]
pred = [tuple(vals[4 * i:4 * i + 4]) for i in range(n)] # 先 N 个预测框
gt = [tuple(vals[4 * (n + j):4 * (n + j) + 4]) for j in range(m)] # 再 M 个真值框
lines = []
for p in pred:
lines.append(" ".join(fmt4(iou(p, g)) for g in gt))
print("\n".join(lines))
main()适用于 N、M 到 200 的全部数据范围。fmt4 用 int(v * 10000 + 0.5) 做四舍五入,再拆整数部分与 4 位小数——IoU 在 [0, 1] 内且非负,不会出现负零。
展开完整参考程序 3:AI015 非极大值抑制(进阶练习)
完整程序:AI015(标准输入 → 标准输出)
Pythonimport sys
def iou(a, b):
# 与 AI014 完全相同的坐标约定:宽 = x2 − x1,只相接记 0
iw = max(0.0, min(a[2], b[2]) - max(a[0], b[0]))
ih = max(0.0, min(a[3], b[3]) - max(a[1], b[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
def nms(boxes, scores, thr):
# 排序键 (−分数, 下标):分数高的先处理,同分先处理下标小的
order = sorted(range(len(boxes)), key=lambda i: (-scores[i], i))
removed = [False] * len(boxes)
keep = []
for pos, i in enumerate(order):
if removed[i]:
continue # 已被压掉的框不再参与
keep.append(i) # 轮到的框保留
for j in order[pos + 1:]: # 只看还没处理的框
if not removed[j] and iou(boxes[i], boxes[j]) > thr:
removed[j] = True # 严格大于阈值才压掉
return keep
def main():
data = sys.stdin.read().split()
n, thr = int(data[0]), float(data[1])
boxes, scores = [], []
for k in range(n):
x1, y1, x2, y2, s = (float(v) for v in data[2 + 5 * k:7 + 5 * k])
boxes.append((x1, y1, x2, y2))
scores.append(s)
print(" ".join(str(i) for i in nms(boxes, scores, thr)))
main()适用于 N 到 300 的全部数据范围。order[pos + 1:] 只看排在后面、还没处理的框;removed 列表保证被压掉的框既不保留、也不再压别人。
08 / 边界、反例与复杂度
错误做法在题库用例上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| AI013 尺寸公式忘 +1 | 示例 1 3 3 2 1 0 / … | -4(只有一格) | -4 -4 / -4 -4 | 答案错误(WA) |
AI013 越界只判上界 r < H and c < W(负下标回绕到末行末列) | 示例 2 3 3 3 1 1 / … | 17 19 20 / 23 25 23 / 28 29 23 | 7 11 11 / 17 25 23 / 19 29 23 | 答案错误(WA) |
AI013 把核翻转再乘 Ker[K-1-a][K-1-b] | 示例 1 | 4 4 / 4 4 | -4 -4 / -4 -4 | 答案错误(WA) |
| AI014 交集宽高不和 0 取较大值 | 1 1 / 0 0 4 4 / 1 6 3 8(x 方向相交、y 方向不相交) | -1.8334 | 0.0000 | 答案错误(WA) |
| AI014 宽高写成 x2−x1+1(像素约定) | 示例 1 | 1.0000 / 0.2857 | 1.0000 / 0.1429 | 答案错误(WA) |
AI014 用 round(v, 4) 直接打印 | 1 1 / 0 0 3 3 / 1 0 4 3 | 0.5 | 0.5000 | 格式错误(PE) |
AI015 比较写成 >= | 2 0.5 / 0 0 2 2 0.90 / 0 0 2 1 0.80(IoU 恰为 0.5) | 0 | 0 1 | 答案错误(WA) |
AI015 同分取大下标 (-scores[i], -i) | 3 0.50 / 0 0 2 2 0.50 / 0 0 2 2 0.50 / 5 5 6 6 0.50 | 2 1 | 0 2 | 答案错误(WA) |
| AI015 被压掉的框继续压别人 | 示例 2(链) | 0 | 0 2 | 答案错误(WA) |
| AI015 输出按下标从小到大排序 | 3 0.50 / 0 0 1 1 0.50 / 5 5 6 6 0.90 / 9 9 10 10 0.70 | 0 1 2 | 1 2 0 | 答案错误(WA) |
第 2 行:Python 的 X[-1] 是最后一行,越界不判下界时角上的格子会悄悄加上对面一侧的值,程序不报错、结果却全错。第 4 行:交集宽 2、交集高 −2,乘出负「交集」、除以被加大的并集,得到负数;两个方向都不相交时负负得正、并集变负,反而碰巧打出 0.0000——所以不能拿一组不相交用例通过就当作截断写对了。每一行的输入都可以直接喂给改错后的程序复现。
| 做法 | 时间 | 本课规模下 |
|---|---|---|
| AI013 四层循环 | O(OH·OW·K²) | H、W≤120,P≤6、S≥1、K≤7;按尺寸公式估算,核位置数在 K=7、OH=OW=126 时最多,为 126×126×49 = 777,924 |
| AI014 逐对 IoU | O(N·M) | N、M≤200,最多 4×10⁴ 次 IoU 计算 |
| AI015 逐框比较 | O(N²) | N≤300,两两比较最多 44,850 对;边处理边算 IoU,不必预先建 N×N 矩阵 |
09 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):把 AI013 示例 1 的 P 改成 1(其余不变:K=2、S=1)。先算输出尺寸,再按第 06 节示例 2 的格式写出第一行四个输出格的求和项与结果。
展开练习 1 答案
输出边长 ⌊(3 + 2 − 2)/1⌋ + 1 = 4。第一行的窗口左上角在原图坐标 (−1, j−1):out[0][0] = 0×1 + 0×0 + 0×0 + 1×(−1) = −1;out[0][1] = 0 + 0 + 0 + 2×(−1) = −2;out[0][2] = 0 + 0 + 0 + 3×(−1) = −3;out[0][3] 的窗口只有左下角 X[0][2] = 3 在图内,对应核元素 Ker[1][0] = 0,所以是 0。第一行 -1 -2 -3 0;整张输出是 -1 -2 -3 0 / -4 -4 -4 3 / -7 -4 -4 6 / 0 7 8 9(参考实现的最后一条断言)。
练习 2(改一个条件):AI014 示例 1 的真值框改成 (1, 1, 3, 3),两个预测框的 IoU 各是多少?为什么第二行会变成 1.0000?
展开练习 2 答案
预测框 0 = (0,0,2,2) 对 (1,1,3,3):交集宽 min(2,3) − max(0,1) = 1、高 1、交集 1,并集 4 + 4 − 1 = 7 → 0.1429;预测框 1 = (1,1,3,3) 就是真值框本身:交集 = 面积 = 4,并集 4 + 4 − 4 = 4 → 1.0000。输出 0.1429 / 1.0000——IoU 对调两个框不变,所以第一行的 0.1429 与示例 1 第二行相同。
练习 3(改一个条件):AI015 示例 1 的阈值从 0.30 改成 0.40,输出是什么?再把阈值改成 0.3913 附近会怎样?
展开练习 3 答案
框 0 与框 1 的 IoU 是 9/23 = 0.3913,不再大于 0.40,框 1 保留;输出 0 1 2。阈值恰好等于 IoU 时按「严格大于」不压掉——题目保证除刻意构造的边界组外,IoU 与 T 的差至少 1e-9,所以用浮点直接比较即可,不需要再加容差。
练习 4(独立实现):不看第 07 节,写出 conv_out 与 conv2d,并让它们通过下面这组断言:尺寸表四行、第 04 节的 [[0, −1], [−1, 1]]、AI013 两个示例、步幅 2 的 [[1, 3], [7, 9]]。
展开练习 4 答案
conv2d 的参考实现(自带断言)
Pythondef conv_out(h, k, s, p, d=1):
# 有效核 k_eff = d·(k−1)+1;d=1 时就是 k
k_eff = d * (k - 1) + 1
return (h + 2 * p - k_eff) // s + 1
def conv2d(X, Ker, S, P):
# 越界位置按 0 参与计算:只判断坐标,不真的建补零大矩阵
H, W, K = len(X), len(X[0]), len(Ker)
OH, OW = conv_out(H, K, S, P), conv_out(W, K, S, P)
out = [[0] * OW for _ in range(OH)]
for i in range(OH):
for j in range(OW):
acc = 0
for a in range(K):
for b in range(K):
r, c = i * S + a - P, j * S + b - P
if 0 <= r < H and 0 <= c < W:
acc += X[r][c] * Ker[a][b]
out[i][j] = acc
return out
# 输出尺寸表的四行
assert conv_out(4, 3, 1, 0) == 2
assert conv_out(4, 3, 1, 1) == 4
assert conv_out(224, 3, 2, 1) == 112
assert conv_out(32, 3, 2, 1, d=2) == 15 # 漏算有效核会得 16
# 第 04 节的 3×3 输入 × 2×2 核
assert conv2d([[1, 2, 0], [0, 1, 3], [2, 1, 0]], [[1, 0], [0, -1]], 1, 0) == [[0, -1], [-1, 1]]
# AI013 题面示例 1 与示例 2
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1, 0], [0, -1]], 1, 0) == [[-4, -4], [-4, -4]]
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[0, 1, 0], [1, 1, 1], [0, 1, 0]], 1, 1) == [[7, 11, 11], [17, 25, 23], [19, 29, 23]]
# 步长 2:窗口跳着走,输出 2×2
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1]], 2, 0) == [[1, 3], [7, 9]]
# 练习 1:示例 1 加一圈补零
assert conv2d([[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[1, 0], [0, -1]], 1, 1)[0] == [-1, -2, -3, 0]见第 07 节展开区(同一份代码)。
练习 5(迁移):检测评估里常要「给每个真值框找 IoU 最大的预测框」。用第 05 节的 iou 写函数 best_match(pred, gt):对每个真值框返回 IoU 最大的预测框下标,并列取下标小的,全部为 0 时返回 −1。用 AI014 题库用例 3 4 / 0 0 2 2 / 1 1 3 3 / 5 5 7 7 / 0 0 1 1 / 2 2 4 4 / 5 5 7 7 / 8 8 9 9 验证。
展开练习 5 答案
这组用例的 IoU 矩阵是 0.2500 0 0 0 / 0 0.1429 0 0 / 0 0 1.0000 0:真值框 0 的最大值在预测框 0,真值框 1 在预测框 1,真值框 2 在预测框 2,真值框 3 一列全 0 → −1。并列取小下标只需「严格更大才换」——从下标 0 往后扫,相等时不换,留下的就是先遇到的小下标。
best_match 的参考实现(自带断言)
Pythondef iou(a, b):
iw = max(0.0, min(a[2], b[2]) - max(a[0], b[0]))
ih = max(0.0, min(a[3], b[3]) - max(a[1], b[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
def best_match(pred, gt):
# 对每个真值框,找 IoU 最大的预测框下标;并列取下标小的;全为 0 记 -1
result = []
for g in gt:
best, best_iou = -1, 0.0
for i, p in enumerate(pred):
v = iou(p, g)
if v > best_iou: # 严格更大才换,自然保留下标小的;不加额外容差
best, best_iou = i, v
result.append(best)
return result
pred = [(0, 0, 2, 2), (1, 1, 3, 3), (5, 5, 7, 7)]
gt = [(0, 0, 1, 1), (2, 2, 4, 4), (5, 5, 7, 7), (8, 8, 9, 9)]
assert best_match(pred, gt) == [0, 1, 2, -1] # AI014 题库用例 grid-mixed 的矩阵
assert best_match([(0, 0, 2, 2), (0, 0, 2, 2)], [(0, 0, 2, 2)]) == [0] # 并列取下标小
assert best_match([(0, 0, 1, 1)], [(3, 3, 4, 4)]) == [-1] # 不相交
assert best_match([(1, 1, 3, 3), (0, 0, 4, 4)], [(0, 0, 4, 4)]) == [1] # 包含 0.25 < 完全重合 1
# 两个 IoU 只差 1.25e-10(2.5e-7 与 2.50125e-7):严格最大仍要换成下标 1,不能用 1e-9 一类的容差吞掉
assert best_match([(0, 0, 200, 200), (0, 0, 199.9, 200)], [(0, 0, 0.1, 0.1)]) == [1]依次检查示例矩阵、相同预测框并列取 0、不相交返回 −1、包含与完全重合,以及两个很接近但不相等的 IoU;严格较大者优先。
10 / 读题要求与复习自评
三道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | AI013 卷积特征图 | AI014 IoU 矩阵 | AI015 非极大值抑制 |
|---|---|---|---|
| 输入顺序 | H W K S P;H 行特征图;K 行核 | N M;N 行预测框;M 行真值框 | N T;N 行 x1 y1 x2 y2 score |
| 坐标 / 尺寸约定 | 输出 (H+2P−K)/S+1,保证整除;越界按 0 | 连续坐标,宽 = x2 − x1;只相接记 0 | 与 AI014 相同 |
| 并列 / 比较 | 无 | 无 | 同分下标小先处理;IoU 严格大于 T 才压掉 |
| 输出 | OH 行、每行 OW 个整数 | N 行、每行 M 个 4 位小数 | 一行保留顺序的原始下标 |
| 数据范围 | H, W ≤ 120;K ≤ 7;S ≤ 4;P ≤ 6 | N, M ≤ 200;坐标至多一位小数 | N ≤ 300;0 ≤ T ≤ 1;分数至多两位小数 |
需要对照解法时,展开本课第 07 节的三份完整参考程序,或在题目页查看题解。完成条件:AI013、AI014 两道必做题都通过判题,并勾选下方「学习完成检查」的六条,本课才记为完成;进阶练习 AI015 单独统计,不影响完成状态。六条自查是自评,勾选不改变题目的通过(AC)状态。复习时用三个问题自测:① 不看正文,写出输出尺寸一般式并算出 H=32、K=3、S=2、P=1、d=2 的结果;② 不看表格,重算 AI015 示例 1 里框 0 与框 1 的 IoU 并说出它为什么被压掉;③ 说出 NMS 的排序键和比较符号,以及被压掉的框为什么不再压别人。答不出哪一条,就回到对应的节重读,再做第 09 节对应的练习。
11 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道;进阶练习已通过 0/1 道
iou)和非极大值抑制函数(nms),再逐题提交在线测试——三道题共享同一套坐标与并列约定,完成基础函数后,再分别处理三道题的输入格式、边界条件和输出规则。参考实现与完整程序在第 07 节的展开区里:先自己写完并提交一次,再展开对照。交并比函数(iou)与非极大值抑制函数(nms)
代码自测自主练习练习重点:交集宽高与 0 取较大值;排序键 (−得分, 下标);严格大于阈值才去掉;预计用时:25 分钟
完成标准:能不看笔记说出 NMS 的三条规则和 IoU 的三种边界
需要时查看提示
iou 先算交集左上/右下角,宽高分别 max(0,·) 再相乘;nms 排序后逐个处理,被去掉的框不再影响其他框。断言里的 0.6 > 0.5 例子是 AI015 的缩小版。第 05 节有推演,参考实现在第 07 节。
自测代码(复制到你的代码文件中运行,检查输出是否一致)
# IoU:讲解例、相接、不相交、包含
assert abs(iou((0, 0, 4, 4), (2, 2, 6, 6)) - 4 / 28) < 1e-9
assert iou((0, 0, 2, 2), (2, 0, 4, 2)) == 0.0 # 只共享一条边,记 0
assert iou((0, 0, 2, 2), (3, 3, 5, 5)) == 0.0 # 不相交,用 max(0,·) 将负的交集宽高截为 0
assert abs(iou((0, 0, 4, 4), (1, 1, 3, 3)) - 0.25) < 1e-9
boxes = [(0, 0, 4, 4), (1, 0, 5, 4), (6, 6, 9, 9)]
scores = [0.9, 0.8, 0.7]
assert nms(boxes, scores, 0.5) == [0, 2] # IoU=0.6 > 0.5,框 1 因与框 0 的 IoU 大于阈值而被抑制
assert nms(boxes, scores, 0.7) == [0, 1, 2] # 阈值提高至 0.7,0.6 不再超过阈值
assert nms([(0, 0, 2, 2), (0, 0, 2, 2)], [0.5, 0.5], 0.5) == [0] # 同分时保留下标较小的框AI013 · 单通道卷积特征图
必做任务 1练习重点:conv2d 加上 stride 与零补边,输出尺寸就是正文那条公式;预计用时:20 分钟
完成标准:能解释越界补零为什么不用真建大矩阵
需要时查看提示
输出第 (i,j) 格对应窗口左上角在补零图的 (i·S, j·S);映射回原图坐标是 i·S+a−P。越界坐标直接按 0 算。约束保证尺寸整除,用整数除法。第 06 节有两个示例的逐格表。
AI014 · IoU 矩阵
必做任务 2练习重点:N×M 逐对 IoU;连续坐标宽 = x2−x1;相接记 0;预计用时:15 分钟
完成标准:输出固定保留 4 位小数,并正确处理负零(没有 -0.0000)
需要时查看提示
交集宽高都和 0 取较大值;四舍五入保留 4 位;结果为 0 打 0.0000。题目给定的 N、M 上限允许使用 O(NM) 的逐对计算,正确性优先。第 06 节有两个示例的逐对表。
AI015 · 非极大值抑制
进阶练习 1进阶练习练习重点:排序键 (−得分, 下标);IoU 严格大于阈值才去掉;被去掉的框不再影响其他框;预计用时:15 分钟
完成标准:同分与阈值边界两类用例都能通过
需要时查看提示
输出是保留顺序(分数从高到低、同分下标小在前)的原始下标。IoU 的坐标约定与 AI014 完全一致——直接复用你写好的 iou。第 06 节有两个示例的逐步表。
提交结果
提交结果说明与处理方法
- WA
答案错误
三个常见错误:尺寸公式漏算有效核或忘 +1、IoU 交集宽高没和 0 取较大值、NMS 并列没按下标小优先。第 08 节的表给出了每种错误在题库用例上的输出
- PE
格式错误
AI014 是 4 位小数、单空格分隔;0 要打 0.0000,小心 -0.0000
- RE
运行错误
补零写成真扩矩阵时下标错位;读入个数与 H、W、K 不匹配
- TLE
超时
本类题数据规模较小;若超时,检查是否在内层循环重复构造列表
- AC
通过
说明膨胀系数 d=2 时输出为 15 而非 16 的计算依据,再看一遍参数量的计算
12 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。