☰
卷积、边缘检测与SIFT:计算机视觉核心考点解析
2026/11/2 5:31:14 网站建设 项目流程

简介:面向北京交通大学计算机视觉课程期末复习整理的考点汇总 PDF,以 40 个问答条目串起从底层滤波到深度检测的完整链路:图像滤波的相关、卷积、内积三种定义及模板匹配、图像增强、特征提取三类用途,高斯核与高斯偏导核的参数设置,Canny 细边缘与双阈值思路,Harris 角点、SIFT 双塔结构与描述子生成,视觉词典与纹理基元构造,以及 NB、KNN 与线性分类器的联系和交叉熵、铰链损失、梯度更新、小批量策略。后半部分覆盖隐藏层作用、卷积核尺寸与参数量、感受野与权值共享、AlexNet、Sigmoid 梯度消失、数据增广、Dropout、Momentum、1×1 卷积、残差模块与 DNN 训练指南,并延伸至 R-CNN 到 Faster R-CNN 的演进、RPN、NMS、PR 图与 AP 计算、全卷积网络问题及转置卷积方案。压缩包为单个 PDF,约 9.69MB,按考点条目式排版便于检索与考前自测,已有 667 人学习。

1. 一个模板滑过整张图:卷积到底在算什么

把计算机视觉课程资源里的考点摊开,最先冒出来的是一串名词:相关、卷积、高斯偏导、Canny、Harris、SIFT、感受野、RPN。它们看着像八个互不相干的知识点,实际上是同一个动作的变体——拿一个小模板滑到图像某个位置,逐元素相乘再求和,然后决定这个标量怎么用。反直觉的地方在于:这门课里最难的几个问题,比如尺度怎么自动选、边缘怎么变细、候选框怎么少算几千万次卷积,答案往往不是新公式,而是对这个动作换个维度重复一遍或者重新组织一遍。先把「模板乘局部等于一个数」这件事吃透,后面那些背诵型结论就能自己推出来。

2. 图像滤波的三张面孔:相关、卷积、内积为什么能互相等价

2.1 相关与卷积:那 180° 旋转改的到底是什么

相关(correlation)的定义很直白:模板盖住图像的一块区域,模板元素和对应位置的像素逐一相乘,再把乘积加起来,结果写到模板中心对应的位置。卷积(convolution)在这个基础上多一步——把模板旋转 180°,再执行完全相同的操作。这个细节在数学上是为了让卷积满足交换律和结合律,从而拥有傅里叶变换下的乘积性质;在工程上它几乎不产生任何额外成本。

import numpy as np def correlate(img, k): """相关:模板不翻转,逐元素相乘再求和""" kh, kw = k.shape H, W = img.shape out = np.zeros((H - kh + 1, W - kw + 1)) for i in range(out.shape[0]): for j in range(out.shape[1]): patch = img[i:i + kh, j:j + kw] out[i, j] = np.sum(patch * k) # 这一步就是内积 return out def convolve(img, k): """卷积 = 把核旋转 180° 再做相关""" return correlate(img, k[::-1, ::-1])

k[::-1, ::-1]同时翻转了行和列,等价于平面内旋转 180°。这里藏着一个常被忽略的事实:深度学习框架里叫conv2d的算子,绝大多数实现做的是相关而不是卷积。原因很简单——卷积核的参数是学出来的,学一个翻转过的核和学一个没翻转的核在表达能力上完全等价,省掉翻转还能少一次内存重排。所以看论文里的公式写的是卷积、跑代码时行为像相关,这不是谁写错了,而是两边各自选了自己方便的定义。

内积是第三种写法:把模板和图像块都按行或按列拉成向量,两个向量逐元素相乘再求和,得到一个标量。它和相关在数值上是同一件事,只是把「二维滑动」重述成了「高维空间里两个向量的夹角问题」。这个视角的价值在后面会反复出现:模板匹配是在问「这块图像和模板有多像」,特征提取是在问「这块图像和某个特定模式有多像」,两者都是内积。

