计算机视觉 CV 八股分享 [自用](更新中......)

发布于:2024-04-26 ⋅ 阅读:(54) ⋅ 点赞:(0)

目录

一、深度学习中解决过拟合方法

二、深度学习中解决欠拟合方法

三、梯度消失和梯度爆炸

解决梯度消失的方法

解决梯度爆炸的方法

四、神经网络权重初始化方法

五、梯度下降法

六、BatchNorm

七、归一化方法

八、卷积

九、池化

十、激活函数

十一、预训练

十二、Transformer

十三、损失函数

十四、度量指标分析

 十五、RCNN系列的发展

 十六、yolo系列和ssd对比

十七、几个数据增强方式 

十八、coding

//IoU

//NMS

//K-means

//快排

//归并排序

十九、Canny边缘检查
​​​


一、深度学习中解决过拟合方法

  1. 数据增强
  2. L1和L2正则化

L1正则化直接在原来的损失函数基础上加上权重参数的绝对值: l o s s = J ( w , b ) + λ 2 m ∑ ∣ w ∣ loss=J(w,b)+\frac{\lambda}{2m}\sum|w| loss=J(w,b)+2mλ​∑∣w∣
L2正则化直接在原来的损失函数基础上加上权重参数的平方和: l o s s = J ( w , b ) + λ 2 m ∑ ∥ w ∥ F 2 loss=J(w,b)+\frac{\lambda}{2m}\sum\lVert w\lVert _F^2 loss=J(w,b)+2mλ​∑∥w∥F2​
L1和L2正则化能够缓解过拟合的原因:
神经网络就是一个函数,对其进行傅里叶变换求得频谱,频谱中低频分量就是变化平滑的部分,高频分量就是变化敏感的部分。模型对于微小扰动的反馈差异大实际就是一个过拟合的表现,也就是高频分量不能多。根据雅各比矩阵(一阶导数矩阵),神经网络这个函数的高频分量存在上界,上界和谱范数正相关。谱范数逆变换回时域,可求得和参数范数正相关。正则就是将参数的范数加入loss里求最优化,故而限制了神经网络学到高频分量,更倾向于一个低频的平滑的函数,从而缓解过拟合。
推导过程:https://blog.csdn.net/StreamRock/article/details/83539937

     3. Dropout正则化

        步骤:

  • 遍历神经网络每一层节点,设置节点保留概率keep_prob(每一层的keep_prob可以不同,参数多的层keep_prob可以小一些,少的可以多一些)。
  • 删除神经网络节点和从该节点进出的连线。
  • 输入样本使用简化后的神经网络进行训练。
  • 每次输入样本都要重复以上三步

       Dropout起到正则化效果的原因:

  • Dropout可以使部分节点失活,起到简化神经网络结构的作用,从而起到正则化的作用。
  • Dropout使神经网络节点随机失活,所以神经网络节点不依赖于任何输⼊,每个输入的权重都不会很⼤。Dropout最终产⽣收缩权重的平方范数的效果,压缩权重效果类似L2正则化。

    4. early stopping

训练时间和泛化误差的权衡。提早停⽌训练神经网络得到⼀个中等大小的W的F范数,与L2正则化类似。

在这里插入图片描述


在训练中计算模型在验证集上的表现,当模型在验证集上的误差开始增大时,停止训练。这样就可以避免继续训练导致的过拟合问题。

    5. BatchNorm

        归一化

二、深度学习中解决欠拟合方法

增加神经网络层数或神经元个数

三、梯度消失和梯度爆炸

梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)
梯度饱和:越来越趋近一条直线(平行X轴的直线),梯度的变化很小

传统的神经网络通常是基于BP算法优化权重参数的,即通过损失函数计算的误差通过梯度反向传播的方式对神经网络中的权重进行更新,其中用到的是连乘形式的链式法则。

梯度消失:产生的原因基本上是网络太深或是采用了不恰当的损失函数,如sigmoid函数、tanh函数等。由于sigmoid函数的导数最大值是0.25,因而通过链式法则,导致靠近输入层的权重更新缓慢甚至停滞。

梯度爆炸:产生的原因一般是深层网络或是权重初始化过大的情况,伴随着传统神经网络的链式法则,梯度会越来越大,训练过程中,很容易使得权重的值也越来越大导致溢出等情况。

解决梯度消失的方法

  1. Relu及其变体
  2. LSTM/GRU
  3. 残差结构
  4. BatchNorm
  5. Xavier初始化(修正w的方差,避免w过小)

解决梯度爆炸的方法

  1. 梯度裁剪
  2. 正则化(将w加入Loss里,如果Loss小则w也要小,而梯度爆炸是w过大[绝对值]造成的)
  3. Xavier初始化(修正w的方差,避免w过大)
  4. BatchNorm

四、神经网络权重初始化方法

  • 随机初始化。这是最简单的初始化方法,权重可以从一个较小的范围内的随机值初始化,通常基于均匀分布或正态分布。

  • Xavier初始化(又名Glorot初始化)。这种方法旨在保持输入和输出的方差一致,以避免梯度消失或爆炸问题。Xavier初始化根据连接权重的个数和输入/输出单元的数量来确定初始值的范围,并支持正态分布和均匀分布。

  • He初始化(又名Kaiming初始化)。这种方法适用于使用ReLU(Rectified Linear Unit)激活函数的神经网络。与Xavier初始化类似,但在计算初始值的范围时,将输入单元的数量乘以一个较大的因子,以适应ReLU激活函数的特性。13

  • 全零或等值初始化。如果初始化的值全都相同,每个神经元学到的东西也相同,这可能导致“对称性”问题。

  • 正态初始化。均值为零,标准差设置一个小值,使得权重有正有负。

  • 均匀初始化。均匀分布的区间通常为【-1/sqrt(fan_in), 1/sqrt(fan_in)】,其中fan_in表示输入神经元的数量。

