文章目录
- 视觉算法知识荟萃
- Transformer 系列
- 手写算法
- 视觉算法零碎
- 目标检测系列
- 语义分割系列
- 模型压缩
- 可分离卷积
- 在端侧部署时性能不足,如何在不改变网络结构的情况下优化性能
- FP32 转 FP8
- 浮点数定点化的方式
- 非对称量化
- 高斯分布
- ToF相机和结构光相机
- 针孔相机模型
- 激光雷达投影到 2D 平面
- 多任务平衡
- 自动驾驶检测模型对 corner case 进行的针对性优化
- 密集行人场景遮挡
- Tiny Object Detection
- Special Object Detection
- 推导全连接层梯度回传
- 梯度消失
- 梯度爆炸
- 为什么多卡训练时,GPU0显存要多一些
- 双线性插值
- 结构风险和经验风险
- Delta K
- Abs Rel
- RMSE
- Silog
- VNL
- Gradient Loss
- 光度一致 Loss
- 光流翘曲 Loss
- SoftMax
- CrossEntropy
- Focal Loss
- Dropout 原理
- 信息熵
- SVM 原理
- 逻辑回归和线性回归的区别
- Transformer(Self-Attention)
- ViT
- Swin-Transformer
- Adam,SGD
- NMS
- IoU
- 边缘检测算法
- Pooling Layer
- 手写 BN
- Bagging
- Boosting
- 过拟合
- P范数
- 欠拟合
- 训练出现NaN
- 目标检测
- 语义分割
- 运动控制
- 单目深度估计
- 双目深度估计
- 轻量级平台配置
视觉算法知识荟萃
Transformer 系列
Self-Attention
QKV是如何出来的
输入乘以三个矩阵得到输入的QKV,实现起来就是使用 nn.linear
其中 q 会和每个 k 计算相似程度
α
\alpha
α,之后
α
\alpha
α 通过 softmax 后生成
α
′
\alpha'
α′,
之后每个
α
′
\alpha'
α′ 和 v 相乘后相加得到输出 b。如果
a
2
a_2
a2 和
a
3
a_3
a3 比较相似,那么输出
b
2
b^2
b2 的值就会更接近
v
3
v^3
v3
位置编码
在 self-attention 中,如果没有位置编码,那么 q1,q2,q3 的顺序改变为 q1,q3,q2 时,对于 q1 的输出没有影响。
为了解决这个情况,在 ViT 中添加了位置编码信息
N
,
E
,
S
N, E, S
N,E,S -> pad to
N
,
E
,
S
+
1
N, E, S + 1
N,E,S+1
position code: torch.rand(500, E).T.squeeze(0)
feature + position code
Self-Attention 为什么要除以 d \sqrt{d} d
点积操作会使得 softmax 函数的输入量级很大,导致回传时梯度为 0;
根据概率论的期望方差计算公式,假设 x 和 y,q 和 k 都是服从期望为 0,方差为 1 的独立随机变量:
E
(
X
Y
)
=
E
(
X
)
E
(
Y
)
=
0
×
0
=
0
E(XY)=E(X)E(Y) = 0\times 0 = 0
E(XY)=E(X)E(Y)=0×0=0
D
(
X
Y
)
=
E
(
X
2
Y
2
)
−
[
E
(
X
Y
)
]
2
=
D
(
X
)
D
(
Y
)
−
[
E
(
X
)
E
(
Y
)
]
2
=
1
D(XY)=E(X^2Y^2)-[E(XY)]^2 = D(X)D(Y) - [E(X)E(Y)]^2 = 1
D(XY)=E(X2Y2)−[E(XY)]2=D(X)D(Y)−[E(X)E(Y)]2=1
相应的:
D
(
Q
K
)
=
D
(
∑
i
d
q
i
k
i
)
D(QK) = D(\sum_i^d{q_i}{k_i})
D(QK)=D(∑idqiki)
D
(
Q
K
d
)
=
d
×
D
(
q
i
k
i
)
/
d
=
1
D(\frac{QK}{\sqrt{d}})= d\times D(q_ik_i) / d = 1
D(dQK)=d×D(qiki)/d=1
所以 self-attention 通过除以
d
\sqrt{d}
d 降低输入的量级,并输出一个符合 0-1 分数的结果
Transformer
Transformer 为什么使用LN,不使用BN
- LN适合处理变长的数据
- LN是对一个样本中的一个词进行归一化,BN是对一个batch中
Multi-Head Self-Attention
多头注意力即将生成的q,k,v拆分为头数个部分,并不会增加参数量,只是多了一个融合矩阵
ViT
ViT 将一张 224x224 的图片视为 16x16 个 14x14 图像块的集合,将 14x14 的子图像拉伸后通过一个 Linear 层视作一个单词,那么一张图像就可以视作 16x16 个单词的集合。添加位置编码后就可以通过 Transformer Encoder 进行下一步计算,其中额外增加的 Embedding 中包含了该图像中的特征信息。
Swin-Transformer
DETR
Deformable DETR
手写算法
手写 SGD
手写 Conv2d
输入和输出的特征图尺寸大小关系:
n
−
k
+
2
p
s
+
1
\frac{n -k+2p}{s} + 1
sn−k+2p+1
def conv2d(inputs, kernels, bias, stride, padding):
"""
正向卷积操作
inputs: 输入数据,形状为 (C, H, W)
kernels: 卷积核,形状为 (F, C, HH, WW),C是图片输入层数,F是图片输出层数
bias: 偏置,形状为 (F,)
stride: 步长
padding: 填充
"""
# 获取输入数据和卷积核的形状
C, H, W = inputs.shape
F, _, kH, kW = kernels.shape
# 在输入数据的第二个轴和第三个轴上的开始和结束位置填充padding个值
inputs_pad = np.pad(inputs, ((0, 0), (padding, padding), (padding, padding)))
# 初始化输出数据,卷积后的图像size大小
outH = (H - kH + 2 * padding) // stride + 1
outW = (W - kW + 2 * padding) // stride + 1
outputs = np.ones((F, outH, outW))
# 进行卷积操作
for i in range(outH):
for j in range(outW):
inputs_slice = inputs_pad[:, i * stride : i * stride + kH,
j * stride : j * stride + kW]
outputs[:, i, j] = np.sum(inputs_slice * kernels, axis=(1, 2, 3)) + bias
return outputs
卷积核的参数量
一个 F × C × k H × k W F\times C\times kH\times kW F×C×kH×kW 的卷积核,参数量是 F × C × H × W × 4 B F\times C\times H\times W\times 4\mathbf{B} F×C×H×W×4B
卷积操作的计算量
特征图大小为
C
×
H
×
W
C\times H\times W
C×H×W,卷积核大小为
F
×
C
×
k
H
×
k
W
F\times C\times kH\times kW
F×C×kH×kW,
卷积操作的计算量为
H
×
W
×
k
H
×
k
W
×
C
×
F
H\times W\times kH\times kW\times C\times F
H×W×kH×kW×C×F,默认为3x3卷积,pad=1,stride=1
手写 BatchNorm2d
手写 Pooling Layer
手写 IoU
手写 NMS
手写 Loss
手写 Metric
手写 Self-Attention
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class Self_Attention(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.linear_q = nn.Linear(self.in_channels, self.out_channels, bias=False)
self.linear_k = nn.Linear(self.in_channels, self.out_channels, bias=False)
self.linear_v = nn.Linear(self.in_channels, self.out_channels, bias=False)
self._norm_fact = 1 / math.sqrt(self.out_channels)
def forward(self, x):
q = self.linear_q(x)
k = self.linear_k(x)
v = self.linear_v(x)
# k^T*q
dist = torch.bmm(q, k.transpose(-2, -1)) * self._norm_fact
dist = torch.softmax(dist, dim = -1)
attn = torch.bmm(dist, v)
return attn
if __name__ == '__main__':
# initial params
batch_size = 2
seq_len = 5
dim_input = 10
x = torch.randn(batch_size, seq_len, dim_input)
# compute self-attention
sa = Self_Attention(10, 12)
output = sa(x)
# show results
print(x.shape)
print(x)
print(output.shape)
print(output)
手写坐标转换
视觉算法零碎
目标检测系列
语义分割系列
模型压缩
量化参数
使用更少的比特来表示模型的权重和激活,例如将 FP32 的参数转化为 INT8
- 压缩比高
- 可能影响精度
剪枝参数
删除模型中不必要的参数,从而减少模型的大小和计算量
- 不会影响精度
- 可能会影响性能
蒸馏
通过将大模型的知识迁移到小模型来提高小模型的性能。具体来说,这种方法通过训练一个小模型来模仿一个大模型的输出,并将大模型作为教师模型来提供知识
- 能提升小模型的参数
- 训练时会提升模型的复杂度
可分离卷积
5
×
5
×
3
→
3
×
3
×
4
5\times 5 \times 3 \to 3\times3\times4
5×5×3→3×3×4
卷积核参数量 =
3
×
3
×
3
×
4
3 \times 3 \times 3 \times 4
3×3×3×4
深度可分离卷积,第一步是逐通道卷积,第二步是逐像素卷积
-
逐通道卷积(一个卷积核遍历一个通道的feature)
5 × 5 × 3 → 3 × 3 × 3 5 \times 5 \times 3 \to 3 \times3 \times 3 5×5×3→3×3×3
卷积核参数量 = 3 × 3 × 3 3 \times 3 \times 3 3×3×3
-
逐像素卷积(一个卷积核遍历三个通道的feature,共有4个这样的卷积核)
3 × 3 × 3 → 3 × 3 × 4 3 \times3 \times 3 \to 3\times3\times4 3×3×3→3×3×4
卷积核参数量 = 1 × 1 × 3 × 4 1 \times 1 \times 3 \times 4 1×1×3×4
在端侧部署时性能不足,如何在不改变网络结构的情况下优化性能
- 优化器选择:选择适合的优化器可以减小模型的大小和计算复杂性,例如Adam、RMSprop等。
- 数据缓存:缓存重复使用的数据,避免重复读取,可以提高推理速度。
- 并行计算:将计算任务分解为多个并行的小任务,并使用多个线程或进程同时进行计算,加速推理过程。
- 模型压缩:使用模型压缩技术,例如知识蒸馏、参数共享等,可以减小模型的大小和计算复杂性,提高推理速度。
- 缓存优化:优化缓存策略,例如使用LRU缓存等,可以减少重复计算和提高缓存命中率,提高推理速度。
- 硬件加速:使用硬件加速器,例如GPU或FPGA等,可以加速模型推理过程。
FP32 转 FP8
INT8 = clip ( around ( FP32 / s + bias), qmin, qmax)
clip 对数进行截断
浮点数定点化的方式
以12.918为例,计算12bit定点化和11bit定点化的量化误差,判断是不是无损量化:
- 12 转换为二进制要用 1100 表示,会占据 4 bit,使用 12 bit定点化时小数位只能剩余 8 bit。
此时表示 0.918 所需的刻度数位为 0.918 × 2 8 = 235.008 0.918\times 2^8=235.008 0.918×28=235.008
这里小数位上的值小于 0.5,可以认为是无损量化。 - 同理,在使用 11 bit 进行量化时,需要的刻度位数为
0.918
×
2
7
=
117.504
0.918\times 2^7=117.504
0.918×27=117.504
此时,小数位上的值大于 0.5,不能被认为是无损量化。
非对称量化
在非对称量化中,信号的幅度被测量并相对于一个预定义的阈值进行比较。
- 如果信号的幅度超过阈值,则被视为“非对称”或“异常”,并且会触发一个报警或其它类型的响应。
- 相反,如果信号的幅度低于阈值,则被视为“对称”或“正常”,并且不触发任何响应。
非对称量化的优点是它可以更准确地检测和识别异常信号,同时减少误报率。
它还可以在处理大量数据时提供更高的效率,因为它只关注那些超出阈值的信号。
高斯分布
ToF相机和结构光相机
原理
- ToF相机:发送短脉冲光源(通常是红外光)到场景中,然后测量光线从相机发送到物体表面并返回相机的时间差来计算距离。ToF相机可以直接测量每个像素点的深度信息。
- 结构光相机:使用红外光在场景上投射结构化的光图案,例如格栅或斑点。相机通过捕获投射到场景上的结构化光图案,并分析图案的形变来计算物体表面的深度信息。
测量原理
- ToF相机:ToF相机测量时间差,因此可以在较短时间内获取深度信息,适用于实时应用。
- 结构光相机:结构光相机需要在场景中投射光图案并捕获反射的图案,因此相对于ToF相机可能需要较长的时间来获取深度信息。
常见相机
- ToF:Kinect V1,RealSense
- 红外结构光:Kinect V2,Kinect DK
针孔相机模型
Z
f
=
X
x
=
Y
y
\frac{Z}{f}=\frac{X}{x}=\frac{Y}{y}
fZ=xX=yY
激光雷达投影到 2D 平面
p i m g = 1 Z K T P l i d a r p_{img} = \frac{1}{Z}KTP_{lidar} pimg=Z1KTPlidar
# lidar2img: cam2img + lidar2cam
def projectPoints(pcd, img_path, lidar2img):
# 1. read files
img = cv2.imread(img_path)
w, h = img.shape
points = np.asarray(pcd.points)
# 2. transform lidar
velo = np.insert(points, 3, 1, axis = 1).T
# 如果x或者y小于0,那么就把整列也就是整个点,给删除了
velo = np.delete(velo, np.where(velo[0, :] < 0), axis = 1)
velo = np.delete(velo, np.where(velo[1, :] < 0), axis = 1)
cam = lidar2img.dot(velo)
cam = np.delete(cam, np.where(velo[2, :] < 0), axis = 1)
# 3. delete invalid pixel
cam[:2] /= cam[2, :]
u, v, z = cam[:3]
u_out = np.logical_or(u < 0, u > w)
v_out = np.logical_or(v < 0, v > h)
outlier = np.logical_or(u_out, v_out)
cam = np.delete(cam, np.where(outlier), axis = 1)
u, v, z = cam[:3]
# 4. visualize
for x, y, z in zip(x, y, z):
cv2.circle(img, (int(x), int(y)), 3, (0, 255, 0), -1)
cv2.imshow("img", img)
cv2.waitkey(0)
cv2.destroyAllWindows()
多任务平衡
- MiDaS 和 DPT 中使用帕累托优化做到多任务平衡
- LeReS 使用课程学习做的多任务平衡,从简单到难
- SM4Depth 使用自适应采样来做到多任务平衡
自动驾驶检测模型对 corner case 进行的针对性优化
- 像素级异常:挡风玻璃有遮挡,隧道穿越的过曝现象,对向车辆的远光灯
使用少样本学习,或者数据增强的方式进行训练 - 域变换:到了环境不同的地方,到了行驶习惯不同的国家
学习正常的数据集,同时判断一个场景是否属于OOD,即对域的匹配程度进行测量 - 未知对象:未知的实例
使用具有LLL,检测OOD能力的目标检测模型,比如OWOD。如果说只是在OOD能力上卓越的话,可以选择我们的Unsniffer
对于异性物体检测,比如倒地的护栏,正在转弯的货车。可以将物体体素化,转化为占据栅格进行处理。 - 潜在场景:行人,异常车辆
对街道上的行人车辆进行行动预测
密集行人场景遮挡
调整目标检测的loss来处理这种场景,使检测框远离周围gt框,并靠近正确的gt框
l
o
s
s
=
d
i
s
(
p
d
,
g
t
)
−
d
i
s
(
p
d
,
s
u
r
r
o
u
n
d
i
n
g
g
t
)
loss = dis(pd, gt) - dis(pd, surrounding gt)
loss=dis(pd,gt)−dis(pd,surroundinggt)
loss 会使得第一项越来越小,第二项越来越大
这种遮挡对跟踪的影响
- 会导致检测算法无法准确检出人物
- NMS的阈值难以设定,阈值过小会使得虚检增多,阈值过大则会滤掉正确框住周围目标的框
Tiny Object Detection
- 样本不平衡 - 数据增强,调整loss超参
- 目标框重叠 - 使用目标框距离loss
- 网络结构 - 多尺度信息
Special Object Detection
- 选择合适的网络结构,可以试试 Faster R-CNN,YOLO,SSD。
- 数据增强
- Anchor 设计,预定义一些不同尺寸和高度的 anchor,来匹配物体的形状。
推导全连接层梯度回传
see here
全连接层的输入和输出公式如下:
X
⋅
W
+
B
=
Y
\mathbf{X} \cdot\mathbf{W} + \mathbf{B} = \mathbf{Y}
X⋅W+B=Y
则有Loss函数
L
\mathbf{L}
L 关于
X
\mathbf{X}
X 和
W
\mathbf{W}
W 的导数分别为:
- ∂ L ∂ X = ∂ L ∂ Y ⋅ W T \frac{\partial L}{\partial \mathbf{X}} = \frac{\partial L}{\partial \mathbf{Y}} \cdot \mathbf{W}^T ∂X∂L=∂Y∂L⋅WT
- ∂ L ∂ W = X T ⋅ ∂ L ∂ Y \frac{\partial L}{\partial \mathbf{W}} = \mathbf{X}^T \cdot \frac{\partial L}{\partial \mathbf{Y}} ∂W∂L=XT⋅∂Y∂L
推导公式2的时候,把第一次两项的位置换一下
梯度消失
原因
梯度消失是指在深层网络中,反向传播时梯度逐层递减,最终变得非常小,导致权重更新几乎没有效果。这通常发生在深层网络中,尤其是当使用一些激活函数(如 Sigmoid 或 Tanh)时,在链式求导的基础上,堆叠多层梯度后梯度值会非常小。
解决方法
- 选择合适的激活函数,如 ReLU,在ReLU函数的导数是固定值,所以不会出现梯度消失和梯度爆炸的现象,并且计算速度更快。并且由于左侧部分导数为0,令网络感知到哪部分神经元是不起作用的,达到稀疏化的目的。但是如果这部分神经元过多,会导致过多死神经元,改进方法有 LeakyReLU,ELU(更鲁棒,计算时间更长),SoftPlus(主要用于固定输出恒为正值)
- BatchNorm,通过标准化使输入接近于正态分布,使数据主要集中于激活函数导数的最大值处
- 残差连接,使梯度直接从上层传递到下层,缓解了梯度消失
- 梯度裁剪,限制梯度的大小,防止梯度消失
梯度爆炸
原因
梯度爆炸是指在训练过程中,梯度逐层递增,导致权重更新变得非常大,从而产生不稳定的训练。这通常发生在很深的网络中,特别是当网络中存在大量参数时。主要原因是层之间的权重太大,导致反向传播时梯度指数级增长。
解决方法
- BN,使数据接近正态分布(如果数据集中在0附近,也能让数据分开),除了能缓解梯度消失还能缓解梯度爆炸
- 预训练
- 梯度裁剪,限制梯度的大小,防止梯度爆炸
为什么多卡训练时,GPU0显存要多一些
-
DataParallel会将定义的网络模型参数默认放在GPU 0上
-
这里GPU0作为master来进行梯度的汇总和模型的更新,再将计算任务下发给其他GPU,所以他的内存和使用率会比其他的高。
双线性插值
y
−
y
0
y
1
−
y
0
=
x
−
x
0
x
1
−
x
0
\frac{y-y_0}{y_1-y_0}=\frac{x-x_0}{x_1-x_0}
y1−y0y−y0=x1−x0x−x0
y
=
x
1
−
x
x
1
−
x
0
y
0
+
x
−
x
0
x
1
−
x
0
y
1
y=\frac{x_1-x}{x_1-x_0}y_0+\frac{x-x_0}{x_1-x_0}y_1
y=x1−x0x1−xy0+x1−x0x−x0y1
α
=
a
b
s
(
x
1
−
x
)
,
y
=
a
⋅
y
0
+
(
1
−
α
)
⋅
y
1
\alpha=abs(x_1-x), y=a\cdot y_0 + (1-\alpha)\cdot y_1
α=abs(x1−x),y=a⋅y0+(1−α)⋅y1
结构风险和经验风险
- 期望风险:机器学习模型关于真实分布的平均损失称为期望风险
- 经验风险:机器学习模型关于训练集的平均损失称为经验风险,当样本数无穷大∞的时候趋近于期望风险(大数定律)
- 结构风险:结构风险 = 经验风险 + 正则化项
Delta K
1 T ∑ i m a x ( d / d , d / d ) \frac{1}{T}\sum_i{max(d/\mathbf{d}, \mathbf{d}/d)} T1∑imax(d/d,d/d)
thresh = np.maximum((gt[valid_mask]/pd[valid_mask]), (pd[valid_mask]/gt[valid_mask]))
d1 = (thresh < 1.25).mean()
d2 = (thresh < 1.25 ** 2).mean()
d3 = (thresh < 1.25 ** 3).mean()
Abs Rel
1 T ∑ i ∣ d − d ∣ d \frac{1}{T}\sum_i{\frac{|d-\mathbf{d}|}{\mathbf{d}}} T1∑id∣d−d∣
rel = np.abs(gt[valid_mask] - pd[valid_mask]) / gt[valid_mask]
rel = rel.mean()
RMSE
1 T ∑ i ( d − d ) 2 \sqrt{\frac{1}{T}\sum_i(d - \mathbf{d})^2} T1∑i(d−d)2
rms = ((gt[valid_mask] - pd[valid_mask]) ** 2).mean()
rms = torch.sqrt(rms)
Silog
α 1 T ∑ i g 2 − λ T 2 ( ∑ i g ) 2 , g = log d − log d \alpha\sqrt{\frac{1}{T}\sum_ig^2-\frac{\lambda}{T^2}(\sum_ig)^2},g=\log d-\log \mathbf{d} αT1∑ig2−T2λ(∑ig)2,g=logd−logd
valid_mask = torch.logical_and(gt > args.eval_depth_min, gt < args.eval_depth_max)
err = torch.log(gt[valid_mask]) - torch.log(pd[valid_mask])
silog_loss = 10.0 * torch.sqrt((d ** 2).mean() - 0.85 * (d.mean() ** 2))
VNL
1 V ∑ i ∣ ∣ n i − n i ∣ ∣ 1 \frac{1}{V}\sum_i{||n_i-\mathbf{n}_i||_1} V1∑i∣∣ni−ni∣∣1
Gradient Loss
import torch
import torch.functional.nn as nn
def gradient(prediction, target, mask):
M = torch.sum(mask, (1, 2))
diff = prediction - target
diff = torch.mul(diff, mask)
grad_x = torch.abs(diff[:, :, 1:] - diff[:, :, :-1])
mask_x = torch.mul(mask[:, :, 1:], mask[:, :, :-1])
grad_x = torch.mul(grad_x, mask_x)
grad_y = torch.abs(diff[:, 1:, :] - diff[:, :-1, :])
mask_y = torch.mul(mask[:, 1:, :], mask[:, :-1, :])
grad_y = torch.mul(grad_y, mask_y)
loss = torch.sum(grad_x) + torch.sum(grad_y)
divisor = torch.sum(M) * 2
return loss / divisor if divisor != 0 else 0
class gradient_loss(nn.Module):
def __init__(self, gradient_scales = 4):
super().__init__()
self.gradient_scales = gradient_scales
def forward(self, prediction, target, mask):
losses = []
for idx in range(prediction):
loss = 0
pd = prediction[idx]
gt = target[idx]
for scale in range(self.gradient_scales):
loss += gradient(pd[:, ::scale, ::scale], gt[:, ::scale, ::scale], mask[:, ::scale, ::scale])
losses.append(loss)
return losses.mean()
光度一致 Loss
将左图的 RGB 值通过相机内参映射到相机坐标系下,然后通过坐标转换转移到右图的相机坐标系下,然后得到新的右侧 RGB 图。将两个右侧的 RGB 图求得 Loss(SSIM + L1)
光流翘曲 Loss
使用光流将第 n 帧的深度值转换到第 n-1 帧下,然后计算通过光流得到的第 n-1 帧深度和预测得到的第 n-1 帧深度的误差。
SoftMax
e q i / ∑ i ( e q i ) e^{q_i}/\sum_i(e^{q_i}) eqi/∑i(eqi)
n = np.random.rand(10)
softmax_n = np.exp(n)/np.sum(np.exp(n))
SoftMax 反向传播
反向传播时对
∂
y
i
∂
x
j
\frac{\partial y_i}{\partial x_j}
∂xj∂yi 求导,当
i
=
j
i=j
i=j 时,
∂
y
i
∂
x
j
=
∂
∂
x
i
(
e
x
i
∑
)
=
y
i
−
y
i
2
\frac{\partial y_i}{\partial x_j} = \frac{\partial }{\partial x_i}(\frac{e^{x_i}}{\sum } )=y_i-y_i^2
∂xj∂yi=∂xi∂(∑exi)=yi−yi2
当
i
≠
j
i\ne j
i=j 时,
∂
y
i
∂
x
j
=
∂
∂
x
j
(
e
x
i
∑
)
=
−
y
i
⋅
y
j
\frac{\partial y_i}{\partial x_j} = \frac{\partial }{\partial x_j}(\frac{e^{x_i}}{\sum } )=-y_i\cdot y_j
∂xj∂yi=∂xj∂(∑exi)=−yi⋅yj
所以
∂
y
i
∂
x
j
=
{
y
i
−
y
i
⋅
y
i
i
=
j
0
−
y
i
⋅
y
j
i
≠
j
\frac{\partial y_i}{\partial x_j} =\left\{\begin{matrix} y_i-y_i\cdot y_i & i=j\\ 0\ -y_i\cdot y_j & i\ne j \end{matrix}\right.
∂xj∂yi={yi−yi⋅yi0 −yi⋅yji=ji=j
即当Y为 [1, n] 的向量时:
∂
Y
∂
X
=
d
i
a
g
(
Y
)
−
Y
T
⋅
Y
\frac{\partial Y}{\partial X}=\mathrm{diag} (Y)-Y^T\cdot Y
∂X∂Y=diag(Y)−YT⋅Y
为什么分类用交叉熵损失,回归用MSE族?
- 分类问题使用交叉熵损失函数的原因是,交叉熵损失函数可以更好地处理分类问题中类别的不平衡性。交叉熵损失函数可以通过对每个类别的损失进行加权,从而更好地处理这种不平衡性。
- 回归问题使用均方误差(MSE)或平均绝对误差(MAE)等损失函数的原因是,回归问题通常需要预测连续值,而不是离散的类别。MSE和MAE等损失函数可以更好地度量预测值与真实值之间的差异,并且对于数据集中的异常值不太敏感。
- 然而,对于某些回归问题,使用交叉熵损失函数也是合适的选择。如,在回归问题中,如果预测的值是离散的,而不是连续的,例如预测某个事件发生的或不发生的,使用交叉熵损失函数也是一个不错的选择。这样的问题就是一个二分类的问题。
CrossEntropy
− ∑ i ( q i ∗ l o g ( p i ) ) -\sum_i(q_i*log(p_i)) −∑i(qi∗log(pi))
label = np.array([0, 0, 0, 0, 1])
predict = np.random.rand(5)
softmax_pd = np.exp(predict)/sum(np.exp(predict))
cel = -1*sum(label*np.log(softmax_pd))
Focal Loss
用于解决样本不平衡的情况
− ∑ i ( q i ∗ ( 1 − p i ) γ ∗ l o g ( p i ) ) , γ = 2 -\sum_i(q_i*(1-p_i)^\gamma *log(p_i)), \gamma = 2 −∑i(qi∗(1−pi)γ∗log(pi)),γ=2
对于困难样本,概率预测值较低,此时权重会远大于简单样本的权重,也就使得困难样本的 loss 极大地缩小了。
Dropout 原理
在训练过程中,Dropout以概率p(通常取0.5)随机丢弃神经网络中的一些神经元及其连接权重。换句话说,每个神经元在每次前向传播过程中都有p的概率被“丢弃”,即将其输出设置为0。
Dropout 前向传播
对于每个训练样本,在前向传播过程中,以概率p随机选择要丢弃的神经元。被丢弃的神经元的输出设置为0,未被丢弃的神经元的输出乘以1/p的缩放因子,以保持输出值的期望不变。
Dropout 反向传播
- 在反向传播过程中,与前向传播过程相对应,被丢弃的神经元的梯度不传播,其梯度设置为0。
- 未被丢弃的神经元的梯度乘以p的缩放因子,以抵消前向传播时的缩放,从而保持梯度的期望不变。
- 训练过程中,因为每个批次都随机丢弃了一些神经元,导致网络的结构随机变化,相当于训练了多个不同的子网络,这些子网络共享参数,但权重不同,这有助于提高模型的泛化能力。
Dropout 和 Droppath 的区别
Dropout 随机丢弃节点,Droppath 随机丢弃分支结构
信息熵
H ( x ) = − p 1 ⋅ log 2 p 1 − p 2 ⋅ log 2 p 2 − . . . − p n ⋅ log 2 p n H(x)=-p_1\cdot \log_2{p_1} -p_2\cdot \log_2{p_2} -...-p_n\cdot \log_2{p_n} H(x)=−p1⋅log2p1−p2⋅log2p2−...−pn⋅log2pn
SVM 原理
SVM是一个二分类算法,在空间里找一个超平面,将空间分为两块,并且最大化两类之间的距离。
- SVM的目标是结构风险最小化
- SVM本身并不能有效避免过拟合,但可以通过调整核函数和正则化参数等来实现一定的防止过拟合的效果
如果遇到不可分的情况,可以将数据映射到更高维的空间进行分类
逻辑回归和线性回归的区别
see here
二分类算法,也可以用于多分类问题。
逻辑回归分为两部分:逻辑和回归。
线性回归模型里的因变量是连续变量,而逻辑回归里的因变量可以理解为分类变量,现在我们需要用回归模型去表示这个分类,比如说大或小,黑或白。那么考虑类别之间相对的关系,我们可以用概率来表示这个问题,比如说分类为黑的概率大于白的概率时,就把样本预测为黑。所以我们可以定制一个映射关系,将负无穷到正无穷之间的数值映射成概率值,即0-1之间,就可以解决线性回归到分类问题的过渡。
最常见的激活函数是Sigmoid函数。
- Logistic回归可以用于预测二分类问题的事件发生概率
- Logistic回归中,目标函数通常是最小化交叉熵损失,这实际上是最大化后验概率
线性回归模型中的结果是连续值(来表示相对关系),使用 MSE 等损失函数,适用于金融,趋势等领域;
逻辑回归是将线性回归的结果(通过 sigmoid)映射到0-1之间,输出概率值,使用 CrossEntropy Loss,用于分类问题
手写 sigmoid
σ ( x ) = 1 1 + e − x \sigma(x)=\frac{1}{1+e^{-x}} σ(x)=1+e−x1
x = 1/(1 + np.exp(-1 * x))
为什么选择 sigmoid
根据伯努利公式的指数族形式
sigmoid反向传播
σ ′ ( x ) = ( 1 1 + e − x ) ′ = σ − σ 2 \sigma^\prime (x)=(\frac{1}{1+e^{-x}} )^\prime =\sigma -\sigma^2 σ′(x)=(1+e−x1)′=σ−σ2
Transformer(Self-Attention)
Multi-head Attention 的流程:
ViT
- 输入transformer模块的是token序列,以ViT-B为例,使用16x16的卷积核,stride为14对224x224的图片进行处理:
[224, 224, 3] -> [14, 14, 768] -> [196, 768],经过展平操作后得到的二维矩阵中,196表示embedding的个数,768表示embedding的维度 - 拼接 class token:cat([1, 768], [196, 768])
- 叠加 position embedding,直接相加(使用1维或者2维的位置编码差别不大)
Swin-Transformer
- swin transformer中讲w-msa和sw-msa成对使用,这些就是为什么encoder中的swin block都是偶数的原因
- patch merging是做下采样操作:
- 对特征图划分window之后进行分别的self-attention的计算(A1, A2, A3, A4) (B1, B2, B3, B4) (C1, C2, C3, C4) (D1, D2. D3, D4)
- 然后将相同位置的特征图提取出来组合成新的特征图:(A1, B1, C1, D1) (A2, B2, C2, D2) (A3, B3, C3, D3) (A4, B4. C4, D4)
- 将这些特征图进行cat:[ (A1, B1, C1, D1) (A2, B2, C2, D2) (A3, B3, C3, D3) (A4, B4. C4, D4) ]
- LayerNorm 之后再进行线性映射到指定维度
BN,LN(Transformer中的LN),IN的区别
BN是对不同batch的相同通道进行归一化
LN是对相同batch的相同patch的所有通道进行归一化(相同词的所有通道归一化,对图片特征展开后单个像素的所有通道归一化)
IN是对相同batch的单个通道进行归一化
MSA 和 W-MSA 的计算量区别
- MSA
QKV的计算量: A h w × C ⋅ W q C × C = Q h w × C A^{hw\times C}\cdot W_q^{C\times C} = Q^{hw\times C} Ahw×C⋅WqC×C=Qhw×C, 3 × h w C 2 3\times hwC^2 3×hwC2
QK相乘计算量: Q h w × C ⋅ K T ( C × h w ) Q^{hw\times C}\cdot K^{T(C\times hw)} Qhw×C⋅KT(C×hw), ( h w ) 2 C (hw)^2C (hw)2C
QK结果乘以V: Q K T ( h w × h w ) ⋅ V h w × C QK^{T(hw\times hw)}\cdot V^{hw\times C} QKT(hw×hw)⋅Vhw×C, ( h w ) 2 C (hw)^2C (hw)2C
乘以多头注意力的融合矩阵: B h w × C ⋅ W O C × C B^{hw\times C}\cdot W_O^{C\times C} Bhw×C⋅WOC×C, h w C 2 hwC^2 hwC2
总计: 4 × h w C 2 + 2 × ( h w ) 2 C 4\times hwC^2 + 2\times (hw)^2C 4×hwC2+2×(hw)2C - W-MSA
假设单个窗口大小为 M × M M\times M M×M,则有 h M × w M \frac{h}{M}\times \frac{w}{M} Mh×Mw个窗口
总计算量有: h M × w M × ( 4 × ( M C ) 2 + 2 × M 4 C ) = 4 × h w C 2 + 2 × M 2 h w C \frac{h}{M}\times \frac{w}{M}\times (4\times (MC)^2 + 2\times M^4C)=4\times hwC^2 + 2\times M^2hwC Mh×Mw×(4×(MC)2+2×M4C)=4×hwC2+2×M2hwC - 两者差了 2 × h w C ( h w − M 2 ) 2\times hwC(hw-M^2) 2×hwC(hw−M2)
Shifted Window MSA
如果直接计算9个方块上的attention,那么需要将周围的块填充至4x4才能进行并行计算,所以要将window向下移动后,重新计算MSA
AC下去,BA再去右边,对不连续的区域使用mask遮挡(将不连续区域的相似度置为0)后再计算attention.
Adam,SGD
SGD是随机梯度下降法,是最基本的优化器;
-
SGD 随机梯度下降
-
SGD + momenta:根据之前梯度和当前梯度更新本次梯度,解决SGD的震荡问题
-
SGD + netrosv:根据下一步的梯度更新本次梯度,解决SGD局限于局部极小值的问题
-
Adagrad:根据之前的梯度,动态更新学习率
-
RMSProp:根据历史梯度的平方更新本次梯度
-
Adam:结合了SGD的一阶动量(历史梯度的累计)和RMSProp的二阶动量(历史梯度的平方累计)
Adam的创新点
梯度的一阶动量,约等于近几个时刻梯度向量的平均值,这样来梯度的下降方向同时由当前的梯度方向和累计的下降方向决定,记为
m
m
m
梯度的二阶动量, 等于迄今为止所有梯度值的平方和,当参数更新越频繁,二阶动量就越大,学习率就会越小,记为
1
/
V
1/\sqrt{V}
1/V
Adam 就是结合了这两项:
- m t = β 1 ⋅ m t − 1 + ( 1 − β 1 ) ⋅ g t m_t = \beta_1 \cdot m_{t-1}+(1 - \beta_{1}) \cdot g_t mt=β1⋅mt−1+(1−β1)⋅gt
- V t = β 2 ⋅ V t − 1 + ( 1 − β 2 ) ⋅ g t 2 V_t = \beta_2 \cdot V_{t-1}+(1 - \beta_{2}) \cdot g^2_t Vt=β2⋅Vt−1+(1−β2)⋅gt2
此处的
β
1
,
β
2
\beta_1, \beta_2
β1,β2就是Adam的超参,所以梯度下降公式如下:
η
t
=
α
⋅
m
t
/
V
t
,
ω
t
+
1
=
ω
t
−
η
t
\eta_t = \alpha \cdot m_t/\sqrt{V_t}, \omega_{t+1} = \omega_t-\eta_t
ηt=α⋅mt/Vt,ωt+1=ωt−ηt
如何修改SGD
- 添加一阶动量解决震荡问题
- 使用上一步的累计动量更新一步,用下一步的梯度方向和历史累计动量相结合,计算出当前的梯度更新方向,来解决局限于局部极小值的问题。
SGD 是否需要 shffule 数据集
需要,只有在从样本中获得顺序特征时才不 shuffle
SGD 解方程
# 定义方程,使用 SGD 算法求解 当 init=4,func=x**3+2*x-24,y=80 的解为多少
def func(x):
return 3 * (x ** 2) + 2 * x - 24
def loss(x, target):
return (x - target) ** 2
def derivative_loss(x, target, eps = 1e-8):
return (loss((x + eps), target) - loss(x, target)) / eps
def solve(x, target, lr = 0.001, iters = 60000):
print('init x:', x)
for i in range(iters):
y = func(x)
if abs(y - target) < 1e-5:
return x
else:
grad_x = derivative_loss(y, target)
x = x - grad_x * lr
return x
if __name__ == '__main__':
init = 4
target = 80
x = solve(init, target)
print(x, func(x), target)
NMS
在目标检测中,常会利用非极大值抑制算法 (NMS) 对生成的大量候选框进行后处理 (post processing) ,去除冗余的候选框,得到最具代表性的结果,以加快目标检测的效率。一般是先得到生成的所有框,然后用一个参数 score_thr 筛除置信度太低的框,再将剩余框送入 nms 函数进行抑制,nms 一般有一个 iou_thr 参数,一般为 0.5。
NMS 算法的主要流程如下所示:
根据候选框的类别分类概率做排序:A<B<C<D<E<F
- 先标记最大概率矩形框 F 是我们要保留下来的;
- 从最大概率矩形框 F 开始,分别判断 A~E 与 F 的重叠度 IOU(两框的交并比)是否大于设定的 iou_thr ,假设 B、D 与 F 的重叠度超过阈值,那么就扔掉 B、D;
- 从剩下的矩形框 A、C、E 中,选择概率最大的 E,标记为要保留下来的,然后判读 E 与 A、C 的重叠度,扔掉重叠度超过设定阈值的矩形框
就这样一直重复下去,直到剩下的矩形框没有了,标记完所有要保留下来的矩形框
import numpy as np
def nms(dets, thresh):
x1, y1, x2, y2 = dets[:, 0], dets[:, 1], dets[:, 2], dets[:, 3]
scores = dets[:, 4]
regions = (x2 - x1 + 1) * (y2 - y1 + 1)
order = scores.argsort()[::-1]
keep = []
while order.size > 0:
i = order[0]
keep.append(i)
xA = np.maximum(x1[i], x1[order[1:]])
yA = np.maximum(y1[i], y1[order[1:]])
xB = np.minimum(x2[i], x2[order[1:]])
yB = np.minimum(y2[i], y2[order[1:]])
inter = np.maximum(0, xB - xA + 1) * np.maximum(0, yB - yA + 1)
iou = inter / (regions[order[1:]] + regions[i] - inter)
idx = np.where(iou <= thresh)[0]
order = order[idx:]
return keep
if __name__ == "__main__":
dets = np.array([[30, 20, 230, 200, 1],
[50, 50, 260, 220, 0.9],
[210, 30, 420, 5, 0.8],
[430, 280, 460, 360, 0.7]])
thresh = 0.5
keep_dets = nms(dets, thresh)
print(keep_dets)
print(dets[keep_dets])
SoftNMS
对NMS进行修改,对IoU高的框不直接进行删除,而是赋予一个较低的score
一般对二阶段的算法有用
IoU
def compute_iou(box1, box2):
boxA = [int(x) for x in box1]
boxB = [int(x) for x in box2]
x1 = max(boxA[0], boxB[0])
y1 = max(boxA[1], boxB[1])
x2 = min(boxA[2], boxB[2])
x3 = min(boxA[3], boxB[3])
inter = max(0, x2 - x1 + 1) * max(0, y2 - y1 + 1)
regionA = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
regionB = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
return inter / float(regionA + regionB - inter)
边缘检测算法
Sobel
图像中的边缘部分的像素值变化剧烈,属于是高频的信息,提取边缘本质上就是对图像进行求导,OpenCV 中的 Sobel 算子就近似这个过程。
因为噪声的区域的像素变化也很大,会对结果产生很大影响,所以我们首先要做的就是用高斯滤波将图像中的噪声过滤。
然后对图像进行 x 和 y 方向上的求导(运用垂直和水平的奇数大小卷积模版对图像进行卷积)。
得到图像在 x 和 y 方向上的梯度 Gx 和 Gy,然后得到总的梯度
G
=
G
x
2
+
G
y
2
G = \sqrt{G_x^2+G_y^2}
G=Gx2+Gy2
Laplacian
Laplacian算子是Sobel算子的改进版,对图像求二阶导数
Pooling Layer
手写 Pooling Layer
# 手撕以max pooling为例,如果是平均pooling,只需要把np.max改成np.mean
def max_pooling(inputs, pool_size, stride):
"""
最大池化操作
inputs: 输入数据,形状为 (C, H, W)
pool_size: 池化核的大小
stride: 步长
"""
C, H, W = inputs.shape
# 初始化输出数据
H_out = (H - pool_size) // stride + 1
W_out = (W - pool_size) // stride + 1
outputs = np.zeros((C, H_out, W_out))
# 进行最大池化操作
for i in range(H_out):
for j in range(W_out):
inputs_slice = inputs[:, i * stride : i * stride + pool_size,
j * stride : j * stride + pool_size]
outputs[:, i, j] = np.max(inputs_slice, axis=(1, 2))
return outputs
Pooling Layer 传播方式
Pooling Layer没有可以训练的参数,所以Pooling Layer只需要将误差传递到上一层,并不需要做梯度的计算,只需要保持梯度总量不变即可
- avgpooling,直接将梯度平均分配到对应的多个像素中,可以保证梯度之和是不变的。
- maxpooling,在正向传播时要记录最大值的index,反向传播时梯度值传递给index,其他像素的梯度为0
手写 BN
def batch_norm(inputs, gamma, beta, eps):
"""
批量归一化操作。N, C, H, W样本数可以看做N*H*W,CNN中,BN通常在每个通道上独立进行
inputs: 输入数据,形状为 (N, C, H, W)
gamma: 缩放因子,形状为 (C,)
beta: 偏移因子,形状为 (C,)
eps: 防止除0的小数值
"""
N, C, H, W = inputs.shape
# 在N、H和W的维度上计算每个通道的均值和方差
mean = np.mean(inputs, axis=(0, 2, 3), keepdims=True) # (1,C,1,1)
var = np.var(inputs, axis=(0, 2, 3), keepdims=True)
# 计算归一化的输入。eps防止除0
inputs_norm = (inputs - mean) / np.sqrt(var + eps)
# 缩放和偏移
outputs = gamma * inputs_norm + beta
return outputs
BN 层的作用
为了在网络的训练过程中也对数据进行归一化,方便网络的训练。
可以使用 BN,对一个 batch 中同一通道的数据集计算均值和方差
优点:
- 减少了对学习率的要求
- 破坏了原来数据的分布,一定程度上缓解了过拟合,防止每批训练中某个样本经常被挑选到
- 减少梯度消失,加快收敛速度,提高训练精度
BN 层参数的作用
对输入的特征进行缩放位移
BN在训练和测试时的区别
- 训练时针对每一个batch计算均值和方差
- 测试时,使用之前训练得到的均值和方差进行归一化,并使用相同的缩放和平移参数
BN 如何加速
推理时BN的参数固定,又因为卷积和BN都是线性操作,所以可以和在一起加速
BN,LN,IN 的区别
BN 是对一个 batch 中相同通道的数据计算均值和方差
LN 是对一个样本计算均值和方差
IN 是对一个样本中的一个通道计算均值和方差
Transformer 中是使用了 LN 的函数,使用的维度仅有最后一维度,实际上是 IN,是对单个词计算均值和方差
Bagging
使用不同的采样比例的数据集,训练多个子模型,最终预测时使用多个子模型的均值。
通过这个方式来降低方差,即把 n 个不稳定的模型放在一起,得到一个相对稳定的模型
Boosting
将多个较弱的模型组合在一起,得到更强的一个模型,来降低偏差 (GBDT,XGBoost)
- 按照时间顺序训练出模型 Ht
- 使用一个小模型 ft 拟合 Ht 上表现较差的样本
- 把 t 时刻的模型 Ht 和 t 时刻训练的小模型加起来作为下一时刻 boosting 的模型。并且利用一个学习率 yeta 来避免过拟合(不要轻易相信,不要轻易拟合
过拟合
过拟合是指训练误差和测试误差之间的差距太大。模型在训练集上表现很好,但在测试集上却表现很差,也就是泛化能力差。引起的原因有 ①训练数据集样本单一,样本不足 ②训练数据中噪声干扰过大 ③模型过于复杂。防止过拟合的几种方法:
- 增加更多的训练集或者数据增强(根本上解决问题)
- 采用更简单点的模型(奥卡姆剃刀准则,简单的就是正确的)
- 正则化(L1,L2)
- 可以减少不必要的特征或使用较少的特征组合
P范数
p范数: ∣ ∣ x ∣ ∣ p = ( ∣ x 1 ∣ p + ∣ x 2 ∣ p + … + ∣ x n ∣ p ) 1 / p ||x||_p = (|x_1|^p+|x_2|^p+…+|x_n|^p)^{1/p} ∣∣x∣∣p=(∣x1∣p+∣x2∣p+…+∣xn∣p)1/p,在最后的 loss 函数中加一个正则项,将模型的权重 W 作为参数传入范数中
0范数
参数中非0元素的个数
1范数
∣
∣
x
∣
∣
1
=
(
∣
x
1
∣
+
∣
x
2
∣
+
…
+
∣
x
n
∣
)
||x||_1 = (|x_1|+|x_2|+…+|x_n|)
∣∣x∣∣1=(∣x1∣+∣x2∣+…+∣xn∣)
一范数会鼓励部分参数为0,这样只保留了对目标变量有重要影响的参数,使得部分参数为0,实现了参数的稀疏化。
2范数
∣
∣
x
∣
∣
2
=
(
∣
x
1
∣
2
+
∣
x
2
∣
2
+
…
+
∣
x
n
∣
2
)
1
/
2
||x||_2 = (|x_1|^2+|x_2|^2+…+|x_n|^2)^{1/2}
∣∣x∣∣2=(∣x1∣2+∣x2∣2+…+∣xn∣2)1/2
二范数对参数的平方和进行监督,会鼓励参数整体偏向较小值,防止模型参数过大(参数过大时会对数据中的噪声和随机波动过度拟合,并不是学习到了一般化的规律),从而降低了模型对噪声的敏感性,避免模型过度拟合训练数据中的噪声点。
二范数同样可以使得模型稀疏化,令一部分特征为0,达到了筛选特征的目的,使得模型更易于解释。
欠拟合
欠拟合是指模型不能在训练集上获得足够低的误差。换句换说,就是模型复杂度低,模型在训练集上就表现很差,没法学习到数据背后的规律。防止欠拟合的几种方法:
- 采用更大的模型
- 采用更强的特征
训练出现NaN
有可能是除以 0 或者是出现了 log(0) 的情况
目标检测
两阶段检测模型
使用相应的Region Proposal算法从输入图片中生成建议目标候选区域,将所有的候选区域送入分类器进行分类。
R-CNN
contribution:
1.使用CNN进行基于区域的定位和物体分割
2.监督训练样本数紧缺时,在额外的输入上需训练的模型经过fine-tuning可以获得很好的效果。
Pipeline:
1.基于图片提出若干Region Proposal,文中使用的是Selective Serach算法。该算法将图片划分为多个patch,每个patch之间计算颜色相似性,纹理相似性,相近的patch进行合并。
2.训练N个SVM分类器来判断框是否属于这个类别,SVM是二分类的,输出True表示属于这个类别,输出False表示不属于这个类别
3.使用NMS去除相交的多余的框。NMS中会对每个类别的框分别进行类内的NMS,在这一类中,首先挑选置信度最大的框,然后把这个框和与这个框IoU过大的框都从候选框中删除,重复上一过程直到没有候选框。最后把置信度小于一定阈值的框删除,得到最后的结果。
4.最后预测框的偏移量,对这些框进行回归修正。
Fast R-CNN
将基础网络在图片整体上运行(共享了大部分计算)后,再传入R-CNN子网络。
共享卷积层 + ROI Pooling
ROI Pooling,将ROI通过pooling进行尺度上的归一化:
- 实现对任意大小物体的检测能力
- 速度快于ROI Align
Faster R-CNN
提出了RPN网络使得目标检测任务端到端地完成。
RPN的主要功能:
- 在特征图上生成区域候选框(可以看做是许多潜在的边界框,也叫 anchor,它是包含4个坐标的矩形框)
- 从生成的框中判断是否存在物体,如果存在就将proposal送给RCNN
- 最后使用NMS去除多余的框
Unsniffer
GOC
使用GOC得分抑制包围过大的框,部分包围的框,提升全包围的框
- 发现编码器对ID和OOD都进行了有效的编码,在特征图中这些都为高亮区域。意味着Encoder是可以同时发现ID和OOD的。
- OOD和ID在tSNE图(tsne是根据特征和groundtruth对特征向量进行降维,降到2维空间进行可视化,一般用来看不同类别样本的特征是否可分)中相近,这意味着许多OOD和ID有着相近的特征。
所以,希望通过设计GOC得分,利用ID的特征类推出哪个区域是OOD。 - GOC得分是啥?
- Loss的具体监督细节
Ncut
在得到OOD检测框后,为了处理多物体重叠的case,使用ncut对proposal进行聚类,并在类内使用NMS挑选框
负能量抑制
使用负能量抑制对非物体的框进行抑制
单阶段检测模型
YOLO
网络首先输出 SSchannel 的特征图,其中SS表示输出SS个网格。在此之后每个网格会预测B个框,每个框带有4个坐标值和1个置信度得分,再加上大小为C的类别信息,网络最后的输出为SS(5*B+C)
更快,但是性能不如2-stage的方法。使用全图的信息,把背景错认为物体的情况较少。泛化能力强
SSD (Single Shot multibox Detector)
YOLO是对每个patch进行检测,容易造成漏检测,并且对物体的尺度比较敏感。
改进:
1.使用多尺度的feature map,将vggnet多个尺度的特征值输入回归器中提升了对小物体的检测效率。对尺度不敏感,并且能检测不同大小的物体。
2.采用了更多的achor box,并且基于box对物体的类别进行了预测。
选择原因:
1.计算平台:
树莓派缺少GPU,因此首选tflite,而非GPU下的tensorRT,手机端的ncnn。tflite官方推荐的有ssd,yolov5,efficientnetv3,efficientnetv2
2.检测性能:
相同图片下,efficientnetv2无法检测到苹果,yolov5给出的框和置信度不高,ssd和efficientnetv3给出的框较好,且置信度较高。
距离较远的情况下,ssd(输入尺寸小)和efficientnetv3检测能力下降,yolo能勉强框住物体。
3.运行速度:
模型大小ssd,efficientnetv3,yolo为:1:2:3
FPS为:11:7:1
Anchor-free 和 Anchor-base
Anchor-free
Anchor-base
Anchor-free 和 Anchor-base 的区别
目标检测常用 Loss
语义分割
语义分割和像素级分类的区别
为什么像素级分类不使用语义分割做
语义分割常用 Loss
运动控制
PID
比例积分微分控制,p代表响应时间,i代表累计误差,d代表稳定性,参见 SLAM
使用marker绘制指定的路线,然后找到距离机器人最近的marker,输出速度
KCF(Kernel Correlation Filter)
- 将图像通过滤波器处理,把图像和滤波器的输出通过傅里叶变换,计算响应图,将响应图逆傅里叶变换到空域得到最大响应值的位置,作为目标估计的当前位置。
- 在持续跟踪视频中的检测框时,KCF算法会根据上一帧的目标位置,对下一帧图像进行搜索,找到与上一帧目标最相似的区域,然后更新目标位置。这个过程会持续进行,从而实现持续跟踪。
- 在跟踪过程中,可以根据 KCF 的检测框和 SSD 检测框的差距来决定是否使用 SSD 检测框对目标进行更新
单目深度估计
MRDE
归一化方式
- MiDaS DPT
pd和gt分别减去中值后除以MAS,然后两者之间求MAE
p d ′ = p d − m e d i a n ( p d ) ( 1 M ∑ ∣ p d − m e d i a n ( p d ) ∣ ) , g t ′ = g t − m e d i a n ( g t ) ( 1 M ∑ ∣ g t − m e d i a n ( g t ) ∣ ) pd' = \frac{pd - median(pd)}{(\frac{1}{M}\sum|pd - median(pd)|)}, gt' = \frac{gt - median(gt)}{(\frac{1}{M}\sum|gt - median(gt)|)} pd′=(M1∑∣pd−median(pd)∣)pd−median(pd),gt′=(M1∑∣gt−median(gt)∣)gt−median(gt)
L o s s s s i = ρ ⋅ M A E ( p d ′ − g t ′ ) Loss_{ssi} = \rho\cdot \mathrm{MAE}(pd' - gt') Lossssi=ρ⋅MAE(pd′−gt′) - LeReS
pd和gt分别减去均值和标准差之做MAE和Tanh的计算
p d ′ = p d − μ p d σ p d , g t ′ = g t − μ g t σ g t pd' = \frac{pd - \mu_{pd}}{\sigma _{pd}}, gt' = \frac{gt - \mu_{gt}}{\sigma _{gt}} pd′=σpdpd−μpd,gt′=σgtgt−μgt
L o s s i l n r = 1 M ∑ ( ∣ p d ′ − g t ′ ∣ + ∣ t a n h ( p d ′ / 100 ) ∣ − t a n h ( g t ′ / 100 ) ∣ ) Loss_{ilnr} = \frac{1}{M}\sum(|pd' - gt'| + |\mathrm{tanh}(pd'/100)|-\mathrm{tanh}(gt'/100)|) Lossilnr=M1∑(∣pd′−gt′∣+∣tanh(pd′/100)∣−tanh(gt′/100)∣)
轻量级单目深度估计
CVPR 2022 WorkShop
- TCL
- 类似于UNet的结构,将输入图片下采样到原图的1/4,然后传递给MobileNetV3
- 删除了MobileNetV3的最后一层,来提升网络的速度
- Encoder的输出被送到CLB中,最后x10缩放到原始的大小
- 使用silog作为损失函数
- HIT
- 多种Loss:silog,gradient,vnl
- 知识蒸馏
- 数据增强
- 设计合适的特征融合模块
或者
DANet
- EfficientNetb0
- 可分离卷积
- 降低深度分类个数
- 深度图直接x2作为最后输出
双目深度估计
视差计算和极线约束
- 在右侧图像钟暴力搜索方法不仅效率非常低,而且很容易由于各种因素(如弱纹理、重复纹理等)搜索到错误的对应点;
- 极线是极平面与两张图像的交线,极平面是物体与两个相机中心共同所在的平面,而极线约束描述的是物体在两张图像上的投影点一定在同一个极平面上,进而可以推导出,左图像上每个像素点在右图像上的同名点一定在该像点所在极平面与右图像的交线(即极线)上。
- 极线约束将原先二维的搜索空间缩小至一维的极线空间,大大的减小了搜索范围,是提高匹配效率的十分有效的约束。
视差转深度的公式推导
D e p t h = B a s e l i n e × F o c a l L e n g t h D i s p a r i t y × 1000 Depth = \frac{Baseline \times Focal Length}{Disparity\times 1000} Depth=Disparity×1000Baseline×FocalLength
轻量级平台配置
TFLite 配置流程
- 安装 TFLite 虚拟环境
- 安装 TFLite 解释器和下载 TFLite 模型