2.2 内积视角:滤波就是一次相似度打分

把滤波看成打分,三类用途立刻变得统一。模板匹配是显式的打分:人为设计一个目标原型(比如某个手势的轮廓),和测试图做卷积,响应最大的位置就是匹配位置。图像增强是隐式的打分:高斯核在问「这块区域有多接近一个平滑斑块」,响应大说明这里平坦,于是用它做加权平均就完成了去噪;拉普拉斯核在问「这块区域有多偏离周围均值」,响应大说明这里是边缘或细节,把它加回原图就完成了锐化。特征提取则是更有针对性的打分:Canny 用的是高斯偏导核,问的是「这里在某个方向上灰度变化有多剧烈」;Harris 问的是「这里的梯度在两个正交方向上是不是同时很大」;SIFT 问的是「这里的斑块尺寸是不是和当前滤波器尺度匹配」。

这里值得停一下。很多复习资料把这三类用途并列成三个记忆条目,但它们共享同一个数学骨架:响应值等于内积,内积衡量相似度,相似度的含义由模板决定。理解了这一层,遇到没见过的滤波器也能猜出它在干什么——看它的形状像什么,它就在找什么。

2.3 高斯核的参数账:σ、3σ 原则和 kernel size 为什么翻倍也不变

二维高斯核的公式是 G(x, y) = 1/(2πσ²) · exp(-(x² + y²)/(2σ²))。σ 决定核的宽度,也就是平滑的力度;核的尺寸(kernel size)决定这个核在图像上覆盖多大范围。教材里那句「size 取 6σ」不是拍脑袋,它来自正态分布的 3σ 原则:数值落在 ±3σ 之外的概率不到 0.3%,也就是说 ±3σ 之外的权重对加权平均的贡献可以忽略。

import numpy as np def gauss_kernel(size, sigma): ax = np.arange(size) - (size - 1) / 2.0 # 以中心为原点 xx, yy = np.meshgrid(ax, ax) g = np.exp(-(xx**2 + yy**2) / (2 * sigma**2)) return g / g.sum() # 归一化,保证亮度不变 for size in (13, 25, 51): k = gauss_kernel(size, 2.0) print(size, round(k.max(), 5), "%.2e" % k[0, 0])

σ=2 时 6σ=12,size=13 正好覆盖 ±3σ。把 size 换成 25 甚至 51,中心权重几乎不动,角落权重从 1e-5 迅速掉到 1e-15 量级——多出来的部分全是接近零的权重。这就是「kernel size 不同、σ 相同,结果类似」的数学解释:平滑本质上是求一个范围内的定积分,积分区间从 ±3σ 扩到 ±6σ,被积函数已经衰减到可以忽略,积分值自然趋于稳定。所以工程上没必要把核开得太大,13×13 和 51×51 的效果差不了多少,计算量却差 15 倍。

注意:size 必须取奇数,否则核没有唯一的中心像素,滤波后的图像会整体偏移半个像素。归一化也不能省,否则卷积后的图像整体变亮或变暗。

还有一个实现层面的常识:二维高斯核是可分离的,G(x, y) = g(x)·g(y),所以一次二维卷积可以拆成先横着卷一维核、再竖着卷一维核,复杂度从 O(k²) 降到 O(2k)。核越大,这个优化越划算。

def gauss_1d(size, sigma): ax = np.arange(size) - (size - 1) / 2.0 g = np.exp(-ax**2 / (2 * sigma**2)) return g / g.sum() g1 = gauss_1d(13, 2.0) print(np.abs(np.outer(g1, g1) - gauss_kernel(13, 2.0)).max()) # 1e-17 量级

2.4 高斯偏导核:把平滑和求导合成一次卷积

边缘检测的标准流程是先用高斯核平滑去噪,再对平滑后的图求梯度。两步串行意味着两次卷积,但卷积满足结合律与交换律,求导又是线性算子,于是有:

∂/∂x (G * I) = (∂G/∂x) * I

