CV深度学习面试问题记录

2022-09-02 12:09:49 浏览数 (1)

preface

这是在牛客网上根据大家的面经收集过来的题目,并以自己的理解来作出回答,也查阅了很多博客和资料。水平有限,不一定是正确的,欢迎指正,铁子们要找工作的时候可以看看

图像细粒度分类是什么

不同于普通的分类任务,图像细粒度分类是指的对同一个大类别进行更加细致的分类,例如哈士奇与柯基。

RPN是怎么做的

有空补上

过拟合欠拟合是啥,怎么解决?

过拟合

过拟合是指训练误差和测试误差之间的差距太大。模型在训练集上表现很好,但在测试集上却表现很差,也就是泛化能力差。引起的原因有 ①训练数据集样本单一,样本不足 ②训练数据中噪声干扰过大 ③模型过于复杂。

防止过拟合的几种方法:

  1. 增加更多的训练集或者数据增强(根本上解决问题)
  2. 采用更简单点的模型(奥卡姆剃刀准则,简单的就是正确的)
  3. 正则化(L1,L2,Dropout)
  4. 可以减少不必要的特征或使用较少的特征组合

欠拟合

欠拟合是指模型不能在训练集上获得足够低的误差。换句换说,就是模型复杂度低,模型在训练集上就表现很差,没法学习到数据背后的规律。

欠拟合基本上都会发生在训练刚开始的时候,经过不断训练之后欠拟合应该不怎么考虑了。欠拟合可以通过使用非线性模型来改善,也就是使用一个更强的模型,并且可以增加训练特征

ResNet为啥能够解决梯度消失?怎么做的,能推导吗?

  • 输入是x;
  • F(x)相当于residual,它只是普通神经网络的正向传播;
  • 输出是这两部分的加和H(x) = F(x)(就是residual) x(就是shortcut,此代码shortcut部分做了一次卷积,也可以不做);

之所以可以避免梯度消失问题,是因为反向传播时,ε 代表的是 loss 方程,由链式求导法得:

可以看出,反向传播的梯度由2项组成的:

  • 对x的直接映射,梯度为1;
  • 通过多层普通神经网络映射结果为:

即使新增的多层神经网络的梯度

为0,那么输出结果也不会比传播前的x更差。同时也避免了梯度消失问题。

cxyzjd.com/article/qq_32172681/100177636

深度学习里面PCA的作用

PCA 用于降低维度,消除数据的冗余,减少一些不必要的特征。常用步骤如下(设有 m 条 n 维数据):

reference: 王兴政老师的PPT https://blog.csdn.net/Virtual_Func/article/details/51273985

YOLOv3的框是怎么聚出来的?

YOLOv3 的框是作者通过对自己的训练集的 bbox 聚类聚出来的,本质上就是一个 kmeans 算法,原理如下:

  1. 随机选取 k 个类别的中心点,代表各个聚类
  2. 计算所有样本到这几个中心点的距离,将每个样本归为距离最小的类别
  3. 更新每个类别的中心点(计算均值)
  4. 重新进行步骤 2 ,直到类的中心点不再改变

YOLOv3 的 kmeans 想法不能直接用 w 和 h 来作为聚类计算距离的标准,因为 bbox 有大有小,不应该让 bbox 的尺寸影响聚类结果,因此,YOLOv3 的聚类使用了 IOU 思想来作为距离的度量 (d(bbox, centroid) = 1 - IOU(bbox, centroid)) 也就是说 IOU 越大的话,表示 bbox 和 box_cluster 就越类似,于是将 bbox 划归为 box_cluster

在 AlexeyAB 大佬改进的 darknet 中实现了这个过程,具体就是加载所有训练集中的 bbox 的 w 和 h,随机选择 k 个作为 centroids,然后用所有样本去和这 k 个 centroids 做 IOU 得出距离,并将样本归为距离最小的 cluster,然后更新 centroids 重复上面的步骤,直到 centroids 不再更新

代码语言:javascript复制
import numpy as np
def kmeans(boxes, k, dist=np.median):
    """
    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 k: number of clusters
    :param dist: distance function
    :return: numpy array of shape (k, 2)
    """
    rows = boxes.shape[0]

    distances = np.empty((rows, k))
    last_clusters = np.zeros((rows,))

    np.random.seed()

    # the Forgy method will fail if the whole array contains the same rows
    # 随机 sample k 个 box 作为聚类中心
    clusters = boxes[np.random.choice(rows, k, replace=False)]

    while True:
        for row in range(rows):
          	# 计算每一个 box 到聚类中心的距离(用 IOU 衡量距离)
            distances[row] = 1 - iou(boxes[row], clusters)
				# 选一个最近的聚类中心
        nearest_clusters = np.argmin(distances, axis=1)
				# 聚类中心不变的话就结束聚类
        if (last_clusters == nearest_clusters).all():
            break

        for cluster in range(k):
          	# 根据聚类结果更新聚类中心
            clusters[cluster] = np.median(boxes[nearest_clusters == cluster], axis=0)

        last_clusters = nearest_clusters

    return clusters

reference: https://github.com/AlexeyAB/darknet/blob/master/scripts/gen_anchors.py https://www.cnblogs.com/sdu20112013/p/10937717.html

YOLO和SSD的本质区别?

R-CNN系列和SSD本质有啥不一样吗?

SSD的致命缺点?如何改进

