CV深度学习面试问题记录

Posted by kevin on June 30, 2020

preface

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

图像细粒度分类是什么

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

RPN是怎么做的

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

过拟合

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

防止过拟合的几种方法:

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

欠拟合

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

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

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

深度学习里面PCA的作用

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

  1. 将原始数据按照列组成 m 行 n 列矩阵 X
  2. 将 X 的每一列(代表一个属性字段)进行零均值化(去中心化)减去这一列的均值得到特征中心化后的矩阵 $B = X_i - \mu_i$
  3. 求出协方差矩阵 $C = (X_i - \mu_i)*(X_i - \mu_i)^T$
  4. 求出矩阵 C 的特征值($det A-\lambda I =0$)和特征向量,按照特征值的大小将对应的特征向量从左往右排列,取前 k 个最大的特征值对应的特征向量组成矩阵 P
  5. Y = PX 即为降维到 k 维后的数据,即从 n 维降到了 k 维,保留了原本的大部分信息

reference:

王兴政老师的PPT

https://blog.csdn.net/Virtual_Func/article/details/51273985

YOLOv3的框是怎么聚出来的?

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

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

kmeans.png

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 不再更新

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去检测小目标,而低级特征卷积层数少,存在特征提取不充分的问题。 ———————————————— 版权声明:本文为CSDN博主「一个新新的小白」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq_31511955/article/details/80597211

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

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

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

Sigmoid

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

值域为(0,1)

Focal Loss

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

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

softmax

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

\[\sigma(Z)_j = \frac{e^{z_j}}{\sum^K_{k=1}e{z_k}}\]
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 两个函数的组合

>>> 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范数的值。

更一般的情况下,对于一个 p- 范数,如果 $x = [x_1, x_2, x_3, …, x_n]^T$, 那么向量 x 的 p- 范数就是 $$ \begin{equation}

  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/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3L1%E3%80%81L2%E8%8C%83%E6%95%B0/

pooling layer怎么反向传播

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

  • average pooling

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

average

  • max pooling

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

max

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

手撸IOU计算公式

就是一个点,要记得加上 1 ,不然是错的!

def bb_intersection_over_union(boxA, boxB):
    boxA = [int(x) for x in boxA]
    boxB = [int(x) for x in boxB]

    xA = max(boxA[0], boxB[0])
    yA = max(boxA[1], boxB[1])
    xB = min(boxA[2], boxB[2])
    yB = min(boxA[3], boxB[3])
	
    # 要加上 1 ,因为像素是一个点,并不是一个矩形
    interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)

    boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
    boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
    
    iou = interArea / float(boxAArea + boxBArea - interArea)

    return iou

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

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

下面就是深度可分离卷积的两个步骤,假设输入的 input 是 $D_F$ * $D_F$ * $M$,我们要的输出为 $D_F$ * $D_F$ * $N$,$D_K$ 为卷积核的大小

Depthwise separable convolution

用了深度可分离卷积所需要的运算量和普通卷积的计算量如下,在 MobileNet 中,卷积核的尺寸 $D_K$ 为 3 * 3,因此这样的计算量相当于减少了 9 倍

computation

NMS 怎样实现的

在目标检测中,常会利用非极大值抑制算法 (NMS) 对生成的大量候选框进行后处理 (post processing) ,去除冗余的候选框,得到最具代表性的结果,以加快目标检测的效率。

NMS 算法的主要流程如下所示:

根据候选框的类别分类概率做排序:A<B<C<D<E<F

  1. 先标记最大概率矩形框 F 是我们要保留下来的;
  2. 从最大概率矩形框 F 开始,分别判断 A~E 与 F 的重叠度 IOU(两框的交并比)是否大于某个设定的阈值,假设 B、D 与 F 的重叠度超过阈值,那么就扔掉 B、D;
  3. 从剩下的矩形框 A、C、E 中,选择概率最大的 E,标记为要保留下来的,然后判读 E 与 A、C 的重叠度,扔掉重叠度超过设定阈值的矩形框

就这样一直重复下去,直到剩下的矩形框没有了,标记完所有要保留下来的矩形框

import numpy as np

def py_nms(dets, thresh):
    """Pure Python NMS baseline."""
    #x1、y1、x2、y2、以及score赋值
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    scores = dets[:, 4]

    #每一个候选框的面积
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    #order是按照score降序排序的
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)
        #计算当前概率最大矩形框与其他矩形框的相交框的坐标,会用到numpy的broadcast机制,得到的是向量
        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:]])

        #计算相交框的面积,注意矩形框不相交时w或h算出来会是负数,用0代替
        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        #计算重叠度IOU:重叠面积/(面积1+面积2-重叠面积)
        ovr = inter / (areas[i] + areas[order[1:]] - inter)

        #找到重叠度不高于阈值的矩形框索引
        inds = np.where(ovr <= thresh)[0]
        #将order序列更新,由于前面得到的矩形框索引要比矩形框在原order序列中的索引小1,所以要把这个1加回来
        order = order[inds + 1]
    return keep

# test
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.35
    keep_dets = py_nms(dets, thresh)
    print(keep_dets)
    print(dets[keep_dets])

reference:

https://github.com/kuaikuaikim/DFace/

https://blog.csdn.net/Blateyang/article/details/79113030

为什么用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 了,我裂开

mask

color

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

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

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

    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 的卷积,然后再执行标准卷积的操作。

dilation.gif

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

转置卷积

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

deconv.jpg

可变形卷积

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