也就是说,可以先对高斯核求偏导,得到一个「高斯偏导核」,再拿它去卷原图,一次卷积同时完成去噪和求导。这不是近似,是恒等变形。

def gauss_dx(size, sigma): ax = np.arange(size) - (size - 1) / 2.0 g = np.exp(-ax**2 / (2 * sigma**2)) g = g / g.sum() dg = -ax / sigma**2 * g # 对 x 求导,注意多出来的 -x/σ² 因子 return dg.reshape(1, -1) # 水平方向的一维偏导核 def gauss_dy(size, sigma): return gauss_dx(size, sigma).T # 垂直方向就是转置

导数核有一个容易忽略的性质:它的所有元素之和为 0。这是必然的——高斯核总和为 1,常数的导数为 0。这个性质保证了滤波器对图像的整体亮度不敏感,均匀区域经过它之后响应为零,只有灰度发生变化的地方才有输出。设计上,偏导核的尺寸同样取 6σ,σ 越大抗噪越强但边缘定位越模糊,这是一个无法回避的取舍:噪声抑制和定位精度在频率域上本来就是一对矛盾。

3. 从细边缘到尺度不变:Canny、Harris、SIFT 各自在算什么

3.1 非极大值抑制:Canny 的「细」是怎么来的

梯度幅值图上的边缘是一坨一坨的,一条真实边缘会在好几个像素宽度上都有较高响应。非极大值抑制(NMS)做的事很朴素:对每个像素,沿着它的梯度方向往前看一步、往后看一步,如果当前像素的幅值不比这两个邻居都大,就把它置零。留下来的点构成一条单像素宽的脊线。

import numpy as np def nms_quantized(mag, ang): """ang 为弧度,取值 [0, pi);把方向量化为 4 个之一""" H, W = mag.shape out = np.zeros_like(mag) for i in range(1, H - 1): for j in range(1, W - 1): deg = (ang[i, j] * 180 / np.pi) % 180 if deg < 22.5 or deg >= 157.5: d = (0, 1) # 水平 elif deg < 67.5: d = (1, 1) # 45° elif deg < 112.5: d = (1, 0) # 垂直 else: d = (-1, 1) # 135° if mag[i, j] >= mag[i + d[0], j + d[1]] and \ mag[i, j] >= mag[i - d[0], j - d[1]]: out[i, j] = mag[i, j] return out

难点在于梯度方向是连续的,而邻居只有 8 个离散位置。这里有两种处理方式:角度量化把连续方向近似到 0°、45°、90°、135° 四个方向之一,实现简单、速度快,代价是斜边缘附近会有轻微的方向误差;线性插值则按比例取两个相邻像素的幅值加权,结果更准,代价是多几次乘加。多数工程实现选量化,因为 Canny 后面还有双阈值和连通性分析,这点误差会被抹平。这里的血泪经验是:如果 NMS 写错,边缘会整体变粗一倍,而你会以为是高斯核的 σ 设大了,调半天参数也调不回来。

3.2 双阈值:滞后阈值为什么能连断边又不留噪

单阈值 Canny 有个两难:阈值高,边缘干净但断断续续;阈值低,边缘连贯但噪声全进来了。双阈值用两个阈值加一个连通性规则绕开这个矛盾:梯度幅值高于高阈值的点直接判定为强边缘,低于低阈值的直接丢弃,处在两者之间的点算弱边缘,只有当它和某个强边缘在 8 邻域内连通时才保留。这个过程通常用一次广度优先或深度优先的标记来完成,把弱边缘当作待定区域,从强边缘出发做扩散。

这个设计的关键在于它把「判定」拆成了两步:强边缘负责确定「这里确实有边缘」,弱边缘只负责「把这条边缘补完整」。噪声点大多是孤立的,很少能和强边缘连成一片,所以它们在第一轮就被低阈值筛掉了;真实边缘即使中间有一段响应偏弱,只要两端接着强边缘,就会被补回来。阈值本身没有普适值,常见做法是取梯度幅值最大值的 0.2 倍作高阈值、0.5 倍作低阈值,或者按分位数取。换数据集就得重调,这是 Canny 在实际项目里最不「自动」的地方。