需要人工设置 prior box 的 min_size,max_size 和 aspect_ratio 值。网络中prior box的基础大小和形状不能直接通过学习获得,而是需要手工设置。而网络中每一层 feature 使用的 prior box 大小和形状恰好都不一样,导致调试过程非常依赖经验。 虽然采用了 pyramdial feature hierarchy 的思路,但是对小目标的 recall 依然一般,并没有达到碾压 Faster RCNN 的级别。作者认为,这是由于 SSD 使用 conv4_3 低级 feature 去检测小目标,而低级特征卷积层数少,存在特征提取不充分的问题。Kevin 补充:没错,SSD 的 anchor 设置导致 anchor 和小目标的 IoU 依然很小,大概只有 0.2 左右所以会漏掉很多小物体。 ———————————————— 版权声明:本文为CSDN博主「一个新新的小白」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_31511955/article/details/80597211

SSD 的先验框和 Gt 框是怎么匹配的

在训练时 首先需要确定训练图片中的 ground truth 是由哪一个先验框来匹配,与之匹配的先验框所对应的边 界框将负责预测它。SSD 的先验框和 ground truth 匹配原则主要有 2 点。第一点是对于图片中的每个 ground truth,找到和它 IOU 最大的先验框,该先验框与其匹配,这样可以保证每个 ground truth 一 定与某个 prior 匹配。第二点是对于剩余的未匹配的先验框,若某个 ground truth 和它的 IOU 大于某 个阈值 (一般设为 0.5),那么这个先验框匹配这个 ground truth,剩下没有匹配上的先验框都是负样本(如果多个 ground truth 和某一个先验框的 IOU 均大于阈值,那么先验框只与 IOU 最大的那个进行匹配)。

怎么解决梯度消失和梯度弥散

YOLOv1到YOLOv3的发展历程以及解决的问题

FCOS 存在什么问题,如何解决

FCOS 的改进 trick - 极市社区 (cvmart.net)

为什么分类要用交叉熵损失函数

说一下RCNN发展史

RCNN -> Fast RCNN -> Faster RCNN

#TODO

Faster RCNN

以经典的Faster R-CNN为例。整个网络可以分为两个阶段,training阶段和inference阶段 training 阶段,RPN 网络提出了 2000 左右的 proposals,这些 proposals 被送入到 Fast R-CNN 结构中,在 Fast R-CNN 结构中,首先计算每个 proposal 和 gt 之间的 iou,通过人为的设定一个 IoU 阈值(通常为 0.5),把这些 Proposals 分为正样本(前景)和负样本(背景),并对这些正负样本采样,使得他们之间的比例尽量满足(1:3,二者总数量通常为 128),之后这些 proposals(128 个)被送入到 Roi Pooling,最后进行类别分类和 box 回归。inference 阶段,RPN 网络提出了 300 左右的 proposals,这些 proposals 被送入到 Fast R-CNN 结构中,和 training 阶段不同的是,inference 阶段没有办法对这些 proposals 采样(inference 阶段肯定不知道 gt 的,也就没法计算 iou),所以他们直接进入 Roi Pooling,之后进行类别分类和 box 回归

NMS 的缺点以及改进工作

各种边缘检测算法的原理 (Sobel, Canny, Laplacian)

Sobel 算子

Laplacian 算子

laplacian 算子在 Sobel 的基础上进行改进,Sobel 那会儿已知图像的边缘部分是邻域梯度最大的部分,那么再对这个求一次导数呢,那么边缘部分的二阶导数就是 0 了。真不错,因此 laplacian 算子的原理就是对图像求二阶导数,不过预处理部分和 Sobel 差不多,也是要先转化成灰度图,再高斯滤波去除噪声,再进行求导操作。

laplacian 算子运用的卷积核是如下数值的,因为还是求导数,所以内部调用的是 Sobel 函数。 (begin{bmatrix} 0 & 1 & 0 \ 1 & -4 & 1 \ 0 & 1 & 0 end{bmatrix})

Canny 算子

Canny 算子是一种性能更好的算子,原理分为几步:

  1. 预处理(高斯去噪,灰度化)
  2. 对图像进行差分求导(Sobel)得到梯度方向和角度
  3. 进行极大值抑制去除真正的边缘周围的杂质得到比较好的边缘
    1. 这一步的目的是将模糊(blurred)的边界变得清晰(sharp)。通俗的讲,就是保留了每个像素点上梯度强度的极大值,而删掉其他的值。对于每个像素点,进行如下操作:     a) 将其梯度方向近似为以下值中的一个(0,45,90,135,180,225,270,315)(即上下左右和 45 度方向)     b) 比较该像素点,和其梯度方向正负方向的像素点的梯度强度     c) 如果该像素点梯度强度最大则保留,否则抑制(删除,即置为 0)
  4. 双阈值筛选更精细的边缘

边缘 A 在 maxVal 之上,因此被认为是 “sure-edge”。尽管 C 边低于 maxVal 值,但它与 A 边相连,因此这条边也被认为是有效的我们得到了这条完整的曲线。但是 B 边,尽管它在 minVal以 上并且和 C 边在同一区域,但是它没有连接到任何 “sure-edge”,所以它被丢弃了。因此,为了得到正确的结果,我们必须相应地选择 minVal 和 maxVal,这是非常重要的。

关于相机标定以及姿态估计

相机内外参是什么意思

由于小孔成像原理,我们将现实生活中的一个点 P 投影到成像平面上 (中心为零点),成像点的坐标和 P 点真实坐标存在一个关系,和相机焦距有关,然后映射到成像平面还不够,我们还要将这个像给映射到像素坐标系 (u, v, 左上角为零点) 中,这个映射和像素坐标和 u 轴 v 轴的缩放系数以及原点平移的距离有关。将这些关系转化成矩阵可以得到下面的式子,