五、梯度下降法

  • 梯度下降
  • SGD(随机梯度下降法)

SGD的核心思想是在每次迭代中随机选择一个样本(或一小批样本)来估计梯度,而不是使用整个数据集。这样做的优点是计算效率高,尤其是当数据集很大时。SGD也能够逃离局部最小值,因为随机性引入了一定的噪声,有助于模型探索更多的参数空间。

优点:

  1. 计算效率高:SGD每次更新只使用一个样本或一小批样本来计算梯度,这使得它在每次迭代时的计算成本较低。
  2. 内存使用少:由于每次只处理一小部分数据,SGD不需要将整个数据集加载到内存中,这使得它适合处理大规模数据集。
  3. 能够逃离局部最小值:由于其随机性质,SGD在搜索参数空间时可能会逃离局部最小值,有时能够找到更好的全局最小值。
  4. 实现简单:SGD算法相对简单,易于实现和理解。
  5. 适应性强:SGD可以很容易地适应不同的学习率和正则化策略,如引入动量(Momentum)或RMSprop等变种。

缺点:

  1. 收敛速度慢:SGD的收敛速度通常比其他优化算法慢,因为它在每一步都使用随机梯度,这可能导致优化路径震荡。
  2. 需要仔细调整学习率:SGD的性能在很大程度上依赖于学习率的选择。如果学习率设置得太高,可能会导致模型无法收敛;如果设置得太低,收敛过程会非常缓慢。
  3. 可能不收敛:在某些情况下,SGD可能根本不会收敛,特别是当目标函数不是严格凸形时。
  4. 对非凸问题敏感:SGD在处理非凸优化问题时可能会遇到问题,因为它可能会在多个局部最小值之间震荡,而不是收敛到全局最小值。
  5. 参数更新策略单一:标准的SGD没有考虑梯度的二阶信息,这可能在某些情况下限制了其性能。

  • Momentum(动量梯度下降)

在时间步0,动量法创建速度变量v0,并将其元素初始化成0。在时间步t > 0,动量法对每次迭代的步骤做如下修改:

其中,动量超参数γ 满足0 ≤ γ < 1。当γ = 0时,动量法等价于小批量随机梯度下降。

  • Adagrad

  AdaGrad:全称Adaptive Gradient,自适应梯度,是梯度下降优化算法的扩展。AdaGrad是一种具有自适应学习率的梯度下降优化方法。它使参数的学习率自适应,对不频繁的参数执行较大的更新,对频繁的参数执行较小的更新(It adapts the learning rate to the parameters, performing larger updates for infrequent and smaller updates for frequent parameters)。因此,它非常适合处理稀疏数据。AdaGrad可大大提高SGD的鲁棒性.

        AdaGrad算法的核心思想是对每个参数根据其历史梯度的平方和进行自适应地调整学习率。这意味着对于出现频率高的特征,其学习率会较低;而对于出现频率低的特征,其学习率会较高。这种方式使得模型在稀疏数据上的表现更好。

  • RMSprop

   AdaGrad的一个限制是,它可能会在搜索结束时导致每个参数的步长(学习率)非常小,这可能会大大减慢搜索进度,并且可能意味着无法找到最优值。RMSProp和Adadelta都是在同一时间独立开发的,可认为是AdaGrad的扩展,都是为了解决AdaGrad急剧下降的学习率问题。

      RMSProp采用了指数加权移动平均(exponentially weighted moving average)。

      RMSProp比AdaGrad只多了一个超参数,其作用类似于动量(momentum),其值通常置为0.9。

      RMSProp旨在加速优化过程,例如减少达到最优值所需的迭代次数,或提高优化算法的能力,例如获得更好的最终结果。

  • Adam

深度学习中的神经网络优化。Adam(Adaptive Moment Estimation)优化器是一种自适应优化算法,可以根据历史梯度信息来调整学习率。它结合了RMSProp和Momentum两种优化算法的思想,并且对参数的更新进行了归一化处理,使得每个参数的更新都有一个相似的量级,从而提高训练效果。Adam优化器在很多实际问题中表现良好,尤其是在大规模数据集上训练深度神经网络时效果更佳。

功能
Adam 优化器的主要功能是根据梯度信息来更新神经网络参数,从而最小化损失函数。具体来说,它的主要功能包括:
1.自适应调整学习率:Adam 优化器可以根据历史梯度信息来自适应地调节学习率,使得在训练初期使用较大的学习率,能够快速收敛,在训练后期使用较小的学习率,能够更加准确地找到损失函数的最小值。
2.调整动量:Adam 优化器能够调整动量参数,以平衡上一次梯度和当前梯度对参数更新的影响,从而避免过早陷入局部极小值。
3.归一化处理:Adam 优化器对参数的更新进行了归一化处理,使得每个参数的更新都有一个相似的量级,从而提高训练效果。
4.防止过拟合:Adam 优化器结合了L2正则化的思想,在更新时对参数进行正则化,从而防止神经网络过度拟合训练数据。
总体来说,Adam 优化器能够快速、准确地最小化损失函数,提高深度神经网络的训练效果和泛化能力。

mk和 vk 分别是梯度的一阶矩和二阶矩的估计,β1和 β2 是控制这两个矩估计的指数衰减率,通常设置为 0.9 和 0.999。ϵ是一个非常小的数(例如1e-8),防止除以零。 k是当前迭代次数,用于做偏差校正。