3.3 Harris:结构张量的两个特征值在说什么

Harris 的出发点是一个很物理的问题:把一个小窗口在图像上平移 (u, v),窗口里的灰度变化有多大。变化量写成 E(u, v) = Σ w(x, y)·[I(x+u, y+v) - I(x, y)]²。直接算这个式子对每个 (u, v) 都要重来一遍,代价太高,所以对 I(x+u, y+v) 做一阶泰勒展开,把位移项提出来,得到:

E(u, v) ≈ [u v] · M · [u v]ᵀ,其中 M = Σ w · [[Ix², IxIy], [IxIy, Iy²]]

这一步的意义是把「图像内容」和「平移量」解耦了:M 只和图像局部有关,位移 (u, v) 只和你要往哪挪有关。M 是一个实对称半正定矩阵,对它做相似对角化,两个特征值 λ1、λ2 就描述了这块区域在两个主方向上的灰度变化强度。两个都大,说明往任何方向挪灰度都剧烈变化,这是角点;一大一小,说明只有一个方向变化剧烈,这是边缘;两个都小,说明这块区域很平。

import numpy as np def harris_response(Ix, Iy, k=0.04, win=3): """Ix, Iy 为水平/垂直梯度;用均值滤波做窗口求和""" from scipy.ndimage import uniform_filter Sxx = uniform_filter(Ix * Ix, win) Sxy = uniform_filter(Ix * Iy, win) Syy = uniform_filter(Iy * Iy, win) det = Sxx * Syy - Sxy ** 2 # λ1·λ2 tr = Sxx + Syy # λ1+λ2 return det - k * tr ** 2 # 角点响应 R

真正做特征值分解的代价不小,所以实践中用 R = det(M) - k·trace(M)² 代替。这个替换不是随便凑的:det = λ1λ2,trace = λ1+λ2,两个特征值都大时 det 大、trace 也被 k 压住,R 就大;一大一小时 det 小,R 变负;都小时 R 的绝对值很小。k 一般取 0.04 到 0.06,它控制的是「多接近边缘才算边缘」这条分界线。需要提醒的是,R 的数值范围依赖图像本身,直接用一个固定阈值筛角点会翻车,通常取 R 的最大值的某个比例,或者在局部邻域内做非极大值抑制。

3.4 尺度空间:LoG 内积最大值处的物理含义

Harris 有一个致命短板:它没有尺度概念。同一个角点,图像放大两倍,窗口还是那么大的话就检测不到了。自动尺度选择的做法是构造尺度空间——用一系列从小到大的高斯核(或 LoG 核)去卷同一张图,得到一组响应图,然后对每个空间位置,在尺度维度上找响应的局部极大值,那个 σ 就是该特征的尺度。

物理意义可以用信号与滤波器的关系说清楚:把图像局部看成一个亮斑,把 LoG 核看成一个中间负、外围正的墨西哥草帽,两者的内积等于亮斑和草帽重叠区域的加权和。当亮斑的最高点刚好落在 LoG 的零交叉点上时,正负两部分的贡献达到最优配比,内积取到最大值。换句话说,响应的峰值出现在「滤波器尺寸和斑块尺寸匹配」的时候,这就是特征尺度的物理含义。

提示:跨尺度比较响应时必须做尺度归一化。LoG 的幅值会随 σ 增大而衰减,常见做法是乘上 σ²(归一化 LoG),否则大尺度永远比不过小尺度,尺度选择就失效了。

3.5 SIFT 双塔:四处简化省在了哪里

SIFT 的金字塔结构模仿了人眼「近大远小、近清晰远模糊」的特性:同一组(octave)内,用逐步增大的 σ 对图像做高斯平滑,堆成高斯金字塔;组与组之间把图像降采样一半,σ 范围翻倍。相邻两层高斯图相减,得到高斯差分金字塔(DoG),用它在三维邻域(同一层的 8 邻域加上下两层的各 9 个点,共 26 个)里找极值点。