其中的 K 就是相机的内参,包含了焦距以及像素坐标系到相机坐标系 x 和 y 方向上的平移距离 (一般是分辨率的 1/2),这个用 MATLAB 对相机标定就可以获得,一般出厂之后就是固定的了,另外,相机标定除了内参矩阵以外还可以获得一个畸变矩阵,用来矫正相机的畸变。

外参指的是将一个点从世界坐标系 (三维)转变到相机坐标系 (二维) 所需要的旋转矩阵和平移矩阵,一般称为 R 和 t,有如下形式 (begin{align*} begin{bmatrix} X_c \ Y_c \ Z_c \ 1 end{bmatrix} &= hspace{0.2em} ^{c}bf{T}_w begin{bmatrix} X_{w} \ Y_{w} \ Z_{w} \ 1 end{bmatrix} \ begin{bmatrix} X_c \ Y_c \ Z_c \ 1 end{bmatrix} &= begin{bmatrix} r_{11} & r_{12} & r_{13} & t_x \ r_{21} & r_{22} & r_{23} & t_y \ r_{31} & r_{32} & r_{33} & t_z \ 0 & 0 & 0 & 1 end{bmatrix} begin{bmatrix} X_{w} \ Y_{w} \ Z_{w} \ 1 end{bmatrix} end{align*})

pnp 问题怎么解

https://docs.opencv.org/3.4/d5/d1f/calib3d_solvePnP.html https://zhuanlan.zhihu.com/p/389653208

边框回归怎么做的,为什么需要 Encode

Most recently object detection programs have the concept of anchor boxes, also called prior boxes, which are pre-defined fix-sized bounding boxes on image input or feature map. The bounding box regressor, instead of predicting the bounding box location on the image, predicts the offset of the ground-truth/predicted bounding box to the anchor box. For example, if the anchor box representation is [0.2, 0.5, 0.1, 0.2], and the representation of the ground-truth box corresponding to the anchor box is [0.25, 0.55, 0.08, 0.25], the prediction target, which is the offset, should be [0.05, 0.05, -0.02, 0.05]. The object detection bounding box regressor is trying to learn how to predict this offset. If you have the prediction and the corresponding anchor box representation, you could easily calculate back to predicted bounding box representation. This step is also called decoding.

https://leimao.github.io/blog/Bounding-Box-Encoding-Decoding/

https://blog.csdn.net/zijin0802034/article/details/77685438

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

有空写一下!!!!

gt 在做 loss 之前就已经和 Anchor 经过了 encode ,也就是从 Anchor 到 gt 所要经过的线性变换 (Gx, Gy, Gw, Gh),回归网络的输出就是这四个变换,也就是说网络在学习这种映射关系。所以网络的输出并不是真正的坐标,得到坐标只需要根据 encode 得到 decode 函数做一下解码就行了。

而且也不是所有的 Anchor 都参与了回归损失(拿 SSD 中的 8732 个 Anchor 和 smooth L1 来说),最终可以计算得到一个 pos_inx 代表正样本的索引,根据这个索引在 gt 和 Anchor 中找到对应的元素拿来做回归损失,因为负样本不用回归。

常见的Loss,回归的,分类的,Focal Loss

Sigmoid

[sigma=frac{1}{1 e^-x}]

值域为(0,1)

Focal Loss

变形的交叉熵 (CE = -alpha(1-y_{pred})^gamma log(y_{pred}))

代码语言:javascript复制
def FocalLoss(self, logit, target, gamma=2, alpha=0.5):
    n, c, h, w = logit.size()
    criterion = nn.CrossEntropyLoss(weight=self.weight, ignore_index=self.ignore_index,
                                    size_average=self.size_average)
    if self.cuda:
        criterion = criterion.cuda()

        logpt = -criterion(logit, target.long())
        pt = torch.exp(logpt)
        if alpha is not None:
            logpt *= alpha
            loss = -((1 - pt) ** gamma) * logpt

            if self.batch_average:
                loss /= n

                return loss

reference: https://www.cnblogs.com/king-lps/p/9497836.html FocalLoss 对样本不平衡的权重调节和减低损失值 - 知乎 (zhihu.com) 这个和 CE BCE 进行对比,比较清晰,但是这两个都是基于 softmax 的 FocalLoss,mmdet 中默认是基于 sigmoid 的 BCE loss 作为原型进行 FocalLoss 的。

softmax

常用于分类问题,在最后一层全连接之后用一个 softmax 将各神经元输出归一化到 0-1 之间,但总体相加的值还是等于 1

[sigma(Z)_j = frac{e^{z_j}}{sum^K_{k=1}e{z_k}}]

代码语言:javascript复制
import numpy as np
z = np.array([1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 3.0])
print(np.exp(z)/sum(np.exp(z)))
>>> [0.02364054 0.06426166 0.1746813 0.474833 0.02364054 0.06426166 0.1746813 ]

BCELoss

二分类常用的 loss,Binary CrossEntropyLoss,注意,在使用这个之前要使用 sigmoid 函数将预测值放到(0,1)之间

[L = -frac{1}{n}(ylog(y_{pred}) (1-y)log(1-y_{pred}))]

BCEWithLogitsLoss

这是集成了 sigmoid 和 BCELoss 的损失函数,一步到位

CrossEntropyLoss

[CE = -frac{1}{n}ylog(y_{pred})]

