本文是“三年面试五年模拟”之独孤九剑秘籍的特别系列,Rocky将独孤九剑秘籍前十二式的内容进行汇总梳理成汇总篇,并制作成pdf版本,大家可在公众号后台【精华干货】菜单或者回复关键词“三年面试五年模拟”进行取用。
由于篇幅的原因,本文只展现“三年面试五年模拟”独孤九剑秘籍第六式到第十二式的汇总内容。
除此之外Rocky还将YOLOv1-v7全系列大解析也制作成相应的pdf版本,大家可在公众号后台【精华干货】菜单或者回复关键词“YOLO”进行取用。
由于【三年面试五年模拟】系列都是Rocky在工作之余进行整理总结,难免有疏漏与错误之处,欢迎大家对可优化的部分进行指正,我将在后续的优化迭代版本中及时更正。
希望独孤九剑秘籍的每一式都能让江湖中的英雄豪杰获益。
So,enjoy(与本文的BGM一起食用更佳哦):
造成棋盘效应的原因是转置卷积的不均匀重叠(unevenoverlap)。这种重叠会造成图像中某个部位的颜色比其他部位更深。
在下图展示了棋盘效应的形成过程,深色部分代表了不均匀重叠:
接下来我们将卷积步长改为2,可以看到输出图像上的所有像素从输入图像中接收到同样多的信息,它们都从输入图像中接收到一个像素的信息,这样就不存在转置卷带来的重叠区域。
我们也可以直接进行插值Resize操作,然后再进行卷积操作来消除棋盘效应。这种方式在超分辨率重建场景中比较常见。例如使用双线性插值和近邻插值等方法来进行上采样。
InstanceNormalization(IN)和BatchNormalization(BN)一样,也是Normalization的一种方法,只是IN是作用于单张图片,而BN作用于一个Batch。
BN对Batch中的每一张图片的同一个通道一起进行Normalization操作,而IN是指单张图片的单个通道单独进行Normalization操作。如下图所示,其中C代表通道数,N代表图片数量(Batch)。
IN适用于生成模型中,比如图片风格迁移。因为图片生成的结果主要依赖于某个图像实例,所以对整个Batch进行Normalization操作并不适合图像风格化的任务,在风格迁移中使用IN不仅可以加速模型收敛,并且可以保持每个图像实例之间的独立性。
下面是IN的公式:
其中t代表图片的index,i代表的是featuremap的index。
一般而言,featuremap上有效感受野要小于实际感受野。其有效性,以中心点为基准,类似高斯分布向边缘递减。
总的来说,感受野主要描述featuremap中的最大信息量,有效感受野则主要描述信息的有效性。
全局池化主要包括全局平均池化和全局最大池化。
接下来,Rocky以全局平均池化为例,讲述其如何在深度学习网络中发挥作用。
刚才已经讲过,全局平均池化就是对最后一层卷积的特征图,每个通道求整个特征图的均值。如下图所示:
一般网络的最后会再接几个全连接层,但全局池化后的featuremap相当于一像素,所以最后的全连接其实就成了一个加权相加的操作。这种结构比起直接的全连接更加直观,参数量大大幅下降,并且泛化性能更好:
全局池化的作用:
随机梯度下降的优化算法在科研和工业界是很常用的。
很多理论和工程问题都能转化成对目标函数进行最小化的数学问题。
举个例子:梯度下降(GradientDescent)就好比一个人想从高山上奔跑到山谷最低点,用最快的方式奔向最低的位置。
SGD的公式:
动量(Momentum)公式:
基本的mini-batchSGD优化算法在深度学习取得很多不错的成绩。然而也存在一些问题需解决:
AdaGrad优化算法(AdaptiveGradient,自适应梯度),它能够对每个不同的参数调整不同的学习率,对频繁变化的参数以更小的步长进行更新,而稀疏的参数以更大的步长进行更新。
AdaGrad公式:
表示t时刻的梯度。
表示t时刻参数的梯度平方和。
与SGD的核心区别在于计算更新步长时,增加了分母:梯度平方累积和的平方根。此项能够累积各个参数的历史梯度平方,频繁更新的梯度,则累积的分母逐渐偏大,那么更新的步长相对就会变小,而稀疏的梯度,则导致累积的分母项中对应值比较小,那么更新的步长则相对比较大。
AdaGrad能够自动为不同参数适应不同的学习率(平方根的分母项相当于对学习率α进进行了自动调整,然后再乘以本次梯度),大多数的框架实现采用默认学习率α=0.01即可完成比较好的收敛。
优势:在数据分布稀疏的场景,能更好利用稀疏梯度的信息,比标准的SGD算法更有效地收敛。
RMSProp结合梯度平方的指数移动平均数来调节学习率的变化。能够在不稳定的目标函数情况下进行很好地收敛。
计算t时刻的梯度:
计算梯度平方的指数移动平均数(ExponentialMovingAverage),是遗忘因子(或称为指数衰减率),依据经验,默认设置为0.9。
梯度更新的时候,与AdaGrad类似,只是更新的梯度平方的期望(指数移动均值),其中,避免除数为0。默认学习率。
优势:能够克服AdaGrad梯度急剧减小的问题,在很多应用中都展示出优秀的学习率自适应能力。尤其在不稳定(Non-Stationary)的目标函数下,比基本的SGD、Momentum、AdaGrad表现更良好。
Adam优化器结合了AdaGrad和RMSProp两种优化算法的优点。对梯度的一阶矩估计(FirstMomentEstimation,即梯度的均值)和二阶矩估计(SecondMomentEstimation,即梯度的未中心化的方差)进行综合考虑,计算出更新步长。
Adam的优势:
Adam的实现原理:
然后计算梯度的指数移动平均数,初始化为0。
类似于Momentum算法,综合考虑之前累积的梯度动量。
系数为指数衰减率,控制动量和当前梯度的权重分配,通常取接近于1的值。默认为0.9。
接着,计算梯度平方的指数移动平均数,初始化为0。
系数为指数衰减率,控制之前的梯度平方的影响情况。默认为0.999。
类似于RMSProp算法,对梯度平方进行加权均值。
由于初始化为0,会导致偏向于0,尤其在训练初期阶段。
所以,此处需要对梯度均值进行偏差纠正,降低偏差对训练初期的影响。
同时也要进行偏差纠正:
最后总的公式如下所示:
其中默认学习率,避免除数变为0。
从表达式中可以看出,对更新的步长计算,能够从梯度均值和梯度平方两个角度进行自适应地调节,而不是直接由当前梯度决定。
Adam的不足:
虽然Adam算法目前成为主流的优化算法,不过在很多领域里(如计算机视觉的图像识别、NLP中的机器翻译)的最佳成果仍然是使用带动量(Momentum)的SGD来获取到的。
Normalize非常重要,没有处理过的图片是没办法收敛的。图片Normalize一种简单的方法是(images-127.5)/127.5,然后送到判别器去训练。同理生成的图片也要经过判别器,即生成器的输出也是-1到1之间,所以使用Tanh激活函数更加合适。
最好去掉整个Pooling逻辑,因为使用Pooling会损失信息,这对于GAN训练没有益处。
对判别器和生成器使用不同的学习速度。使用较低的学习率更新生成器,判别器使用较高的学习率进行更新。
使用梯度惩罚机制可以极大增强GAN的稳定性,尽可能减少modecollapse问题的产生。
Spectralnormalization可以用在判别器的weightnormalization技术,可以确保判别器是K-Lipschitz连续的。
可以使用多个GAN/多生成器/多判别器结构来让GAN训练更稳定,提升整体效果,解决更难的问题。
变量在时刻记为,为变量在时刻训练后的取值,当不使用滑动平均模型时,在使用滑动平均模型后,的更新公式如下:
上式中,。相当于没有使用滑动平均。
时刻变量的滑动平均值大致等于过去个时刻值的平均。并使用biascorrection将除以修正对均值的估计。
加入Biascorrection后,和的更新公式如下:
当越大,越接近1,则公式(1)和(2)得到的结果(和)将越来越接近。
下图代表不同方式计算权重的结果:
滑动平均的优势:占用内存少,不需要保存过去10个或者100个历史值,就能够估计其均值。滑动平均虽然不如将历史值全保存下来计算均值准确,但后者占用更多内存,并且计算成本更高。
为什么滑动平均在测试过程中被使用?
滑动平均可以使模型在测试数据上更鲁棒(robust)。
采用随机梯度下降算法训练神经网络时,使用滑动平均在很多应用中都可以在一定程度上提高最终模型在测试数据上的表现。
训练中对神经网络的权重使用滑动平均,之后在测试过程中使用滑动平均后的作为测试时的权重,这样在测试数据上效果更好。因为滑动平均后的的更新更加平滑,对于随机梯度下降而言,更平滑的更新说明不会偏离最优点很远。比如假设decay=0.999,一个更直观的理解,在最后的1000次训练过程中,模型早已经训练完成,正处于抖动阶段,而滑动平均相当于将最后的1000次抖动进行了平均,这样得到的权重会更加鲁棒。
SpectralNormalization是一种wegihtNormalization技术,和weight-clipping以及gradientpenalty一样,也是让模型满足1-Lipschitz条件的方式之一。
Lipschitz(利普希茨)条件限制了函数变化的剧烈程度,即函数的梯度,来确保统计的有界性。因此函数更加平滑,在神经网络的优化过程中,参数变化也会更稳定,不容易出现梯度爆炸。
Lipschitz条件的约束如下所示:
其中代表一个常数,即利普希茨常数。若,则是1-Lipschitz。
在GAN领域,SpectralNormalization有很多应用。在WGAN中,只有满足1-Lipschitz约束时,W距离才能转换成较好求解的对偶问题,使得WGAN更加从容的训练。
如果想让矩阵A映射:满足K-Lipschitz连续,K的最小值为(是的最大特征值),那么要想让矩阵A满足1-Lipschitz连续,只需要在A的所有元素上同时除以(Spectralnorm)。
SpectralNormalization实际上在做的事,是将每层的参数矩阵除以自身的最大奇异值,本质上是一个逐层SVD的过程,但是真的去做SVD就太耗时了,所以采用幂迭代的方法求解。过程如下图所示:
得到谱范数后,每个参数矩阵上的参数皆除以它,以达到Normalization的目的。
激活函数可以引入非线性因素,提升网络的学习表达能力。
Sigmoid激活函数
函数的定义为:
如下图所示,其值域为。也就是说,输入的每个神经元、节点都会被缩放到一个介于和之间的值。
当大于零时输出结果会趋近于,而当小于零时,输出结果趋向于,由于函数的特性,经常被用作二分类的输出端激活函数。
Sigmoid的导数:
当时,。
Sigmoid的优点:
Sigmoid的缺陷:
Tanh激活函数
Tanh函数的定义为:
如下图所示,值域为。
Tanh的优势:
Tanh的导数:
由Tanh和Sigmoid的导数也可以看出Tanh导数更陡,收敛速度比Sigmoid快。
Tanh的缺点:
导数值恒小于1,反向传播易导致梯度消失。
Relu激活函数
Relu激活函数的定义为:
ReLU的优势:
ReLU的导数:
通常时,给定其导数为和。
ReLU的不足:
LeakyReLU激活函数
LeakyReLU激活函数定义为:
如下图所示(),值域为。
LeakyReLU的优势:
该方法与ReLU不同的是在小于0的时候取,其中是一个非常小的斜率(比如0.01)。这样的改进可以使得当小于0的时候也不会导致反向传播时的梯度消失现象。
LeakyReLU的不足:
SoftPlus激活函数
SoftPlus激活函数的定义为:
值域为。
函数图像如下:
可以把SoftPlus看作是ReLU的平滑。
ELU激活函数
ELU激活函数解决了ReLU的一些问题,同时也保留了一些好的方面。这种激活函数要选取一个值,其常见的取值是在0.1到0.3之间。
函数定义如下所示:
如果我们输入的值大于,则结果与ReLU一样,即值等于值;但如果输入的值小于,则我们会得到一个稍微小于的值,所得到的值取决于输入的值,但还要兼顾参数——可以根据需要来调整这个参数。公式进一步引入了指数运算,因此ELU的计算成本比ReLU高。
下面给出了值为0.2时的ELU函数图:
ELU的导数:
导数图如下所示:
ELU的优势:
ELU的不足:
反向传播(Backpropagation,BP)算法是一种与最优化方法(如梯度下降法)结合使用的,用来训练人工神经网络的常见算法。BP算法对网络中所有权重计算损失函数的梯度,并将梯度反馈给最优化方法,用来更新权值以最小化损失函数。该算法会先按前向传播方式计算(并缓存)每个节点的输出值,然后再按反向传播遍历图的方式计算损失函数值相对于每个参数的偏导数。
接下来我们以全连接层,使用sigmoid激活函数,Softmax+MSE作为损失函数的神经网络为例,推导BP算法逻辑。由于篇幅限制,这里只进行简单推导,后续Rocky将专门写一篇PB算法完整推导流程,大家敬请期待。
首先,我们看看sigmoid激活函数的表达式及其导数:
可以看到sigmoid激活函数的导数最终可以表达为输出值的简单运算。
我们再看MSE损失函数的表达式及其导数:
其中代表groundtruth(gt)值,代表网络输出值。
由于偏导数中单且仅当时才会起作用,故进行了简化。
接下来我们看看全连接层输出的梯度:
我们用,则能再次简化:
最后,我们看看那PB算法中每一层的偏导数:
输出层:
倒数第二层:
倒数第三层:
像这样依次往回推导,再通过梯度下降算法迭代优化网络参数,即可走完PB算法逻辑。
分组卷积(GroupConvolution)最早出现在AlexNet网络中,分组卷积被用来切分网络,使其能在多个GPU上并行运行。
普通卷积进行运算的时候,如果输入featuremap尺寸是,卷积核有N个,那么输出的featuremap与卷积核的数量相同也是N个,每个卷积核的尺寸为,N个卷积核的总参数量为。
分组卷积的主要对输入的featuremap进行分组,然后每组分别进行卷积。如果输入featuremap尺寸是,输出featuremap的数量为个,如果我们设定要分成G个group,则每组的输入featuremap数量为,则每组的输出featuremap数量为,每个卷积核的尺寸为,卷积核的总数仍为N个,每组的卷积核数量为,卷积核只与其同组的输入map进行卷积,卷积核的总参数量为,易得总的参数量减少为原来的。
分组卷积的作用:
FocalLoss是解决了分类问题中类别不均衡、分类难度差异的一个损失函数,使得模型在训练过程中更加聚焦在困难样本上。
FocalLoss是从二分类问题出发,同样的思想可以迁移到多分类问题上。
我们知道二分类问题的标准loss是交叉熵:
FocalLoss论文中给出的式子如下:
其中是真实标签,是预测概率。
我们再定义
那么,上面的交叉熵的式子可以转换成:
有了上面的铺垫,最初FocalLoss论文中接着引入了均衡交叉熵函数:
针对类别不均衡问题,在Loss里加入一个控制权重,对于属于少数类别的样本,增大即可。但这样有一个问题,它仅仅解决了正负样本之间的平衡问题,并没有区分易分/难分样本。
为什么上述公式只解决正负样本不均衡问题呢?
因为增加了一个系数,跟的定义类似,当的时候;当的时候,,的范围也是。因此可以通过设定的值(如果这个类别的样本数比这个类别的样本数少很多,那么可以取到来增加这个类的样本的权重)来控制正负样本对整体Loss的贡献。
为了可以区分难/易样本,FocalLoss雏形就出现了:
为了应对正负样本不均衡的问题,在上面的式子中再加入平衡交叉熵的因子,用来平衡正负样本的比例不均,最终得到FocalLoss:
FocalLoss论文中给出的实验最佳取值为,。
人脸检测相对于通用目标检测来说,算是一个子任务。比起通用目标检测任务动辄检测1000个类别,人脸检测任务主要聚焦于人脸的单类目标检测,使用通用目标检测模型太过奢侈,有点“杀鸡用牛刀”的感觉,并且大量的参数冗余,会影响部署侧的实用性,故针对人脸检测任务,学术界提出了很多轻量型的人脸检测模型,Rocky在这里给大家介绍一些比较有代表性的:
LFFD(A-Light-and-Fast-Face-Detector-for-Edge-Devices)适用于人脸、行人、车辆等单目标检测任务,具有速度快,模型小,效果好的特点。LFFD是Anchor-free的方法,使用感受野替代Anchors,并在主干结构上抽取8路特征图对从小到大的人脸进行检测,检测模块分为类别二分类与边界框回归。
LFFD模型结构
我们可以看到,LFFD模型主要由四部分组成:tinypart、smallpart、mediumpart、largepart。
模型中并没有采用BN层,因为BN层会减慢17%的推理速度。其主要采用尽可能快的下采样来保持100%的人脸覆盖。
LFFD主要特点:
LFFD损失函数
LFFD损失函数是由regressionloss和classificationloss的加权和。
分类损失使用了交叉熵损失。
回归损失使用了L2损失函数。
LFFD论文地址:LFFD:ALightandFastFaceDetectorforEdgeDevices论文地址
U-Net网络结构如下所示:
U-Net网络的特点:
U-Net在医疗图像,缺陷检测以及交通场景中有非常丰富的应用,可以说图像分割实际场景,U-Net是当仁不让的通用Baseline。
U-Net的论文地址:U-Net
RepVGG模型的基本架构由20多层卷积组成,分成5个stage,每个stage的第一层是stride=2的降采样,每个卷积层用ReLU作为激活函数。
RepVGG的主要特点:
那么是什么让RepVGG能在上述情形下达到SOTA效果呢?
答案就是结构重参数化(structuralre-parameterization)。
在训练阶段,训练一个多分支模型,并将多分支模型等价转换为单路模型。在部署阶段,部署单路模型即可。这样就可以同时利用多分支模型训练时的优势(性能高)和单路模型推理时的好处(速度快、省内存)。
更多结构重参数化细节知识将在后续的篇章中展开介绍,大家尽情期待!
2014年,IanGoodfellow第一次提出了GAN的概念。YannLeCun曾经说过:“生成对抗网络及其变种已经成为最近10年以来机器学习领域最为重要的思想之一”。GAN的提出让生成式模型重新站在了深度学习这个浪潮的璀璨舞台上,与判别式模型开始谈笑风生。
GAN由生成器和判别器组成。其中,生成器主要负责生成相应的样本数据,输入一般是由高斯分布随机采样得到的噪声。而判别器的主要职责是区分生成器生成的样本与样本,输入一般是样本与相应的生成样本,我们想要的是对样本输出的置信度越接近越好,而对生成样本输出的置信度越接近越好。与一般神经网络不同的是,GAN在训练时要同时训练生成器与判别器,所以其训练难度是比较大的。
GAN的对抗思想主要由其目标函数实现。具体公式如下所示:
上面这个公式看似复杂,其实不然。跳出细节来看,整个公式的核心逻辑其实就是一个min-max问题,深度学习数学应用的边界扩展到这里,GAN便开始发光了。
接着我们再切入细节。我们可以分两部分开看这个公式,即判别器最小化角度与生成器最大化角度。在判别器角度,我们希望最大化这个目标函数,因为在公示第一部分,其表示样本输入判别器后输出的置信度,当然是越接近越好。而公式的第二部分表示生成器输出的生成样本再输入判别器中进行进行二分类判别,其输出的置信度当然是越接近越好,所以越接近越好。
在生成器角度,我们想要最小化判别器目标函数的最大值。判别器目标函数的最大值代表的是真实数据分布与生成数据分布的JS散度,JS散度可以度量分布的相似性,两个分布越接近,JS散度越小(JS散度是在初始GAN论文中被提出,实际应用中会发现有不足的地方,后来的论文陆续提出了很多的新损失函数来进行优化)
写到这里,大家应该就明白GAN的对抗思想了,下面是初始GAN论文中判别器与生成器损失函数的具体设置以及训练的具体流程:
在图中可以看出,将判别器损失函数离散化,其与交叉熵的形式一致,我们也可以说判别器的目标是最小化交叉熵损失。
设计特征金字塔的结构,用于解决目标检测中的多尺度问题,在基本不增加原有模型计算量的情况下,大幅度提升小物体(smallobject)的检测性能。
原来很多目标检测算法都是只采用高层特征进行预测,高层的特征语义信息比较丰富,但是分辨率较低,目标位置比较粗略。假设在深层网络中,最后的高层特征图中一个像素可能对应着输出图像的像素区域,那么小于像素的小物体的特征大概率已经丢失。与此同时,低层的特征语义信息比较少,但是目标位置准确,这是对小目标检测有帮助的。FPN将高层特征与底层特征进行融合,从而同时利用低层特征的高分辨率和高层特征的丰富语义信息,并进行了多尺度特征的独立预测,对小物体的检测效果有明显的提升。
传统解决这个问题的思路包括:
Bottom-uppathway(自底向上线路)
自底向上线路是卷积网络的前向传播过程。在前向传播过程中,featuremap的大小可以在某些层发生改变。
Top-downpath(自顶向下线路)和Lareralconnections(横向链路)
自顶向下线路是上采样的过程,而横向链路是将自顶向下线路的结果和自底向上线路的结构进行融合。
上采样的featuremap与相同大小的下采样的featuremap进行逐像素相加融合(element-wiseaddition),其中自底向上的feature先要经过卷积层,目的是为了减少通道维度。
论文中FPN直接在FasterR-CNN上进行改进,其backbone是ResNet101,FPN主要应用在FasterR-CNN中的RPN和FastR-CNN两个模块中。
FPN+RPN:
将FPN和RPN结合起来,那RPN的输入就会变成多尺度的featuremap,并且在RPN的输出侧接多个RPNhead层用于满足对anchors的分类和回归。
FPN+FastR-CNN:
FastR-CNN的整体结构逻辑不变,在backbone部分引入FPN思想进行改造。
在目标检测领域,很多检测算法最后使用了全连接层,导致输入尺寸固定。当遇到尺寸不匹配的图像输入时,就需要使用crop或者warp等操作进行图像尺寸和算法输入的匹配。这两种方式可能出现不同的问题:裁剪的区域可能没法包含物体的整体;变形操作造成目标无用的几何失真等。
而SPP的做法是在卷积层后增加一个SPPlayer,将featuresmap拉成固定长度的featurevector。然后将featurevector输入到全连接层中。以此来解决上述的尴尬问题。
SPP的优点:
AP:PR曲线下的面积。
AP50:固定IoU为50%时的AP值。
AP75:固定IoU为75%时的AP值。
AP@[0.5:0.95]:把IoU的值从50%到95%每隔5%进行了一次划分,并对这10组AP值取平均。
mAP:对所有的类别进行AP的计算,然后取均值。
mAP@[.5:.95](即mAP@[.5,.95]):表示在不同IoU阈值(从0.5到0.95,步长0.05)(0.5、0.55、0.6、0.65、0.7、0.75、0.8、0.85、0.9、0.95)上的平均mAP。
YOLOv2中引入K-means算法进行anchor的生成,可以自动找到更好的anchor宽高的值用于模型训练的初始化。
但如果使用经典K-means中的欧氏距离作为度量,意味着较大的Anchor会比较小的Anchor产生更大的误差,聚类结果可能会偏离。
由于目标检测中主要关心anchor与groundtruebox(gtbox)的IOU,不关心两者的大小。因此,使用IOU作为度量更加合适,即提高IOU值。因此YOLOv2采用IOU值为评判标准:
具体anchor生成步骤与经典K-means大致相同,在下一个章节中会详细介绍。主要的不同是使用的度量是,并将anchor作为簇的中心。
机器学习中通常根据数据是否有标签可以分为监督学习(supervisedlearning)、非监督学习(unsupervisedlearning),半监督学习(semi-supervisedlearning)以及弱监督学习(weaklysupervisedlearning)。
监督学习
机器学习模型在训练过程中的所有数据都有标签,就是监督学习的逻辑。
监督学习是最常见的学习种类,常见场景为分类和回归问题。
深度学习模型大都数都遵从监督学习的流程,并且支持向量机(SupportVectorMachine,SVM),朴素贝叶斯(NaiveBayes),逻辑回归(LogisticRegression),K近邻(K-NearestNeighborhood,KNN),决策树(DecisionTree),随机森林(RandomForest),AdaBoost以及线性判别分析(LinearDiscriminantAnalysis,LDA)等也属于监督学习算法的范畴。
非监督学习
非监督学习与监督学习完全相反,机器学习模型在训练过程中的所有数据都是没有标签的,主要学习数据本身的一些特性。
比如想象一个人从来没有见过猫和狗,如果给他看了大量的猫和狗,虽然他还是没有猫和狗的概念,但是他是能够观察出每个物种的共性和两个物种间的区别的,并对这个两种动物予以区分。
半监督学习
半监督学习的逻辑是机器学习模型在训练过程中,部分数据有标签,与此同时另外一部分数据没有标签,并把这两种数据都利用起来用于训练。
弱监督学习
弱监督学习的逻辑是机器学习模型在训练过程中使用的数据的标签存在不可靠的情况。这里的不可靠可以是标注不正确,多重标记,标记不充分,局部标记,包含噪声等情况。一个直观的例子是相对于分割的标签来说,分类的标签就是弱标签。
我们首先可以设目标函数为,目标函数中的权值参数为,那么目标函数和权值参数的关系如下所示:
如上图所示,最优的在绿色的点处,而且非零。
我们首先可以使用L2正则进行优化,新的目标函数:,示意图如下蓝线所示:
我们可以看到,最优的出现在黄点处,的绝对值减小了,更靠近横坐标轴,但是依然是非零的。
为什么是非零的呢?
我们可以对L2正则下的目标函数求导:
我们发现,权重每次乘上的是小于1的倍数进行收敛,而且其导数在时没有办法做到左右两边导数异号,所以L2正则使得整个训练过程稳定平滑,但是没有产生稀疏性。
接下来我们使用L1正则,新的目标函数:,示意图如下粉线所示:
这里最优的就变成了0。因为保证使用L1正则后处左右两个导数异号,就能满足极小值点形成的条件。
我们来看看这次目标函数求导的式子:
可以看出L1正则的惩罚很大,每次都是减去一个常数的线性收敛,所以L1比L2更容易收敛到比较小的值,而如果|f^{'}(0)|'=""data-formula-type='inline-equation'>,就能保证处取得极小值。
上面只是一个权值参数。在深层网路中,L1会使得大量的最优值变成0,从而使得整个模型有了稀疏性。
n维欧式空间中任意k个向量之间两两的内积所组成的矩阵,称为这k个向量的格拉姆矩阵(Grammatrix),这是一个对称矩阵。
格拉姆矩阵在风格迁移中有广泛的应用,深度学习中经典的风格迁移流程是:
感知损失在图像生成领域中比较常用。其核心是将gt图片卷积得到的高层feature与生成图片卷积得到的高层feature进行回归,从而约束生成图像的高层特征(内容和全局结构)。
上面的公式中,划线部分代表了高层特征,一般使用VGG作为特征提取器。
Accuracy、Precision、Recall、F1Scores的公式如下所示:
Accuracy(准确率):分类正确的样本数占样本总数的比例。
Precision(精准度/查准率):当前预测为正样本类别中被正确分类的样本比例。
Recall(召回率/查全率):预测出来的正样本占正样本总数的比例。
F1-score是Precision和Recall的综合。F1-score越高,说明分类模型越稳健。
一般在深层神经网络中,我们需要预防梯度爆炸和梯度消失的情况。
梯度消失(gradientvanishingproblem)和梯度爆炸(gradientexplodingproblem)一般随着网络层数的增加会变得越来越明显。
例如下面所示的含有三个隐藏层的神经网络,梯度消失问题发生时,接近输出层的hidenlayer3的权重更新比较正常,但是前面的hiddenlayer1的权重更新会变得很慢,导致前面的权重几乎不变,仍然接近初始化的权重,这相当于hiddenlayer1没有学到任何东西,此时深层网络只有后面的几层网络在学习,而且网络在实际上也等价变成了浅层网络。
我们来看看看反向传播的过程:
(假设网络每一层只有一个神经元,并且对于每一层)
可以推导出:
而sigmoid的导数如下图所示:
可以知道,的最大值是,而我们初始化的权重通常都小于1,因此,而且链式求导层数非常多,不断相乘的话,最后的结果越来越小,趋向于0,就会出现梯度消失的情况。
梯度爆炸则相反,1'data-formula-type='inline-equation'>时,不断相乘结果变得很大。
梯度爆炸和梯度消失问题都是因为网络太深,网络权重更新不稳定造成的,本质上是梯度方向传播的连乘效应。
当有多个不同的模型(结构不同、超参数不同等)可以选择时,我们通过K折交叉验证来选取对于特定数据集最好的模型。
5折交叉验证(5-foldcross-validation)用来验证从不同的模型中选取最优的模型(最合适的模型)。将数据集分成5份,轮流将其中4份作为训练数据,1份作为验证数据,进行试验。每次试验都会得出相应的正确率。5次的结果的正确率的平均值作为对算法精度的估计。同时对不同的模型(如CNN、SVM、LR等)做上述相同的操作,得出每个模型在特定数据集上的平均能力,从中选优。
例子:
假设我们有一个特定数据集,我们想从YOLOv4、MaskR-CNN、SSD、FasterR-CNN、RetinaNet这五个模型中选取在这个特定数据集中有最好效果的一个模型作为baseline,我们可以进行交叉验证来进行判断:
步骤:
KL散度(Kullback-Leiblerdivergence),可以以称作相对熵(relativeentropy)或信息散度(informationdivergence)。KL散度的理论意义在于度量两个概率分布之间的差异程度,当KL散度越大的时候,说明两者的差异程度越大;而当KL散度小的时候,则说明两者的差异程度小。如果两者相同的话,则该KL散度应该为0。
接下来我们举一个具体的:
我们设定两个概率分布分别为和,在设定为连续随机变量的前提下,他们对应的概率密度函数分别为和。如果我们用去近似,则KL散度可以表示为:
从上面的公式可以看出,当且仅当时,。此外我们可以知道KL散度具备非负性,即=0'data-formula-type='inline-equation'>。并且从公式中我们也发现,KL散度不具备对称性,也就是说对于的KL散度并不等于对于的KL散度。因此,KL散度并不是一个度量(metric),即KL散度并非距离。
我们再来看看离散的情况下用去近似的KL散度的公式:
接下来我们对上面的式子进行展开:
最后得到的第一项称作和的交叉熵(crossentropy),后面一项就是熵。
在信息论中,熵代表着信息量,代表着基于分布自身的编码长度,也就是最优的编码长度(最小字节数)。而则代表着用的分布去近似分布的信息,自然需要更多的编码长度。并且两个分布差异越大,需要的编码长度越大。所以两个值相减是大于等于0的一个值,代表冗余的编码长度,也就是两个分布差异的程度。所以KL散度在信息论中还可以称为相对熵(relativeentropy)。
对深度学习中的生成模型来说,我们希望最小化真实数据分布与生成数据分布之间的KL散度,从而使得生成数据尽可能接近真实数据的分布。在实际场景中,我们是几乎不可能知道真实数据分布的,我们使用训练数据形成的生成分布在逼近。
JS散度全称Jensen-Shannon散度,简称JS散度。在概率统计中,JS散度也与KL散度一样具备了测量两个概率分布相似程度的能力,它的计算方法基于KL散度,继承了KL散度的非负性等,但有一点重要的不同,JS散度具备了对称性。
JS散度的公式如下所示,我们设定两个概率分布为和,另外我们还设定,KL为KL散度公式。
如果我们把KL散度公式写入展开的话,结果如下所示:
深度学习中使用KL散度和JS散度进行度量的时候存在一个问题:
如果两个分布,离得很远,完全没有重叠的时候,那么KL散度值是没有意义的,而JS散度值是一个常数。这对以梯度下降为基础的深度学习算法有很大影响,这意味梯度为0,即梯度消失。
K-means算法是一个实用的无监督聚类算法,其聚类逻辑依托欧式距离,当两个目标的距离越近,相似度越大。对于给定的样本集,按照样本之间的距离大小,将样本集划分为个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
K-means的主要算法步骤:
K-Means的主要优点:
K-Means的主要缺点:
K近邻(K-NN)算法计算不同数据特征值之间的距离进行分类。存在一个样本数据集合,也称作训练数据集,并且数据集中每个数据都存在标签,即我们知道每一个数据与所属分类的映射关系。接着输入没有标签的新数据后,在训练数据集中找到与该新数据最邻近的K个数据,然后提取这K个数据中占多数的标签作为新数据的标签(少数服从多数逻辑)。
K近邻算法的主要步骤:
K近邻算法的结果很大程度取决于K的选择。其距离计算一般使用欧氏距离或曼哈顿距离等经典距离度量。
K近邻算法的主要优点:
K近邻算法的主要缺点:
Python中assert(断言)用于判断一个表达式,在表达式条件为的时候触发异常。
断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况。
Rocky直接举一些例子:
在Python中,当我们想要互换两个变量的值或将列表中的两个值交换时,我们可以使用如下的格式进行,不需要创建临时变量:
x,y=y,x这么做的原理是什么呢?
首先一般情况下Python是从左到右解析一个语句的,但在赋值操作的时候,因为是右值具有更高的计算优先级,所以需要从右向左解析。
对于上面的代码,它的执行顺序如下:
先计算右值(这里是简单的原值,但可能会有表达式或者函数调用的计算过程),在内存中创建元组(tuple),存储分别对应的值;计算左边的标识符,元组被分别分配给左值,通过解包(unpacking),元组中第一个标示符对应的值,分配给左边第一个标示符,元组中第二个标示符对应的值,分配给左边第二个标示符,完成了和的值交换。
可变对象与不可变对象的区别在于对象本身是否可变。
可变对象:list(列表)dict(字典)set(集合)
不可变对象:tuple(元组)string(字符串)int(整型)float(浮点型)bool(布尔型)
None是一个特殊的常量,表示空值,其和False,0以及空字符串不同,它是一个特殊Python对象,None的类型是NoneType。
None和任何其他的数据类型比较返回False。
我们可以将None复制给任何变量,也可以给None赋值。
和主要用于函数定义。我们可以将不定数量的参数传递给一个函数。
这里的不定的意思是:预先并不知道函数使用者会传递多少个参数,所以在这个场景下使用这两个关键字。
是用来发送一个非键值对的可变数量的参数列表给一个函数。
我们直接看一个例子:
deftest_var_args(f_arg,*argv):print('firstnormalarg:',f_arg)forarginargv:print('anotherargthrough*argv:',arg)test_var_args('hello','python','ddd','test')-----------------结果如下-----------------------firstnormalarg:helloanotherargthrough*argv:pythonanotherargthrough*argv:dddanotherargthrough*argv:test允许我们将不定长度的键值对,作为参数传递给一个函数。如果我们想要在一个函数里处理带名字的参数,我们可以使用。
我们同样举一个例子:
Python的Numpy库是一个非常实用的数学计算库,其broadcasting机制给我们的矩阵运算带来了极大地方便。
我们先看下面的一个例子:
>>>importnumpyasnp>>>a=np.array([1,2,3])>>>aarray([1,2,3])>>>b=np.array([6,6,6])>>>barray([6,6,6])>>>c=a+b>>>carray([7,8,9])上面的代码其实就是把数组和数组中同样位置的每对元素相加。这里和是相同长度的数组。
如果两个数组的长度不一致,这时候broadcasting就可以发挥作用了。
比如下面的代码:
broadcasting会把扩展成,然后上面的代码就变成了对两个同样长度的数组相加。示意图如下(broadcasting不会分配额外的内存来存取被复制的数据,这里只是方面描述):
我们接下来看看多维数组的情况:
>>>earray([[1.,1.,1.],[1.,1.,1.],[1.,1.,1.]])>>>e+aarray([[2.,3.,4.],[2.,3.,4.],[2.,3.,4.]])在这里一维数组被扩展成了二维数组,和的尺寸相同。示意图如下所示:
我们再来看一个需要对两个数组都做broadcasting的例子:
在这里和都被扩展成相同的尺寸的二维数组。示意图如下所示:
总结broadcasting的一些规则:
>>>a=np.arange(3)>>>aarray([0,1,2])>>>b=np.ones((2,3))>>>barray([[1.,1.,1.],[1.,1.,1.]])>>>a.shape(3,)>>>a+barray([[1.,2.,3.],[1.,2.,3.]])接下来我们看看报错的例子:
不用@classmethod和@staticmethod修饰的方法为实例方法。在类中定义的方法默认都是实例方法。实例方法最大的特点是它至少要包含一个self参数,用于绑定调用此方法的实例对象,实例方法通常可以用类对象直接调用。
采用@classmethod修饰的方法为类方法。类方法和实例方法相似,它至少也要包含一个参数,只不过类方法中通常将其命名为cls,Python会自动将类本身绑定给cls参数。我们在调用类方法时,无需显式为cls参数传参。
采用@staticmethod修饰的方法为静态方法。静态方法没有类似self、cls这样的特殊参数,因此Python的解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。
[:n]代表列表中的第一项到第n项。我们看一个例子:
example=[1,2,3,4,5,6,7,8,9,10]print(example[:6])---------结果---------[1,2,3,4,5,6][n:]代表列表中第n+1项到最后一项:
[-1]代表取列表的最后一个元素:
example=[1,2,3,4,5,6,7,8,9,10]print(example[-1])---------结果---------10[:-1]代表取除了最后一个元素的所有元素:
[::-1]代表取整个列表的相反列表:
example=[1,2,3,4,5,6,7,8,9,10]print(example[::-1])---------结果---------[10,9,8,7,6,5,4,3,2,1][1:]代表从第二个元素意指读取到最后一个元素:
[4::-1]代表取下标为4(即第五个元素)的元素和之前的元素反转读取:
example=[1,2,3,4,5,6,7,8,9,10]print(example[4::-1])---------结果---------[5,4,3,2,1]【十】Python中如何进行异常处理?一般情况下,在Python无法正常处理程序时就会发生一个异常。异常在Python中是一个对象,表示一个错误。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。
捕捉异常可以使用try,except和finally语句。
try和except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
remove,del以及pop都可以用于删除列表、字符串等里面的元素,但是具体用法并不相同。
>>>a=[0,1,2,1,3]>>>a.remove(1)>>>a[0,2,1,3]>>>a=[0,1,2,1,3]>>>dela[1][0,2,1,3]>>>a=[0,1,2,1,3]>>>a.pop(1)1>>>a[0,2,1,3]【十二】C/C++中野指针的概念?野指针也叫空悬指针,不是指向null的指针,是未初始化或者未清零的指针。
产生原因:
解决办法:
内存泄漏是指己动态分配的堆内存由于某种原因导致程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
解决方法:
造成内存泄漏的主要原因是在使用new或malloc动态分配堆上的内存空间,而并未使用delete或free及时释放掉内存造成的。所以解决方法就是注意new/delete和malloc/free一定要配套使用。
面向对象(ObjectOrientedProgramming,OOP)编程模型首先抽象出各种对象(各种类),并专注于对象与对象之间的交互,对象涉及的方法和属性都封装在对象内部。
面向对象的编程思想是一种依赖于类和对象概念的编程方式,一个形象的例子是将大象装进冰箱:
面向过程(ProcedureOrientedProgramming,POP)编程模型是将问题分解成若干步骤(动作),每个步骤(动作)用一个函数来实现,在使用的时候,将数据传递给这些函数。
面向过程的编程思想通常采用自上而下、顺序执行的方式进行,一个形象的例子依旧是将大象装进冰箱:
vector是封装动态数组的顺序容器。
成员函数:
queue是容器适配器,他是FIFO(先进先出)的数据结构。
deque是有下标顺序容器,它允许在其首尾两段快速插入和删除。
集合基于红黑树实现,有自动排序的功能,并且不能存放重复的元素。
无序集合基于哈希表实现,不能存放重复的元素。元素类型必须可以比较是否相等,因为这可以确定元素什么时候相等。
unordered_map是关联容器,含有带唯一键的键-值对。
元素在内部不以任何特定顺序排序,而是组织进桶中。元素放进哪个桶完全依赖于其键的哈希。这允许对单独元素的快速访问,因为一旦计算哈希,则它准确指代元素所放进的桶。
C语言的指针让我们拥有了直接操控内存的强大能力,而C++在指针基础上又给我们提供了另外一个强力武器引用。
首先我们来看一下C++中对象的定义:对象是指一块能存储数据并具有某种类型的内存空间。
一个对象a,它有值和地址&a。运行程序时,计算机会为该对象分配存储空间,来存储该对象的值,我们通过该对象的地址,来访问存储空间中的值。
指针p也是对象,它同样有地址&p和存储的值p,只不过,p存储的是其他对象的地址。如果我们要以p中存储的数据为地址,来访问对象的值,则要在p前加引用操作符,即。
对象有常量(const)和变量之分,既然指针本身是对象,那么指针所存储的地址也有常量和变量之分,指针常量是指,指针这个对象所存储的地址是不可改变的,而常量指针的意思就是指向常量的指针。
实际上,我们也可以把引用看作是通过一个指针常量来实现的,指向的地址不变,地址里的内容可以改变。
接下来我们来看看指针和引用的具体区别:
我们可以看下面的代码:
“&”不仅能表示引用,还可以表示成地址,还有可以作为按位与运算符。这个要根据具体情况而定。比如上面的例子,等号左边的,被解释为引用,右边的被解释成取地址。
引用的操作加了比指针更多的限制条件,保证了整体代码的安全性和便捷性。引用的合理使用可以一定程度避免“指针满天飞”的情况,可以一定程度上提升程序鲁棒性。并且指针与引用底层实现都是一样的,不用担心两者的性能差距。
宏定义可以把一个名称指定成任何一个文本。在完成宏定义后,无论宏名称出现在源代码的何处,预处理器都会将其替换成指定的文本。
//define宏名文本#defineWeThinkIn666688889999//define宏名(参数)文本#defineR(a,b)(a/b)//注:带参数的宏替换最好在表达式整体上加括号,避免结果受其他运算影响。宏定义的优点:
宏定义和函数的区别:
我们可以使用typedef关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称以及其他类型等名称。
在工业界中,我们一般在如下两个场景中会见到typedef的身影。
typedef与宏定义的区别:
首先,异构现象是指不同计算平台之间,由于硬件结构(包括计算核心和内存),指令集和底层软件实现等方面的不同而有着不同的特性。
异构计算是指联合使用两个或者多个不同的计算平台,并进行协同运算。比如CPU和GPU的异构计算,TPU和GPU的异构计算以及TPU/GPU/CPU的异构计算等等。
通常的深度学习模型参数是FP32浮点型的,而模型量化主要是使用FP16,INT8以及INT4等低精度类型来保存模型参数,从而有效的降低模型计算量和内存占用,并将精度损失限制在一个可接受的范围内。
模型量化主要分在线量化和离线量化。在线量化在模型训练阶段采用量化方法进行量化。离线量化主要在模型离线工具(模型转换阶段)中采用量化方法进行量化。
工业界中主要使用离线量化作为通用模型量化的解决方案。
模型剪枝按照剪枝粒度可分为突触剪枝、神经元剪枝、权重矩阵剪枝等,主要是将权重矩阵中不重要的参数设置为0,结合稀疏矩阵来进行存储和计算。通常为了保证性能,需要逐步进行迭代剪枝,让精度损失限制在一个可接受的范围。
突触剪枝剪掉神经元之间的不重要的连接。对应到权重矩阵中,相当于将某个参数设置为0。
神经元剪枝则直接将某个节点直接裁剪。对应到权重矩阵中,相当于某一行和某一列置零。
除此之外,也可以将整个权重矩阵裁剪,每一层中只保留最重要的部分,这就是权重矩阵剪枝。相比突触剪枝和神经元剪枝,权重矩阵剪枝压缩率要大很多。
GPU乃至硬件侧的整体逻辑,是CV算法工作中必不可少的组成部分,也是算法模型所依赖的重要物理载体。
现在AI行业有个共识,认为是数据的爆发和算力的突破开启了深度学习在计算机视觉领域的“乘风破浪”,而其中的算力,主要就是指以GPU为首的计算平台。
GPU(GraphicalProcessingUnit)从最初用来进行图形处理和渲染(玩游戏),到通过CUDA/OpenCL库以及相应的工程开发之后,成为深度学习模型在学术界和工业界的底层计算工具,其有以下的一些特征:
在使用GPU时,在命令行输入nvidia-smi命令时会打印出一张表格,其中包含了GPU当时状态的所有参数信息。
CUDA/cuDNN/OpenCL科普小知识:
深度学习的端侧设备,又可以叫做边缘计算设备,深度学习特别是CV领域中,模型+端侧设备的组合能够加快业务的即时计算,决策和反馈能力,极大释放AI可能性。
深度学习的端侧设备主要由ARM架构的CPU+GPU/TPU/NPU等协处理器+整体功耗+外围接口+工具链等部分组成,也是算法侧对端侧设备进行选型要考虑的维度。
在实际业务中,根据公司的不同,算法工程师涉及到的硬件侧范围也会不一样。如果公司里硬件和算法由两个部门分别负责,那么算法工程师最多接触到的硬件侧知识就是硬件性能评估,模型转换与模型硬件侧验证,一些硬件高层API接口的开发与使用;如果公司里没有这么细分的部门,那么算法工程师可能就会接触到端侧的视频编解码,模型推理加速,Opencv,FFmpeg,TensorRT,工具链开发等角度的知识。
首先,整体上还是要将硬件侧工具化,把端侧设备当做算法模型的一个下游载体,会熟练的选型与性能评估更加重要。
端侧设备是算法产品整体解决方案中一个非常重要的模块,算法+硬件的范式将在未来的边缘计算与万物智能场景中持续发力。
在日常业务中,算法模型与端侧设备的适配性与兼容性是必须要考虑的问题,端侧设备是否兼容一些特殊的网络结构?算法模型转化并部署后,精度是否下降?功耗与耗时能否达标?等等都让算法工程师的模型设计逻辑有更多的抓手。
当设置静态多Batch后,如Batch=6,那么之后不管是输入2Batch还是4Batch,都会按照6Batch的预设开始申请资源。
而动态多Batch不用预设Batch数,会根据实际场景中的真实输入Batch数来优化资源的申请,提高端侧实际效率。
由于动态多Batch的高性能,通常Inference耗时和内存占用会比静态多Batch时要大。
ONNX是一种神经网络模型的框架,其最经典的作用是作为不同框架之间的中间件,成为模型表达的一个通用架构,来增加不同框架之间的交互性。
ONNX的优势:
TensorRT是一个高性能的深度学习前向Inference的优化器和运行的引擎。
TensorRT的核心:将现有的模型编译成一个engine,类似于C++的编译过程。在编译engine过程中,会为每一层的计算操作找寻最优的算子方法,将模型结构和参数以及相应kernel计算方法都编译成一个二进制engine,因此在部署之后大大加快了推理速度。
我们需要给TensorRT填充模型结构和参数,也就是解析我们自己的模型结构和参数文件,获取数据放到其中。官方给了三种主流框架模型格式的解析器(parser),分别是:ONNX,Caffe以及TensorFlow。
TensorRT的优势:
使用摄像头时,可能会出现图像边缘线条弯曲的情况,尤其是边缘部分是直线时,这种现象更为明显。比如摄像头显示画面中的门框、电线杆、墙面棱角、吊顶线等出现在边缘时,可能会有比较明显的弯曲现象,这种现象就叫做畸变。
畸变是指光学系统对物体所成的像相对于物体本身而言的失真程度,是光学透镜的固有特性,其直接原因是因为镜头的边缘部分和中心部分的放大倍率不一样导致。畸变并不影响像的清晰程度,只改变物体的成像形状,畸变是一种普遍存在的光学现象。
仿射变换是对图片进行平移,缩放,倾斜和旋转等操作,是一种二维坐标到二维坐标之间的线性变换。它保持了二维图形的“平直性”(直线经过变换之后依然是直线)和“平行性”(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。
透视变换是将图片投影到一个新的视平面,也称作投影映射。它是将二维图片投影到三维空间,再投回另一个二维空间的映射操作。
仿射变换和透视变换的最大区别:一个平行四边形,经过仿射变换后依然是平行四边形;而经过透视变换后只是一个四边形(不再平行了)。
可以使用频域滤波器如小波变换,傅里叶变换,余弦变换,形态学滤波等方法将图像高低频信息分离。
想要得到一张图像的梯度,要在图像的每个像素点处计算偏导数。因此一张图像在位置处的和方向上的梯度大小和分别计算为:
上述两个公式对所有的和的计算值可用下面的一维模版对的滤波得到。
用于计算梯度偏导数的滤波器模版,通常称之为梯度算子、边缘算子和边缘检测子等。
Roberts算子
Roberts算子又称为交叉微分算法,它是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正45度或负45度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。
Roberts算子的模板分为水平方向和垂直方向,如下式所示,从其模板可以看出,Roberts算子能较好的增强正负45度的图像边缘。
例如,下面给出Roberts算子的模板,在像素点处和方向上的梯度大小和分别计算为:
下图是Roberts算子的运行结果:
Prewitt算子
Prewitt算子是一种图像边缘检测的微分算子,其原理是利用特定区域内像素灰度值产生的差分实现边缘检测。由于Prewitt算子采用卷积模板对区域内的像素值进行计算,而Robert算子的模板为,故Prewitt算子的边缘检测结果在水平方向和垂直方向均比Robert算子更加明显。Prewitt算子适合用来识别噪声较多、灰度渐变的图像,其计算公式如下所示:
例如,下面给出Prewitt算子的模板,在像素点处和方向上的梯度大小和分别计算为:
Prewitt算子运行结果如下:
Sobel算子
Sobel算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值,根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。Sobel算子在Prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。
Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息。因为Sobel算子结合了高斯平滑和微分求导(分化),因此结果会具有更多的抗噪性,当对精度要求不是很高时,Sobel算子是一种较为常用的边缘检测方法。
Sobel算子的边缘定位更准确,常用于噪声较多、灰度渐变的图像。其算法模板如下面的公式所示,其中表示水平方向,表示垂直方向。
例如,下面给出Sobel算子的模板,在像素点处和方向上的梯度大小和分别计算为:
sobel算子的效果如下:
Roberts算子利用局部差分算子寻找边缘,边缘定位精度较高,但容易丢失一部分边缘,不具备抑制噪声的能力。该算子对具有陡峭边缘且含噪声少的图像效果较好,尤其是边缘正负45度较多的图像,但定位准确率较差。
Sobel算子考虑了综合因素,对噪声较多的图像处理效果更好,Sobel算子边缘定位效果不错,但检测出的边缘容易出现多像素宽度。
Prewitt算子对灰度渐变的图像边缘提取效果较好,而没有考虑相邻点的距离远近对当前像素点的影响,与Sobel算子类似,不同的是在平滑部分的权重大小有些差异。
拉普拉斯算子是一个二阶算子,比起一阶微分算子,二阶微分算子的边缘定位能力更强,锐化效果更好。
使用二阶微分算子的基本方法是定义一种二阶微分的离散形式,然后根据这个形式生成一个滤波模版,与图像进行卷积。
滤波器分各向同性滤波器和各向异性滤波器。各向同性滤波器与图像进行卷积时,图像旋转后响应不变,说明滤波器模版自身是对称的。如果是各向异性滤波器,当原图旋转90度时,原图某一点能检测出细节(突变)的,但是现在却检测不出来了,这说明滤波器不是对称的。由于拉普拉斯算子是最简单的各向同性微分算子,它具有旋转不变形。
对于二维图像,二阶微分最简单的定义(拉普拉斯算子定义):
对于任意阶微分算子都是线性算子,所以二阶微分算子和后面的一阶微分算子都可以用生成模版然后卷积的方式得出结果。
根据前面对二阶微分的定义有:
根据上面的定义,与拉普拉斯算子的定义相结合,我们可以得到:
也就是一个点的拉普拉斯的算子计算结果是上下左右的灰度和减去本身灰度的四倍。同样,可以根据二阶微分的不同定义,所有符号相反,也就是上式所有灰度值全加上负号,就是-1,-1,-1,-1,4。但是我们要注意,符号改变,锐化的时候与原图的加或减应当相对变化。上面是四临接的拉普拉斯算子,将这个算子旋转45度后与原算子相架,就变成了八邻域的算子了,也就是一个像素周围一圈8个像素的和与中间像素8倍的差,作为拉普拉斯计算结果。
因为要强调图像中突变(细节),所以平滑灰度的区域,无响应,即模版系数的和为0,也是二阶微分的必备条件。
最后的锐化公式:
其中,是输出,为原始图像,是系数,用来对细节添加的多少进行调节。
我们接下来用更加形象的图像来解释拉普拉斯算子的有效性。
在边缘部分,像素值出现”跳跃“或者较大的变化。下图(a)中灰度值的”跃升”表示边缘的存在。如果使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在(这里显示为高峰值)图(b)。
如果在边缘部分求二阶导数会出现什么情况呢,图(c)所示。
我们会发现在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。但是,二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),但是我们可以过滤掉这些点。
为了更适合于数字图像处理,我们如上面的式子所示,将其表示成了离散形式。为了更好的进行变成,我们也可以将其表示成模版的形式:
上图(a)表示离散拉普阿拉斯算子的模版,(b)表示其扩展模版,(c)则分别表示其他两种拉普拉斯的实现模版。
从模版形式中容易看出,如果在图像中一个较暗的区域中出现了一个亮点,那么用拉普拉斯运算就会使这个亮点变得更亮。因为图像中的边缘就是那些灰度发生跳变的区域,所以拉普拉斯锐化模板在边缘检测中很有用。
一般增强技术对于陡峭的边缘和缓慢变化的边缘很难确定其边缘线的位置。但此算子却可用二次微分正峰和负峰之间的过零点来确定,对孤立点或端点更为敏感,因此特别适用于以突出图像中的孤立点、孤立线或线端点为目的的场合。同梯度算子一样,拉普拉斯算子也会增强图像中的噪声,有时用拉普拉斯算子进行边缘检测时,可将图像先进行平滑处理。
图像锐化处理的作用是使灰度反差增强,从而使模糊图像变得更加清晰。图像模糊的实质就是图像受到平均运算或积分运算,因此可以对图像进行逆运算,如微分运算能够突出图像细节,使图像变得更为清晰。由于拉普拉斯是一种微分算子,它的应用可增强图像中灰度突变的区域,减弱灰度的缓慢变化区域。因此,锐化处理可选择拉普拉斯算子对原图像进行处理,产生描述灰度突变的图像,再将拉普拉斯图像与原始图像叠加而产生锐化图像。
这种简单的锐化方法既可以产生拉普拉斯锐化处理的效果,同时又能保留背景信息,将原始图像叠加到拉普拉斯变换的处理结果中去,可以使图像中的各灰度值得到保留,使灰度突变处的对比度得到增强,最终结果是在保留图像背景的前提下,突现出图像中小的细节信息。但其缺点是对图像中的某些边缘产生双重响应。
最后我们来看看拉普拉斯算子的效果:
通常其他图像读取函数读取图片的时候是按RGB格式读取,但在OpenCV在读取图片时,是按BGR读取的。
均值滤波也称为线性滤波,其采用的主要方法为邻域平均法。线性滤波的基本原理是用均值代替原图像中的各个像素值,即对待处理的当前像素点,选择一个模板,该模板由其近邻的若干像素组成,求模板中所有像素的均值,再把该均值赋予当前像素点,作为处理后图像在该点上的灰度值,即,为该模板中包含当前像素在内的像素总个数。这样的方法可以平滑图像,速度快,算法简单。但是无法去掉噪声,但能微弱的减弱它。
中值滤波是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。具体实现过程如下:
ps命令(ProcessStatus)是最基本同时也是非常强大的进程查看命令。
这个指令可以显示进程详细的状态。
参数解释:
如果直接用ps命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。
grep(globalsearchregularexpression(RE)andprintouttheline,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
例如我想要查看Python的所有进程,可以在终端输入如下命令:
我们可以使用kill命令来结束进程。
如下面的指令所示:
Git是当前主流的一种开源分布式版本控制系统,可以有效、快速的进行项目版本管理。
Git没有中央服务器,不同于SVN这种需要中央服务器的集中式版本控制系统。
Git的功能:版本控制(版本管理,远程仓库,分支协作)
Git的工作流程:
Git的常用命令:
gitinit创建仓库gitclone克隆github上的项目到本地gitadd添加文件到缓存区gitcommit将缓存区内容添加到仓库中GitLabGitLab是一个基于Git实现的在线代码仓库软件,可以基于GitLab搭建一个类似于GitHub的仓库,但是GitLab有完善的管理界面和权限控制,有较高的安全性,可用于企业和学校等场景。
SVN全名Subversion,是一个开源的版本控制系统。不同于Git,SVN是集中式版本控制系统。
SVN只有一个集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
SVN的特点是安全,效率,资源共享。
SVN的常用操作:
协程(Coroutine,又称微线程)运行在线程之上,更加轻量级,协程并没有增加线程总数,只是在线程的基础之上通过分时复用的方式运行多个协程,大大提高工程效率。
协程的特点:
协程适用于有大量I/O操作业务的场景,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。
在协程中尽量不要调用阻塞I/O的方法,比如打印,读取文件等,除非改为异步调用的方式,并且协程只有在I/O密集型的任务中才会发挥作用。
Linux系统是一种操作系统(OperatingSystem简称OS),它是软件的一部分,是硬件基础上的第一层软件,即硬件和应用软件沟通的桥梁。
Linux系统系统会控制其他程序运行,管理系统资源,提供最基本的计算功能,如管理及配置内存、决定系统资源供需的优先次序等,同时还提供一些基本的服务程序。Linux系统内核指的是提供硬件抽象层、硬盘及文件系统控制及多任务功能的系统核心程序。Linux发行套件系统是由Linux系统内核与各种常用应用软件的集合产品。
在Linux系统中一切都是文件。在linux系统中,目录、字符设备、块设备、套接字、打印机等都被抽象成了文件,Linux系统中的一切文件都是从“根(/)”目录开始的,并按照树形结构来存放文件,且定义了常见目录的用途,文件和目录名称严格区分大小写。
POC(ProofofConcept),即概念验证。通常是企业进行产品选型时或开展外部实施项目前,进行的一种产品或供应商能力验证工作。主要验证内容:
验证内容归根结底,就是证明企业选择的产品或供应商能够满足企业提出的需求,并且提供的信息准确可靠。
POC测试工作的前提:
POC测试工作参与者:
使用用户代表、业务负责人、项目负责人、技术架构师、测试工程师、商务经理等。
POC测试工作准备文档:
POC测试工作的主要流程:
第一阶段:工作启动
由商务或者对外代表对供应商发布正式邀请并附POC测试工作说明。
建立POC协同群。以满足快速沟通,应答。
涉及到私有化部署的,需要收集供应商部署环境要求,并与供应商一起进行部署工作,同时企业参与人员对部署工作情况做好记录。
第二阶段:产品宣讲及现场集中测试
供应商根据企业提供的POC测试工作说明及相应测试模块的用例或方案进行产品现场测试论证。
企业参与人员参与功能测试,并填写记录和意见。此阶段供应商往往需进行现场操作指导。
第三阶段:技术测评
涉及到场景demo设计的,建议企业对实施人员能力、实施时长、实施准确性进行对比。
第四阶段:间歇性测试工作
第五阶段:商务验证
供应商根据企业提供的商务测评方案,积极配合工作。涉及到客户核实的,还需要企业进行考证。该部分工作也是从第一阶段启动时,就可以开始了。
第六阶段:背书归档、分析总结
POC工作按照不同企业和程度,测试的方式和投入力度不一样。但是目的都是相同的——验证产品或供应商能力是否满足企业需求。
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。
容器完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的app),更重要的是容器性能开销极低。
Docker的应用场景:
Docker包括三个基本单元:
Docker客户端
Docker客户端非常简单,我们可以直接输入docker命令来查看到Docker客户端的所有命令选项。也可以通过命令dockercommand--help更深入的了解指定的Docker命令使用方法。
docker容器使用
获取本地没有的镜像。如果我们本地没有我们想要的镜像,我们可以使用dockerpull命令来载入镜像:
启动容器。以下命令使用ubuntu镜像启动一个容器,参数为以命令行模式进入该容器:
dockerrun-it镜像/bin/bash参数解释:
我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。
要退出终端,直接输入exit或者CTRL+D。
启动已经停止运行的容器。查看所有的容器的命令如下:
我们也可以用dockerps命令查看正在运行的容器。
dockerps我们可以使用dockerstart启动一个已停止的容器:
想要后台运行容器,我们可以过-d指定容器的运行模式:
dockerrun-itd--name指定创建的容器名容器/bin/bash加了-d参数默认不会进入容器,想要进入容器需要使用下面的指令进入容器:
想要停止容器,其命令如下:
dockerstop容器ID停止的容器重启命令:
删除容器:
dockerrm-f容器IDDocker镜像使用列出镜像列表。我们可以使用dockerimages来列出本地主机上的镜像。
各个参数解释:
查找镜像:
dockersearch镜像各个参数解释:
删除镜像:
docker镜像的更新
在启动docker镜像后,写入一些文件、代码、更新软件等等操作后,退出docker镜像,之后在终端输入如下命令:
dockercommit-m='...'-a='...'容器ID指定要创建的目标镜像名称参数解释:
接着可以用dockerimages查看镜像是否更新成功。(注意:不要创建名称已存在的镜像,这样会使存在的镜像名称为none,从而无法使用)
镜像名称修改和添加新标签
更改镜像名称(REPOSITORY):
更改镜像tag,不修改名称:
dockertagIMAGEID(镜像id)REPOSITORY:TAG(仓库:标签)Docker容器和本机之间的文件传输主机和容器之间传输文件的话需要用到容器的ID全称。
从本地传输到容器中:
从容器传输到本地上:
dockercp容器name:/root/(容器路径)本地文件路径Docker挂载宿主机文件目录docker可以支持把一个宿主机上的目录挂载到镜像的目录中。
在启动docker镜像时,输入如下命令:
通过-v参数,冒号前为宿主机目录,冒号后为镜像内挂载的路径,必须为绝对路径。
如果宿主机目录不存在,则会自动生成,镜像里也是同理。
默认挂载的路径权限为读写。如果指定为只读可以用:ro
dockerrun-it-v/宿主机绝对路径:/镜像内挂载绝对路径:ro容器REPOSITORY/bin/bash【九】深度学习中常用的文件格式汇总【十】TCP和UDP的区别?----【开放性问题】----这些问题基于Rocky的思考提出,希望除了能给大家带来面试的思考,也能给大家带来面试以外的思考。这些问题没有标准答案,我相信每个人心中都有自己灵光一现的创造,你的呢?
这是一个非常有价值的问题,随着AI技术进入全面的落地阶段,如何将AI技术与公司定位相适配,利用好AI技术并产生更多现金流闭环,成为未来各个公司重点考虑的问题。
深度学习发展至今,工业界,学术界,投资界都对其优势和局限有所判断了,基于此,各个公司的AILab也进入了全新的阶段,如何调整架构,如何改变定位,如何转变认知,是一件需要思考的事情。
这是一个非常有价值的问题,随着宏观环境的变化,各个公司更加注重现金流,纯研究院等部门会被持续优化,这时如何高效的转化竞赛侧与研究侧的实用性成果,以及如何将业务侧经验向竞赛侧与研究侧延伸,成为各个公司重点考虑的问题。
深度学习发展至今,工业界,学术界,投资界都对其优势和局限有所判断了。作为面试者,也需要对深度学习的趋势有所把握,不仅仅能在面试中进行交流,也能作为职业发展规划的一个先验知识。
在“CV兵器”基本上都是开源的情况下,数据成为了支持业务迭代最重要的一部分,如何建立数据护城河,形成业务与数据双向正反馈,是AI行业从业者必须要面对的课题。
这个问题不仅可以考察面试者,面试者也可以用来反向判断面试官及其背后公司的运行逻辑。陷入demo业务,一次性业务以及外包业务的循环中是无法成长的,也不利于建立业务/产品的护城河。知道了这一点,那么如何去选择部门,如何去选择公司,如何去看需求,就变成了非常值得研究的事情。
AI除了落地,还有持续的愿景。这个问题启发我们,除了日常的技术深耕,也需要思考未来AI的发展趋势。如何扩大公司的AI影响力,是AI从业人员有必要去思考的一个维度。
这是一个非常有价值的问题,随着的AI更加务实的落地,业务场景和算法解决方案的适配是非常关键的。如何获得针对性设计的能力,需要对现有现金流业务的深耕,以及提升产品侧视野的广度。
这是一个非常值得去思考的问题,在AI算法大范围落地之后,随之而来的就是安全风险,我相信AI安全将会是一个很大的课题。
GAN的思想无疑让人眼前一亮,在目标检测,图像分割,图像分类等领域都有其辅助的身影,并在数据增强领域站在了舞台中央。
这是一个非常好的问题,既可以反映出面试者自身的CV学习入场逻辑,也能反映出面试者对于自己的学习过程是否有总结与提炼。
我觉得这个问题可以从我经常提到的业务侧,竞赛侧,研究侧三个维度去思考和表达。在不同维度下,CV算法技术的发展前景会更加真实的体现,我们也能更从容地去表述我们的观点。