这里的计算量如果老实做,会大到不可接受。SIFT 用了四招来砍:

第一,同组内的大尺度可以由小尺度递推。已经算过 σ 的卷积结果,再卷一个 sqrt((kσ)² - σ²) 的高斯核,就得到了 kσ 的结果。因为高斯核的卷积还是高斯核,方差相加。这样每组只需一次真正的卷积,后面全是递推。

第二,跨组时靠降采样而不是靠加大核。图像缩小一半,同样大小的核在原图上对应的感受野就翻倍了,所以核的尺寸不必随尺度线性增长。

第三,Octave 2 的 2σ 层可以由 Octave 1 的倒数第三层直接降采样得到,避免重复计算。

第四,用 DoG 近似 LoG。两者的关系是 DoG ≈ (k-1)·σ²·∇²G,比例因子与 σ 无关,所以极值位置一致。DoG 只需要做减法,而 LoG 要做二阶偏导卷积,减法比卷积快得多。

def build_dog(gauss_pyr): """gauss_pyr[o][s] 表示第 o 组第 s 层的高斯图""" dog = [] for octave in gauss_pyr: dog.append([octave[s + 1] - octave[s] for s in range(len(octave) - 1)]) return dog # 每组 s+1 层高斯得到 s 层 DoG

这四招合起来,把一个看起来是 O(尺度数 × 图像面积 × 核面积) 的过程压到了接近 O(图像面积) 的量级。这也是 SIFT 在 2004 年就能在普通机器上跑起来的原因,设计上的精巧程度比算法本身更值得看。

3.6 方向投票与 128 维描述子:4×4×8 是怎么来的

找到关键点之后,要给每个点定一个主方向,目的是让描述子具备旋转不变性。做法是以关键点为圆心、1.5σ 为半径取一个邻域,计算邻域内每个像素的梯度幅值和方向,然后把 0° 到 360° 分成 36 个 bin(每 10° 一个),每个像素按自己的方向往对应的 bin 里投票,票数是梯度幅值。这里有两重加权:离关键点越远权重越小(通常用高斯加权),幅值越大权重越大。投完之后取峰值所在的方向作为主方向。

def orientation_hist(mag, ang, bins=36): hist = np.zeros(bins) for m, a in zip(mag.ravel(), ang.ravel()): idx = int((a % (2 * np.pi)) / (2 * np.pi) * bins) % bins hist[idx] += m # 幅值加权投票 # 圆周高斯平滑,抑制单个 bin 的抖动 kernel = np.array([0.25, 0.5, 0.25]) hist = np.convolve(np.r_[hist[-1], hist, hist[0]], kernel, 'same')[1:-1] return hist / (hist.sum() + 1e-9)

如果一个 bin 的峰值超过主峰值的 80%,常见实现会再给它一个辅方向,同一个关键点因此产生多个描述子。这算是一个工程上的补丁:真实图像里的角点方向未必那么明确,多给几个候选能提高匹配召回率。

描述子的生成过程是把邻域切成 4×4 共 16 个子块,每个子块内统计 8 个方向的梯度直方图,最后拼成 16×8=128 维向量。窗口大小通常取 16×16 像素,每个子块 4×4 像素;整个窗口要先旋转到主方向,保证旋转不变性。生成之后还要做两次归一化:先除以向量的模长,把值截断到 0.2 以内,再归一化一次。截断的作用是压制个别超大梯度的影响——那些通常来自光照突变或饱和像素,不压住的话描述子会被它们主导。

注意:128 这个数字不是玄学,它是 4×4 个子块 × 8 个方向 bin 的结果。子块数决定空间分辨率,bin 数决定方向分辨率,两个都要在「描述力」和「维度灾难」之间平衡。改成 4×4×4 也能跑,只是匹配精度会掉。

3.7 视觉词典与纹理基元:从局部特征到全局表示