其中,y_{pred} 又是网络输出通过一个 softmax 得到的值,也就是说函数会自动给他加一个 softmax 函数,所以最终公式就是

[CE = -frac{1}{n}ylog(frac{e^{y_c}}{sum_je^{y_j}})]

也就是 NLLLoss 和 LogSoftmax 两个函数的组合

代码语言:javascript复制
>>> i = torch.randn(3,3)
>>> i
tensor([[-1.8954, -0.3460, -0.6694],
        [ 0.5421, -1.1799,  0.8239],
        [-0.0611, -0.8800,  0.8310]])
>>> label = torch.tensor([[0,0,1],[0,1,0],[0,0,1]])
>>> label
tensor([[0, 0, 1],
        [0, 1, 0],
        [0, 0, 1]])
>>> sm = nn.Softmax(dim=1)
>>> sm(i)
tensor([[0.1097, 0.5165, 0.3738],
        [0.3993, 0.0714, 0.5293],
        [0.2577, 0.1136, 0.6287]])
>>> torch.log(sm(i))
tensor([[-2.2101, -0.6607, -0.9840],
        [-0.9180, -2.6400, -0.6362],
        [-1.3561, -2.1751, -0.4640]])
>>> ll = nn.NLLLoss()
>>> target = torch.tensor([2,1,2])
>>> ll(torch.log(sm(i)), target)
tensor(1.3627)

>>> i
tensor([[-1.8954, -0.3460, -0.6694],
        [ 0.5421, -1.1799,  0.8239],
        [-0.0611, -0.8800,  0.8310]])
>>> los = nn.CrossEntropyLoss()
>>> los(i, target)
tensor(1.3627)

L1、L2范数是什么,区别呢?

范数是具有 “长度” 概念的函数。在向量空间内,为所有的向量的赋予非零的增长度或者大小。不同的范数,所求的向量的长度或者大小是不同的。举个例子,2 维空间中,向量 (3,4) 的长度是 5,那么 5 就是这个向量的一个范数的值,更确切的说,是欧式范数或者L2范数的值。

x

_p = (

x_1

^p

x_2

^p …

x_n

^p)^{1/p}

end{equation} (那么 L1 范数就是 p 为 1 的时候,也就是向量内所有元素的绝对值之和:) begin{equation}

x

_p = (

x_1

x_2

x_n

)

end{equation} (L2 范数就是向量内所有元素的平方相加然后再开方:) begin{equation}

x

_p = (

x_1

^2

x_2

^2 …

x_n

^2)^{1/2}

end{equation} $$ 特别的,L0 范数是指向量中非零元素的个数!


在深度学习中,常常会在最后的 loss 函数中加一个正则项,将模型的权重 W 作为参数传入范数中,为的就是防止模型参数过大,这样可以防止过拟合发生。

reference: https://zhuanlan.zhihu.com/p/28023308 https://www.cnblogs.com/LXP-Never/p/10918704.html(解释得挺好的,借鉴了下面这篇文章) http://www.chioka.in/differences-between-l1-and-l2-as-loss-function-and-regularization/# https://wangjie-users.github.io/2018/11/28/深入理解L1、L2范数/

pooling layer怎么反向传播

池化层没有可以训练的参数,因此在卷积神经网络的训练中,池化层只需要将误差传递到上一层,并不需要做梯度的计算。要追求一个原则,那就是梯度之和不变。

  • average pooling

直接将梯度平均分到对应的多个像素中,可以保证梯度之和是不变的

  • max pooling

max pooling要记录下最大值像素的index,然后在反向传播时将梯度传递给值最大的一个像素,其他像素不接受梯度,也就是为0

reference: https://blog.csdn.net/qq_21190081/article/details/72871704

MobileNet在特征提取上有什么不同

MobileNet 用的是深度可分离卷积(Depthwise separable convolution),将传统的卷积分成了 Depthwise convolution 和 Pointwise convolution,也就是先对每个通道进行单独的卷积,不把这些特征组合在一起,然后用 Pointwise convolution 也就是一个 1* 1 卷积核将通道特征组合在一起,这样可以大大减少模型的参数量以及运算量,适合运用于嵌入式设备等对延时和性能要求较高的场景中。

图 (a) 代表标准卷积。假设输入特征图尺寸为

,卷积核尺寸为

,输出特征图尺寸为

,标准卷积层的参数量为:

。用了深度可分离卷积所需要的参数量和普通卷积的参数量如下,在 MobileNet 中,卷积核的尺寸

DK

DK为 3 * 3,因此这样的参数量相当于减少了 9 倍

Depthwise convolution 的实现是通过 nn.Conv2d 中的 groups 参数,这个数字要能被 in_channels 和 out_channels 整除,一般来说,Depthwise convolution 中的第一个 Group convolution 的输入输出通道都是一样的,pointwise convolution 才是起到改变维度的作用。下面例子中通过通道可分离卷积保存的模型比标准卷积更小 (47KB VS 362KB)

代码语言:javascript复制
import torch.nn as nn