在 Adam 优化算法中,β1和 β2用于进行偏差校正(bias correction)。这是因为在算法的初期,由于 mk和 vk(分别是梯度的一阶矩和二阶矩的估计)是从 0 开始初始化的,会导致它们在初始阶段被低估。为了补偿这种估计的偏差,Adam 算法引入了偏差校正步骤。

梯度更新:

优点
自适应学习率: Adam 通过计算一阶和二阶矩估计来为每个参数自适应地调整学习率。
偏差校正: 初始阶段梯度估计可能偏低,通过偏差校正可以加速初期的学习速率。
适应性强: Adam 在很多不同的模型和数据集上都表现出了良好的性能。
缺点
超参数调整: 尽管默认的超参数在很多情况下都表现良好,但某些问题可能需要仔细的超参数调整。
内存需求: 相对于一些简单的优化算法,Adam 需要存储更多的变量(例如一阶和二阶矩估计),这可能会增加计算资源的消耗。

六、BatchNorm

Batch Norm 层执行的计算

  • 激活

前一层的激活作为输入传递给 Batch Norm。 数据中的每个特征都有一个激活向量。

  • 计算均值和方差

分别对于每个激活向量,计算小批量中所有值的均值和方差。

  • 归一化

使用相应的均值和方差计算每个激活特征向量的归一化值。 这些标准化值现在均值和单位方差为零。

  • 缩放和移位

这一步是 Batch Norm 引入的巨大创新,赋予了它强大的力量。 与输入层要求所有归一化值具有零均值和单位方差不同,Batch Norm 允许其值移动(到不同的均值)和缩放(到不同的方差)。 它通过将归一化值乘以系数 gamma 并添加系数 beta 来实现此目的。 请注意,这是逐元素乘法,而不是矩阵乘法。

缩放和移位这项创新的巧妙之处在于,这些因素不是超参数(即模型设计者提供的常量),而是由网络学习的可训练参数。 换句话说,每个 Batch Norm 层都能够最佳地找到适合自身的最佳因子,从而可以移动和缩放归一化值以获得最佳预测。

  • 滑动平均

此外,Batch Norm 还保留均值和方差的指数滑动平均 (EMA:Exponential Moving Average) 的运行计数。 在训练期间,它只是计算 EMA,但不会对其执行任何操作。 在训练结束时,它只是将该值保存为层状态的一部分,以供在推理阶段使用。

  • 推理过程中的批归一化

正如我们上面所讨论的,在训练期间,Batch Norm 首先计算小批量的均值和方差。 然而,在推理过程中,我们只有一个样本,而不是一个批。 在这种情况下我们如何获得均值和方差?

这就是两个滑动平均参数的用武之地—我们在训练期间计算并与模型一起保存的参数。 我们在推理过程中使用这些保存的平均值和方差值进行批归一化:

BN的作用

  1. 加速SGD收敛(将batch里的数据分布变为一样),效果与特征归一化一致。
  2. 使分布更稳定。
  3. 防止过拟合。有轻微正则化效果,BN的均值和方差是在mini batch上计算得到的,含有轻微噪音。将噪音添加到隐藏单元上,这迫使后部单元不依赖于任何⼀个隐藏单元(将Batch中所有样本都被关联在了一起,网络不会从某一个训练样本中生成确定的结果),类似dropout。
  4. 解决梯度消失和梯度爆炸问题(使用BN后,网络的输出就不会很大,梯度就不会很小)。
  5. 提高激活函数准确度,增强优化器性能。

BN的缺点

高度依赖于batchsize的大小,实际使用中会对batchsize大小进行约束,不适合batchsize=1的情况。不适用于RNN,RNN的序列长度是不一致的

七、归一化方法

在这里插入图片描述

LayerNormalization

对单个样本所有维度特征做归一化。LN通过hidden size这个维度归一化来让分布稳定下来,计算每个样本所有通道的均值方差。LN通常运用在RNN中。

Instance Normalization

IN是针对于不同的batch, 不同的chennel进行归一化(计算单个C和N里的WH的均值和方差)。还是把图像的尺寸表示为[N, C, H, W]的话,IN则是针对于[H,W]进行归一化。这种方式通常会用在风格迁移的训练中。

Group Nomalization

把所有的channel都放到同一个group中的时候就变成了layer normal(计算单个N里位于group里所有C的WH的均值和方差)。

对比

与BN不同,LN/IN和GN都没有对batch作平均,所以当batch变化时,网络的错误率不会有明显变化。但论文的实验显示:LN和IN 在时间序列模型(RNN/LSTM)和生成模型(GAN)上有很好的效果,而GN在GAN上表现更好。

八、卷积

深度学习中将互相关称之为卷积.

在PyTorch中,nn.Conv2d类的参数定义了二维卷积操作的各种属性。以下是对nn.Conv2d参数的详细解释:

  • in_channels(输入通道数)。这是输入数据的高度和宽度维度的大小,对于彩色图像(如RGB),这通常是3。
  • out_channels(输出通道数)。这是在卷积操作后希望得到的特征图的数量。
  • kernel_size(卷积核大小)。这可以是一个整数或一个元组,指定了卷积核的高度和宽度。例如,kernel_size=3表示一个3x3的卷积核,而kernel_size=(3,5)表示高度为3、宽度为5的卷积核。
  • - stride(步长)。这是卷积核在输入数据上移动的步长。它可以是一个整数或一个元组,分别对应于水平和垂直步长。如果未指定,默认为1。
  • - padding(填充)。这决定了在输入数据的边缘补充多少0。这可以是整数或元组,分别对应于上下左右的填充。如果未指定,默认为0。
  • - dilation(扩张率)。这定义了卷积核处理数据时各值之间的间距。对于标准的卷积操作,扩张率为1。
  • - groups(分组数)。这是输入通道和输出通道之间的分组连接数。默认值为1,表示所有通道全连接。如果分组数小于输入输出通道数的乘积,那么卷积操作将进行分组卷积。
  • - bias(偏置)。如果为True,则添加偏置项。默认为True。
  • - padding_mode(填充模式)。这决定了如何计算边界上的填充值。默认为'zeros',意味着使用0进行填充。