SIFT 给出的是一堆无序的关键点描述子,没法直接喂给分类器。视觉词袋(BoW)的思路是先把描述子聚成若干类:对所有训练图像的描述子跑一次 K-means,每个聚类中心就是一个「视觉词」,所有聚类中心组成视觉词典。之后每张图都被表示成词典上的直方图——统计图中有多少个描述子落进了每个词。词典大小就是 K-means 的 k,取太小学不出区分度,取太大则直方图稀疏、计算量上升。

纹理基元的构造走的是同一条路,只是换了个起点:把图像中每个像素邻域表示成一个 48 维向量,对这些向量聚类得到 k 个视觉词,然后用固定大小的窗口在图上滑动,统计窗口内每个词出现的次数,得到一个直方图。两个窗口的直方图相似,就认为它们属于同一种纹理区域。这种做法本质上是把「纹理」定义成了「局部模式的分布」,而不是某种具体的频率特征,好处是不依赖傅里叶变换那套假设。

4. 从线性分类器到残差网络:W、隐藏层、感受野的结构账

4.1 W 的代数与几何双视角

线性分类器的打分函数写成 s = f(x, W) = Wx。代数视角下,把 W 按行拆开,每一行是一个和输入同维度的向量,可以重新排列成一张和输入图像同尺寸的「模板图」。Wx 的第 i 个分量就是第 i 个模板和输入的内积,值越大说明输入越像这个类别的平均模式。几何视角下,W 的每一行是一个超平面的法向量,Wx 的符号告诉你输入落在超平面的哪一侧,数值大小(在法向量归一化后)是输入到超平面的有符号距离。n 个类别就是 n 张超平面,预测结果是得分最高的那个。

这两个视角说的是同一件事,但用处不同。代数视角方便可视化——把学到的 W 画出来,能看出模型到底在找什么模式;几何视角方便理解损失函数——SVM 的 hinge loss 就是在要求正确类别的得分比错误类别高出至少一个间隔,也就是要求样本离决策边界足够远。

import numpy as np def softmax_ce(s, y): """s: 各类得分; y: 正确类索引""" s = s - s.max() # 减最大值防指数溢出 p = np.exp(s) / np.exp(s).sum() return -np.log(p[y]) # one-hot 下只剩 GT 位置 def svm_hinge(s, y, margin=1.0): """折页损失:错误类得分超过正确类 + margin 才计惩罚""" m = np.maximum(0, s - s[y] + margin) m[y] = 0 # 正确类自己不算 return m.sum()

交叉熵的数学解释是最大似然:softmax 把得分转成概率分布,交叉熵衡量预测分布和 one-hot 真实分布之间的差异,最小化它就是最大化正确类别的对数概率。铰链损失的解释是间隔最大化:它不关心具体概率,只关心正确类别的得分有没有比错误类别高出足够多,超出的部分不再产生梯度。两者的差别在实际训练中表现为:交叉熵的梯度永远不会饱和到零(除非概率已经极端接近 1),hinge 则在满足间隔后直接停止惩罚,对异常点更鲁棒但也更容易欠拟合。

4.2 隐藏层:从单一模板到分布式表示

线性分类器的能力边界很清楚:每个类别只能学一个模板,决策边界必须是输入空间里的一个超平面。异或问题就是最经典的反例——四个点,无论怎么画直线都分不开。隐藏层的作用是做一次特征变换,把输入从原始空间映射到另一个空间,让原本线性不可分的数据在新空间里变得线性可分。

import numpy as np X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float) W1 = np.array([[1,-1],[-1,1]], dtype=float) # 手工构造的隐层权重 b1 = np.array([0,-1], dtype=float) H = np.maximum(0, X @ W1.T + b1) # ReLU 之后的表示 print(H)

这段代码里没有训练,权重是手写的,目的只是展示「变换之后线性可分」这件事长什么样。第一层把四个点搬到了一个新坐标里,第二层用一条直线就能把它们分开。反向传播的作用是在数据上自动找到这组权重,而不是靠人猜。