class Normal(nn.Module):
    def __init__(self, i, o, k):
        super(Normal, self).__init__()
        self.conv = nn.Conv2d(i, o, k, padding=k // 2)
    def forward(self, x):
        x = self.conv(x)
        return x

class DepwiseSeparable(nn.Module):
    def __init__(self, i, o, k):
        super(DepwiseSeparable, self).__init__()
        self.conv = nn.Sequential(
                        nn.Conv2d(i, i, k, groups=i, padding=k // 2),
                        nn.Conv2d(i, o, 1)
                    )
    def forward(self, x):
        x = self.conv(x)
        return x

normal = Normal(100, 100, 3)
dep = DepwiseSeparable(100, 100, 3)
torch.save(normal, 'normal.pth')
torch.save(dep, 'dep.pth')

我们可以通过另外一个实验说明深度可分离卷积的优越性

代码语言:javascript复制
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=1)
conv.weight.data.size()
-> torch.Size([6, 6, 1, 1])
conv = nn.Conv2d(in_channels=6, out_channels=3, kernel_size=1, groups=1)
conv.weight.data.size()
-> torch.Size([3, 6, 1, 1])

说明 conv.weights.data.size() 的内容为 (out_channels, in_channels, kernel_size, kernel_size),我们设置不同的 groups 看看结果

代码语言:javascript复制
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=2)
conv.weight.data.size()
-> torch.Size([6, 3, 1, 1])
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=3)
conv.weight.data.size()
-> torch.Size([6, 2, 1, 1])
conv = nn.Conv2d(in_channels=6, out_channels=6, kernel_size=1, groups=6)
conv.weight.data.size()
-> torch.Size([6, 1, 1, 1])

所以当 group=1 时,该卷积层需要 6*6*1*1=36 个参数,即需要 6 个 6*1*1 的卷积核。计算时就是 6*H_in*W_in 的输入整个乘以一个 6*1*1 的卷积核,得到输出的一个 channel 的值,即 1*H_out*W_out。这样经过 6 次与 6 个卷积核计算就能够得到 6*H_out*W_out 的结果了。

如果将 group=3 时,卷积核大小为 torch.Size([6, 2, 1, 1]),即 6 个 2*1*1 的卷积核,只需要 6*2*1*1=12 个参数。那么每组计算就只被 in_channels/groups=2 个 channels 的卷积核计算,其实就相当于是将 6*H_in*W_in 大小的输入按 channels 分成 3 分,每份大小为 2*H_in*W_in,6 个卷积核也会对应分成 3 份,即 2*1*1,每份输入对应每份卷积核。所以将每份输入 2*H_in*W_in 看成一个输入,应用 2 个 2*1*1 的卷积核,就能够得到 2*H_out*W_out。一份得到一个 2*H_out*W_out 的输出,那么 3 份就能够得到 3 个 2*H_out*W_out 的输出,在 channels 维 concat 起来得到最后的 6*H_out*W_out 输出。

pytorch的函数中的group参数的作用 - 慢行厚积 - 博客园 (cnblogs.com)

为什么用smooth L1 loss,和L1 loss、L2 loss有啥区别?

关于数据集的一些碎碎的知识点

COCO

coco2017 有 118287 张训练集,5000 张验证集,40670 张测试集

Pascal VOC

VOC 语义分割时,之前一直不知道 gt 和 output 是怎么做 loss 的,记录一下。一共有 20 个类,gt_mask 是一张 8 位的 png 彩图,虽然是 8 位,但是他其实有 RGB 值,用 ps 打开的话可以直接看到各个像素的 RGB 值,这时你会怀疑这是个三通道的彩图,其实不是,用 PIL 打开 mask 的时候,打印 shape 会发现他就是单通道的图,将其像素值打印出来又会发现,大多数都是 0,而且基本不会超过 20,但是有些会到 255,这是因为,看下图好了,白色的区域是不计算 loss 的,在损失函数里面表现为 ignore_index=255,然后黑色部分像素为 0,代表背景,所以网络最后的输出其实有 21 个通道。其余对应的颜色的像素值就代表的这个类别的 label,之前我还一直在想他到底是怎么将颜色值转化成 label 的,原来 PIL 读进来就直接是 label 了,我裂开

reference: https://blog.csdn.net/weixin_38437404/article/details/78788250

目标检测中有些代码在训练时会将模型的 BN 层给冻结,这是为什么?

目标检测中一般会用到在 ImageNet 上预训练好的分类器权重,别人的 batch_size 很大,所以对 BN 层应该训练出了一个比较不错的参数(γ 和 β),而目标检测的 batch_size 可能没有那么大,可能 minibatch 不足以反映出样本整体的分布,训练出来的参数不一定有别人好,所以冻结住 BN 说不定会使模型表现更好

代码语言:javascript复制
    def freeze_bn(self):
        '''Freeze BatchNorm layers.'''
        for layer in self.modules():
            if isinstance(layer, nn.BatchNorm2d):
                layer.eval()

It’s a common practice to freeze BN while training object detection models. This is done because you usually start from some Imagenet pre-trained weights which were trained with large BS (like 256 or larger) while in object detection BS is much smaller. You don’t want to ruin good pretrained statistics so freezing BN is a good idea https://github.com/yhenon/pytorch-retinanet/issues/151

各种卷积

分组卷积

分组卷积最早在 AlexNet 中出现,当时作者在训练模型时为了减少显存占用而将 feature map 分组然后给多个 GPU 进行处理,最后把多个输出进行融合。具体计算过程是,分组卷积首先将输入 feature map 分成 g 个组,每个组的大小为 (1, iC/g, iH, iW),对应每组中一个卷积核的大小是 (1,iC/g,k,k),每组有 oC/g 个卷积核,所以每组输出 feature map 的尺寸为 (1,oC/g,oH,oW),最终 g 组输出拼接得到一个 (1,oC,oH,oW) 的大 feature map,总的计算量为 iC/g×k×k×oC×oH×oW,是标准卷积的 1/g,参数量也是标准卷积的 1/g。