卷积的优点

  1. 权值共享:每个过滤器对应的输出都可以在输⼊图片的不同区域中使用相同参数卷积得到。
  2. 局部连接: 每⼀个输出仅依赖于⼀小部分输⼊。

1x1Conv(点卷积)

  1. 1x1的卷积能够灵活的调控特征的深度(升维和降维)。
  2. 减少参数量和计算量(先用少量普通卷积核再升维,或先降维再普通卷积等可以减少参数量以及计算量)。
  3. 实现了跨通道的信息组合,并增加了非线性特征(实现降维和升维的操作其实就是channel间信息的线性组合变化)。

九、池化

作用:
对卷积层输出的特征图进行特征选择和信息的过滤。能够实现对特征图的下采样,从而减少下一层的参数和计算量。并且具有防止过拟合,以及保持特征的不变性(平移、旋转、尺度)的作用。缺点是不存在要学习的参数。

average pooling(不常用)、max pooling求的是每一个维度的窗口内的平均/最大值,global average pooling求的是每一个维度的平均值。
Max Padding的作用:如果在卷积核中提取到某个特征,则其中的最大值很⼤; 如果没有,则其 中的最⼤值很小。
使用Conv替换Pool的优点:Conv的参数是可学习的,可以达到保留更多数据信息和防止过滤掉有用信息的作用。

十、激活函数

作用:加入非线性因素的,提高线性模型的表达能力(没有激活函数的模型无论多少层都是线性模型,而大部分数据都是线性不可分的)。
比较常用的激活函数为:Sigmoid、ReLU(及其变体)、tanh

Sigmoid优点:

1、其值域为[0,1],非常适合作为模型的输出函数用于输出一个(0,1)范围内的概率值,可用于将预测概率作为输出的模型,比如用于表示二分类的类别或者用于表示置信度。

2、Sigmoid 函数的输出范围是 0 到 1。由于输出值限定在0到1,因此它对每个神经元的输出进行了归一化

3、该函数是连续可导的(即可微),可以提供非常平滑的梯度值,防止模型训练过程中出现突变的梯度(即避免「跳跃」的输出值)。

Sigmoid不足:

1、从其导数的函数图像上可以看到,其导数的最大值只有0.25,而且当x在[-5,5]的范围外时其导数值就已经几乎接近于0了。这种情况会导致训练过程中神经元处于一种饱和状态,反向传播时其权重几乎得不到更新,从而使得模型变得难以训练,这种现象被称为梯度消失问题。

2、其输出不是以0为中心而是都大于0的(这会降低权重更新的效率),这样下一层的神经元会得到上一层输出的全正信号作为输入,所以Sigmoid激活函数不适合放在神经网络的前面层而一般是放在最后的输出层中使用

3、需要进行指数运算(计算机运行得较慢),计算量大及计算复杂度高,训练耗时;指数的越大其倒数就越小,容易产生梯度消失。

Tanh优点:

1、在分类任务中,双曲正切函数(Tanh)逐渐取代 Sigmoid 函数作为标准的激活函数,其具有很多神经网络所钟爱的特征。它是完全可微分的,反对称,对称中心在原点。

2、输出是S型曲线,具备打破网络层与网络层之间的线性关系,可以把网络层输出非线形地映射到 (−1,1) 区间里。负输入将被强映射为负,而零输入被映射为接近零;tanh 的输出间隔为1且值域是以0为中心的[-1,1](可以解决Sigmoid激活函数输出不以0为中心的问题。)

3、在一般的二元分类问题中,tanh 函数用于隐藏层,而 sigmoid 函数用于输出层,但这并不是固定的,需要根据特定问题进行调整。

Tanh不足:

1、当输入较大或较小时,输出几乎是平滑的并且梯度较小,这不利于权重更新。

2、Tanh函数也需要进行指数运算,所以其也会存在计算复杂度高且计算量大的问题。

3、当神经网络的层数增多的时候,由于在进行反向传播的时候,链式求导,多项相乘,函数进入饱和区(导数接近于零的地方)就会逐层传递,这种现象被称为梯度消失

ReLU函数:

1、ReLU 函数在正输入时是线性的,收敛速度快,计算速度快,同时符合恒等性的特点。当输入为正时,由于导数是1,能够完整传递梯度,不存在梯度消失的问题(梯度饱和问题)。

2、计算速度快。ReLU 函数中只存在线性关系且无论是函数还是其导数都不包含复杂的数学运算,因此它的计算速度比 sigmoid 和 tanh 更快。

3、当输入大于0时,梯度为1,能够有效避免链式求导法则梯度相乘引起的梯度消失和梯度爆炸;计算成本低

4、它保留了 step 函数的生物学启发(只有输入超出阈值时神经元才激活),不过当输入为正的时候,导数不为零,从而允许基于梯度的学习(尽管在 x=0 的时候,导数是未定义的)。当输入为负值的时候,ReLU 的学习速度可能会变得很慢,甚至使神经元直接无效,因为此时输入小于零而梯度为零,从而其权重无法得到更新,在剩下的训练过程中会一直保持静默。

ReLU不足:

1、ReLU的输入值为负的时候,输出始终为0,其一阶导数也始终为0,这样会导致神经元不能更新参数,也就是神经元不学习了,这种现象叫做“Dead Neuron”。为了解决ReLU函数这个缺点,在ReLU函数的负半区间引入一个泄露(Leaky)值,所以称为Leaky ReLU函数。