代数视角下还有一层更深的解释。假设是 10 分类,单层模型只能学 10 个模板,每个类别一个。加一个 100 维的隐藏层,相当于学了 100 个共享模板,每个类别不再绑定一个模板,而是从这 100 个里挑几个组合起来表达自己——这就是分布式表示。它的好处是模板可以复用:猫和狗都需要「毛」这个模板,只是组合方式不同。代价是参数量上去了,简单的梯度下降不好使,得靠反向传播逐层算梯度。这也是为什么神经网络在理论上早就成立,却要等到反向传播和算力都到位才真正跑起来。

4.3 感受野与权值共享的神经计算解释

感受野指的是一个神经元能看到的输入区域。全连接层里,每个输出神经元都连着全部输入,感受野就是整张图;卷积层里,一个 3×3 卷积核的输出神经元只连着输入的 3×3 区域,感受野就是 3×3。堆叠多层之后感受野会扩大:两个 3×3 堆叠的感受野是 5×5,三个是 7×7,通式是 R_n = R_{n-1} + (K_n - 1) × 步幅。这个公式是算网络结构时最常用的一个,算错就会导致后面的特征图尺寸对不上。

权值共享说的是同一个卷积核在图像的不同位置重复使用。用神经计算的语言讲,全连接网络里每一条边都有一个独立的权重,任意两条边的权重几乎不可能相同;卷积网络里,同一个核在不同位置产生的那些连接,权重是同一组数。这不是为了省参数而省参数,它编码了一个先验:图像中同一个模式出现在左上角和右下角,应该有相同的响应。这个先验叫平移等变性,是卷积结构有效的根本原因。

结构参数量连接方式
全连接 224×224×3 → 1000约 1.5 亿每个输入输出对一条独立边
64 个 3×3×3 卷积核1792空间位置共享同一组权重

参数量的差距是四个数量级。这也解释了为什么早年间的网络动辄上亿参数、训练不动,而换成卷积结构之后参数量骤降。

4.4 小卷积核、1×1 卷积与残差:三笔结构账

小卷积核的价值不止「参数少」这一条。三个 3×3 堆叠的感受野等于一个 7×7,但参数量是 3×(3×3×C×C)=27C² 对 49C²,少了将近一半;更重要的是中间多了两次 ReLU,非线性更强,判决函数更有区分度;此外还有隐式正则化的效果——分解成多个小核,相当于对权重施加了约束,不容易过拟合。唯一的代价是中间层的特征图要占更多显存,反向传播时这些中间结果都得留着。

def conv_params(k, cin, cout): return (k * k * cin + 1) * cout # +1 是偏置项 print(conv_params(7, 512, 512)) # 12,845,568 print(3 * conv_params(3, 512, 512)) # 7,079,424

1×1 卷积经常被误解成「什么都没做」。它确实不扩大感受野,但它在通道维度上做了一次全连接:把每个像素位置的 C 个通道线性组合成 C' 个通道,再过一个非线性。作用有三个——升降维(用 1×1 把通道数压下去,再做 3×3,能省大量计算)、跨通道信息融合、增加非线性而不改变空间结构。在瓶颈结构里,它承担的是「先压缩再卷积再扩张」中的压缩和扩张两步。

残差模块解决的是退化问题:网络加深,训练误差反而变大,这不是过拟合,而是深层网络难以优化。残差的写法是把输出表示为 F(x) + x,让网络去学残差 F(x) 而不是完整的映射 H(x)。如果某一层其实什么都不用做,学 F(x)=0 比学 H(x)=x 容易得多,因为把权重压到零比让一堆非线性层精确地复现恒等映射简单。捷径连接还有一个副产品:反向传播时梯度可以直接沿着这条通路流回去,不用穿过所有中间层,梯度消失的风险小了很多。

4.5 训练侧的四个开关:Sigmoid、增广、Dropout、Momentum

Sigmoid 引起梯度消失的原因是它的导数最大只有 0.25,在 |x| 大于 5 之后几乎为零。反向传播是连乘,10 层

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询