但由于 feature map 组与组之间相互独立,存在信息的阻隔,所以 ShuffleNet 提出对输出的 feature map 做一次 channel shuffle 的操作,即通道混洗,打乱原先的顺序,使得各个组之间的信息能够交互起来

空洞卷积

空洞卷积是针对图像语义分割问题中下采样会降低图像分辨率、丢失信息而提出的一种卷积思路。通过间隔取值扩大感受野,让原本 3x3 的卷积核,在相同参数量和计算量下拥有更大的感受野。这里面有个扩张率 (dilation rate) 的系数,这个系数定义了这个间隔的大小,标准卷积相当于 dilation rate 为 1 的空洞卷积,下图展示的是 dilation rate 为 2 的空洞卷积计算过程,可以看出3×3的卷积核可以感知标准的 5×5 卷积核的范围,还有一种理解思路就是先对 3×3 的卷积核间隔补 0,使它变成 5×5 的卷积,然后再执行标准卷积的操作。

reference: https://mp.weixin.qq.com/s/LO1W2saWslf6Ybw_MZAuQQ

转置卷积

转置卷积又称反卷积 (Deconvolution),它和空洞卷积的思路正好相反,是为上采样而生,也应用于语义分割当中,而且他的计算也和空洞卷积正好相反,先对输入的 feature map 间隔补 0,卷积核不变,然后使用标准的卷积进行计算,得到更大尺寸的 feature map。

可变形卷积

以上的卷积计算都是固定的,每次输入不同的图像数据,卷积计算的位置都是完全固定不变,即使是空洞卷积/转置卷积,0 填充的位置也都是事先确定的。而可变形卷积是指卷积核上对每一个元素额外增加了一个 h 和 w 方向上偏移的参数,然后根据这个偏移在 feature map 上动态取点来进行卷积计算,这样卷积核就能在训练过程中扩展到很大的范围。而显而易见的是可变形卷积虽然比其他卷积方式更加灵活,可以根据每张输入图片感知不同位置的信息,类似于注意力,从而达到更好的效果,但是它比可变形卷积在增加了很多计算量和实现难度,目前感觉只在 GPU 上优化的很好,在其他平台上还没有见到部署。

loss 出现 NaN 怎么解决

NaN 可以看成是无穷大或者无穷小,经常是由于在网络计算 loss 的时候除以 0 或者 log(0) 导致的,如果是这种原因的话就得检查代码是否正确,一般在 loss 除以正样本的时候会加个 eps=0.0001 来防止出现 NaN,但如果代码没有问题的话可能就是梯度爆炸了,变得很大,这时候要么就做个梯度裁剪(梯度大于某个值就不要了),或者降低学习率让网络每一步更新的梯度不要太夸张

matting 跟 segmentation 有什么区别

segmentation 就是对图片中的每一个像素进行分类,判断其所属概率最大的类别。抠图比分割更加严格, 在抠图中有一个公式 I = αF (1-α)B。我们需要是把 α(不透明度)、F(前景色)和B(背景色)三个变量给解出来。和其它图片进行融合的时候,就会用到 α(不透明度),它可以使得融合效果特别自然,就像 PS 的羽化边缘。

feature map 上的像素怎么映射到原图的

根据感受野,例如通过一层层卷积最终被 stride 16 的 feature map,该层的感受野是 31,因此映射回原图的话相当于一个像素点对应原图的 31x31 的区域,能够看到更广的区域,并不是单纯的对应 16x16 的区域。

↓↓↓↓↓以下为收集到的面试题↓↓↓↓↓

mAP 计算公式

mmdetection 中,将结果送去计算 mAP 之前,先调用了 bbox_head 的 get_bbox,这个函数默认用了 test_cfg 中的 nms_cfg,也就是会先将得到的 bbox 进行一个类间的 nms 筛除掉一些重复的框再送入 evaluator

通用流程为以下:

  1. 送去计算 mAP 之前先经过 nms 将 box 给筛一遍(optional)
  2. 再将整个数据集的 box 按照 confidence 从高到低进行排序
  3. 对每个类别计算 AP:
    1. 取预测为该类别的 box,记录下每一个 box 对应的 image id,confidence,与 image id 那张图中的所有gt_box 求得的 max_iou,以及与之匹配的 gt_box 的 index,然后判定这个 box 是 FP 还是 TP
    2. 如果 max_iou 大于 IOU 阈值,则 box 是 TP,反之为 FP,但是如果与这个 box 计算所得的 IOU 最大的 gt_box 已经跟其他 box 计算过了的话,那么这个 box 是 FP,因为一个 gt_box 只能匹配一个 box
    3. 累加之前计算出的 FP、TP 和当前的 FP、TP,可以得到每一次迭代的 precision 和 recall
    4. P = TP / (TP FP) 分母代表所有 predictions,R = TP / (TP FN) 分母代表所有 gt_boxes
    5. 以 P 为纵轴,R 为横轴,根据 P R 点画图,将图像插值为一个个长方形柱,计算面积。(COCO 的做法是取 101 个点进行 AP 的计算)
  4. 平均一下所有类别的 AP,得到该 IOU 阈值下的 AP
  5. 更换 IOU 阈值,得到不同阈值下的 AP,对这些 AP 进行平均得到 mAP

SENet 实现细节

SoftNMS 的优缺点

原理:

NMS 直接将和得分最大的 box 的 IOU 大于某个阈值的 box 的得分置零,很粗暴,Soft NMS 就是用稍低一点的分数来代替原有的分数,而不是直接置零

优点:

