离散卷积与多通道互相关:参考答案#
说明#
以下答案与正文10道题逐题对应。推导题给出主要步骤;编程和实验题给出参考程序和检查方法,并说明结果适用于哪些条件。
空洞卷积核覆盖的有效长度为 \(f_{\mathrm{eff}}=r(f-1)+1\)。填充后长度为 \(d_x+2p\),合法左端点为 \(0,s,2s,\ldots\),最大值不超过 \(d_x+2p-f_{\mathrm{eff}}\)。因此起点个数为 \(\lfloor(d_x+2p-f_{\mathrm{eff}})/s\rfloor+1\),化简即得题中的 \(d'_x\)。要求 \(s\geq1\)、\(r\geq1\),且 \(d_x+2p\geq r(f-1)+1\);否则没有合法输出位置。
每个位置先求第一通道窗口和,再减去第二通道的窗口和 4。四个第一通道窗口和依次为 4、6、4、5,因此输出为 \(\begin{bmatrix}4-4&6-4\\4-4&5-4\end{bmatrix}=\begin{bmatrix}0&2\\0&1\end{bmatrix}\)。该计算是互相关,核没有翻转;两个输入通道的结果在同一输出通道内求和。
每个输出通道有 \(3(3)(3)=27\) 个核参数和 1 个偏置,总参数量为 \(16(27+1)=448\)。输出共有 \(32(32)(16)\) 个位置,每个位置进行 27 次乘加,故卷积部分为 \(32(32)(16)(27)=442368\) 次乘加。若把乘法和加法分别计数,应明确采用的换算约定。
记 \(\delta=(\delta_1,\delta_2)\),并按正文定义 \((T_\delta\bX)_{cij}=X_{c,i-\delta_1,j-\delta_2}\)。代入互相关定义后,\(\mathcal C(T_\delta\bX)_{qij}=\sum_{c,u,v}X_{c,i+u-\delta_1,j+v-\delta_2}W_{q,c,u,v}=\mathcal C(\bX)_{q,i-\delta_1,j-\delta_2}\),即 \(T_\delta\mathcal C(\bX)\)。有限填充时,平移会改变哪些位置接触人工边界;步幅大于 1 时只保留一个采样子网格,任意 1 像素平移不一定对应输出网格上的整数平移。
直接实现应把输出尺寸与索引写清楚:
import numpy as np def _pair(value, name): if np.isscalar(value): value = int(value) return value, value if len(value) != 2: raise ValueError(f"{name} 必须是一个整数或两个整数") return int(value[0]), int(value[1]) def conv2d_nchw(x, w, b, padding=0, stride=1, dilation=1): m, c_in, h, width = x.shape c_out, c_w, kh, kw = w.shape if c_in != c_w: raise ValueError("输入通道数与卷积核不一致") p1, p2 = _pair(padding, "padding") s1, s2 = _pair(stride, "stride") d1, d2 = _pair(dilation, "dilation") if min(p1, p2) < 0 or min(s1, s2, d1, d2) < 1: raise ValueError("填充不能为负,步幅和空洞率必须为正整数") h_out = (h + 2*p1 - d1*(kh-1) - 1) // s1 + 1 w_out = (width + 2*p2 - d2*(kw-1) - 1) // s2 + 1 if min(h_out, w_out) <= 0: raise ValueError("卷积核大于填充后的输入") xp = np.pad(x, ((0,0), (0,0), (p1,p1), (p2,p2))) out = np.empty((m, c_out, h_out, w_out), dtype=np.result_type(x, w, b)) for i in range(m): for q in range(c_out): for r in range(h_out): for s in range(w_out): row = r * s1 col = s * s2 window = xp[i, :, row:row+d1*(kh-1)+1:d1, col:col+d2*(kw-1)+1:d2] out[i, q, r, s] = np.sum(window * w[q]) + b[q] window_info = { "padded_shape": xp.shape, "window_shape": (c_in, kh, kw), "output_shape": out.shape, "padding": (p1, p2), "stride": (s1, s2), "dilation": (d1, d2), } return out, window_info
window_info给出核验窗口抽取所需的维度、填充、步幅和空洞率信息;调试时还可增加首末窗口的索引样例,但不应返回所有窗口而无谓占用内存。循环版与向量化版必须使用相同的 NCHW、填充和互相关约定;PyTorch
conv2d也不翻转核。随机float64小张量上用allclose比较,同时记录最大绝对误差。再取只在左上角为 1 的非对称核;若程序错误翻核,输出会向相反方向偏移,测试应稳定失败。向量化版的窗口矩阵可能占用较多内存,因此速度比较必须同时报告峰值内存。单位脉冲用于检查核方向与位置,全 1 输入用于检查窗口求和,非对称核用于区分卷积与互相关;多通道测试验证沿输入通道求和和输出通道相互独立;分别改变 \(p_1,p_2\)、\(s_1,s_2\) 和 \(r_1,r_2\) 后,断言理论尺寸等于实际维度。最小合法输入应产生 \(1\times1\) 空间输出,非法通道数、非正步幅和非正空洞率必须报错。整数输出尺寸应完全一致,浮点值再使用与数据类型相符的误差范围。
三种实现先在同一小张量上确认数值一致,再放入相同网络并复制相同权重。数据批次、种子、更新次数和硬件保持一致。框架内核通常最快,朴素循环最慢,
im2col以额外窗口内存换取矩阵运算效率;但具体结果以实测为准。表中应给出测试指标、最大数值误差、训练秒数、448 等实际参数量和峰值内存。两个 \(3\times3\) 卷积在步幅为 1 时产生 \(5\times5\) 的 感受野,并在中间加入一次非线性变换;空洞率为 2 的 \(3\times3\) 空洞卷积 也覆盖 \(5\times5\) 的范围,但读取的输入位置更加分散。为了使比较结果可靠,各组应使用相同的数据划分、随机种子、训练轮数和优化器尝试次数,只改变卷积形式,并报告性能及其波动、训练时间、固定批量预测时间、参数量、乘加量和峰值内存。串联小卷积核通常更容易控制参数量,但网络层数更深;空洞卷积不增加核参数量,却可能遗漏相邻位置的连续变化。
Valid会逐层缩小特征图,保持空间尺寸的Same填充保留更多边界附近位置,但人工填充值也会产生边界效应。两组必须调整分类输出层并如实报告由此产生的参数差异,其他训练条件相同。结果表包括测试指标、训练秒数、参数量及峰值内存。若保持尺寸组更慢或占用更多内存,可由更大的中间特征图解释;不能只比较准确率。