2、与Sigmoid一样,其输出不是以0为中心的(ReLU的输出为0或正数)。

3、ReLU在小于0的时候梯度为零,导致了某些神经元永远被抑制,最终造成特征的学习不充分;这是典型的 Dead ReLU 问题,所以需要改进随机初始化,避免将过多的负数特征送入ReLU。

高级的有:Swish(及其变体)、Maxout等
这里主要介绍:Sigmoid、ReLU、tanh和Swish
其它激活函数可到此链接了解

十一、预训练

Frozen:冷冻,冰冻。
Fine-Tuning:微调,更好的对参数进行调整使得更适应当前任务。

预训练的好处

  1. 训练数据较少时, 难以训练复杂网络。
  2. 加快训练任务的收敛速度。
  3. 初始化效果好,有利于优化 。

什么是预训练

在CV里,网络底层参数使用其它任务学习好的参数,高层参数仍然随机初始化。之后,用该任务的训练数据训练网络,底层参数微调。

十二、Transformer

NLP

ViT主要有Encoding和MLP组成,Encoding由Mutil_Head Attention、MLP(feed forward)和残差结构组成。

在这里插入图片描述

上图是论文中 Transformer 的内部结构图,左侧为 Encoder block,右侧为 Decoder block。红色圈中的部分为 Multi-Head Attention,是由多个 Self-Attention组成的,可以看到 Encoder block 包含一个 Multi-Head Attention,而 Decoder block 包含两个 Multi-Head Attention (其中有一个用到 Masked)。Multi-Head Attention 上方还包括一个 Add & Norm 层,Add 表示残差连接 (Residual Connection) 用于防止网络退化,Norm 表示 Layer Normalization,用于对每一层的激活值进行归一化

Decoder block 结构,与 Encoder block 相似,但是存在一些区别:

  • 包含两个 Multi-Head Attention 层。
  • 第一个 Multi-Head Attention 层采用了 Masked 操作。

  • 第二个 Multi-Head Attention 层的K, V矩阵使用 Encoder 的编码信息矩阵C进行计算,而Q使用上一个 Decoder block 的输出计算。这样做的好处是在 Decoder 的时候,每一位单词都可以利用到 Encoder 所有单词的信息 (这些信息无需 Mask)。
  • 最后有一个 Softmax 层计算下一个翻译单词的概率。

因为 Self-Attention是 Transformer 的重点,所以我们重点关注 Multi-Head Attention 以及 Self-Attention,首先详细了解一下 Self-Attention 的内部逻辑。

  •  Self-Attention 结构


上图是Self-Attention的结构,在计算的时候需要用到矩阵Q(查询), K(键值), V(值)。在实际中,Self-Attention接收的是输入(单词的表示向量x组成的矩阵X) 或者上一个 Encoder block 的输出。而Q, K, V正是通过输入进行线性变换得到的。

  • Multi-Head Attention

在上一步,我们已经知道怎么通过 Self-Attention 计算得到输出矩阵 Z,而 Multi-Head Attention 是由多个 Self-Attention 组合形成的,下图是论文中 Multi-Head Attention 的结构图。

得到 8 个输出矩阵Z_1Z_8之后,Multi-Head Attention 将它们拼接在一起 (Concat),然后传入一个Linear层,得到 Multi-Head Attention 最终的输出Z。

  • Add & Norm

Add & Norm 层由 Add 和 Norm 两部分组成,其计算公式如下:

  • Feed Forward

Feed Forward 层比较简单,是一个两层的全连接层,第一层的激活函数为 Relu,第二层不使用激活函数,对应的公式如下。

十三、损失函数

  • L2 Loss

这是计算损失函数最基本的损失函数,PyTorch中也将其命名为torch.nn.MSELoss。这依赖于两个向量[预测和真实标签]之间的Euclidean距离(欧氏距离)。它有几个别称分别为

  1. L2 范数损失
  2. 最小均方值偏差(LSD)
  3. 最小均方值误差(LSE)

  • Softmax 函数

分类的损失函数一般都要求算法的每个标量输出输入概率 p在0至1之间且和为1。但是预测值并非总是如此,我们可以使用Softmax 函数(非线性函数)将预测值变为概率在0至1之间且和为1。因此Softmax 函数也称为归一化指数函数,其公式如下:

  • SmoothL1 Loss 

  •  交叉熵

交叉熵主要刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。假设概率分布p为期望输出,概率分布q为实际输出,H(p,q)为交叉熵,则

CrossEntropyLoss()损失函数结合了nn.LogSoftmax()和nn.NLLLoss()两个函数。它在做分类(具体几类)训练的时候是非常有用的,如上所述,softmax用于多分类过程中,它将多个神经元的输出,映射到(0,1)区间内,可以看成概率来理解,从而来进行多分类。

  1. Cross Entropy Loss Function交叉熵损失函数是使用对数(loge)的更高级的损失函数。与L2 Loss 相比,这有助于加快对神经网络的训练。
  2. 在二分类的情况下交叉熵损失函数被称为BCE Loss,模型最后需要预测的结果只有两种情况,对于每个类别我们的预测得到的概率为 p 和 1-p 。

  • focal loss

