【置顶】计算机视觉知识点总结

章节导航篇

机器学习: 基本概念, 逻辑回归, KNN, 支持向量机, 决策树, 朴素贝叶斯, 降维, 聚类, XGBoost, Bagging

深度学习: 优化方法, 初始化方法, 损失函数, 激活函数, 正则化, 归一化, 感受野, 全连接层, 卷积层, 反卷积层, 空洞卷积, 池化层, 训练问题

网络结构: AlexNet, VGGNet, InceptionV1, InceptionV2/3, InceptionV4, InceptionResNet, Xception, ResNet, ResNeXt, DenseNet, SqueezeNet, MobileNet, MobileNetV2, ShuffleNet, ShuffleNetV2, SENet,

目标检测:

机器学习篇

基本概念

TP, TN, FP, FN 及各种比值代表的含义

混淆矩阵(二分类, 多分类的情况类似):

- Positive Predictions Negative Predictions
Positive Label TP FN
Negative Label FP TN

Accuracy(准确率):

  • 准确率的局限性: 当正负样本数量极度不均衡时, 准确率无法正确的反映出模型的好坏, 例如, 某一分类模型的分类准确率超过了 95%, 但是在实际使用时, 对于正样本(奢侈品用户)的识别率仍然很差. 出现这种情况的原因可能是因为正样本(奢侈品用户)的数量极少, 数据集中的 99% 都是负样本, 那么, 此时即使模型完全不学习, 直接将所有样本识别成负样本, 也能获得 99% 的准确率. 所以, 当不同类别的样本比例非常不均衡时, 占比大的类别往往成为影响准确率的最主要因素.
  • 样本比例不均衡时的解决方法: 换用更为有效的平均准确率(每个类别下的样本准确率的算术平均)作为模型评估的指标.
  • 即使换用了正确的评估指标, 模型的实际表现还是很差, 可能原因是:
    1. 模型过拟合或欠拟合
    2. 测试机和训练集划分不合理
    3. 线下评估和线上测试的样本分布存在差异.

Precision(精度):

Precision 在面对正负样本数量极度不均衡时的缺陷: 当负样本数量大幅增加时, FP 也会大幅增加, 这样, 就会使得 Precision 相对降低. 因此, 对于类别数量不均衡的情况, PR 曲线无法完美的反应模型的性能. 而 ROC 曲线使用真正率和假正率, 他们的分子分母都依赖于相同的类别, 这样, 单个类别的数量大幅增加时, 对于分子和分母是同比例增加的, 因此 ROC 受类别不均衡的影响较小.

TPR(召回率):

  • 模糊搜索时, Top 5 精度很高, 但是用户经常找不到想要的结果, 这是为什么. 可能是因为阈值卡的很高, 导致前面的正样本几乎全部预测正确, 但是却也筛掉了大量的低置信度的正样本, 这是, 即使 Precision@5 达到了 100%, Recall@5 也仅仅只有 5%.

FPR(误诊率, 误报率):

FNR(漏报率):

PR 曲线

精度又名查准率, 关心的是 “查出的所有正例中, 哪些正例是查对的”
召回率又名查全率, 关心的是 “对于所有的正例, 正确查出了多少个”

这二者是一对矛盾的度量, 因为我们很容易知道:

  • 如果我们希望查准率高, 那么可以认为是 “只有当十成把握认为其是正例时, 才将其挑出”.
  • 而如果我们希望召回率高, 那么可以认为是 “宁错杀一百, 不放过一个”. 查准率和查全率的曲线又叫 PR 曲线, 如下图所示.

SummaryOfComputerVision%2FPR.jpg

注意: 上图中有一个明显的误区, 那就是在严格意义上, PR 曲线是不会经过 (1, 0) 点的. 这是因为, 当 recall = 1 时, 说明已经找到了所有的正样本, 而此时的 $precision = (tp) / (tp + fp)$, 由于此时的分子不为 0, 所以 precision 也肯定不为 0. 只不过, 由于当 recall = 1 时, 其 fp 往往会非常大, 这样 precision 就会无限接近 0. 另外, 外 recall = 0 时, 我们令 precision = 1 也是通过 $0 / 0 = 1$ 得到的.

通常情况下, 如果一个学习器的 PR 曲线被另一个学习器 完全包住. 那么我们就认为后者的性能优于前者. 当二者存在交叉时, 我们可以通过四种方式来确定学习器的优劣:

  1. 计算 PR 曲线与横纵坐标轴围成的面积, 面积越大越好;
  2. 利用平衡点 (BEP, 查准率=查全率), BEP 越大越好;
  3. 利用 $F_1$ 度量, $F_1$ 越大越好. $F_1$ 度量实际上就是当 $\beta = 1$ 时的 $F_\beta$ 度量, $F_1$ 度量认为查准率和查全率的重要性相同.
  4. 利用 $F_\beta$ 度量, 当 $\beta < 1$ 时, 查准率权(精度)重更大, 当 $\beta > 1$ 时, 查全率(召回率)权重更大. $F_\beta$ 的计算公式来自于加权调和平均数.

在 PR 曲线的前段, 相同 precision 下的不同 recall 代表的含义:

  • recall 高
  • recall 低: 阈值卡的较高? 导致 precision 相同下, recall 较低

ROC 曲线

很多学习器是为测试样本产生一个实值或概率预测, 然后将这个预测值与一个分类阈值进行比较, 若大于阈值分为正例, 否则分为负例, 因此分类过程可以看做是选取一个合适的截断点, 那么到底什么样的截断点更合适. ROC 正是从这个角度来研究学习器好坏的工具.

ROC 曲线的纵坐标和横坐标分别是召回率(查全率)和假正率(误诊率), 下图为 ROC 曲线图, 实际任务中会利用有限个测试样本来绘制 ROC 图, 所以产生的大多不是平滑的曲线.

SummaryOfComputerVision%2FROC.jpg

  • 和 PR 曲线不同, ROC 曲线一定是经过 (0, 0) 和 (1, 1) 的一条曲线. 因为当阈值选择为正无穷时, FP 和 TP 均为 0, 所以一定经过 (0, 0) 点, 将阈值为 0 时, 所有的样本都被预测为正样本, 因此 FN 和 TN 均为 0, 此时 TPR 和 FPR 均为 1, 所以一定经过 (1, 1) 点
  • ROC 曲线一般都处于 $y=x$ 这条直线的上方, 如果不是的话, 只要把模型预测的概率反转成 $1-p$ 就能得到一个更好的分类器, 所以 AUC 的取值通常在 $0.5~1$ 之间.

和 PR 曲线类似, 如果一个学习器的 ROC 曲线被另一个学习器 “完全包住”, 则后者的性能优于前者. 对于 ROC 曲线来说, 我们需要先观察其是否没有剧烈的波动, 如果曲线不够平滑, 波动距离, 那么猜测可能发生了过拟合现象, 如果 ROC 是光滑的, 这个时候就可以通过曲线的 AUC (area under curve) 来判断模型的好坏, AUC 越大的模型越好. 因为 AUC 越大, 说明模型可以在较低的误诊率下达到较高的召回率.

绘制 ROC 曲线

假设已经得出一系列样本被划分为正类的概率,然后按照大小排序,下图是一个示例,图中共有20个测试样本,”Class” 一栏表示每个测试样本真正的标签(p表示正样本,n表示负样本),”Score” 表示每个测试样本属于正样本的概率。

SummaryOfComputerVision%2Fauc1.jpg

接下来,我们从高到低,依次将“Score”值作为阈值threshold,当测试样本属于正样本的概率大于或等于这个threshold时,我们认为它为正样本,否则为负样本。举例来说,对于图中的第4个样本,其“Score”值为0.6,那么样本1,2,3,4都被认为是正样本,因为它们的“Score”值都大于等于0.6,而其他样本则都认为是负样本。每次选取一个不同的threshold,我们就可以得到一组FPR和TPR,即ROC曲线上的一点。这样一来,我们一共得到了20组FPR和TPR的值,将它们画在ROC曲线的结果如下图

SummaryOfComputerVision%2Fauc2.jpg

计算 FPR 和 TPR 的方法:

  1. 先统计 20 组样本中, 有多少个正样本, 有多少个负样本, 假设为别为 $N$ 和 $M$, $N+M=20$.
  2. 对于每一组样本, 选取它的 socre 作为阈值, 那么在 score 之上的预测结果中(包括当前样本), 我们都认为将其预测成正样本, 那么, 假设这些样本的实际正样本数量为 $n$, 实际负样本数量为 $m$, 则 TPR 和 FPR 分别为: $n/N$, $m/M$.
  3. 按照 score 的大小从高到低重复执行 2 过程

注意1: 上面的 Score 使用了经过 softmax 转换后的值, 也可以看做是概率, 但是实际上, 我们在画 ROC 图的时候, 只需要获取到样本之间的相对大小即可, 所以我们可以直接使用为经过 softmax 转换的 socre 来画图, 得到的 ROC 和 AUC 不会发生变化.
注意2: 上图中, 我们可以看到 ROC 曲线是根据一组组的 FPR 和 TPR 的值得到的, 因此呈现出 “阶梯状”, 并且面积均为矩形. 但是如果预测出来的 score 存在有相同分数的情况, 那么就会出现梯形, 此时不利用直接计算面积.

  • 形成矩形的原因: 每新增一个样本, 它要么只增加 FPR, 要么只增加 TPR, 所以曲线要么向右延伸, 要么向上延伸;
  • 形成梯形的原因: 如果说, 有多个样本的 score 相同, 那么当选择该 score 作为阈值时, 就会同时增加 TPR 和 FPR, 因此曲线就会想右上方延伸, 故而形成梯形.

AUC 的含义及计算

AUC 越大, 说明分类器越可能把真正的正样本排在前面, 分类性能越好.

含义: 首先, AUC 的值是处于 [0, 1] 区间内的, 实际上, 从 ROC 的绘制过程中我们就可以看出, AUC 可以看做是一个概率值, 它代表着当我们随机挑选一个正样本和负样本时, 当前学习器对正样本的预测值大于负样本的概率, 也就是说当前学习器将这个正样本排在负样本前面的概率. 我们通常希望学习器的 AUC 的值越大越好, 实际上也就是希望当我们随机拿出一个正样本和负样本时, 学习器都能够将这个正样本排在负样本的前面, 很容易知道, 当 AUC 的值为 1 时, 我们按照 score 排列正负样本, 所有的正样本都会处在负样本的前面, 这个时候我们很容易找到一个阈值使得学习器的分类完全正确; 当 AUC 的值为 0 时, 此时按照 score 排列, 所有的负样本都处在正样本的前面, 这个时候学习器的性能最差.(我们这里讲的 socre 代表样本是正样本的概率, 因此不能反过来用). 也就是说, 它衡量的是模型将一堆样本进行分类的能力.

计算:
一般情况下, AUC 的计算都是指 ROC 曲线的 AUC, 其计算方式有以下三种

  1. 计算每一段小矩形的面积, 之后求和. 这种计算方式只适用于 score 均不相等时, ROC 曲线只有小矩形构成的情况, 如果 score 有相等的情况出现, 那么就需要计算梯形, 比较麻烦.
  2. 根据 AUC 的含义, 我们可以从另一个角度来计算 AUC. 那就是随机挑选一个正样本和一个负样本, 正样本排在负样本前面的概率就是 AUC 的值. 对于有限的样本数量, 我们认为频率可以近似概率. 因此, 对于具有 $N$ 个正样本, $M$ 个负样本组成的测试集合, 我们总共有 $N\times M$ 组不同的正负样本组合, 然后只需要统计这 $N\times M$ 个组合中, 正样本 score 大于负样本 score 的数量, 将其除以 $N\times M$ 即可. 算法时间复杂度为 $O(N^2M^2)$
  3. 方法二的复杂度较高, 方法三用一种更高效的方式来计算. 我们先将样本按照 score 排列, 那么可以知道, 第一个样本与任意的样本组合, 都是前者的 score 大, 于是, 我们按照排列的顺序, 为每一个样本赋予 rank, 其中第一位的 rank 为 $N+M$, 最后一位的 rank 为 1. 然后, 利用下面的公式计算 AUC:

上面的公式非常好理解, 其中分母 $N\times M$ 代表了所有可能的 (正样本, 负样本) 的组合数量, 分子中 rank 的值实际上代表了该样本能够产生多少种 前大后小 的组合, 而这些组合中需要减去 $\frac{N(1+N)}{2}$ 中 (正样本, 正样本) 的组合情况. 另外, 需要特别注意的是, 在存在 score 相等的情况时, 需要赋予其相同的 rank (不论正负样本), 具体操作就是将这些样本原来的 rank 求和去平均.

ROC 曲线与 PR 曲线的比较

相比 PR 曲线, ROC 曲线有一个特点, 那就是当正负样本的分布发生变化时, ROC 曲线的形状能够基本保持不变, 而 PR 曲线的性质一般会发生剧烈的变化. 具体来说就是, Recall 和 FPR 受正负样本数量不均衡问题的影响较小, 而 Precision 受其影响大(负样本数量升高时, Precision 会大幅降低), 所以 PR 曲线会发生剧烈变化

这个性质的实际意义: 能够更加客观的衡量模型本身的性能.
在很多实际问题中, 正负样本数量往往很不平衡, 比如, 计算广告领域经常设计转化率模型, 正样本的数量往往是负样本数量的 1/1000 甚至 1/10000. 若选择不同的测试集, PR 曲线的变化率就会非常大, 而 ROC 曲线则能够更加稳定的反应模型本身的好坏.

余弦距离

在机器学习问题中, 通常将特征表示为向量的形式, 所以在分析两个特征向量之间的相似性时, 常使用余弦相似度来标识, 其定义为: 对于两个向量 A 和 B, 其余弦相似度定义为:

即两个向量夹角的余弦, 关注的是向量之间的角度关系, 并不关心它们的绝对大小, 其取值范围为 [-1, 1], 相同的两个向量之间的相似度为 1. 如果希望得到类似于距离的表示, 将 1 减去余弦相似度即为余弦距离. 因此, 余弦距离的取值范围是 [0, 2], 相同的两个向量余弦距离为 0.

余弦距离与欧式距离的取舍: 在图像领域, 对应的特征维度通常比较高, 而余弦距离在高维情况下依然可以保持 “相同时为 1, 正交时为 0, 相反时为 -1” 的性质, 而欧式距离的数值则受到维度的限制, 范围不固定. 总体来说, 欧式距离体现的是数值上的绝对差异, 而余弦距离体现的是方向上的相对差异.

余弦距离是否是一个严格定义的距离?: 不是, 仅满足距离定义的正定性和对称性, 不满足三角不等式, 因此不是一个严格定义的距离.
距离的定义: 在一个集合中, 如果每一对元素均可唯一确定一个实数, 使得三条距离公理(正定性, 对称性, 三角不等式)成立, 则该实数可称为这对元素之间的距离. 详细见 百机 p035

AB 测试

AB 测试时验证新模块, 新功能, 新产品是否有效, 新算法, 新模型的效果是否有提升, 新设计是否受到用户欢迎的主要测试方法. 在机器学习领域, AB 测试时验证模型最终效果的主要手段.

需要进行在线 AB 测试的原因:

  1. 离线评估无法完全消除模型过拟合的影响, 因此, 得出的离线评估结果无法完全代替线上评估结果
  2. 离线评估无法完全还原线上的工程环境. 如数据丢失, 标签确实等等
  3. 线上系统的某些商业指标在离线评估中无法计算. 如用户点击率, 留存时长, PV 访问量等等.

如何进行 AB 线上测试:
主要手段是进行用户分桶, 使其分为实验组和对照组, 在分桶过程中, 注意样本的独立性和采样方式的无偏性.

如何划分实验组和对照组: 百机 p039

模型评估方法

  • Holdout 检验: 最简单最直接的验证方法, 将原始的样本集合随机划分成训练集和验证集, 80% 用于训练, 20% 用于模型验证. 缺点: 验证集上计算出来的评估指标和原始分组关系很大
  • 交叉检验:
    • k-折交叉验证: 首先将全部样本划分成 $k$ 个大小相等的样本子集; 依次遍历这 $k$ 个子集, 每次把当前子集作为验证集, 其余所有子集作为训练集, 进行模型的训练和评估; 最后把 $k$ 次评估指标的平均值作为最终的评估指标. 实际中, $k$ 通常取 5 或 10.
    • 留一验证: 每次留下 1 个 样本作为验证集, 其余所有样本作为测试集. 样本总数为 $n$, 依次对 $n$ 个样本遍历, 进行 $n$ 次验证, 再将评估指标求平均值得到最终的评估指标. 在样本总数较多的情况下, 留一验证法的时间开销极大. 工程中很少使用.
  • 自助法: 基于自助采样的检验方法, 对于总数为 $n$ 的样本集合, 进行 $n$ 次有放回的随机抽样, 得到大小为 $n$ 的训练集. $n$ 次采样过程中, 有的样本会被重复采样, 有的样本没有被抽出过, 将这些没有被抽过的样本作为验证集, 进行模型验证.

在自助法的采样过程中, 对 $n$ 个样本进行 $n$ 次有放回的采样, 当 $n$ 趋于无穷大时, 最终有多少数据从未被选择过? 答: 36.8%
解: 一个样本在一次抽样过程中未被抽中的概率为 $(1 - \frac{1}{n})$, $n$ 次抽样均未被抽中的概率为 $(1-\frac{1}{n})^n$, 当 $n$ 趋于无穷大时, 概率为 $\lim_{n \rightarrow \infty} (1 - \frac{1}{n})^n$, 根据重要极限, $\lim_{n \rightarrow \infty} (1 + \frac{1}{n})^n = e$, 所以有: $\lim_{n \rightarrow \infty} (1 - \frac{1}{n})^n = \frac{1}{e} \approx 0.368$

超参数调优

  • 网格搜索: 查找搜索范围内的所有点来确定最优值. 实际使用时一般会先使用较广的搜索范围和较大的步长, 来找到全局最优可能的位置, 然后逐渐缩小搜索范围和步长, 来寻找更精确的最优值.
  • 随机搜索: 如字面意思一般, 在搜索范围内随机选取样本点, 其理论依据是, 如果样本点集足够大, 那么大概率可以找到全局最优
  • 贝叶斯优化: 首先根据先验分布, 假设一个搜索函数; 然后, 每一次使用新的采样点来测试目标函数时, 利用这个信息来更新目标函数的先验分布, 最后, 算法测试由后验分布给出的全局最优值. 缺点: 一旦找到了一个局部最优值, 就会在该区域不断采样, 很容易陷入局部最优.

线性回归

学习目标: 使得 $f(x_i)$ 无限近似于 $y_i$

误差: 均方误差

求解: 最小二乘法: 试图找到一条直线, 使得所有样本到直线上的欧式距离之和最小. 通过参数估计, 分别求均方误差相对于 $w$ 和 $b$ 的偏导数, 令偏导数=0, 得到最终的解

最小二乘法的缺点: 数据存在异常值时, 受到的影响大

逻辑回归

逻辑回归与线性回归

逻辑回归和线性回归的定义

逻辑回归定义
逻辑回归通常用来解决二分类问题(也可以解决多分类问题), 用于估计某种事物的可能性. 我通过 Logistic 函数将拟合函数的输出值归一化到 (0, 1) 之间, 我们可以将其认为是分类为 1 类的预测概率. Logistic 函数公式(和 Sigmoid 函数形式形式相同)如下:

Logistic(Sigmoid) 函数的求导公式有一个特性: $g’(z) = g(z)(1 - g(z))$.

线性回归定义:
线性回归通常是解决连续数值预测问题, 利用数理统计的回归分析, 来确定变量之间的相互依赖关系. 其公式通常表示如下:

逻辑回归用极大似然求解

逻辑回归的预测函数为:

由于因变量(标签)服从二项分布(伯努利), 取值为 0 和 1. 那么可以得到下面两个式子:

将上面两个式子合并:

根据上面的式子,给定一定的样本之后,我们可以构造出似然函数,然后可以使用极大似然估计MLE的思想来求解参数。为了满足最小化风险理论,我们可以将MLE的思想转化为最小化风险化理论,最大化似然函数其实就等价于 最小化负的似然函数。 对于MLE,就是利用已知的样本分布,找到最有可能(即最大概率)导致这种分布的参数值;或者说是什么样的参数才能使我们观测到目前这组数据的概率最大。

使用MLE推导LR的loss function的过程如下。
首先,根据上面的假设,写出相应的极大似然函数(假定有m个样本):

直接对上面的式子求导会不方便,因此,为了便于计算,我们可以对似然函数取对数,经过化简可以得到下式的推导结果:

因此,损失函数可以通过最小化负的似然函数得到,即下式:

所以, 利用梯度法或者牛顿法都可以得到其最优解, 例如梯度法, 计算每个参数的偏导, 如下(注意, sigmoid 的导数 = $g(x)(1-g(x))$:

上述中, $x_{ij}$ 表示第 $i$ 个样本的第 $j$ 个属性的取值.
于是, $w$ 的更新方式为:

逻辑回归和线性回归的区别和联系

联系
逻辑回归本质上还是线性回归, 只是在特征到结果的映射中加入了一层函数映射, 即先把特征线性求和, 然后使用函数 $g(z)$ 将连续结果值映射到 (0, 1) 之间, 我们将线性回归模型的表达式代入到 Logistic(Sigmoid) 函数之中, 就得到了逻辑回归的表达式:

实际上, 我们将逻辑回归的公式整理一下, 就可以得到 $log\frac{p}{1-p} = \theta^T x$, 其中, $p = P(y=1 | x)$, 也就是将给定输入 $x$ 预测为正样本的概率. 那也就是说, 逻辑回归实际上也可以看做是对 $log\frac{p}{1-p}$ 的线性回归. 但是在关于逻辑回归的讨论中, 我们均认为 $y$ 是因变量, 而不是 $\frac{p}{1-p}$, 这便引出逻辑回归与线性回归最大的区别, 即 逻辑回归中的因变量是离散的, 而 线性回归中的因变量是连续的. 并且在自变量 $x$ 和超参数 $\theta$ 确定的情况下, 逻辑回归可以看做是广义线性模型在因变量 $y$ 服从二元分布时的一个特殊情况, 而使用最小二乘法求解线性回归时, 我们认为因变量 $y$ 服从正态分布.

区别:
最本质区别: 逻辑回归处理的是分类问题, 线性回归处理的是回归问题. 在逻辑回归中, 因变量的取值是一个 二元分布(不是二项分布). 而线性回归中实际上求解的是对真实函数关系的一个近似拟合.

对于一个二分类问题, 如果数据集中存在一些离异值, 在不清洗数据的情况下, 选择逻辑回归还是 SVM? 为什么?

用 SVM, 因为 SVM 的分类只与支持向量有关, 所以对离异值的忍受能力更强.

逻辑回归与 SVM 的区别是什么

两种方法都是常见的分类算法, 从目标函数上看, 区别在于逻辑回归采用的是 log 损失, 而 SVM 采用的是 hinge 损失. 这两个损失函数的目的都是增加对分类影响较大的数据点的权重, 减少与分类关系较小的数据点的权重. SVM 的处理方法是只考虑支持向量, 也就是和分类最相关的少数点, 去学习分类器. 而逻辑回归通过非线性映射, 大大减小了离分类平面较远的点的权重, 相对提升了与分类最相关的数据点的权重. 两者的根本目的都是一样的. 此外, 根据需要, 两个方法都可以增加不同的正则化项, 如 L1, L2 等. 所以在很多实验中, 两种算法的结果是很接近的.
但是逻辑回归相对来说模型更加简单, 并且实现起来, 特别是大规模线性分类时比较方便. 而 SVM 的实现和优化相对来说复杂一些, 但是 SVM 的理论基础更加牢固, 有一套结构化风险最小化的理论基础, 另外, SVM 转化成对偶问题后, 分类只需要计算与少数几个支持向量的距离即可, 这在进行复杂核函数计算时有时很明显, 能够大大简化模型和计算量

损失函数

逻辑回归和 SVM 的损失函数分别为:

上式中, $g(z) = \frac{1}{1 + exp^(-z)}$. 可以看出, 逻辑回归采用的是对数损失(log loss), 而 SVM 采用的是铰链损失(hinge loss), 即:

  • LR 损失: $Loss(z) = log(1 + exp(-z))$
  • SVM 损失: $Loss(z) = (1 - z)^{+}$

逻辑回归产出的是概率值, 而 SVM 只能产出正负类, 因此 LR 的预估结果更容易解释.
SVM 主要关注的是 “支持向量”, 也就是和分类最相关的少数点, 即关注局部关键信息; 而逻辑回归是在全局进行优化的, 这导致 SVM 天然比逻辑回归有更好的泛化能力, 防止过拟合.

逻辑回归和 SVM 哪个是参数模型, 哪个是非参数模型

LR 是参数模型, SVM 是非参数模型

定义: 参数模型通常假设总体随机变量服从某一个分布, 该分布由一些参数确定(比如正态分布的均值和方差), 在此基础上构建的模型称为参数模型; 非参数模型对于总体的分布不做任何假设, 只是知道总体是一个随机变量, 其分布是存在的(分布中也可能存在参数), 但是无法知道其分布的形式, 更不知道分布的相关参数, 只有在给定一些样本的条件下, 能够依据非参数统计的方法进行推断. 因此, 问题中有没有参数, 并不是参数模型和非参数模型的区别. 其主要区别在于总体的分布形式是否已知. 为何强调 “参数” 与 “非参数”, 主要原因在于参数模型的分布可以由参数直接确定.

参数算法包括两部分: (1) 选择目标函数的形式; (2) 从训练数据中学习目标函数的系数. LR 会预先假设目标函数(直线或其他), 因此它是参数模型. 其他参数模型还有: 线性成分分析, 感知机.
参数模型的优点:

  • 简单: 理论容易理解, 结果容易解释
  • 快速: 参数模型的学习和训练速度较快
  • 数据更少: 通常不需要大量的数据也可以较好的拟合?

参数模型的缺点:

  • 约束: 以选定函数形式的方式来学习本身就限制了模型的解空间
  • 有限的复杂度: 通常只能应对简单的问题
  • 拟合度小: 实际中通常无法和潜在的目标函数温和.

非参数算法: 对于目标函数的形式不作过多的假设. 当有用许多数据而先验知识很少时, 非参数学习通常很有用, 因为此时不需要关注参数的选取. 常用的非参数算法包括: K 最近邻, 决策树, SVM, 朴素贝叶斯, 神经网络.
非参数算法的优点:

  • 可变性: 可以拟合许多不同的函数形式
  • 模型强大: 对于目标函数不作假设或者作微小的假设
  • 表现良好: 对于预测结果表现通常较好

非参数算法的局限性:

  • 需要更多数据: 对于拟合目标函数需要更多的训练数据
  • 速度慢: 参数更多, 所以训练通常较慢

逻辑回归和 SVM 分别适合在什么情况下使用

令 $n = 特征数量$, $m = 训练样本数量$, 则:

  • 如果 $n > m$, 则使用 LR 或者不带核函数的 SVM, 因为特征数相对于训练样本数已经够大了, 使用线性模型就能取得不错的效果, 不需要过于复杂的模型;
  • 如果 $n < m$, 则使用 SVM(高斯核函数), 因为在训练样本数量足够大而特征数量较小的情况下, 可以通过复杂核函数的 SVM 来获得更好的预测性能, 而且因为训练样本数量并没有达到百万级, 使用复杂核函数的 SVM 也不会导致运算过慢;
  • 如果 $n << m$, 此时因为训练样本数量特别大, 使用复杂核函数的 SVM 会导致训练过慢, 因此应该考虑通过引入更多特征, 然后使用 LR 或者不带核函数的 SVM 来训练更好的模型

在实际使用中, 通常当数据非常非常大(几个 G, 几万维度特征), 跑不动 SVM 时, 用 LR. 如今数据量大幅增加, 相比来说 LR 反而用的更多了.

逻辑回归为什么用Sigmoid

逻辑回归的形式是从最大熵模型推导出来的.

最大熵原理认为:在所有可能的概率模型中,熵最大的模型就是最好的模型。通常地,还会借助一些约束条件来约束概率模型的集合,故最大熵原理也可表述为在满足约束条件的模型中选取熵最大的模型。

最大熵原理体现了一种思想:不要把鸡蛋都放到一个篮子里。比如说扔骰子并判断每一面朝上的概率各是多少,我们总是会说都是 [公式] ,因为这是最安全的选择,保留了各种可能性,确保了最大的不确定性。

假设离散型随机变量 $X$ 的概率分布是 $P(X)$ ,那么它的熵为:

最大熵模型的思想:

  1. 根据现有观察样本, 计算现有的条件概率分布对应的熵. 条件熵是已知 X 的条件下, Y 的条件概率对应的熵, 如下:
  1. 让条件熵最大 $max(H(Y\vert X))$, 也可以说是让它的相反数最小 $min(-H(Y\vert X))$

  2. 将最大条件熵转换成一个带约束的最优化问题, 然后用拉格朗日函数转换成对偶问题, 利用极大似然函数进行求解. 其二分类的最终形式与 sigmoid 函数相似, 同时也解释了为什么用极大似然估计来求解 LR.

逻辑回归为什么用极大似然函数做损失

  1. 最大熵模型的推导过程就是用的极大似然估计.
  2. 因为这样构成的损失函数是高阶可导的凸函数, 比较方便用经典的数值优化算法(梯度下降, 牛顿)进行优化.

KNN

简述 KNN 算法的原理

KNN 算法进行分类和回归时的区别

KNN 做回归和分类的主要区别在于最后做预测时候的决策方式不同。KNN 做分类预测时,一般是选择 多数表决法,即训练集里和预测的样本特征最近的 K 个样本,预测为里面有最多类别数的类别。而 KNN 做回归时,一般是 选择平均法,即最近的K个样本的样本输出的平均值作为回归预测值。

KNN 算法的三要素

  1. K 值的选取: 对于k值的选择,没有一个固定的经验,一般根据样本的分布,选择一个较小的值,可以通过交叉验证选择一个合适的k值。选择较小的k值,就相当于用较小的领域中的训练实例进行预测,训练误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是泛化误差会增大,换句话说,K 值的减小就意味着整体模型变得复杂,容易发生过拟合;选择较大的k值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少泛化误差,但缺点是训练误差会增大。这时候,与输入实例较远的(不相似的)训练实例也会对预测器作用,使预测发生错误,且 K 值的增大就意味着整体的模型变得简单。一个极端是k等于样本数m,则完全没有分类,此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单。
  2. 距离的度量方式: 曼哈顿距离($p=1$), 欧氏距离($p=2$), 闵可夫斯基距离($p=3$)
  3. 分类决策规则: 一般使用 “多数表决法”

KNN 算法是否可微

不可微, 因为求 K 近邻(argmax)的操作是不可微的

编程实现 KNN 算法

暴力实现:(实际算法中会采用更高效的实现, 如 KD 树实现, Ball 树实现等)
计算预测样本和所有训练集中的样本的距离,然后选出最小的k个距离即可,接着多数表决,很容易做出预测. 复杂度过大, 通常样本量成千上万.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
from collections import Counter

class KNN(object):

def __init__(self):
self.train_data = None
self.train_label = None
self.dists = []

def train(self, train_data, train_label):
# train_data: [N x d] 的数组, N 为样本数量, d 为样本维度
# train_label: [N x 1] 的数组, N 为样本数量, 1 代表类别标签维度(1维)
self.train_data = np.array(train_data)
self.train_label = np.array(train_label)

def predict(self, test_data, k=3, distance='l2'):
test_data = np.array(test_data)
preds = []
for item in test_data:
if distance == 'l1':
self.dists = self.L1Distance(item)
elif distance == 'l2':
self.dists = self.L2Distance(item)
dists_argsort = np.argsort(self.dists)
# knearest_class = self.train_label(dists_argsort)[1:k+1]
knearest_class = self.train_label[dists_argsort][:k]
pred = Counter(knearest_class).most_common(1)[0][0] # 用 Counter 统计类别元素出现的个数, most_common 以键值对(元素,出现次数)的形式返回 top N, 这里指定 top 1
#class_count = np.bincount(knearest_class) # 当标签不是 int 型时, 无法用 bincount
#pred = np.argmax(class_count)
preds.append(pred)
return np.array(preds)

def L1Distance(self, x):
return np.sum(np.abs(self.train_data - x), axis=1)# 千万不要忘了求和时 axis=1

def L2Distance(self, x):
return np.sqrt(np.sum(np.square(self.train_data-x), axis=1))# 千万不要忘了求和时 axis=1

train_data = [[1, 1, 1], [2, 2, 2], [10, 10, 10], [13, 13, 13]]
train_label = ['aa', 'aa', 'bb', 'bb']
test_data = [[3, 2, 4], [9, 13, 11], [1,2,3]]

knn = KNN()
knn.train(train_data, train_label)
preds = knn.predict(test_data, k=3, distance='l2')
print(preds)

支持向量机

SVM深入解析

简述 SVM 的基本概念和原理

最简单的 SVM 从线性分类器导出, 根据最大化样本点分类间隔的目标, 我们可以得到线性可分问题的 SVM 目标函数. 然后可以利用拉格朗日乘子法得到其对偶问题, 并根据 KKT 条件和 SMO 算法就可以高效的求出超平面的解. 但是实际任务中, 原始样本空间内也许并不存在一个能正确划分两类样本的超平面. 因此, 我们需要利用核函数将样本从原始空间映射到一个更高维的特征空间, 使得样本在这个特征空间内线性可分. 核函数的选择对于支持向量机的性能至关重要. 但是现实任务中往往很难确定合适的核函数使得训练样本在特征空间内线性可分, 因此, 我们引入了 “软间隔” 的概念, 也就是松弛变量和惩罚因子, 其基本思想就是, 允许支持向量机在一些样本上出错, 并对违反约束条件的训练样本进行惩罚. 所以, 最终的优化目标就是在最大化间隔的同时, 使得不满足约束的样本尽可能地少.

SVM 推导过程

给定训练样本集(二分类问题):

注意,这里用的是分号, 表示这是一个列向量. SVM做的事情就是试图把一根 “木棍” 放在最佳位置, 好让 “木棍” 的两边都有尽可能大的 “间隔”.

这个 “木棍” 就叫做 “划分超平面”, 可以用下面的线性方程来描述:

其中 $\vec w =(w^(1); w^(2);…; w^(d))$ 为 $d$ 维法向量(注意,这里用的是分号, 表示这是一个列向量), 决定了超平面的方向, $\vec x$ 为 “木棍” 上的点的坐标($d$ 维列向量), $b$ 为位移项, 决定了超平面与原点之间的距离.

根据点到 “直线” 的距离公式,我们可以得到样本空间中任意点 $\vec x$ 到超平面 $(\vec w,b)$ 的距离为:

$||\vec w || = \sqrt{w_1^2 + w_2^2 + … + w_d^2}$ 为向量长度(也即向量的L2范式)

首先假设 当前的超平面可以将所有的训练样本正确分类, 那么就有如下式子:

上式可以统一写成如下的约束不等式:

上面的式子其实是冗余的, 因为假设样本点不在超平面上, 所以不可能出现等于0的情况, 又因为超平面方程两边都乘一个不等于0的数,还是 同一个超平面, 因此为了简化问题的表述, 我们对 $\vec w$ 和 $b$ 加上如下约束(这里的1没有什么特别的含义, 可以是任意的常数, 因为这里的点 $\vec x_i$ 不是超平面上的点, 所以所得值不为0):

即离超平面最近的正, 负样本距离超平面的距离为: $\frac{1}{||\vec w||}$ , 我们将这些距离超平面最近的几个训练样本点为定义 “支持向量”, 那么, 两个异类支持向量到超平面的距离之和就为 $\gamma = \frac{2}{||\vec w||}$ , 我们将这称为”间隔”.

同时, 根据此约束, 我们可以消除超分类平面约束的冗余, 得到新的超分类平面约束如下:

SVM的目的就是找到具有 “最大间隔” 的划分超平面, 也就是要找到满足约束 $y_i(\vec w^T\vec x_i + b) \geq 1$ 中的参数 $\vec w, b$ , 使得其具有最大的间隔 $\gamma$ , 也就是:

显然, 为了最大化间隔 $\gamma$ , 我们仅需要最大化 $|\vec w|^{-1}$ , 这就等于最小化 $|\vec w|^2$, 于是上式等价为:

下图即为SVM示意图, 注意,图中的1可以被任意常数替换(只要前面乘上对应的系数即可, =0说明在超分类平面上, !=0说明在两侧)

以上就是线性可分时的SVM基本型(现实中大多数问题是线性不可分的, 所以线性可分的SVM没有太多实用价值)

SVM 如何解决线性不可分问题

解决线性不可分的基本思路有两个:

  • 加入松弛变量和惩罚因子, 找到 相对较好 的超平面, 这里的 相对较好 可以理解为 尽可能 的将数据正确分类
  • 使用核函数, 将低维的数据映射到更高维的空间, 使得高维空间中的数据是线性可分的, 那么在高维空间吗使用线性分类模型即可.

核函数

在之前的讨论中,我们假设 训练样本 是线性可分的, 然而在现实任务中, 原始样本空间内也许并不存在一个能正确划分两类样本的超平面, 对于这样的问题, 可将一样本从原始空间映射到一个更高维的特征空间, 使得样本在这个特征空间内线性可分 .

需要知道, 如果原始空间是有限维, 即属性数有限, 那么一定存在一个高维特征空间使样本可分

令 $\phi(\vec x)$ 表示将 $\vec x$ 映射后的特征向量, 于是, 在特征空间中划分超平面所对应的模型可表示为:

类似式(1), 有:

其对偶问题为:

求解上式涉及到计算 $\phi(\vec x_i)^T \phi(\vec x_j$ , 这是样本 $\vec x_i$ 与 $\vec x_j$ 映射到特征空间之后的内积, 由于特征空间维数可能很高, 甚至是无穷维, 因此直接计算 $\phi(\vec x_i)^T \phi(\vec x_j$ 是很困难的, 为了避开这个障碍, 可以设想这样一个函数:

即 $x_i$ 与 $x_j$ 在特征空间的内积等于它们在原始样本空间中通过函数 $K(\cdot, \cdot)$ 计算的结果. (有可能是先内积再函数映射, 也有可能是求范式再函数映射). 于是(9)式可重写为:

注意, 前面几个小节的推导过程也用了符号 $K$ , 但是就像前面所说的, 前几个小节的 $K$ 是为了方便书写而使用的, 你可以把它看作是一个恒等映射的核函数

当我们解出上式得到 $\vec \alpha$ 后, 就可以得到划分超平面对应的模型(式中 $\vec x$ 为样本点, $f(\vec x)$ 为该样本点的预测结果):

核函数定理: 令 $\chi$ 为输入空间 $K(\cdot, \cdot)$ 是定义在 $\chi \times \chi$ 上的对称函数, 则 $K(\cdot, \cdot)$ 是核函数 当且仅当 对于任意数据 $D = \\{\vec x^{(1)}, \vec x^{(2)},…,\vec x ^{(m)} \\}$ , 核矩阵 $K$ 总是半正定的

从以上分析可知, 核函数的选择决定了特征空间的好坏, 因此, 一个合适的核函数,就成为了支持向量机的最大变数.

下面是几种常用的核函数:

名称 表达式 参数
线性核
高斯核
拉普拉斯核
Sigoid核

此外,还可以通过函数组合得到:

  • 若 $K_1$ 和 $K_2$ 都是核函数 ,则对任意的正数 $\gamma_1, \gamma_2$ , 其线性组合 $\gamma_1 K_1 + \gamma_2 K_2$ 也是核函数
  • 若 $K_1$ 和 $K_2$ 为核函数, 则函数的直积 $K_1 \otimes K_2 (\vec x , \vec z) = K_1(\vec x, \vec z) K_2(\vec x, \vec z)$
  • 若 $K_1$ 是核函数, 则对任意函数 $g(\vec x)$, $K(\vec x, \vec z) = g(\vec x) K_1(\vec x, \vec z) g(\vec z)$ 也是核函数

软间隔与正则化

在实现任务中, 往往很难确定合适的核函数, 使得训练样本在特征空间中线性可分, 即便是找到了, 也无法断定是否是由于过拟合造成的 , 因此, 我们需要 允许支持向量机在一些样本上出错 , 以缓解上面的问题.

硬间隔(hard margin)与软间隔(soft margin)的区分:

  • 硬间隔: 所有样本都必须分类正确
  • 软间隔: 允许某些样本不满足约束(11)式(即,预测结果和真实结果符号相反,分类错误,或预测结果绝对值小于1,相当于越过了支持向量划定的边界)

我们要在最大化间隔的同时, 使得不满足约束的样本应尽可能的少, 于是, 优化目标可写为:

其中, $C>0$ 为惩罚因子, 是一个常数(注意与前几节推导SVM时的常数区分), $l_{0/1}$ 是 “0/1 损失函数”:

当C无穷大时, (10)式就会迫使所有样本均满足约束, 也就是令所有训练样本都分类正确(容易产生过拟合), 当C取有限值时, 则允许有一些样本不满足约束(11)式.

在工业上, 通常 C 的默认值取 1, 然后根据模型的具体表现, 分别尝试 0.1, 0.5, 10 等.

但是, $l_{0/1}$ 非凸, 不连续, 数学性质不好, 因此, 通常使用其他函数来替代, 称为” 替代损失”, 下面为三种常用的替代损失:

  • hinge损失: $l_{hinge}(z) = max(0,1-z)$
  • 指数损失(exponential loss): $l_{exp}(z) = exp(-z)$
  • 对率损失(logistic loss): $l_{log}(z) = log(1+ exp(-z))$

假设采用hinge损失损失, 然后可以引入 “松弛变量”(slack variables) $\xi_i \geq 0$ ,每一个样本都有一个对应的松弛变量, 用以表征该样本不满足约束(11)的程度 则可将(10)式重写为:

可以看出, 上式是与之前推导相似的二次规划问题, 只不过是约束条件变的宽松了(为了允许一些样本犯错), 因此,同样利用拉格朗日乘子法求解, 首先得到上式的拉格朗日函数:

其中, $\alpha_i \geq 0, \mu_i \geq 0$ 是拉格朗日乘子, 令 $L(\vec w, b, \vec \alpha, \vec \xi, \vec \mu)$ 对 $\vec w, b, \vec \alpha, \vec \xi$ 求偏导, 并令其为0 , 可得:

得到(12)式对应的对偶问题如下:

可以看到, 此时, $\alpha_i$ 的约束条件变成了 $0 \leq \alpha_i \leq C$ , 上式的KKT条件要求为:

于是, 从KKT条件中我们可以看出, 对任意的训练样本 $(\vec x_i, y_i)$, 总有 $\alpha_i = 0$ 或 $y_i f(\vec x_i) = 1 - \xi_i$.

  • 若 $\alpha_i = 0$, 则该样本不会对 $f(\vec x)$ 产生影响.
  • 若 $\alpha_i > 0$, 则必有 $y_i f(\vec x_i) = 1 - \xi_i$, 即该样本是支持向量
  • 因为 $C = \alpha_i + \mu_i$ , 所以, 若 $\alpha_i < C$ , 则有 $\mu_i > 0$ , 进而有 $\xi_i = 0$, 即该样本在最大间隔边界上(是否也就是支持向量?)
  • 若 $\alpha_i = C$ , 则有 $\mu_i = 0$, 此时若 $\xi_i \leq 1$, 则该样本落在最大间隔内部, 若 $\xi_i > 1$, 则该样本被错误分类.

以上讨论, 我们可以看出, 最终的模型依然只与支持向量有关, 保持了稀疏性(hinge损失有一块平坦的零区域,这使得SVM的解具有稀疏性)

以上是对使用hinge损失时讨论的情况, 还可以将其替换成别的损失函数以得到其他学习模型, 这些模型的性质与所用的替代函数直接相关, 但它们具有一个共性: 优化目标中的第一项用来描述划分超平面的”间隔”大小, 另一项用来表示训练集上的误差, 可写为更一般的形式:

其中, $\Omega(f)$ 称为 “结构风险”(structural risk), 用于描述模型 $f$ 自身的性质; 第二项 $C\sum_{i=1}^{m} l(f(\vec x_i)$ 称为 “经验风险”(empirical risk), 用于描述模型与训练数据的契合程度. $C$ 用于对二者进行折衷.

从预测误差的角度来看, 第二项相当于模型误差, 第一项相当于正则化项, 表述了模型本身的性质, 一方面, 这为引入领域知识和用户意图提供了途径, 另一方面, 该信息有助于消减假设空间, 降低过拟合风险

为什么SVM的分类结果仅依赖于支持向量?

对偶问题求解 $\vec w$ 和 $b$

对偶问题(dual problem):在求出一个问题解的同时, 也给出了另一个问题的解

我们希望通过求解式(1)来得到具有最大间隔的划分超平面的模型参数,由于该式是一个凸二次规划问题(目标函数是变量的二次函数, 约束条件是变量的线性不等式). 因此,对该式使用拉格朗日乘子法得到其 “对偶问题”.

对于式(1)的 每个样本点 约束添加拉格朗日乘子 $\alpha_i \geq 0$, 则该问题的拉格朗日函数为:

其中, $\vec \alpha = (\alpha_1, \alpha_2,…,\alpha_m)$ ,每一个 $\alpha_i$ 均为标量 .接着令 $L(\vec w,b,\vec \alpha)$ 对 $\vec w$ 和 $b$ 求偏导, 并令其为0, 可得:

于是, 我们可以用 $\sum_{i=1}^{m} \alpha_i y_i \vec x_i$ 来代替 $\vec w$,

将(3)和(4)代入(2)式中, 消去 $\vec w$ 和 $b$ ( 注意, 这里 $\sum_{i=1}^{m}\alpha_i y_i = 0$, 但是不代表 $\alpha_i y_i = 0$ ), 可得:

这里 $\vec x_i,\vec x_j$ 位置可互换, 为了好看,我将 $\vec x_i$ 写在了前面. 到此, 我们就得到了式(2)的对偶问题:

为了满足原始问题(1) 和对偶问题(5)之间的充分必要条件, 上述推导过程还需要满足KKT(Karush-Kuhn-Tucker)条件(其中前两条已经在上述推导过程中满足) , 即要求:

当我们解出上式得到 $\vec \alpha$ 后, 就可以通过求得 $\vec w$ 和 $b$ 的值, 进而可得到划分超平面对应的模型:

根据 KKT 条件我们可以轻易得出, 对任意的训练样本 $(\vec x_i , y_i)$ , 总有 $\alpha_i = 0$ 或 $y_i f(\vec x_i) = 1$ . 若 $\alpha_i = 0$ , 则该项对应的样本不会出现在求和项中 ; 若 $\alpha_i > 0$ , 则必有 $y_i f(\vec x_i) = 1$ , 这说明该样本点出现在最大间隔边界上, 是一个支持向量. 这显示出支持向量机的一个重要性质: 训练完成后, 大部分的训练样本都不需要保留(这些样本对应的系数 $\alpha_i = 0$ ), 最终模型仅与支持向量有关.

如何选取核函数

最常用的是线性核与高斯核, 也就是 Linear 核与 RBF 核. 一般情况下 RBF 效果不会差于 Linear, 但是时间上 RBF 会耗费更多.

  • Linear 核: 主要用于线性可分的情形. 参数少, 速度快, 对于一般数据, 分类效果已经很理想了.
  • RBF 核: 主要用于线性不可分的情况. 参数多, 分类结果非常依赖于参数. 有很多人是通过训练数据的交叉验证来寻找合适的参数, 不过这个过程比较耗时. 个人体会是: 使用 libsvm, 默认参数, RBF 核比 Linear 核效果稍差. 通过进行大量参数的尝试, 一般能找到比 linear 核更好的效果. 至于到底该采用哪种核, 要根据具体问题和数据分析, 需要多尝试不同核以及不同参数. 如果特征提取的好, 包含的信息量足够大, 很多问题都是线性可分的. 当然, 如果有足够的时间去寻找合适的 RBF 核参数, 应该能取得更好的效果.

吴恩达的观点:

  1. 如果 Feature 的数量很大, 跟样本数量差不多, 这时候可以使用 LR 或者是 Linear Kernel 的 SVM. (因为核函数需要计算内积, 两两样本都得算, 所以样本过多的话时间消耗太大, 很明显高斯核比线性核复杂的多)
  2. 如果 Feature 的数量比较小, 样本数量一般, 不算大也不算小, 就选用 SVM + Gaussian Kernel
  3. 如果 Feature 的数量比较小, 而样本数量比较多, 就需要手工添加一些 feature, 使之变成第一种情况.

为什么说高斯核函数将原始特征空间映射成了无限维空间?

https://blog.csdn.net/lin_limin/article/details/81135754

核函数中不同参数的影响

https://blog.csdn.net/lin_limin/article/details/81135754

https://mp.weixin.qq.com/s?__biz=MzU4MjQ3MDkwNA==&mid=2247484495&idx=1&sn=4f3a6ce21cdd1a048e402ed05c9ead91&chksm=fdb699d8cac110ce53f4fc5e417e107f839059cb76d3cbf640c6f56620f90f8fb4e7f6ee02f9&scene=21#wechat_redirect

既然深度学习技术性能表现以及全面超越 SVM, SVM 还有存在的必要吗?

待补充

决策树

朴素贝叶斯

降维

聚类

简述 K-Means 聚类的原理

K-Means算法是无监督的聚类算法,它对于给定的样本集, 会按照样本之间的距离大小, 将样本集划分为 K 个簇, 让簇内的样本尽量紧密的连在一起, 而让簇间的距离尽量的大.
如果用数据表达式表示, 则假设簇划分为 $(C_1, C_2, …, C_k)$, 则我们的目标是最小化平方误差 $E$:

其中 $\mu_i$ 是簇 $C_i$ 的均值向量, 有时也称为质心, 表达式为:

K-Means 的收敛性

联系 EM 算法, 可知一定是收敛点
推导kmeans算法收敛的条件

EM 算法, 即期望最大算法, 每次迭代由两步组成: E 步, 求期望; M 步, 求极大.

在 EM 框架下, 求得的参数 $\theta$ 一定是瘦脸的, 能够找到似然函数的最大值. 而 K-Means 算法, 同样也可以看做是在 EM 框架下执行的:

  1. 假设使用平方误差作为目标函数:
  2. E-Step: 固定参数 $\mu_k$, 将每个数据点分配到距离它本身最近的一个簇类中;
  3. M-Step: 固定数据点的分配, 更新参数(中心点) $\mu_k$

K-Means 算法的优点和缺点

优点: 计算时间短, 速度快, 效果好

缺点: 一方面需要人工设置 k 值, 另一方面 K-Means 算法对于初始点非常敏感, 如果初始点全部处于一点, 则最终可能无法迭代到合适的解, 原因在于所有的样本都会属于某一个点类, 此时其他的点会无法更新.

K-Means 实现流程

想直接求上式的最小值并不容易,这是一个NP难的问题,因此只能采用启发式的迭代方法, 如下图所示。

SummaryOfComputerVision%2Fkmeans.jpg

  1. 上图a表达了初始的数据集,假设k=2。
  2. 在图b中,我们 随机 选择了两个k类所对应的类别质心,即图中的红色质心和蓝色质心
  3. 然后分别求样本中所有点到这两个质心的距离,并标记每个样本的类别为和该样本距离最小的质心的类别,如图c所示,经过计算样本和红色质心和蓝色质心的距离,我们得到了所有样本点的第一轮迭代后的类别。
  4. 此时我们对我们当前标记为红色和蓝色的点分别求其新的质心,如图d所示,新的红色质心和蓝色质心的位置已经发生了变动。
  5. 图e和图f重复了我们在图c和图d的过程,即将所有点的类别标记为距离最近的质心的类别并求新的质心。最终我们得到的两个类别如图f。

算法实现流程: 输入是样本集 $D = \{x_1, x_2, …, x_m\}$, 聚类的簇为 $k$, 最大的迭代次数为 $N$.

  1. 从数据集 $D$ 中随机选择 $k$ 个样本作为初始的 $k$ 个质心向量: $\{\mu_1, \mu_2, …, \mu_k \}$
  2. 对于 $n = 1, 2, …, N$
    1. 将簇的划分 $C$ 初始化为 $C_t = \emptyset, t = 1, 2, …, k$;
    2. 对于 $i=1,2,…,m$, 计算样本 $x_i$ 和各个质心向量 $\mu_j(j=1, 2, …, k)$ 的距离, $d_{ij} = \Vert x_i - \mu_j \Vert_2^2$, 将 $x_i$ 标记为距离最小的簇所对应的类别 $\lambda_i$. 更新 $C_{\lambda_i} = C_{\lambda_i} \cup \{x_i\}$
    3. 对于 $j = 1, 2, …, k$, 对 $C_j$ 中的所有样本点重新计算新的质心 $\mu_j = \frac{1}{\vert C_j \vert} \sum_{x\in C_j} x$
    4. 如果所有的 $k$ 个质心向量都不再发生变化, 则可提前跳出循环, 无序执行 N 次迭代
  3. 输出簇划分 $C = \{C_1, C_2, …, C_k \}$

K-Means 常规实现代码

输入为 [N, d] 维度的样本集合, 其中, N代表样本的数量, d代表每个样本的维度, 算法的实现思路为:

  1. __init__函数初始化相关变量
  2. fit函数首先确定最初的n_clustercenter;
  3. fit函数循环执行以下两个过程:
    1. 更新各个点到centers的距离
    2. 更新各个centers的位置

PyTorch 实现: https://www.jianshu.com/p/1c000d9296ae

Numpy 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import numpy as np

class KMeans:

def __init__(self, k, max_iter=1000, stop_var=1e-03, dist_type='l1'):
self.num_cluster = k
self.max_iter = max_iter
self.stop_var = stop_var
self.dist_type = dist_type
self.variance = 10 * stop_var
self.dists = None
self.labels = None
self.centers = None

def fit(self, samples):
# 随机初始化 centers 点, 更好的方法可以使用 sklearn 的 kmeans++ 初始化方法
init_row = np.random.randint(0, samples.shape[0], self.num_cluster)
self.centers = samples[init_row]
for cur_iter in range(self.max_iter):
self.update_dists(samples) # 更新样本到各个 centers 的距离, 同时更新每个样本点对应的center类
self.update_centers(samples) # 更新各个 centers
if self.variance < self.stop_var: # 如果 centers 更新停止, 则提前退出
print("cur_iter:", cur_iter)
break

def l1_distance(self, sample):
return np.sum(np.abs(sample - self.centers), axis=1)

def l2_distance(self, sample):
return np.sqrt(np.sum(np.square(sample - self.centers), axis=1))

def update_dists(self, samples):
labels = np.empty(samples.shape[0]) # shape: [N, 1]
dists = np.empty((0, self.num_cluster)) # shape: [N, n_cluster]
for i, sample in enumerate(samples):
if self.dist_type == 'l1':
dist = self.l1_distance(sample)
elif self.dist_type == 'l2':
dist = self.l2_distance(sample)
else:
raise ValueError('..')
labels[i] = np.argmin(dist) # 距离最小的center就是该样本对应的类
dists = np.vstack((dists, dist[np.newaxis, :])) # 将该样本对应的各个center距离加入到dists中
if self.dists is not None:
self.variance = np.sum(np.abs(self.dists - dists))
self.dists = dists # 更新
self.labels = labels # 更新

def update_centers(self, samples):
centers = np.empty((0, samples.shape[1]))
for i in range(self.num_cluster):
mask = (self.labels == i)
center_samples = samples[mask]
if len(center_samples) != 0:
center = np.mean(center_samples, axis=0)
else:# 说明 centers 点出现了重复情况, 导致某个点类没有样本与它对应, 这种情况要报警告, 因为最终解通常无法形成合理的簇划分
center = self.centers[i]
centers = np.vstack((centers, center[np.newaxis, :]))
self.centers = centers

if __name__ == '__main__':
samples = np.random.rand(1000, 4) # 样本量 N=1000, 每个样本特征维度为 d=4
print(samples.shape)
num_cluster = 5 # 簇个数为 5
kmeans = KMeans(num_cluster)
kmeans.fit(samples)
print(kmeans.centers)

K-Means 实现 anchor 划分

实现对 anchor 的 K-Means 聚类算法, 核心思想和常规的聚类实现相同, 不同之处在于 “距离” 的定义, 和更新centers的方式, 具体来说就是:

  • 距离的定义: 用1-iou定义每个框与centers之间的距离, iou越大, 距离越近
  • 聚类点更新: centers更新时, 采用np.median选择当前类中的中位数box作为新的 center. 经过实验验证, 使用 mean 的效果不如使用 median. 这里由于我们使用的是 median, 也就是众多 boxes 中的某一个作为最终的聚类点, 因此我们不设置最大的迭代次数, 而是一直循环到聚类点不再更新为止.

注意, 由于确定 anchor 时, 我们仅仅只需要样本框的宽和高这两个信息即可, 不需要知道样本的具体location, 所以, 在传入boxes时, 我们传入的是 [N, 2] 维度的数据, 其中 N 代表 box 的数量, 2 代表每个 box 的 (w, h)

K-Means 算法 numpy 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import numpy as np

def cal_iou(box, clusters):
"""
Calculates the Intersection over Union (IoU) between a box and k clusters.
:param box: tuple or array, shifted to the origin (i. e. width and height)
:param clusters: numpy array of shape (k, 2) where k is the number of clusters
:return: numpy array of shape (k, 0) where k is the number of clusters
"""
min_w = np.minimum(box[0], clusters[:, 0])
min_h = np.minimum(box[1], clusters[:, 1])
intersection = min_w * min_h
if (np.count_nonzero(intersection == 0) > 0):
raise ValueError("Boxes has no areaes")
box_area = box[0] * box[1]
clusters_area = clusters[:, 0] * clusters[:, 1]
iou = (intersection) / (box_area + clusters_area - intersection)
return iou

def cal_avg_iou(boxes, clusters):
"""
Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters.
:param boxes: numpy array of shape (r, 2), where r is the number of rows
:param clusters: numpy array of shape (k, 2) where k is the number of clusters
:return: average IoU as a single float
"""
return np.mean([np.max(cal_iou(box, clusters)) for box in boxes]) # 注意这里是 np.max, 就是只找到与 centers 的最大 iou 即可
#return np.mean([np.max(cal_iou(boxes[i], clusters)) for i in range(boxes.shape[0])]) # 与上面等价


def kmeans_anchor(boxes, n_clusters):
"""
Calculates k-means clustering with the Intersection over Union (IoU) metric.
:param boxes: numpy array of shape (r, 2), where r is the number of rows
:param n_clusters: number of clusters
:return: numpy array of shape (k, 2)
"""
n_boxes = boxes.shape[0]
distances = np.empty([n_boxes, n_clusters]) # 占位, 记录每个box与center的iou距离
last_boxes_labels = np.empty([n_boxes]) # last_boxes_label
centers = boxes[np.random.choice(n_boxes, n_clusters, replace=False)] # replace 代表的意思是抽样后是否放回, 这里要求 clusters 均不相同, 因此使用 False, 不放回

while True:
for i in range(n_boxes):
distances[i] = 1 - cal_iou(boxes[i], centers) # 距离定义为 1 - iou, iou越大, 距离越近

boxes_labels = np.argmin(distances, axis = 1) # 选择距离最近的 cluster 下标作为每个 box 的类别, box_label

if (last_boxes_labels == boxes_labels).all(): # 如果 box 类别不再更新, 则退出
return centers

for i in range(n_clusters): # 更新 centers, 使用所有属于该类别的 box 的中位数作为新center
centers[i] = np.median(boxes[(boxes_labels==i)], axis=0)

last_boxes_labels = boxes_labels#last_clusters = nearest_clusters

下面我们以 VOC 数据格式为例, 来使用上面的 K-Means 算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import glob
import xml.etree.ElementTree as ET

import numpy as np

from kmeans_anchor import kmeans_anchor, cal_avg_iou

# VOC 格式的数据标注
ANNOTATIONS_PATH = "/media/zerozone/WinD/DataSets/VOC/VOCdevkit/VOC2007/Annotations/"
# ANNOTATIONS_PATH = "/media/zerozone/WinD/Ubuntu/BackUp/Works/Competition/DC/VOC_trans/Annotations"
CLUSTERS = 5

def load_dataset(path):
dataset = []
for xml_file in glob.glob("{}/*xml".format(path)):
tree = ET.parse(xml_file)

height = int(tree.findtext("./size/height"))
width = int(tree.findtext("./size/width"))

for obj in tree.iter("object"):
xmin = int(obj.findtext("bndbox/xmin")) / width
ymin = int(obj.findtext("bndbox/ymin")) / height
xmax = int(obj.findtext("bndbox/xmax")) / width
ymax = int(obj.findtext("bndbox/ymax")) / height

dataset.append([xmax - xmin, ymax - ymin])

return np.array(dataset)


print("loading dataset:", ANNOTATIONS_PATH)
data = load_dataset(ANNOTATIONS_PATH)
print("loaded successfully!")
print("calculating kmeans: cluster_num={}".format(CLUSTERS))
out = kmeans_anchor(data, CLUSTERS) # 计算 kmeans
print("Accuracy: {:.2f}%".format(cal_avg_iou(data, out) * 100)) # 计算 avg_iou
print("Boxes:\n {}".format(out))

ratios = np.around(out[:, 0] / out[:, 1], decimals=2).tolist()
print("Ratios:\n {}".format(sorted(ratios)))

XGBoost

Bagging

待整理

各种机器学习算法的应用场景分别是什么(比如朴素贝叶斯、决策树、K 近邻、SVM、逻辑回归最大熵模型)
https://www.zhihu.com/question/26726794/answer/151282052

深度学习篇

优化方法

各种优化方法整理总结

梯度下降: SGD, Momentum, Nesterov, Adagrad, Adadelta, RMSprop, Adam, Adamax
牛顿法:
拟牛顿法:
共轭梯度法:

各种优化方法的概念及其优缺点

名称 公式 优化方法简述 优点 缺点
SGD $g_t = \nabla_{\theta_{t-1}} f(\theta_{t-1})$
$\Delta \theta_t = - \eta \times g_t$
每一次都计算mini-batch的梯度, 然后对参数进行更新. 公式中的 $\eta$ 是学习率, $g_t$ 是当前 batch 的梯度 在合理的学习率和相应的衰减策略下, 通常能够优化到一个不错的点, 配合下面的 Momentum, 通常可以获得比自适应方法更优的点 (1) 因为要兼顾整个神经网络中所有参数的训练效果, 因此对学习率的选择比较敏感. (2) SGD 容易收敛到局部最优, 并且在某些情况下容易被困在鞍点( 这句话是不对的, 只有在特定的 inital point 时才会被困在鞍点, 通常情况下, 我们使用 random inital point, 被困在鞍点的概率非常小, 当使用合适的初始化和步长时, 几乎不会出现鞍点问题 ); (3) 参数的更新仅仅依赖于当前 batch 中的数据, 当数据分布波动较大时, 更新往往不够稳定
SGD+Momentum $g_t = \nabla_{\theta_{t-1}} f(\theta_{t-1})$
$m_t = \mu \times m_{t-1} + g_t$
$\Delta \theta_t = -\eta \times m_t$
公式中的 $\mu$ 为动量因子, 通常取值 0.9 或 0.99, 借助于物理学里面动量的概念, 通过动量的积累来在相关方向上加速 SGD 优化速度, 抑制震荡, 同时有助于跳出局部最优, 进而加快收敛. 注意, 这里动量的添加并不是滑动平均, 具体看查看原文 (1) 下降初期, 动量因子可以加速网络的训练速度; (2) 当遇到鞍点时, 梯度虽然为零, 但是动量不为零, 可以跳出鞍点(局部最优) ; (3) 在梯度改变方向时, 能够降低更新幅度, 减小震荡, 加速网络收敛; 总之, momentum 项能够在相关方向加速 SGD, 抑制震荡, 从而加快收敛 需要人工设置学习率
SGD+Nesterov $g_t=\nabla_{\theta_{t-1}} f(\theta_{t-1} - \eta \times \mu \times m_{t-1})$
$m_t = \mu \times m_{t-1} + g_t$
$\Delta \theta_t = -\eta \times m_t$
可以看出, Nesterov 与 Momentum 公式的区别在于, 前者不是在当前的位置上求梯度, 而是根据本来计划要走的那一步提前前进一步以后, 再在新的位置上求梯度, 然后对这个新求得的梯度进行 Momentum 梯度下降计算 (1) 先站在下一步的位置看看, 再进行更新, 使得梯度更新方向更具前瞻性; (2) 实际使用中, NAG 会比 Momentum 收敛的速度更快 (1) 需要人工设置学习率
AdaGrad $n_t = n_{t-1} + g^2_t$
$\Delta \theta_t = -\frac{\eta}{\sqrt{n_t + \epsilon}} \times g_t$
Adagrad相当于在学习率前面乘了一个约束项 $\frac{1}{\sqrt {n_t + \epsilon}}$, 该约束项会随着算法的不断迭代而越来越大, 那么对应学习率就会越来越小, 也就是说 Adagrad 算法在开始时是大步前进的, 而在后面则会减小步伐, 缓慢收敛 (1) 在整个更新期间学习率不是固定的, 会随着训练过程变化; (2) 适合面对稀疏梯度; (3) 对于每一个不同的参数, 其具有不同的学习率, 当参数梯度较大时, 其约束项会使得步长变小, 返回, 会令步长变大, 动态调节 (1) 仍然依赖于一个人工设置的全局学习率; (2) 中后期, 分母上的梯度累加和会越来越大, 使得更新提早停滞, 训练提前结束
AdaDelta $Eg^2_t = \rho\times Eg^2_{t-1} + (1-\rho)\times g^2_t$
$\Delta \theta_t = -\frac{\eta}{\sqrt{Eg^2_t + \epsilon}} g_t$
$= -\frac{\eta}{RMS[g]_t} g_t$
$= -\frac{RMS[\Delta\theta]_{t-1}}{RMS[g]_t} g_t$
$RMS[\Delta \theta]_t = \sqrt{E[\Delta \theta^2]_t + \epsilon}$
$E[\Delta \theta^2]_t = \gamma E[\Delta \theta^2]_{t-1} + (1-\gamma)\Delta \theta^2_t$
$\rho$ 类似于冲量项, 其值在 0.9 附近. Adadelta是对Adagrad的扩展, 和 Adagrad 相比, 其改进是将分母约束项换成了 过去的梯度平方的衰减平均值, 相当于梯度的均方根(root mean squared, RMS), 此外, 如果将学习率也换成 $RMS[\Delta \theta]$ 的话, 甚至可以不用设置学习率了 (1) 对 Adagrad 的扩展, 约束项只计算梯度平方一段时间内的平均值, 而不是累计值, 不容易产生太大值而使得更新提早结束; (2) 无需人工设置学习率, 可以动态改变学习率的大小; (1) 训练后期会反复在局部最小值附近抖动, 无法收敛到最优点, 这时候用 SGD+Momentum, 通常会有 2%~5% 的验证集正确率提升.
RMSprop $Eg^2_t = \rho\times Eg^2_{t-1} + (1-\rho)\times g^2_t$
$\Delta \theta_t = -\frac{\eta}{\sqrt{Eg^2_t + \epsilon}} g_t$
RMSprop可以算作是Adadelta的一个特例, 可以看出 RMSprop 仍然需要设置全局学习率 (1) Adadelta 的特例, 也是对学习率添加约束, 适合处理非平稳目标, 对 RNN 效果较好 (1) …
Adam $m_t = \beta_1 \times m_{t-1} + (1-\beta_1) \times g_t$
$n_t = \beta_2 \times n_{t-1} + (1 - \beta_2) \times g^2_t$
$\hat m_t = \frac{m_t}{1-\beta_1^t}$
$\hat n_t = \frac{n_t}{1- \beta_2^t}$
$\Delta \theta_t = -\frac{\hat m_t}{\sqrt{\hat n_t} + \epsilon} \times \eta$
Adam本质上是带有 动量项(分子部分) 的 RMSprop, 它利用 修正后的 梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率. 公式中, $m_t, n_t$ 分别是对梯度的一阶矩估计和二阶矩估计, 可以看做是对期望 $E g_t$, $E g^2_t$ 的估计, $\hat m_t$, $\hat n_t$ 是对 $m_t$, $n_t$ 的校正, 这样可以近似为对期望的无偏估计 (1) 经过偏置校正后, 每一次迭代学习率都有一个确定的范围, 使得参数更新比较平稳; (2) 结合了动量 RMSprop 的优点; 既可以加速收敛, 又可以根据梯度的大小动态调节每个参数的学习步长 (3) 对内存需求较小; (4) 适用于大多非凸优化, 适用于大数据集和高维空间; (5) 超参数可以比较直观的解释, 同时只需要极少量的调参 最终的收敛点通常比经过精心调参后的 SGD+Momentum 的收敛点差一些. 常取参数值: $\beta_1 = 0.9$, $\beta_2 = 0.999$, $\epsilon = 10^{-8}$
Adamax $n_t = max(\nu \times, abs(g_t))$
$\Delta x = -\frac{\hat m_t}{n_t + \epsilon}\times\eta$
Adamax 是 Adam 的一种变体, 此方法对学习率的上限提供了一个更简单的范围, 可以看出, 学习率的边界范围更加简单
Nadam(PyTorch 目前未实现该接口)

自适应的优化方法虽然在前中期效果较好, 但是最终往往无法收敛到最好的解空间. 基本上 SOTA 的结果, 最后都是从 SGD 调出来的, 这也是 SGD 为什么至今在 SOTA 的论文中这么流行的原因. 虽然麻烦, 但是实在.

各损失函数更新动画

SummaryOfComputerVision%2Foptim1.gif

SummaryOfComputerVision%2Foptim2.gif

如何选择合适的优化方法

  • SGD+Momentum 相比于自适应优化器通常训练时间长, 但是经过多次调节后, 在好的学习率和衰减方案的情况下, 结果更优
  • AdaGrad, RMSprop, Adam 等适合希望得到快速结果的情况下使用
  • 在训练较深层的网络时, 也推荐先使用 Adam 方法进行正确性验证, 然后再使用 SGD+Momentum 微调.
  • 在使用 RMSprop 和 Adam 的地方, 大多可以使用 Nadam 取得更好的效果.
  • 在实际训练中比较好的方法是: 先用 Adam 预训练一段时间, 然后使用 SGD+Momentum, 以达到最佳性能. Adam vs SGD 的表现通常如下图所示, 由于鲁棒性和自适应的学习速率, Adam 在一开始表现更好, 而 SGD 最终更容易达到全局最优.

SummaryOfComputerVision%2Fadam_vs_sgd.jpg

一阶矩, 二阶矩的计算方法及其代表的含义

原点矩: 设 $X$ 是随机变量, 则称 $E(X^k)$ 为 $X$ 的 $k$ 阶矩估计; 零阶原点矩恒为 1, 一阶原点矩代表期望, 二阶原点距为平方的期望;
中心距: 设 $X$ 是随机变量, 则称 $E\{ [X - E(X)]^k\}$ 为随机变量 $X$ 的 $k$ 阶中心矩; 零阶中心矩恒为 1, 一阶中心矩恒为 0, 二阶中心距为方差;

期望: 一阶样本原点矩来估计总体的期望
方差: 二阶样本中心距来估计总体的方差

由此可看出, Adam, RMSprop 等算法, 使用的都是一阶原点矩和二阶原点矩. 并且是利用滑动平均法来对一阶矩和二阶矩进行估计.

简述 Adam 中使用的指数加权滑动平均法

加权滑动平均法, 就是对不同时刻的观察值分别赋予不同的权重, 按不同的权重来求最终的滑动平均值. 而指数加权滑动平均法就是指各个观察值的加权系数随着时间呈指数递减, 越靠近当前时刻的观察值权重越大. 公式如下:

上式中, $\theta_t$ 代表当前时刻的观察值, 系数 $\beta$ 代表加权下降的速率, 其值越小下降的越快, $v_t$ 代表当前时刻的指数加权滑动平均值.

PS: 在数学中一般会以 $\frac{1}{e}$ 来作为一个临界值, 小于该值的加权系数对应的值不作考虑. 因此, 当 $\beta = 0.9$ 时, $0.9^{10}$ 约等于 $\frac{1}{e}$, 认为此时是约 10 个数值的加权平均.

偏差修正: 当初始化 $v_0 = 0$ 时, 由于初始化的值太小, 导致初期的滑动平均值偏小, 随着时间的增长, 初期的值影响减小, 滑动平均值才逐渐正常. 为了让初期的滑动平均值也相对正常, 我们利用下面的式子进行修正:

Adam 算法的原理机制

Adam 算法和传统的随机梯度下降不同。随机梯度下降保持单一的学习率(即 alpha)更新所有的权重,学习率在训练过程中并不会改变。而 Adam 通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。

Adam 算法和传统的随机梯度下降不同。随机梯度下降保持单一的学习率(即 alpha)更新所有的权重,学习率在训练过程中并不会改变。而 Adam 通过计算梯度的一阶矩估计和二阶矩估计而为不同的参数设计独立的自适应性学习率。

Adam 算法的提出者描述其为两种随机梯度下降扩展式的优点集合,即:

适应性梯度算法(AdaGrad)为每一个参数保留一个学习率以提升在稀疏梯度(即自然语言和计算机视觉问题)上的性能。
均方根传播(RMSprop)基于权重梯度最近量级的均值为每一个参数适应性地保留学习率。这意味着算法在非稳态和在线问题上有很有优秀的性能。

Adam 算法与相关的 AdaGrad 和 RMSprop 方法有什么区别

Adam 算法同时获得了 AdaGrad 和 RMSprop 算法的优点。Adam 不仅如 RMSprop 算法那样基于一阶矩均值计算适应性参数学习率,它同时还充分利用了梯度的二阶矩均值(即有偏方差/uncentered variance)。具体来说,算法计算了梯度的指数移动均值(exponential moving average),超参数 beta1 和 beta2 控制了这些移动均值的衰减率。

移动均值的初始值和 beta1、beta2 值接近于 1(推荐值),因此矩估计的偏差接近于 0。该偏差通过首先计算带偏差的估计而后计算偏差修正后的估计而得到提升。

事实上,Insofar、RMSprop、Adadelta 和 Adam 算法都是比较类似的优化算法,他们都在类似的情景下都可以执行地非常好。但是 Adam 算法的偏差修正令其在梯度变得稀疏时要比 RMSprop 算法更快速和优秀。Insofar 和 Adam 优化算法基本是最好的全局选择。同样在 CS231n 课程中,Adam 算法也推荐作为默认的优化算法。

虽然 Adam 算法在实践中要比 RMSprop 更加优秀,但同时我们也可以尝试 SGD+Nesterov 动量来作为 Adam 的替代。即我们通常推荐在深度学习模型中使用 Adam 算法或 SGD+Nesterov 动量法。

Adam 算法如何调参, 其常用的参数配置

  • alpha:同样也称为学习率或步长因子,它控制了权重的更新比率(如 0.001)。较大的值(如 0.3)在学习率更新前会有更快的初始学习,而较小的值(如 1.0E-5)会令训练收敛到更好的性能。
  • beta1:一阶矩估计的指数衰减率(如 0.9)。
  • beta2:二阶矩估计的指数衰减率(如 0.999)。该超参数在稀疏梯度(如在 NLP 或计算机视觉任务中)中应该设置为接近 1 的数。
  • epsilon:该参数是非常小的数,其为了防止在实现中除以零(如 10E-8)。

另外,学习率衰减同样可以应用到 Adam 中。原论文使用衰减率 alpha = alpha/sqrt(t) 在 logistic 回归每个 epoch(t) 中都得到更新。

Adam 论文建议的参数设定, 默认参数设定为:alpha=0.001、beta1=0.9、beta2=0.999 和 epsilon=10E−8。

我们也可以看到流行的深度学习库都采用了该论文推荐的参数作为默认设定。

  • TensorFlow:learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08.
  • Keras:lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0.
  • Blocks:learning_rate=0.002, beta1=0.9, beta2=0.999, epsilon=1e-08, decay_factor=1.
  • Lasagne:learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08
  • Caffe:learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-08
  • MxNet:learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8
  • Torch:learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8

Adam 实现优化的过程和权重更新规则

SummaryOfComputerVision%2Fadam.png

如上图所示, 首先确定超参 $\eta$ (学习率步长, 也就是上面的 $\alpha$), $\beta_1$, $\beta_2$, 以及目标函数 $f(\theta)$. 然后初始化参数量 $\theta = \theta_0$, 一阶矩向量 $m_0$ 和二阶矩向量 $n_0$ (也就是上图中的 $v_0$). 然后进行循环迭代, 直至 $\theta_t$ 收敛, 更新过程如下:

  1. 时间戳 $t$ 增 1;
  2. 获取目标函数在当前时间戳 $t$ 上对各个参数 $\theta$ 的梯度;
  3. 利用当前时间戳的梯度 $g_t$ 和滑动平均公式更新当前时间戳的有偏一阶矩估计和二阶矩估计.
  4. 将一阶矩估计和二阶矩估计更新成无偏的;
  5. 有矩估计更新参数 $\theta$, 对于每一个参数, 其矩估计的值都不尽相同, 因此起到了自适应调节学习率的作用.

Adam 的初始化偏差修正的推导

感官的解释:
偏差修正: 当初始化 $v_0 = 0$ 时, 由于初始化的值太小, 导致初期的滑动平均值偏小, 随着时间的增长, 初期的值影响减小, 滑动平均值才逐渐正常. 为了让初期的滑动平均值也相对正常, 我们利用下面的式子进行修正:

数学的证明:
https://www.jiqizhixin.com/articles/2017-07-12

我们以二阶原点矩的无偏估计为例进行推导, 一阶原点矩的推导完全类似.

首先我们可以求得目标函数 $f$ 的梯度, 然后我们利用 梯度平方 的衰减率为 $\beta_2$ 的指数滑动平均值来近似估计该梯度的 有偏的二阶原点矩.
令 $g_1, …, g_T$ 为时间序列上的梯度, 其中每个梯度都服从一个潜在的梯度分布 $g_t \sim P(g_t)$. 我们将指数滑动平均值初始化为 $n_0 = 0$, 然后用下面的公式更新滑动平均值($g_t^2$ 代表 element-wise multiply):

我们希望知道时间戳 $t$ 上的指数滑动平均值的期望 $E[n_t]$ 如何与真实的二阶矩 $E[g_t^2]$ 相关联, 于是我们先对上式的两边分别取期望, 如下:

通常 $C’$ 的值很小, 因此可以将其去除, 于是乎就得到了无偏估计的式子.

Adam 的扩展形式: AdaMax

各种优化方法的源码实现

参考文献及其他

Adam 无法收敛?: https://www.jiqizhixin.com/articles/2017-12-06
SGD 的参数设置

https://www.cnblogs.com/happylion/p/4172632.html

https://www.cnblogs.com/shixiangwan/p/7532830.html

https://www.cnblogs.com/hlongch/p/5734105.html

https://www.baidu.com/link?url=8EyCqGYnldJzHuqBBGagV9juEA_nhCYvRElM2Tw0lBdewSmc0qshAy_AHAEegO-wT3vLsrcY1xSDdyLOmL09Ltm_UICAFX_C02QdkkSCcWW&wd=&eqid=ce9adcb10004685c000000035b5d4fb6

https://mp.weixin.qq.com/s/lh4jTYJroq6AKb2fYsP-GQ

初始化方法

深度学习-各种初始化方法深入分析

constant, uniform, gaussian, xavier, msra(kaiming), bilinear

各个初始化方法的形式,

初始化方法 服从分布 说明
均匀分布 将权值与偏置进行均匀分布的初始化
高斯分布 初始化为服从 $N(\mu, \sigma ^2)$ 的高斯分布
Xavier $W \sim U\Big[-\frac{\sqrt{6}}{\sqrt{n_j+n_{j+1}}},\frac{\sqrt{6}}{\sqrt{n_j + n_{j+1}}} \Big]$,
服从均值为 0, 方差为 $\frac{2}{n_i + n_{i+1}}$ 的均匀分布
公式中, $n_i$ 为本层输入的神经元个数, $n_{i+1}$ 为本层输出的神经元个数, 适合于线性激活函数(原文公式推导的假设)
MSRA(Kaiming) 基于均值为0, 方差为 $\sqrt{\frac{2}{(1+a^2)\times fan_{in}}}$ 的高斯分布 它特别适合 ReLU 激活函数(非线性)
双线性初始化 常用在反卷积网络里的权值初始化

Xavier 初始化推导

核心理念是: 优秀的初始化方法应该使得各层的激活值和状态梯度在传播过程中的方差保持一致

再继续推导之前, 需要先提出以下假设:

  • 首先,输入数据来说,其均值和方差应满足: $E(x)=0, Var(x)=1$ (通过BN,较容易满足)
  • 权重矩阵 $W$ 和 网络输入 $x$ 互相独立
  • 每层输入的每个特征方差一样
  • 激活函数对称: 这主要是为了满足均值为0的假设
  • 激活函数是线性的, 也就是说其导数为1
  • 初始时, 状态值落在激活函数的线性区域, 即此时导数为1

现在假设有一个 $n$ 维的输入向量 $\vec X$ 和一个单层的线性神经网络, 它的权重向量是 $\vec W$, 网络的输出是 $Y$, 则有:

对于每个 $W_i X_i$, 它对应的方差为:

当输入的 $X$ 均值为 0 时(通过 BN, 较容易满足), 输出的方差就是:

进一步假设 $W_i$ 和 $X_i$ 是独立同分布的, 就可以得到:

也就是说输出的方差跟输入的方差只是相差了一个倍数 $nVar(W_i)$, 因此, 为了保证前向传播和反向传播时每一层的方差一致, 则有下面的公式成立:

同时考虑反向传播时输入输出刚好相反, 于是就有:

权衡上面两个公式, 最终给出的权重方差为:

再由概率统计中均匀分布方差的性质反推,可以得到Xavier最终的初始化分布如下:

Xavier在Caffe中的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template <typename Dtype>
class XavierFiller : public Filler<Dtype> {
public:
explicit XavierFiller(const FillerParameter& param)
: Filler<Dtype>(param) {}
virtual void Fill(Blob<Dtype>* blob) {
CHECK(blob->count());
int fan_in = blob->count() / blob->num();
int fan_out = blob->count() / blob->channels();
Dtype n = fan_in; // default to fan_in
if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_AVERAGE) {
n = (fan_in + fan_out) / Dtype(2);
} else if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_FAN_OUT) {
n = fan_out;
}
Dtype scale = sqrt(Dtype(3) / n);
caffe_rng_uniform<Dtype>(blob->count(), -scale, scale,
blob->mutable_cpu_data());
CHECK_EQ(this->filler_param_.sparse(), -1)
<< "Sparsity not supported by this Filler.";
}
};

可以看出, Caffe的Xavier实现有三种选择:

(1) FAN_IN, 方差只考虑输入个数:

(2) FAN_OUT, 方差只考虑输出个数:

(3) AVERAGE, 方差同时考虑输入和输出个数:

训练时是否可以将全部参数初始化为 0

不能
首先, 在神经网络中, 每一层中的任意神经元都是同构的, 它们拥有相同的输入, 如果再将参数全部初始化为同样的值(如0), 那么输出也就是相同的, 反过来它们的梯度也都是相同的. 那么无论是前向传播还是反向传播的取值都是完全相同的, 那么每一个神经元都是基于input做相同的事情, 这样一来, 不同的神经元根本无法学到不同的特征, 这样就失去网络学习特征的意义了

损失函数

深度学习-各种损失函数深入解析

0-1 损失

绝对值损失(L1)

平方损失(L2)

带有正则项:

简写形式

这里 $f(x) = a^L(x)$ 代表最后一层的激活输出.

Softmax

上式中, $t$ 和 $y$ 分别表示神经网络的真实标签和预测输出, 第一个公式代表 softmax 激活函数.

Softmax 的上溢下溢问题:
当 softmax 函数中的 $x_i$ 为很大的正数时, 分子, $e^{x_i}$ 的值可能很大, 造成分子上溢
当 $x_i$ 为很小的负数时, 分母的值可能非常小, 有可能四舍五入为 0, 导致下溢.
如何防止 Softmax 的上溢下溢问题:
令 $M = max(x_i)$, 然后将 $f(x_i)$ 改成 $f(x_i - M)$ 的值, 即可解决上溢下溢问题. 此时分子最大值为 $(x_i-M)=0$ 时的情况, 即分子最大为 1, 分母则至少一项为 1.
并且计算结果理论上仍然保持一致. 因为在指数项的减法, 可以换成对应的除法, 分子分母一约分, 就和原始公式相同了.

KL散度(相对熵)和交叉熵

熵用来表示随机变量不确定的度量, 是对所有可能发生的事件产生的信息量的期望, 公式如下:

当分布的不确定性越强, 则其熵值越大, 例如, 有均匀分布[1/4, 1/4, 1/4, 1/4] 和非均匀分布[1/2, 1/4, 1/8, 1/8], 则前者的熵为(基任意, 这里取 2) $H(X) = -4\times (1/4) log(1/4) = 2bits$, 后者的为 1.75 bits. 可以看出, 由于均匀分布的不确定性更大, 因此它的熵更大.

KL散度(相对熵)
相对熵又称KL散度,用于衡量对于同一个随机变量x的两个分布p(x)和q(x)之间的 差异。在机器学习中,p(x)常用于描述样本的真实分布,例如[1,0,0,0]表示样本属于第一类,而q(x)则常常用于表示预测的分布,例如[0.7,0.1,0.1,0.1]。显然使用q(x)来描述样本不如p(x)准确,q(x)需要不断地学习来拟合准确的分布p(x)。

KL散度的公式如下:

KL 散度的值越小表示两个分布越接近

交叉熵
我们将 KL 散度的公式进行变形, 得到:

上式中, 前半部分就是真实分布 p(x) 自己的熵, 后半部分就是我们的交叉熵

交叉熵损失

https://www.zhihu.com/search?type=content&q=softmax%20%E4%BA%A4%E5%8F%89%E7%86%B5

首先定义符号说明:

  • $p^{(i)}$: 第i个样本类别为1的真实概率(如第i个样本真实类别为1, 则概率为1, 否则为0)
  • $f^{(i)}$: 第i个样本预测类别为1的概率
  • $p_k^{(i)}$: 第i个样本类别为k的真实概率(如第i个样本真实类别为k, 则概率为1, 否则为0)
  • $f_k^{(i)}$: 第i个样本预测类别为k的概率

面对二分类问题, 损失函数形式为:

面对多分类问题, 损失函数形式为:

交叉熵衡量了两个分布之间的差异性, 当概率相等时, 交叉熵最大, 则损失函数达到最小(因为加了负号)

Smooth L1

smooth L1 损失是一种鲁棒性较强的 L1 损失, 相比于 R-CNN 和 SPPNet 中使用的 L2损失, 它对离异点的敏感度更低. 当回归目标趋于无限时, L2 损失需要很小心的处理学习率的设置以避免发生梯度爆炸, 而 smooth L1 损失则会消除这种敏感情况.
相比于 $L_2$ 损失, $L_1$ 损失对于离异值更加鲁棒, 当预测值与目标值相差很大时, 梯度很容易爆炸, 因为梯度里面包含了 $(t_i^u - v_i)$ 这一项, 而smooth L1 在值相差很大是, 其梯度为 $\pm 1$ ( $L_1$ 在 $x$ 绝对值较大时, 是线性的, 而 $L_2$ 是指数的, 很容易爆炸).

Focal Loss

对上式可以进行如下简写:

注1: $\gamma$ 的值越大, 则 easy example 对于loss的贡献程度越小, 当 $\gamma = 0$ 时, 会退化成普通的交叉熵函数.

注2: 实际中在使用 $\gamma$ 参数的情况下, 还会用了另一个参数 $\alpha$ , 该参数的主要作用是调节正负样本的权重, $\alpha$ 的取值通常会根据正负样本的实际比例决定, 如下所示:

通常也可缩写为:

DR Loss

训练的模型实际效果不错, 但是平方根误差一直很高, 为什么

平方根误差(RMSE)公式如下:

模型在绝大部分情况下可以取得较好的预测结果. 但是平方根误差一直很高, 这可能是因为存在若干个偏离真实样本程度很大的异常点(Outlier), 这样一来, 即使离群点的数量非常少, 也会让 RMSE 指标变的很差.

解决方法:

  1. 将这些异常点判定为 “噪声”, 在数据预处理的阶段将这些噪声点过滤掉;
  2. 如果不认为这些异常点为 “噪声”, 那么就需要进一步的提高模型的预测能力, 使其能够将这些异常点考虑在模型内.
  3. 可以换用比 RMSE 鲁棒性更好的评估指标, 比如平均绝对百分比误差(Mean Absolute Precent Error, MAPE), 其定义为:

平方误差和交叉熵误差损失函数的误差项(相对于临时输出值的梯度)

根据常见问题篇中的反向传播算法推导, 我们知道每一层的参数更新依赖于每一层的误差项, 而每一层的误差项都依赖于后一层的误差项. 最终, 参数的更新会由最后一层的损失函数的误差项决定, 其形式如下所示(相关符号定义可切换到 BP 公式推导部分查看):

根据平方损失公式和交叉熵损失公式, 我们可以分别得到这两种损失在误差项:

  • 平方损失:
  • 平方损失误差项:上式中 $f(x) = a^L = \sigma(z^l)$ 代表最后一层的激活输出.
  • n 分类交叉熵损失:我们简化公式, 假设样本数量 $m=1$, 则 n 分类问题的交叉熵为
  • n 分类交叉熵损失误差项:上式中 $f_k = a_k^L = \sigma(z_k^l)$ 代表最后一层的激活输出. 另外, 通常情况下, 交叉熵分类损失的激活函数为 softmax, 其导数 $f’(x) = f(x)(1-f(x))$, 因此, 可以看出, 此时的导数是线性的, 而不是趋近于 0, 因此不会存在学习速度过慢的问题.

平方损失和交叉熵损失的使用场景

平方损失: 适用于输出值为连续的回归任务.
交叉熵损失: 适用于分类任务.

平方损失不适用于分类任务的原因:
在上面可以看到, 平方损失的误差项中, 包含有激活函数相对于临时输出值的导数 $\sigma{‘} (z^L)$, 而在进行分类损失时, 例如二分类, 通常会使用 sigmoid 激活函数, 这时, 如果 $z^L$ 的绝对值较大, 激活函数的梯度就会趋近于饱和, 那么误差项的值就会非常小, 当梯度很小时, 参数的更新很慢, 当梯度接近于 0 时, 就会产生梯度消失现象, 使得参数无法更新.

综上, 平方损失不适用于分类任务

交叉熵损失不适用于回归任务的原因:
最小二乘的好处:

  • 唯一确定性, 只有当 x = y 时, d(x, y) = 0
  • 对称性, d(x, y) = d(y, x)
  • 物理性质明确,

交叉熵为什么用 softmax 进行归一化

  1. 在物理含义上, 用 softmax 归一化以后, 每个神经元的输出可以认为是属于这一类的概率, 使得每个值所代表的含义比较明确.
  2. 交叉熵本身就是用来衡量两个分布之间的差异性的, 用 softmax 处理后的输入, 可以将元素的值看做是一个概率分布. 符合交叉熵的物理含义.
  3. 数值上的稳定性, 如果直接用不经过归一化的原始值的话, 模型对过大的值或者过小的值会很敏感, 使得无法学习出来的模型鲁棒性不高

激活函数

Sigmoid, Tanh, ReLU, Leaky ReLU, PReLU, RReLU, ELU, Maxout

深度学习-各种激活函数深入解析

写出常用的激活函数的公式及其导数形式

激活函数 形式 导数形式
Sigmoid $f(x) =\frac{1}{1+e^{-x}}$ $f’(x) = f(x)(1-f(x))$
Tanh $f(x) = tanh(x)= \frac{e^x-e^{-x}}{e^x+e^{-x}}$ $f’(x) = 1-(f(z))^2$
ReLU $f(x)=max(0,x)=\begin{cases} 0 & x \leq 0 \\ x & x>0 \end{cases}$ $f’(x)=\begin{cases} 0 & x\leq 0 \\ 1 & x>0 \end{cases}$
Leaky ReLU $f(x)=max(0.001 x,x)=\begin{cases} 0.001x & x \leq 0 \\ x & x>0 \end{cases}a$ $f(x)=max(0.001 x,x)=\begin{cases} 0.001 & x \leq 0 \\ 1 & x>0 \end{cases}$
PReLU $f(x)=max(\alpha x,x)=\begin{cases} \alpha x & x \leq 0 \\ x & x>0 \end{cases}$ $f(x)=max(\alpha x,x)=\begin{cases} \alpha & x \leq 0 \\ 1 & x>0 \end{cases}$
RReLU PReLU中的 $\alpha$ 随机取值 $f(x)=max(\alpha x,x)=\begin{cases} \alpha & x \leq 0 \\ 1 & x>0 \end{cases}$
ELU $f(x) = \begin{cases} x & x \geq 0 \\ \alpha(e^x - 1) & x<0 \end{cases}$ $f(x) = \begin{cases} 1 & x \geq 0 \\ \alpha e^x & x<0 \end{cases}$
Maxout $f(x) = max(w_1^T x + b_1, w_2^T x + b_2)$ $f(x) = max(w_1, w_2)$

简单画出常用激活函数的图像

sigmoid:

SummaryOfComputerVision%2Fact_sigmoid.jpg

tanh:

SummaryOfComputerVision%2Fact_tanh.jpg

relu

SummaryOfComputerVision%2Fact_relu.jpg

leaky_relu

SummaryOfComputerVision%2Fact_leaky_relu.jpg

prelu:

SummaryOfComputerVision%2Fact_prelu.jpg

rrelu:

SummaryOfComputerVision%2Fact_rrelu.jpg

elu

SummaryOfComputerVision%2Fact_elu.jpg

为什么需要激活函数?

标准说法

这是由激活函数的性质所决定来, 一般来说, 激活函数都具有以下性质:

  • 非线性: 首先,线性函数可以高效可靠对数据进行拟合, 但是现实生活中往往存在一些非线性的问题(如XOR), 这个时候, 我们就需要借助激活函数的非线性来对数据的分布进行重新映射, 从而获得更强大的拟合能力. (这个是最主要的原因, 其他还有下面这些性质也使得我们选择激活函数作为网络常用层)
  • 可微性: 这一点有助于我们使用梯度下降发来对网络进行优化
  • 单调性: 激活函数的单调性在可以使单层网络保证网络是凸优化的
  • $f(x) \approx x:$ 当激活满足这个性质的时候, 如果参数初值是很小的值, 那么神经网络的训练将会很高效(参考ResNet训练残差模块的恒等映射); 如果不满足这个性质, 那么就需要用心的设值初始值( 这一条有待商榷 )

如果不使用激活函数, 多层线性网络的叠加就会退化成单层网络,因为经过多层神经网络的加权计算,都可以展开成一次的加权计算

更形象的解释

对于一些线性不可分的情况, 比如XOR, 没有办法直接画出一条直线来将数据区分开, 这个时候, 一般有两个选择.

如果已知数据分布规律, 那么可以对数据做线性变换, 将其投影到合适的坐标轴上, 然后在新的坐标轴上进行线性分类

而另一种更常用的办法, 就是使用激活函数, 以XOR问题为例, XOR问题本身不是线性可分的,

https://www.zhihu.com/question/22334626

举例说明为什么激活函数可以解决 XOR 问题

首先, XOR问题如下所示:

$x_1$ $x_2$ y
1 0 1
0 1 1
1 1 0
0 0 0

首先构造一个简单的神经网络来尝试解决XOR问题, 网络结构如下图所示:

SummaryOfComputerVision%2Fxor.jpg

先来看看不使用激活函数时的情况, 当不使用激活函数时, 整个网络的函数表达式如下所示:

可以看到, 多层无激活函数的网络叠加, 首先是会退化成单层网络, 而对于单层网络, 求解出来的参数 $w’$ 和 $b’$ 无法对非线性的数据进行分类.

再来看看进入ReLU以后, 是如何解决XOR问题的, 首先, 引入后的公式如下所示:

可以看到, 此时函数是无法化简, 因为此时引入了非线性的ReLU函数, 于是 ,就可以求得一个参数组合${w,W,c,b}$ 使得对于特定的输入$x_1, x_2$ ,能够得到正确的分类结果 $y$. 至于这个参数组合具体是什么, 这是需要通过梯度下降来不断学习的, 假如我们现在找到了一组参数如下(当然不一定是最优的), 来看看这组参数具体是如何解决XOR问题的:

然后, 分别将4种 $x_1, x_2$的值代入上式, 可以得到, y的值如下:

$x_1$ $x_2$ 计算过程 y
1 0 $[1, -2] max \bigg(0 , \bigg[\begin{matrix} 1 & 1 \\ 1 & 1 \end{matrix} \bigg] \Big[\begin{matrix} 1 \\ 0 \end{matrix} \Big]+ \Big[\begin{matrix} 0 \\ -1 \end{matrix} \Big] \bigg) + 0$ 1
0 1 $[1, -2] max \bigg(0 , \bigg[\begin{matrix} 1 & 1 \\ 1 & 1 \end{matrix} \bigg] \Big[\begin{matrix} 0 \\ 1 \end{matrix} \Big]+ \Big[\begin{matrix} 0 \\ -1 \end{matrix} \Big] \bigg) + 0$ 1
1 1 $[1, -2] max \bigg(0 , \bigg[\begin{matrix} 1 & 1 \\ 1 & 1 \end{matrix} \bigg] \Big[\begin{matrix} 1 \\ 1 \end{matrix} \Big]+ \Big[\begin{matrix} 0 \\ -1 \end{matrix} \Big] \bigg) + 0$ 0
0 0 $[1, -2] max \bigg(0 , \bigg[\begin{matrix} 1 & 1 \\ 1 & 1 \end{matrix} \bigg] \Big[\begin{matrix} 0 \\ 0 \end{matrix} \Big]+ \Big[\begin{matrix} 0 \\ -1 \end{matrix} \Big] \bigg) + 0$ 0

为什么 Sigmoid 和 Tanh 激活函数会导致梯度消失现象

p208

ReLU 系列相对于 Sigmoid 和 Tanh 的优势

ReLU 有哪些局限性, 如何改进

各个激活函数的优缺点和适用场景是什么

神经元饱和问题: 当输入值很大或者很小时, 其梯度值接近于0, 此时, 不管从深层网络中传来何种梯度值, 它向浅层网络中传过去的, 都是趋近于0的数, 进而引发梯度消失问题

zero-centered: 如果数据分布不是zero-centered的话就会导致后一层的神经元接受的输入永远为正或者永远为负, 因为 $\frac{\partial f}{\partial w} = x$ , 所以如果x的符号固定,那么 $\frac{\partial f}{\partial w}$ 的符号也就固定了, 这样在训练时, weight的更新只会沿着一个方向更新, 但是我们希望的是类似于zig-zag形式的更新路径 (关于非0均值问题, 由于通常训练时是按batch训练的, 所以每个batch会得到不同的信号, 这在一定程度上可以缓解非0均值问题带来的影响, 这也是ReLU虽然不是非0 均值, 但是却称为主流激活函数的原因之一)

激活函数 优势 劣势 适用场景
Sigmoid 可以将数据值压缩到[0,1]区间内 1. 神经元饱和问题
2.sigmoid的输出值域不是zero-centered的
3. 指数计算在计算机中相对来说比较复杂
在logistic回归中有重要地位
Tanh 1. zero-centered: 可以将 $(-\infty, +\infty)$ 的数据压缩到 $[-1,1]$ 区间内
2.完全可微分的,反对称,对称中心在原点
1. 神经元饱和问题
2. 计算复杂
在分类任务中,双曲正切函数(Tanh)逐渐取代 Sigmoid 函数作为标准的激活函数
ReLU 1. 在 $(0,+\infty)$ ,梯度始终为1, 没有神经元饱和问题
2. 不论是函数形式本身,还是其导数, 计算起来都十分高效 3. 可以让训练过程更快收敛(实验结果表明比sigmoid收敛速度快6倍)
4. 从生物神经理论角度来看, 比sigmoid更加合理
1. 非zero-centered
2. 如果输入值为负值, ReLU由于导数为0, 权重无法更新, 其学习速度可能会变的很慢,很容易就会”死”掉(为了克服这个问题, 在实际中, 人们常常在初始化ReLU神经元时, 会倾向于给它附加一个正数偏好,如0.01)
在卷积神经网络中比较主流
LeakyReLU 1. 没有神经元饱和问题
2. 计算高效
3. 收敛迅速(继承了ReLU的优点)
4. 神经元不会”死”掉(因为在负值时, 输出不为0, 而是x的系数0.001)
PReLU 1. 没有神经元饱和问题
2. 计算高效
3. 收敛迅速(继承了ReLU的优点)
4. 神经元不会”死”掉(因为在负值时, 输出不为0, 而是x的系数 $\alpha$ )
5. 相对于Leaky ReLU需要通过先验知识人工赋值, PReLU通过迭代优化来自动找到一个较好的值, 更加科学合理, 同时省去人工调参的麻烦
ELU 1. 拥有ReLU所有的优点
2. 形式上更接近于zero-centered
3. 在面对负值输入时,更加健壮
1. 引入了指数计算, 使计算变的复杂
Maxout 1. 跳出了点乘的基本形式
2. 可以看作是ReLU和Leaky ReLU 的一般化形式 3. linear Regime(啥意思?)!
4. 在所有输入范围上都没有神经元饱和问题
5. 神经元永远不会”死”掉
6. 拟合能力非常强,它可以拟合任意的的凸函数。作者从数学的角度上也证明了这个结论,即只需2个maxout节点就可以拟合任意的凸函数了(相减),前提是”隐含层”节点的个数可以任意多
1. 使得神经元个数和参数个数加倍, 导致优化困难

Sigmoid 激活函数和 Softmax 激活函数的区别

sigmoid是将一个正负无穷区间的值映射到(0,1)区间, 通常用作二分类问题,而softmax把一个k维的实值向量映射成一个$(b_1,b_2,…,b_k)$ ,其中$b_i$为一个0~1的常数, 且它们的和为1, 可以看作是属于每一类的概率,通常用作多分类问题. 在二分类问题中, sigmoid和softmax是差不多的, 都是求交叉熵损失函数, softmax可以看作是sigmoid的扩展, 当类别k为2时, 根据softmax回归参数冗余的特点, 可以将softmax函数推导成sigmoid函数

https://www.jianshu.com/p/22d9720dbf1a

什么情况下 ReLU 的神经元会死亡? 为什么? 可以复活吗?

对于 ReLU 来说, 如果某次更新过程中, 梯度值过大, 同时学习率又不小心设置的过大, 就会导致权重一下走更新过多, 那么就有一定概率出现这种情况: 对于任意的训练样本 $x_i$, 当前神经元的输出都是小于 0, 那么该神经元流向 ReLU 时, 其所有的激活值都会小于 0, 那么对应的激活输出就均为 0. 此时, 反向传播回去的梯度也都变成了 0. 此时我们就认为该神经元死亡.

举个确切的例子说明, 现在假设,这个神经元已经经过若干次迭代,其参数 $(\vec w, b)$ 已经迭代得趋于稳定。现在,神经元接收到了一个异常的输入 $\vec x$ 。比方说,它的某一维特征 $x_i$ 与对应的权重 $w_i$ 的乘积 $w_i x_i$ 非常大。一般来说,这意味着 $x_i$ 的绝对值非常大。于是,ReLU 的输入就会很大,对应 ReLU 的输出 $y$ 也就会很大。好了,假设这个 ReLU 神经元期望的输出(ground truth)是 $\hat y$ ,这个时候损失就会很大——损失一般是 $\vert y − \hat y\vert$ 的增函数,记为 $f(\vert y − \hat y \vert)$。

于是,在反向传播过程中,传递到 ReLU 的输入时的梯度就 $g$ 相应的就会很大。考虑对于偏置 $b$ 有更新

考虑到 $g$ 是一个很大的正数,于是 $b$ 可能被更新为一个远小于 0 的负数。此后,对于常规输入来说,ReLU 的输入大概率是个负数。这也就是说,ReLU 大概率是关闭的。这时,梯度无法经 ReLU 反向传播至 ReLU 的输入函数。也就是说,这个神经元的参数再也不会更新了。这就是所谓的「神经元死亡」。

复活问题: 如果是 ReLU 的话, 几乎就无法复活了, 因为由于此时大部分的输入都是小于 0 的, 这样导致反向传播回来的梯度一直为 0, 那么就无法一点点的更新权重, 使之回归正常值, 也就无法复活神经元了. 由此也可以看出, LeakyReLU 可以在负半区一点点的更新权重, 使之有可能复活.

为什么实际使用中不使用 LeakyReLU?(死亡的神经元也可以起到一定的正则作用, 实际使用效果较好)

谈谈由异常输入导致的 ReLU 神经元死亡的问题: https://liam.page/2018/11/30/vanishing-gradient-of-ReLU-due-to-unusual-input/
深度学习中,使用relu存在梯度过大导致神经元“死亡”,怎么理解?: https://www.zhihu.com/question/67151971

如何解决 ReLU 神经元死亡问题

  1. 把 ReLU 换成 LReLU 或者 PReLU,保证让激活函数在输入小于零的情况下也有非零的输出。
  2. 采用较小的学习率
  3. 采用自适应的优化算法,动态调整学习率

谈谈 ReLU6

在Mobile v2里面使用 ReLU6,ReLU6 就是普通的ReLU但是限制最大输出值为6(对输出值做clip)

看mobilenetv2的论文注意到激活函数是 relu6,查了一下,有人说是方便后面参数的定点化Why the 6 in relu6?, 也有说relu6可以让模型学到稀疏性?
首先说明一下ReLU6,卷积之后通常会接一个ReLU非线性激活,在Mobile v1里面使用ReLU6,ReLU6就是普通的ReLU但是限制最大输出值为6(对输出值做clip),这是为了在移动端设备float16的低精度的时候,也能有很好的数值分辨率,如果对ReLU的激活范围不加限制,输出范围为0到正无穷,如果激活值非常大,分布在一个很大的范围内,则低精度的float16无法很好地精确描述如此大范围的数值,带来精度损失。

本文提出,最后输出的ReLU6去掉,直接线性输出,理由是:ReLU变换后保留非0区域对应于一个线性变换,仅当输入低维时ReLU能保留所有完整信息。

在看MobileNet v1的时候,我就疑问为什么没有把后面的ReLU去掉,因为Xception已经实验证明了Depthwise卷积后再加ReLU效果会变差,作者猜想可能是Depthwise输出太浅了应用ReLU会带来信息丢失,而MobileNet还引用了Xception的论文,但是在Depthwise卷积后面还是加了ReLU。在MobileNet v2这个ReLU终于去掉了,并用了大量的篇幅来说明为什么要去掉(各种很复杂的证明,你不会想自己推一遍的= =)。

总之,结论就是最后那个ReLU要去掉,效果更好。

激活函数的使用原则

  1. 优先使用ReLU, 同时要谨慎设置初值和学习率 ( 实际操作中,如果你的learning rate 很大,那么很有可能你网络中的40%的神经元都 “dead” 了。 当然,如果你设置了一个合适的较小的learning rate,这个问题发生的情况其实也不会太频繁 )
  2. 尝试使用LeakyReLU/PReLU/Maxout/ELU等激活函数
  3. 可以试下tanh, 但是一般不会有太好的结果
  4. 不要使用sigmoid

正则化

L1, L2

深度学习-正则化方法深入解析

简述 L1 正则和 L2 正则的形式

L1 正则

L1正则项如下所示, 其中 $L_0$ 代表原始的不加正则项的损失函数, $L$ 代表加了正则项以后的损失函数, $m$ 则代表训练batch的样本大小 :

将上式对参数 $w$ 求导如下(由于正则项与 $b$ 无关, 因此参数 $b$ 的导数不变):

上式中 $sign(w)$ 表示 $w$ 的符号, 当 $w>0$ 时, $sign(w)=1$ , 当 $w<0$ 时, $sign(w)=-1$, 为了实现方便, 我们特意规定, 当 $w=0$ 时, $sign(w) = 0$ , 相当于去掉了正则项.

因此, 权重 $w$ 的更新表达式可如下表示:

L2 正则

L2正则项如下所示, 其中 $L_0$ 代表原始的不加正则项的损失函数, $L$ 代表加了正则项以后的损失函数, 式中的系数 $\frac{1}{2}$ 主要是为了消去求导后产生的常数 $2$, 方便表示 (因为可以根据 $\lambda$ 的值来替代这些常数):

将上式对参数 $w$ 求导如下:

则, 权重 $w$ 的更新表达式可如下表示:

由于, $\eta, \lambda, m$ 三个值都是正的, 因此, 加上 $L2$ 正则化以后, 权重整体减小了, 这也是”权重衰减”的由来.

L1 正则和 L2 正则的特点是什么? 各有什么优势?

二者共同的特点都是能够防止过拟合问题.

L1 的优点: 能够获得更加稀疏的模型, 权重参数最终大部分会变成 0
L1 的缺点: 加入 L1 后会使得目标函数在原点不可导, 需要做特殊处理

L2 的有点: 在任意位置都可导, 优化求解过程比较方便, 而且更加稳定
L2 的缺点: 无法获得真正的稀疏模型, 参数值只是缓慢趋近于0, 不是直接变成 0

在实际应用过程中, 大部分情况下都是 L2 正则的效果更好, 因此推荐优先使用 L2 正则

L1 和 L2 的区别有哪些?

  1. L1 相对于 L2 能够产生更加稀疏的模型
  2. L2 相比于 L1 对于离异值更敏感(因为平方的原因, L2 对于大数的乘法比对小数的惩罚大)
  3. L1 和 L2 梯度下降速度不同: 前者梯度恒定, 并且接接近于 0 的时候会很快将参数更新成0, 后者在接近于0 时, 权重的更新速度放缓, 使得不那么容易更新为0 (这也解释了为什么 L1 具有稀疏性)
  4. 二者解空间性状不同

下图是 L1 和 L2 对向量中值的分布的先验假设, L1 是蓝色的线, L2 是红色的线, 可以看出, L1 的分布对于极端值更能容忍. L1 和 L2 分别对应拉普拉斯先验和高斯先验(why)

SummaryOfComputerVision%2Fl1l2_1.jpg

下图是 L1 和 L2 的示意图

SummaryOfComputerVision%2Fl1l2_2.jpg

L1正则化使模型参数稀疏的原理是什么?

角度一: 函数图像

L1 在 0 处迅速下降到 0, L2 在 0 处会变得缓慢, 并不会直接更新为 0.

角度二: 函数叠加(梯度下降更新公式)

从以上的更新表达式我们可以看出, 当 $w$ 为正时, L1正则化会将更新后的 $w$ 变的再小一点, 而当 $w$ 为负时, L1正则化会将其变的更大一点—-因此L1的正则化效果就是让 $w$ 尽可能的向 $0$ 靠近, 即最终的 $w$ 参数矩阵会变的更加稀疏

角度三: 贝叶斯先验, “百面机器学习”
角度四: 解空间性状, “百面机器学习”

为什么 L1 和 L2 分别对应拉普拉斯先验和高斯先验?

为什么权重矩阵稀疏可以防止过拟合?

可以从两个方面来理解:

1)特征选择(Feature Selection):稀疏性可以实现特征的自动选择, 可以在进行预测时减少无用信息的干扰

大家对稀疏规则化趋之若鹜的一个关键原因在于它能实现特征的自动选择。一般来说,$x_i$ 的大部分元素(也就是特征)都是和最终的输出 $y_i$ 没有关系或者不提供任何信息的,在最小化目标函数的时候考虑 $x_i$ 这些额外的特征,虽然可以获得更小的训练误差,但在预测新的样本时,这些没用的信息反而会被考虑,从而干扰了对正确 $y_i$ 的预测。稀疏规则化算子的引入就是为了完成特征自动选择的光荣使命,它会学习地去掉这些没有信息的特征,也就是把这些特征对应的权重置为 0。

2)可解释性(Interpretability):较稀疏的模型表示最终的预测结果只与个别关键特征有关, 这符合实际生活中的历史经验

另一个青睐于稀疏的理由是,模型更容易解释。例如患某种病的概率是y,然后我们收集到的数据x是1000维的,也就是我们需要寻找这1000种因素到底是怎么影响患上这种病的概率的。假设我们这个是个回归模型: $y=w_1 \times x_1+w_2 \times x_2+…+w_{1000} \times x_{1000}+b$ (当然了,为了让 $y$ 限定在 $[0,1]$ 的范围,一般还得加个Logistic函数)。通过学习,如果最后学习到的 $w$ 就只有很少的非零元素,例如只有 5 个非零的 $wi$ ,那么我们就有理由相信,这些对应的特征在患病分析上面提供的信息是巨大的,决策性的。也就是说,患不患这种病只和这5个因素有关,那医生就好分析多了。但如果 1000 个 $w_i$ 都非 0,医生面对这 1000 种因素,累觉不爱.

为何权重参数 $w$ 减小就可以防止过拟合?

直观解释:

更小的权值w,从某种意义上说,表示网络的复杂度更低,对数据的拟合刚刚好(这个法则也叫做奥卡姆剃刀),而在实际应用中,也验证了这一点,L2正则化的效果往往好于未经正则化的效果

“数学一点”的解释:

过拟合的时候,拟合函数的系数往往非常大,为什么?因为过拟合说明拟合函数会顾忌每一个点,最终形成的拟合函数波动很大。在某些很小的区间里,函数值的变化很剧烈。这就意味着函数在某些小区间里的导数值(绝对值)非常大,由于自变量值可大可小,所以只有系数足够大,才能保证导数值很大. 而正则化是通过约束参数的范数使其不要太大,所以可以在一定程度上减少过拟合情况。

就比如二维平面上的直线拟合, 过拟合时, 由于会顾及每一个点, 最终会形成非常复杂的曲线, 而较好的你和方式是一条顾及大多数点的直线即可.

L0 范式和 L1 范式都能实现稀疏, 为什么不选择用 L0 而要用 L1?

L0范数是指向量中非零元素的个数

一是因为L0范数很难优化求解(NP难问题),二是L1范数是L0范数的最优凸近似,而且它比L0范数要容易优化求解

为什么说 L2 范式可以优化计算?

防止过拟合:

最基本的好处是可以提高模型泛化能力, 防止过拟合

优化计算:

从优化或者数值计算的角度来说, L2正则化有利于提高模型训练速度, 加快计算

原因: https://www.cnblogs.com/callyblog/p/8094745.html

正则项前面的系数一般怎么设置?

通常做法是一开始将正则项系数 $\lambda$ 设置为 0, 然后先确定出一个比较好的 learning rate, 接着固定该 learning rate, 给 $lambda$ 一个初始值, 如 1e-4. 然后根据验证集上的准确率, 将 $\lambda$ 增大或者缩小 10 倍, 这里增减 10 倍是粗调节, 当确定了 $\lambda$ 合适的数量级以后, 再进一步的细调节.

归一化

SummaryOfComputerVision%2Fnorm.jpg

归一化有哪些常用的归一化方法

线性归一化,线性归一化会把输入数据都转换到[0 1]的范围
0均值标准化,0均值归一化方法将原始数据集归一化为均值为0、方差1的数据集

为什么要进行归一化

在神经网络中, 数据分布对训练会产生影响. 当数据较大时, 有的激活函数比如 sigmoid 或者 tanh 的输出就会接近于 1, 此时的梯度就会接近于0, 处于激活函数的饱和阶段, 也就是说无论输入再怎么扩大, sigmoid 或者 tanh 激励函数输出值也还是接近1. 换句话说, 神经网络在初始阶段已经不对那些比较大的 x 特征范围敏感了. 因此我们需要用之前提到的对数据做 normalization 预处理, 使得输入的 x 变化范围不会太大, 让输入值经过激励函数的敏感部分. 需要注意的是, 这个不敏感问题不仅仅发生在神经网络的输入层, 而且在隐藏层中也经常会发生.
当没有进行normalizatin时,数据的分布是任意的,那么就会有大量的数据处在激活函数的敏感区域外, 对这样的数据分布进行激活后, 大部分的值都会变成1或-1, 造成激活后的数据分布不均衡, 而如果进行了Normallizatin, 那么相对来说数据的分布比较均衡.
一句话总结就是: 通过Normalization让数据的分布始终处在激活函数敏感的区域

Batch Normalization

简述 BN 的原理

在训练深度神经网络时, 每个隐藏层的参数变化会使得后一层的输入发生变化, 从而每一批训练数据的分布也随之改变, 导致网络在每次迭代中都需要拟合不同的数据分布, 增大了训练的复杂度以及过拟合的风险. 这使得我们需要非常谨慎的去设定学习率, 初始化权重等参数更新策略. 因此, 为了保证网络层中的每一批数据都处于相同的数据分布下, 我们需要在网络的每一层输入之前增加归一化处理, 具体来说, 就是对当前 batch 中的所有元素减去均值再除以标准差. 这样的归一化操作可以对原始数据添加额外的约束, 从而可以增强模型的泛化能力, 但同时, 由于简单归一化之后的数据分布被强制为 0 均值和 1 标准差, 因此可能会破坏原始数据本身的特征. 为了能够还原原始数据分布, BN 的第二个关键操作就是引入了用于变换重构的线性偏移参数 $\gamma$ 和 $\beta$, 它们分别对简单归一化后的数据执行 scale 和 shift 操作, 可以在一定程度还原数据本身的分布特别. 总体来说, BN 的作用可以简单概括为两步, 第一步是进行归一化, 用于统一不同网络层的数据分布; 第二步变换重构, 用于恢复原始数据的特征信息.

BN 解决了什么问题

BN 主要解决的是深层网络中不同网络数据分布不断发生变化的问题, 也就是 Internal Covariate Shift. 该问题是指在深层网络训练的过程中,由于网络中参数变化而引起内部结点数据分布发生变化的这一过程被称作Internal Covariate Shift. ICS 带来了两个问题:

  • 上层网络需要不停调整来适应输入数据分布的变化,导致网络学习速度的降低
  • 网络的训练过程容易陷入梯度饱和区,减缓网络收敛速度

使用 BN 有什么好处

总的来说,BN通过将每一层网络的输入进行归一化, 保证输入分布的均值与方差固定在一定范围内, 减少了网络中的 Internal Covariate Shift问题, 加速了模型收敛; 并且 BN 可以使得网络对参数的设置如学习率, 初始权重等不那么敏感, 简化了调参过程, 使得网络学习更加稳定; 同时 BN 使得网络可以使用饱和性激活函数如 Sigmoid, tahh 等, 从而缓解了梯度消失问题; 最后 BN 在训练过程中由于使用的 mini-batch 的均值和方差每次都不同,因此引入了随机噪声,在一定程度上对模型起到了正则化的效果, 也就是说, BN 可以起到和 Dropout 类似的作用, 因此在使用 BN 时可以去掉 Dropout 层而不会降级模型精度.

原因如下: https://zhuanlan.zhihu.com/p/34879333
(1) BN使得网络中每层输入数据的分布相对稳定,加速模型学习速度
BN通过规范化与线性变换使得每一层网络的输入数据的均值与方差都在一定范围内,使得后一层网络不必不断去适应底层网络中输入的变化,从而实现了网络中层与层之间的解耦,更加有利于优化的过程,提高整个神经网络的学习速度。

(2) BN使得模型对初始化方法和网络中的参数不那么敏感,简化调参过程,使得网络学习更加稳定
在神经网络中,我们经常会谨慎地采用一些权重初始化方法(例如Xavier)或者合适的学习率来保证网络稳定训练。当学习率设置太高时,会使得参数更新步伐过大,容易出现震荡和不收敛…

(3) BN允许网络使用饱和性激活函数(例如sigmoid,tanh等),缓解梯度消失问题
在不使用BN层的时候,由于网络的深度与复杂性,很容易使得底层网络变化累积到上层网络中,导致模型的训练很容易进入到激活函数的梯度饱和区;通过normalize操作可以让激活函数的输入数据落在梯度非饱和区,缓解梯度消失的问题;另外通过自适应学习 $\gamma$ 与 $\beta$ 又让数据保留更多的原始信息。

(4)BN具有一定的正则化效果
在Batch Normalization中,由于我们使用mini-batch的均值与方差作为对整体训练样本均值与方差的估计,尽管每一个batch中的数据都是从总体样本中抽样得到,但不同mini-batch的均值与方差会有所不同,这就为网络的学习过程中增加了随机噪音,与Dropout通过关闭神经元给网络训练带来噪音类似,在一定程度上对模型起到了正则化的效果。原作者也证明了网络加入BN后,可以丢弃Dropout,模型也同样具有很好的泛化效果。

BN 的文章中说使用 BN 后可以丢弃 Dropout, 所以大家会误认为 BN 本身具有防止过拟合的作用, 实际上这一点并没有被严格证明, 因为有可能是因为 BN 和 Dropout 之间有冲突, 其中一方带来的收益会被另一方面削弱, 16 年有一篇 paper 讨论了 BN 在防止过拟合上的作用, 其结论是 BN 没有防止过拟合的作用 它最多可以使 Overfitting 来的晚一些, 但是却无法防止它.
https://www.zhihu.com/question/275788133

BN 可以看做是对参数搜索空间做的一种约束, Dropout 可以看做是另一种约束, 这两种约束有相似的作用, 但是有时候又会相互冲突, 毕竟二者所限定的约束不能完全相容, 所以共存时无法将受益叠加.

补充: 旷视的笔试题任务 BatchNorm 具有防止过拟合的作用

BN 放在不同位置的区别

BN 通常应用于网络中的非线性激活层之前, 将卷积层的输出归一化, 使得激活层的输入服从 (0, 1) 正态分布, 避免梯度消失的问题.

结论: 由于目前我们对于神经网络内部网络层之间的影响机制还不是特别清楚, 所以在实际中通常就是前面放着试一下, 后面放着试一下, 然后取一个在具体场景下效果最好的. 目前在 实际上, Conv-ReLU-BN 的组合方式效果较好.
个人拙见: BN 放在激活层之前还是之后取决于你想要进行归一化的对象, 它更像是一个超参数, 需要通过实验结合实际场景来决定. 做了一些简单的实验, 发现小模型使用 BN-ReLU, 大模型使用 ReLU-BN 效果较好.

有一个观点是: BN 放在非线性激活函数的前面还是后面取决于你要 normalize 的对象, 更像一个超参数.

  1. Conv-BN-ReLU: 这是比较常见的使用方式, 这种实现方法有一个直接的好处就是可以在网络做前向 inference 的时候, 将 BN 融合到 Conv 中进行加速. 还有另一种好处是个人理解, 就是 BN 在 ReLU 的激活之前, 可以防止某一层的激活值全部被抑制(及某一层的值均小于0), 从而防止从这一层传播的梯度全是 0, 进而可以防止梯度消失现象. (BN 的减均值处理会使得相当一部分响应值变为正, 进而解决了零梯度问题)

  2. Conv-ReLU-BN: 在具体的实验中, 通常 BN 放在最后面效果最好. 个人见解: BN 实际上就是一种归一化, 而归一化通常是对于输入层使用的, 因此, 把 BN 放在最后, 实际上就是对下一个卷积段的输入进行归一化, 从这个角度看, 将 BN 放在激活层之后, 是比较自然的一种做法. 另外, BN 的原文使用的是 sigmoid 和 tanh 激活函数, 但是对于 ReLU 激活来说, 其曲线图像有较大区别, 而 BN 层会起到一定的平滑隐藏层输入分布的作用, 因此, 对于不同的激活函数, BN 的最佳位置或许有些许不同.

BN 中 batch 的大小对网络性能有什么影响

由于 BN 在计算均值和方差时是在当前的 batch 上进行计算的, 因此, 当 batch 较小时, 求出来的均值和方差就会有较大的随机性, 从而导致效果下降, 具体来说, 当 batch 的大小低于 16 时, 就不建议使用 BN, 当 batch 低于 8 时, 网络的性能就会有非常明显的下降.

BN 中线性偏移的参数个数怎么计算的

对于 BN 层来说, 如果它的输入 shape 均为为 $(N, C, H, W)$, 则其输出 shape 也为 $(N, C, H, W)$, 即保持输入输出 shape 不变. BN 中的线性偏移参数 $\gamma$ 和 $\beta$ 的个数 与输入 shape 的通道数相同, 均为 $C$. PyTorch 中 $\gamma$ 和 $\beta$ 参数分别对应着weightbias, 下面是 BN 的声明.

1
torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

BN 中的使用的均值和方差是如何求得的

在训练阶段, 就是利用当前 batch 中的均值和方差进行计算.
在测试阶段, 采用的是网络中维护的滑动平均值进行计算的, 滑动平均值的维护方式是用当前的滑动平均值乘以一个 $decay$ 系数, 然后再加上 $(1 - decay)$ 倍的当前 batch 的统计值. $decay$ 决定了数值的更新速度, 通常 $decay$ 会设成一个非常接近于 1 的数, 比如, 0.99 或 0.999.

在训练的时候, 在最后的几个 epoch, 我们通常会固定住 BN 层的参数多训练一会, 这样可以确保 training 和 inference 之间的一致性

在多卡训练使用 BN 时, 需要注意什么问题

需要注意多卡之间的通信同步问题
如果对于 BatchNorm 的实现只考虑了 single GPU, 也就是说 BN 使用的均值和标准差是单个 GPU 计算的, 这相当于缩小了 mini-batch size. 目前很多主流框架已经支持多卡通信了

为什么不支持多卡通信: 至于为什么这样实现: (1) 因为没有 sync 的需求, 因为对于大多数 vision 问题, 单 GPU 上的 mini-batch 已经够大了, 完全不会影响结果. (2) 影响训练速度, BN layer 通常是在网络结构里面广泛使用的, 这样每次都同步一下 GPUs, 十分影响训练速度.

BN 在 Inference 阶段的加速

在 Inference 阶段, 我们已经确定了 BN 层所需的 mean, std, $\gamma$, $\beta$ 等参数, 不用再单独的计算这些参数的值, 又因为 BN 层的运算实际上就相当于两次 Scale (缩放平移) 操作, 因此当 BN 层和 Conv 层相邻时, 我们可以将 BN 层融合到 Conv 层中, 这对于 Conv 层来说只是改变了一些计算规则, 并没有增加卷积层的计算量, 因此可以起到一定的加速作用.

具体在融合时分为两个情况:

  1. Conv-BN:
    卷积层操作: $Y = \vec w X + \vec b$
    BN层操作: $Y’ = \gamma \frac{Y - \hat \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta$
    将上面两个公式融合后可变为: $Y’ = \gamma \frac{Y - \hat \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta = \gamma \frac{\vec w X + \vec b - \hat \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta = (\frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \vec w)\cdot X + \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}} \cdot \vec b - \frac{\gamma}{\sqrt{\sigma^2 + \epsilon}}\hat \mu + \beta = k\vec w \cdot X + \vec b’$
    可见, 融合后的 BN 就相当于是给卷积层多乘了一个常量, 同时多加了一个常量.

  2. BN-Conv: BN-Conv 无法进行融合, 始终都需要再单独执行 BN 的 Scale 操作, 然后再执行 Conv 的卷积操作

  3. Conv 和 BN 中间有激活层: 无法融合, 需要单独执行每一个网络层

使用 BN 时, 前一层的卷积网络需不需要偏置项, 为什么

使用 BN 时的前一层卷积网络可以不加偏置项(降低模型参数量)
当使用 BN 时, 无论加偏置还是不加偏置, 效果都是一样的, 公式证明如下:

bn 操作的关键一步可以简写为:

当给卷积层加上偏置后, 就变成了:

其中:

然后, 我们推导此时的 $y_i^b$, 将其化简为:

而 方差 = 平方的期望 - 期望的平方:

所以, 可以得到 $y_i^b = y_i$, 故而, 当使用 BN 时, 前面的卷积层无需使用偏置, 节省参数量. 另外, 在 BN 中的 $\beta$ 参数也可以起到一定的偏置作用.

当卷积层后跟batchnormalization层时为什么不要
https://blog.csdn.net/u010698086/article/details/78046671

CChttps://zhuanlan.zhihu.com/p/36222443

Group Normalization

简述 GN 的原理

BN 在很多任务上都取得了很好的效果(BN通常会在每一层都执行), 但是, BN 依赖于 batch 的平均值和方差, 这使得 batch size 的大小对BN的效果有较大的影响, 同时, 在测试阶段, 单个的图片无法提供良好的均值和方差进行归一化, 所以只能用整个数据集的均值和方差来代替, 通常会使用滑动平均来维护这两个变量, 也就是说, 在使用 BN 时, 如果数据集改变了, 则均值和方差就会有较大改变, 这就造成了训练阶段和测试阶段的不一致性, 由此也会带来一些问题. 因此, GN 为了解决 BN 对 batch 大小的依赖问题, 转而从另一个角度来进行归一化, GN 更像是介于 LN 和 IN 中间的一种归一化方法, 它会将通道分成不同的组, 同时在固定下标 N 的同时, 求取当前组内的均值和方差来进行归一化. 通过实验分析和论证, GN 可以取得不错的效果, 避免了对 batch 的依赖问题.

为什么 GN 效果好

GN 是从 LN 和 IN 中变化来的, 组的划分实际上可以看做是一种对数据分布的假设, 以 LN 为例, 它实际上假设了每张图片所有通道的特征都是同分布的, 而 GN 则是假设每个组的分布不同, 条件没有那么苛刻, 因此 GN 的表现力和包容性会更强, 而 IN 只依赖与独立的某一维, 没有探究不同通道之间特征的关联性. 相对于 BN 来说, 当 batch 的大小足够时, BN 的性能表现依然很不错, 因此, GN 充当的角色更像是当 batch 较小, 无法使用 BN 时的一种替代措施.

传统角度来讲,在深度学习没有火起来之前,提取特征通常是使用SIFT,HOG和GIST特征,这些特征有一个共性,都具有按group表示的特性,每一个group由相同种类直方图的构建而成,这些特征通常是对在每个直方图(histogram)或每个方向(orientation)上进行组归一化(group-wise norm)而得到。而更高维的特征比如VLAD和Fisher Vectors(FV)也可以看作是group-wise feature,此处的group可以被认为是每个聚类(cluster)下的子向量sub-vector。

从深度学习上来讲,完全可以认为卷积提取的特征是一种非结构化的特征或者向量,拿网络的第一层卷积为例,卷积层中的的卷积核filter1和此卷积核的其他经过transform过的版本filter2(transform可以是horizontal flipping等),在同一张图像上学习到的特征应该是具有相同的分布,那么,具有相同的特征可以被分到同一个group中,按照个人理解,每一层有很多的卷积核,这些核学习到的特征并不完全是独立的,某些特征具有相同的分布,因此可以被group。
导致分组(group)的因素有很多,比如频率、形状、亮度和纹理等,HOG特征根据orientation分组,而对神经网络来讲,其提取特征的机制更加复杂,也更加难以描述,变得不那么直观。另在神经科学领域,一种被广泛接受的计算模型是对cell的响应做归一化,此现象存在于浅层视觉皮层和整个视觉系统。
作者基于此,尝试使用了组归一化(Group Normalization)的方式进行模型训练, 结果意外发现训练效果非常不错, 显著优于BN、LN、IN等。

简述 BN, LN, IN, GN 的区别

SummaryOfComputerVision%2Fnorm.jpg

这些 Norm 方法的不同之处就在于计算均值和方差时使用的像素集合不同(如图2所示), 假设输入的 tensor 的 shape 为 $(N, C, H, W)$:

  • BN 是固定 $C$ 不变, 求固定 $C$ 时所有 $(N,H,W)$ 像素点的均值和方差, 这个均值和方差会用来归一化所有处于当前通道 $C$ 上的像素.
  • LN 是固定 $N$ 不变, 求固定 $N$ 时所有 $(C,H,W)$ 像素点的均值和方差, 这个均值和方差会用来归一化所有处于当前 $N$ 上的像素. 可以看出, 这里 LN 在求取均值和方差时, 由于固定了 $N$, 所以与 batch 的大小无关.
  • IN 是同时固定 $N$ 和 $C$ 不变, 求固定 $N$ 和 $C$ 时所示 $(H,W)$ 像素点的均值和方差.
  • GN 是介于 LN 和 IN 中的一种 Norm 方法, 它首先也是固定 $N$ 不变, 然后会将 $C$ 分成若干个 Group, 然后分别求取每个 Group 的均值和方差, 并对 Group 中的像素进行归一化

注意, 无论是哪种 Norm 方法, 它们使用的线性偏移的参数个数都等于通道 $C$ 的大小.

GN 中线性偏移的参数个数怎么计算的

对于 GN 层来说, 如果它的输入 shape 均为为 $(N, C, H, W)$, 则其输出 shape 也为 $(N, C, H, W)$, 即保持输入输出 shape 不变. GN 中的线性偏移参数 $\gamma$ 和 $beta$ 的个数 与输入 shape 的通道数相同, 均为 $C$. GN 除了需要确定输入层的通道数以外, 还需要确定 Goup 的数量. 下面给 PyTorch 中 GN 的声明.

1
torch.nn.GroupNorm(num_groups, num_channels, eps=1e-05, affine=True)

注意, GN, 在进行归一化时, 使用的mean和var是按照组进行划分的, 但是, 在进行偏移时的gamma和beta参数, 仍然是与 channel 数量保持一致的

SummaryOfComputerVision%2FGroupNorm_code.png

Layer Normalization

Instance Normalization

Switchable Normalization

感受野

感受野的计算公式

其中: $RF$ 表示特征感受野的大小, $l$ 表示当前层级, $\text{feature_stride}_l$ 表示当前特征图谱相对于原图的总 stride

如果有 dilated conv 的话, 计算公式为:

理论感受野和有效感受野的区别

特征的有效感受野(实际起作用的感受野)实际上是远小于理论感受野的.

以一个两层 kernel_size = 3, stride = 1 的网络为例,该网络的理论感受野为 5,计算流程可以参见下图。其中 $x$ 为输入,$w$ 为卷积权重,$o$为经过卷积后的输出特征。

很容易可以发现,$x_{1,1}$ 只能影响第一层 feature map 中的 $o_{1, 1}^1$;而 $x_{3,3}$ 会影响第一层 feature map 中的所有特征,即 $o^1_{1,1}, o^1_{1,2}, o^1_{1,3}, o^1_{2,1}, o^1_{2,2}, o^1_{2,3}, o^1_{3,1}, o^1_{3,2}, o^1_{3,3}$。

第一层的输出全部会影响第二层的 $o^2_{1,1}$

于是 $x_{1,1}$ 只能通过 $o^1_{1,1}$ 来影响 $o^2_{1,1}$;而 $x_{3,3}$ 能通过 $o^1_{1,1}, o^1_{1,2}, o^1_{1,3}, o^1_{2,1}, o^1_{2,2}, o^1_{2,3}, o^1_{3,1}, o^1_{3,2}, o^1_{3,3}$ 来影响 $o^2_{1,1}$。显而易见,虽然 $x_{1,1}$ 和 $x_{3,3}$ 都位于第二层特征感受野内,但是二者对最后的特征的影响却大不相同,输入中越靠感受野中间的元素对特征的贡献越大。

SummaryOfComputerVision%2Frf1.jpg

目标检测中的 anchor 的设置和感受野的大小之间有什么关系?

现在流行的目标检测网络大部分都是基于anchor的,比如SSD系列,v2以后的yolo,还有faster rcnn系列。

基于anchor的目标检测网络会预设一组大小不同的anchor,比如32x32、64x64、128x128、256x256,这么多anchor,我们应该放置在哪几层比较合适呢?这个时候感受野的大小是一个重要的考虑因素。

放置anchor层的特征感受野应该跟anchor大小相匹配,感受野比anchor大太多不好,小太多也不好。如果感受野比anchor小很多,就好比只给你一只脚,让你说出这是什么鸟一样。如果感受野比anchor大很多,则好比给你一张世界地图,让你指出故宫在哪儿一样。

全连接层

全连接层的作用是什么

https://www.zhihu.com/question/41037974

  1. 最直观的作用, 起到 “分类器” 的作用. 通常在网络的最后, 会利用全连接层将网络学习到的 “分布式特征表示” 映射到 样本标记空间. 在实际使用中, 全连接层也可以利用卷积操作来实现, 如果前一层是全连接层, 那么可以把前一层的全连接层当做是 $h=1, w=1, c = len(FC)$ 的特征图谱, 然后利用核大小为 $1\times 1$, 通道数为前一层神经元个数的卷积层进行计算, 卷积核的个数根据当前层的神经元个数决定. 如果前一层是卷积层, 则只需核的大小设置为前一层的特征图谱的大小, 进行全局卷积即可, 核的通道数由前一层的卷积结果决定, 核的个数由当前层的神经元个数决定.
  2. 特征融合, 全连接的任意一个神经元, 都能够 “看到” 前一层网络层输出的所有特征信息(FC 认为下一层的输出与上一层所有输入都有关, 实际上这样很容易 overfitting), 全连接层会根据这些信息, 决定它当前某一个神经元的输出. 这样也就弥补了卷积层只能 “看到” 局部信息的缺点.
  3. 不太直观的作用, 目前由于 FC 存在大量的参数冗余, 所有大多数时候我们会用全局平均池化来代替 FC. 但是我之前有看过一篇论文说 FC 的参数冗余也并不是一无是处, 它可以在一定程度上保证模型的迁移能力, 当原模型和目标数据集相差较大的时候, 使用 FC 的模型比不使用 FC 的模型的迁移效果好. 原因可能是冗余的参数对于特征的表示可能更加丰富.

将全连接层转换成卷积层由什么好处

https://www.cnblogs.com/liuzhan709/p/9356960.html

  1. 可以接受更多尺寸的图片输入, 我们只需要固定网络的通道数符合要求, 然后利用卷积层即可完全最终的分类.
  2. 高效, 当我们需要在一张图片上以一定大小的滑动窗口进行多次计算时, 由于这些窗口之间有大量的重合区域, 因此直接使用全连接层会造成会多的计算浪费, 而卷积操作在大多数框架中都得到了性能优化, 十分擅长处理这种操作, 因此在时间上更占优势.

推导两层全连接网络的反向传播公式

https://zhuanlan.zhihu.com/p/39195266

全连接层失宠原因之一: 目前大多数的任务,如目标检测,或是分割,并不要求提取全图特征,只需要提取能够覆盖目标物体的大小的感受野内特征即可。尤其是小物体检测问题,感受野很小即可,如果还去接全连接提取全图特征,我们待检测的目标会被淹没在和其它背景的平均特征之中变得不可识别。

卷积层

https://github.com/vdumoulin/conv_arithmetic

卷积层的计算公式

不带 Dilation 的计算:

带有 Dilation 的计算:

卷积层的作用?

简述 1x1 卷积层的作用

  • 改变特征图谱的深度: 通常用作降维
  • xception 和 mobilenet 系列将其用作解耦: 将 cross-channel correlation 和 spatial correlation 的学习进行解耦. 大大降低计算量
  • 实现了跨通道的信息组合: 使用 1x1 卷积核,实现降维和升维的操作其实就是channel间信息的线性组合变化, 3x3 ,64channels的卷积核前面添加一个 1x1 ,28channels的卷积核,就变成了 3x3 ,28channels的卷积核,原来的64个channels就可以理解为跨通道线性组合变成了28channels,这就是通道间的信息交互。因为 1x1 卷积核,可以在保持feature map尺度不变的(即不损失分辨率)的前提下大幅增加非线性特性(利用后接的非线性激活函数),把网络做的很deep,增加非线性特性。

卷积操作的本质特性包括稀疏交互和参数共享, 具体解释这两种特性及其作用

稀疏连接(稀疏交互):

  • 定义:传统的全连接网络,每一个输出都与每一个输入单元产生交互,卷积使用了稀疏交互:每个输出神经元只与前一层的特定局部区域内的神经元产生交互
  • 好处:
    • 参数更少,降低模型的复杂度,防止过拟合
    • 提高模型的统计效率,原本一幅图像只能提供少量特征,现在每个像素区域都可以提供一部分特征

参数共享:
定义:在模型的不同模块中(也可以说是多个函数中)使用相同的参数。也可以叫作一个网络含有绑定的权重。
传统的全连接网络中,在计算一层的输出时,权重矩阵的每一个元素只使用一次,乘以输入的一个元素之后,再也不会用到了。而在卷积神经网络中,卷积核的每个元素将作用于每一次局部输入的特定位置上。
只需要学习一个参数集合,而不是对于每一个位置都学习一个单独的参数集合。

卷积层实现如何优化

https://jackwish.net/convolution-neural-networks-optimization.html

im2col 优化

给定一个卷积层 C in x C out x H k x W k, 以及输入 feature map C in x H x W,
im2col
Mat A: (H x W) x (C in x H k x W k )
Mat B: (C in x H k x W k )x (C out )

一种比较方便也是比较偷懒的卷积层实现方法都是将图片或者特征图谱利用 im2col 方法展开成矩阵, 将卷积操作变成通用矩阵乘法(GEMM), 然后利用 cuBLAS 或者 OpenBLAS 的库函数进行计算(这些库里面的矩阵乘法是经过高度优化的, 所以速度也不慢).

具体来说, 对于任意的输入图谱, 根据卷积核的大小在特征图谱上获得一个 patch, 将这个 patch 里面的元素拿出来变成矩阵的一列, 按照卷积操作, 取出所有的 path 组成一个新的矩阵. 然后将卷积核展开成一个矩阵, 矩阵的每一行都是卷积核中的元素, 总共的行数和输出图谱的通道数相关. 这样, 卷积的计算操作就变成了普通的矩阵乘法.

这里可以看出, 对于卷积核大于 1 的卷积层来说, 我们需要按照卷积核的大小对图谱重新进行排列. 但是, 当卷积核大小为 1 时, 我们就无需排列, 直接将其展开即可. 这也解释了 MobileNet 中提到的 $1\times 1$ 卷积在实现上执行速度很快的原因.

im2col+GEMM 的卷积实现方法有一个很明显的问题就是, 会存储大量的冗余元素, 使得内存消耗比较大.

空间组合优化算法

空间组合主要是采用了分治的思想, 它基于空间特性将卷积计算划分为若干份, 分别处理, 虽然划分后的计算总量保持不变, 但是计算小矩阵时的访问内存的局部性更好, 可以借由计算机存储层次结果获得性能提升.
对于不同规模的卷积, 寻找合适的划分方法不是一件容易的事情. 该划分也可以通过 AutoTVM 自动化来完成.

Winograd 优化算法

量化神经网络的优化方法

概念, 简介缓冲区, 向量化卷积计算, 卷积计算工作流

参考

其他计算卷积的方法:

  • FFT: 大卷积核时使用, 时域卷积等于频域相乘, 因此可以将问题转化成简单的乘法问题. cuFFT
  • Winograd: 据说在 GPU 上效率更高, 貌似是针对 $2\times2$ 和 $3\times 3$ 的卷积核专门使用的?
  • NNPACK: FFT 和 Winograd 方法的结合
  • MEC(17年): 一种内存利用率高且速度较快的卷积计算方法, http://cn.arxiv.org/pdf/1706.06873v1. 主要改进了 im2col+GEMM 的策略, 目的主要是减少内存消耗的同时顺便提升速度. 由于同样可以利用现有的矩阵运算库, 因此算法的实现难度并不大.

CNN 基础之卷积及其矩阵加速 http://shuokay.com/2016/06/08/convolution

Winograd 方法快速计算卷积 http://shuokay.com/2018/02/21/winograd/

https://blog.csdn.net/antkillerfarm/article/details/78829889

https://blog.csdn.net/xiaoxiaowenqiang/article/details/82050354

BLAS 接受, 矩阵乘法优化 https://www.leiphone.com/news/201704/Puevv3ZWxn0heoEv.html

im2col 讲解: https://blog.csdn.net/Mrhiuser/article/details/52672824

实现矩阵乘法并简述其优化方法

https://jackwish.net/gemm-optimization.html

矩阵乘法代码实现(三重循环):

1
2
3
4
5
6
7
8
9
10
11
12
13
def matrixMul(A, B):
m = len(A)
p = len(A[0])
pp = len(B)
n = len(B[0])
if p != pp:
raise ValueError("error input")
C = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
for k in range(p):
C[i][j] += A[i][p] * B[p][j]
return C

SummaryOfComputerVision%2Fgemm1.jpg

对矩阵乘法进行优化的方法可分为两类:

  • 基于算法分析的方法: 根据矩阵乘法的计算特性, 从数学角度优化, 典型的算法包括 Strassen 算法和 Coppersmith-Winograd 算法
  • 基于计算机系统优化的方法: 根据计算机存储系统的层次结构特性, 选择性的调整计算顺序, 主要有循环拆分向量化, 内存重排等.

SummaryOfComputerVision%2Fgemm2.jpg

Strassen 算法

SummaryOfComputerVision%2Fgemm_strassen.jpg

完全应用 Strassen 算法的一个局限是其要求矩阵乘的规模为 $2^n$,这在现实情况中不容易满足。一种解决方法是将规模分解为 $2^n X$ 其中 $X$ 无法被 2 整除,那么可以应用 Strassen 算法不断递归拆分计算直到小矩阵规模为 $X$。此时可以用朴素算法计算小矩阵;或者将 $X$ 补零为 $2^n$ 再继续应用 Strassen 算法(亦可直接对大矩阵补零)。最终的性能取决于实现方法和运行的硬件平台。

有时在实际使用 Strassen 算法时,耗时不但没有减少,反而剧烈增多,在 $n=512$ 时计算时间就无法忍受,效果没有朴素矩阵算法好。网上查阅资料,现罗列如下:

  1. 采用Strassen算法作递归运算,需要创建大量的动态二维数组,其中分配堆内存空间将占用大量计算时间,从而掩盖了Strassen算法的优势
  2. 于是对Strassen算法做出改进,设定一个界限。当n<界限时,使用普通法计算矩阵,而不继续分治递归。需要合理设置界限,不同环境(硬件配置)下界限不同
  3. 矩阵乘法一般意义上还是选择的是朴素的方法,只有当矩阵变稠密,而且矩阵的阶数很大时,才会考虑使用Strassen算法。

Winograd 算法

Coppersmith–Winograd 算法的思想和 Strassen 算法类似。其证明过程比较复杂,使用的定理太多,这里就不再介绍(实际上是没看懂…)

Winograd 将矩阵乘法的复杂度降到了 $O(n^{2.376})$, 从上图2可以看出, 到目前为止, Winograd 仍然是最优的一类优化算法, 也是在各个深度学习框架中广泛使用的一类算法.

通过划分降低访存次数

SummaryOfComputerVision%2Fgemm3.jpg

首先给出朴素矩阵乘法的计算过程(半伪码):

1
2
3
4
5
for m in range(M):
for n in range(N):
c[m][n] = 0
for k in range(K):
C[m][n] += A[m][k] * B[k][n]

从上面的代码我们可以看出:

  • 总共计算操作数为 $2_{mla} MNK$, 其中, $M, N, K$ 分别指代三重循环的执行次数, $2_{mla}$ 指代最内层循环执行的 Multiply-Adds 的次数为 2(一次乘法, 一次加法)
  • 内存访问操作次数为 $4MNK$, 其中 $4 = 2 (C 读取, C 存储) + 1 (A 读取) + 1 (B 读取)$

我们以图形化的方式来介绍如何通过划分矩阵来实现降低访存次数的优化.

首先我们对 $N$ 维度进行划分, 也就是说, 我们将矩阵 C 上的输出划分成 $1 \times 4$ 的小块, 这样, 在计算该输出时, 就需要使用矩阵 A 的 一行, 和矩阵 B 的 四列, 恰如下图所示

SummaryOfComputerVision%2Fgemm4.jpg

要想实现上面的操作, 我们就需要将 $N$ 上的循环分出一部分到最内侧去计算, 伪代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
for m in range(M):
for n in range(0, N, 4): # 注意这里的步长变成了 4
c[m][n+0] = 0
c[m][n+1] = 0
c[m][n+2] = 0
c[m][n+3] = 0
for k in range(K):
C[m][n+0] += A[m][k] * B[k][n+0]
C[m][n+1] += A[m][k] * B[k][n+1]
C[m][n+2] += A[m][k] * B[k][n+2]
C[m][n+3] += A[m][k] * B[k][n+3]

可以看出, 展开后的计算操作总数仍然是 $2MNK$, 这一点在将降低访存优化方法中一直不变.
但是我们通过简单的观察即可发现, 上述伪代码最内层的计算使用的矩阵 A 元素是一直的, 因此我们可以将 A[m][k] 读取到寄存器中(代码中未体现), 从而实现 4 次的数据复用. 进行这样的优化后, 内存的访问操作数量就从 $4MNK$ 变成了 $(2+1+\frac{1}{4}) MNK$, 其中, $\frac{1}{4}$ 是对 A 优化的结果.

类似的, 我们可以继续拆分 $M$ 维度, 从而在最内层循环中进行 $4\times 4$ 大小的块计算, 如下图所示:

SummaryOfComputerVision%2Fgemm5.jpg

同样的, 我们给出上图的计算伪码, 注意, 这里的[0~3]是对[0], [1], [2], [3]采取的缩写:

1
2
3
4
5
6
7
8
9
10
11
for m in range(0, M, 4): # 注意, 现在 m 的步长也变成了 4
for n in range(0, N, 4): # 注意这里的步长变成了 4
c[m+0][n+0~3] = 0
c[m+1][n+0~3] = 0
c[m+2][n+0~3] = 0
c[m+3][n+0~3] = 0
for k in range(K):
C[m+0][n+0~3] += A[m+0][k] * B[k][n+0~3]
C[m+1][n+0~3] += A[m+1][k] * B[k][n+0~3]
C[m+2][n+0~3] += A[m+2][k] * B[k][n+0~3]
C[m+3][n+0~3] += A[m+3][k] * B[k][n+0~3]

可以看到, 我们同样可以将 B 中的元素复用四次, 这样, 我们就通过 $4\times 4$ 的划分, 将输入数据的访存次数缩减到了 $2MNK + \frac{1}{4}MNK + \frac{1}{4}MNK = (2+\frac{1}{2})MNK$. 这相对于最开始的 $4MNK$ 已经得到了 1.6 倍的改进.

接下来, 我们还可以继续对 $K$ 维度进行划分, 也就是令 k 的步长也变成 4, 如下图6所示

SummaryOfComputerVision%2Fgemm6.jpg

对 $K$ 进行划分后, 我们每次最内层循环会计算出 $\frac{4}{K}$ 的部分和, 而不是每次都只计算出 $\frac{1}{K}$ 的和, 在对 $K$ 展开时, 我们可以将部分和累加在寄存器中, 在最内层循环结束时才一次性写到 C 的内存中, 这样就可以在 $K$ 的维度上降低访存次数, 具体的伪码如下所示:

1
2
3
4
5
6
7
8
9
10
11
for m in range(0, M, 4): # 注意, 现在 m 的步长也变成了 4
for n in range(0, N, 4): # 注意这里的步长变成了 4
c[m+0~3][n+0~3] = 0
c[m+0~3][n+0~3] = 0
c[m+0~3][n+0~3] = 0
c[m+0~3][n+0~3] = 0
for k in range(0, K, 4): # 可以看到, 现在 k 的步长也变成了 4
C[m+0~3][n+0~3] += A[m+0~3][k+0] * B[k+0][n+0~3]
C[m+0~3][n+0~3] += A[m+0~3][k+1] * B[k+1][n+0~3]
C[m+0~3][n+0~3] += A[m+0~3][k+2] * B[k+2][n+0~3]
C[m+0~3][n+0~3] += A[m+0~3][k+3] * B[k+3][n+0~3]

注意上面代码中总的 Multiply-Adds 次数依然是 $2MNK$, 只不过有一部分计算放在了最内层循环中而已. 而我们通过对 $M, N, K$ 三个维度的划分, 成功的将内存访问次数降低到了 $2\times \frac{1}{4}MNK + \frac{1}{4}MNK + \frac{1}{4}MNK = MNK$, 相对于原始实现提升了 4 倍.

上述的优化方法降低了内存访问次数, 但是一条计算指令只能完成一次乘加操作, 效率较低, 因此可以通过向量化优化指令条数, 如下图所示…(这一部分没太看懂, 更详细的讲解请看原文 https://jackwish.net/gemm-optimization.html)

SummaryOfComputerVision%2Fgemm7.jpg

优化内存布局进一步降低访存

(这一部分没太看懂, 更详细的讲解请看原文 https://jackwish.net/gemm-optimization.html)
上一小节列出的是在输入输出原有内存布局上所做的优化。在最后向量化时,每次内存访问都是四个元素。当这些元素为单精度浮点数时,内存大小为 16 字节,这远小于现代处理器高速缓存行大小(Cache line size)——后者一般为 64 字节。在这种情况下,内存布局对计算性能的影响开始显现。

SummaryOfComputerVision%2Fgemm8.jpg

SummaryOfComputerVision%2Fgemm9.jpg

根据矩阵的尺寸和稀疏程度的不同, 也有不同的优化方法, 对于特定尺寸的卷积核来说, 也存在有特定的优化方法.

神经网络量化中的矩阵乘法优化

(这一部分没太看懂, 更详细的讲解请看原文 https://jackwish.net/gemm-optimization.html)

SummaryOfComputerVision%2Fgemm10.jpg

SummaryOfComputerVision%2Fgemm11.jpg

SummaryOfComputerVision%2Fgemm12.jpg

卷积核的大小如何确定

卷积核的大小决定了该卷积核在上一层特征图谱上的感受野大小,在确定卷积核的大小时有以下原则(并非通用性原则,实际设计时需要结合具体情况决定):在网络的起始层,选用较大的卷积核(7×7),这样可以使得卷积核“看到”更多的原图特征;在网络中中间层,可以用两个3×3大小的卷积层来代替一个5×5大小的卷积层,这样做可以在保持感受野大小不变的情况下降低参数个数,减少模型复杂度;通常使用奇数大小的卷积核,原因有二,一是可以更加方便的进行padding,二是奇数核相对于偶数核,具有天然的中心点,并且对边沿、对线条更加敏感,可以更有效的提取边沿信息

池化层

池化层的作用是什么

  1. 降低优化难度和参数个数: 池化层可以降低特征图谱的维度,从而降低网络整体的复杂度,不仅可以加速计算,也能起到 一定的防止过拟合的作用(只保留最大值, 相于不保留非关键信息).
  2. 增大感受野: 当没有池化层时,一个3×3,步长为1的卷积,它输出的一个像素的感受野就是3×3的区域,再加一个stride=1的3×3卷积,则感受野为5×5。当使用pooling后,很明显感受野迅速增大,感受野的增加对于模型的能力的提升是必要的, 当然还有其他更有效的提升感受野的方法, 只不过池化对于感受野的提升也有一定作用.
  3. 增加网络平移, 伸缩, 旋转不变性: 池化层只会关注核内的值,而不会关注该值的位置,因此,当目标位置发生移动时,池化层也可以得到相同的结果,所以池化层在一定程度上可以增加CNN网络的平移不变性. 同时一定程度上也具有旋转不变性(旋转可以看做是特殊的平移)和尺度不变性(与具体的缩放插值方式有关, 但通常也能保持池化输出值不变)

池化层反向传播的梯度时如何求的

无论是最大池化还是均值池化, 都没有需要学习的参数。因此,在卷积神经网络的训练中,Pooling层需要做的仅仅是将误差项传递到上一层,而没有梯度的计算。

  • 对于 mean pooling,backward的时候,把一个值分成四等分放到前面2x2的格子里面就好了。如下

    1
    2
    forward: [1 3;2 2] -> [2]
    backward: [2] -> [0.5 0.5;0.5 0.5]
  • 对于 max pooling,backward的时候把当前的值放到之前那个最大的位置,其他的三个位置都设置成0。如下

    1
    2
    forward: [1 3;2 2] -> 3
    backward: [3] -> [0 3;0 0]

SummaryOfComputerVision%2Fmax_pool_forward.jpg

SummaryOfComputerVision%2Fmax_pool_backward.jpg

SummaryOfComputerVision%2Fmean_pool_forward.jpg

SummaryOfComputerVision%2Fmean_pool_backward.jpg

SummaryOfComputerVision%2Fpool_1.jpg

SummaryOfComputerVision%2Fpool_2.jpg

最大池化和平均池化有什么异同, 分别适用于什么场景

最大池化是取核中的最大值, 平均池化是取核中的最小值.

最大池化多用于提取特征, 因为我们需要提取出物体中最显著的若干特征来帮助我们感知和识别物体, 而最大池化的计算规则将是保留特征相应最大的值, 就像是人眼一样, 我们往往只通过一些很明显的特征就可以判断出一个物体的种类, 最大池化多多少少也有这一层含义. 举一个例子, 比如两处不同的位置进行 mean pooling, 一处的最大值是100, 然后经过mean pooling 之后, 它的输出值变成了 20, 而另一处的最大值是50, 然后经过mean pooling 之后它的输出值也是20, 这样, 对于不同的特征, 我们却得到了重复的结果, 这实际上是一种信息冗余, 也可以认为是一种特征丢失. 在使用中, 由于网络内部的大部分时候都是在进行特征提取, 因此 maxpooling 更常用.

平均池化的作用是可以聚合核内的所有特征信息, 因此通常在整个网络的最后, 我们会使用全局平均池化来整合整体的特征, 此时, 因为特征图谱已经是经过高度提取抽象后的, 所以, 我们不能只关注那些最大的值, 图谱上的每一个值所对应的特征我们都需要综合考虑, 这一点和全连接层本身的计算规则相符合, 因为全连接中的每一个神经元都能够 “看到” 全一层所有的输出, 使得全连接可以通过整合所有的特征信息来决定最终的分类结果, GAP 也能够起到相类似的作用.

全局平均池化层(GAP)的作用

  1. GAP 的第一个作用就是可以替代全连接层, 根据全连接层本身的计算规则可知, 使用全局平均池化, 将池化核的个数设置为全连接的神经元个数, 就可以获得相同维度的计算结果.
  2. 全连接层本身用于包含大量的参数, 因此在一定程度上, 全连接层上容易产生过拟合现象, 从而影响整个模型的泛化能力. 而 GAP 本身不包含任何参数, 它直接在 feature map 和样本标签空间内建立了联系, 使得每一个 feature map 本身具有的含义更加清晰, 也就是一个 feature map 对应一个 label. 明确学习目标, 简化学习过程.
  3. 全连接层通常需要 dropout 来避免过拟合, 而 GAP 本身就可以看做是一种正则, 因此可以使用模型的泛化性能更好

反卷积层

https://github.com/vdumoulin/conv_arithmetic

又名 Transposed Convolution, Deconvolution, Fractionally-strided convolution.

https://zhuanlan.zhihu.com/p/48501100

正向卷积的实现过程

假设输入图像尺寸为 $4 \times 4$, 元素矩阵为:

卷积核尺寸为 $3\times 3$, 元素矩阵为:

假设正向卷积操作步长 stride = 1, 填充 padding = 0, 则按照卷积计算公式 $o = \frac{i + 2p - k}{s} + 1$, 输出图像尺寸为 $2\times 2$.

用矩阵乘法描述卷积

我们首先将 input 的元素矩阵展开成一个列向量 $X$:

然后我们把输出图像 output 的元素矩阵也进行展开, 形成向量 $Y$:

对于输入的元素矩阵 $X$ 和输出的元素矩阵 $Y$, 用矩阵运算来描述这个过程:

通过简单的推导, 我们可以知道这个矩阵 $C$ 的形式应该如下:

而反卷积的操作就是对这个矩阵进行逆运算, 即先将 $C$ 进行转置得到 $C^T$, 然后有 $Y$ 得到 $X$

注意, 这里我们只需要保证反卷积的输出尺寸和正卷积相对应即可, 也就是说反卷积的操作知识恢复了矩阵 $X$ 的尺寸的大小, 并不能回复 $X$ 的每个元素值.

反卷积和双线性插值的区别, 各自的优势

双线性插值, 计算速度快, 实现简单
反卷积, 具有学习参数, 可以学习相应特征

Deconvolution是目前争议比较多的方法,主要是名字上的争议,由于实现上采用转置卷积核的方法,所以有人说应该叫(transposed convolution),但是思想上是为了还原原有特征图,类似消除原有卷积的某种效果,所以叫反卷积(deconvolution).

反卷积和上采样+卷积的区别

反卷积计算公式

大体上相当于是卷积计算公式的逆过程, 不过额外多了 output_padding, 该参数代表反卷积操作完成后, 在输出的图谱外围额外添加的维度.

不带 dilation 的计算如下:

带有 dilation 的计算如下

空洞卷积

https://github.com/vdumoulin/conv_arithmetic

Dilated Convolution

空洞卷积的作用

可以在不引入额外计算量的前提下, 可以扩大感受野

训练问题

训练过程中遇到的问题及解决方案

网络结构篇

关于层数的定义: 值得是网络的深度. 通常只计算卷积层和全连接层, 有一种说法是层数是具有参数的网络层的个数, 不太准确, 因为 BN, SE 等也有参数, 但是通常不计入层数. 另外, 卷积层数不等于卷积个数, 例如 Inception bottleneck 结构, 拥有 9 个卷积层, 但是在计算网络层数时只算两层.
参数量, FLOPs: M = $10^6$, G = $10^9$
显存占用, 模型大小: MB = $2^{20}$, GB = $2^{30}$

InceptionV1:

  • 深度 22 (21 Convs + 1 FCs) = 1 Conv + 1 Conv + 1 Conv + 9 Inceptions(2) + 1 FCs
  • 层数 91 (86 FCs + 5 FCs)= 1 Conv + 1 Conv + 1 Conv + 9 Inceptions(9) + 侧枝(1 Conv + 2 FCs) + 侧枝(1 Conv + 2 FCs) + 1 FCs

InceptionV3(paper):

  • 深度 47 (46 Convs + 1FCs) = 6 Convs + 3 InceptionsA(3) + 5 Inceptions(5) + 2 Inception(3) + 1 FCs
  • 层数: 略
模型 层数(深度) 参数量 FLOPs 显存占用 (模型大小, BP 存储) img_size Acc@1 Acc@5
AlexNet 8 = 5 Convs + 3 FCs 61.1M 823.0M + 242MB (233, 8) - 56.432 79.194
VGGNet16 16 = 13 Convs + 3 FCs 138.4M 17.3G + 747MB (527, 218) - 71.636 90.354
VGGNet16_BN 16 = 13 Convs + 3 FCs 138.4M 17.3G + 747MB (527, 218) - 73.518 91.608
VGGNet19 19 = 16 Convs + 3 FCs 143.6M 21.9G 787MB (548, 238) - 72.080 90.822
VGGNet19_BN 19 = 16 Convs + 3 FCs 143.6M 21.9G 787MB (548, 238) - 74.266 92.066
ResNet50 50 = 1 Conv + 48 Convs + 1 FCs 25.5M 4.1G 384MB (97, 286) - 76.002 92.980
ResNet101 101 = 1 Conv + 99 Convs + 1 FCs 44.5M 7.8G 600MB (169, 429) - 77.438 93.672
ResNet152 152 = 1 Conv + 150 Convs + 1 FCs 60.2M 11.6G 836MB (229, 606) - 78.438 94.110
InceptionV1 (GoogLeNet) 22(深度) 13.0M 1.5G 144MB (49, 94) - 69.8 89.6
InceptionV3 47(深度) 27.1M 5.7G 328MB (103 + 224) 299 77.294 93.454
Xception 29 = 28 Convs + 1 FCs 22.8M 8.5G 887MB (87 + 798) 229 78.888 94.292
InceptionV4 42.6M 12.3G 715MB (162 + 551) 299 80.062 94.926
InceptionResNetV2 55.8M 16.7G 904MB (213 + 689) 299 80.170 95.234
ResNeXt50 50 25.0M 4.2G 457MB (95 + 361) -
ResNeXt101_32x8d 101 88.8M 16.5G 1111MB (338 + 772) - 78.188 93.886
MobileNetV1 28 = 27 Convs + 1 FC 4.2M 569M - 70.6
MobileNetV2 (原文)54 Convs = 1 + 51 + 2
(PT实现)53 = 1 + 51 Convs + 1 FC
3.5M 334.8M 166MB (13 + 153) - 71.88 90.29
ShuffleNetV1 50 = 1 + 48 Convs + 1 FC 1.9M 275.8M 69MB (7 + 62) -
ShuffleNetV2 51 = 1 + 48 + 1 Convs + 1 FC 2.3M 155.2M 57MB (8 + 48) - 69.36 88.32
DenseNet121 121 = 1 + 3 + 116 Convs + 1 FC 7.9M 2.9G 325MB (30 + 294) - 74.646 92.136
DenseNet161 161 = 1 + 3 + 156 Convs + 1 FC 28.7M 7.8G 647MB (109 + 536) - 77.560 93.798
SENet154 115.1M 20.8G 1517MB (439 + 1077) - 81.32 95.53

AlexNet

SummaryOfComputerVision%2Fnet_arch%2FAlexNet.jpg

AlexNet 的网络结构相对来说比较简单, 它包括 5 层卷积层, 3 层最大池化层, 以及 3 层全连接层. 池化层被分别放置在 conv1, conv2, 和 conv5 的后面. 虽然 AlexNet 结构简单, 但是由于全连接层的存在, 使得 AlexNet 的参数量较大, 大约有 6000w 个参数.

VGGNet

SummaryOfComputerVision%2Fnet_arch%2FVGGNet.jpg

VGGNet 的网络结构延续了 AlexNet 的设计思想. 将卷积层分成 5 段, 每一段之间通过池化层分隔开, 后面同样跟了 3 层全连接层, 同时他用多个小卷积核替换了 AlexNet 中的大卷积核, 可以在减少参数量的同时提高感受野的范围, 并且通过统建更深层的网络, 使得提取到的特征图谱的表征能力更强. VGGNet 比较常用的结构有 VGG16 和 VGG19. 二者的区别在于前者每个卷积段的卷积层数量是(2, 2, 3, 3, 3), 后者每个卷积段中的卷积层数量是(2, 2, 4, 4, 4).

InceptionV1 (GoogLeNet)

SummaryOfComputerVision%2Fnet_arch%2FInceptionV1_module.jpg

GoogLeNet 模型的核心思想是 卷积神经网络中的最优局部稀疏结构可以被现有的组件逼近和覆盖, 因此, 只要找到这个局部最优结构, 并在网络结构中重复使用它, 就可以进一步提升神经网络的拟合能力. 于是, InceptionV1 跳出了传统卷积神经网络的简单堆叠结构, 首次提出了 Inception 模块. Inception 模块综合了 1x1, 3x3, 5x5 这三种不同尺度的卷积核进行特征提取, 同时, 考虑到池化层的重要作用, 还综合了 3x3 大小的最大池化层. 并且, 还在 3x3 和 5x5 的卷积层之前, 以及池化层之后, 使用了 1x1 的卷积层来降低特征维度, 从而减少计算量.

SummaryOfComputerVision%2Fnet_arch%2FInceptionV1.jpg

以 Inception 模块为基本单元就可以构建出 IncetionV1 模型, 构建时仍然遵循了 5 个卷积段的段落形式, 段之间通过最大池化层分隔, 具体来说, 前两段使用的是传统的卷积层, 其中第一段是单层的 7x7 大小的卷积层, 第二段是两层较小尺寸的卷积层(1x1, 3x3)(上图没有写出 1x1), 后三段卷积段都是由 Inception 模块组成, 每一段使用的 Inception 模块数量分别为 2, 5, 2. 最后的分类层由全局平均池化层, 全连接层, Softmax 激活层组成. 另外, 由于网络结构较深, 因此, 为了防止梯度消失, InceptionV1 分别在 4a 和 4d 的 Inception 模块上添加了辅助侧枝分类器, 该分类器由一层平均池化层, 一层 1x1 卷积层, 两层全连接层和 Softmax 激活层组成.

简述一下 GoogLeNet 采用多个卷积核的原因

Inception Module这类结构非常看中模型在局部区域的拟合能力。它们认为:一张图像通常具有总体特征和细节特征这两类特征,一般小卷积核能够更好的捕捉一些细节特征,随着深层网络的小卷积不断计算下去,总体特征也会慢慢的被提炼出来(感受野慢慢增大),但是这样存在一个问题,那就是在如果只采用小卷积,那么网络结构的前段一般只有细节特征,后段才慢慢有一些总体特征(感受野增大),而我们希望这两方面的特征总是能够一起发挥作用,因此,Inception 模型考虑采用更多不同尺寸的卷积核来提取特征,并把这些特征连接起来,一起送到后面的网络中去计算,使得网络可以获取到更多的特征信息。

Inception 中为什么使用 1×1 卷积层

关于Inception Module,有一种很直接的做法就是将1×1,3×3,5×5卷积和3×3 max pooling直接连接起来,如 Inception module 中的 naive version 所示,但是这样的话就有个问题,那就是计算量增长太快了。

为了解决这个问题,文章在3×3和5×5的卷积之前,3×3max pooling之后使用了1×1卷积,使其输出的 feature map 的 depth 降低了,从而达到了降维的效果,抑制的过快增长的计算量。

Inception 中为什么使用全局平均池化层

  1. GAP 的第一个作用就是可以替代全连接层, 根据全连接层本身的计算规则可知, 使用全局平均池化, 将池化核的个数设置为全连接的神经元个数, 就可以获得相同维度的计算结果.
  2. 全连接层本身用于包含大量的参数, 因此在一定程度上, 全连接层上容易产生过拟合现象, 从而影响整个模型的泛化能力. 而 GAP 本身不包含任何参数, 它直接在 feature map 和样本标签空间内建立了联系, 使得每一个 feature map 本身具有的含义更加清晰, 也就是一个 feature map 对应一个 label. 明确学习目标, 简化学习过程.
  3. 全连接层通常需要 dropout 来避免过拟合, 而 GAP 本身就可以看做是一种正则, 因此可以使用模型的泛化性能更好

为什么使用侧枝

为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(辅助分类器)。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有裨益。而在实际测试的时候,这两个额外的softmax会被去掉。也就是说在测试的时候,只会用最后的softmax结果作为分类依据。

当时Inception网络还是太深了,不好训练,因此网络中还加了两个侧枝,通过中间层的feature map,来得到预测结果(有了ResNet的shortcut以后,这种侧枝用的比较少了)。

GoogLeNet 在哪些地方使用了全连接层

在两个侧枝使用了 Avgpool+conv+FC+FC+SoftmaxActivation 的结构,在最后一层使用了 GAP+FC+SoftmaxActivation的结构。

InceptionV2/3

简述 InceptionV2 相比于 GoogLeNet 有什么区别

InceptionV2 改进的主要有两点. 一方面加入了 BN 层, 减少了 Internal Covariate Shift 问题(内部网络层的数据分布发生变化), 另一方面参考了 VGGNet 用两个 $3\times 3$ 的卷积核替代了原来 Inception 模块中的 $5\times 5$ 卷积核, 可以在降低参数量的同时加速计算.

SummaryOfComputerVision%2Fnet_arch%2FInceptionV3_1.jpg

简述 InceptionV3 相比于 GoogLeNet 有什么区别

InceptionV3 最重要的改进是分解(Factorization), 这样做的好处是既可以加速计算(多余的算力可以用来加深网络), 有可以将一个卷积层拆分成多个卷积层, 进一步加深网络深度, 增加神经网络的非线性拟合能力, 还有值得注意的地方是网络输入从 $224\times 224$ 变成了 $299\times 299$, 更加精细设计了 $35\times 35$, $17\times 17$, $8\times 8$ 特征图谱上的 Inception 模块.
具体来说, 首先将第一个卷积段的 $7\times 7$ 大小的卷积核分解成了 3 个 $3\times 3$ 大小的卷积核. 在第二个卷积段也由 3 个 $3\times 3$ 大小的卷积核组成. 第三个卷积段使用了 3 个 Inception 模块, 同时将模块中的 $5\times 5$ 卷积分解成了两个 $3\times 3$ 大小的卷积. 在第四个卷积段中, 使用了 5 个分解程度更高的 Inception 模块, 具体来说, 是将 $n\times n$ 大小的卷积核分解成 $1\times n$ 和 $n\times 1$ 大小的卷积核, 在论文中, 对于 $17\times 17$ 大小的特征图谱, 使用了 $n = 7$ 的卷积分解形式. 在第五个卷积段中, 面对 $8\times 8$ 大小的特征图谱, 使用了两个设计更加精细的 Inception 模块. 它将 $3\times 3$ 大小的卷积层分解成 $1\times 3$ 和 $3\times 1$ 的卷积层, 这两个卷积层不是之前的串联关系, 而是并联关系.

SummaryOfComputerVision%2Fnet_arch%2FInceptionV3.jpg

  1. 在网络的浅层要避免过度的压缩特征信息, 特征图谱的尺寸应该温和的降低;
  2. 高维的特征信息更适合在本地进行处理, 在网络中逐渐增加非线性激活层, 这样可以使得网络参数减少, 训练速度更快;
  3. 低维信息的空间聚合不会导致网络表达能力的降低, 因此, 当进行大尺寸的卷积之前, 可以先对输入进行进行降维处理, 然后再进行空间聚合操作;
  4. 网络的深度和宽度需要反复权衡, 通过平衡网络中每层滤波器的个数和网络的层数使用网络达到最大性能.

Xception

简述 Xception 的特点

Xception 就是利用 Separable Conv 搭建的网络, Separable Conv 就是将普通的卷积按照通道个数进行分组, 使得每一组内只包含一个通道, 将 spatial learning 和 cross depth learning 解耦.

Inception模块是一大类在ImageNet上取得顶尖结果的模型的基本模块,例如GoogLeNet、Inception V2/V3和Inception-ResNet。有别于VGG等传统的网络通过堆叠简单的3x3卷积实现特征提取,Inception模块通过组合1x1,3x3,5x5和pooling等结构,用更少的参数和更少的计算开销可以学习到更丰富的特征表示。通常,在一组特征图上进行卷积需要三维的卷积核,也即卷积核需要 同时学习空间上的相关性和通道间的相关性。将这两种相关性显式地分离开来,是Inception模块的思想之一:Inception模块首先使用 1x1 的卷积核将特征图的各个通道映射到一个新的空间,在这一过程中学习通道间的相关性;再通过常规的3x3或5x5的卷积核进行卷积,以同时学习空间上的相关性和通道间的相关性。
但此时,通道间的相关性和空间相关性仍旧没有完全分离,也即3x3或5x5的卷积核仍然是多通道输入的, 那么是否可以假设它们们可以被完全分离?显然,当所有3x3或5x5的卷积都作用在只有一个通道的特征图上时,通道间的相关性和空间上的相关性即达到了完全分离的效果(解耦)。

若将Inception模块简化,仅保留包含3x3的卷积的分支, 就如下图所示

Xception%2Ffig2.png

再将所有 1x1 的卷积进行拼接, 就如下图3所示, 进一步增多3x3的卷积的分支的数量,使它与1x1的卷积的输出通道数相等, 就如下图4所示了.

Xception%2Ffig34.png

此时每个3x3的卷积即作用于仅包含一个通道的特征图上,作者称之为“极致的Inception(Extream Inception)”模块,这就是Xception的基本模块。事实上,调节每个3x3的卷积作用的特征图的通道数,即调节 3x3 的卷积的分支的数量与1x1的卷积的输出通道数的比例,可以实现一系列处于传统Inception模块和“极致的Inception”模块之间的状态。

运用“极致的Inception”模块,作者搭建了Xception网络,它由一系列SeparableConv(即“极致的Inception”)、类似ResNet中的残差连接形式和一些其他常规的操作组成, 如下图所示. Xception

Xception%2Ffig5.png

Xception 中使用的 “extreme” inception module 的 MobileNet 中使用的传统的 Depthwise Separable Conv 有什么区别?

区别主要有两点:

  1. 在 MobileNet 中的 Separable Conv 中, 是先进行 Depthwise Conv, 再进行 1x1 的 Pointwise Conv; 而 Xception 主要是优化 InceptionV3, 因此它是先进性 1x1 的卷积, 然后再进行 Depthwise Conv; (顺序的区别影响不大, 因为这些操作是堆叠组合的, 相对位置差不多)
  2. 在 Xception 中, 作者发现在 1x1 的卷积之后不使用 ReLU 等激活函数时具有较好的效果. 这一点在 MobileNetV2 中被详细分析过, 主要是因为通道在经过 1x1 降维后, 过少的通道数目容易被 ReLU 激活函数破坏其携带的特征信息, 因此在 1x1 降维后, 通常不是激活数.(ShuffleNet 也是如此, 只不过他的通道数是在 depthwise 的时候很小, 所以没有在 depthwise conv 之后使用 relu, 但是疑惑的是为什么第一个 1x1 GConv 的输出通道数也很低, 但是仍然用了 ReLU?)

面对通道数较低的特征图谱, 使用 ReLU 会因为低维数据坍塌现象, 即 ReLU 有可能会让某个通道的值全为 0, 从而维度降低, 这些低纬度时会引起信息缺少, 而其他的激活函数又存在梯度消失问题, 所以没有使用激活

InceptionV4 and Inception ResNet

Inception 系列的缺点: 模块过于复杂, 人工设计的痕迹太重了.

InceptionV4 做了哪些改进

InceptionV4 使用了更复杂的结构重新设计了 Inception 模型中的每一个模块. 包括 Stem 模块, 三种不同的 Inception 模块以及两种不同的 Reduction 模块. 每一个模块的具体参数设置均不太一样, 但是整体来说都遵循的 卷积分解和空间聚合 的思想.

SummaryOfComputerVision%2Fnet_arch%2FInceptionV4.jpg

Inception-ResNet-v1 做了哪些改进

Inception ResNet v1 网络主要被用来与 Inception v3 模型性能进行比较, 因此它所用的 Inception 子网络的计算相对常规模块有所减少, 这是为了保证使得它的整体计算和内存消耗与 Inception v3 近似, 如此才能保证公平性. 具体来说, Inception ResNet v1 网络主要讲 ResNet 中的残差思想用到了 Inception 模块当中, 对于每一种不太的 Inception 模块, 都添加了一个短接连接来发挥残差模型的优势.

SummaryOfComputerVision%2Fnet_arch%2FInception-ResNet-v1.jpg

Inception-ResNet-v2 做了哪些改进

Inception ResNet v2 主要被设计来探索残差模块用于 Inception 网络时所尽可能带来的性能提升. 因此它是论文给出的最终性能最高的网络设计方案, 它和 Inception ResNet v1 的不同主要有两点, 第一是使用了 InceptionV4 中的更复杂的 Stem 结构, 第二是对于每一个 Inception 模块, 其空间聚合的维度都有所提升. 其模型结构如下所示:

SummaryOfComputerVision%2Fnet_arch%2FInception-ResNet-v2.jpg

ResNet

SummaryOfComputerVision%2Fnet_arch%2FResNet.jpg

简单介绍一下 ResNet

SummaryOfComputerVision%2Fnet_arch%2FResNet_block.jpg

ResNet 的网络结构依然遵循经典的五段式, 其中第一段是一个比较大的卷积层(7x7, 64, s2), 主要用来获取最初的特征图谱, 后四段是由不同数量的残差模块组成的, 残差模块有两种, 分别为基本的 ResNet Block(上图左侧) 和经过 1x1 卷积降维的 Bottleneck(上图右侧). 在 ResNet50 以上的通常都用 Bottleneck, 因为不仅层数更深, 同时参数量也更少, 提取特征的能力更强一些.
每一段使用的残差模块的数量都不同一样, 深层残差网络的 残差模块主要在 conv4 大量堆叠.

简述 ResNet 的提出动机

首先文章提出了一个假设:
有一个L层的深度神经网络, 如果我们在上面加入一层, 直观来讲得到的L+1层深度神经网络的效果应该至少不会比L层的差. 因为可以简单的学习出最后一层为前一层的恒等映射, 并且其它层参数设置不变.(说明是这种更深的网络是存在是的性能不下降的解的)
但是, 通过实验发现, 当网络层数加深时, 网络的性能会下降(说明后面几层网络层没有学习到恒等映射这个解), 也就是所谓的”模型退化”问题,

观察这个现象后, 作者认为产生模型退化的根本原因很大程度上也许不在于过拟合, 而在于梯度消失问题. 为了解决模型退化问题, 作者基于以上假设, 提出了深度残差学习框架, 没有直接堆叠网络层来 fit 期望的映射函数, 而是选择让这些网络层来 fit 一个残差映射. 也就是说, 如果我们期望得到的映射函数为 $H(x)$, 那么我们不是通过堆叠网络来直接学习这个映射函数, 而是学习对应的残差函数: $F(x):=H(x)-x$. 那么, 原始的映射函数就可以通过 $F(x)+x$ 得到(如图2所示). 我们认为这个残差映射比原始的映射函数更容易学习和优化. 因为我们可以在原始网络之上直接添加一个恒等连接, 进而加深网络深度, 但是保持网络性能不降低. 这样一来, 在深层网络中, 如果某一层的输出已经较好的拟合了期望结果, 那么它们的梯度就会被直接传送到两层网络之前, 从而减少了深度神经网络中由于连乘问题导致的梯度消失现象, 进而使得网络有可能拟合到更好的结果上.

ResNet 中可以使用哪些短接方式

基本来说, 有三种选项可以选择

  • (A). 使用恒等映射, 如果需要改变输出维度时, 对增加的维度用0来填充, 不会增加任何参数.(这种就是之前讨论的 parameter-free 的恒等短接)
  • (B). 在输入输出维度一致时使用恒等映射, 不一致时使用矩阵映射以保证维度一致, 增加部分参数.
  • (C). 对所有的block均使用矩阵映射, 大量增加参数

在效果上, 通常 C>B>A, 我们认为这是因为在 A 中的 zero-padded dimensions 实际上并没有进行残差学习. 但是由于 A/B/C 之间的差距比较小, 而线性变换需要引进额外的参数, 因此这是一个可以根据实际问题进行权衡的事情(通常不要用C, 因为增加的参数较多, 且性能提升并不是很明显).
对于 bottleneck 结构来说, parameter-free 的恒等短接尤其重要. 如果用矩阵映射替换了 bottleneck 中的恒等短接, 那么因为 shortcuts 需要处理的维度很高, 使得模型的 size 和时间复杂度都会加倍. 因此, 对于 bottlenect 来说, 选择恒等短接可以大大降低模型复杂度.

如何理解所谓的残差 $F(x)$ 比原始目标 $H(x)$ 更容易优化

假设我们要学习一种从输入x到输出H(x)的mapping, 最简单的例子, 假设解空间里的函数只有两个,就是在这两个可能的mapping 函数里面选择一个更好的。
另一方面, 由于恒等连接的存在, 当我们令学得的 $F(x)=0$ 时, 那么就有 $H(x)=x$, 比如如果我们将残差模块拼接在普通的 vgg 网络之后, 最终的模型性能也不会比 vgg 差, 因为后面几层相当于是一种恒等短接, 也可以认为是为模型的性能做到了一种保底措施.

也可以理解为, 在最差的情况下, 我们可以完全不学习, 这样依然不会损坏精度, 所以有了保底措施, 就更容易优化.

如果是非resnet的情况,那么给定 $H(5)=5.1$ 和 $H(5)=5.2$ 这两个函数映射, 其对应权重参数分别是 $H(x) = wx = \frac{5.1}{5} x$ 和 $H(x) =w x = \frac{5.2}{5} x$ ,这两个函数的w近似的都近似等于1, 或者说一个 $w$ 是另一个 $w$ 的1.04/1.02=1.0196倍. 也就是说,如果用sgd来选择参数 $w$ 的话,是容易认为两个 $w$ 很像的(对数据不敏感, 导致训练慢,学错)。
但是resnet就不同了,在resnet下,原输入输出数据相当于变成了 $H(5)=0.1$ 和 $H(5)=0.2$, 这两个对应的潜在函数变成了 $F(x)= wx = \frac{0.1}{5} x$ 和 $H(x) = wx = \frac{0.2}{5} x$ , 两个 $w$ 的关系变成了一个 $w$ 是另一个 $w$ 的0.2/0.1 = 2倍,所以 $w$ 的选取对于数据集非常敏感了。 这是基于这个原因,resnet里面的参数 $w$ 会更加”准确”反映数据的细微变化。(因此也更容易学到不同数据的特征)(感觉不太对, ResNet 的实现上, 只是直接将 out = x, 而其中的权重依然是 H(x), 而不是 F(x), 所以, 上面的说法不太对)

为什么恒等映射x之前的系数是1,而不是其他的值, 比如0.5

关于为什么是 $x$ 而不是 $\lambda_i x$,
主要是因为如果是 $\lambda_i x$ 的话,梯度里面 就会有一项 $\lambda_i$ 的连乘 $\prod_{i=1}^{L-1}{\lambda_i}$,就是从输出到当前层之间经过的 shortcut上的所有$\lambda_i$相乘,假如$\lambda_i$都大于 1 那经过多层之后就会爆炸,都小于1就会趋向0而引发梯度消失.

具体公式分析可见下面关于”用简单缩放来替代恒等连接”的讨论

ResNet 为什么好

因为 shortcut 可以解决梯度消失问题

补问: 后面都使用 relu 了, 导数恒等于 1, 怎么还会有梯度消失问题?

一方面, relu 正半轴导数为 1, 可以缓解梯度消失问题, 但是负半区导数为 0, 仍然存在梯度消失问题.
另一方面, 存在 relu 那一层的导数为 1, 但是其他层的导数并不一定为 1, 神经网络中不只有 relu 这一种变换, 其他的网络层的导数若大量小于 1, 仍然能够引起梯度消失问题. 当网络层数很深时, relu 的作用就不那么明显, 此时 shortcut 的作用就显现出来了.
故relu只是相对于其他激活函数可以缓解梯度消失,并不能消除。

ResNet 残差模块中激活层应该如何放置

推荐采用预激活的方式来放置激活层: BN+ReLU+Conv

ResNeXt

SummaryOfComputerVision%2Fnet_arch%2FResNeXt1.jpg

ResNeXt 在 ResNet 上做了哪些改进

ResNeXt 实际上是将 ResNet Block 当中的输入数据的通道划分到了不同的组, 每个组的计算过程相对独立, 最终将所有组的计算结果进行空间聚合, 作为最终的输出. ResNeXt 可以在不增加参数量的情况下进一步提高 ResNet 的特征提出能力, 从而表现出更好的网络性能. ResNeXt 的卷积方式实际上可以看做是通道分组卷积. 在实现上, 只需要将普通的卷积替换成 Group Conv 即可, 下图是 Group=32 的示例.

SummaryOfComputerVision%2Fnet_arch%2FResNeXt2.jpg

SummaryOfComputerVision%2Fnet_arch%2FResNeXt3.jpg

DenseNet

简述 DenseNet 的原理

SummaryOfComputerVision%2Fnet_arch%2FDenseNet1.jpg

SummaryOfComputerVision%2Fnet_arch%2FDenseNet2.jpg

在训练特别深层的网络时, 随着深度的增加, 梯度消失的问题会越来越明显, 对此, ResNet 给出了一个很好的解决方案, 那就是在接近输入层的网络中添加一个短接路径到靠近输出层的网络. DenseNet 也延续了这个思路, 与 ResNet 不同的是, 他采用了一个更暴力的方式, 就是将所有网络层都连接起来, 具体来说, 就是每一层的输入会来自于 前面所有层的输出(这些层的特征图谱大小是相同的, 因此可以在 Channel 维度上进行叠加). 如果假设一个 DenseBlock 的层数是 $L$, 那么 DenseNet 就会有 $\frac{L(L+1)}{2}$ 个短接路径. 在传统的卷积神经网络中, 通常会利用 Pooling 层或者卷积层来降低特征图谱的大小, 而 DenseNet 的密集连接需要特征图大小保持一致, 因此, 为了解决这个问题, DenseNet 将网络分成了若干个 DenseBlock + Transition 的结构. 在 DenseBlock 内部, 特征图谱大小相同, 可以直接连接, 在具有不同大小特征图谱的 DenseBlock 之间, 使用 Transition Layer 进行过渡.

DenseBlock 每一层输出的特征图谱是怎么构成的, 如何确定输出通道数

DenseBlock 内部的网络层都具有相同大小的特征图谱, 因此可以直接使用 dense connect 的方式在通道维度上进行连接. 在 DenseBlock 内部, 每一层网络输出的特征图谱的通道数是通过一个 超参数增长率 $k$ 来决定的, 这个 $k$ 可以设定的比较小, 比如32, 在 DenseBlock 中, 所有的网络层都输出 $k$ 维通道, 虽然每一个网络层的输出图谱通道数较低, 但是每一层的输入是综合前面所有层的输出的, 因此随着网络层的增加, 其最终的输出通道数依然是增加的(会叠加前面所有的输出), 只不过每一层只有 $k$ 层通道数自己独自输出的.

Transition Layer 怎么构成的, 有什么作用

对于不同的 DenseBLock, 其具有的尺寸是不同的, 因此无法直接使用 dense 链接, 为此, 作者利用 1x1 卷积层和 2x2 池化层定义了一个 transition layer, 其主要作用有两个, 一个是降低通道维数, 另一个是对特征图谱进行下采样. Transition Layer 的结构为: BN + ReLU + 1x1 Conv + 2x2 AvgPooling.
另外,Transition层可以起到压缩模型的作用。假定Transition的上接DenseBlock得到的特征图channels数为 $m$,Transition层可以产生 $\theta m$ 个特征(通过卷积层),其中 $theta$ 是压缩系数(compression rate)。当 $\theta = 1$ 时,特征个数经过Transition层没有变化,即无压缩,而当压缩系数小于1时,这种结构称为DenseNet-C,文中使用 $\theta = 0.5$ 。

深层的网络层输入的特征图谱很大, 怎么解决

另外, 由于深层的网络层输入非常大, 因此 DenseBlock 内部会采用 bottleneck 来减少计算量, 主要是在原来的 $3 \times 3$ 卷积层之前添加 $1\times 1$ 的卷积层, 变成 BN + ReLU + 1x1 Conv + BN + ReLU + 3x3 Conv 的结构(DenseNet-B), $1\times 1$ 卷积会将 $l\times k$ 的通道数降低成 $4\times k$ 的通道数, 从而提升计算效率.

对于使用bottleneck层的DenseBlock结构和压缩系数小于1的Transition组合结构我们称为DenseNet-BC。

SqueezeNet

简述 SqueezeNet 的原理

SqueezeNet%2Ffig1.jpg

SqueezeNet 定义了 Fire Modules 作为其基本的组成部件, Fire Modules 由 squeeze layer 和 expand layer 组成, 其中前者是通过 1x1 的卷积层实现的, 后者是通过 1x1 和 3x3 的卷积层实现的(这两个卷积层的输入都是 sequeeze layer, 输出会将二者在通道维度叠加). SqueezeNet 的第一层是传统的 7x7 卷积层, 之后由 8 个 Fire Modules 组成, 最后一层 conv10 是传统的 1x1 卷积层, 最后是由 GAP 和 Softmax 组成的分类层. SqueezeNet 会在 conv1(自身也是下采样), fire4, fire8, conv10 之后添加 max-pooling 层来进行下采样(总步长为 32). 同时, SqueezeNet 受到 ResNet 的启发, 可以在 Fire Modules 3, 5, 7, 9 的输入和输出之间添加 bypass 连接, 令这些模型学习输入输出之间的残差(可以获得更高的精度). 对于通道数不同的其他 Modules, 可以通过 1x1 卷积层来建立 complex bypass).

SqueezeNet 的网络结构

SqueezeNet%2Ffig2.jpg

SqueezeNet%2Ftab1.jpg

MobileNet

简述 MobileNet

MobileNet 的设计原则是要构建出模型 size 小, 并且执行速度快的卷积网络. 它的整体结构框架和 AlexNet 以及 VGGNet 类似, 都是通过不断堆叠卷积层的方式来构建深层的卷积神经网络. 但与传统卷积网络不同的是, MobileNet 除了在第一层使用了标准的卷积层之外, 其余的卷积层都是基于深度可分离卷积构建的. 具体来说, 标准的卷积层在进行操作时, 包括 filter 和 combining 两步, 而深度可分离卷积将这两步分成两个网络层执行, 分别为 Depthwise Convolution 和 Pointwise Convolution

  • Depthwise Conv(用 Group Conv 实现, group_num = in_channels): 对每个 input channel(input depth) 使用一个单独的 filter. 因此输出通道数保持不变, 依然为 $C_{in}$
  • Pointwise Conv(用 1x1 Conv 实现, 每个卷积核深度为 $C_{in}$): 利用 1x1 卷积跨通道的融合 depthwise conv 的输出, 并输出指定的通道数 $C_{out}$, 从而建立新的特征图谱.

MobileNets%2Ftab1.jpg

MobileNets%2Ffig2.jpg

推导 Separable Conv 的参数量和计算量

输入特征图谱为: $W_{in}\times H_{in} \times C_{in}$
输出特征图谱为: $W_{out} \times H_{out} \times C_{out}$
卷积核大小为: $W_k \times H_k \times C_{in}$

关于分组卷积: 当采用分组卷积时, 令$group num = G$, 则 $G$ 必须满足能够同时被 $C_{in}$ 和 $C_{out}$ 整除, 此时, 分组卷积会将输入的特征图谱在通道维度上分成 $G$ 组, 每组的输入特征图的尺寸为 $W_{in} \times H_{in} \times \frac{C_{in}}{G}$, 每组中的卷积核尺寸为 $W_k \times H_k \times \frac{C_{in}}{G}$, 卷积核个数为 $\frac{C_{out}}{G}$, 所以输出的特征图尺寸为 $W_{out} \times H_{out} \times \frac{C_{out}}{G}$. 然后将 $G$ 个输出的特征图在通道维度上拼接(concat), 得到的最终尺寸为 $W_{out} \times H_{out} \times \frac{C_{out}}{G} \times G = W_{out} \times H_{out} \times C_{out}$. 易知:

  • 分组卷积的参数量为 每组的参数量乘以组数, 即为 $W_k \times H_k \times \frac{C_{in}}{G} \times \frac{C_{out}}{G} \times G = W_k \times H_k \times \frac{C_{in}}{G} \times C_{out}$
  • 分组卷积的 FLOPs 为参数量和输出图谱尺寸的乘积, 即为: $W_k \times H_k \times \frac{C_{in}}{G} \times C_{out}\times W_{out} \times H_{out} $

参数量:

  • 标准卷积: $W_k \times H_k \times C_{in} \times C_{out}$
  • Separable:
    • Depthwise(分组数=输入通道数=输出通道数): $W_k \times H_k \times \frac{C_{in}}{G} \times C_{out} = W_k \times H_k \times C_{out}$
    • Pointwise(1x1 普通卷积): $W_k \times H_k \times C_{in} \times C_{out} = C_{in} \times C_{out}$

于是乎, 在参数量上, 可以节省:

卷积计算量(Multiply-Adds):参数量与输出图谱尺寸的成绩
标准卷积: $W_k\times W_k \times C_{in} \times C_{out} \times W_{out} \times H_{out}$

  • Separable:
    • Depthwise(分组数=输入通道数=输出通道数): $W_k \times H_k \times \frac{C_{in}}{G} \times C_{out}\times W_{out} \times H_{out} = W_k \times H_k \times C_{out}\times W_{out} \times H_{out}$
    • Pointwise(1x1 普通卷积): $W_k\times W_k \times C_{in} \times C_{out} \times W_{out} \times H_{out} = C_{in} \times C_{out} \times W_{out} \times H_{out}$

于是乎, 在计算量上, 可以节省:

MobileNets 为什么快

第一个原因, MobileNet 将普通卷积替换为 Separable Conv 以后, 其参数量和计算量都大约缩小了 $O(\frac{1}{D_K\times D_K})$ 倍, 如果是 3x3 的卷积, 就是 8~9 倍左右. 虽然计算量小了 8~9 倍, 但实际中并不能加速 8~9 倍, 还要同时考虑带宽等其他因素.

第二个原因, 在 MobileNet 中, 参数量上, Conv 1x1 占比约 75%, FC 约 24%. 而在 FLOPs 上, Conv 1x1 占比约 95%, Conv DW 3x3 约 3%, FC 不到 1%.
当卷积层使用 im2col+ GEMM 的方式实现时, $1\times 1$ 的卷积层不需要在内存中重新排序, 因此它的实际执行速度很快.

最后, 原文还给出了两个超参数来进一步压缩模型大小, 分别 width multiplier $\alpha$ 和 resolution multiplier $\rho$, 前者用于控制特征图谱的通道数, 后者用于控制输入图片的尺寸.

MobileNetV2

MobileNetV2 做了哪些改进

相比于 MobileNetV1, MobileNetV2 的改进主要有两点: Linear Bottleneck 和 Inverted Residual.

  • Linear Bottleneck(用 1x1 conv2d 实现): 在 MobileNetV1 中, Separable Conv 的结构是: 3x3 Conv + BN + ReLU + 1x1 Conv + BN + ReLU. 在 MobileNetV2 中, 先将原来的结果换成了 Bottleneck (conv 1x1 + conv 3x3 + conv 1x1), 同时去掉了最后一个 conv 1x1 的 ReLU, 这是因为 ReLU 会造成低纬度数据的坍塌, 在 channel 少的 feature map 后如果使用 ReLU, 就会破坏 feature map, 造成信息缺失.

MobileNetV2%2Ffig2.jpg

  • Inverted Residual: 添加了 Inverted Residual 加强特征复用和梯度扩多层传播的能力, 将 shortcut 连接位置迁移到 channel 数量较少的层. ResNet Bottleneck 是先降维, 后升维, MobileNetV2 刚好与之相反, 因此称为 Inverted Residual. 这样做的原因其一是因为在内存的使用上更加高效(论述过程比较繁琐, 没细看), 其二是因为在实际实验中效果也比较好.

MobileNetV2%2FInverted.png

MobileNetV2%2Ffig3.jpg

expansion ratio: the ratio between the size of the input bottleneck and the inner size

某一层的 Bottleneck, 其中层与之类似, 可以看出, 先利用 1x1 conv 升维, 再进行 Depthwise Conv, 再利用 1x1 conv 降维, 降维后由于 channel 过少, 因此不使用 ReLU

1
2
3
4
5
6
7
8
9
10
11
12
InvertedResidual(
(conv): Sequential(
(0): Conv2d(96, 576, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(576, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU6(inplace)
(3): Conv2d(576, 576, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=576, bias=False)
(4): BatchNorm2d(576, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU6(inplace)
(6): Conv2d(576, 96, kernel_size=(1, 1), stride=(1, 1), bias=False)
(7): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)

MobileNetV1/2 中为什么使用 ReLU6? 哪些层后面不用 ReLU 激活? 为什么?

ReLU6 就是将普通的 ReLU 的最大输出值限制为 6(对输出值做 clip). 这是因为在移动端设备上, 由于资源限制, 使用的数据类型通常为 float16, 如果不对 ReLU 的输出值限制范围, 则其输出值为 0 到正无穷, 而低精度的 float 16 无法很好的精确描述如此大范围的数值, 因此会带来精度损失.

在 Separable Block 中对于 channel 数量较少的 1x1 conv, 使用 Linear Bottleneck 来替换 ReLU. 这么做的原因个人理解是对于 channel 较少的层, 其本身某一维度的张量容易被 ReLU 置为全 0, 张量维度的减小意味着特征描述容量的下降, 从而造成当前层的特征信息损耗.

MobileNetV2 的网络结构及与其他轻量级网络的区别

MobileNetV2%2Ftab2.jpg

MobileNetV2%2Ffig4.jpg

ShuffleNet

简述 ShuffleNet

分组卷积具有降低计算量和跨通道信息解耦的优势, 但不论是 Xception 还是 ResNeXt 还是 MobileNet, 他们在进行分组卷积的时候, 都只是对 3x3 的卷积核进行分组卷积. 而对于这些网络结构来说, 占计算量 90% 甚至 95% 的 1x1 卷积却没有进行分组卷积. 其主要原因就是这些网络需要 1x1 卷积来学习跨通道的信息.
ShuffleNet 就是在这一点上提出了改进方案, 利用一个简单 Shuffle Channel 操作来实现不同组之间的通道交互, 进而使得可以对 1x1 卷积也是用分组卷积. 如下图所示.

ShuffleNet%2Ffig1.jpg

Shuffle Channel 的实现十分简单, 只需要一个reshape, transposeflatten操作即可, 如下所示:

1
2
3
                         111                  123
111222333 -(reshape)-> 222 -(transpose)-> 123 -(flatten)-> 123123123
333 123

利用 Shuffle Channel 操作, 作者基于 ResNet 中的 bottleneck 进行改进(所以 ShuffleNet Bottleneck 也是中间小两边大, 和 MobileNetV2 相反). 主要改进方法是先将 bottlenect 中的 3x3 卷积替换成 Xception/MobileNet 中的 Depthwise Conv. 然后将两个 1x1 卷积替换成 Group Conv, 接着对第一个 1x1 卷积的输出图谱执行 Shuffle Channel 操作即可. 第二个 Group Conv(Pointwise) 的目的是恢复通道数以匹配 shortcut 连接, 为了简单起见, 我们没有这里使用额外的 Channel Shuffle 操作, 因为这已经可以产生不错的效果了.
注意, 在 Depthwise Conv 之后, 没有使用 ReLU 等激活层, 其原因是因为 Depthwise Conv 输出的通道数较低, 使用 ReLU 会因为低维数据坍塌现象, 即 ReLU 有可能会让某个通道的值全为 0, 从而维度降低, 这些低纬度时会引起信息缺少, 而其他的激活函数又存在梯度消失问题, 所以没有使用激活
疑问: 为什么第一层的 1x1 GConv 的输出通道数也很低, 但是却用了 ReLU? 猜想: ShuffleNet 并没有对 ReLU 激活函数进行大量验证, 它只是采用了 Xception 中的设定, 这一块有待实验验证.

ShuffleNet%2Ffig2.jpg

上图为 ShuffleUnit 结构. 其中, (b) 为基本的 Shuffle Unit, Add 表示 element-wise add, (c) 为进行下采样时的 Shuffle Unit(步长为2, 图片缩小一半), concat 表示在 depth 维度上叠加, 用于加深深度.

ShuffleNet 的网络结构

ShuffleNet 的网络结构最开始由 3x3 的卷积层和最大池化层组成, 二者的步长均为2, 也就说这里的 Downsample 总步长为 4. 然后是三个不同的 Stage(3,7,3), 每个 Stage 最开始第一个 building block 的步长为2(stride=2), 用于降低特征图谱的尺寸, 同时会借助旁路的 avg pool 使得 channels 数量翻倍. 最后是由 GAP+FC+Softmax 组成的分类层.

ShuffleNet%2Ftab1tab2.jpg

ShuffleNet 的计算量推导

ShuffleNet 最主要的特点是可以在较少的计算资源限制下达到更高的精度和计算效率. 举例来说

Input feature map: $c\times h\times w$
bottlenect 3x3 卷积的通道数: $m$

计算量分别为(单位: FLOPs, 文中单位是 FLOPs, 但实际上应该是 Mult-Adds, 也即 MAC, 注意二者区别):

  • ResNet: $hwcm + 9m^2 + hwmc$
  • ResNeXt: $hwcm + 9m^2/g + hwmc$, $g$ 代表分组数量
  • ShuffleNet: $hwcm/g + 9m + hwmc/g$, $g$ 代表 1x1 卷积的分组数量, 3x3 是 Depthwise Conv, 分组数等于通道数

注意 Xception 和 MobileNet 没有采用 bottleneck 结构, 而是对普通的 Conv 替换成了 Separable Conv(DP Conv), 所以这里不与之比较
而 MobileNetV2 采用的 bottleneck 是两边小中间大, 与上面相反, 所以也不做比较

, 当给定输入尺寸为 $c\times h\times w$, 而 bottleneck 的通道数为 $m$ 时, ResNet unit 需要 $hw(2cm + 9m^2)$ FLOPs, ResNeXt 需要 $hw(2cm + 9m^2/g)$ FLOPs(ResNeXt 的 $g\neq m$), 而 ShuffleNet 只需要 $hw(2cm/g + 9m)$ FLOPs(Depthwise 的分组是每个图谱单独一组). 也就说, 当给定计算资源限制后, ShuffleNet 可以使用更大的特征图谱, 这对于小型网络来说非常重要, 因为小型网络通畅没有足够的通道来处理信息.

ShuffleNetV2

ShuffleNetV2 做了哪些改进

作者通过分析影响网络执行速度的原因, 给出了四条十分实用的建议, 分别为:

  1. 在 Pointwise Conv 中, 当输入输出通道维度相等时,内存访问成本(MAC)最小. 理论证明如下:
    假设 Pointwise Conv 的输入通道为 $c_1$, 输出通道为 $c_2$。设 $h$ 和 $w$ 为 feature map 的空间大小,则 1×1 卷积的 FLOPs 为 $B = hwc_1c_2$
    为了简单起见,本文假设计算设备中的缓存足够大,可以存储整个特征图谱和参数。因此,内存访问成本(MAC)或内存访问操作的数量是 $MAC = hwc_1+ hwc_2+c_1c_2$。这三项分别为: 访问输入图谱, 访问输出图谱, 每次访问输出图谱的一个点时, 需要 c_1 个输入图谱的像素, 总共访问 c_2 个输出图谱的像素.
    均值不等式,对于非负实数有 $a + b \geq 2\sqrt {ab}$, 并且仅当 $a=b$ 时才取得最小值, 由此本文得到:推理过程如下:因此,MAC有一个由FLOPs给出的下界。当输入通道和输出通道数目相等时,到达下界。 上面结论只是理论性的。实际上,许多设备上的缓存不够大。但是根据实验表明, 这种理论上的 MAC 同样具有指导意义, 也就是说 当 $c_1 = c_2$ 时, MAC 最小, 网络推演速度最快.
  2. Group Conv 虽然有助于提高精度, 但是过多的分组会增加 MAC
    Group Conv 可以降低 FLOPs, 降低幅度为 $\frac{1}{g}$, $g$ 为分组数量, 但是, 过多的分组会增加内存访问成本, 以 1x1 Group Conv 的 MAC 和 FLOPs 之间的关系证明:其中 $g$ 为组数,$B = hwc_1c_2 / g$ 为 FLOPs。可以看出,当给定固定的输入形状 $c_1\times h\times w$ 和计算量 B(根据 $g$ 的值改变自身的 $c_2$ 值, 以保持 B 不变), MAC 会随着 $g$ 的增大而增大。 实验证明也是如此, 当 group 为 1 时, 网络速度最快.
  3. Building Block 中过多的子模块会降低并行程度.
    在 Inception 系列和 NAS 结构中, 一个 build block 里面通常会包含需要种不同的卷积模块(例如, NASNET-A 有 13 种卷积模块, 而 ResNet 只有 2,3个). 这种将多个卷积模块组合作为一个 building block 的方式通常可以提高精度, 因为特征表达能力明显增强, 但是它对 GPU 并行设备非常不友好, 即使是并行排列的子模块, 也会大幅降低实际并行速度, 因为它们需要引入额外的开销, 比如 kernel launching 和信息同步(synchronization), 实验证明如下图所示
    ShuffleNetV2%2FAfig1.jpg
    表3的结果显示,多段结构(即使是多段并行)在GPU上显著降低了速度,如4段结构比1段慢3倍。在ARM上,减速相对较小, 因为主要影响的是并行程度, 而 CPU 上主要进行串行计算.
    ShuffleNetV2%2Ftab3.jpg
  4. Element-wise 操作也会影响速度
    在轻量模型 ShuffleNetV1 和 MobileNetV2 中,element-wise 操作占用了相当多的时间(15%, 23%),尤其是在GPU上。这里的元素操作符主要包括 ReLU、Shortcut(Tensor add)、Add Bias等。它们的 FLOPs 较小,但 MAC 相对较高, 也就是说他们具有较高的 MAC/FLOPs ratio。特别地,本文还考虑了深度卷积(depthwise convolution)作为一个 element-wise 运算符的情况,因为它也具有较高的MAC/FLOPs比。

ShuffleNetV2 的网络结构

根据上面四条 Guide, ShuffleNetV2 的设计目标是在保持较大通道数的情况下, 减少密集卷积和分组数量, 为此, 它使用 Channel Split 方法对 ShuffleNetV1 进行了如下改进:

ShuffleNetV2%2Ffig3.jpg

上图中 (a), (b) 是 ShuffleNetV1 中的两个核心 building block. 易知, 它同时违反了 G1, G2, G4. 为此, ShuffleNetV2 改进如下:

  • 将输入图谱的 Channel 分成两部分(分配比例默认 1:1), 一部分直接通过 Shortcut 传递, 另一部分有 bottleneck 传递;
  • 为了遵循 G1, G2, bottleneck 的三个卷积层的输入输出通道均相等, 同时两端的 1x1 卷积使用普通卷积, 而不是 Group Conv; (Channel Split 本身也具有一定的分组作用)
  • 对于下采样模块, 没有使用 Channel Split, 这样输出通道数刚好可以翻倍, 另外将原先的 3x3 AVG Pool 替换成了 3x3 Depth-wise Conv + 1x1 Conv.

将这两个 building-block 反复堆叠,进而构建整个网络, 总体网络结构与ShuffleNet v1相似,如表5所示。只有一个区别: 在全局平均池之前添加一个传统的1×1卷积层来混合特性,而在ShuffleNet v1中没有。与类似,将每个块中的信道数进行缩放,生成不同复杂度的网络,标记为0.5×、1×等。

ShuffleNetV2%2Ftab5.jpg

补充1:

  • concat, channel shuffle, 以及下一层的 channel split 可以 merge 到同一个 element-wise 中去, 减少 element-wise 的冗余;
  • 疑问: ShuffleNetV2 Unit 中的 Depthwise Conv 后面依然没有用 RelU ? 但是个人认为, 当通道数已经相同时, 可以使用 ReLU.

补充2: ShuffleNet 可以和 Residual, SE 联合使用, 如下所示:

ShuffleNetV2%2FAfig2.jpg

现有的轻量级网络存在哪些问题

  • ShuffleNetV1:
    • 使用基于 ResNet bottleneck 的基本结构, point-wise conv 的通道比不为 1, 违反 G1;
    • 使用大量的 Group Conv, 分组过多, 违反 G2;
    • Shortcut, ReLU, 存在大量的 Element-wise 操作(15%), 违反 G4
  • MobileNetV1:
    • 使用 Point-Wise 改变通道维度, 违反 G1;
    • 使用 Gourp Conv, 分组过多, 违反 G2;
  • MobileNetV2:
    • 使用 Inverted Residual bottleneck, 通道比不为 1, 违反 G1;
    • 使用大量的 Group Conv, 范围 G2;
    • 在一个很 “厚” 的特征图谱上使用 ReLU, 以及大量的 Shortcut, 使用 Element-wise 操作较多(23%), 违反 G4;
  • NAS 网络:
    • building block 内部存在过多路径, 碎片化程度严重, 违反 G3;

简述 ShuffleNetV1/2 和 MobileNetV1/2 的区别

  • Xception: 基于 InceptionV3, 先进行 1x1 Pointwise Conv, 在进行 Group Conv. (由于 building block 是堆叠使用的, 因此相对位置的变化影响并不大), 在 1x1 卷积降维后不使用 ReLU.
  • MobileNetV1: 将 3x3 Conv 替换成 Depthwise Conv + Pointwise Conv, 正常使用 ReLU6.
    MobileNetV2: Inverted Residual + Linear Bottleneck, 两边小中间大, 最后的 1x1 卷积不使用 ReLU6.
  • ShuffleNetV1: 基于 ResNet Bottleneck, 在 Bottleneck 两端的 1x1 卷积上也使用 Group Conv, 为了促进组间通信, 在第一个 1x1 卷积后使用 Channel Shuffle. 两边大中间小, 在中间的 DWConv 不使用 ReLU. 疑问: 为什么输出通道少的第一个 1x1 GConv 依然使用 ReLU?
  • ShuffleNetV2: 基于 4 条 Guide, 利用 Channel Split 改良 ShuffleNetV1 Unit, 减少 MAC 通信和 FLOPs, 提升模型运行效率

SENet

简述 SENet 的原理

SENet 提出了 SEBlock, 该模块可以加入到任意的特征图谱上, 进而可以重新校准各个通道上的特征信息. 该模块分两个步骤执行:

  1. Squeeze: 在特征图谱上进行全局平均池化, 得到 channel embedding, 这是最简单的 Squeeze 策略.
  2. Excitation: 利用 FC(r) + ReLU(r) + FC + Sigmoid 的组合普通各个通道之间的依赖关系, 为了能够同时关注多个通道, 使用 Sigmoid 激活(而不是 One-Hot 激活), 这样, 就有可能有多个通道的权重都很高. 上面的 r 代表通道个数的衰减系数, 前两个网络层的通道数为 $\frac{C}{r}$, 后两个为 $C$. 使用两个 FC 的原因主要是为了构成 bottleneck 结构, 减少参数量和计算量.

SENet%2Ffig1.jpg

SENet%2Ffig2.jpg

SENet%2Ffig3.jpg

SE-Block 放置的位置是否会严重影响性能?

不会, 通常网络是堆叠放置的, 因此改变 SE Block 的绝对位置并不会对其相对位置产生太大的影响, 因此, SE Block 对于各种位置都具有较好的性能, 如下实验所示.

SENet%2Ffig5.jpg

SENet%2Ftab14.jpg

SENet 的网络结构

SE Block 可以使用在任意的网络中, 下表是在 ResNet-50 和 ResNeXt-50 中使用的情况, 可以看出, SE BLock 会在每个 bottleneck 中使用

SENet%2Ftab1.jpg

Convolutional Block Attention Module (CBAM)

除了 Channel Attention 之外, 还添加了 Spatial Attention, Spatial Attention 可以告诉神经网络特征图谱上 “where to focus”

CBAM%2Ffig1.jpg

CBAM%2Ffig2.jpg

$M_c(F)$ 代表 Channel Attention, $M_s(F)$ 代表 Spatial Attention

CBAM%2Fform1.jpg

CBAM%2Fform2.jpg

CBAM%2Fform3.jpg

作者建议 CBAM 插入到 backbone 网络的 basic block 部分, 如下图 3 所示.

CBAM%2Ffig3.jpg

Block Attention Module (BAM)

和 CBAM 结构类似, 只不过插入位置不同

BAM%2Ffig1.jpg

BAM%2Ffig2.jpg

目标检测篇

IoU

在目标检测中, IoU 是评价生成的预测框好坏的一个很重要的数值, 计算两个边框 IoU 的公式如下所示:

根据上面的公式, 我们可以写出计算预测框和 GT 框之间任意两个框的 IoU 代码(返回一个二维数组, matrix[i][j]表示第i个预测框和第j个真实框的 IoU)

Numpy 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import numpy as np

def get_inter_matrix(boxes_pd, boxes_gt):
A = boxes_pd.shape[0]
B = boxes_gt.shape[0]

lt_xy = np.maximum( # left top
np.repeat(np.expand_dims(boxes_pd[:, :2], axis=1), B, axis=1),
np.repeat(np.expand_dims(boxes_gt[:, :2], axis=0), A, axis=0)
)
rb_xy = np.minimum( # right bottom
np.repeat(np.expand_dims(boxes_pd[:, 2:], axis=1), B, axis=1),
np.repeat(np.expand_dims(boxes_gt[:, 2:], axis=0), A, axis=0)
)
inter = np.clip(rb_xy - lt_xy, a_min=0, a_max=None) # (A, B, 2): (h, w)
return inter[:, :, 0] * inter[:, :, 1] # return (A, B), inter_matrix

def get_iou_matrix(boxes_pd, boxes_gt):
"""
Args:
box_pd: (tensor) Prior boxes from priorbox layers, Shape: [num_priors,4]
box_gt: (tensor) Ground truth bounding boxes, Shape: [num_objects,4]
Return:
jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)]
"""
A = boxes_pd.shape[0]
B = boxes_gt.shape[0]

area_pd = (boxes_pd[:, 3]-boxes_pd[:, 1]) * (boxes_pd[:, 2]-boxes_pd[:, 0]) # h * w
area_gt = (boxes_gt[:, 3]-boxes_gt[:, 1]) * (boxes_gt[:, 2]-boxes_gt[:, 0]) # h * w

inter_matrix = get_inter_matrix(boxes_pd, boxes_gt)

return inter_matrix / (area_gt + area_pd - inter_matrix) # (A, B), iou_matrix

PyTorch 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import torch
def intersect(box_a, box_b):
""" We resize both tensors to [A,B,2] without new malloc:
[A,2] -> [A,1,2] -> [A,B,2]
[B,2] -> [1,B,2] -> [A,B,2]
Then we compute the area of intersect between box_a and box_b.
Args:
box_a: (tensor) bounding boxes, Shape: [A,4].
box_b: (tensor) bounding boxes, Shape: [B,4].
Return:
(tensor) intersection area, Shape: [A,B].
"""
A = box_a.size(0)
B = box_b.size(0)
max_xy = torch.min(box_a[:, 2:].unsqueeze(1).expand(A, B, 2),
box_b[:, 2:].unsqueeze(0).expand(A, B, 2))
# print(box_a[:, :2])
# print(box_a[:, :2].unsqueeze(1))
# print(box_a[:, :2].unsqueeze(1).expand(A, B, 2))
min_xy = torch.max(box_a[:, :2].unsqueeze(1).expand(A, B, 2),
box_b[:, :2].unsqueeze(0).expand(A, B, 2))
inter = torch.clamp((max_xy - min_xy), min=0)
return inter[:, :, 0] * inter[:, :, 1]


def jaccard(box_a, box_b):
"""Compute the jaccard overlap of two sets of boxes. The jaccard overlap
is simply the intersection over union of two boxes. Here we operate on
ground truth boxes and default boxes.
E.g.:
A ∩ B / A ∪ B = A ∩ B / (area(A) + area(B) - A ∩ B)
Args:
box_a: (tensor) Ground truth bounding boxes, Shape: [num_objects,4]
box_b: (tensor) Prior boxes from priorbox layers, Shape: [num_priors,4]
Return:
jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)]
"""
inter = intersect(box_a, box_b)
area_a = ((box_a[:, 2]-box_a[:, 0]) *
(box_a[:, 3]-box_a[:, 1])).unsqueeze(1).expand_as(inter) # [A,B]
area_b = ((box_b[:, 2]-box_b[:, 0]) *
(box_b[:, 3]-box_b[:, 1])).unsqueeze(0).expand_as(inter) # [A,B]
union = area_a + area_b - inter
return inter / union # [A,B]

box_a = torch.Tensor([[1,2,3,4],[2,3,4,5]])
box_b = torch.Tensor([[1,2,3,4],[2,3,4,5],[4,5,6,7]])
iou = jaccard(box_a, box_b)
print(iou)

mAP

基本概念

mAP 对于不同的 score 阈值通常会有不同的表现.(判定框是正样本还是负样本)

  • 当 threshold 较高时, 说明预测非常严格, 只有在很大把握时, 才会判断成正样本, 这样的话, precision 就会很高, 也因为筛选太严格, 也放过了一些 score 比较低的框, 导致 recall 降低.
  • 如果 threshold 太低, 那么 precision 就会降低, recall 就会很高.

TP: $IoU > 0.5$ 的检测框数量(同一个 GT 只计算一次)
FP: $IoU \leq 0.5$ 的检测框数量, 或者检测到同一个 GT 的多余检测框的数量
FN: 没有被检测到的 GT 的数量

准确率(Acc):

精确度(Pre):

在目标检测中, 由于 不存在 预测正确的负样本这种说法, 因此, 我们在目标检测中有时候也默认准确率就是精确度.

召回率(Recall):

对于 Pre 和 Recall 来说, 一般都是针对某一类的, 在不说明类的情况下, 则认为是二分类问题, 即只有正负样本.

AP: Average Precision

以 Recall 为横轴, Precision 为纵轴, 可以画出一条 PR 曲线, PR 曲线下的面积就定义为 AP. 由于积分计算相对困难, 因此通常用插值法计算.

AP 计算方式: $\sum_{k=1}^{N}\max_{k’ \geq k} P(k’) \Delta r(k)$

  • VOC2010 以前: 只需要选取当 Recall >= 0, 0.1, 0.2, …, 1 共 11 个点时的 Precision 最大值,然后 AP 就是这 11 个 Precision 的平均值
  • VOC2010 以后: 针对样本产生的每一个不同的 Recall 值(包括0和1),选取其大于等于这些 Recall 值时对应的 Precision 最大值,然后计算 PR 曲线下面积作为 AP 值。

mAP: mean Average Precision 指的是分别求每个类别的 AP, 然后取其平均值.

AP 计算示例

假设, 对于 Aeroplane 类别, 我们网络有以下输出 (BB 表示 BBox 序号, IoU > 0.5 时, GT = 1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BB  | confidence | GT
----------------------
BB1 | 0.9 | 1
----------------------
BB2 | 0.9 | 1
----------------------
BB1 | 0.8 | 1
----------------------
BB3 | 0.7 | 0
----------------------
BB4 | 0.7 | 0
----------------------
BB5 | 0.7 | 1
----------------------
BB6 | 0.7 | 0
----------------------
BB7 | 0.7 | 0
----------------------
BB8 | 0.7 | 1
----------------------
BB9 | 0.7 | 1
----------------------

因此,我们有 TP=5 (BB1, BB2, BB5, BB8, BB9), FP=5 (重复检测到的BB1也算FP)。除了表里检测到的5个GT以外,我们还有2个GT没被检测到,因此: FN = 2. 这时我们就可以按照Confidence的顺序给出各处的PR值,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rank=1  precision=1.00 and recall=0.14
----------
rank=2 precision=1.00 and recall=0.29
----------
rank=3 precision=0.66 and recall=0.29
----------
rank=4 precision=0.50 and recall=0.29
----------
rank=5 precision=0.40 and recall=0.29
----------
rank=6 precision=0.50 and recall=0.43
----------
rank=7 precision=0.43 and recall=0.43
----------
rank=8 precision=0.38 and recall=0.43
----------
rank=9 precision=0.44 and recall=0.57
----------
rank=10 precision=0.50 and recall=0.71
----------

因此, 根据不同的计算方法, 其对应的 AP 分别为:

  • VOC2010以前, 选取 Recall >= 0, 0.1, …, 1 的 11 处 Percision 的最大值:
  • VOC2010以后,

AP 计算代码实现

AP: Average Precision, 以 Recall 为横轴, Precision 为纵轴, 可以画出一条 PR 曲线, PR 曲线下的面积就定义为 AP.

当给定一组样本时, 已知其 score 和 label, 我们首先需要求取它们的 precision 和 recall. 具体求取代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# input:
# - score: 样本为正样本的概率
# - label: 样本的真实标签

import numpy as np

def get_pre_rec(scores, labels):
sort_index = np.argsort(scores)[::-1]
scores = scores[sort_index]
labels = labels[sort_index]

y_preds = scores >= 0.5

tps = np.cumsum((y_preds == True) & (labels == True))
fps = np.cumsum((y_preds == True) & (labels == False))
tns = np.cumsum((y_preds == False) & (labels == False))
fns = np.cumsum((y_preds == False) & (labels == True))

print("tps:", tps)
print("fps:", fps)
print("tns:", tns)
print("fns:", fns)
pres = tps / (tps + fps)
recs = tps / (tps[-1]+fns[-1])
return pres, recs

scores = np.array([0.9, 0.89, 0.8, 0.78, 0.7, 0.7, 0.7, 0.67, 0.62, 0.58, 0.4, 0.3])
labels = np.array([1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1])
pres, recs = get_pre_rec(scores, labels)
print("pres:", pres)
print("recs:", recs)

求得 precision 和 score 以后, 计算 PR 曲线的面积即为 AP 的值
下面是 AP 的代码实现, mAP 只需分别对不同类别执行下述计算即可

固定 Recall 区间 (0, 0.1, …, 1 总共 11 个 点)

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
def average_precision(precision, recall):
if pre is None or rec is None:
return np.nan
psum = 0
for t in np.arange(0., 1.1, 0.1):
if np.sum(recall >= t) == 0:
break # 如果后面已经不存在大于阈值的 recall 了, 则可跳出
else: # 找到大于阈值的所有对应精度中, 最大的那个
psum += np.max(np.nan_to_num(precision)[recall >= t])
return psum / 11.0 # 除以采样点个数(11个)

样本 Recall 区间 (对于每一个不同的 recall 都进行计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
def average_precision_rec(pre, rec):
pre = np.nan_to_num(pre)
# pre = np.concatenate([[0], pre, [0]]) # 添加哨兵元素
pre = np.concatenate([[1], pre, [0]]) # 添加哨兵元素
rec = np.concatenate([[0], rec, [1]]) # 添加哨兵元素

n = len(pre)
for i in range(n-1, 0, -1): # 将pre置为后面最大的
pre[i-1] = max(pre[i-1], pre[i]) # 对于每一个阈值, 我们选取大于该 recal 阈值的最大精度作为积分精度

i = np.where(rec[1:] != rec[:-1])[0] # i = 0 ~ n-2, 找到 rec 变化的点
print((rec[i+1] - rec[i]), pre[i+1]) # 输出相乘元素, 方便检查
return np.sum((rec[i+1] - rec[i]) * pre[i+1])

调用上方代码

1
2
3
4
5
6
import numpy as np
pre = np.array([1, 1, 0.66, 0.5, 0.4, 0.5, 0.43, 0.38, 0.44, 0.5])
rec = np.array([0.14, 0.29, 0.29, 0.29, 0.29, 0.43, 0.43, 0.43, 0.57, 0.71])

print(average_precision(pre, rec))
print(average_precision_rec(pre, rec))

mmAP

给定一组IOU阈值,在每个IOU阈值下面,求所有类别的AP,并将其平均起来,作为这个IOU阈值下的检测性能,称为mAP(比如mAP@0.5就表示IOU阈值为0.5时的mAP);最后,将所有IOU阈值下的mAP进行平均,就得到了最终的性能评价指标:mmAP。

NMS

简述 NMS 的原理

非极大值抑制(Non-Maximum Suppression, NMS), 顾名思义就是抑制那些不是极大值的元素, 可以理解为局部最大值搜索. 对于目标检测来说, 非极大值抑制的含义就是对于重叠度较高的一部分同类候选框来说, 去掉那些置信度较低的框, 只保留置信度最大的那一个进行后面的流程, 这里的重叠度高低与否是通过 IoU 的大小来判断的.

NMS 算法源码实现

算法逻辑:
输入: $n$ 行 $4$ 列的候选框数组, 以及对应的 $n$ 行 $1$ 列的置信度数组.
输出: $m$ 行 $4$ 列的候选框数组, 以及对应的 $m$ 行 $1$ 列的置信度数组, $m$ 对应的是去重后的候选框数量
算法流程:

  1. 计算 $n$ 个候选框的面积大小
  2. 对置信度进行排序, 获取排序后的下标序号, 即采用argsort
  3. 将当前置信度最大的框加入返回值列表中
  4. 获取当前置信度最大的候选框与其他任意候选框的相交面积
  5. 利用相交的面积和两个框自身的面积计算框的交并比, 将交并比大于阈值的框删除.
  6. 对剩余的框重复以上过程

Python 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import cv2
import numpy as np

def nms(bounding_boxes, confidence_score, threshold):
if len(bounding_boxes) == 0:
return [], []
bboxes = np.array(bounding_boxes)
score = np.array(confidence_score)

# 计算 n 个候选框的面积大小
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
areas =(x2 - x1 + 1) * (y2 - y1 + 1)

# 对置信度进行排序, 获取排序后的下标序号, argsort 默认从小到大排序
order = np.argsort(score)

picked_boxes = [] # 返回值
picked_score = [] # 返回值
while order.size > 0:
# 将当前置信度最大的框加入返回值列表中
index = order[-1]
picked_boxes.append(bounding_boxes[index])
picked_score.append(confidence_score[index])

# 获取当前置信度最大的候选框与其他任意候选框的相交面积
x11 = np.maximum(x1[index], x1[order[:-1]])
y11 = np.maximum(y1[index], y1[order[:-1]])
x22 = np.minimum(x2[index], x2[order[:-1]])
y22 = np.minimum(y2[index], y2[order[:-1]])
w = np.maximum(0.0, x22 - x11 + 1) # 这一步千万不要忘了, 因为在求 iou 时, 如果不相交, 则会出现负值
h = np.maximum(0.0, y22 - y11 + 1)
intersection = w * h

# 利用相交的面积和两个框自身的面积计算框的交并比, 将交并比大于阈值的框删除
ratio = intersection / (areas[index] + areas[order[:-1]] - intersection)
left = np.where(ratio < threshold) # 当 left 进行计算时, 如 left+1, 才使用 [0], 详见求 AP 的代码
order = order[left]

return picked_boxes, picked_score

C++ 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <vector>
#include <algorithm>

struct Bbox {
int x1;
int y1;
int x2;
int y2;
float score;
Bbox(int x1_, int y1_, int x2_, int y2_, float s):
x1(x1_), y1(y1_), x2(x2_), y2(y2_), score(s) {};
};

float iou(Bbox box1, Bbox box2) {
float area1 = (box1.x2 - box1.x1 + 1) * (box1.y2 - box1.y1 + 1);
float area2 = (box2.x2 - box2.x1 + 1) * (box2.y2 - box2.y1 + 1);
int x11 = std::max(box1.x1, box2.x1);
int y11 = std::max(box1.y1, box2.y1);
int x22 = std::min(box1.x2, box2.x2);
int y22 = std::min(box1.y2, box2.y2);
float intersection = (x22 - x11 + 1) * (y22 - y11 + 1);
return intersection / (area1 + area2 - intersection);
}

std::vector<Bbox> nms(std::vector<Bbox> &vecBbox, float threshold) {
auto cmpScore = [](Bbox box1, Bbox box2) {
return box1.score < box2.score; // 升序排列, 令score最大的box在vector末端
};
std::sort(vecBbox.begin(), vecBbox.end(), cmpScore);

std::vector<Bbox> pickedBbox;
while (vecBbox.size() > 0) {
pickedBbox.emplace_back(vecBbox.back());
vecBbox.pop_back();
for (size_t i = 0; i < vecBbox.size(); i++) {
if (iou(pickedBbox.back(), vecBbox[i]) >= threshold) {
vecBbox.erase(vecBbox.begin() + i);
}
}
}
return pickedBbox;
}

CUDA 实现:
待补充

Soft-NMS 简介

在 Soft-NMS 中, 对于那些重叠度大于一定阈值的 box, 我们并不将其删除, 而仅仅只是根据重叠程度来降低那些 box 的 socre, 这样一来, 这些 box 仍旧处于 box 列表中, 只是 socre 的值变低了. 具体来说, 如果 box 的重叠程度高, 那么 score 的值就会变得很低, 如果重叠程度小, 那么 box 的 score 值就只会降低一点, Soft-NMS 算法伪代码如下图所示:

Soft-NMS 算法源码实现

计算机视觉-SoftNMS-ICCV2017

算法逻辑:
输入:

  • bboxes: 坐标矩阵, 每个边框表示为 [x1, y1, x2, y2]
  • scores: 每个 box 对应的分数, 在 Soft-NMS 中, scores 会发生变化(对外部变量也有影响)
  • iou_thresh: 交并比的最低阈值
  • sigma2: 使用 gaussian 函数的方差, sigma2 代表 $\sigma^2$
  • score_thresh: 最终分数的最低阈值
  • method: 使用的惩罚方法, 1 代表线性惩罚, 2 代表高斯惩罚, 其他情况代表默认的 NMS

返回值: 最终留下的 boxes 的 index, 同时, scores 值也已经被改变.
算法流程:

  1. 在 bboxes 之后添加对于的下标[0, 1, 2…], 最终 bboxes 的 shape 为 [n, 5], 前四个为坐标, 后一个为下标
  2. 计算每个 box 自身的面积
  3. 对于每一个下标 $i$, 找出 i 后面的最大 score 及其下标, 如果当前 i 的得分小于后面的最大 score, 则与之交换, 确保 i 上的 score 最大.
  4. 计算 IoU
  5. 根据用户选定的方法更新 scores 的值
  6. 以上过程循环 $N$ 次后($N$ 为总边框的数量), 将最终得分大于最低阈值的下标返回, 根据下标获取最终存留的 Boxes, 注意, 此时, 外部 scores 的值已经完成更新, 无需借助下标来获取.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import numpy as np

def soft_nms(bboxes, scores, iou_thresh=0.3, sigma2=0.5, score_thresh=0.001, method=2):
# 在 bboxes 之后添加对于的下标[0, 1, 2...], 最终 bboxes 的 shape 为 [n, 5], 前四个为坐标, 后一个为下标
N = bboxes.shape[0] # 总的 box 的数量
indexes = np.array([np.arange(N)]) # 下标: 0, 1, 2, ..., n-1
bboxes = np.concatenate((bboxes, indexes.T), axis=1) # concatenate 之后, bboxes 的操作不会对外部变量产生影响

# 计算每个 box 的面积
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
x2 = bboxes[:, 2]
y2 = bboxes[:, 3]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)

# for i in range(N):
for i in range(N-1): # 实际上, i = N-1 时, 循环内不会发生任何改变, 因此, 可以令 i 最大循环到 N-2
# 找出 i 后面的最大 score 及其下标
pos = i + 1

maxscore = np.max(scores[pos:], axis=0)
maxpos = np.argmax(scores[pos:], axis=0)
# 因为现在 i 为 0 ~ n-2, 因此无序进行下面的判断, 可以直接利用上面的式子代替
"""
if i != N-1:
maxscore = np.max(scores[pos:], axis=0)
maxpos = np.argmax(scores[pos:], axis=0)
else:
maxscore = scores[-1]
maxpos = 0
"""

# 如果当前 i 的得分小于后面的最大 score, 则与之交换, 确保 i 上的 score 最大
if scores[i] < maxscore:
bboxes[[i, maxpos + i + 1]] = bboxes[[maxpos + i + 1, i]]
scores[[i, maxpos + i + 1]] = scores[[maxpos + i + 1, i]]
areas[[i, maxpos + i + 1]] = areas[[maxpos + i + 1, i]]

# IoU calculate, 注意, 这里使用 bboxes 而不使用 x1, y1, ... 的原因是因为上面只交换了 bboxes, 而没有交换 x1, y1, ...
xx1 = np.maximum(bboxes[i, 0], bboxes[pos:, 0])
yy1 = np.maximum(bboxes[i, 1], bboxes[pos:, 1])
xx2 = np.minimum(bboxes[i, 2], bboxes[pos:, 2])
yy2 = np.minimum(bboxes[i, 3], bboxes[pos:, 3])
w = np.maximum(0.0, xx2 - xx1 + 1)
h = np.maximum(0.0, yy2 - yy1 + 1)
intersection = w * h
iou = intersection / (areas[i] + areas[pos:] - intersection)

# Three methods: 1.linear 2.gaussian 3.original NMS
if method == 1: # linear
weight = np.ones(iou.shape)
weight[iou > iou_thresh] = weight[iou > iou_thresh] - iou[iou > iou_thresh]
elif method == 2: # gaussian
weight = np.exp(-(iou * iou) / sigma2)
else: # original NMS
weight = np.ones(iou.shape)
weight[iou > iou_thresh] = 0

scores[pos:] = weight * scores[pos:]

# select the boxes and keep the corresponding indexes
inds = bboxes[:, 4][scores > score_thresh]
keep = inds.astype(int)
return keep

# boxes and scores
boxes = np.array([[200, 200, 400, 400], [220, 220, 420, 420],
[240, 200, 440, 400], [200, 240, 400, 440],
[1, 1, 2, 2]], dtype=np.float32)
boxscores = np.array([0.9, 0.8, 0.7, 0.6, 0.5], dtype=np.float32)
index = soft_nms(boxes, boxscores, method=2)
print(index) # 按照 scores 的排序指明了对应的 box 的下标
print(boxes[index])
print(boxscores) # 注意, scores 不需要用 index 获取, scores 已经是更新过的排序 scores

其他的 NMS 算法

待补充

R-CNN

R-CNN 简介

  1. Selective Search 提取出候选区域框;
  2. 根据候选区域框与真实框的交并比决定正负样本标签(此时不关心框内物体的类别);
  3. 送入到 AlexNet 中提取 CNN 特征
  4. 将提取到的特征送入到 SVM 分类器中进行分类, 每一个类别都单独训练了一个 SVM 分类器;
  5. 对每一个框进行边框回归, 学习特征图谱候选区域框到真实框的转换, 调整框的位置.

Feature Extraction: AlexNet (5层卷积, 2层FC, 最终特征向量的维度为 4096).

输入图片大小: $227\times 227$.

正负样本划分: 与 gt-box 的 IoU 大于 0.5 的认为是正样本, 反之认为是负样本. 训练时, mini-batch 中正样本采样数为32(over all classes), 负样本的采样数为 96. 负样本数量多是因为在真实情况下, 背景的区域数量远大于物体数量.

分类器: 为每个类别训练了一个 SVM.

Selective Search 简介

首先, 首先利用分割算法(Graph-Based Image Segmentation, 2004, IJCV, 贪心)得到一些初始化的区域, 然后计算每个相邻区域的相似性, 相似性的计算依赖于颜色相似性和纹理相似性, 同时给较小的区域赋予更多的权重, 也就是优先合并小区域(否则大区域有可能会不断吞并周围区域, 使得多尺度之应用了在局部区域, 而不是在每个位置都具有多尺度), 接着找出相似性最大的区域, 将它们合并, 并计算新合并的区域与其他相邻区域的相似性, 重复这个过程, 直到所有的区域被合并完为止.

为什么要 R-CNN 使用 SVM 而不用更加方便的 Softmax 分类器

  • 作者尝试过但是 mAP 从 54.2% 降到了 50.9%
  • 下降的原因是多因素造成的, 比如对正负样本的定义, 再比如在训练 Softmax 时使用的负样本是随机采样的, 而训练 SVM 时的负样本更像是 “hard negatives” 的子集, 导致训练精度更高等等.
  • 后续的 Fast RCNN 使用 Softmax 也达到了和 SVM 差不多的准确率, 训练过程更加简单.

Bounding Box 的回归方式简介

在 R-CNN 的边框回归中, 我们不是直接学习真实框的坐标, 而是学习从 Proposals 到 真实框的一个偏移变换函数, 具体来说, 对于中心点, 需要学习的是 proposal 和 真实框相对位移, 这个位移会用 proposal 的宽和高进行归一化, 对于宽和高, 需要学习的是真实框相对于 proposal 的 log 缩放度.

SummaryOfComputerVision%2Fbbox_regression1.jpg

Bounding box 回归的时候, 为什么不直接对坐标回归, 而是采用偏移量和缩放度

为了获得对物体回归过程的尺度不变性

  • 尺度不变性: 对于不同尺度下的同一个物体, 如果不使用归一化, 那么对于固定的偏移量(像素值), 大物体只会挪动一点, 小物体会挪动很多. 但是由于这是同一物体, 我们得到的特征应该是相似的, 因此这样不合理. 如果使用归一化的偏移量, 那么其偏移程度就与物体尺寸无关, 故而具有尺寸不变性.

平移不变性: 直接对坐标回归, 则回归过程不具有平移不变性, 即对于相同的 GT 和 Prior, 改变位置时, 回归的值应该是不变的, 因此需要对相对偏移量进行回归, 而不是对绝对坐标回归.

SummaryOfComputerVision%2Fbbox_regression2.jpg

边框回归(BoundingBoxRegression)详解
http://caffecn.cn/?/question/160
https://blog.csdn.net/zijin0802034/article/details/77685438

为什么当 Region Proposals 和 Ground Truth 较接近时, 即 IoU 较大时, 可以认为是边框回归函数是线性变换?

当输入的 Proposal 与 Ground Truth 相差较小时(RCNN 设置的是 IoU>0.6), 可以认为这种变换是一种线性变换, 那么我们就可以用线性回归来建模对窗口进行微调, 否则会导致训练的回归模型不 work (当 Proposal跟 GT 离得较远,就是复杂的非线性问题了,此时用线性回归建模显然不合理). 对于这一段的话解释如下:

首先, Log 函数肯定不满足线性函数的定义, 但是根据极限的相关定义, 我们如下面的等式成立:

根据上面的公式, 我们可以对公式 $t_w$ 作如下推导:

从上式我们可以看出, 当 $G_w - P_w = 0$ 的时候, 回归函数 $t_w$ 可以看做是线性函数.

这里还有一点疑问: 从公式来说, $t_x$ 和 $t_y$ 本身就已经是线性函数, 而 $t_w$ 和 $t_h$ 只需要 Proposals 和 Ground Truth 的宽高相似即可满足线性回归条件. 那么为什么必须要 IoU 较大才可以? 不是只要宽高相似就可以吗?

http://caffecn.cn/?/question/160
边框回归(BoundingBoxRegression)详解

R-CNN 缺点

  • 训练过程是分阶段的(Training is a multi-stage pipeline)
  • 耗费资源(Training is expensive in space and time)
  • 目标检测速度太慢(Object detection is slow)

SPPNet

SPPNet 简介

(1) 提出了一种新的池化方法—-空间金字塔池化SPP:

  • 可以接受任意尺寸的输入图片,并生成固定长度的表征向量
  • 可以进行多尺度的联合训练, 提升模型精度
  • 这种池化方法是比较general的, 可以提升不同模型架构的性能(分类任务)

SPPNet实现原理如下图所示:

SPPNet%2Ffig3.jpg

首先, 设定好固定的网格划分方法, 以便得到spatial bins, 如上图, 有三种不同spatial bins, 网格划分粒度分别为 4×4, 2×2 和 1×1, 因此, spatial bins的数量为:$4\times 4+2\times 2+ 1\times 1 = 21 = M$, 图中的256代表卷积层输出的特征图谱的通道数量, 也就是特征图谱的 depth = $k$. 因此, SPP层的输出为 $kM$ 维的一维向量.

注意: 这里最粗粒度的spatial bins 是对整张特征图谱上进行pooling, 这实际上是为了获得一些全局信息.(之前也很很多work集成了这种全局pooling方法, 貌似有助于提升精度, 同时由于是全局信息, 所以相当general, 可以一定程度上起到降低过拟合的作用)

(2) 将SPP用于目标检测, 并且提出了先求卷积特征图谱, 然后在特征图谱上取区域的的策略:

  • 大大提升了模型训练和预测的速度(在预测阶段, 比RCNN快24~102倍, 同时取得了更好的精度).

注1: 在特征图谱上使用检测方法不是首次提出, 而SPP的贡献可以结合了deep CNN结构强大的特征提取能力和SPP的灵活性, 使得精度和速度同时提高.
注2: 相比于RCNN, SPPNet使用了EdgeBoxes( $0.2s/img$ )的方法来进行候选区域推荐, 而不是Selective Search( $1\sim 2s/img$ )
注3: SPPNet在ILSVRC2014的目标检测任务上取得第二名, 在图片分类任务上取得第三名

SPPNet 缺点

  • 训练过程是分阶段的(Training is a multi-stage pipeline)
  • 无法 Fine-Tuning 金字塔池化层之前的卷积层

Fast R-CNN

Fast R-CNN 有哪些改进

  • 直接采用在特征图谱上的候选框组成 mini-batch, 共享卷积计算结果, 大大加速训练和推演速度.
  • 提出了 RoI Pooling, 从而可以在特征图谱上截取任意尺寸的候选框
  • 采用 Smooth L1 损失, 使得训练过程中对于回归值的离异点鲁棒性更好, 同时分类损失从用 Softmax 交叉熵, 将二者进行联合训练, 训练过程更加统一.
  • 对于计算量较大的全连接层, 使用奇异值分解加速计算

RoI Pooling 简介

RoI Pooling Layer 使用 max pooling 来将 任意尺寸 的有效感兴趣区域中的特征转换成一个具有 固定尺寸 $H\times W (e.g., 7\times 7)$的较小的 feature map, 这里的 $H$ 和 $W$ 是超参数. 在本文中, 一个 RoI(感兴趣区域)就是 feature map 上面的一个矩形窗口. 每一个 RoI 都通过四元组 $(r,c,h,w)$ 来表示(top-left corner, and its height and width).

RoI Pooling的前向传播过程如下:

FastR-CNN%2Froi_pooling1.jpg

对于任意给定尺寸为 $h\times w$ 的feature map的 RoI 窗口, 将其划分成 $W\times H$ 的网格大小(上图中的示例为 $W\times H= 3\times 3$ ), 这样, 每一个网格 cell 中的尺寸大约为 $h/H \times w/W$(就近取整), 然后我们在网格 cell 中执行max pooling操作. 和标准的 max pooling 相同, RoI pooling 在卷积图谱上的各个通道之间是独立计算的. 这样, 对于任意size的输入, 都可以获得固定长度的输出. 可以看出, RoI layer 实际上是 spatial pyramid pooling layer 中的一个特例, 即只有一个 pyramid level. (但是相比于金字塔池化, RoI 池化只确定了唯一大小的 pooling 窗口, 这使得我们可以利用反向传播更新池化层之前的网络层参数, 进而提高准确率)

RoI Pooling 如何进行反向传播

常规的 RoI Pooling 的反向传播过程如下:

上式中, x_i 代表池化前的特征图谱上的像素点, $y_{rj}$ 代表池化后的第 $r$ 个候选区域的第 $j$ 个点, $i^\ast (r, j)$ 代表第 $y_{rj}$ 映射回池化前特征图谱上的位置. 因此, 对于池化前的特征图谱上的每一个像素点的梯度, 为所有 RoI 在池化过程中对应的位置传回梯度的总和.

RoI Pooling 的反向传播过程和 Max Pooling 类似, 不同的是, 对于每个 mini-batch 的 RoI $r$ 和每个 pooling 单元 $j$ 及其输出 $y_{rj}$ ,偏导数 $\partial L / \partial y_{rj}$ 是所有 RoI 反向传播回来的累加和. 具体如下图所示, 当不同的 RoI 区域出现重叠时, 恰好这两个区域都选取了 $x_{2, 3}$ 作为激活点, 那么对这个点的反向传播值 $\frac{\partial L}{\partial x_{2, 3}}$ 就应该等于 $\frac{\partial L}{\partial y_{0,2}} + \frac{\partial L}{\partial y_{1, 0}}$.

FastR-CNN%2Froi_pooling3.jpg

为什么 RoI Pooling 比 SPP 效果好

SPP的Pooling方式是组合不同划分粒度下feature map的max pooling. 它也具有和 RoI Pooling 类似的效果, 可以接受任意尺度的特征图谱, 并将其提取成固定长度的特征向量, 但是 SPPNet 的池化方式使得无法用恰当的方式来进行反向传播, 因此 SPPNet 没有对浅层的网络进行 fine-tuning, 而是直接在最后两个全连接层上进行fine-tune, 虽然最后也取得了不错的成果, 但是Roos认为, 虽然离输入层较近的前几层卷积层是比较generic和task independent的, 但是靠近输出层的卷积层还是很有必要进行fine-tune的, 他也通过实验证实了这种必要性, 于是他简化了SPP的Pooling策略, 用一种更简单粗暴的Pooling方式来获得固定长度的输出向量, 同时也设计了相应的RoI Pooling的反向传播规则, 并对前面的基层卷积层进行了fine-tune, 最终取得了不错的效果.

Fast R-CNN 的 Multi-task Loss

Multi-task loss: 下式中, $L_{cls}(p,u) = - log p_u$, 即对于真实类别 $u$ 的 log 损失.

Smooth L1 相比于 L2 损失, 在回归时有什么优势

smooth L1 损失是一种鲁棒性较强的 L1 损失, 相比于 R-CNN 和 SPPNet 中使用的 L2损失, 它对离异点的敏感度更低. 当回归目标趋于无限时, L2 损失需要很小心的处理学习率的设置以避免发生梯度爆炸, 而 smooth L1 损失则会消除这种敏感情况.

相比于 $L_2$ 损失, $L_1$ 损失对于离异值更加鲁棒, 当预测值与目标值相差很大时, 梯度很容易爆炸, 因为梯度里面包含了 $(t_i^u - v_i)$ 这一项, 而smooth L1 在值相差很大时, 其梯度为 $\pm 1$ ( $L_1$ 在 $x$ 绝对值较大时, 是线性的, 而 $L_2$ 是指数的, 很容易爆炸).

公式(1)中的超参数 $\lambda$ 用于平衡两种损失之间的影响力. 默认情况下 $\lambda = 1$.

SVG 奇异值分解简介

对于一个权重矩阵为 $u\times v$ 的全连接层来说, 该矩阵可以被近似的因式分解为:

式中, $U$ 是一个 $u\times t$ 的矩阵, $\Sigma_t$ 是一个 $t\times t$ 的对角矩阵, 包含着矩阵 $W$ 的值最大的 $t$ 个奇异值, $V$ 是一个 $v\times t$ 的矩阵. 可以看到, 奇异值分解将矩阵 $W$ 的参数量从 $uv$ 降低到了 $ut+tv$, 这个 $t$ 就是奇异矩阵中的奇异值数量, 奇异值有一个非常重要的性质, 就是它的下降速度很快, 在很多情况下, 前 10% 甚至 1% 的奇异值的和就站了全部奇异值之和的 99% 以上的比例. 也就是说, 我们可以用最大的 $k$ 个奇异值来近似描述矩阵. 由于 $k$ 远远小于 $\min(u,v)$, 因此可以大大节省参数量. 在实现上, 将单个的全连接网络层的权重矩阵 $W$ 用两层全连接层所替代, 注意在这两层全连接层中间没有非线性激活函数. 第一个全连接层使用的权重矩阵为 $\Sigma_t V^T$ (没有偏置项), 第二个权重矩阵为 $U$ (带有原始矩阵 $W$ 的偏置项).
关于奇异值分解更详细的介绍可以看 奇异值分解解析

Faster R-CNN

注意, RPN 网络和 Fast R-CNN 网络各自可以进行端到端的训练, 但是 Faster R-CNN 网络并非是端到端的, 它需要结合 RPN 和 Fast R-CNN 网络才可以工作

Faster R-CNN 简介

Faster R-CNN 主要由两部分组成. 其一是用于生成候选区域框的深度全卷积网络, 其二是 Fast R-CNN 检测模型. 二者在训练的时候会进行参数共享.

RPN 主要是通过在 BackBone 网络输出的特征图谱上设置一组固定大小的 anchor boxes 实现. 对于图谱上的每一个像素点, 都会枚举 k 个具有预设尺寸的 anchor boxes. 对于一个 $W\times H$ 大小的特征图谱, 总共会产生 $WHk$ 个 anchor boxes. 对于每一个 anchor box, 我们需要预测 (4+2) 个值, 分别代表 location 的偏移量和是否包含物体的二分类预测.

在实现上, RPN 通常会先用一个 3x3 的卷积层融合 BackBone 传来的特征图谱, 并且保持尺寸不变和通道数不变(通道数也可以变小, 节省计算量), 依然为 $W\times H\times C$. 然后是两个并行的 1x1 卷积构成的 branch, 分别进行回归预测和分类预测, 其输出分别为 $W\times H\times 4k$ 和 $W\times H \times 2k$(这里的 two-class 是为了实现方便, 因此使用了 softmax, 对于二分类来说, 也可以使用 Sigmoid, 这样就只需要输出 $k$ 个 scores).

FasterR-CNN%2Fanchor.jpg

FasterR-CNN%2Ffig3.jpg

正负样本标定策略

我们会根据 RPN 网络的输出结果, 选取 128 个 positive anchor boxes 和 128 个 negative anchor boxes 参与 Fast R-CNN 网络的训练, 注意, 在 Fast R-CNN 网络中, 然后会有一次 location regression 操作, 因此, Faster R-CNN 网络实际上进行了两次 location regression.(同理也进行了两次 NMS)

我们通常利用下面的方式确定 anchor 属于正样本还是负样本

  • 正样本:
    • 和某个 GT box 具有 最大(不一定大于0.7) 的 IoU (防止有些 GT boxes 没有匹配的 anchor)
    • 和某个 GT box 的 IoU 大于0.7
  • 负样本:
    • 和所有的 GT boxes 的 IoU 都小于 0.3

注意1: 对于剩下的既不是正样本也不是负样本的 anchor boxes, 不参与训练过程
注意2: 一个 GT box 可能会与 多个 anchor boxes 相匹配
注意3: 一个 anchor box 只能与 一个 GT box 相匹配

损失函数

上式中, $i$ 代表 mini-batch 中 anchor 的下标, $p_i$ 代表预测 anchor $i$ 是一个物体的可能性大小. 如果 anchor 是正样本, 则真实标签 $p^\ast_i$ 为1, 反之, 如果是负样本, 则为0. $t_i$ 是一个 vector, 用来表示参数化以后的边框坐标, $t^\ast_i$ 是正样本 anchor 对应的真实框的参数化坐标. 分类损失函数 $L_{cls}$ 是一个二分类 log 损失. 对于回归损失, 我们使用 $L_{reg}(t_i, t^\ast_i) = R(t_i - t^\ast_i)$, 这里 $R$ 代表 robust loss function(smooth L1). $p^\ast_i L_{reg}$ 代表回归损失仅仅会被正样本的 anchor 所激活.
$N_{cls}$ 和 $N_{reg}$ 是两个归一项, 同时 $\lambda$ 会调节每一项的权重. 在本文的代码中, $N_{cls}$ 设定为 mini-batch 的大小(256), $N_{reg}$ 设定为 anchor locations 的大小(约2400). 同时 $\lambda$ 默认为10, 这样, $cls$ 和 $reg$ 所占的比重大致相等. 我们在表9中给出了 $\lambda$ 值在很大范围内对结果的影响并不敏感(这是比较好的, 说明我们不需要过度调整该超参的值). 同时, 我们发现归一化也不是必须的, 可以被简化.

共享参数训练方法

  • 4-Step Alternating training(四步式交叉训练, 实验默认训练方法):
    1. 用 ImageNet 初始化 RPN, 训练 region proposals 任务直至收敛(得到一个不错的边界框生成器)
    2. 使用第一步生成的 proposals 来训练一个单独的 Fast R-CNN 网络. 这个检测网络同样也是用 ImageNet 预训练的模型进行初始化. 到这里为止, 这两个网络还没有共享卷积层.
    3. 第三步, 使用检测网络的参数对 RPN 网络进行初始化, 但是此时 我们固定住共享的卷积层(backbone), 仅仅 fine-tuning 属于 RPN 独有的那些网络层.
    4. 利用新的 RPN 产生的 proposals, 训练 Fast R-CNN, 同样固定住共享的卷积层, 仅仅 fine-tuning 属于 Fast R-CNN 独有的网络层.
  • Approximate joint training(近似联合训练, 包含在官方代码中)
    将 RPN 和 Fast R-CNN 在训练期间合并到一个网络中, 在每个 SGD 迭代过程, 前向计算会先由 RPN 生成 proposals (修正后的), 然后这些 proposals 会作为输入送到 Fast R-CNN 中, 在反向传播的时候, 对于共享层的参数更新, 会同时考虑来自 RPN 损失和 Fast R-CNN 损失传递过来的信号. 但是这种策略忽略了相对于 proposals boxes 坐标的导数, 因此这只是一种粗略的联合训练方式. 在实验中, 近似联合训练可以大幅减少训练时间(25~50%), 精度会有所损失, 但是性能依然客观.
  • Non-approximate joint training(非近似联合训练)
    正如上面讨论的, RPN 预测的 bounding boxes 的坐标同样与输入数据之间存在联系. 在 Fast R-CNN 的 RoI pooling Layer 中会接受卷积特征, 同时也会将预测的 bounding boxes 作为输入, 因此理论上来说, 一个有效的优化器(backpropagation solver)应该包含相对于 box coordinate 的梯度. 因此, 我们需要 RoI pooling layer 相对于 box coordinates 是可导的.

Ross 原话:

it is actually very easy to train RPN + Fast R-CNN approximately* jointly. I’m planning on adding prototxt files and training scripts for how to do this soon. The mAP ends up about the same, but the training time is reduced by about 50%.

  • “approximately” because the RoI pooling layer is not differentiable w.r.t. the RoI coordinates and therefore gradients of the objective w.r.t. the RoI coordinates cannot be passed back through the network. This is likely a minor issue.

如果两个物体重合度很高, Faster R-CNN 会怎么样?

在 RPN 阶段, 优势 proposals 是类别不可知的(class-agnostic)
因此重叠度非常高的两个框会被 NMS 过滤掉. 因此对于重合度非常高的两个物体, 很有可能会发生漏检

注, 但是在 Detection 阶段, 每个 anchor boxes 都与一个 GT 对应, 并且会预测特定的类别, 在进行 NMS 的时候, 是对每个类别分别进行 NMS, 所以, 也有一定的概率能够检测成功, 不过可能置信度较低, 同时框的 IoU 水平也较低(因为较高的 IoU 已经在 RPN 阶段被 NMS 掉了)

Faster R-CNN 由两部分组成, 分别为 class-agnostic 的 RPN, 以及 class-specific 的 detection

Mask R-CNN

Mask R-CNN 简介

Mask R-CNN 的大体框架还是 Faster R-CNN, 它在 Faster R-CNN 模型中添加了一个与分类和回归分支平行的掩膜预测分支. 掩膜分支(mask branch) 是一个小型的 FCN 网络, 它会作用在每一个 RoI 上, 以像素到像素的方式来预测一个分割掩膜. Mask R-CNN 的掩膜预测分支对于每一个 RoI 的输出维度为 $Km^2$, 也就是每一个类别都会单独输出一个 $m\times m$ 大小的掩膜. 在预测掩膜时非常关键的一点就是要对分类任务和分割任务解耦, 否则对于多分类任务会引起类别之间的竞争, 因此, Mask R-CNN 使用了基于单像素的 sigmoid 二值交叉熵来替换基于单像素的 Softmax 多项式交叉熵. 另外, 在 Faster R-CNN 中使用的 RoI pooling 需要经过两次量化取整(图像坐标到特征图谱坐标, 特征图谱划分固定网格)才能获得固定长度的特征向量. 这种粗糙的量化操作会造成 RoI 和特征图谱之间的不对齐, 这对精度要求较高的的分割任务来说有较大影响. 为了克服这个问题, Mask R-CNN 提出了 RoI Align 层来替代 RoI Pooling, RoI Align 的核心思想就是避免在 RoI 边界上或者 bins 中执行任何量化计算. 它在处理每一个 RoI 的时候, 会保持其浮点边界的值而不进行量化操作, 然后将 RoI 划分成的 $k\times k$ 大小的网格, 对于每一个网络, 都会固定四个采样点, 并利用双线性插值法来计算每个采样点的数值, 最后根据这些数值进行池化操作. 除了这些比较重要的点之外, Mask R-CNN 也有一些其他的优化, 比如说更多的 anchor, 更大的 batch size, 更强的 backbone 网络(ResNeXt+FPN)等等.

MaskR-CNN%2Ffig4.jpg

RoI Align

由于掩膜预测分支是像素级别的二分类网络, 因此它对于 proposals 的精度要求更多, 而原先的 RoI Pooling 存在两次量化操作, 使得位置映射关系较为粗糙, 为此, MaskRCNN 利用双线性插值法消除量化, 提出了 RoI Align 进行改进.

RoI Pooling存在两次量化过程:

  • 将 RoI 坐标量化为特征图谱上的整数坐标值(使用 $[x/16]$ 计算 RoI 在特征图谱上的坐标, 16 是总步长, $[]$ 代表四舍五入)
  • 将量化后的 RoI 分割成 $k\times k$ 个bins, 并对每一个 bin 的边界量化, 使其与特征图谱的整数坐标对应.

要将下图中的 RoI 均等分成 4 块是做不到的, 因此, RoI Pooling 会就近取整. 这是第二次量化, 第一次量化是将 RoI 与 feature map 上的网格对齐

MaskR-CNN%2Froi_pooling.jpg

可以看出, 上面的量化操作是一种很粗糙的 Pooling 方式, 由于 feature map 可以看做是对原图特征的高度概括信息, 所以 feature map 上的细微差别映射回原图时, 往往会导致产生很大的像素位移差. 故此, 提出了RoI Align的解决思路: 取消量化操作(即, 我们使用 $x/16$, 而不是 $[x/16]$), 使用双线性内插的方法获得坐标为浮点数的像素点上的图像数值, 从而将整个特征聚集过程转换为一个连续的操作. 其具体流程如下(假设 RoI 使用 2x2 bins):

  1. 计算 RoI 在特征图谱上的浮点坐标 $x/16$
  2. 在每一个 bin 当中, 均匀的选取若干个(实验效果显示 4 个较好)采样点
  3. 对于每一个采样点, 利用离它最近的四个 feature map 点计算当前采样点的值
  4. 对每个 bin 中的所有采样点, 分别执行 max / average 操作.
  5. 最终输出 $k \times k$ 的固定尺寸特征图谱 (如 2x2).

MaskR-CNN%2Ffig3.jpg

这里对上述步骤的第三点作一些说明:这个固定位置是指在每一个矩形单元(bin)中按照固定规则确定的位置。比如,如果采样点数是1,那么就是这个单元的中心点。如果采样点数是4,那么就是把这个单元平均分割成四个小方块以后它们分别的中心点。显然这些采样点的坐标通常是浮点数,所以需要使用插值的方法得到它的像素值。在相关实验中,作者发现将采样点设为4会获得最佳性能,甚至直接设为1在性能上也相差无几。事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多,但却可以获得更好的性能,这主要归功于解决了misalignment的问题。值得一提的是,我在实验时发现,ROI Align在VOC2007数据集上的提升效果并不如在COCO上明显。经过分析,造成这种区别的原因是COCO上小目标的数量更多,而小目标受misalignment问题的影响更大(比如,同样是0.5个像素点的偏差,对于较大的目标而言显得微不足道,但是对于小目标,误差的影响就要高很多

RoI Align 的反向传播过程

常规的 RoI Pooling 的反向传播过程如下:

上式中, x_i 代表池化前的特征图谱上的像素点, $y_{rj}$ 代表池化后的第 $r$ 个候选区域的第 $j$ 个点, $i^\ast (r, j)$ 代表第 $y_{rj}$ 映射回池化前特征图谱上的位置. 因此, 对于池化前的特征图谱上的每一个像素点的梯度, 为所有 RoI 在池化过程中对应的位置传回梯度的总和.

类比 RoI Pooling, RoI Align 的反向传播需要作出稍许修改. 首先, 在 RoI Align 中, $x_{i^\ast(r,j)}$ 是一个浮点数的坐标位置(前向传播时计算出的采样点), 在池化前的特征图中, 每一个与 $x_{i^\ast(r, j)}$ 横纵坐标均小于 1 的点都会接受与次对应的点 $y_{rj}$ 回传的梯度. 故 RoI Align 的反向传播公式如下:

上式中, $d(.)$ 表示两点之间的距离, $\Delta h$ 和 $\Delta w$ 表示 $x_i$ 与 $x_{i^\ast (r, j)}$ 横纵坐标的插值, 这里作为双线性内插的系数乘在原始梯度上.

Mask 分支怎么实现的

可以用多层感知机(若干全连接层)或者全卷积网络(若干卷积层)实现, 如下图中(e)所示, 卷积层由于具有空间编码的优势, 因此效果更好.

MaskR-CNN%2Ftab2.jpg

其他

  • 基于 Faster R-CNN 的基本结构, 替换 backbone 为 ResNet-50/101-C4 和 ResNet-50/101-FPN, ResNeXt-101-FPN.
  • 除了边框回归和目标分类两个分支外, 新添加了一个全卷积的 Mask Prediction 网络, 该分支在每一个 RoI 上的输出维度为 $K\times m^2$, 代表 $K$ 个类别的二值掩膜.
  • 在计算 mask 的损失时, 使用的是 sigmoid 的二分类损失, 而不是多分类的 softmax 损失, 这减少了类别之前的竞争, 使得可以生成更好的分割结果.
  • RoIAlign 消除了 RoI Pooling 中的两次量化操作, 使得最终提取到的特征可以 RoI 尽可能的对齐. 从而大幅提升在实例分割任务上的性能表现.

图1

FPN

FPN 简介

将最后一层特征图谱进行不断尽快上采样, 并与每一个金字塔阶级的特征图谱进行加法合并操作, 得到新的表征能力更强的不同金字塔层次的特征图谱, 然后将RoI按照尺寸分别映射到这些特征图谱上, 再在每个特征图谱上进行类别和位置预测. 在横向连接 $\{C_2, C_3, C_4, C_5\}$ 与 $\{P_2, P_3, P_4, P_5\}$ 时, 我们使用 element-wise add 操作. 然后我们使用一个 3x3 的卷积对每一个融合后的图谱进行操作以生成最终的金字塔图谱, 这是为了消除上采样的混叠效应(aliasing effect of unsampling??), 由于金字塔的每一层都使用了共享的分类器和回归器(就像传统的 featurized image pyramid 一样), 因此我们固定了其输出深度 $d$(numbers of channels), 本文中, 我们令 $d=256$, 也就是说所有 额外添加的卷积层 的输出通道数都是 256.

可以直观感受到, 这种多尺度的特征图谱在面对不同尺寸的物体时, 具有更好的鲁棒性, 尤其是在面对小型物体时. 同时, 这种特征金字塔结构是一种通用的特征提取结构, 可以应用到不同的网络框架中, 显著提高(5~8%)模型的召回率(因为提出了更多不同尺度, 不同特征信息的anchor box), 并且可以广泛提高(2~3%)模型的mAP.

思想: 浅层特征负责感知和检测小物体, 但是欠缺足够深度的高级语义信息, 因此将具备深层语义信息的特征层通过反卷积的方式扩大 feature map 的 size, 然后结合浅层和深层的特征图谱来进行预测.

FPN%2Ffig3.jpg

FPN 的使用(RPN, Fast R-CNN)

  • FPN for RPN:
    对于特征图谱 $\{P_2, P_3, P_4, P_5, P_6\}$, 为其分别分配不同大小的 anchors ${32^2, 64^2, 128^2, 256^2, 512^2}$, 然后利用一个 共享的 rpn heads 进行训练和预测
  • FPN for Fast R-CNN:
    将FPN用于FastRCNN时, 我们需要在不同的金字塔层次上赋予不同尺度的 RoI 大小(因为 RoI pooling 是根据特征图谱和原图的尺寸关系决定的), 较小的 RoI, 会被分配到较浅的网络层, 因为太深的图谱可能已经不包含小物体信息了. 注意, detect heads 在所有层级上的权重都是共享的

FCN

FCN 简介

FCN 是分割模型, 这里是为介绍 R-FCN 做的铺垫

FCN 将网络中的全连接层全都换成了卷积层(卷积核大小为 $1\times 1$, 通道数为 FC 神经元个数), 这么做有两个好处:

  1. 可以接受任意尺寸的图片输入
  2. 对于较大的图片输入, 最终会产生多个卷积块, 共用一套权重, 减少重复计算, 从而可以加快模型的计算速度.

在进行分割任务时, FCN 会利用 反卷积跳跃连接 进行像素预测. 也就是说它会将深层图谱的预测结果和 upsample 后的图谱预测结果融合(采用 max fusion), 这样预测出来的结果会更加精细, 在 FCN 中, 分别在步长为 16 和 步长为 8 的特征图谱进行了分割预测, 结果也显示越精细的特征图谱, 分割的结果也越好. 只不过随着步长的缩短, 获得的提升也慢慢变小了.

FCN%2Ffig2.jpg

在 pool4 上添加一个 1x1 卷积来生成额外的 class predictions, 然后在 conv7 (stride 32) 上使用 2x upsampling 来获得 2x upsampled prediction, 最后将二者结合, 如下所示

FCN%2Ffig3.jpg

反卷积层相对于双线性插值来说, 具有可学习的参数, 可以通过网络的输出 loss 动态的调整自身的上采样过程

R-FCN

R-FCN 简介

R-FCN 的主要贡献在于解决了“分类网络的位置不敏感性(translation-invariance in image classification)”与“检测网络的位置敏感性(translation-variance in object detection)”之间的矛盾,在提升精度的同时利用“位置敏感得分图(position-sensitive score maps)”提升了检测速度。
对于 two-stage 检测网络来说, 在 RoI Pooling 之前的 backbone 网络层的计算时被所有 RoI 所共享的, 而 RoI Pooling 之后的计算时单独对每个 RoI 进行分类和回归, 因此虽然各个 RoIs 的计算存在大量的重复, 依然无法共享这部分计算. 而对于前面的 backbone 网络来说, 它本身不会考虑坐标信息, 因此具有 “位置不敏感性(translation-invariance)”, 而 RoI Pooling 之后的网络, 它会考虑目标的位置信息, 因此具有 “位置敏感性(translation-variance)”. 为了让目标检测网络具有 “位置敏感性”, Faster R-CNN 采取了一种不太自然的做法, 那就是将 RoI Pooling 插在了 backbone 网络的卷积层之间, 对于 ResNet 来说, 它将 RoI Pooling 插在了 C4 卷积段的后面, 这样带来的问题就是 RoI Pooling Layer 之后的 C5 卷积段是无法在 RoIs 上共享卷积计算的, 牺牲了速度来换取精度. 如下表1中所示.

R-FCN%2Ffig1_tab1.jpg

为了解决上述问题, R-FCN (Region-based Fully Convolutional Network) 使用 position-sensitive score map 来编码每个物体的 RoI 信息, 它将 RoI 划分成 $k\times k$ 子区域, 每个子区域代表了 RoI 的某一特定部位. R-FCN 首先会共享 backbone 的所有卷积层, 然后在共享的卷积层的最后接上一层卷积, 这个卷积层的输出图谱就是 position-sensitive score map, 它的 height, width 保持不变, 其 channels = $k^2(C+1)$, $C+1$ 代表物体类别, 也就是说每个类别都有 $k^2$ 个 score maps, 每个 score map 都对应了物体某一特定部位的响应值.
然后利用 Position-sensitive RoI Pooling 对这个尺度为 $W\times H \times k^2 (C+1)$ 的特征图谱进行池化, 池化时对于每个 RoI 的第 $i$ 个子区域, 我们就在第 $i$ 个 score map 上的对应区域采用池化来得到 RoI 第 $i$ 个子区域的值, 对于每一个类别都是如此, 最终, PsRoI Pooling 的输出就是一个 $k^2\times (C+1)$ 大小的特征图谱(注意 RoI Pooling 的输出尺寸是用划分的网格数决定的).
最后, 我们对得到的 $k^2\times (C+1)$ 大小的特征图谱通过取 平均值 的方式来投票, 得到 $(C+1)$ 维的向量, 然后利用 softmax 得到每个类别的预测概率.
对于回归操作, 其方法类似, 通过在回归分支上添加一个卷积层实现, 卷积层的输出尺寸是 $W\times H\times 4k^2$, 然后经过 Ps-RoI Pooling, 对每个 RoI 都输出 $k^2 \times 4$ 尺寸的特征图谱, 再经过取平均投票的方式, 得到一个 $4-d$ 的向量, 该向量就代表了这个 RoI 所对应的偏移量 $t = (t_x, t_y, t_w, t_h)$

R-FCN 的缺点: 对于遮挡情况效果很差, 因为当物体被遮挡时, 其关键部位往往被被识别成遮挡物体, 使得无法正确识别被遮挡物体, 详情见 CoupleNet 解释.

R-FCN%2Ffig2.jpg

CoupleNet

CoupleNet 简介

R-FCN 利用 PSRoI Pooling, 利用物体的局部特征, 将位置敏感行引入到了检测网络中, 但是同时, PsRoI Pooling 的设计在一定程度上忽略了物体的整体结构信息以及它的 Global Feature. 因此 CoupleNet 提出利用 RoI Pooling 捕捉物体的 Global Feature, 并将其与 PsRoI Pooling 提取到的 local feature 相结合, 共同决定最终的预测结果. 其网络整体结构如下图所示. CoupleNet 在 C4 阶段利用 RPN 网络提取 Proposals, 然后在 C5 阶段之后, 分成了两个分支:

  1. Local FCN: 由 PSRoI Pooling 和相应的预测网络组成, 用于提取 local feature, 就像 R-FCN 中的那样
  2. Global FCN: 由 RoI Pooling 和相应的预测网络组成, 其中, RoI Pooling 会分别在原始的 RoI 和 扩大两倍 后的 RoI 上提取特征, 然后将结果在通道维度上连接. 这样做不仅可以提取到该物体的全局特征, 同时也可以提取到物体的 context 信息, 这对于解决遮挡类问题有很大帮助.

CounpleNet%2Ffig2.jpg

CoupleNet 是怎么结合 Local Feature 和 Global Feature 的

Normalization:

  1. L2 归一化: 会损伤性能
  2. 1x1 卷积自动学习合适的输出: 输入输出 Tensor 形状不变, 每个值都根据学习到的结果进行变化. 效果好, 提升 0.6 个百分点.

Coupling Structure(结合 local 和 global 分支):

  1. element-wise sum: 在任何情况下有最优, 即使在不使用 Normalization 的情况下, 也是最优选择
  2. element-wise product: 很差
  3. element-wise maximum: 不如 sum

CounpleNet%2Ftab1.jpg

CoupleNet 为什么可以解决遮挡类问题

以下图为例, 沙发坐了了两个人, 当我们对检测沙发时, PSRoI Pooling 会给出非常低的置信度, 因他目标框内的各个部位都不是沙发的特定部位, 而是其他类别的物体, 因此, R-FCN 对于遮挡问题表现很差. 对于 RoI Pooling 来说, 由于它在一定程度上考虑了 RoI 的整体特征, 因此, 可以给出一定的置信度, 但是也只有 0.45.
而 CoupleNet 一方面使用 PSRoI Pooling 解决了分类网络位置不敏感和检测网络位置敏感性之间的矛盾, 同时利用 global 信息和 context 信息弥补了 PSRoI Pooling 只关注 local feature 的缺点, 因此在结合两个分支的输出结果以后, 可以很好的检测出被局部遮挡的物体.(人坐在沙发, 椅子, 桌子前等等)

CounpleNet%2Ffig1.jpg

Deformable ConvNets V1

Deformable ConvNets V1 简介

Deformable ConvNet 从目标检测任务中物体的几何形变角度发出, 在神经网络中引入了具有学习空间几何形变能力的可形变卷积网络(convolutional neutral networks). 该网络主要由两个模块组成, 分别是 deformable convolution 和 deformable RoI. 对于可形变卷积来说, 通过在每个卷积核的采样点上加一个偏移量来达到更好的采样效果. 对于可形变 RoI pooling 来说, 通过对传统的 RoI bins 添加一个偏移量还使得 RoI pooling 中的窗口具有能够适应几何形变的效果.
Deformable ConvNet 一个比较好的性质就是它不会对原有检测模型的整体结构进行更改, 也不会增加过多的计算量, 因此可以相对容易的添加到现有的检测模型当中, 同时还可以和其他多种提升精度的 trick 叠加使用.

Deformable 模块具体是怎么实现的

csdn
github official implementation

Deformable Convolution: 在原始特征图谱平面的每一个 location 上都生成一组与卷积核采样点个数相关的偏移量

  1. 对于一个形状为 $W\times H \times C$ 的特征图谱, 我们将其作为输入, 添加一个新的卷积层, 这个卷积层的输出形状为 $W\times H \times 2N$, 即为 offset filed, 其中 $N = W_k \times H_k$ 代表卷积核平面上的采样点个数, $N = \vert R \vert, R = \{ (-1, -1), (-1, 0), …, (0, 1), (1, 1)\}
  2. 在计算某一个的输出值时, 会根据 offset filed 的偏移量来决定新的采样点 position
  3. 利用双线性插值重新计算每个 position 的值(因为加了偏移量后的新 position 是浮点的, 所以利用双线性插值根据四角的值来算这个 position 的值)
  4. 得到所有 position 的值后, 就利用卷积计算得到该点的结果.
  5. 对所有的卷积计算都执行类似的过程, 直到结算结束.

DCN%2Ffig2.jpg

Deformable RoI Pooling:

  1. 先利用普通的 RoI Pooling 得到池化后 $k\times k$ 大小的特征图谱
  2. 利用一个 FC 层生成归一化的 offsets
  3. 将 offsets 应用到对应的 bins 中得到新的像素值.

DCN%2Ffig3.jpg

Deformable Ps RoI Pooling:

DCN%2Ffig4.jpg

Deformable ConvNets V2

Deformable ConvNets V1 存在哪些问题

DCNv1 虽然可以适应不同几何形变的物体形状, 但是它存在的问题就是学习到的 offsets 不可控, 进而导致引入了过多的 context, 而这些 context 对于网络来说属于干扰信息, 是有害的

Deformable ConvNets V2 做了哪些改进

  1. 增加了更多的 Deformable Convolution
    DCNv1 中只有 ResNet C5 中有 Deformable Conv(共 3 个), 而在 DCNv2 中把 C3~C5 的 3x3 conv 都换成了 Deformable Conv(共 12 个)
  2. 让 Deformable Conv 不仅能够学习 offsets, 还能够学习每个采样点的权重, 就是文中的 modulation 机制
    在 DCNv1 中, Deformable Conv 只学习 offset:而在 DCNv2 中, 还加入了对每个采样点的权重 $\Delta m_k$:这样做的好处是对采样点值的选取起到了更灵活的规范作用, 对于一些不想要的采样点, 只需令 $\Delta m_k$ 学成 0 即可.
  3. 利用知识蒸馏让 DCNv2 来模拟 R-CNN 的 feature.
    有论文指出将 R-CNN 和 Faster R-CNN 的 Classification score 结合起来可以提升 performance, 说明 R-CNN 学到的 focus 在物体上的 feature 可以解决 redundant context 的问题. 但是增加的额外计算会使得 inference 速度慢很多, 因此 DCNv2 的解决方法就是让 R-CNN 当做 teacher network, 让 DCNv2 的 RoI Pooling 之后的 feature 去模拟 R-CNN 的feature.

DCNv2%2Ffig3.jpg

Cascade R-CNN

Cascade R-CNN 简介

本文针对检测问题中正负样本区分的 IoU 阈值选择问题提出了一种新的目标检测框架, Cascade R-CNN
在 two-stage 的目标检测模型当中, 需要设置 IoU 阈值来区分正样本和负样本, 通常, 阈值选的越高, 正样本的框就与真实框越接近, 但是这样就会使得正样本的数量大大降低, 训练时容易产生过拟合问题, 而如果阈值选的较低, 就会产生大量的 FP 样本. 根据经验和实验证明可知, 当输入的 proposals 和真实框的 IoU 的值, 与训练器训练时采用的 IoU 的阈值比较接近的时候, 训练器的性能会比较好, 为此, 作者提出了一种级联式的阈值训练方法, 先在较低的阈值上训练检测器, 得到具有更高 IoU 的候选框输出, 然后在此基础上进行训练, 不断提升 IoU 的阈值, 这样一来, 最终生成的候选框质量会变得更高 (与真实框的 IoU 更大). 作者提出这种框架的启发来自于图1(c), 整体来说, 输入的 proposals 的 IoU 在经过检测器边框回归以后, 其输出的边框与真实框会有更大的 IoU, 因此可以将这个具有更大 IoU 的框作为下一个检测器的输入, 同时调高训练时的 IoU(H1 变成 H2), 进而得到质量更高的框

CascadeR-CNN%2Ffig1.jpg

CascadeR-CNN%2Ffig3.jpg

由上图可知, 在 Cascade R-CNN 中:

  • 每一个 stage 的 detector 都可以有足够多满足阈值的样本进行训练, 不会发生过拟合问题
  • 更深层的 detector 可以在精度更高的 proposals 上进行预测, 生成的结果效果更好
  • 每个 stage 的阈值是逐级升高的(对比 Iterative BBox 使用的是相同的阈值), 使之可以逐渐提高 proposals 的 IoU

SNIP

SNIP 简介

SNIP: Scale Normalization for Image Pyramids
MST: Multi-Scale Training

SNIP 可以看做是 MST 的改进版, 只有当某个 RoI 的尺度处于预定的尺度范围内时, 该 RoI 才会参与训练, 否则, BP 的时候会将其忽略(梯度置为 0). 这里有一个假设前提, 即: 对于一个物体, 因为多尺度训练, 它总有机会落在一个合理的尺度范围内. 只有这部分合理尺度的物体参与了训练, 剩余部分在BP的时候被忽略了.

对于一个特定的分辨率 $i$, 如果 RoI 的面积 $ar(r)$ 落在了合理区间 $[s_i^c, e_i^c]$ 内, 我们就将其标记为有效样本, 否则将其标记为无效样本. 与 invalid gt box 的交并比大于 0.3 的 anchors 都会在训练中剔除(即, 他们的梯度设置为 0).

在 Inference 阶段, 我们在每个分辨率下都使用 RPN 来生成 proposals, 并在每个分辨率下进行单独分类, 如图 6 所示. 和训练阶段相同, 我们在每个分辨率下没有选择落在特定范围外的检测结果(是 detectin, 不是 proposals).

池化后的 RoI 的分辨率和预训练的网络相匹配, 因此在 fine-tuning 期间网络更容易学习.

如果某个分辨率下的图像在 valid range 内没有 gt boxes, 那么这个 image-resolution pair 在训练时会被忽略.

图6

SNIPER

SNIPER 简介

缩放图像后挑处于某一尺度下的gt,用包含挑选gt的chips来做正样本,然后用一个RPN网络来生成proposal后挑选负样本.

这样不用对原始图像进行训练, 而且chip尺寸不大,可以用大batch, 显著加速了训练3x.

chips是某个图片的某个scale上的一系列固定大小的(比如KxK个像素)的以恒定间隔(比如d个像素)排布的窗口(window) ,每个window都可能包含一个或几个objects。

一张图的每个scale,会生成 若干个 Positive Chips 和 若干个 Negative Chips 。每个Positive Chip都包含若干个ground truth boxes,所有的Positive Chips则覆盖全部有效的ground truth boxes。每个 Negative Chips 则包含若干个 false positive cases。

positive chip:
对于每个scale,都会有个 area range $R^i=[r^i_{min},r^i_{max}],i\in [1,n]$ ,这个范围决定了这个scale上哪些ground truth box是用来训练的。所有落在 $R^i$ 范围内的 ground truth box 称为有效的(对某个scale来说),其余为无效的,有效的 gt box 集合表示为 G_i 。从所有chips中选取包含(完全包含)有效 gt box最多的 top K 个 chips,作为Positive Chips,其集合称为 $C^i_{pos}$.
每一个 gt box 都会有 chip 包含它。因为 $R^i$ 的区间会有重叠,所以一个gt box可能会被不同scale的多个chip包含,也有可能被同一个scale的多个chip包含。被割裂的gt box(也就是部分包含)则保持残留的部分。

Negative Chip Selection

如果只用正chips作为样本进行训练,模型的假正利率往往很高(不包含任何物体的chip判断为包含包含物体的chip)。具体原因是因为参与模型训练的数据都是包围着Ground Truth的chips,而在测试的时候(第2节详细介绍SNIPER的测试部分),输入的是整张图像的图像金字塔,这时候必然包含不包围任何Ground Truth的背景区域. 为此, SNIPER 会生成相应的 Negative Chip 来弥补.

生成方式:
论文中给出的策略是首先只使用正 chip 训练一个只有几个 Epoch 的弱 RPN。在这里我们对 RPN 的精度并没有特别高的要求,因为它只是我们用来选择 chip 的一个工具,对最终的结果影响十分微弱。尽管这个 RPN 检测能力很弱,但是其并不是随机初始化的一个模型,它得到的检测框还是有一定的置信度的。所以策略的第二步是根据弱 RPN 的检测结果选择那些 “假正” 的样本。详细的说,首先去掉 $C^i_{pos}$ 中的正 chips,然后根据弱 RPN 的检测结果,从每个尺度选择至少包含 M 个候选区域的chips组成负 chips 池,最后在训练的时候从中随机选择固定数量的负样本进行训练.

SNIPER 小结

小物体检测一直是困扰物体检测领域的一个重要难题,传统的图像金字塔式解决该问题的一个常见的传统策略,但是速度太慢,SNIPER的提出便是动机便是解决图像金字塔的速度问题。

需要注意SNIPER并不是一个检测算法,而是对输入图像的一个采样策略,其采样的结果(chips)将作为输入输入到物体检测算法中。

算法虽然使用了RPN,但是并不是离开了RPN就无法工作了,RPN提供了一个提取假正利率的功能,这个可以通过Selective Search或者Edge Box近似替代。

另外,SNIPER仅仅是对训练速度的提升,往往更重要的检测速度并没有提升,反而是模型必须依赖图像金字塔,这反而降低了模型的通用性。

最后,作者开源的源码和论文出入较大,读起来比较费劲,等之后有时间的话再详细学习这份源码。

SSD

SSD 简介

SSD 是一种 one-stage 检测模型, 它最主要的特点就是使用了多尺度的特征图谱进行 one-stage 的目标检测预测, 具体来说, SSD 在 VGGNet 之后又添加了五个卷积段, 每个卷积段都是用 $1\times 1$ 和 $3\times 3$ 大小的卷积核组成的, 然后在加上 VGGNet 的 conv4_3 卷积层, 总共可以得到六种不同尺度的特征图谱. 然后对于每一个特征图谱上的每一个 location, 都会有 $k$ 个 default boxes 作为初始的候选框, 不同尺度的特征图谱对应的 $k$ 的大小也不相同(4, 6, 6, 6, 4, 4). 对于一个尺度为 $m \times n$ 的特征图谱来说, 它具有的 default box 的个数就是 $m\times n\times k$, 又因为 one-stage 模型会在回归的同时进行分类, 因此, 最终的输出结果是一个形状为 $m\times n\times k\times (c + 4)$ 的 tesor, $k$ 就代表了 $k$ 个 default box, $(c+4)$ 代表了每个 box 的分类得分和坐标偏移量.

SSD%2Ffig2.jpg

SSD 中如何计算 default box 的大小

假如feature maps数量为 $m$, 那么每一个feature map中的default box的尺寸大小计算如下:

上式中, $s_{min} = 0.2 , s_{max} = 0.9$. 对于原文中的设置 $m=6 (4, 6, 6, 6, 4, 4)$, 因此就有 $s = \{0.2, 0.34, 0.48, 0.62, 0.76, 0.9\}$
然后, 几个不同的aspect ratio, 用 $a_r$ 表示: $a_r = {1,2,3,1/2,1/3}$, 则每一个default boxes 的width 和height就可以得到( $w_k^a h_k^a=a_r$ ):

对于宽高比为1的 default box, 我们额外添加了一个 scale 为 $s_k’ = \sqrt{s_k s_{k+1}}$ 的 box, 因此 feature map 上的每一个像素点都对应着6个 default boxes (per feature map localtion).

SSD 使用了哪些数据增广方法?

水平翻转, 随机裁剪+颜色扭曲(random crop & color distortion), 随机采集区域块(randomly sample a patch, 目标是为了获取小目标训练样本)

为什么SSD不直接使用浅层的特征图谱, 而非要额外增加卷积层, 这样不是增加模型的复杂度了吗?

FPN: 理想情况下, SSD 的特征金字塔是从多个卷积层输出的特征图谱得到的, 因此它的计算成本几乎为零. 但是为了避免使用到那些表征能力不强的低阶特征图谱(浅层), SSD 只使用了深层的特征图谱(conv4_3), 同时在 backbone 网络的后面又添加了几层卷积层来提取高表征能力的特征图谱. 但是这样就使得 SSD 错过了那些低阶特征的信息, 这些低阶特征中往往包含了高阶特征不具有的信息, 如小物体的特征信息, 这也是为什么 SSD 对小物体不敏感的原因之一.

SSD PyTorch 源码实现

点击下方链接跳转至源码实现解析
SSD PyTorch 源码实现解析

RefineDet

RefineDet 简介

One-Stage 比 Two-Stage 精度低的主要原因有三点:

  1. 正负样本的极度不均衡
  2. bbox 回归不够精细
  3. 分类和回归过程比较精简(不像 Faster R-CNN 那样, 先进性 RPN 二分类, 再进行 Detection 多分类)

为此, RefineDet 提出将 Two-Stage 模型中的一些思想集成到 One-Stage 框架中, 以此来综合 One-Stage 和 Two-Stage 各自的优势. 具体来说, RefineDet 在 SSD 模型的基础上, 将 SSD 的 forward 流程分成了两大部分, 分别是 ARM(Anchor Refinement Module) 和 ODM(Object Detection Module), ARM 的作用有两点: (1), 移除 negative anchors 解决正负样本不均衡问题 (2), 粗略的调整 anchors 的 locations 和 sizes, 为后续的回归器提供更好地初始 anchors. 而 ODM 的作用是: 将 ARM refine 后的 anchor 作为输入, 进一步的提升 bbox reg 和 multi-class pred 的精度. 这两个模块的设计思想分别来自于 Two-Stage 网络的 RPN 网络和 Detection 网络. 与 Two-Stage 的不同之处在于 RefineDet ARM 和 ODM 的具体实现上.

RefineDet%2Ffig1.jpg

RefineDet 的网络框架设计和 SSD 类似, 首先在常规的 SSD 金字塔特征图谱上(即: conv4_3, conv5_3, conv_fc7, conv6_2) 产生预定义的固定数量的 default boxes, 然后每一个特征图都会共享两个子网络分支, 分别为 anchor 的回归网络和正负样本的二分类预测网络. 这一步产生的负样本置信度高于 0.99 的 anchor 不会传入 ODM 阶段, 起到了预先去除 easy negative examples 的作用. 然后, 利用一个 Transfer Connection Block 结构将 down-top 的特征图谱和 top-down 的特征图谱结合, 并利用 ARM 产生的 anchor 作为输入, 传入 ODM 中进行第二次的 bbox regression 和物体类别的多分类预测.

Transfer Connection Block 结构如下图所示: ARM 的特征图谱经过两个卷积层, top-down 的特征图谱有反卷积层得到, 二者进行 Eltw sum 后, 输出的图谱再经过一层卷积, 最终产生在 ODM 中进行预测的特征图谱

RefineDet%2Ffig2.jpg

refinedet 使用了 two-stage 的边框回归过程, 为什么还说它是 one-stage 模型?

RefineDet 是 One-Stage 和 Two-Stage 的结合
虽然有了 anchor refine 的步骤, 但是不想 Faster R-CNN 那样, proposals 的生成和最终 bbox 的预测有很明显的分割, 因此, 勉强算 One-Stage.

RFBNet

RFBNet 简介

RFBNet 从感受野的角度出发, 提出了利用空洞卷积(Dilated Conv)来构建 RFB(Receptive Field Block), 其核心思想是利用具有不同空洞间距的 Dilated Conv 来集成不同范围的感受野, 以便可以在同一个特征图谱上获得更大尺度范围内的信息

RFB 模块结构如下两图所示

RFBNet%2Ffig2.jpg

RFBNet%2Ffig4.jpg

RFB 与其他具有多尺度感受野的工作之间的区别

RFBNet%2Ffig3.jpg

RFB Net Detection Architecture

RFBNet%2Ffig5.jpg

TridentNet

TridentNet 简介

RFBNet 虽然考虑了 Dilated Conv, 但是它的 RFB 结构较为复杂, 带来的计算量也很高. 为此, TridentNet 从不同的角度来使用 Dilated Conv, 对于目标检测问题来说, 不同的特征图谱感受野对于不同的物体尺寸, 其检测效果是不同的, 简单来说, 更大的 receptive field 对于大物体的性能会更好, 更小的 receptive filed 对于小物体的性能会更好. 因此, TridentNet 提出了 Trident Block 结构, 它具有以下三个特点

  1. 构造了多路并行的 Trident 分支, 每个 Trident Block 分支具有不同的感受野大小, 用于检测不同尺度的物体
  2. 在每一个 Trident Block 当中, 它们的参数是共享的, 区别仅在于去感受野的大小不同;
  3. 对于每一个 branch, 训练和测试都只负责一定尺度范围内的样本(借鉴 SNIP), 这样避免了极端 scale 对检测性能的影响.

补充: 实际上, 在测试阶段,我们可以只保留一个branch来近似完整TridentNet的结果,后面我们做了充分的对比实验来寻找了这样single branch approximation的最佳setting,一般而言,这样的近似只会降低0.5到1点map,但是和baseline比起来不会引入任何额外的计算和参数。

M2Det

M2Det 从特征金字塔的构建角度出发, 认为现有的 sota 的特征金字塔的构建方式存在两个缺点, 一是直接简单利用了 backbone 网络固有的金字塔式的特征图谱来构建, 但这些 backbone 实际上是针对分类任务设计的, 因此不足以表示针对目标检测任务的特征. 二是构建的金字塔中每一个尺度的特征仅仅来自于 backbone 中单一层级(level)的特征. 这样一来, 小尺度的特征图谱往往缺少浅层低级的语义信息, 而大尺度的特征图谱又缺少深层的高级语义信息(同尺寸的物体可能本身所需的语义层级不同, 如人需要深层的高级语义信息才能识别, 而交通灯只需要浅层的低级语义信息就可以识别). 因此, 作者就提出了融合多个层级特征的 MLFPN (multi-level FPN). MLFPN 主要由三个模块组成, 分别是: 特征融合模块(Feature Fusion Module, FFM), 简化的 U-shape 模块(Thinned U-shape Module, TUM), 以及尺度特征聚合模块(Scale-wise Feature Aggregation Module, SFAM). 首先, FFMv1 融合了 backbone 网络中浅层和深层的特征来生成 base feature, 具体来说就是 VGGNet 的 conv4_3 和 conv5_3. 其次, 若干个 TUMs 和 FFMv2 交替连接. 具体的说, 每一个 TUM 都会生成多个不同尺度的 feature maps. FFMv2 融合了 base feature 和前一个 TUM 输出个最大的 feature map. 融合后的 feature maps 会被送到下一个 TUM 中. 最终, SFAM 会通过按照尺度划分的特征连接操作(scale-wise feature concatenation operation)和通道注意力机制(channel-wise attention mechanism)来聚集 multi-level multi-scale features, 形成最终的特征金字塔结构. 最后用两个卷积层在特征金字塔上分别进行回归和分类预测. 可以看出, 整体的流程和 SSD 类似, 不同之处就在于特征金字塔的构建方式.

M2Det%2Ffig1.jpg

M2Det%2Ffig2.jpg

M2Det%2Ffig4.jpg

YOLOv1

http://caffecn.cn/?/question/1842

YOLOv1 首先将图像分成 $S\times S$ 的格子(cell), 如果一个目标物体的中心落入格子, 那么该格子就负责检测该目标. 每一个格子都会预测 B 个 bbox, 每一个 bbox 包含 5 个值, 分别是坐标和置信度(表示是否包含物体). YOLOv1 的损失函数综合了坐标, 分类标签和分类置信度三部分, 都使用了平方和损失进行计算, 并且通过不同的权重系数平衡了 loss 之间的贡献度.

YOLOv1 的缺点: YOLO 的每一个网络只预测两个 boxes 和一套分类概率值(供两个 boxes 共享), 这导致模型对相邻目标的预测准确率下降, 因此, YOLO 对成群的目标识别准确率低

YOLOv1%2Ffig3.jpg

从图中可以看出, YOLO网络的输出网格是 7×7 大小的, 另外, 输出的channel数目30, 在每一个cell内, 前20个元素是每个类别的概率值, 然后2个元素对应2个边界框的置信度, 最后8个元素时2个边界框的 $(x,y,w,h)$.(每个cell会预测两个框, 最后选择IOU较大的来复杂物体的预测)

PS:
注一: YOLO中采用 $S\times S$ 的网格划分来确定候选框, 这实际上是一种很粗糙的选框方式, 同时也导致了YOLO在面对小目标物以及群落目标物时, 性能较差.(因为YOLOv1的同一个cell无法预测多个目标, 也就是说YOLOv1理论上最多检测出49个物体).

损失函数:

需要注意的是, 网络并不会总是计算所有的 loss 项, 具体地说:

  1. 对于有物体中心落入的 cell, 需要计算分类 loss 和两个 confidence loss,
  2. 对于没有物体中心落入的 cell, 只需要计算 confidence loss.
  3. 对于两个 bbox loss, 只计算 IOU 较大的 bounding box loss

另外, 我们发现每一项的计算(即使是分类)都是 L2 loss, 从另一角度体现出 YOLO 把分类问题转化为了回归问题.

YOLOv1%2Ffig2.jpg

YOLOv2

YOLOv2(也叫做 YOLO9000)

YOLOv1 对于 bbox 的定位不是很好, 同时在精度上和同类网络还有一定差距, 所以 YOLOv2 对于速度和精度都做了很大的优化, 并且吸收了同类网络的有点, 主要包括以下几点:

  • 提高图片分辨率: 将预训练阶段的输入图片的分辨率扩大到 $448\times 448$, mAP 提升了 4%.
  • 使用 BN: 利用 BN 起到正则化作用, 从而舍弃了 dropout 层, mAP 提升了 2.4%
  • 引入 anchor: 在检测时使用了 $416\times 416$ 的图片大小, YOLOv2模型下采样的总步长是 32, 因此最终得到的特征图大小为 $13\times 13$, 维度控制成奇数, 这样特征图谱恰好有一个中心位置, 对于一些大物体, 它们中心点往往落入图片中心位置, 此时使用特征图的一个中心点来预测这些物体的边界框相对容易些. YOLOv1 上一个 cell 的两个 boxes 共用一套分类概率值, 而 YOLOv2 的每个 anchor box 都会单独预测一套坐标, 一个置信度, 和一套分类概率值. (这和 SSD 类似, 不过 SSD 没有预测置信度, 而是将背景作为一个类进行处理). YOLO 中一个 anchor 只会与一个 gt 匹配, 这与 Faster R-CNN 和 SSD 有所区别.
  • 用 k-mean 来确定 anchor 的初始形状: 根据训练集的聚类分析结果, 选取 $k$ 个聚类中心作为 anchor 的初始形状设置.
  • Direct location prediction: Faster R-CNN 中的偏移公式是无约束的, 这样预测的边界框就可能落在图片的任何位置, 这导致模型训练时的不稳定性, 需要训练很长时间才能预测出正确的偏移量. 因此, YOLOv2 沿用了 YOLOv1 的方法, 预测的是边界框中心相对于网格单元位置的位置坐标. 综合 anchor + kmean + driect, mAP 提升了 5%.
  • 利用 ResNet 的 identity mapping 获得细粒度的特征图谱(多尺度图谱方法): YOLOv2 的输入图片大小为 $416\times 416$, 最终 max pooling 以后得到 $13\times 13$ 大小的特征图谱, 这样的图谱预测大物体相对足够, 但是对于小物体还需要更精细的特征图, 因此 YOLOv2 利用 identity mapping 的思想将前一段的特征图谱 $26\times 26 \times 512$ reshape 成 $13\times 13 \times 2048$ 的特征图谱, 然后与原来的 $13\times 13 \times 1024$ 的特征图谱连接在一起形成 $13\times 13 \times 3072$ 的特征图谱, 然后在此特征图谱上进行预测. 该方法提升 mAP 1%.
  • Multi-Scale Training: 在训练过程中, 每隔一定(10)的 iterations 之后就随机改变输入图片的大小, 图片大小为一系列 32 倍数的值(因为总的 stride 为 32): {320, 352, …, 608}.
  • Darknet-19: YOLOv2 采用了一个新的模型(特征提取器), 包括 19 个卷积层和 5 个 maxpooling 层(卷积层分配为 1,1,3,3,5,5+1). Darknet-19 与 VGG16 模型的设计原则是一致的, 主要采用 $3\times 3$ 卷积和 $2\times 2$ 的 maxpooling 层. Darknet-19 最终采用 global avgpooling 做预测, 并且在 $3\times 3$ 卷积之间利用 $1\times 1$ 卷积来压缩特征图的通道数以降低模型的计算量和参数.

YOLO 利用 anchor 会生成 $13\times 13\times 5 = 845$ 个候选区域框, 相比于YOLOv1的98个, 多多了, 所以会大大提高召回率, 但是会降低准确率. 下降的原因, 个人觉得是YOLO在采用anchor box获取候选框的同时, 依然采用YOLOv1的训练方法, YOLOv2的损失函数是一个非常复杂的形式, 导致其在更新参数时很容易顾不过来, 因此其出错的概率也相应提升.

YOLOv2的训练包含三个阶段: 预训练(224), finetune(448), 检测模型训练

对样本的处理方式: 和 YOLOv1 相同, 对于训练图片中的 ground truth, 如果其中心落在了某个 cell 内, 那么该 cell 内的 5 个 anchor 就负责预测该物体, 并且最终只有一个边界框会与之匹配, 其他的会被 NMS 掉. 所以 YOLOv2 同样假定每个 cell 至多含有一个 ground truth, 而在实际上基本不会出现多于一个的情况. 与 gt 匹配的 anchor 会计算坐标误差, 置信度误差, 和分类误差, 而其他的边框则只计算置信度误差.

损失函数 YOLOv2 的 loss 形式如下:

YOLOv2%2Floss.jpg

YOLOv3

YOLOv3 加入了更多被验证过的有效技术, 使得 YOLO 模型的 mAP 可以与 SSD 系列相媲美, 同时速度依然很快(约为 SSD 的三倍). YOLOv3 的改进主要如下:

  • Bounding Box Prediction: YOLOv3 使用了和 YOLOv2 相同的 bbox 回归策略, 都是预测相对于 cell 左上角的偏移量进行回归. YOLO 中每个 gt box 只会与负责它的 cell 中的一个 anchor box 匹配(IoU 最大), 其他的 anchor box 只会计算 objectness 置信度损失, 而不会计算坐标和分类损失.
  • 类别预测: 由于有的物体可能不止属于一个标签, 如 “人” 和 “女人”. 因此, 为了减少类别之间的对抗性, YOLOv3 没有使用 softmax 计算分类损失, 而是采用了 二值交叉熵 来预测类别(属于某类或其他类).
  • 采用特征金字塔: 使用类似于 FPN 的方法(upsample and top-down)提取到不同尺度的特征图谱(文中使用了3个), 在每个尺度的特征图谱上的都会预测三个boxes, 因此, 每个尺度的特征图谱的输出为: $N\times N \times [3\times (4+1+80)]$. 在确定 anchor 大小时, 利用 kmean 聚类选出 9 个聚类中心, 然后划分成 3 个簇分别赋给 3 个尺度的特征图谱.
  • Darknet-53: 使用了残差模块的思想, 提出了层数为 53 的 Darknet-53 网络 (1, 2, 8, 8, 4). 在 ImageNet 上, Darknet-53 is better than ResNet-101 and 1.5x faster. Darknet-53 has similar performance to ResNet-152 and is 2x faster.

在使用了 anchor 和 multi-scale 之后, YOLOv3 在小物体的检测上有所改善.
Focal Loss 对 YOLOv3 不起作用的原因可能是 YOLO 的选框策略和损失函数(objectness+cls)使得 YOLO 在背景样本不均衡问题上影响较小.

YOLO 系列为什么速度这么快?

首先 YOLO 是 one-stage 的, 它对 bbox 的分类和回归同时进行的, 其次, 在不使用 anchor 时, YOLO 是 anchor free 的, 他所需的计算量远远小于基于 anchor 的 Faster R-CNN 和基于 Default Box 的 SSD.

OHEM

提出了一种在线的难样例挖掘算法:
作者根据每个 RoI 的 loss 的大小来决定哪些是难样例, 哪些是简单样例, 通过这种方法, 可以更高效的训练网络, 并且可以使得网络收敛到更好的解. 同时, OHEM 还具有以下两个优点:

  • 消除FastR-CNN系列模型中的一些不必要这参数 , 这些参数大多都是为了解决难样例问题服务的, 在使用 OHEM 以后, 不仅无需在对这些超参数进行调优, 同时还能获得更好的性能表现.
  • OHEM算法可以与其他多种提升模型精度的trick相结合, 对于大多数模型(R-CNN系列), 在使用了OHEM以后, 都能够获得精度上的提高, 可以看做是一种普适性的提升精度的方法.

注: 在实现OHEM上, 作者为了提升速度和效率, 特意设计了两个RoI网络, 以减少无用的计算.

Focal Loss

Focal Loss 简介

one stage 方法由于没有对候选框的预先过滤策略, 因此后产生大量的 easy negative examples(易分类的背景区域), 这些 easy negative examples 由于数量众多, 最终会对loss有很大的贡献, 从而导致优化的时候过度关注这些 easy examples, 这样会收敛到一个不够好的结果.

FocalLoss%2Ffig1.jpg

Focal Loss 正是为了解决这个问题而提出的, 它的核心思想非常直接, 就是在优化的过程中抑制这些 easy (negative) examples 对最终 Loss 的贡献程度. 所谓 easy example 指的就是那些预测概率与真实概率十分相近的样本. 这些样本已经被网络很容易的正确分类了, 所以应该适当减少他们的loss以降低他们对参数更新的影响程度. 以下面的公式为例, 当真实标签为1时, 如果预测概率(假设二分类) $p$ 接近于1, 则此样本是easy样本, 因此, 前面的 $(1-p)^{\gamma}$ , 就会非常小, 起到了抑制易分类样本的作用.

对上式可以进行如下简写:

注1: $\gamma$ 的值越大, 则 easy example 对于loss的贡献程度越小, 当 $\gamma = 0$ 时, 会退化成普通的交叉熵函数.

注2: 实际中在使用 $\gamma$ 参数的情况下, 还会用了另一个参数 $\alpha$ , 该参数的主要作用是调节正负样本的权重, $\alpha$ 的取值通常会根据正负样本的实际比例决定, 如下所示:

通常也可缩写为:

下图可以看出, 对于易分类样本来说, 绝大部分都是负样本, 因为 FocalLoss 对于正样本的抑制效果并不明显, 而对负样本的抑制效果会随着 $\gamma$ 参数的升高而迅速增大

FocalLoss%2Ffig4.jpg

Focal Loss 的两个参数各自的作用是什么? 二者有什么关系?

$\gamma$ 决定了对 easy example 的抑制力度, 当 $\gamma$ 的值越大时, 则 easy example 对于 loss 的贡献程度就越小, 而 $\gamma$ 的值为 0 时, 相对于不施加抑制, Focal Loss 推退化成普通的交叉熵函数

$\alpha$ 的作用是调节正负样本的权重, $\alpha$ 的取值通常会根据正负样本的实例比例决定. 对于样本数量较多的类别, 其对应的权重应该较小. 但是当 $\alpha$ 参数和 $\gamma$ 参数共同使用时, 则需要综合考虑样本的数量和 $\gamma$ 的取值

二者关系: 当 $\gamma$ 取值较小时, FocalLoss 对易分类样本的抑制力度不强, 此时由于存在大量的易分类负样本, 因此 $\alpha$ 应当对正样本施加更多的权重($\gamma = 0$ 时, $\alpha$ 最优值为 0.75). 当 $\gamma 取值较大时$ Focal Loss 对易分类负样本抑制力度变强, 使得原本在数量上不在优势的前景区域或许在对 loss 的贡献度上反超了背景区域, 因此, 需要对前景区域赋予更低的权重.$\gamma = 2$ 时, $\alpha$ 最优值为 0.25 (最优组合).

R-CNN 系列为什么不用 Focal Loss 进行优化

因为 Focal Loss 主要解决的问题是正负样本框的极度不均衡问题.
而 Two-Stage 网络, 通常会利用 RPN 和启发式的采样算法来过滤掉大量的负样本, 因而不存在正负样本不均衡的问题.

  • RPN 会将物体候选框的数量降低很多, 移除了大量的易分类负样本(背景框)
  • 采用了 biased-minibatch 的采样策略, 比如, 保证正样本和负样本的比例为 1:3 进行训练 (这其实相等于起到了 $\alpha$ 因子的作用)

RetinaNet 的结构

RetinaNet 由一个backbone网络和两个子网络组成。backbone网络是现成的,主要负责计算卷积特征图谱。第一个子网络负责物体分类任务,第二个子网络负责bounding box回归任务,它们都是在backbone网络输出的卷积图谱上进行计算的。

FocalLoss%2Ffig3.jpg

  • Backbone: RetinaNet 选用 FPN 作为 backbone 网络, 对P3到P7使用了不同大小的anchors.
  • 分类子网: 由一个简单的 FCN 组合, 分类子网的参数在所有的金字塔层级都是共享的,对于规定的金字塔层级, 其输出的特征图谱的通道数为 $C$, 则分类子网由 4 个 3x3 的卷积层构成, 每个卷积层的输入输出通道数都是 $C$, 尺寸大小维持不变, 并且均使用 ReLU 作为激活函数, 最后接上一层输出通道数为 $KA$ 的 3x3 卷积, 并利用 Sigmoid 激活函数在每个 location 上输出 $KA$ 的二值预测概率. 通常情况下 $C=256, A=9$
  • 回归子网: 整体结构与分类子网相同, 唯一区别在于最后一层卷积层的输出通道数变成了 $4A$, 因为它要在每一个 location 上为每一个 anchor 预测 4 个坐标偏移量. 回归子网的参数在金字塔层级上也是共享的. 注意, 从回归子网的通道数就可以看出, RetinaNet 的 bbox 预测是类别不可知(class-agnostic)的

DenseBox

CornerNet

在 CornerNet 中, 舍弃了 anchor 的设定, 转而利用一对角点来表示物体, 卷积网络会预测输出两组 heatmap, 分别代表了 bbox 的左上角和右下角. 为了能够很好的将角点进行分组, CornerNet 同时还会为每个 corner 预测一个 embedding vector, 当一组角点的 embedding vector 的点积越小时, 则说明这组角点被分到同一组的可能性更高. 如果距离小于阈值,则生成对象边界框。为边界框分配置信度分数,该分数等于 corner pairs 的平均分数。

CornerNet 会预测两组热图, 每一组热图都具有 $C$ 个通道, $C$ 代表了物体类别的数量, 热图的尺寸为 $H\times W$. 注意, 这里没有背景的通道(background channel). 每一个 channel 都是一个二值的 mask, 用于指示某个类别的角点位置.
对于每个角点来说, 都会有一个 gt positive location, 而其他的 locations 都将是负样本. 由于 gt positive location 周围的像素点比其它的像素点更接近 gt, 所以, 我们不能对所有的负样本给予同样的标签, 因此, CornerNet 使用了一个非规范的高斯分布来产生对应的 GT 样本, 其正中心位于 gt positive location, $\sigma$ 的值根据 GT 物体的大小来确定, 大约是物体半径的三分之一.

CornerNet%2Ffig1.jpg

为了帮助卷积网络更好的定位 bbox 的 corner, CornerNet 提出了 Corner Pooling Layer. 对于给定的特征图谱, Corner Pooling 为分别进行自下而上的 max pooling 和自右向左的 max pooling, 然后将二者的结果相加, 即可得到左上角的 pooling 结果, 对于右下角的 pooling 操作也是类似的.

CornerNet%2Ffig3.jpg

CornerNet%2Ffig6.jpg

损失函数

Corner Points 损失:
对于每个角点来说, 都会有一个 gt positive location, 而其他的 locations 都将是负样本. 在训练阶段, 我们不会等价的惩罚负样本, 我们减少了在正样本的一定半径内负样本的惩罚. 这是因为对于一对假的角点检测来说, 如果它们接近各自的真实位置, 那么就仍然可以产生一个与真实框差不多的框
设 $p_{cij}$ 为预测热图中 $c$ 类在位置 $(i,j)$ 处的得分,$y_{cij}$ 为使用 unnormalized 高斯增广的 gt 热图。我们设计了一种 focal loss[23]的变体:

其中 $N$ 是图像中物体的数量, $\alpha$ 和 $\beta$ 是用于控制每个点贡献度的超参数. (我们在所有实验中设置 $alpha=2, \beta=4$)。$y_{cij}$ 使用 Gaussian bumps 进行编码,$(1-y_{cij})$ 项减少了对 gt locations 的惩罚。

角点偏置(offsets)损失:
网络在进行下采样时, 需要将图像中的位置 $(x, y)$ 映射到 heatmap 中的位置 $(x/n, y/n)$ ,其中 $n$ 为下采样因子, 这一步会使用取整, 这样一来, 当我们将热图中的位置重新映射到输入图像时,就会丢失一些精度,这将极大地影响 gt 和小边界框的 IoU 大小。为了解决这个问题,我们预测 location offsets,以便在将它们重新映射到输入分辨率之前稍微调整转角位置。

其中 $o_k$ 为 offsets,$x_k$ 和 $y_k$ 为角点 $k$ 的 $x$ 和 $y$坐标,我们会预测一组被左上角所 共享 的 offsets (因为在同一张图谱上, 位置的偏置是相同的), 以及另一组被右下角所共享的 offets. 在训练阶段,我们在 ground-truth corner 位置使用 smooth L1 Loss.

Grouping Centers 损失:
我们遵循Newell等人的方法使用一维的 embeddings。设 $e_{t_k}$ 为对象 $k$ 的左上角的 embeddings,$e_{b_k}$ 为对象 $k$ 的右下角的 embeddings。与[26]一样,我们使用 “pull” loss 训练网络对各个角进行分组,使用 “push” loss 分离各个角点:

上式中, $e_k$ 是 $e_{t_k}$ 和 $e_{b_k}$ 的平均值, 在本文所有实验中, 设置 $\Delta = 1$. 与 offset loss 类似,我们只对 gt corner location 计算损失。

可以看出, $L_{pull}$ 的目的就是让属于同一个物体角点的 embeddings 距离越来越小, $L_{push}$ 的目的就是让不同物体的角点距离越来越大.

检测 Corner 相对于检测 bbox 的优势

  1. 定位 anchor box 更难一些, 因为它需要依赖物体的4个边界才能确定, 但是 corner 只需要物体的两个边界就可以确定, corner pooling 也是如此, 它编码了关于 corner 的一些明确的先验知识.
  2. Corners 提供了一种更加有效的方式来密集的对 boxes 的空间进行离散化: 我们只需要 $O(wh)$ 的 corners, 而 anchor boxes 需要的复杂度是 $O(w^2h^2)$.

CornerNet-Lite

CornerNet-Lite 描述
基于关键点的对象检测[53,56,26]是一类通过检测和分组其关键点来生成对象边界框的方法。 CornerNet [26]是其中最先进的技术,可以检测并分组边界框的左上角和右下角; 它使用堆叠 HourGlass 网络[39]来预测角落的热图,然后使用 associate embeddings [38]对它们进行分组。 CornerNet 允许简化设计,无需使用 anchor box[46],并且在 One-Stage 探测器中实现了COCO [32]的 SOTA 精度。

CorNetNet 虽然精度较高, 但是其计算量的高处理成本也是它的一个明显的缺点. 为此, CornerNet-Lite 提出了两种变体, 来改善计算量:

  1. CornerNet-Saccade: 使用 缩小的图像 来预测 3 个 attention maps, 分别用于小, 中, 大物体. attention 模块通过 3x3 conv-relu + 1x1 conv-sigmoid 来实现, 然后利用获得的粗糙的 locations 信息, 从原始图像中截取相应大小的 crop 进行精细化的预测, 对于不同大小的物体, 通常具有不同的放大比例, 小对象需要放的更大些, 中对象次之, 大对象最后. 通过这种方式, 大幅降低了 location 的采样空间. 加速了检测速率.

CorNetNet 虽然精度较高, 但是其计算量的高处理成本也是它的一个明显的缺点. 为此, CornerNet-Lite 提出了两种变体, 来改善计算量:

  1. CornerNet-Saccade: 使用 缩小的图像 来预测 3 个 attention maps, 分别用于小, 中, 大物体. attention 模块通过 3x3 conv-relu + 1x1 conv-sigmoid 来实现, 然后利用获得的粗糙的 locations 信息, 从原始图像中截取相应大小的 crop 进行精细化的预测, 对于不同大小的物体, 通常具有不同的放大比例, 小对象需要放的更大些, 中对象次之, 大对象最后. 通过这种方式, 大幅降低了 location 的采样空间. 加速了检测速率.
  2. CornerNet-Squeeze, 主要改进如下:
    1. 受 SqueezeNet 启发: 将 Residual Block 替换成 Fire Module(1x1 + 1x1 & 3x3)
    2. 受 MobileNet 启发: 利用 3x3 的 Dwise Conv 替换 Fire Module 中第二层的 3x3 卷积
      CornerNet-Lite%2Ftab1.jpg

CornerNet-Squeeze-Saccade

当二者同时使用时, 模型本身的容量被限制过多, 使得模型无法在检测物体的同时良好的预测 attention map, 因此效果不好.

FSAF

FSAF 简介

有了 FSAF, 就不需要为 anchor 定义 anchor scale 和 aspect ratio 了, 金字塔层级的选择由 FASF 决定

以 RetinaNet 为基础,FSAF模块仅为每个金字塔层引入两个额外的conv层,如图4中虚线 feature maps 所示。这两个网络层分别负责 anchor free 分支的分类和回归预测。更具体地说,我们会在分类子网中最后的 feature map 上附加了一个带有 $K$ 个 filter 的 3×3 conv 层,后面是 sigmoid 函数, 用于二分类,该网络层与 anchor based 分支的网络层并行。它为K个对象类预测对象会出现在每个空间位置的概率。同样的,回归子网中的 feature map 上也附加了一个 3×3 conv 层,带有 4 个filter,然后是ReLU[26]函数。它负责预测以 anchor free 方式编码的 bbox 偏移量。 因此,anchor free 和 anchor based 分支可以以多任务的方式联合工作,共享每个金字塔级别的特性。

FSAF%2Ffig4.jpg

Groud-truch and Loss

FSAF%2Ffig5.jpg

  • Classification Output: 针对分类输出的 gt 是 $K$ 个 maps,每个 map 对应一个类。每个实例以三种方式影响第 $k$ 个 gt map:
    1. 物体的中心区域(bbox 的 0.2 倍)被定义为 positive region(白色), 表示该实例的存在性。
    2. 物体中心区域外围的部分区域(bbox 的 0.5 倍再去除中央部分)被定义为 ignoring region(灰色) 这意味着该区域的梯度不会传播回网络。其余区域由黑色填充, 表示没有对象
    3. 相邻金字塔层 $(b^{l-1}_i, b^{l+1}_i)$ 中的 ignoring region 如果存在,那么在当前层级上也将忽略该区域。
      注意,如果两个实例的 effective box 在一个特征级别上重叠,则较小的实例具有更高的优先级。我们使用 Focal Loss 作为损失函数进行监督(正负样本), 超参数 $\alpha = 0.25$, $\gamma = 2.0$。图像 anchor free 分支的分类总损失是所有非忽略区域上的 Focal Loss 之和,由 所有 effective box 区域内的像素总数归一化
  • Box Regression Output: 回归输出的 gt 是 4 个与类别无关的 offset maps。实例只影响 offset maps 上的 effective 区域。对于 effective region 中的每一个像素 location $(i, j)$, 我们将其映射成一个四维向量, 分别表示当前像素和 origin region 顶, 左, 右, 底之间的距离. 采用 IoU Loss 进行优化, 并且只考虑 effective region 内的损失. 将4个 offset maps 上(i, j)处的四维向量设置为 $d^l_{i,j}/S$,每个 map 对应一个维度。$S$ 是一个归一化常量,我们根据经验选择 $S = 4.0$

在 Inference 过程中,很容易从分类和回归输出中解码 predicted boxes。在每个像素位置 $(i, j)$,假设 predicted offsets 为$[\hat o_{t_{i,j}}, \hat o_{l_{i,j}}, \hat o_{b_{i,j}}, \hat o_{r_{i,j}}]$。然后 predicted distances 为 $[S\hat o_{t_{i,j}}, S\hat o_{l_{i,j}}, S\hat o_{b_{i,j}}, S\hat o_{r_{i,j}}]$。predicted projected box 左上角和右下角分别为 $i-S\hat o_{t_{i,j}}, j-S\hat o_{l_{i,j}}$ 和 $i+S\hat o_{b_{i,j}}, j+S\hat o_{r_{i,j}}$。我们进一步将投影框放大 $2^l$,得到图像平面中的最终框。框的置信度和类别由分类输出图上位置 $(i, j)$ 处的 $k$ 维向量的最大得分和对应的类决定。

Online Feature Selection

以实例 $I$ 为例,将其在 $P_l$ 上的分类损失和box回归损失分别定义为 $L^I_{FL}(l)$ 和 $L^I_{IoU}(l)$。它们是通过平均有效盒区 $b^l_e$ 上的 Focal Loss 和 IoU loss 得到的,即:

其中 $N(b^l_e)$ 为 $b^l_e$ 区域内的像素个数,$FL(l,i,j)$、$IoU(l,i,j)$ 分别为 $P_l$ 在 location $(i,j)$ 处的 Focal Loss[22]和IoU Loss[36]。

在训练阶段, 每个实例都会通过特征金字塔所有层级 $l$ 的 forward 计算. 在此基础上,对所有 anchor free 支路计算 $L^I_{FL}(l)$ 和 $L^I_{IoU}(l)$。最后,我们选择产生最小损失和的金字塔级 $P_l$ 作为学习实例的最佳目标层级,即

然后, 我们只更新这个最佳目标层级上的梯度, 这就达到了在线选择特征的目的

FSAF%2Ffig6.jpg

在 inference 阶段, 我们不需要进行特征选择来决定金字塔层级, 因此最合适的特征金字塔自然会输出较高的置信度分数, 我们只需取最大者即可.

FASF 和 Anchor-based 结合使用时, 能有更好的效果. 增加的计算量很少, 只有 $l*2$ 个卷积层. 二者再结合时, 会合并各自的 box predictions, 然后利用阈值为 0.5 的 NMS, 得到最终的检测结果.

FoveaBox

FoveaBox 简介

FoveaBox 整体架构基于 RetinaNet. 由一个 backbone 网络和两个 subnet 组成, FoveaBox 的输出通道数是 RetinaNet 的 $1/A$

FoveaBox%2Ffig4.jpg

它的主要特点有两个:

  1. class 分支负责预测类别敏感的语义图, 将其看做是目标存在的概率.
    score map 上的 positive area 定义为原始四边形的缩小版(见图3). $R^{pos} = (x_1’’, y_1’’, x_2’’, y_2’’)$. 其中 $\sigma_1$ 是收缩因子。 在训练的时候, positive area 内的每个 cell 都用相应的目标类标签进行标注以进行训练。 对于负样本的定义,我们引入另一个缩小因子 $\sigma_2$ 并使用等式(4)生成 $R^{neg}$。negative area 是整个 feature map 中不包括 $R^{neg}$ 的区域(注意, 这里没有写错, 通常 $R^{neg}$ 会比 $R^{pos}$ 大一圈, 而除了这两处之外的其他地方, 皆认为是 background, 即负样本)。 如果单元格未分配,则在训练期间将忽略该单元格。 positive area 通常占整个特征图的一小部分,因此我们使用Focal Loss [28]来训练该分支的 target $L{cls}$。
    FoveaBox%2Ffig3.jpg
  2. box regression 分支负责预测 中心坐标与 gt box $G = (x1, y1, x2, y2)$ 四条边界之间的距离
    FoveaBox 的 box regression 分支学习的是 中心坐标与 gt box $G = (x1, y1, x2, y2)$ 四条边界之间的距离. 它首先将 feature map 上的坐标映射到原始图像上, 然后计算出 归一化(用 $z =\sqrt{S_l}$ 后的偏移量, 并将其映射到对数空间上, FoveaBox 学习的就是这样的一个 transformations, 具体如下所示. (用 smooth L1 损失训练)

FoveaBox 的 Scale Assignment

在FoveaBox中,每个特征金字塔会学习响应特定尺度的对象。 金字塔等级 $l$ 的目标框的有效比例范围计算为

其中 $\eta$ 是根据经验设置的,以控制每个金字塔的比例范围。注意,一个对象可能被网络的多个金字塔检测到,这与以前只将对象映射到一个特征金字塔的做法不同(FPN, MaskRCNN).

FoveaBox 与其他模型的比较

  • RetinaNet: FoveaBox 每个位置只会预测 1 个物体, 因此复杂度是 RetinaNet 的 $1/A$

  • Anchor-based: FoveaBox 没有预设 box 的性状, 因此对于那些性状比较极端的物体来说, 效果更好

  • RPN: FoveaBox 本身还可以将模型的 head 修改成 class agnostic scheme, 将其作用 region proposals 使用, 可以进一步提升 two-stage 的性能

  • Guided-Anchoring: FoveaBox 不依赖于 center 进行预测, 而是依赖于每个情景位置的物体上下左右边界, 这对于哪些 (x, y) 不在目标中心的 bbox 来说, 鲁棒性更好.

  • FASF

    1. FASF 的依靠在线特征选择模块为每个实例和锚点选择合适的特征, FoveaBox 依然采用根据尺度进行分配的策略. 个人观点: 是否可以将 FASF 模型加入到 FoveaBox 中, 进一步提升性能
    2. FASF 使用 IoU Loss, FoveaBox 使用 Smooth L1, 后者相对简洁
    3. FoveaBox 整体性能略强于 FSAF
  • CornerNet, CenterNet, ExtremeNet 等:
    FoveaBox 与其他基于 points 的 anchor-free 模型的一大优势: FoveaBox 直接将每个实例和 bbox 关联在一起, 不需要额外的分组方案来将 points 组合成特定实例.

FCOS

FCOS 简介

FCOS 利用全卷积网络, 对于特征图谱上到每一点 $(x, y)$, 先将他映射回原始图片中, 差不多刚好位于 $(x, y)$ 的感受野中心附近. 然后, 如果位置 $(x,y)$ 落入到任何 GT Box $B_i$ 内部, 那么就将其视为正样本, 并且该位置的类标签就是该 GT Box 的类标签 $c^\ast $, 否则它就是负样本并且 $c^\ast = 0$(背景类)。 对于点 $(x, y)$ 的回归目标, 将其定义为当前点当 GT Box 四条边界的距离, 因此,如果位置 $(x,y)$ 与边界框 $B_i$ 相关联,则该位置的训练回归目标可以表示为:

FCOS%2Ffig1.jpg

Loss Function:

上式中, $L_{cls}$ 是 Focal Loss, $L_{reg}$ 是 IOU loss (正如 UnitBox 中的一样). N_{pos} 代表 positive samples 的数量, $\lambda$ 在本文中均为 1. 上面的求和是在 feature map $F_i I{c^\ast_i >0}$ 上所有的 locations 上进行的.

目标重叠问题

FCOS 存在的一个问题是如果目标存在重叠, 那么特征图谱上的点就会同时落入多个物体的边界框, 此时这个点就变成了模糊样本, 对于模糊样本, 我们可以利用 FPN 来解决.

与 anchor based detectors 根据物体尺寸决定匹配层级不同,FCOS 直接限制边界框回归的范围。 更具体地说,我们首先计算所有特征级别上每个位置的回归值 $l^\ast,t^\ast,r^\ast$ 和 $b^\ast$。 接下来, 根据这个回归值大小与预设的特征级别大小的关系, 将其分配到不同的特征级别上去

由于 具有不同大小的对象被分配给不同的特征级别 并且 大多数重叠发生在具有显著不同大小的对象之间,因此多级预测可以在很大程度上减轻上述模糊样本带来的干扰.

FCOS%2Ffig2.jpg

这里存在一个假设前提, 就是发生重叠的物体尺寸大小必须不同, 只有这样才能将目标分别匹配到不同的金字塔层级上进行预测. 否则, 依然会产生漏检问题.

Center-ness

FCOS 中, 正样本的数量虽然多, 但是 远离物体中心的正样本位置会生成大量的低质量预测框. 为此 FCOS 在分类分支上, 添加一个并行的单层网络分支, 它负责预测每个位置 处于物体中心的概率(center-ness), 具体形式定义为 从该位置到该位置所负责对象的中心的距离, 如下所示

当某个点处于中心位置时, 该位置距离 bbox 的左右, 上下距离相等, 所以 center-ness 的值就接近于 1. 最终每个位置的预测框的置信度 将通过 center-ness 与类别得分的乘积共同给出. 这样一来, 可以通过 NMS 来消除大量的低质量预测框

基于 anchor 的检测器使用两个 IOU 阈值 $T_{low}$ 和 $T_{high}$ 将 anchor boxes 标记为负、忽略和正样本,center-ness 可以看作是一个 软阈值。它是在网络训练中学习的,不需要调整。此外,利用该策略,我们的检测器仍然可以将任何落在 GT Box 中的位置视为正样本.

可以注意到,也可以使用预测的回归向量计算中心,而不引入额外的中心分支。然而,如表5所示,从回归向量计算的中心不能改善性能,因此需要单独学习的中心。

FCOS 与其他模型的比较

FCOS 对于正样本的定义, 使用在训练时, 正样本的数量可以大幅增加, 很大程度上缓解了正负样本不均衡的问题.

ExtremeNet

对 CornerNet 的一种扩展, 损失函数和 CornerNet 类似, 不过由于分组方式不同, 因此没有 embedding 损失. ExtremeNet 会预测四个 extreme points(最上, 最左, 最下, 最右) 和一个中心点. 将检测任务当做是关键点估计来做.

ExtremeNet 使用 HourGlass Net 来检测每个类的五个关键点(四个极值点和一个中心)。offset predictions 与类别无关,但与 extreme point 有关。中心点映射没有 offset predictions。因此,ExtremeNet 的输出为 $5×C$ 的 heatmap 和 $4×2$ 的偏移图,其中 $C$ 为类别数(COCO 中 $C = 80$)。Figure 3 shows an overview. 一旦提取出极值点,我们就把它们分组成纯几何形式的检测形式。

ExtremeNet%2Ffig3.jpg

ExtremeNet 的分组策略

Center Grouping

  1. 将五个 heatmap 作为输入, 对于给定的 heatmap, 检测所有的值大于 $\tau p$ 的极大值(大于周围 3x3)坐标, 将其记为该 heatmap 的峰值.
  2. 对于给定的任意组合的四个极值点 $(t, b, r, l)$, 计算它们的几何中心 $c =\frac{(l_x + r_x)}{2}, \frac{(t_y + b_y)}{2}$。 如果在预测的 center heatmap $\hat Y^{(c)}$ 中该中心具有较高响应 $\hat Y^{(c)}_{cx,cy} \geq \tau c$,则我们就将这些极值点作为一个有效的组合.
  3. 以暴力搜索的方式对所有关键点 $t,b,r,l$ 的进行四元组的枚举。我们独立地提取每个类别的检测。我们在所有实验中设定 $\tau p= 0.1$ 和 $\tau c = 0.1$。

复杂度是 $O(n^4)$, 但是在 COCO 上 $n\leq 40$, 因此勉强可以接受.

ExtremeNet%2Ffig2.jpg

Ghost box suppression(分组存在的问题)

ExtremeNet 的分组策略存在一些显而易见的问题,

中心分组会对 相同大小的三个等间距共线 物体给出高置信度 FP 检测(仔细想一想就能知道, 如果三个相同大小的物体共线, 那么左右两边的物体的左右极值点很有可能会被分组到中间物体上, 这是非常错误的!)。 中心物体在这里有两个选择,一是提交正确的小方框,二是预测一个包含其邻居极值点的更大的方框(FP)。 我们将这些 FP 检测称为 Ghost box。 正如我们将在实验中展示的那样,这些鬼盒很少见,但仍然是 a consistent error mode of our grouping.

我们提供了一个简单的后处理步骤来删除 ghost box。根据定义, ghost box 会包含许多其他较小的检测框。为了阻止 ghost box ,我们使用了一种 soft-nms 的变种, 即 如果某一个大边界框中包含的所有框的得分之和超过其自身得分的3倍,则将这个大边界框(可能是 ghost box)得分除以2。这种非极大值抑制类似于标准的基于重叠的非极大值抑制,但是会惩罚潜在的 ghost box,而不是多个重叠的 box。

Edge aggregation

极值点并不总是唯一定义的。 如果物体的垂直或水平边缘形成极值点(例如,汽车的顶部),沿着该边缘的任何点都可以被认为是极值点。 因此,我们的网络会沿对象的任意对齐边缘产生多处弱响应,而不是单个强峰响应。 这种弱响应存在两个问题:第一,较弱的响应可能低于我们的峰值选择阈值 $\tau p$,我们将完全错过极值点。 其次,即使我们检测到关键点,其得分也会低于具有强烈峰值响应的轻微旋转对象。

我们使用 边缘聚合 来解决这个问题。对于提取为局部最大值的每个极值点,我们在垂直方向(对于左和右极值点)或水平方向(对于顶部和底部关键字点)汇总其得分。我们对所有 单调递减 的分数进行聚合,并在聚合方向上以 局部最小值 停止聚合。具体地说, 我们令 $m$ 是一个 extreme point, $N_i^{(m)} = \hat Y_{m_x+i, m_y}$ 该点的垂直线段或水平线段. 令 $i_0 < 0$ 和 $0 < i_1$ 为两个最近的局部最小点, 于是我们更新 extreme point 的值为 $\tilde Y_m = \hat Y_m + \lambda_{aggr} sum^{i_1}_{i=i_0} N^{(m)}_i$, 其中, $\lambda_{aggr}$ 是 aggregation weight, 在我们的实验中, 取其值为 $0.1$. 效果如图 4 所示.

ExtremeNet%2Ffig4.jpg

extreme points 与 corner points 的区别

corner points 是 bbox 的左上角和右下角, 大多数情况下, corner points 都位于物体的外面, 因此缺少足够的物体特征.

extreme points 定义在物体上, 因此具有更丰富的物体特征, 因此检测难度相对较低.

CenterNet(Triplets)

简介

CornerNet 的表现受到 其获取物体全局信息的能力相对较弱 的限制.

CenterNet(Triplets) 将靠近几何中心的区域,作为一个额外的关键点.

CenterNet_Triplets%2Ffig2.jpg

整个网络架构如图2所示。CenterNet 通过中心关键点和一对角点来表示每个物体。具体来说,作者在 CornerNet 的基础上 为中心关键点嵌入了热图,并 预测了中心关键点的偏移。然后,作者使用 CornerNet 中提出的方法生成 $top-k$ 的边界框。但是,为了有效滤除不正确的边界框,作者会利用检测到的中心关键点并采用以下程序:

  1. 根据中心关键点自身的分数先选择 $top-k$ 个点;
  2. 使用相应的 offsets 将这些中心关键点重新映射到输入图像上;
  3. 为每个边界框定义一个中心区域,并检查中心区域是否包含中心关键点。请注意,检查过的中心关键点的类标签应与边界框的类标签相同;
  4. 如果在中心区域检测到中心关键点,我们将保留这个预测到的边界框。边界框的分数将由三个点的平均分数代替,即左上角,右下角和中心关键点。如果在其中心区域中未检测到中心关键点,则将删除该边界框。

边界框中的中心区域的大小会影响检测结果。例如,较小的中心区域导致小边界框的召回率较低,而较大的中心区域导致较大的边界框的精度较低。 因此,作者提出了一种可以 感知尺度(scale-aware)的中心区域,以便自适应地拟合边界框的大小。尺度感知的中心区域倾向于为小的边界框生成相对大的中心区域,而对于大的边界框则生成相对小的中心区域。 假设想确定是否需要保留边界框 $i$。 设 $tl_x$ 和 $tl_y$ 表示角点 $i$ 的 top-left corner 坐标, $br_x$ 和 $br_y$ 表示角点 $i$ 的 bottom-right corner 坐标。定义中心区域为 $j$, 设 $ctl_x$ 和 $ctl_y$ 表示 $j$ 的左上角坐标, $cbr_x$ 和 $cbr_y$ 表示 $j$ 的右下角坐标。那么 $tl_x,tl_y,br_x,br_y,ctl_x,ctl_y,cbr_x$ 和 $cbr_y$ 应该满足以下关系:

其中 $n$ 是奇数,它决定了中心区域 $j$ 的比例。 在本文中,对于小于或大于 150 的边界框的比例,$n$ 被设置为 3 和 5。 图3示出了当 $n = 3$ 且 $n = 5$ 时的两个中心区域。 根据等式(1),我们可以确定尺度感知的中心区域,然后检查中心区域是否包含中心关键点。

CenterNet_Triplets%2Ffig3.jpg

损失函数

上式中 $L^{co}_{det}$ 和 $L^{ce}_{det}$ 表示 Focal Losses,它们分别用于检测 Corners 和 Center Keypoints。$L^{co}_{pull}$ 是 corners 的 “pull” 损失,用于 最小化对于相同物体的嵌入向量的距离。 $L^{co}_{push}$ 是 corners 的 “push” 损失,用于 最大化属于不同物体的嵌入向量的距离。$L^{co}_{off}$ 和 $L^{ce}_{off}$ 是 $l_1$ 损失,用于预测角点和中心关键点的 offsets。 $\alpha$ ,$\beta$ 和 $\gamma$ 表示相应损失的权重,分别设置为 0.1, 0.1 和 1。关于损失函数可以参考 CornerNet 了解详细信息。我们使用8个Tesla V100(32GB)GPU训练CenterNet,并使用批量大小为48.最大迭代次数为480K。我们在前450K迭代中使用2.5×10-4的学习速率,然后以2.5×10-5的速率继续训练30K迭代。

Enriching Center and Corner Information

Center Pooling: 物体的几何中心不一定传达可识别性非常强的视觉图案(例如,人的头部包含特征很强的视觉图案,但是中心关键点通常位于人体的中间)。为了解决这个问题,我们提出了使用 Center Pooling 来捕获更丰富和更易识别的视觉模式。图4(a)显示了 Center Pooing 的原理。 Center Pooling 的详细过程如下:backbone 输出一个特征图,并确定特征图中的某个像素点是否是中心关键点,我们需要在 水平和垂直方向上找到最大值并将它们加在一起。通过这样做, Center Pooling 有助于更好地检测中心关键点。

CenterNet_Triplets%2Ffig4.jpg

Cascade Corner Pooling: Corners 通常位于物体外部,因此缺乏物体的局部外观特征。Corner-Net 使用 Corner Pooling 来解决这个问题。Corner Pooling 的原理如图4(b)所示。Corner Pooling 旨在找到边界方向上的最大值,以便确定角点。 但是,它会使得 corners 对边缘变得敏感(不稳定)。 为了解决这个问题,我们需要让角落 “看到” 物体的 visual pattern。 Cascade Corner Pooling 的原理如图4(c)所示。它首先沿着边界查找边界最大值,然后沿着边界最大值的位置向物体内部 “看” 以找到内部最大值,最后,将两个最大值一起添加。 通过这样做,Corners 可以同时获得边界信息和物体的 visual pattern。

CenterNet_Triplets%2Ffig5.jpg

通过在不同方向组合 Corner Pooling [20],可以轻松实现 Center Pooling 和 Cascade Corner Pooling。 图5(a)显示了 Center Pooling 模块的结构。为了在一个方向(例如水平方向)上取最大值,我们只需要将左池和右池连接起来即可找到任意一点在水平方向上的最大值。 图5(b)显示了 Cascade Top Corner Pooling 模块的结构。与 CornerNet 中的 Top Corner Pooling 相比,我们在 Top Corner Pooling 之前添加了一个 Left Corner Pooling.

CenterNet(Objects as Points)

简介

首先假设输入图像为 $I \in R^{W\times H\times 3}$,其中 $W$ 和 $H$ 分别为图像的宽和高,然后在预测的时候,我们要生成关键点的热点图(keypoint heatmap): $\hat Y \in [0, 1]^{\frac{W}{R}\times \frac{H}{R} \times C}$ ,其中 $R$ 为输出对应原图的步长, 文章中为 4,而 $C$ 是在目标检测中对应着检测点的数量,如在COCO目标检测任务中,这个 $C$ 的值为80,代表当前有80个类别。

这样, $\hat Y_{x, y, c} = 1$ 就代表在 $(x,y)$ 处存在有类别为 $c$ 的物体. 在训练流程中, CenterNet 和 CornerNet 类似, 对于每个 ground truth 的对应类, 都会先确定其真实的关键点(true keypoint), 中心关键点的计算方式为 $P = \bigg( \frac{x_1 + x_2}{2}, \frac{y_1 + y_2}{2}\bigg)$, 对于下采样后的坐标, 我们设置为 $\tilde p = \lfloor \frac{p}{R} \rfloor$, 其中 $R$ 是下采样因子, 文中使用 $R=4$.
然后利用 $Y \in [0, 1]^{\frac{W}{R} \times \frac{H}{R} \times C}$ 来对图像进行标记, 并用一个高斯核 $Y_{xyc} = exp\bigg(- \frac{(x-\tilde p_x)^2 + (y - \tilde p_y)^2}{2 \sigma^2_p} \bigg)$ 来将关键点分布到特征图上, 注意, 如果某一个类的两个高斯分布发生了重叠, 那么直接取较大的值即可. 下图是官方源码中生成的一个高斯分布.

SummaryOfComputerVision%2Fgaussian.jpg

CenterNet 使用同一个网络(攻击卷积计算)来预测关键点 $\hat Y$,offsets $\hat O$ 和 size $\hat S$. 因此整个网络在每个位置的输出总共 $C + 4$ 维。其中 4 代表中心点的坐标和边界框的 w, h(size prediction).

损失函数

CenterNet 的损失函数由三部分组成, 分别为:

  • $L_k$: 判断特征图谱上某一点存在物体的可能性, 用 Focal Loss 进行定义
  • $L_{off}$: 为了恢复由输出步幅引起的离散化误差, 预测每个中心点的 local offset, 用 L1 损失进行定义
  • $L_{size}$: 回归物体 $k$ 的尺寸 $s_k = (x^{(k)}_2 - x^{(k)}_1, y^{(k)}_2 - y^{(k)}_1)$. 为了控制计算成本, 我们对所有的物体类别都使用 single size 的预测, $\hat S \in R^{\frac{W}{R} \times \frac{H}{R} \times 2}$. 使用 L1 损失进行定义.

在原文所有实验中 $\lambda_{size}= 0.1$, $\lambda_{off}= 1$。 所有的输出都共享同一个 fully-convolutional backbone 网络. 整个网络在每个位置的输出总共 $C + 4$ 维。其中 4 代表中心点的坐标和边界框的 w, h(size prediction).

中心点正负分类损失:

首先, 如果不看第二项中的 $(1-Y_{xyc})^{\beta}$, 那么这个损失就是经典的 Focal Loss 损失, 其中的 $\alpha$ 相当于 Focal Loss 原文中的 $\gamma$. 而 $(1-Y_{xyc})^{\beta}$ 的主要作用是: 减少 Focal Loss 对于那些距离 gt 中心点较近的 hard example 的关注度. 首先, 我们知道 Focal Loss 会对于 hard example 分配更加关注, 分配更大的权重, 而根据 CenterNet 对于正样本的定义, 只有物体真正的中心才是正样本, 其余的都是负样本, 因此, 对于靠近中心点的位置, 同样也会被标记成负样本, 但是这些负样本由于更靠近中心点的原因, 尝尝会以较高的置信度被识别成正样本(FP), 形成 hard example. 但是这种 hard example 是情有可原的, 因为它离中心点较劲, 所以我们不希望 Loss 过多的关注这些点, 而由于 $Y_{xyc}$ 是根据真实中心点向外扩散的高斯分布, 因此, 热图上的值恰恰也反映了这个点距离中心点的远近(值越大离的越近), 因此我们使用 $(1-Y_{xyc})^{\beta}$ 来降低这些距离中心点较近的 hard negative examples 对 Loss 的贡献程度. 一方面这样做是比较合情合理的, 另一方面, 由于我们只选用 gt center 作为正样本, 而其他的所有点都是负样本, 因此正负样本的数量会极度的不均衡(这一点和 FCOS 正相反, FCOS 有大量的正样本), 因此 $(1-Y_{xyc})^{\beta}$ 还可以起到缓解正负样本不均衡的作用.

中心点偏置(offset)补偿损失:
在将中心点从原图映射到特征图上时, 物体在特征图上的中心点会发生一次取整操作, 这样再将特征图中心点重新映射到原始图像上时, 会带来精度误差, 因此对于每一个中心点, 额外采用了一个 local offset $\hat O \in R^{\frac{W}{R} \times \frac {H}{R} \times 2}$ 来补偿, 该偏置(offset)使用 L1 loss 来训练.

上式中, $\hat O_{\tilde p}$ 是我们预测出来的偏置, 而 $(\frac{p}{R} - \tilde p) = (\frac{p}{R} - \lfloor \frac{p}{R} \rfloor)$ 则是真实的偏置. 对于某 k 类的热图, 由于尺寸固定, 因此它所有位置的精度损失是相同的, 所以同一类的中心点, 会共享同一个 offset prediction. 在官方代码中, 这部分实现为:

1
2
3
4
# ct 即 center point reg是偏置回归数组,存放每个中心点的偏置值 k 是当前图中第 k 个目标
reg[k] = ct - ct_int
# 实际例子为
# [98.97667 2.3566666] - [98 2] = [0.97667, 0.3566666]

中心点对应物体尺寸的损失:

假设 $(x^{(k)}_1, y^{(k)}_1, x^{(k)}_2, y^{(k)}_2)$ 代表物体 $k$ 的 bbox, 其类别为 $c_k$. 它的 center points 为 $p_k = \bigg(\frac{x^{(k)}_1 + x^{(k)}_2}{2}, \frac{y^{(k)}_1 + y^{(k)}_2}{2} \bigg)$, 我们利用 keypoint estimator $\hat Y$ 去预测所有的 center points. 然后, 对每个物体 $k$ 的尺寸进行回归, 最终回归到物体尺寸的真实值 $s_k = (x^{(k)}_2 - x^{(k)}_1, y^{(k)}_2 - y^{(k)}_1)$. 对于物体尺寸回归, 同样适用 L1 损失, 如下:

图4显示了网络输出的 overview。

CenterNet_Points%2Ffig4.jpg

From points to bounding boxes: 在 Inference 阶段,我们首先 独立 地提取 每个类别 热图中的峰值。我们会检测其值大于或等于其8个连接邻居的所有响应作为峰值, 并保持前100个峰值(每个类别都会提取 100 个峰值)。令 $\hat P_c$ 为 $c$ 类的 $n$ 个检测中心点的集合。 每个关键点位置由整数坐标 $(x_i,y_i)$ 给出。我们使用关键点值 $\hat Y_{x_i y_i c}$ 作为其检测置信度的度量,并在该位置处生成边界框

上式中, $(\delta \hat x_i, \delta \hat y_i) = \hat O_{\hat x_i, \hat y_i}$ 代表 offset prediction, $\hat w_i, \hat h_i = \hat S_{\hat x_i, \hat y_i}$ 代表 size prediction. 所有的输出都直接从 keypoint estimation产生,无需使用基于 IoU 的非最大值抑制(NMS)或其他后处理。 峰值关键点提取可以看做是 NMS 的替代方案,并且可以使用3×3最大池化操作在设备上有效地实现。

CenterNet(Points) 与其他 One-Stage 方法的区别

CenterNet的 “anchor” 仅仅会出现在当前目标的中心位置处而不是整张图上,所以也没有所谓的 box overlap 大于多少多少的算 positive anchor 这一说,也不需要区分这个 anchor 是物体还是背景 - 因为每个目标只对应一个 “anchor”,这个anchor是从heatmap中提取出来的,所以不需要NMS再进行来筛选
CenterNet的输出分辨率的下采样因子是4,比起其他的目标检测框架算是很小的(Mask-Rcnn最小为16、SSD为最小为16)

本文的方法与基于 anchor 的一阶段方法密切相关[33,36,43]。中心点可以看作是一个与形状无关的 anchor(见图3)。 但是,有一些重要区别:

  1. CenterNet 仅使用中心点的 location 作为正样本,而不是根据 gt box 的 overlap 来确定正负样本, 因此不需要设置区分前景和背景的手动阈值.
  2. 每个物体只对应一个 positive anchor,因此物体之间的 box 没有重复的, 故而不需要 NMS 来对 box 进行去重.
    CenterNet_Points%2Ffig3.jpg
  3. 与传统物体探测器(输出步幅为16)相比,CenterNet的输出分辨率的下采样因子是 4,比起其他的目标检测框架算是比较小的(Mask-Rcnn最小为16、SSD为最小为16). 使用较小的步幅原因, 个人认为是因为 CenterNet 仅仅利用物体的中心点作为 box 的回归依据, 那么就需要特征图谱上对应点保留有足够的信息才行, 如果步幅过大, 对于小物体来说, 信息就不足了, 回归效果也会变差.

CenterNet(Points) 的缺点

  • 在实际训练中,如果在图像中,同一个类别 中的某些物体的GT中心点,在下采样时会挤到一块,也就是两个物体在GT中的中心点重叠了,CenterNet对于这种情况也是无能为力的,也就是将这两个物体的当成一个物体来训练(因为只有一个中心点)。同理,在预测过程中,如果两个同类的物体在下采样后的中心点也重叠了,那么CenterNet也是只能检测出一个中心点. 但是这种问题属于同类别的群落遮挡, 目前 Anchor-based 方法也不能很好的解决这个问题.
  • CenterNet(Points) 只使用了一个步长上的特征图谱进行预测, 这使得他在面对尺度范围变化较大的情况时, 无法更好的拟合物体尺度的变化. 另外, 对于小物体的检测, 效果也有限.

其他

RefineDet, SNIP, SNIPPER, Fitness NMS, RFBNet, M2Det, TridentNet, HTC, NAS-FPN(为开源)

Guided Anchoring
Libra R-CNN
Hybrid Task Cascade

图像处理篇

图像旋转

图像左右翻转,

1
for (auto vi : matrix) reverse(vi.begin(), vi.end());

图像上下翻转

1
reverse(matrix.begin(), matrix.end())

旋转图像, 图像旋转 90 度.
先逆置, 再转置.

1
2
3
4
5
6
7
8
9
10
11
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
matrix[:] = matrix[::-1]
n = len(matrix)
for i in range(n):
for j in range(i+1, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
return

图像放缩

Python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
img = [[1] * w for _ in range(h)]

dst_img = [[0] * dst_w for _ in range(dst_h)]

scale_w = w / dst_w
scale_h = h / dst_h

for dst_x in range(dst_w):
for dst_y in range(dst_h):

src_x = (dst_x + 0.5) * scale_w - 0.5
src_y = (dst_y + 0.5) * scale_h - 0.5

x1 = int(math.floor(src_x))
x2 = int(math.ceil(src_x))

y1 = min(int(math.floor(src_y)), src_w - 1)
y2 = min(int(math.ceil(src_y)), src_h - 1)

r1 = (src_x - x1) * src_img[x1][y1] + (x2 - src_x) * src_img[x2][y1]
r2 = (src_x - x1) * src_img[x1][y2] + (x2 - src_x) * src_img[x2][y2]
dst_img[dst_x][dst_y] = int((src_y - y1) * r1 + (y2 - src_y) * r2)

双线性插值
双线性插值本质上就是在两个方向上做线性插值.

双线性插值

假如我们想得到未知函数 $f$ 在点 $P = (x, y)$ 的值, 在图像中, 这个 $f$ 代表的就是某个像素点的像素值. 当我们已知点 $P$ 周围四个点的值以后, 我们可以首先在 $x$ 方向上进行线性插值, 得到:

然后在 $y$ 方向上进行线性插值, 得到:

有一个更简单的确定权重系数的方法, 就是直接使用浮点数的小数部分, 另一边的权重为 1 - 小数部分. 例如上图中, P 的坐标为 (2.4, 3.7), 那么可以很容易知道, P 距离左边的距离就是 0.4, 距离下边的距离就是 0.7, 而右边和上边的距离分别用 1 减去对应距离即可.

边缘检测

https://www.jianshu.com/p/2334bee37de5

https://blog.csdn.net/KYJL888/article/details/78253053

边缘的定义

边缘一般是指图像在某一局部强度剧烈变化的区域, 强度变化一般有两种情况:

  1. 阶跃变化: 灰度逐渐变明亮
    SummaryOfComputerVision%2Fedge1.jpg
  2. 屋顶变化: 灰度由暗到亮再变暗的过程
    SummaryOfComputerVision%2Fedge2.jpg

边缘检测基本原理:
既然边缘是灰度变化最剧烈的位置, 最直观的想法就是求微分.
对于第一种情况: 一阶微分的峰值为边缘点, 二阶微分的零点为边缘点
对于第二种情况: 一阶微分的零点为边缘点, 二阶微分的峰值为边缘点

1.导数,连续函数上某点斜率,导数越大表示变化率越大,变化率越大的地方就越是“边缘”,但是在计算机中不常用,因为在斜率90度的地方,导数无穷大,计算机很难表示这些无穷大的东西。

2.微分,连续函数上x变化了 $dx$,导致 $y$ 变化了 $dy$,$dy$ 值越大表示变化的越大,那么计算整幅图像的微分,$dy$ 的大小就是边缘的强弱了。
微分与导数的关系:$dy = f ‘(x) dx$

SummaryOfComputerVision%2Fedge3.jpg

在连续函数里叫微分,因为图像是离散的,叫 差分,和微分是一个意思,也是求变化率。
差分的定义: $f(x + 1) - f(x)$,用后一项减前一项。
按先后排列: -f(x) + f(x + 1)
提出系数 [-1, 1] 作为滤波模板,跟原图 f(x) 做卷积运算就可以检测边缘了

在实际的图像处理中, 通常采用差分的方式来求边缘, 但是用差分方法进行边缘检测必须使差分的方向和边缘的方向 互相垂直., 这就需要对图像的不同方向分别进行差分运算, 一般情况下, 我们将边缘分为水平边缘, 垂直边缘和对角线边缘三种. 对其使用不同方向的差分, 求得边缘集合.

Roberts算子

使用 对角方向相邻两像素值之差 作为差分结果, 其计算方法如下:

如果写成卷积运算的形式, 卷积核分别为:

Prewitt 算子

prewitt 算子结合了差分运算和领域平均的方法

Sobel算子

sobel 算子和 prewitt 算子类似, 但考虑到了相邻不同像素点的权重应该不同, 其认为中心点的权重应该更高, 所以采用的是加权平均

laplacian(拉普拉斯)算法

拉普拉斯是使用二阶差分计算边缘的, 先看连续函数的情况下:
在一阶微分图中极大值或极小值处,认为是边缘。
在二阶微分图中极大值和极小值之间的过 0 点,被认为是边缘。

拉普拉斯算子推导:
一阶差分:$f ‘(x) = f(x) - f(x - 1)$
二阶差分:$f ‘(x) = (f(x + 1) - f(x)) - (f(x) - f(x - 1))$
化简后:$f ‘(x) = f(x - 1) - 2 f(x)) + f(x + 1)$
提取前面的系数:$[1, -2, 1]$

二维情况下, 同理可得
f ‘(x, y) = -4 f(x, y) + f(x-1, y) + f(x+1, y) + f(x, y-1) + f(x, y+1)
提取各个系数,写成卷积模板的形式:

考虑两个斜对角的情况:

Canny算子

canny计算过程

  1. 高斯滤波器平滑图像。
  2. 一阶差分偏导计算梯度值和方向。
  3. 对梯度值不是极大值的地方进行抑制。
  4. 用双阈值连接图上的联通点。

通俗说一下,

  1. 用高斯滤波主要是去掉图像上的噪声。
  2. 计算一阶差分,OpenCV 源码中也是用 sobel 算子来算的。
  3. 算出来的梯度值,把不是极值的点,全部置0,去掉了大部分弱的边缘。所以图像边缘会变细。
  4. 双阈值 t1, t2, 是这样的,t1 <= t2
    大于 t2 的点肯定是边缘
    小于 t1 的点肯定不是边缘
    在 t1, t2 之间的点,通过已确定的边缘点,发起8领域方向的搜索(广搜),图中可达的是边缘,不可达的点不是边缘。
    最后得出 canny 边缘图。

各个算子之间的区别

sobel 产生的边缘有强弱,抗噪性好
laplace 对边缘敏感,可能有些是噪声的边缘,也被算进来了
canny 产生的边缘很细,可能就一个像素那么细,没有强弱之分。

滤波模板为什么大多是奇数

因为模板是偶数的话,卷积出来的结果应该是放在中间的,不方便表示。
例如:图像 [10, 20, 30] 跟 [-1, 1] 卷积后的值,应该放在图像 10 跟 20 中间的位置,就是应该放在 0 和 1 号位置的中间,也就是 0.5 号位,但是图像是离散的,中间没得放,只能放在 0 号位,也就是 10 的位置,就偏差了 0.5 的位置,为了方便处理,滤波模板一般都是奇数个的,3,5,7 个的。

常用滤波

均值滤波

用像素点周围像素的平均值代替原像素值, 在滤除噪声的同时也会滤掉图像的边缘信息.

均值滤波的核为:

中值滤波

用像素点周围领域像素集中的中值代替原像素. 中值滤波在去除椒盐噪声和斑块噪声时, 效果非常明显.

高斯滤波

可以用两个一维高斯进行矩阵乘法, 使其变成一个二维高斯.

其中 W 是权重, $i$ 和 $j$ 是像素索引, $K$ 是归一化常量. 公式中可以看出, 权重之和像素之间的空间距离有关系, 无论图像的内容是什么, 都有相同的滤波效果.

高斯核半径与 $\sigma$ 之间的关系(opencv 采用的默认值):

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import cv2
import numpy as np

kern = cv2.getGaussianKernel(5, 1)
# print(kern)

kern2 = cv2.getGaussianKernel(5, 1)

kernel_cv2 = kern * kern2.reshape(1,5)
print('cv2 kernel')
print(kernel_cv2)

# import numpy as np

def matlab_style_gauss2D(shape=(3,3),sigma=0.5):
"""
2D gaussian mask - should give the same result as MATLAB's
fspecial('gaussian',[shape],[sigma])
"""
m,n = [(ss-1.)/2. for ss in shape]
y,x = np.ogrid[-m:m+1,-n:n+1]
h = np.exp( -(x*x + y*y) / (2.*sigma*sigma) )
h[ h < np.finfo(h.dtype).eps*h.max() ] = 0
sumh = h.sum()
if sumh != 0:
h /= sumh
return h

kernel = matlab_style_gauss2D((5,5), sigma=1)
print('matlab kernel')
print(kernel)

双边滤波

双边滤波器是一种非线性滤波器, 也是一种保边滤波器, 所谓的保边滤波器是指滤波过程中能够有效的保留图像中的边缘信息的一类特殊滤波器.

双边滤波器简单的说就是一种同时考虑了像素空间差异与强度差异的滤波器, 因此具有保持图像边缘的特性. 双边滤波器 只是在原有的高斯函数的基础上加了一项, 如下:

其中, $I$ 是像素的强度值, 所有在强度差距大的地方(边缘), 权重会减小, 滤波效应也对应变小. 总体而言, 在像素强度变化不大的区域, 双边滤波有类似于高斯滤波的效果, 而在图像边缘的强度变化较大的地方, 由于滤波的权重变小, 使得当前像素值更关注自身, 起到了保持边缘信息的作用.

图像处理中滤波, 卷积, 相关之间的区别

  • 滤波: 图像对应像素和掩膜的乘积之和. 滤波操作默认会对图像边缘填充, 使得最终产生的滤波结果的 图像大小不变
  • 卷积: 卷积操作也是卷积核与图像对应位置的乘积和, 但是卷积操作在做乘积之前, 需要先将卷积核翻转 180 度, 之后再做乘积. 卷积操作默认不会填充, 所以 卷积操作会改变图像的大小
  • 相关: 相关操作和卷积差不多, 只不过在进行乘积和之前 不会进行 180 度的翻转., 同样, 相关操作也会改变图像的大小.

常见问题篇

反向传播算法推导

SummaryOfComputerVision%2Fback_propagation.jpg

变量定义

  • $w^l_{jk}$ 表示第 $(l-1)$ 层的第 $k$ 个神经元连接到第 $l$ 层的第 $j$ 个神经元的权重
  • $b^l_j$ 表示第 $l$ 层的第 $j$ 个神经元的偏置
  • $z^l_j$ 表示第 $l$ 层的第 $j$ 个神经元的 临时输出值(未经过激活的), 即 $z^l_j = \sum_k w^l_{jk} a^{l-1}_k + b^l_j$.
  • $a^l_j$ 表示第 $l$ 层的第 $j$ 个神经元的输出, 即 $a^l_j = \sigma \bigg(\sum_k w^l_{jk} a^{l-1}_{k} + b^l_j \bigg)$. 其中, $\sigma$ 表示激活函数.

代价函数

代价函数被用来计算网络输出值和实际值之间的误差, 我们以常用的代价函数, 二次代价函数为例:

其中, $x$ 表示输入样本, $y$ 表示实际值, $a^L$ 表示网络最终预测输出, $L$ 表示神经网络的最大层数.

参数更新

我们主要利用反向传播算法来更新神经网络中每个参数的值, 包括 $w^l_{jk}$ 和 $b^l_j$ 等. 根据梯度下降法, 在更新参数时, 我们需要获得损失函数相对于该参数的偏导数.

对于网络中任意一层的神经元的权重, 根据链式法则, 它的导数如下所示:

也就是说, 某一层某个神经元的权重梯度, 等于 前一层对应神经元的输出值 乘以 当前层对应节点的 “误差项”. 注意, 这里的 “误差” 是指损失函数对神经网络每一层临时输出值的梯度(注意是临时, 指未经激活的输出值).

对于网络中任意一层的神经元的偏置, 它的导数如下所示:

也就是说, 某一层某个神经元的偏置梯度, 等于 当前层对应节点的 “误差项”.

可以看出, 权重矩阵和偏置的梯度都与 “误差项” 有关, 下面来推导误差项的计算公式.

注意, $\odot$ 表示 Hadamard 乘积, 也就是两个矩阵或者向量对应位相乘(element-wise 相乘)

首先, 根据求导法则可以计算最后一层神经网络的误差项(相对于临时输出值的梯度):

上式是针对单个神经元的, 根据全连接层计算规则可知, 每个神经元的值不受其他神经元的影响, 因此可以用 Hadamard 乘积来表示当前层所有神经元的误差项, 只不过对于上面的权重梯度和偏置梯度来说, 只需要用对应神经元的误差项即可.

然后, 根据链式法则, 我们可以计算每一层神经网络产生的误差项(相对于临时输出值的梯度)

所以, 对于当前层所有神经元的误差项, 可以如下表示:

反向传播算法伪码

  • 输入训练集
  • 对于训练集中的每个样本 $x$, 设置输入层对应的激活值 $a^1$
  • 计算前向传播过程
  • 计算输出层误差项
  • 计算每一层的误差项
  • 使用梯度下降, 更新参数:

https://blog.csdn.net/u014313009/article/details/51039334

目前的 SOTA 目标检测模型

比较经典的, 比如 R-CNN 系列, YOLO, 以及 SSD 等属于比较具有代表性的一些模型. 在此基础上, 从 17 年开始, 又有很多新的模型出现, 按照不同的关注角度来分, 大致有这么几类.
首先是对卷积网络的特征金字塔的构建和生成进行优化和改进的模型, 比如 FPN 和 M2Det 等.
其次是从感受野的角度进行优化的模型, 比如 Deformable ConvNet, RFBNet.
还有从 bounding box 回归角度进行优化的 RefineDet.
然后还有从损失函数及样本不均衡角度进行优化, 使用 Focal Loss 的 RetinaNet等.

  • 在 feature map 的多尺度金字塔特征上进行优化: SSD, FPN, PFPNet, M2Det
  • 在 bbox 回归上进行优化: RefineDet
  • 然后也可以在感受野上进行优化: DCN, RFBNet
  • 以另一种角度来选取 bbox: CornerNet
  • 在 anchor 的生成上进行优化: CornerNet, MetaAnchor
  • 对 NMS 和 IoU 上进行优化: Soft-NMS, Sofer-NMS, Fitness NMS, Relation Network, IoUNet, Cascade R-CNN
  • 在 backbone 网络上优化: DetNet
  • 在损失函数和样本不均衡上进行优化: Focal Loss, Gradient Harmonized(梯度均衡)
  • 针对移动端设备: Pelee
  • 多尺度问题: SNIP, SNIPER
  • 小目标检测: STDNet, Augmentation for small od.
  • 超大目标检测: HKRM

FPN,
RefineDet, RFBNet
STDN

SSD, FPN, RefineDet, PFPNet, STDN, M2Det 等特征金字塔的区别

SSD 是通过使用 backbone(VGG16) 的最后两个卷积段的特征图谱和 4 个额外卷积层特征图谱来构建特征金字塔的.
FPN 是通过融合深层和浅层的特征图谱来构建特征金字塔. 主要做法是将最后一层特征图谱进行不断的上采样得到 top-down 结构的特征图谱, 然后与原始的 down-top 结构的特征图谱相结合, 从而得到新的表征能力更强的特征金字塔结构.
M2Det 主要是从现在特征金字塔的一些缺点出发进行优化, 它认为, 现有金字塔结构中每一个尺度的特征仅仅来自于 backbone 中单一层级(level)的特征. 这样一来, 小尺度的特征图谱往往缺少浅层低级的语义信息, 而大尺度的特征图谱又缺少深层的高级语义信息. 因此, 作者就提出了融合多个层级特征的 Multi-Level FPN (MLFPN). 从而可以让小尺寸的特征图谱上包含有更多的低级语义信息, 让大尺寸的特征图谱包含更多的高级语义信息.

FCN 是如何降低计算量的

面对 $384\times 384$ 的图像, 让含全连接层的初始卷积神经网络以 32 像素的步长独立对图像中的 $224\times 224$ 块进行多次评价, 其效果和使用全卷积网络进行一次评价时相同的. 后者通过权值共享起到了加速计算的作用.

PyTorch 和 TensorFlow 的区别

两个框架虽然都是在张量上运行, 并且将模型都看做是一个有向非循环图(DAG), 但是二者对于图的定义不同. TensorFlow 是基于静态计算图, 因此是先定义再运行, 一次定义多次运行, 而 PyTorch 是基于动态计算图的, 是在运行过程中被定义的, 在运行的时候进行构建, 可以多次构建多次运行.
动态图的还有一个好处就是比较容易调试, 在 PyTorch 中, 代码报错的地方, 往往就是代码错写的地方, 而静态图需要先根据代码生成 Graph 对象, 然后在 session.run() 时报错, 但是这种报错几乎很难直接找到代码中对应的出错段落.

链接】Variable和Tensor合并后,PyTorch的代码要怎么改
https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/80105285

浅层特征图检测小目标,为什么不同时也检测大目标

浅层感受野较小, 并且语义信息比较低级
深层感受野较大, 包含更多高级语义信息

onestage目标检测算法中,浅层特征图检测小目标

GPU 两个重要指标之间的关系

科普帖,深度学习中GPU和显存分析: https://zhuanlan.zhihu.com/p/31558973

SummaryOfComputerVision%2Fgpu_nvidia-smi.jpg

上图是nvidia-smi命令的输出, 其中最重要的两个指标:

  • 显存占用
  • GPU 利用率

显存占用和GPU利用率是两个不一样的东西,显卡是由GPU计算单元和显存等组成的,显存和GPU的关系有点类似于内存和CPU的关系。

显存可以看成是空间,类似于内存

  • 显存用于存放模型,数据
  • 显存越大,所能运行的网络也就越大
    GPU计算单元类似于CPU中的核,用来进行数值计算。衡量计算量的单位是flop: the number of floating-point multiplication-adds,浮点数先乘后加算一个flop。计算能力越强大,速度越快。衡量计算能力的单位是flops: 每秒能执行的flop数量
1
2
1*2 + 3  # 2 flop
1*2 + 3*4 + 4*5 # 5 flop

神经网络显存占用

神经网络模型占用显存的部分包括:

  • 模型的输入
  • 模型中间网络层的输出
  • 模型参数
  • 模型参数的梯度
  • 优化器动量

首先, 要确定运算过程中使用的数据类型, 常用的数据类型有: int8, int16, float16, float32 等. 通常, 在训练时, 我们为了更高的精度需要使用 float32 的数据类型, 而在预测时, 我们为了压缩模型的大小会选用 float16 的数据类型(预测时几乎不会带来精度损失).
距离来说, 一个 $(32, 3, 256, 256)$ 的 tensor, 如果使用的是 float32 的数据类型, 那么它的显存占用约为 24M, 而如果使用的是 float16 的数据类型, 那么它的显存占用约为 12M.

下面来逐一分析:

  1. 模型的输入: 一般就是图片的分辨率大小, 另外需要注意的时, 一般不需要计算模型输入(图片)的梯度.
  2. 模型中间网络层的输出: 这部分显存主要是指每个网络层输出的 feature map, 它的形状和模型的具体结构有关, 越大越厚的 feature map 占用的显存越多.
  3. 模型参数: 只有有参数的层, 才会占用显存, 这些网络层一般包括卷积层, 全连接层, BN 层等等. 其它的不包括参数的层如激活层, 池化层, Dropout 层等均不占用显存. 在 PyTorch 中, 当执行完model=MyModel().cuda()之后就会占用相应的显存, 占用的显存大小基本与下面计算的差不多(会稍大一些, 因为还存在一些其它的开销):
    • Linear(M->N): $M\times N$
    • Conv2d(Cin, Cout, K): $C_{in} \times C_{out} \times K\times K$
    • BatchNorm(N): $2N$
    • Embedding(N, W): $N\times W$
  4. 模型参数的梯度: 在训练的时候, 由于需要进行反向传播的原因, 我们还需要保存每个参数对应的梯度, 这部分的显存占用和参数占用的显存大小一致. 通常来说, 神经网络的每一层输入输出都需要保存下来, 用来进行反向传播. 但是在某些特殊情况下, 我们可以不保存输入. 比如 ReLU, 在 PyTorch 中, 使用nn.ReLU(inplace=True)能将激活函数的输出直接覆盖保存于模型的输入之中, 节省不少内存(此时的反向传播:$y = relu(x) -> dx = dy.copy(); dx[y<=0]=0$).
  5. 优化器的动量: 不同的优化器需要的信息量不同, 对于普通的 SGD 来说, 仅仅需要参数的梯度就足够了, 因此 总显存(参数+梯度+动量) 占用为参数显存的 两倍, 但是对于 Momentum-SGD, 不仅需要梯度, 还需要动量, 因此总显存占用为参数显存的 三倍, 对于 Adam 优化器, 需要更多的动量信息, 总显存占用为参数显存的 四倍.

在深度学习神经网络的显存占用中, 我们可以得到: . 可以看出, 显存并不是简单的和 batch_size 成正比, 尤其是在模型自身比较大的情况下.

节省显存的方法

  • 降低 batch_size
  • 进行下采样, 降低 feature map 的大小
  • 减少全连接层
  • 将激活层设置成inplace=True
  • 使用字节数更少的数据类型(float8, float16等)

显存计算, PyTorch 显存跟踪: https://www.cnblogs.com/kk17/p/10262688.html
https://oldpan.me/archives/how-to-calculate-gpu-memory

各个网络层的参数量

同时计算 weight 和 bias

  • Linear (M->N): $(1+M)\times N$
  • Conv2d (Cin, Cout, Kw, Kh): $C_{out}\times (C_{in} \times K_w\times K_h + 1)$
  • BatchNorm (N): $2N$
  • Embedding (N, W): $N\times W$

FLOPs 计算

FLOPs: Floating point operations, 通常是指浮点运算的次数, 乘法和加法都各自算作一次运算. Paper 里比较流行的单位是 GFLOPs, 即 $10^9$ FLOPs
FLOPS: Floating Point Operations Per Second, 代表算力
Mult-Adds: 统计乘法加法对, 因此 FLOPs 通常是 Mult-Adds 的两倍, 但是有时候, 也会用 FLOPs 直接指代 Mult-Adds (ShuffleNet 论文中就是这么干的)

  1. 矩阵乘法: $M\times N$ 和 $N\times P$ 的两矩阵相乘, 最终输出的矩阵形状为 $M\times P$, 计算 $M\times P$ 上一个元素需要 $N$ 次乘法, 以及 $N-1$ 次加法, 因此, 矩阵乘法所需的总的 FLOPs 为:
  2. 卷积层: ($C_{in}, C_{out}, K_w, K_h, W_{out}, H_{out}$)(输入输出通道数, 卷积核尺寸, 输出特征图谱尺寸): 首先计算得到 output feature map 上一个像素的值需要的 FLOPs 为 $C_{in}\cdot K_w \cdot K_h$ 次乘法, 以及 $C_{in}\cdot K_w \cdot K_h - 1$ 次加法, 如果算上 bias 加法, 则为 $C_{in}\cdot K_w \cdot K_h$ 次加法, 然后根据输出的 tensor 的 shape, 可以知道总共需要计算的像素点数为 $C_{out}\cdot W_{out} \cdot H_{out}$. 于是可以得到一个卷积层总共的 FLOPs 为:
  3. 全连接层: (M, N) (输入神经元数量, 输出神经元数量). 输出一个神经元需要 $M$ 次乘法, $M-1$ 次加法, 如果算上 bias, 就是 $M$ 次加法, 扩展到输出 N 个神经元, 所需要 FLOPs 为:

除 FLOPs 外其他影响模型速度的因素

  • MAC (Memory Access Cost): FLOPs 与 MAC 并非完全一致, 例如 element-wise 操作 FLOPs 较低, 但是却具有更多的 MAC, 另外, Pointwise Conv 和 Group Conv 也会根据不同的超参而具有不同的的 MAC, 即使控制他们的 FLOPs 不变.
  • 计算并行度: 碎片化程度较高的 building block (如 Inception 系列和 NAS 结构), 其并行度比较低, 进而影响速度
  • 硬件平台的优化: 硬件平台对于不同尺寸的卷积核, 有时会有不一样的优化程度.
  • 内存带宽, 见下文

https://www.jiqizhixin.com/articles/2017-09-11-2 (内存带宽与计算能力,谁才是决定深度学习执行性能的关键)

结论: 模型的执行速度除了和总的计算量有关外, 还与运算强度(每单位数据执行的运算操作)有关, 比如 1x1 卷积虽然参数量是 3x3 卷积的 1/9. 但是由于运算强度降低, 导致计算性能下降, 因此其执行速度并不能降低到 1/9. 在进行算法的速度优化的时候, 可以参考 Roofline 模型曲线, 进而决定到底是应该增大带宽还是应该降低计算量.

分析:

SummaryOfComputerVision%2Fgpu_bottle.jpg

内存带宽对于硬件系统的性能影响如上图所示。如果把内存比做瓶子,运算单元比作杯子,那么数据就是瓶子里的各色颗粒,而内存接口就是瓶口,通过瓶口数据才能进入杯子被消费(处理)掉。而内存带宽就是瓶口的宽度了。瓶口宽度越窄,则数据需要越多时间才能进入杯子(处理单元)。正所谓「巧妇难为无米之炊」,如果带宽有限,那么即使处理单元无限快,在大多数时候也是处理单元在空等数据,造成了计算力的浪费.
算法对于内存带宽的需求通常使用「运算强度 (operational intensity,或称 arithmetic intensity)」这个量来表示,单位是 OPs/byte。这个量的意思是,在算法中平均每读入单位数据,能支持多少次运算操作。运算强度越大,则表示单位数据能支持更多次运算,也就是说算法对于内存带宽的要求越低。所以,运算强度大是好事!
我们来举一个例子。对于步长(stride)为 1 的 3x3 卷积运算,假设输入数据平面大小为 64x64。简单起见,假设输入和输出 feature 都为 1。这时候,总共需要进行 62x62 次卷积运算,每次卷积需要做 3x3=9 次乘加运算,所以总共的计算次数为 34596,而数据量为(假设数据和卷积核都用单精度浮点数 2byte):64x64x2(输入数据)+ 3x3x2(卷积核数据)= 8210 byte,所以运算强度为 34596/8210=4.21。如果我们换成 1x1 卷积,那么总的计算次数变成了 64x64=4096,而所需的数据量为 64x64x2 + 1x1x2=8194。显然,切换为 1x1 卷积可以把计算量降低接近 9 倍,但是运算强度也降低为 0.5,即对于内存带宽的需求也上升了接近 9 倍。因此,如果内存带宽无法满足 1x1 卷积计算,那么切换成 1x1 卷积计算虽然降低了接近 9 倍计算量,但是无法把计算速度提升 9 倍。

SummaryOfComputerVision%2Fgpu_RooflineModel.jpg

典型的 Roofline 曲线模型如上图所示,坐标轴分别是计算性能(纵轴)和算法的运算强度(横轴)。Roofline 曲线分成了两部分:左边的上升区,以及右边的饱和区。当算法的运算强度较小时,曲线处于上升区,即计算性能实际被内存带宽所限制,有很多计算处理单元是闲置的。随着算法运算强度上升,即在相同数量的数据下算法可以完成更多运算,于是闲置的运算单元越来越少,这时候计算性能就会上升。然后,随着运算强度越来越高,闲置的计算单元越来越少,最后所有计算单元都被用上了,Roofline 曲线就进入了饱和区,此时运算强度再变大也没有更多的计算单元可用了,于是计算性能不再上升,或者说计算性能遇到了由计算能力(而非内存带宽)决定的「屋顶」(roof)。拿之前 3x3 和 1x1 卷积的例子来说,3x3 卷积可能在 roofline 曲线右边的饱和区,而 1x1 卷积由于运算强度下降,有可能到了 roofline 左边的上升区,这样 1x1 卷积在计算时的计算性能就会下降无法到达峰值性能。虽然 1x1 卷积的计算量下降了接近 9 倍,但是由于计算性能下降,因此实际的计算时间并不是 3x3 卷积的九分之一。

显然,一个计算系统的内存带宽如果很宽,则算法不需要运算强度很大也能轻易碰到计算能力上限决定的「屋顶」。在下图中,计算能力不变,而随着内存带宽的上升,达到计算力屋顶所需的运算强度也越低。

SummaryOfComputerVision%2Fgpu_RooflineModel2.png

Roofline 模型在算法-硬件协同设计中非常有用,可以确定算法和硬件优化的方向:到底应该增加内存带宽/减小内存带宽需求,还是提升计算能力/降低计算量?如果算法在 roofline 曲线的上升区,那么我们应该增加内存带宽/减小内存带宽需求,提升计算能力/降低计算量对于这类情况并没有帮助。反之亦然。

Anchor 的作用

结论: 为了将 bbox 的 scale 和 aspect ratio 划分到若干个固定的子空间中, 降低问题的复杂度, 同时也降低模型的学习难度. 另一方面, anchor 可以解决多个物体 overlap 过大导致检测结果容易丢失的问题(YOLO 单个 cell 只负责单个物体, Overlap 过大的多个物体可能会落在同一个 cell 中).

Faster RCNN 引入 Anchor 的 motivation:

In contrast to prevalent methods [8], [9], [1], [2] that use pyramids of images (Figure 1, a) or pyramids of filters (Figure 1, b), we introduce novel “anchor” boxes that serve as references at multiple scales and aspect ratios. Our scheme can be thought of as a pyramid of regression references (Figure 1, c), which avoids enumerating images or filters of multiple scales or aspect ratios.

可见, Anchor 机制要解决的问题是变化范围较大的 bbox 的 scale 和 aspect rotios 问题. 之前的解决方法都是利用 pyramids of images(耗时) 或者 pyramids of filters(传统图像处理). 此外, Anchor 机制还顺便解决了另外一个重要的问题: gt box 之间如果 overlap 较大, 那么它们就会落到一个 cell 上, 从而导致个别 gt box 丢失. 而 anchor 机制不同 scale 和 aspect ratio 的 anchor 会负责各自的 gt box, 即使有多个框映射到同一个 cell, 也不会导致 gt box 丢失.

R-CNN 系列类别冲突问题

怎么解决:
R-CNN 由于在进行框的筛选时, 是类别不可知的, 也就是说如果有两个不同类的物体, 重合度比较高, 那么很有可能其中一个物体的框就被 NMS 掉了. 解决办法有:

  1. 对于每一个类别, 分别进行框的筛选, 也就是变成类别可知的;
  2. 还没想出来

Anchor 的数量

当增加超过 6~9 个 anchor 后模型并没有显示出进一步的收益。 性能饱和意味着认为定义的、密度过大的 anchor 并没有呈现出巨大的优势。
过于密集的 anchor 不仅会增加 前景-背景 的优化难度,而且还可能导致模糊位置定义问题。 对于每个输出空间的 location 来说,其 anchors 的标签根据与 GT 的 IoU 值定义。 其中,有一些 anchor 被定义为 positive samples,而另一些则被定义为 negative samples。 但是,它们共享这相同的 feature maps。因此分类器不仅需要区分不同位置的样本,还需要在同一位置区分不同的 anchor。

anchor 的数量并不是越多越好, 虽然直观上来说, anchor 越多时, 可以覆盖越好的 gt box, 但是, 当 anchor 的数量过多时, 一方面由于每一个点上产生的 anchor 实际上共享了一块相似的特征, 但是这些 anchor 有一部分作为正样本, 而另一部分作为负样本, 因此, 对于神经网络来说, 他要通过不断学习来区分这些样本, 不仅如此, 他还要将这些样本与其他点产生的 anchor 区分开, 虽然 anchor 数量的增多, 学习的难度也就慢慢增多, 最终甚至会出现掉点的现象, 个人认为不会掉的特别多, 因为毕竟更多的 anchor 可以覆盖更多的局部最优解. 但是 anchor 会导致计算量的大幅度上升, 因此不建议设置过多 anchor.

FoveaBox 的优势(就 anchor 来说): (1) 神经网络的输出维度大大降低(1/A), 学习起来相对简单直接; (2) 不会出现 anchor 之间互相矛盾的现象 (3) 没有了 anchor 后, 检测网络变的更加简单直接, 扩展性更好

详细见 FoveaBox 分析

Anchor based 方法和 Anchor free 方法比较

Anchor based 方法优点:

  • Anchor-based 方法由于有先验框的存在, 其学习难度上相对较小, 学出来的框相对更加精准;
  • Anchor-based 方法由于有先验框的存在, 即使在使用特征提取能力较差的轻量级网络, 也能取得一定的效果(如 yolov3, mobilenet+detector 等)

Anchor based 方法缺点:

  • 处理的尺度范围小(基于 anchor 的设定)
  • 需要根据不同的任务设置更多的超参(anchor scale, aspect ratio)
  • 通常是数据集敏感的, 每一种超参只适用于特定的数据集
  • 有时候需要使用特定的启发式方法来解决前背景不平衡问题
  • 产生的预选框要在训练阶段和 gt box 进行 IoU 的计算才能确定正负样本, 这会占用一定的内存和运行时间, 降低训练速度. 此外, 对于 SSD 类的算法来说, 为了使其能够更为有效的检测小目标, 通常会在更大尺度的特征图上生成候选框, 这就导致候选框的数量大幅增加. (Anchor Free 虽然也会在更大尺度的特征图上操作, 但是没有 anchor 的存在, 计算量的增加可以忍受)
  • 针对不同的任务, 所有的超参都需要重新调节, 这样一个模型的迁移能力就体现不出来了.

Anchor free 方法的优点:

  • 没有先验框的约束, 其 box 的尺度变化范围更大. 对于极端尺度的物体, 检测效果强于 Anchor-Based 方法(FoveaBox 实验证明)
  • 无需设置 anchor 相关的超参数. 模型的迁移成本降低, 无需花费大量时间调参. 对于 CenterNet(Object as points) 来说, 无需计算 box 之间的 overlap, 也不用设置区分前景背景的 IoU 阈值, 也无需使用 NMS 进行后处理. 框架流程整体更加简洁.

Anchor free 方法的缺点:

  • 目前来看, 除了 FASF 之外, 大部分的 anchor free 方法都是直接从 backbone 提取的特征图谱上回归 box 的, 这样由于没有任何人工施加的先验知识(anchor), 因此对于 backbone 的特征提取性能要求较高. 在使用轻量级网络时, anchor-free 方法的性能下降要超过 anchor-based 方法的性能下降.
  • CenterNet(Object as points) 只使用了一个步长(4)上的特征图谱进行预测, 因此在面对尺度变化范围较大的物体时, 效果不好, 面对小物体时, 效果也较差.

Anchor free 方法成功的原因

  1. backbone 网络强大的特征提取能力
  2. FPN 结构对于物体多尺度范围变化的拟合能力
  3. Focal Loss 对于正样本数量不均衡问题的解决.(所有的 Anchor free 方法均使用了 Focal Loss, 具体的使用方法在于网络对于正样本的定义, FCOS 将落在 box 内部的均当做正样本, centernet 只使用角点或中心点作为关键点, ExtremeNet 使用边的极值点作为关键点)
  4. 对于 box 回归新的定义方式. (有的是回归中心点到四边的距离, 有的是回归边框的w, h)

Anchor free 方法之间的比较

模型 正负样本定义 边框回归方式 存在问题 解决方法 主要特点 遗留问题
CornerNet 角点作为正样本, 其余点均为负样本, 用高斯核对周围点赋值 由角点坐标直接确定边框, 额外使用 SmoothL1 预测下采样带来角点误差 角点分组 学习角点 embedding, 两角点 embedding 距离越小, 则属于同一组的概率越大 热图, 变体 focal loss
FSAF 物体中心区域(bbox 0.2倍) 作为正样本, 外围区域(bbox 0.5 倍减去中心区域) 作为忽略区域, 其余区域为负样本区域 当前正样本点对 GT Box 四条边界的距离, 采用 IoU Loss 进行优化. 根据在不同特征层级上的损失, 选择最小的部分进行梯度更新, 达到了在线特征选择的目的.
FoveaBox 物体中心区域($\sigma_1$ 决定) 作为正样本, 外围区域($\sigma_2$)决定作为忽略区域. 其余区域作为负样本区域 当前正样本点对 GT Box 四条边界的距离, 用 Smooth L1 优化 FoveaBox 与 FASF 是否类似. 区别在于前者根据尺度分配金字塔, 后者是在线特征选择; 回归时前者使用 Smooth L1, 后者使用 IoU Loss; 实验效果上, 前者优于后者
FCOS 如果位置 $(x,y)$ 落入到任何 GT Box 内部, 那么就将其视为正样本, 负责视为负样本 当前正样本点对 GT Box 四条边界的距离 1. 目标重叠时, 一个点会同时落入多个物体的边界框
2. 正样本数量虽然多, 但是远离物体中心的正样本位置会生成大量的低质量预测框
1. FPN, 将目标分配到不同层级的金字塔上, 这里存在一个假设前提, 就是发生重叠的物体尺寸大小必须不同, 否则依然会漏检.
2. Center-ness. 代表当前位置处于物体正中心的概率, 最终每个位置的置信度将通过 center-ness 与类别得分的乘积共同给出
FCOS 对于正样本的定义, 使得在训练时, 正样本的数量可以大幅增加, 很大程度上缓解了正负样本不均衡的问题.
ExtremeNet 四个 extreme points(最上, 最左, 最下, 最右) 和一个中心点作为正样本, 其余作为负样本. 用高斯核对热图初始化. 由极值点直接给出 bbox 1. 分组策略
2. 中心分组会对 相同大小的三个等间距共线 物体给出高置信度 FP 检测
3. 当物体边与 bbox 对齐时, 会产生多个若响应极值点
对于给定的任意四个极值点, 计算其几何中心, 如果该几何中心在对应的 center 预测热图上有较高响应, 则认为这些极值点是一个有效组合. 复杂度 $O(n^40)$, 在 COCO 上 $, $n<40$, 勉强可以接受.
2. 使用一种 soft-nms 变种来解决, 如果某一个大边界框中包含的所有框的得分之和超过其自身得分的3倍, 则将这个大边界框(可能是 ghost box)得分除以2. 这样只会惩罚潜在的 ghost box, 而不是一味的惩罚互相包含的 box
3. 边缘聚合, 将极值点两边的响应值聚合, 直到遇到局部最小点为止. 以此增强边缘极值点的响应
角点附近往往不包含物体特征信息, 而极值点附近往往包含较显著的物体特征, 因此降低了检测难度, 效果更好
CenterNet(Triplets) 两个角点+一个中心点为正样本, 其余为负样本 用角点确定的 bbox 计算中心点位置, 将热图上的中心点映射回原图, 如果位于预定义的中心区域内, 则保留由角点确定的 bbox, 置信度为角点和中心点的平均值. 1. 几何中心不一定是特征最显著的区域(如人头比人身特征更显著)
2. 角点看不到物体内部
1. center pooling: 在水平和垂直方向上找到最大值并将其相加.
2. cascade corner pooling: 先沿着边界查找边界最大值, 然后沿着边界最大值向物体内部 “看” 以找到内部最大值, 最后, 将两个最大值相加. 即可以同时获得边界信息和物体的视觉信息
CenterNet(Points) 仅仅将物体的中心点作为正样本, 其余均为负样本 由正样本点预测物体的尺寸 w 和 h. 只在步长为 4 的图谱上进行预测, 多尺度的适应能力不强, 面对小物体效果一般 个人见解: 使用 FPN. 但是这样就需要使用 NMS 来消除重叠框 无需设置正负样本的 IoU 阈值, 无需 NMS 去重 同一个类别 中的某些物体的GT中心点,在下采样时会挤到一块, 这时, CenterNet 也只能检测出一个中心点, 造成漏检.

CNN 的平移不变性

首先, 有两个概念需要区分:

  • 平移不变性(translation invariant): 对于同样的 input, 其 output 保持不变.
  • 平移等变性(translation equivalence): 如果 innput 平移, 那么其 output 也会发生相应的平移, 但是 output 的取值应该保持不变.
    对于分类(classification)任务来说, 我们希望网络具有平移不变性, 因为对于一个物体的平移来说, 不应该改变这个物体的类别. 但是对于检测(detection), 分割(segmentation)任务来说, 我们希望网络具有平移等价性, 即当物体的位置发生变化时, 我们输出的框的位置也应该发生相应的变化, 但是它的取值, 即框的大小, 框的类别应该保持不变.

常见网络层对于平移不变性和平移等价性的影响:

  • 卷积层: 从卷积的运算定义来看, 卷积应该具有一定的平移等价性, 但是这种等价性需要在物体平移量为 stride 的整数倍时才 严格成立(不是整数倍时, 也具有一定程度的平移等价性). 而对于平移不变性来说, 只有当卷积核的值分布比较均匀(值相差不大), 而输入的 feature map 上的值也有很多值分布均匀(值相差不大)的区域时来具有比较弱的平移不变性, 因为这个时候 feature map 上的微小移动对于输出的改变较少.
  • 池化层: 目前大部分的观点都认为, 卷积网络的平移不变性主要是通过池化层实现的, 其中最大池化相对于均值池化来说带有更强的平移不变性, 全局池化层则具有更强的平移不变性. 由于池化操作往往会忽略核内元素的位置, 因此通常认为池化层是不具有平移等价性. 另外, 个人觉得正是因为池化层具有一定程度的平移不变性, 且几乎不具有平移等价性, 因此在处理一些需要平移等价性的任务(比如检测, 分割)的时候, 会经常使用 stride 为 2 的卷积层来代替池化层, 而在分类任务中, 往往会使用池化和全局池化来消除平移的影响. 在 18 年的一篇论文(Why do deep convolutional networks generalize so poorly to small image)中也提到了 stride 为 2 的降采样操作会使得 CNN 网络丢失平移不变性.
  • 全连接层: 从全连接层的计算规则来看, FC 即不具有 平移不变性 也不具有 平移等价性. 因为当参与计算的向量值发生偏移时, 很明显 FC 的输出结果会发生较大的变化, 尤其适当 FC 上的神经元权重相差较大时. (等价性也要求结果保持相对不变). 但是, 我们不能就此断定添加了 FC 层的网络就丧失了平移不变性, 18 年的那篇文章里面给出的实验结果发现 VGG 的平移不变性均好于 ResNet 和 InceptionResNetV2, 文中作者的分析是因为 VGG 具有更大的 max pooling 层而另外两个网络相对较少, 同时后面两个网络更深. 但是还有一点要注意的是, VGG 有全连接层 而其他两个没有, 这说明全连接层并不是破坏 CNN 平移不变性的主导因素.

我个人认为评价一个网络层是不是具有平移不变性或者平移等价性, 一方面要看它的结构特点, 另一方面也要着重看一下这个网络层学习到的参数, 不同的参数值具有的平移不变性和等价性的强度有较大区别, 而参数的值又来自于数据, 所以很多时候, 想办法拿到更好的数据也是非常关键的一步. 有文章(也是18那篇)也分析了目前的大部分数据集中的数据, 都具有一定的偏向性, 这种偏向性也就导致训练出来的参数具有一定的偏向性, 不能说这种偏向性不好, 但是它对于网络的泛化能力也确实有一定的影响.

目标检测领域方向可以继续改进或者优化的地方

  1. 首先是精确度和速度的考量, 相对于精度较高的 Faster RCNN, R-FCN 相关系列模型来说, 个人觉得速度更快的 YOLO 系列和 SSD 系列的模型在实际场景应用中会更加实用, 近年来的主要代表有 RefineDet, RFBNet 等都是基于 SSD 模型的研究.

  2. 其次是目标的选框步骤, 从最开始的 Region Based , Anchor Based 到现在的基于角点, 甚至基于 segmentation, 包括 semantic segmentation 和 instance segmentation. 今年比较有代表性的就是 CornerNet. 就目前来说, 目标的选框方法很多还是基于 RPN 的 anchor 思想, 所以, 未来的研究中, 新的更好的目标选框方法依旧是研究的一个重要方向.

  3. 多尺度问题(尺度变换问题), 目标常见的三种思路, 采用专门设计的尺度变换模块, STDN: Scale-Transferrable Object Detection.

目前来说, 最能够提升目标检测性能的地方是哪里, 比如说不是提1, 2个点, 而是提升 10 个点

小目标检测

各个检测网络在小目标上的相对精度:

检测网络 Backbone Trick $AP$ $AP_{50}$ $AP_{75}$ $AP_S$ $AP_M$ $AP_L$
Faster R-CNN+++ ResNet101 1000x600, SS 34.9 55.7 37.4 15.6 38.7 50.9
Faster R-CNN w FPN ResNet101 1000x600, SS 36.2 59.1 39.0 18.2 39.0 48.2
Faster R-CNN by G-RMI Inception-ResNetv2 1000x600, SS 34.7 55.5 36.7 13.5 38.1 52.0
YOLOv2 DarkNet-19 544 21.6 44.0 19.2 5.0 22.4 35.5
SSD513 ResNet-101 513 31.2 50.4 33.3 10.2 34.5 49.8
DSSD513 ResNet-101 513 33.2 53.3 35.2 3.0 35.4 51.1
YOLOv3 608 DarkNet-53 608 33.0 57.9 34.4 18.3 35.4 41.9
RetinaNet w FPN ResNet-101-FPN 800 39.1 59.1 42.3 21.8 42.7 50.2
FSAF ResNet-101 800, SS 40.9 61.5 44.0 24.0 44.2 51.3
FSAF ResNet-101 800, MS 42.8 63.1 46.5 27.8 45.5 53.2
FSAF ResNeXt-101 800, SS 42.9 63.8 46.3 26.6 46.2 52.7
FSAF ResNeXt-101 800, MS 44.6 65.2 48.6 29.7 47.1 54.6
FoveaBox ResNet-101 800, SS 40.6 60.1 43.5 23.3 45.2 54.5
FoveaBox ResNeXt-101 800, SS 42.1 61.9 45.2 24.9 46.8 55.6
FCOS ResNet-101-FPN 800, SS 41.0 60.7 44.1 24.0 44.1 51.0
FCOS ResNeXt-101-32x8d-101-FPN 800, SS 42.1 62.1 45.2 25.6 44.9 52.0
ExtremeNet Hourglass-104 800, SS 40.2 55.5 43.2 20.4 43.2 53.1
ExtremeNet Hourglass-104 800, MS 43.7 60.5 47.0 24.1 46.9 57.6
CenterNet(Triplets) Hourglass52 511, SS 41.6 59.4 44.2 22.5 43.1 54.1
CenterNet(Triplets) Hourglass104 511, SS 44.9 62.4 48.1 25.6 47.4 57.4
CenterNet(Triplets) Hourglass52 511, MS 43.5 61.3 46.7 25.3 45.3 55.0
CenterNet(Triplets) Hourglass104 511, MS 47 64.5 50.7 28.9 49.9 58.9
CenterNet(Points) Hourglass104 511, SS 42.1 61.1 45.9 24.1 45.5 52.8
CenterNet(Points) Hourglass104 511, MS 45.1 63.9 49.3 26.6 47.1 57.7

可以看出, 到目前为止, 所有的检测网络在面对小目标物体时, 检测效果都不是特别突出, 因此, 如果能够解决小目标物体的检测问题, 那么就可以大幅提升检测模型的性能.

针对小目标物体常用的方法

  1. 增大 imgsize, data-augmentation(将小物体复制多次, 插入到原图中)
  2. fpn, 多层级, 多尺度(注意二者区别)的特征融合. 将深层的特征图谱进行上采样, 补充损失的信息, 对速度影响较大.
  3. 利用浅层的特征图谱进行预测(降低 backbone 网络的步长)
  4. 利用 context 信息, 建立小物体和 context 的关系; 利用 dilated conv 扩大感受野. 对于这一点, 存在一些争议, 根据 DCVv2 的实验看出, 冗余的 context 实际上会对检测效果造成不小的干扰
  5. 将小目标进行放大, 可以利用一些超分辨率的方法提升小目标的分辨率, 或者一些自动修补的方法修复小目标损失的特征信息
  6. 在 bbox 的准确度上进行优化: iou loss, cascade rcnn
  7. snip / sniper
  8. 在 anchor 层面进行优化, anchor free, 尤其是 FASF, 通过在线合适的 feature map, 可以有效提高小目标的检测效果.
  9. 利用物体间的关系, 增加检测效果, relation network 思路
  10. 利用 dilation conv, 设计更加聪明的感受野匹配机制, 对于不同步长的 dilation conv 来说, 他们的感受野是不同的, 而不同的感受野对于不同的物体尺寸, 其检测效果也是不同的, 简单来说, 更大的感受野 对于 大物体 的检测性能会更好, 更小的感受野 对于 小物体 的检测性能会更好. 对于 dilaiton 的用法, 可以从两个角度出发:
    1. 一是串行使用, 就是将多个不同 dilation rate 下的采样点汇集到同一个输出图谱上面, 使得输出图谱上同时包含多种不同感受野下的采样特征.
    2. 二是并行使用, 就是将具有不同 dilation rate 的卷积分成不同的分支, 每个分支负责相应尺度范围内的物体训练和检测. 我们可以通过参数共享和单分支近似等方法来降低计算量成本.

深度学习在小目标检测方面有什么进展

小目标检测

小目标检测

样本不均衡问题

  • Data augmentation
  • OHEM
  • Focal Loss
  • GHM

遮挡问题

遮挡问题

  • 非目标遮挡(occlusion)
    • 目前较难解决, 通常的做法是堆数据(数据增广), 以及利用更强的 feature
  • 目标遮挡(crowded)
    • CoupleNet: 利用 PSRoI Pooling 和 RoI Pooling, 分别提取 local feature 和 global feature, 来综合决定最终的预测结果, 可以在一定程度上解决遮挡问题.

梯度消失和梯度爆炸的产生原因及解决方法

梯度消失: 中间梯度小于 1, 连乘后趋近于 0
产生原因:

  1. sigmoid, tanh, 激活函数梯度小于 1
  2. 中间层数据分布使得梯度为0(relu 负半区)
  3. 网络层数过深

解决方法:

  1. ReLU
  2. BN
  3. ShortCut

梯度爆炸:

  1. 参数矩阵初始化值过大, 导致梯度值过大

解决办法: 预训练初始化, xavier 初始化, msra 初始化

在图像分类任务中, 训练数据不足会带来什么问题, 如何缓解数据量不足带来的问题?

(百面: 1.07.1)
带来的问题: 过拟合
处理方法

  • 基于模型的方法: 采用降低过拟合风险的措施,包括简化模型(如将非线性简化成线性), 添加约束项以缩小假设空间(如L1和L2正则化), 集成学习, Dropout超参数等.
  • 基于数据的方法, 主要通过数据扩充(Data Augmentation), 即根据一些先验知识, 在保持特定信息的前提下, 对原始数据进行适合变换以达到扩充数据集的效果.

在图像分类任务中,在保持图像类别不变的前提下,可以对训练集中的每幅图像进行以下变换:

  • 观察角度:一定程度内的随机旋转、平移、缩放、裁剪、填充、左右翻转等
  • 噪声扰动:椒盐噪声、高斯白噪声
  • 颜色变换:在RGB颜色空间上进行主成分分析
  • 其他:亮度、清晰度、对比度、锐度

其他扩充数据方法:特征提取, 在图像的特征空间内进行变换:数据扩充or上采样技术,如SMOTE(Synthetic Minority Over-sampling Technique)。

最后,迁移学习或者用GAN合成一些新样本也可帮助解决数据不足问题。

如何解决数据不均衡问题?

  • 在损失函数中使用权重, 对表征性不足的类别使用更高的而权重
  • 过采样: 对样本量少的类进行多次采样
  • 欠采样: 对样本量多的类降低采样频率
  • 数据增广

训练不收敛的具体表现是什么? 可能的原因是什么? 如何解决?

我们主要通过观察 loss 曲线来判断是否收敛, 根据不同的 loss 曲线, 有以下三种不收敛的情形:

  1. 从训练开始曲线就一直震荡或者发散
    • 可能原因: (1) 学习率设置的过大; (2) 向网络中输入的数据是错误数据, 如标签对应错误, 读取图片时将宽高弄反, 图片本身质量极差等;
    • 解决方法: 调节学习率; 检查数据读取代码
  2. 在训练过程中曲线突然发散
    • 可能原因: (1) 学习率设置过大, 或者没有采用衰减策略; (2) 读取到了个别的脏数据, 如标签对应错误, 或者标签为空等
    • 解决方法: 调整学习率及相应的衰减策略; 将 batch size 设为 1, shuffle 置为 false, 检查发散时对应的数据是否正确;
  3. 在训练过程中曲线突然震荡
    • 可能原因: (1) 损失函数中的正则化系数设置有问题, 或者损失函数本身存在 Bug; (2) 数据存在问题
    • 解决方法: 检查损失函数; 检查数据

训练过程中出现 Nan 值是什么原因? 如何解决?

Nan 是 “Not a number” 的缩写, 出现 Nan 的可能情况一般来说有两种:

  • 一种是梯度爆炸, 使得某一层计算出来的值超过了浮点数的表示范围
  • 另一种是由于损失函数中 $log$ 项的值出现的负值或者 0 导致的, 因为 $logx$ 只在 $x$ 大于 0 的时候才有意义.
  • 学习率过大, 使得更新的时候值过大, 超出表示范围
  • batch_size 过大
  • 0 用作了除数
  • 0 或者负数作为自然对数
  • 某些指数计算, 最后计算结果为 INF(无穷)
  • 输入本身就含有 Nan, 有的图片本身就含有 Nan 值

解决梯度爆炸的方法通常有:

  • 对数据进行归一化(BN, GN 等), 使数据的值不要太大
  • 减小学习率
  • 减小 batch_size, 或者提升数据表示范围, float->double
  • 加入 gradient clipping
  • 更换参数初始化方法(待商榷).

对于 $log$ 项出现负值或者 0 的情况(Softmax 激活函数的取值范围是 [0,1], 因此有可能输出 0), 首先确保网络使用了正确的初始化方法(若参数全为 0, 则输出也为 0), 其次, 检查数据本身是否存在问题, 因为实际业务上的真实数据通常还有大量的脏数据, 有时候数据本身就还有 Nan 值, 因此, 在训练网络之前 先要确保数据是正确的. 可以设计一个简单的小网络, 然后将所有数据跑一遍, 再根据日志信息去除其中的脏数据.

loss 的计算问题, 当我们采用先求取loss的和, 再做归一化除法时, 如果batch_size过大, 那么loss之和可能会超过数据类型的表示上限, 此时, 有两种解决方法, 一种是将数据类型的表示范围提高, 例如将float变成double, 但是这样也不保险, 较好的做法是在计算loss时, 避免添加操作, 或者预先估计loss的大小

1
2
3
4
# 提升 loss 的表示范围, 修改自 ssd.pytorch 代码
N = num_pos.data.sum().double()
loss_l = loss_l.double()
loss_c = loss_c.double()

如果以上方法都不能解决问题的话, 那应该就是网络结构本身或者损失函数可能存在 Bug, 需要进一步更细致的分析. 如将网络拆解开来, 减少层数, 对不同层进行测试等等.

过拟合是什么? 如何处理过拟合?

过拟合定义: 当模型在训练数据上拟合的非常好, 但是在训练数据以外的测试集上却不能很好的拟合数据, 此时就说明这个模型出现的过拟合现象.

解决方法:

  1. 使用正则项(Regularization): L1, L2 正则
  2. 数据增广(Data Augmentation): 水平或垂直翻转图像、裁剪、色彩变换、扩展和旋转等等, 也可利用GAN辅助生成(不常用)
  3. Dropout: Dropout是指在深度网络的训练中, 以一定的概率随机的”临时丢弃”一部分神经元节点. 具体来讲, Dropout作用于每份小批量训练数据, 由于其随机丢弃部分神经元的机制, 相当于每次迭代都在训练不同结构的神经网络, 可以被认为是一种实用的大规模深度神经网络的模型继承算法. 对于包含 $N$ 个神经元节点的网络, 在Dropout的作用下可以看作为 $2^N$ 个模型的集成, 这 $2^N$ 个模型可认为是原始网络的子网络, 它们共享部分权值, 并且拥有相同的网络层数, 而模型整个的参数数目不变, 大大简化了运算. 对于任意神经元来说, 每次训练中都与一组随机挑选的不同的神经元集合共同进行优化, 这个过程会减弱全体神经元之间的联合适应性, 减少过拟合的风险, 增强泛化能力. 工作原理和实现: 应用Dropout包括训练和预测两个阶段, 在训练阶段中, 每个神经元节点需要增加一个概率系数, 在前向传播时, 会以这个概率选择是否丢弃当前的神经元. 在测试阶段的前向传播计算时, 每个神经元的参数都会预先乘以概率系数p, 以恢复在训练中该神经元只有p的概率被用于整个神经网络的前向传播计算
  4. Drop Connect: Drop Connect 是另一种减少算法过拟合的正则化策略,是 Dropout 的一般化。在 Drop Connect 的过程中需要将网络架构权重的一个随机选择子集设置为零,取代了在 Dropout 中对每个层随机选择激活函数的子集设置为零的做法。由于每个单元接收来自过去层单元的随机子集的输入,Drop Connect 和 Dropout 都可以获得有限的泛化性能 [22]。Drop Connect 和 Dropout 相似的地方在于它涉及在模型中引入稀疏性,不同之处在于它引入的是权重的稀疏性而不是层的输出向量的稀疏性。
  5. 早停: 早停法可以限制模型最小化代价函数所需的训练迭代次数。早停法通常用于防止训练中过度表达的模型泛化性能差。如果迭代次数太少,算法容易欠拟合(方差较小,偏差较大),而迭代次数太多,算法容易过拟合(方差较大,偏差较小)。早停法通过确定迭代次数解决这个问题,不需要对特定值进行手动设置。
  6. BN
  7. 降低模型复杂度, 例如减少网络层数, 神经元个数等.
  8. 模型集成

Reference:
https://www.cnblogs.com/callyblog/p/8094745.html

欠拟合是什么? 如何处理欠拟合?

过拟合定义: 当模型在训练数据和测试数据上都无法很好的拟合数据时, 说明出现了欠拟合

解决方法:

  1. 首先看看是否是神经网络本身的拟合能力不足导致的, 具体方法是让神经网络在每次训练时, 只迭代 同样的数据, 甚至每一个 batch 里面也是完全相同一模一样的数据, 再来看看 loss 值和 accurancy 值的变化. 如果这时候 loss 开始下降, accurancy 也开始上升了, 并且在训练了一段时间后神经网络能够正确地计算出所训练样本的输出值, 那么这种情况属于神经网络拟合能力不足. 因为对于大量的数据样本, 神经网络由于自身能力的原因无法去拟合全部数据, 只能拟合大量样本的整体特征, 或者少数样本的具体特征. 对于拟合能力不足问题, 通常可以增加网络层数, 增加神经元个数, 增大卷积核通道数等方法.
  2. 如果不是拟合能力不足导致的欠拟合, 就需要尝试其他方法, 更改网络初始化方法(Xavier, MSRA), 更改优化器, 降低学习率
  3. 添加新特征, 确认现有特征和样本标签之前的相关性是否过弱
  4. 减少正则化系数. 正则项是用来防止过拟合的, 但当模型出现欠拟合现象时, 则需要有针对性的减少正则化系数.

Dropout 的实现方式在训练阶段和测试阶段有什么不同? 如何保持训练和测试阶段的一致性?

Dropout 的实现方式有两种:

  • 直接 Dropout: 使用较少, AlexNet 使用的是这种Dropout. 该方法在训练阶段会按照保留概率来决定是否将神经元的激活值置为0. 同时, 为了保持训练阶段和测试阶段数值的一致性, 会在测试阶段对所有的计算结果乘以保留概率.
  • Inverted Dropout: 这是目前常用的方法. 该方法在训练阶段会按照保留概率来决定是否将神经元的激活值置为0, 并且, 在训练阶段会令输出值都会除以 $\alpha$(dropout 概率), 这样一来, 在训练阶段可以随时更改 dropout 的参数值, 而对于测试阶段来说, 无需对神经元进行任何额外处理, 所有的神经元都相当于适配了训练过程中 dropout 对参数数值大小带来的影响. 因为原始的需要在测试时乘以 dropout 概率, 我们直接将其除以, 这样测试时就不需要改变了.

Dropout 为什么可以起到防止过拟合的作用?

  • 减少神经元之间复杂的共适应关系: 因为dropout程序导致两个神经元不一定每次都在一个dropout网络中出现。(这样权值的更新不再依赖于有固定关系的隐含节点的共同作用,阻止了某些特征仅仅在其它特定特征下才有效果的情况)。 迫使网络去学习更加鲁棒的特征 (这些特征在其它的神经元的随机子集中也存在)。换句话说假如我们的神经网络是在做出某种预测,它不应该对一些特定的线索片段太过敏感,即使丢失特定的线索,它也应该可以从众多其它线索中学习一些共同的模式(鲁棒性)。(这个角度看 dropout就有点像L1,L2正则,减少权重使得网络对丢失特定神经元连接的鲁棒性提高)
  • 起到模型集成的作用: 先回到正常的模型(没有dropout),我们用相同的训练数据去训练5个不同的神经网络,一般会得到5个不同的结果,此时我们可以采用 “5个结果取均值”或者“多数取胜的投票策略”去决定最终结果。(例如 3个网络判断结果为数字9,那么很有可能真正的结果就是数字9,其它两个网络给出了错误结果)。这种“综合起来取平均”的策略通常可以有效防止过拟合问题。因为不同的网络可能产生不同的过拟合,取平均则有可能让一些“相反的”拟合互相抵消。dropout掉不同的隐藏神经元就类似在训练不同的网络(随机删掉一半隐藏神经元导致网络结构已经不同),整个dropout过程就相当于 对很多个不同的神经网络进行集成。而不同的网络产生不同的过拟合,一些互为“反向”的拟合相互抵消就可以达到整体上减少过拟合。

常用的数据增强方法

  • 水平和垂直旋转或翻转图像
  • 改变图像的亮度和颜色
  • 随机模糊图像
  • 随机从图像裁剪补丁

常用的训练 Trick 有哪些, 分别介绍

warm up

label smoothing: “软化” 传统的 one-hot 类型标签, 使得在计算损失值时能够有效抑制过拟合现象. $\epsilon$ 是一个小值, 超参数, $K$ 代表类别数

训练阶段和测试阶段动作不同的操作有哪些

dropout
BN

其他

随机数生成算法:

  1. 线性同余: $X(n+1) = (a\ast X(n) + c)% m$, 各系数为如下, 一般而言, m 取 $2^32$, 或者 $2^64$, 因为这样取模操作截断最右的 32 或 64 就行了.
  • 模: m > 0
  • 系数: 0 < a < m
  • 增量: 0 <= c < m
  • 种子: 0 <= X(0) < m
  1. 平方取中: 将一个2s位的随机数(十进制)平方后得到一个4s位的随机数, 去头截尾的取值中间的2s位数作为一个新的随机数.
  2. XorShift

有一个生成 0-4 的均匀分布的整数随机数生成函数,利用这个函数生成0-9均匀分布的整数随机数生成函数.
随机数 / 4 * 9

如何生成正态分布的随机函数:
在获得均匀分布随机数的基础上, 进行 Box_Muller 变换, 一次计算可获得两个正态分布的随机数.

54张牌,分三堆,问大小王在一堆的概率
17/53
分成3份 总的分法 M=(C54^18)(C36^18)(C18^18) 大小王在同一份 N=(C3^1)(C52^16)(C36^18)(C18^18) P=N/M=17/53