仅需要对传统的 NMS 算法进行简单的改动且不增额外的参数 Soft-NMS 具有与传统NMS 相同的算法复杂度,使用高效 Soft-NMS 不需要额外的训练,并易于实现,它可以轻松的被集成到任何物体检测流程中

缺点:

还是得人工设置置信度的阈值筛选最后保留的框(一般是 0.0001) 对二阶段友好,对很多一阶段的算法没什么用

ROI Pooling 的计算过程

ResNet 和 ResNext 的区别

anchor_box 如何恢复到原始的大小

Corner Pooling 的作用

通过显式地将物体地特征集中到两个角点,给网络添加了一些先验信息,让网络更快收敛。通过 Corner Pooling,直接使网络增加了两个百分点的性能。(Corner Pooling 的思想真的不错,让网络提前知道自己应该学什么,而不是通过 GT 硬学,相当于课前预习了一下!)

##模型压缩的主要方法有哪些

  1. 从模型结构上来说分为:模型剪枝,模型蒸馏,NAS 自动学习模型结构等.
  2. 模型参数量化上包括数值精度量化到 FP16 等.

IOU-aware 的思想是什么

IOU-aware 的思想就是在一阶段检测器进行检测时,头部的回归分支和分类分支经常是单独计算的,两者并没有什么关联性,也就是说当一个 anchor 的置信度很高时,网络并不知道他回归的好不好,也就是不知道这个框的质量。因此,就如果我们能将置信度和框的质量相联系起来的话就可以得到更好的结果。这就是 IOU-aware 的由来,具体思想就是在回归分支上再加一个 IOU 分之来预测当前 prediction 和 gt 的 IOU (需要经过一个 sigmoid 归一化到 0-1),IOU 分支的标签就是当前位置 anchor 和相对应的 gt 的 IOU,作者在论文中使用的是 BCE loss。mmdet 中的实现如下:

代码语言:javascript复制
pred_box = delta2bbox(level_anchor, bbox_pred, self.target_means, self.target_stds)
# the negatives will stores the anchors information(x, y, w, h)
target_box = delta2bbox(level_anchor, bbox_targets, self.target_means, self.target_stds)
iou = bbox_overlaps(target_box, pred_box, is_aligned=True) # (batch*width_i*height_i*A)
iou_pred = iou_pred.permute(0, 2, 3, 1).reshape(-1) # (batch*width_i*height_i*A)
loss_iou = weight_iou*weighted_iou_regression_loss(iou_pred, iou, bbox_weight, avg_factor=num_total_samples)

各种算法对正负样本的定义是怎样的

TODO,加上 PAA,autoassign,以及最新的一些 cost function 的算法

(1) faster rcnn和retinanet系列采用通用的iou阈值来区分正负样本,不够鲁棒

(2) dynamic rcnn通过在训练中自适应的提高iou阈值来动态改变正负样本定义,算是一个不错的改进

(3) fcos采用的是回归范围限制和中心扩展系数超参来区分正负样本,超参难以设置

(4) atss提出了基于每层候选anchor,然后计算均值和方差来自适应的计算正负样本,虽然超参就一个但是其实有先验即物体越靠近中心越有可能是正样本,没有做到基于预测值进行自适应功能

(5) yolov3和v4系列采用了iou和网格约束先验来区分正负样本,而v5创新性的采用了宽高比例和邻近网格约束进行区分

2022.3.10 更新面经题目

https://leetcode-cn.com/circle/discuss/QmTRU4/

BatchNorm 在 train 和 test 的时候有什么不同

高频滤波,低频滤波是什么

TODO

图像滤波是一种非常重要的图像处理技术,现在大火的卷积神经网络其实也是滤波的一种,都是用卷积核去提取图像的特征模式。不过,传统的滤波,使用的卷积核是固定的参数,是由经验丰富的人去手动设计的,也称为手工特征。而卷积神经网络的卷积核参数是未知的,是根据不同的任务由数据驱动去学习得到的参数,更能适应于不同的任务。

口述反向传播过程

信号前向传播,误差反向传播。前项过程中通过与正确的标签计算损失,反向传递损失,更新参数,优化至最后的参数。

神经网络的损失函数为什么是非凸的

空洞卷积为啥会有用,和大 kernel 的卷积有什么不同?

特征图相同情况下,空洞卷积可以得到更大的感受野,从而获得更加密集的数据。更大的感受野可以提高在目标检测和语义分割的任务中的大物体识别分割的的效果。当网络层需要更大的感受野,但是由于计算资源有限无法提高卷积核数量或大小时,可以考虑使用空洞卷积。

空洞卷积存在的问题?

当多次叠加扩张率为 3*3 的 kernel 时,会产生网格效应,也就是说并不是所有的 pixel 都拿来计算了,这样的方式会损失信息的连续性,对于像素级的任务来说是致命的。可以通过修改扩张率来缓解这种现象(如 [1,2,5,1,2,5])。

https://blog.csdn.net/chen_Huiling/article/details/112632740

torch.jit.trace 和 torch.jit.script 的区别

这是将 PyTorch 模型转化成 TorchScript 格式的两种方法,有些差别

  1. trace 只记录走过的 tensor 和对 tensor 的操作,不会记录任何控制流信息,如 if 条件句和循环。因为没有记录控制流的另外的路,也没办法对其进行优化。好处是 trace 深度嵌入 python 语言,复用了所有 python 的语法,在计算流中记录数据流。
  2. script 会去理解所有的 code,真正像一个编译器一样去进行 lexer、parser、Semantic analusis 的分析「也就是词法分析语法分析句法分析,形成 AST 树,最后再将 AST 树线性化」。script 相当于一个嵌入在 Python/Pytorch 的 DSL,其语法只是 pytorch 语法的子集,这意味着存在一些 op 和语法 script 不支持,这样在编译的时候就会遇到问题。此外,script 的编译优化方式更像是 CPU 上的传统编译优化,重点对于图进行硬件无关优化,并对 if、loop 这样的 statement 进行优化。

