使用更少的比特来表示模型的权重和激活,例如将 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
INT8 = clip ( around ( FP32 / s + bias), qmin, qmax)
clip 对数进行截断
以12.918为例,计算12bit定点化和11bit定点化的量化误差,判断是不是无损量化:
在非对称量化中,信号的幅度被测量并相对于一个预定义的阈值进行比较。
非对称量化的优点是它可以更准确地检测和识别异常信号,同时减少误报率。
它还可以在处理大量数据时提供更高的效率,因为它只关注那些超出阈值的信号。
Z f = X x = Y y \frac{Z}{f}=\frac{X}{x}=\frac{Y}{y} fZ=xX=yY
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()
调整目标检测的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 会使得第一项越来越小,第二项越来越大
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 的导数分别为:
梯度消失是指在深层网络中,反向传播时梯度逐层递减,最终变得非常小,导致权重更新几乎没有效果。这通常发生在深层网络中,尤其是当使用一些激活函数(如 Sigmoid 或 Tanh)时,在链式求导的基础上,堆叠多层梯度后梯度值会非常小。
梯度爆炸是指在训练过程中,梯度逐层递增,导致权重更新变得非常大,从而产生不稳定的训练。这通常发生在很深的网络中,特别是当网络中存在大量参数时。主要原因是层之间的权重太大,导致反向传播时梯度指数级增长。
DataParallel会将定义的网络模型参数默认放在GPU 0上
这里GPU0作为master来进行梯度的汇总和模型的更新,再将计算任务下发给其他GPU,所以他的内存和使用率会比其他的高。
输入和输出的特征图尺寸大小关系:
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
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
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()
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()
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)
α 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))
1 V ∑ i ∣ ∣ n i − n i ∣ ∣ 1 \frac{1}{V}\sum_i{||n_i-\mathbf{n}_i||_1} V1∑i∣∣ni−ni∣∣1
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()
将左图的 RGB 值通过相机内参映射到相机坐标系下,然后通过坐标转换转移到右图的相机坐标系下,然后得到新的右侧 RGB 图。将两个右侧的 RGB 图求得 Loss(SSIM + L1)
使用光流将第 n 帧的深度值转换到第 n-1 帧下,然后计算通过光流得到的第 n-1 帧深度和预测得到的第 n-1 帧深度的误差。
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))
反向传播时对 ∂ 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
− ∑ 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))
用于解决样本不平衡的情况
− ∑ 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以概率p(通常取0.5)随机丢弃神经网络中的一些神经元及其连接权重。换句话说,每个神经元在每次前向传播过程中都有p的概率被“丢弃”,即将其输出设置为0。
对于每个训练样本,在前向传播过程中,以概率p随机选择要丢弃的神经元。被丢弃的神经元的输出设置为0,未被丢弃的神经元的输出乘以1/p的缩放因子,以保持输出值的期望不变。
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是一个二分类算法,在空间里找一个超平面,将空间分为两块,其中某一类的点都在超平面上方,另一类都在超平面下方。
see here
二分类算法,也可以用于多分类问题。
逻辑回归分为两部分:逻辑和回归。
线性回归模型里的因变量是连续变量,而逻辑回归里的因变量可以理解为分类变量,现在我们需要用回归模型去表示这个分类,比如说大或小,黑或白。那么考虑类别之间相对的关系,我们可以用概率来表示这个问题,比如说分类为黑的概率大于白的概率时,就把样本预测为黑。所以我们可以定制一个映射关系,将负无穷到正无穷之间的数值映射成概率值,即0-1之间,就可以解决线性回归到分类问题的过渡。
最常见的激活函数是Sigmoid函数。
线性回归模型中的结果是连续值(来表示相对关系),使用 MSE 等损失函数,适用于金融,趋势等领域;
逻辑回归是将线性回归的结果(通过 sigmoid)映射到0-1之间,输出概率值,使用 CrossEntropy Loss,用于分类问题
σ ( x ) = 1 1 + e − x \sigma(x)=\frac{1}{1+e^{-x}} σ(x)=1+e−x1
x = 1/(1 + np.exp(-1 * x))
根据伯努利公式的指数族形式
σ ′ ( x ) = ( 1 1 + e − x ) ′ = σ − σ 2 \sigma^\prime (x)=(\frac{1}{1+e^{-x}} )^\prime =\sigma -\sigma^2 σ′(x)=(1+e−x1)′=σ−σ2
输入乘以三个矩阵得到输入的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
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
多头注意力即将q,k,v拆分为头数个部分,并不会增加参数量,只是多了一个融合矩阵
点积操作会使得 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 分数的结果
BN是对不同batch的相同通道进行归一化
LN是对相同batch的相同patch的所有通道进行归一化(相同词的所有通道归一化,对图片特征展开后单个像素的所有通道归一化)
IN是对相同batch的单个通道进行归一化
如果直接计算9个方块上的attention,那么需要将周围的块填充至4x4才能进行并行计算,所以要将window向下移动后,重新计算MSA
AC下去,BA再去右边,对不连续的区域使用mask遮挡(将不连续区域的相似度置为0)后再计算attention.
SGD是随机梯度下降法,是最基本的优化器;
SGD 随机梯度下降
SGD + momenta:根据之前梯度和当前梯度更新本次梯度,解决SGD的震荡问题
SGD + netrosv:根据下一步的梯度更新本次梯度,解决SGD局限于局部极小值的问题
Adagrad:根据之前的梯度,动态更新学习率
RMSProp:根据历史梯度的平方更新本次梯度
Adam:结合了SGD的一阶动量(历史梯度的累计)和RMSProp的二阶动量(历史梯度的平方累计)
梯度的一阶动量,约等于近几个时刻梯度向量的平均值,这样来梯度的下降方向同时由当前的梯度方向和累计的下降方向决定,记为 m m m
梯度的二阶动量, 等于迄今为止所有梯度值的平方和,当参数更新越频繁,二阶动量就越大,学习率就会越小,记为 1 / V 1/\sqrt{V} 1/V
Adam 就是结合了这两项:
此处的 β 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
需要,只有在从样本中获得顺序特征时才不 shuffle
# 定义方程,使用 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) 对生成的大量候选框进行后处理 (post processing) ,去除冗余的候选框,得到最具代表性的结果,以加快目标检测的效率。一般是先得到生成的所有框,然后用一个参数 score_thr 筛除置信度太低的框,再将剩余框送入 nms 函数进行抑制,nms 一般有一个 iou_thr 参数,一般为 0.5。
NMS 算法的主要流程如下所示:
根据候选框的类别分类概率做排序:A
就这样一直重复下去,直到剩下的矩形框没有了,标记完所有要保留下来的矩形框
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])
对NMS进行修改,对IoU高的框不直接进行删除,而是赋予一个较低的score
一般对二阶段的算法有用
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)
图像中的边缘部分的像素值变化剧烈,属于是高频的信息,提取边缘本质上就是对图像进行求导,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算子是Sobel算子的改进版,对图像求二阶导数
# 手撕以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只需要将误差传递到上一层,并不需要做梯度的计算,只需要保持梯度总量不变即可
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的参数固定,又因为卷积和BN都是线性操作,所以可以和在一起加速
过拟合是指训练误差和测试误差之间的差距太大。模型在训练集上表现很好,但在测试集上却表现很差,也就是泛化能力差。引起的原因有 ①训练数据集样本单一,样本不足 ②训练数据中噪声干扰过大 ③模型过于复杂。防止过拟合的几种方法:
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元素的个数
∣ ∣ x ∣ ∣ 1 = ( ∣ x 1 ∣ + ∣ x 2 ∣ + … + ∣ x n ∣ ) ||x||_1 = (|x_1|+|x_2|+…+|x_n|) ∣∣x∣∣1=(∣x1∣+∣x2∣+…+∣xn∣)
一范数会鼓励部分参数为0,这样只保留了对目标变量有重要影响的参数,使得部分参数为0,实现了参数的稀疏化。
∣ ∣ 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,达到了筛选特征的目的,使得模型更易于解释。
欠拟合是指模型不能在训练集上获得足够低的误差。换句换说,就是模型复杂度低,模型在训练集上就表现很差,没法学习到数据背后的规律。防止欠拟合的几种方法:
有可能是除以 0 或者是出现了 log(0) 的情况
使用相应的Region Proposal算法从输入图片中生成建议目标候选区域,将所有的候选区域送入分类器进行分类。
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.最后预测框的偏移量,对这些框进行回归修正。
将基础网络在图片整体上运行(共享了大部分计算)后,再传入R-CNN子网络。
共享卷积层 + ROI Pooling
ROI Pooling,将ROI通过pooling进行尺度上的归一化:
提出了RPN网络使得目标检测任务端到端地完成。
RPN的主要功能:
使用GOC得分抑制包围过大的框,部分包围的框,提升全包围的框
在得到OOD检测框后,为了处理多物体重叠的case,使用ncut对proposal进行聚类,并在类内使用NMS挑选框
使用负能量抑制对非物体的框进行抑制
网络首先输出 SSchannel 的特征图,其中SS表示输出SS个网格。在此之后每个网格会预测B个框,每个框带有4个坐标值和1个置信度得分,再加上大小为C的类别信息,网络最后的输出为SS(5*B+C)
更快,但是性能不如2-stage的方法。使用全图的信息,把背景错认为物体的情况较少。泛化能力强
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
比例积分微分控制,p代表响应时间,i代表累计误差,d代表稳定性,参见 SLAM
使用marker绘制指定的路线,然后找到距离机器人最近的marker,输出速度
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