图像识别相关算法题解
1. CNN参数计算
(1)卷积层输出张量大小
其中O为输出图像的尺寸,I为输入图像的尺寸,K为卷积层的核尺寸,N为核的数量,S为移动步长,P为填充。以AlexNet网络为例,其中输入图像的尺寸为227x227x3,第一个卷积层有96个尺寸
为11x11x3的卷积核,步长为4,填充为0,
输出图像尺寸为55x55x96.
(2)池化层的输出张量大小
其中O为输出图像的尺寸,I为输入图像的尺寸,S为移动步长,Ps为池化层尺寸(不同于卷积层,池化层的输出通道数不变化)。例:每一层卷积层后的池化层尺寸为3x3,步长为2,基于前面的输出维度55x55x96,池化的输出
即维度为27x27x96。
(3)全连接层的输出向量长度等于神经单元的数量
(4)卷积层参数数量
卷积层中,核的深度等于输入图像的通道数,于是每个核有K*K个参数,当核的个数为N时,有:
其中Wc为卷积层的权重weights数量,Bc为卷积层中的偏移biases数量,Pc为所有参数数量,K为核尺寸,N为核数量,C为输入图像通道数。
(5)全连接层的参数数量
a. 当全连接层接在卷积相邻时,有
其中,Wcf为权重weights数量,Bcf为偏移biases数量,O为前接卷积层的输出图像的维度,N为前接卷积层的核数量,F为全连接层的神经单元数量。
b. 当全连接层相邻全连接层时,有
其中F为当前全连接层的神经单元数量,$$为前接全连接层的神经单元数量。
2. 说说CNN的优点、缺点
对于此类基本概念,一般不会问太细节(面试官也记不住),按自己理解来答即可
优点:
- 共享卷积核,可以方便处理高维数据
- 自动进行特征提取,无需手动设计特征
缺点:
- 池化层会丢失大量信息
- 自动进行特征提取,也就造成了模型可解释性差,更类似一个黑盒
- 需要大批量数据进行训练
3. Faster RCNN的损失函数,由哪几部分构成,anchor如何生成
faster rcnn是很经典的目标检测方法,需要熟练掌握,并且很多部分需要熟悉代码,如最基本的两个bbox的iou计算、nms等
损失函数由四部分组成:
- RPN分类损失:anchor是否为gt,二类交叉熵损失
- RPN位置回归损失:anchor位置微调,bbox的第一次修正
- ROI分类损失:ROI所属类别,分类损失
- ROI位置回归损失:继续对ROI位置微调,第二次对bbox的修正
最终的损失是四个损失相加
anchor生成:
首先,要先熟悉anchor概念:Anchor是以待检测位置为中心,以指定的大小和高宽比构成一组锚框。假设Feature Map的宽度为W,宽度为H,在每个待检测的位置生成的锚框数目为K,根据滑动窗口的方法,生成总的锚框的数量是W * H * K
每个锚点有三种尺寸(128,256,512)和三种长宽比(1:1,1:2,2:1),相互组合,就是每个锚点有9个锚框
扩展:实现生成anchor代码
4. 生成对抗网络比起常用的数据增强为什么有效,有效在哪里
常用的数据增强包括:
- 几何变换(Geometric Transformations)
- 颜色变换(Color Space)
- 旋转 | 反射变换(Rotation/Reflection)
- 噪声注入(Noise Injection)
- 混合图像(Mix)
- 随机擦除(Random Erasing)
- 缩放变换(Zoom)
- 翻转变换(Flipping)
- 裁剪(Cropping)
但这些常用的数据增广无法解决特定场景的复杂的问题,比如对于行人检测而言,我们白天采集数据,然后用白天采集的数据训练模型,把这个模型用到晚上,就会发现效果很差。于是,你不得不晚上再去采集数据。当然,AugGAN的存在让你不再需要晚上再去采集数据。这里有个原因是,白天数据和晚上数据其实冗余成分很大,只是数据域(domain)的不同。如果能把一个数据域的数据搬移到另一个数据域,是不是就增强了数据呢?我们可以通过白天的数据来生成晚上的数据;我们可以通过夏天的数据生成冬天的数据;我们也可以通过晴天的数据生成雾霾的数据……
也就是说,利用生成对抗网络,我们可以很方便地生成海量的复杂数据,有的放矢,有针对性地补足数据,提升模型性能。
这里也给出了一个数据增广的 demo,以及对应的可视化结果。
import numpy as np import imgaug as ia import imgaug.augmenters as iaa images = np.array( [ia.quokka(size=(64, 64)) for _ in range(32)], dtype=np.uint8 ) seq = iaa.Sequential([ # 随机水平翻转 iaa.Fliplr(0.5), # 随机裁剪 iaa.Crop(percent=(0, 0.1)), # 以 50%的概率进行高斯模糊 iaa.Sometimes( 0.5, iaa.GaussianBlur(sigma=(0, 0.5)) ), # 调整对比度 iaa.LinearContrast((0.75, 1.5)), #添加高斯噪声 iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # 调整亮度 iaa.Multiply((0.8, 1.2), per_channel=0.2), # 仿射变换 iaa.Affine( scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}, translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)}, rotate=(-25, 25), shear=(-8, 8) ) ], random_order=True) # 乱序 images_aug = seq(images=images) # 画图 import matplotlib.pyplot as plt %matplotlib inline for i in range(len(images_aug)): plt.subplot(4, 8, i+1) plt.axis('off') plt.imshow(images_aug[i]) plt.show()
数据增广的可视化结果,一张图瞬间变成了 32 份,有效提高模型的泛化能力
延伸考点
你知道哪些用 gan 做数据增广的方法?
提示:
- 基于CycleGAN这类风格迁移的方法、
- DAGAN (Data augmented GAN),它学习如何使用真实图像的低维表示来生成合成图像、
- AutoAugment,使用增强学习从数据本身寻找最佳图像变换策略,对于不同的任务学习不同的增强方法、
- 等等
5. vgg16同期还有哪些网络,inception网络有什么特点
VGG 网络是很早的文章了,那时候学术界主要关注的任务还是图像分类,参加ILSVRC-ImageNet比赛,提高 ImageNet 数据集的结果,同期还有 vgg19、alexNet、inception v1-3,以及非常经典的 resnet 网络。
- AlexNet 因为受限于显存大小,作为副产品,提出了分组卷积;网络结构更深,使用了relu 激活函数、使用max pooling 替代 mean pooling,使用了 dropout
- VGGNet 使用 3x3、1x1 的卷积核,取代之前网络的大卷积核(如 7x7); 网络深度达到 19 层
- Inception and GoogLeNet 谷歌提出的模型,致敬 LeNet; 之前模型都是往深度考虑,inception 网络则是往宽度考虑
- ResNet 何凯明大佬的经典论文,残差思想很棒;残差可以缓解梯度消失和梯度爆炸,网络深度达到 152 层。
早期网络的改进都是往模型深度上考虑的,认为模型越深,把卷积层堆叠得越来越多,效果就越好。
但Inception网络是设计一种能够捕获不同尺度特征的网络,即对输入图像并行地执行多个卷积运算或池化操作,并将所有输出结果拼接为一个非常深的特征图。如下图所示:
网络结构图出自 inception 论文
因为 1x1、3x3 或 5x5 等不同的卷积运算与池化操作可以获得输入图像的不同信息,并行处理这些运算并结合所有结果将获得更好的图像表征。
此后,inception 还有几个变体,整体思想没有变化,做了几点改进:
Inception V2
- 学习VGGNet的特点,用两个33卷积代替55卷积,可以降低参数量。
- 提出BN算法。就是对输入层信息分布标准化处理,使得规范化为N(0,1)的高斯分布,收敛速度大大提高。
Inception V3
学习Factorization into small convolutions的思想,将一个二维卷积拆分成两个较小卷积,例如将7x7卷积拆成1x7卷积和7x1卷积。这样做的好处是降低参数量。paper中指出,通过这种非对称的卷积拆分,比对称的拆分为几个相同的卷积效果更好,可以处理更多,更丰富的空间特征。
Inception V4
借鉴了微软的ResNet网络结构思想。
Inception block 代码示例
import torch from torch import nn import torch.nn.functional as F class BasicConv2d(nn.Module): def __init__(self, in_channels, out_channels, **kwargs): super(BasicConv2d, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs) self.bn = nn.BatchNorm2d(out_channels, eps=0.001) def forward(self, x): x = self.conv(x) x = self.bn(x) return F.relu(x, inplace=True) # inception block class InceptionA(nn.Module): def __init__(self, in_channels, pool_features): super(InceptionA, self).__init__() # 定义 1*1 卷积的分支 self.branch1x1 = BasicConv2d(in_channels, 64, kernel_size=1) # 定义 5*5 卷积的分支 self.branch5x5_1 = BasicConv2d(in_channels, 48, kernel_size=1) self.branch5x5_2 = BasicConv2d(48, 64, kernel_size=5, padding=2) # 定义 3*3 卷积的分支 self.branch3x3dbl_1 = BasicConv2d(in_channels, 64, kernel_size=1) self.branch3x3dbl_2 = BasicConv2d(64, 96, kernel_size=3, padding=1) self.branch3x3dbl_3 = BasicConv2d(96, 96, kernel_size=3, padding=1) self.branch_pool = BasicConv2d(in_channels, pool_features, kernel_size=1) def forward(self, x): branch1x1 = self.branch1x1(x) branch5x5 = self.branch5x5_1(x) branch5x5 = self.branch5x5_2(branch5x5) branch3x3dbl = self.branch3x3dbl_1(x) branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl) branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl) branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1) branch_pool = self.branch_pool(branch_pool) # 三个分支concat outputs = [branch1x1, branch5x5, branch3x3dbl, branch_pool] return torch.cat(outputs, 1)
6. vgg16当时提出有哪几个改进点,3×3卷积和1×1卷积的作用
VGG 的改进点
VGG的核心思想还是探索网络的深度,VGG16、VGG19分别为16层和19层。主要创新点包括:
- 由之前的 7x7, 5x5 大卷积核,采用了固定3x3较小的卷积核,层数增加带了更强的非线性,使模型的判别能力更强;
- 虽然层数增加,因为使用了较小的卷积核,所以反而在卷积层减小了参数数量,与大卷积核相比相当于增加了正则化。
- 将最后三层全连接层改为卷积层,可以应对不同尺寸的输入,并在最后输出直接求平均。
- 训练和预测时的技巧:训练时先训练级别A的简单网络,再复用A网络的权重来初始化后面的几个复杂模型,这样训练收敛的速度更快。预测时采用Multi-Scale的方法,同时还再训练时VGGNet也使用了Multi-Scale的方法做数据增强
3x3 卷积核的作用
前面提到,之前的 7x7, 5x5 大卷积核,为了保证感受野,提取全局的特征。而 vgg网络就提出,多个较小卷积核叠加感受野等于一个大的卷积核
如下图,2个3x3卷积核叠加,可代替一个5x5的卷积核;
2个33卷积核叠加,可代替一个55的卷积核
同样的,3个3x3的卷积核叠加,则可代替一个7x7的卷积核。
1x1 卷积核的作用
1x1卷积核的作用
- 采用非线性激活函数可以提高模型的非线性能力;
- 专注于跨通道的特征组合;
- 对feature map在channel层级进行降维或升维。
Vgg net 代码示例(包含 vgg16 和 vgg19)
import torch import torch.nn as nn # vgg16, 可以看到, 带有参数的刚好为16个 net_arch16 = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M5', "FC1", "FC2", "FC"] # vgg19, 基本和 vgg16 相同, 只不过在后3个卷积段中, 每个都多了一个卷积层 net_arch19 = [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M5', "FC1", "FC2", "FC"] class VGGNet(nn.Module): def __init__(self, net_arch, num_classes): # net_arch 即为上面定义的列表: net_arch16 或 net_arch19 super(VGGNet, self).__init__() self.num_classes = num_classes layers = [] in_channels = 3 # 初始化通道数 for arch in net_arch: if arch == 'M': layers.append(nn.MaxPool2d(kernel_size=2, stride=2)) elif arch == 'M5': layers.append(nn.MaxPool2d(kernel_size=3, stride=1, padding=1)) elif arch == "FC1": layers.append(nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, padding=6, dilation=6)) layers.append(nn.ReLU(inplace=True)) elif arch == "FC2": layers.append(nn.Conv2d(1024,1024, kernel_size=1)) layers.append(nn.ReLU(inplace=True)) elif arch == "FC": layers.append(nn.Conv2d(1024,self.num_classes, kernel_size=1)) else: layers.append(nn.Conv2d(in_channels=in_channels, out_channels=arch, kernel_size=3, padding=1) layers.append(nn.ReLU(inplace=True)) in_channels=arch self.vgg = nn.ModuleList(layers) def forward(self, input_data): x = input_data for layer in self.vgg: x = layer(x) out = x return out
7. 手写MIOU
概念:IoU(Intersection over Union)是交并比,计算式为:
MioU(Mean Intersection over Union)是均交并比,属于语义分割的标准度量,计算两个集合的交并比,在语义分割的问题中,这两个集合为真实值和预测值,计算式如下:
等价于:
MIoU计算两圆交集(橙色部分)与两圆并集(红色+橙色+黄色)之间的比例,理想情况下两圆重合,比例为1。
流程:
(1)求出混淆矩阵,假设有150个样本数据,预测为1, 2, 3类各为50个,分类结束后得到的混淆矩阵为:
预测 | |
---|---|
-- | -- |
-- | -- |
-- | -- |
预测 | ||||
类1 | 类2 | 类3 | ||
实际 | 类1 | 43 | 2 | 0 |
类2 | 5 | 45 | 1 | |
类3 | 2 | 3 | 49 |
每一行之和表示该类别的真实样本数量,每一列之和表示被预测为该类别的样本数量。
(2)求解miou,混淆矩阵的每一行再加上每一列,最后减去对角线上的值。
核心代码:
8. 异常点检测你有了解吗?
概念:异常点是偏离大部分数据的数据,使人们怀疑这些数据是由不同的机制产生的,而非随机误差。
产生原因:
(1)数据测量收集产生的错误,包括录入错误、仪器故障等。如某次竞赛的计分过程中,出现了某个球队的积分为负分,这就是录入错误;而如某家超市营业额为0,可能为仪器故障。通常这类异常点没有任何价值,一般要在检测出来后进行剔除,或者找到原始数据替换,以免影响到模型的建立和诊断。
(2)数据接收到新的机制或者数据内在特性的骤然改变。这类异常点并不是“错误的”数据,而是由一些特殊的事件或者特殊的操作引发的数据。
(3)数据度量单位的不一致,也会产生异常点。
检测方法:
(1)基于统计的方法
- 基于假设检验的方法。首先假设数据集符合某种分布,利用数据集的小概率事件原理,结合假设检验的性质得出异常点。这种方式一般需要数据满足某种分布,有一定局限性。
- 基于模型的方法。假设数据满足某种模型,对原始的数据进行建模,利用AIC或者BIC准则。例如数据集满足ARIMA(p, d, q)模型,φ(B)(1-B)^dX_t = λ(B)a_t,其中Ф(B) = 1-φ_1B-…-φ_pB^p,λ(B) = 1-θ_1B-…-θ_qB^q,B为反向推移算子,对数据的残差进行分析,从拟合效果上来看,那些拟合效果较差的点就是异常点。
(2)基于距离的方法
如果数据集合T中至少有p部分与对象O的距离大于D,则对象O为DB(p, D)异常点。该方法主要用于观测值的分布不满足任何已知的标准分布,另外对高维的数据具有好的检测效果,不像基于深度的方法,维数k必须取较小的值。另外基于距离的算法运算量较低,算法简单。
(3)基于密度的方法
基本思想来源于密度聚类。在判断一个对象t是否为异常点时,首先计算对象t的k距离,然后计算对象t的k距离领域,最后给出对象t相对于其领域内的所有对象的可达距离,推导出可达密度LRD,最后算出对象t的局部异常因子LOF。
(4)基于聚类的方法
基于聚类的方法就是将数据集划分为若干组,使得在同一组的数据具有高度的相似性,那么这样得来的不属于任何一类的点就是异常点。基于聚类的算法主要有DBSCAN等算法,主要是利用已知的聚类算法,如划分法、层次法等。这类算法能同时发现类和异常点,但效率一般。