解决方法是将这两种方式混合使用,最终转的时候用 script,但是中间可以对一些东西加上 trace 的装饰器。

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

Conv BN ReLU 怎样融合

这里假定

是网络的某一激活层特征,我们要对其进行归一化。若模型训练batch中共有

个样例,其特征分别是

,我们采用下列公式进行归一化:

这里

为这个batch上计算得到的均值和方差(在B,H,W维度上计算,每个channel单独计算),而

防止除零所设置的一个极小值,

是放缩系数,而

是平移系数。在训练过程中,

and

在当前batch上计算:

而参数

是和其它模型参数一起通过梯度下降方法训练得到。在测试阶段,我们不太会对一个batch图像进行预测,一般是对单张图像测试。因此,通过前面公式计算

and

就不可能。BN的解决方案是采用训练过程中指数移动平均值

目前,BN层大部分用于CNN网络中,此时上面的4个参数就是针对特征图各个通道的参数,这里我们记

,

,

以及

为第

个通道的参数。

融合方案

首先我们将测试阶段的 BN 层(一般称为 frozen BN)等效替换为一个 1x1 卷积层:

对于一个形状为

的特征图

,归一化后的结果

,可以如下计算:

这里的

是特征图的各个空间位置,我们可以看到上述计算就是

形式,其可以看成是

卷积(只不过每一个通道都只有一个地方是有值的),这里 W 的行向量就是每个对应输出通道的卷积核(

)。由于 BN 层常常在 Conv 层之后,我们可以进行两个操作的合并。

然后我们将 BN 层和 Conv 层融合: 这里我们记:

为BN的参数,

是BN前面Conv层的参数,

为Conv层的输入,

为输入层的channel数,

是Conv层的卷积核大小

我们将

的每个卷积

部分reshape为一个维度为

的向量

,因此Conv层加BN层的操作为:

显然,我们可以将Conv层和BN层合并成一个新的卷积层,其参数为:

  • filter weights:
  • bias:

最后我们在PyTorch中实现这个融合操作:(计算权重的时候用 troch.mm 矩阵相乘)

代码语言:javascript复制
def fuse(conv, bn):

    fused = torch.nn.Conv2d(
        conv.in_channels,
        conv.out_channels,
        kernel_size=conv.kernel_size,
        stride=conv.stride,
        padding=conv.padding,
        bias=True
    )

    # setting weights
    w_conv = conv.weight.clone().view(conv.out_channels, -1)
    w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps bn.running_var)))
    fused.weight.copy_( torch.mm(w_bn, w_conv).view(fused.weight.size()) )
    
    # setting bias
    if conv.bias is not None:
        b_conv = conv.bias
    else:
        b_conv = torch.zeros( conv.weight.size(0) )
    b_conv = torch.mm(w_bn, b_conv.view(-1, 1)).view(-1)
    b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(
                            torch.sqrt(bn.running_var   bn.eps)
                        )
    fused.bias.copy_( b_conv   b_bn )

    return fused

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

TensorRT 加速的原理是什么

移动端模型压缩部署有哪些方法

  1. 剪枝蒸馏
  2. 使用 trt、ncnn 等框架
  3. 量化(FP16,INT8)

https://github.com/scutan90/DeepLearning-500-questions/blob/master/ch17模型压缩、加速及移动端部署/第十七章模型压缩、加速及移动端部署.md

归一化和标准化什么区别

当原始数据不同维度特征的尺度(量纲)不一致时,需要标准化步骤对数据进行标准化归一化处理。

归一化 就是将训练集中某一列数值特征(假设是第 i 列)的值缩放到 0 和 1 之间。方法如下所示:

![img](https:////upload-images.jianshu.io/upload_images/1371984-072ec84a2e0ff603.png?imageMogr2/auto-orient/strip

imageView2/2/w/321/format/webp)

标准化 就是将训练集中某一列数值特征(假设是第i列)的值缩放成均值为 0,方差为 1 的状态(不一定是正态分布,取决于特征原来的分布)。如下所示:

![img](https:////upload-images.jianshu.io/upload_images/1371984-eb9c9c3305070fbd.png?imageMogr2/auto-orient/strip

imageView2/2/w/56/format/webp)

python 中 array 和 list 的区别

list 可以存放不同类型的数据,array 只能存放相同类型的数据

数据不均衡问题怎么解决

mobilenet V1 V2 的优缺点

10000 类的分类问题应该怎么做

用树形图来分类,参考 YOLO-9000

整个WordTree中的对象之间不是互斥的关系,但对于单个节点,属于它的所有子节点之间是互斥关系。比如terrier节点之下的Norfolk terrier、Yorkshire terrier、Bedlington terrier等,各品种的terrier之间是互斥的,所以计算上可以进行softmax操作。上面图10只画出了3个softmax作为示意,实际中每个节点下的所有子节点都会进行softmax。

BN 为什么要重构

为了恢复出原始的某一层所学到的特征。因此我们引入了这个可学习重构参数 γ、β,让我们的网络可以学习恢复出原始网络所要学习的特征分布。

0 人点赞