Focal Loss首次在目标检测框架RetinaNet中提出,它是对典型的交叉信息熵损失函数的改进,主要用于样本分类的不平衡问题,用改变loss的方式来缓解样本的不平衡,因为改变loss只影响train部分的过程和时间,而对推断时间影响甚小,容易拓展。
focal loss就是把(交叉熵损失)CE里的p替换为pt,当预测正确的时候,pt接近1,在FL(pt)中,其系数(1 − p t )^γ 越小(只要γ > 0 ),简单的样例比重越小,难的样例比重相对变大。

  • detloss、IoUloss等
  • DIoU Loss考虑中心距离
  • GIoU Loss考虑实际距离远近程度
  • CIoU Loss重叠面积、中心点距离、长宽比

十四、度量指标分析

FP:False Positive 阴性被预测为阳性
TP: True Positive 阳性被预测为阳性
FN: False Negative 阳性被预测为阴性
TN: True Negative 阴性被预测为阴性

  • F1-score

衡量模型对每个类别的预测精度和召回率是否平衡,能够直观地显示模型对测试集中每个类别的泛化效果。F1值越大,学习器的性能较好。

F1分数的计算方式如下:

精确率(Precision)是指分类器正确预测为正例的样本数量(True Positives,TP)与所有被分类为正例的样本数量(True Positives + False Positives,TP + FP)的比值。精确率表示在所有分类为正例的样本中,有多少是真正的正例。

precision 精确率 = TP / (TP + FP)

召回率(Recall)是指分类器正确预测为正例的样本数量(True Positives,TP)与所有实际正例的样本数量(True Positives + False Negatives,TP + FN)的比值。召回率表示有多少真正的正例被成功预测出来了。

recall 召回率 = TP / (TP + FN)

F1分数是精确率和召回率的调和平均值,用于综合考虑分类器的性能。它的计算公式如下:

F1分数 = 2 * (精确率 * 召回率) / (精确率 + 召回率)

  • PR曲线

(recall,precison)
一个阈值对应PR曲线上的一个点。通过选择合适的阈值,比如50%,对样本进行划分,概率大于50%的就认为是正例,小于50%的就是负例,从而计算相应的精准率和召回率。(选取不同的阈值,就得到很多点,连起来就是PR曲线

  • ROC-AUC

ROC底下的面积,作为数值可以直观的评价分类器的好坏,值越大越好。

我们令 FDR 为平面坐标的横坐标点、TPR 作为平面坐标的纵坐标点

TPR = TP/(TP + FN)

FDR = FP/(FP + TN)

  • mAP 

AP指单个类别平均精确度,而mAP是所有类别的平均精确度,AP是Precision-Recall Curve曲线下面的面积,以Recall为横轴,Precision为纵轴,就可以画出一条PR曲线,PR曲线下的面积就定义为AP

mAP0.5
mAP@0.5: mean Average Precision(IoU=0.5)
即将IoU设为0.5时,计算每一类的所有图片的AP,然后所有类别求平均,即mAP。

mAP0.5:0.95
mAP@.5:.95(mAP@[.5:.95])
表示在不同IoU阈值(从0.5到0.95,步长0.05)(0.5、0.55、0.6、0.65、0.7、0.75、0.8、0.85、0.9、0.95)上的平均mAP。

 十五、RCNN系列的发展

  •  R-CNN 

步骤一:在imagenet分类比赛上寻找一个cnn模型,使用它用于分类的预训练权重参数;对于这个模型修改最后的分类层,分为21类,去掉最后一个全连接层,因为所用的测试集为20类,且还有一类背景类。

步骤二:根据选择性搜索来对输入的图像进行选取2000个候选区域框;然后修改候选区域框的大小,以适应cnn的输入,然后用cnn来提取出每个候选区域的feature map。

步骤三:训练svm分类器,这个svm分类器是对于特定的某一类进行区分,是专门用来对特定敏感区域进行分类,每一个类别对应于一个SVM分类器

步骤四:使用回归器精细修正每个候选框的位置,对于每一个类,需要训练一个回归模型去判定这个框框的是否完美。

  • Fast rcnn

步骤一:同样是寻找一个在imagenet上训练过的预训练cnn模型

步骤二:与rcnn一样,通过selective search在图片中提取2000个候选区域

步骤三:将一整个图片都输入cnn模型中,提取到图片的整体特征(这是相对于rcnn最大的改进的地方)

步骤四:把候选区域映射到上一步cnn模型提取到的feature map里

步骤五:采用rol pooling层对每个候选区域的特征进行上采样,从而得到固定大小的feature map,以便输入模型中

步骤六:根据softmax loss和smooth l1 loss对候选区域的特征进行分类和回归调整的过程,回归操作是对于框调整所使用的bou b reg来训练。

  • Faster-rcnn

最大的亮点之处是faster-rcnn把获取feature map,候选区域选取,回归和分类等操作全部融合在一个深层网络当中,效率较前两种提升了很多。

Faster-rcnn在结构上主要由一下几部分构成:

1.  卷积层,这部分卷积层就是普通的由imagenet比赛上用于分类的预训练模型所用的卷积层,它的主要功能是用来提取整张图片的feature map,卷积层结构也是卷积+激活函数+池化操作组成的。

2.  RPN网络,这是整个faster-rcnn的核心部分,改善了前两种方法用选择搜索来获取候选区域的方法,这种方法不仅快速而且更加高效地利用了cnn网络。在生成候选区域的时候会生成anchors,然后内部通过判别函数判断anchors属于前景还是后景,然后通过边框回归来进行第一次调整anchors获取准确的候选区域。

3.  Roi pooling,这一层的添加主要是为了解决最后输入全连接层的feature map的尺寸大小不同的问题,通过上采样来获取固定大小。

4.  分类和回归,最后通过两个分类层和回归层来分别判断物体属于哪个类别以及精细调整候选区域的位置,以获取最终目标检测的结果。

 十六、yolo系列和ssd对比

1. 检测方式:
- YOLO:YOLO将输入图像划分为SxS个网格,并为每个网格预测B个边界框,以及每个边界框的物体类别和置信度。它使用一个单一的卷积神经网络(CNN)同时预测所有网格的边界框和分类置信度。
- SSD:SSD使用一种多尺度特征图的方法来检测物体。它在不同层次的特征图上执行预测,这样可以检测到不同大小的物体。与YOLO一样,SSD也使用一个单一的CNN来预测边界框和分类置信度。
2. 默认框(Default/Prior boxes):
- YOLO:YOLO不使用默认框。它直接在每个网格单元中预测边界框的坐标、尺寸和置信度。
- SSD:SSD使用默认框或先验框来预测物体边界框。在每个特征图单元上,SSD预测边界框的相对偏移量和尺寸,并结合先验框的坐标和尺寸计算真实的边界框。
3. 尺度敏感性:
- YOLO:YOLO对小物体的检测性能较差,因为使用单一尺度的特征图限制了其捕捉不同大小物体的能力。
- SSD:SSD可以更好地检测各种大小的物体,因为它利用多尺度特征图来预测边界框。这使得SSD在处理小物体时具有更好的性能。
4. 速度和准确性:
- YOLO:YOLO的速度非常快,但其准确性略低于SSD。YOLO的速度优势在于其简单的预测方法和较小的特征图。
- SSD:SSD的准确性通常高于YOLO,尤其是在处理小物体时。然而,由于其使用多尺度特征图和较多的默认框,SSD在计算上可能比YOLO更昂贵。 

十七、几个数据增强方式 

Mixup, Cutmix只用于分类任务,分类分数按比例分配, Cutout适合分类、检测、识别任务,分类结果不变 。

  •  mixup:将随机的两张样本按比例混合,分类的结果按比例分配。只适合分类任务。
  •  cutout:随机的将样本中的部分区域cut掉,并且填充0像素值,分类的结果不变。
  •  cutmix:就是将一部分区域cut掉但不填充0像素而是随机填充训练集中的其他数据的区域像素值,分类结果按一定的比例分配。

十八、coding

//IoU
import numpy as np

def IoU(box1, box2):
    # 计算中间矩形的宽高
    in_w = min(box1[2], box2[2]) - max(box1[0], box2[0])
    in_h = min(box1[3], box2[3]) - max(box1[1], box2[1])

    # 计算交集、并集面积
    inter = 0 if in_w <= 0 or in_h <= 0 else in_h * in_w
    union = (box2[2] - box2[0]) * (box2[3] - box2[1]) +\
            (box1[2] - box1[0]) * (box1[3] - box1[1]) - inter
    # 计算IoU
    iou = inter / union
    return iou

if __name__ == "__main__":
    box1 = [0, 0, 6, 8]  # [左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标]
    box2 = [3, 2, 9, 10]
    print(IoU(box1, box2))
//NMS
def nms(boxes, thresh):
    """Pure Python NMS baseline."""
    x1 = boxes[:, 0]
    y1 = boxes[:, 1]
    x2 = boxes[:, 2]
    y2 = boxes[:, 3]
    scores = boxes[:, 4]

    # 计算每一个anchor的面积
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    # 按照从小到大排序后返回下标,然后顺序取反,即从大到小对应的下标
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        # 置信度高的预测框即当前框与其他框的交集
        # 选择的区域就是取最大的x1, y1和最小的 x2, y2
        xx1 = np.maximum(x1[i], x1[order[1:]])  # 这个就是较差区域的左上角的坐标,下面以此类推
        yy1 = np.maximum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.minimum(y2[i], y2[order[1:]])
        # 计算交叉区域的面积,就是用当前的anchor与其它的anchor计算,是否有相交的面积,如果有,那相交的面积是多少
        w = np.maximum(0.0, xx2 - xx1 + 1)  # 计算w
        h = np.maximum(0.0, yy2 - yy1 + 1)  # 计算h
        inter = w * h  # 交叉面积
        # 计算IOU,  相交区域 / (当前区域 + 某区域面积 - 相交区域面积)
        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        # 保留IOU小于阈值的框
        inds = np.where(ovr <= thresh)[0]
        # 因为ovr数组的长度比order数组少一个,所以这里要将所有下标后移一位
        order = order[inds + 1]
    return boxes[keep]


if __name__ == "__main__":
    a = np.array([[191, 89, 413, 420, 0.80],      # 0
                  [281, 152, 573, 510, 0.99],     # 1
                  [446, 294, 614, 471, 0.65],     # 2
                  [50, 453, 183, 621, 0.98],      # 3
                  [109, 474, 209, 635, 0.78]])    # 4
    nms_result = nms(a, 0.2)
//K-means
import numpy as np
from matplotlib import pyplot

class K_Means(object):
    # k是分组数;tolerance‘中心点误差’;max_iter是迭代次数
    def __init__(self, k=2, tolerance=0.0001, max_iter=300):
        self.k_ = k
        self.tolerance_ = tolerance
        self.max_iter_ = max_iter

    def fit(self, data):
        self.centers_ = {}
        for i in range(self.k_):
            self.centers_[i] = data[i]

        for i in range(self.max_iter_):
            self.clf_ = {}
            for i in range(self.k_):
                self.clf_[i] = []
            # print("质点:",self.centers_)
            for feature in data:
                # distances = [np.linalg.norm(feature-self.centers[center]) for center in self.centers]
                distances = []
                for center in self.centers_:
                    # 欧拉距离
                    # np.sqrt(np.sum((features-self.centers_[center])**2))
                    distances.append(np.linalg.norm(feature - self.centers_[center]))
                classification = distances.index(min(distances))
                self.clf_[classification].append(feature)

            # print("分组情况:",self.clf_)
            prev_centers = dict(self.centers_)
            for c in self.clf_:
                self.centers_[c] = np.average(self.clf_[c], axis=0)

            # '中心点'是否在误差范围
            optimized = True
            for center in self.centers_:
                org_centers = prev_centers[center]
                cur_centers = self.centers_[center]
                if np.sum((cur_centers - org_centers) / org_centers * 100.0) > self.tolerance_:
                    optimized = False
            if optimized:
                break

    def predict(self, p_data):
        distances = [np.linalg.norm(p_data - self.centers_[center]) for center in self.centers_]
        index = distances.index(min(distances))
        return index


if __name__ == '__main__':
    x = np.array([[1, 2], [1.5, 1.8], [5, 8], [8, 8], [1, 0.6], [9, 11]])
    k_means = K_Means(k=2)
    k_means.fit(x)
    print(k_means.centers_)
    for center in k_means.centers_:
        pyplot.scatter(k_means.centers_[center][0], k_means.centers_[center][1], marker='*', s=150)

    for cat in k_means.clf_:
        for point in k_means.clf_[cat]:
            pyplot.scatter(point[0], point[1], c=('r' if cat == 0 else 'b'))

    predict = [[2, 1], [6, 9]]
    for feature in predict:
        cat = k_means.predict(predict)
        pyplot.scatter(feature[0], feature[1], c=('r' if cat == 0 else 'b'), marker='x')

    pyplot.show()
//快排
int PartSort1(int* arr,int left,int right){//hoare版本
    int end=right-1;//拿到当前数组最后一个元素的下标
    while(left<end){
        while(left<end&&arr[left]<=arr[right-1]){//从左往右找到一个大于基准值的值
            left++;
        }
        while(left<end&&arr[end]>=arr[right-1]){//从右往左找到一个小于基准值的值
            end--;
        }
        Swap(&arr[left],&arr[end]);//交换找到的两个元素
    }
    Swap(&arr[left],&arr[right-1]);//别忘了最后把基准值放在对应的位置
    return left;
}

int PartSort3(int* arr,int left,int right){
    int cur=left;
    int prev=cur-1;
    while(cur<right){
        if(arr[cur]<=arr[right-1]&&++prev!=cur){
            Swap(&arr[prev],&arr[cur]);
        }
        cur++;
    }
    return prev;
}

void QuickSort(int *arr,int left,int right){
    if(right-left<=1){//元素个数小于一个,不用分割
        return;
    }
    int div=PartSort3(arr,left,right);//分割当前数组
    QuickSort(arr,left,div);//处理当前数组的左半部分,不包括下标为div的元素
    QuickSort(arr,div+1,right);//处理当前数组的右半部分,不包括下标为div的元素
}
//归并排序
void MergeData(int* arr,int left,int mid,int right,int* s){//对两组有序序列进行归并
    int begin1=left;//第一组序列的首元素下标
    int end1=mid-1;//第一组序列最后元素下标
    int begin2=mid;//第二组序列首元素下标
    int end2=right-1;//第二组序列最后元素下标
    int count=left;//进行归并时第一个元素应放在对应的位置
    while(begin1<=end1&&begin2<=end2){//归并两组序列
        if(arr[begin1]<=arr[begin2]){
            s[count]=arr[begin1];
            begin1++;
        }else{
            s[count]=arr[begin2];
            begin2++;
        }
        count++;
    }
    while(begin1<=end1){//如果第一组序列有剩余,继续归并
        s[count]=arr[begin1];
        count++;
        begin1++;
    }
    while(begin2<=end2){//如果di二组序列有剩余,继续归并
        s[count]=arr[begin2];
        count++;
        begin2++;
    }
}

void MergeSort(int* arr,int left,int right,int* s){//归并排序递归
    if(right-left<=1){//元素个数小于1,直接返回
        return;
    }
    int mid=left+(right-left)/2;
    MergeSort(arr,left,mid,s);//归并左半部分
    MergeSort(arr,mid,right,s);//归并右半部分
    MergeData(arr,left,mid,right,s);//将当前有序的序列进行归并
    memcpy(arr+left,s+left,sizeof(arr[0])*(right-left));//将归并后的将结果拷贝回原来的数组,为下次归并做准备
 
}

十九、Canny边缘检查

1.彩色图转化为灰度图
2.由于边缘检测容易受到图像中噪声的影响,应用高斯滤波来平滑图像–>去除噪声。
3.找寻图像的强度梯度。 Canny的基本思想是找寻一幅图像中强度变化最强的位置。所谓的变化最强,即指梯度方向。平滑后的图像中每个像素点的梯度可以由Sobel算子来获得:
(1)首先,利用Sobel算子得到沿x轴和y轴方向的梯度G_x和G_y。
(2)由G_X和G_Y便可计算每一个像素点的梯度幅值G。
(3)接着,每一个像素点用G代替。对于变化剧烈的边界处,G值越大,对应的颜色为白色。
(4)然后,这些边界通常非常粗,难以标定边界的真正位置,还必须存储梯度的方向θ。
4.应用非极大抑制技术来消除边误检(本来不是边缘但检测出来是),沿着梯度θ方向上比较该像素点,若该像素点与两侧相比最大则保留,否则抑制(置为0)。这一步的目的是将模糊的边界变得清晰,剔除一大部分不是边缘的点。
5.双阈值边缘连接处理
规则:设定两个阈值,minVal和maxVal。
大于maxVal的边缘肯定是边缘(保留),低于minVal的边缘是非边缘(舍去)。
对于介于两者之间的值,判断是否与真正的边界(强边界)相连,相连就保留,否则丢弃。
6.二值化图像输出结果。