【三年面试五年模拟】算法工程师的独孤九剑秘籍(第八式)
写在前面
【三年面试五年模拟】栏目专注于分享CV算法与机器学习相关的经典&&必备&&高价值的面试知识点,并向着更实战,更真实,更从容的方向不断优化迭代。也欢迎大家提出宝贵的意见或优化ideas,一起交流学习💪
大家好,我是Rocky。
本文是“三年面试五年模拟”之独孤九剑秘籍的第八式,之前我们将独孤九剑秘籍前六式进行汇总梳理成汇总篇。由于本系列都是Rocky在工作之余进行整理总结,难免有疏漏与错误之处,欢迎大家对可优化的部分进行指正,我将在后续的优化迭代版本中及时更正。
本系列接下来的每一篇文章都将以独孤九剑秘籍框架的逻辑展开,考虑到易读性与文章篇幅,一篇文章中只选取每个分支技能树中的2-3个经典&&高价值知识点和面试问题,并配以相应的参考答案(精简版),供大家参考。
希望独孤九剑秘籍的每一式都能让江湖中的英雄豪杰获益。
So,enjoy(与本文的BGM一起食用更佳哦):
干货篇
----【目录先行】----
深度学习基础:
Spectral Normalization的相关知识
激活函数的作用,常用的激活函数有哪些?
经典模型&&热门模型:
FPN(Feature Pyramid Network)的相关知识
SPP(Spatial Pyramid Pooling)的相关知识
机器学习基础:
KL散度相关概念
JS散度相关概念
Python/C/C++知识:
Python中常见的切片操作
C/C++中指针和引用的区别
模型部署:
端侧静态多Batch和动态多Batch的区别?
优化模型端侧性能的一些方法
图像处理基础:
有哪些常用一阶微分梯度算子?
拉普拉斯算子的相关概念
计算机基础:
POC验证测试的概念
Docker的相关概念及常用命令
开放性问题:
对AI安全相关技术的发展前景的看法?
对GAN算法技术的发展前景的看法?
----【深度学习基础】----
【一】Spectral Normalization的相关知识
Spectral Normalization是一种wegiht Normalization技术,和weight-clipping以及gradient penalty一样,也是让模型满足1-Lipschitz条件的方式之一。
Lipschitz(利普希茨)条件限制了函数变化的剧烈程度,即函数的梯度,来确保统计的有界性。因此函数更加平滑,在神经网络的优化过程中,参数变化也会更稳定,不容易出现梯度爆炸。
Lipschitz条件的约束如下所示:
其中代表一个常数,即利普希茨常数。若
,则是1-Lipschitz。
在GAN领域,Spectral Normalization有很多应用。在WGAN中,只有满足1-Lipschitz约束时,W距离才能转换成较好求解的对偶问题,使得WGAN更加从容的训练。
如果想让矩阵A映射:满足K-Lipschitz连续,K的最小值为
(
是
的最大特征值),那么要想让矩阵A满足1-Lipschitz连续,只需要在A的所有元素上同时除以
(Spectral norm)。
Spectral Normalization实际上在做的事,是将每层的参数矩阵除以自身的最大奇异值,本质上是一个逐层SVD的过程,但是真的去做SVD就太耗时了,所以采用幂迭代的方法求解。过程如下图所示:
得到谱范数后,每个参数矩阵上的参数皆除以它,以达到Normalization的目的。
【二】激活函数的作用,常用的激活函数有哪些?
激活函数的作用
激活函数可以引入非线性因素,提升网络的学习表达能力。
常用的激活函数
Sigmoid 激活函数
函数的定义为:
如下图所示,其值域为 。也就是说,输入的每个神经元、节点都会被缩放到一个介于
和
之间的值。
当大于零时输出结果会趋近于
,而当
小于零时,输出结果趋向于
,由于函数的特性,经常被用作二分类的输出端激活函数。
Sigmoid的导数:
当时,
。
Sigmoid的优点:
- 平滑
- 易于求导
- 可以作为概率,辅助解释模型的输出结果
Sigmoid的缺陷:
- 当输入数据很大或者很小时,函数的梯度几乎接近于0,这对神经网络在反向传播中的学习非常不利。
- Sigmoid函数的均值不是0,这使得神经网络的训练过程中只会产生全正或全负的反馈。
- 导数值恒小于1,反向传播易导致梯度消失。
Tanh激活函数
Tanh函数的定义为:
如下图所示,值域为 。
Tanh的优势:
- Tanh函数把数据压缩到-1到1的范围,解决了Sigmoid函数均值不为0的问题,所以在实践中通常Tanh函数比Sigmoid函数更容易收敛。在数学形式上其实Tanh只是对Sigmoid的一个缩放形式,公式为
(
是Sigmoid的函数)。
- 平滑
- 易于求导
Tanh的导数:
当时,
。
由Tanh和Sigmoid的导数也可以看出Tanh导数更陡,收敛速度比Sigmoid快。
Tanh的缺点:
导数值恒小于1,反向传播易导致梯度消失。
Relu激活函数
Relu激活函数的定义为:
如下图所示,值域为 。
ReLU的优势:
- 计算公式非常简单,不像上面介绍的两个激活函数那样涉及成本更高的指数运算,大量节约了计算时间。
- 在随机梯度下降中比Sigmoid和Tanh更加容易使得网络收敛。
- ReLU进入负半区的时候,梯度为0,神经元此时会训练形成单侧抑制,产生稀疏性,能更好更快地提取稀疏特征。
- Sigmoid和Tanh激活函数的导数在正负饱和区的梯度都会接近于0,这会造成梯度消失,而ReLU函数大于0部分都为常数保持梯度不衰减,不会产生梯度消失现象。
稀疏:在神经网络中,这意味着激活的矩阵含有许多0。这种稀疏性能让我们得到什么?这能提升时间和空间复杂度方面的效率,常数值所需空间更少,计算成本也更低。
ReLU的导数:
通常时,给定其导数为
和
。
ReLU的不足:
- 训练中可能会导致出现某些神经元永远无法更新的情况。其中一种对ReLU函数的改进方式是LeakyReLU。
- 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的优势:
- 能避免ReLU中一些神经元无法更新的情况。
- 能得到负值输出。
ELU的不足:
- 包含指数运算,计算时间长。
- 无法避免梯度爆炸问题。
- 神经网络无法学习
值。
----【经典模型&&热门模型】----
【一】FPN(Feature Pyramid Network)的相关知识
FPN的创新点
- 设计特征金字塔的结构
- 提取多层特征(bottom-up,top-down)
- 多层特征融合(lateral connection)
设计特征金字塔的结构,用于解决目标检测中的多尺度问题,在基本不增加原有模型计算量的情况下,大幅度提升小物体(small object)的检测性能。
原来很多目标检测算法都是只采用高层特征进行预测,高层的特征语义信息比较丰富,但是分辨率较低,目标位置比较粗略。假设在深层网络中,最后的高层特征图中一个像素可能对应着输出图像的像素区域,那么小于
像素的小物体的特征大概率已经丢失。与此同时,低层的特征语义信息比较少,但是目标位置准确,这是对小目标检测有帮助的。FPN将高层特征与底层特征进行融合,从而同时利用低层特征的高分辨率和高层特征的丰富语义信息,并进行了多尺度特征的独立预测,对小物体的检测效果有明显的提升。
传统解决这个问题的思路包括:
- 图像金字塔(image pyramid),即多尺度训练和测试。但该方法计算量大,耗时较久。
- 特征分层,即每层分别输出对应的scale分辨率的检测结果,如SSD算法。但实际上不同深度对应不同层次的语义特征,浅层网络分辨率高,学到更多是细节特征,深层网络分辨率低,学到更多是语义特征,单单只有不同的特征是不够的。
FPN的主要模块
- Bottom-up pathway(自底向上线路)
- Top-down path(自顶向下线路)
- Lareral connections(横向链路)
Bottom-up pathway(自底向上线路)
自底向上线路是卷积网络的前向传播过程。在前向传播过程中,feature map的大小可以在某些层发生改变。
Top-down path(自顶向下线路)和Lareral connections(横向链路)
自顶向下线路是上采样的过程,而横向链路是将自顶向下线路的结果和自底向上线路的结构进行融合。
上采样的feature map与相同大小的下采样的feature map进行逐像素相加融合(element-wise addition),其中自底向上的feature先要经过卷积层,目的是为了减少通道维度。
FPN应用
论文中FPN直接在Faster R-CNN上进行改进,其backbone是ResNet101,FPN主要应用在Faster R-CNN中的RPN和Fast R-CNN两个模块中。
FPN+RPN:
将FPN和RPN结合起来,那RPN的输入就会变成多尺度的feature map,并且在RPN的输出侧接多个RPN head层用于满足对anchors的分类和回归。
FPN+Fast R-CNN:
Fast R-CNN的整体结构逻辑不变,在backbone部分引入FPN思想进行改造。
【二】SPP(Spatial Pyramid Pooling)的相关知识
在目标检测领域,很多检测算法最后使用了全连接层,导致输入尺寸固定。当遇到尺寸不匹配的图像输入时,就需要使用crop或者warp等操作进行图像尺寸和算法输入的匹配。这两种方式可能出现不同的问题:裁剪的区域可能没法包含物体的整体;变形操作造成目标无用的几何失真等。
而SPP的做法是在卷积层后增加一个SPP layer,将features map拉成固定长度的feature vector。然后将feature vector输入到全连接层中。以此来解决上述的尴尬问题。
SPP的优点:
- SPP可以忽略输入尺寸并且产生固定长度的输出。
- SPP使用多种尺度的滑动核,而不是只用一个尺寸的滑动窗口进行pooling。
- SPP在不同尺寸feature map上提取特征,增大了提取特征的丰富度。
在YOLOv4中,对SPP进行了创新使用,Rocky已在【Make YOLO Great Again】YOLOv1-v7全系列大解析(Neck篇)中详细讲解,大家可按需取用~
----【机器学习基础】----
【一】KL散度相关概念
KL散度(Kullback-Leibler divergence),可以以称作相对熵(relative entropy)或信息散度(information divergence)。KL散度的理论意义在于度量两个概率分布之间的差异程度,当KL散度越大的时候,说明两者的差异程度越大;而当KL散度小的时候,则说明两者的差异程度小。如果两者相同的话,则该KL散度应该为0。
接下来我们举一个具体的🌰:
我们设定两个概率分布分别为和
,在设定为连续随机变量的前提下,他们对应的概率密度函数分别为
和
。如果我们用
去近似
,则KL散度可以表示为:
从上面的公式可以看出,当且仅当时,
。此外我们可以知道KL散度具备非负性,即
。并且从公式中我们也发现,KL散度不具备对称性,也就是说
对于
的KL散度并不等于
对于
的KL散度。因此,KL散度并不是一个度量(metric),即KL散度并非距离。
我们再来看看离散的情况下用去近似
的KL散度的公式:
接下来我们对上面的式子进行展开:
最后得到的第一项称作和
的交叉熵(cross entropy),后面一项就是熵。
在信息论中,熵代表着信息量,代表着基于
分布自身的编码长度,也就是最优的编码长度(最小字节数)。而
则代表着用
的分布去近似
分布的信息,自然需要更多的编码长度。并且两个分布差异越大,需要的编码长度越大。所以两个值相减是大于等于0的一个值,代表冗余的编码长度,也就是两个分布差异的程度。所以KL散度在信息论中还可以称为相对熵(relative entropy)。
对深度学习中的生成模型来说,我们希望最小化真实数据分布与生成数据分布之间的KL散度,从而使得生成数据尽可能接近真实数据的分布。在实际场景中,我们是几乎不可能知道真实数据分布的,我们使用训练数据形成的生成分布在逼近
。
【二】JS散度相关概念
JS散度全称Jensen-Shannon散度,简称JS散度。在概率统计中,JS散度也与KL散度一样具备了测量两个概率分布相似程度的能力,它的计算方法基于KL散度,继承了KL散度的非负性等,但有一点重要的不同,JS散度具备了对称性。
JS散度的公式如下所示,我们设定两个概率分布为和
,另外我们还设定
,KL为KL散度公式。
如果我们把KL散度公式写入展开的话,结果如下所示:
深度学习中使用KL散度和JS散度进行度量的时候存在一个问题:
如果两个分布,
离得很远,完全没有重叠的时候,那么KL散度值是没有意义的,而JS散度值是一个常数
。这对以梯度下降为基础的深度学习算法有很大影响,这意味梯度为0,即梯度消失。
----【Python/C/C++知识】----
【一】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项到最后一项:
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(example[6:]) ---------结果--------- [7, 8, 9, 10]
[-1]代表取列表的最后一个元素:
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(example[-1]) ---------结果--------- 10
[:-1]代表取除了最后一个元素的所有元素:
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(example[:-1]) ---------结果--------- [1, 2, 3, 4, 5, 6, 7, 8, 9]
[::-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:]代表从第二个元素意指读取到最后一个元素:
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(example[1:]) ---------结果--------- [2, 3, 4, 5, 6, 7, 8, 9, 10]
[4::-1]代表取下标为4(即第五个元素)的元素和之前的元素反转读取:
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(example[4::-1]) ---------结果--------- [5, 4, 3, 2, 1]
【二】C/C++中指针和引用的区别
C语言的指针让我们拥有了直接操控内存的强大能力,而C++在指针基础上又给我们提供了另外一个强力武器引用。
首先我们来看一下C++中对象的定义:对象是指一块能存储数据并具有某种类型的内存空间。
一个对象a,它有值和地址&a。运行程序时,计算机会为该对象分配存储空间,来存储该对象的值,我们通过该对象的地址,来访问存储空间中的值。
指针p也是对象,它同样有地址&p和存储的值p,只不过,p存储的是其他对象的地址。如果我们要以p中存储的数据为地址,来访问对象的值,则要在p前加引用操作符,即
。
对象有常量(const)和变量之分,既然指针本身是对象,那么指针所存储的地址也有常量和变量之分,指针常量是指,指针这个对象所存储的地址是不可改变的,而常量指针的意思就是指向常量的指针。
我们可以把引用理解成变量的别名。定义一个引用的时候,程序把该引用和它的初始值绑定在一起,而不是拷贝它。计算机必须在声明引用r的同时就要对它初始化,并且r一经声明,就不可以再和其他对象绑定在一起了。
实际上,我们也可以把引用看作是通过一个指针常量来实现的,指向的地址不变,地址里的内容可以改变。
接下来我们来看看指针和引用的具体区别:
- 指针是一个新的变量,要占用存储空间,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量。而引用只是一个别名,还是变量本身,不占用具体存储空间,只有声明没有定义。对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的。
- 引用只有一级,而指针可以有多级。
- 指针传参的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作。引用传参的时候,传进来的就是变量本身,因此变量可以被修改。
- 引用它一定不为空,因此相对于指针,它不用检查它所指对象是否为空,这样就提高了效率。
- 引用必须初始化,而指针可以不初始化。
我们可以看下面的代码:
int a,b,*p,&r=a;//正确 r=3;//正确:等价于a=3 int &rr;//出错:引用必须初始化 p=&a;//正确:p中存储a的地址,即p指向a *p=4;//正确:p中存的是a的地址,对a所对应的存储空间存入值4 p=&b//正确:p可以多次赋值,p存储b的地址
“&”不仅能表示引用,还可以表示成地址,还有可以作为按位与运算符。这个要根据具体情况而定。比如上面的例子,等号左边的,被解释为引用,右边的被解释成取地址。
引用的操作加了比指针更多的限制条件,保证了整体代码的安全性和便捷性。引用的合理使用可以一定程度避免“指针满天飞”的情况,可以一定程度上提升程序鲁棒性。并且指针与引用底层实现都是一样的,不用担心两者的性能差距。
----【模型部署】----
【一】端侧静态多Batch和动态多Batch的区别
当设置静态多Batch后,如Batch=6,那么之后不管是输入2Batch还是4Batch,都会按照6Batch的预设开始申请资源。
而动态多Batch不用预设Batch数,会根据实际场景中的真实输入Batch数来优化资源的申请,提高端侧实际效率。
由于动态多Batch的高性能,通常Inference耗时和内存占用会比静态多Batch时要大。
【二】优化模型端侧性能的一些方法
- 设计能最大限度挖掘AI协处理器性能的模型结构。
- 多模型共享计算内存。
- 减少模型分支结构,减少模型元素级操作。
- 卷积层的输入和输出特征通道数相等时MAC最小,以提升模型Inference速度。
----【图像处理基础】----
【一】有哪些常用一阶微分梯度算子?
梯度算子
想要得到一张图像的梯度,要在图像的每个像素点处计算偏导数。因此一张图像在
位置处的
和
方向上的梯度大小
和
分别计算为:
上述两个公式对所有的和
的计算值可用下面的一维模版对
的滤波得到。
用于计算梯度偏导数的滤波器模版,通常称之为梯度算子、边缘算子和边缘检测子等。
经典一阶梯度算子
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算子
Roberts算子利用局部差分算子寻找边缘,边缘定位精度较高,但容易丢失一部分边缘,不具备抑制噪声的能力。该算子对具有陡峭边缘且含噪声少的图像效果较好,尤其是边缘正负45度较多的图像,但定位准确率较差。
Sobel算子
Sobel算子考虑了综合因素,对噪声较多的图像处理效果更好,Sobel 算子边缘定位效果不错,但检测出的边缘容易出现多像素宽度。
Prewitt算子
Prewitt算子对灰度渐变的图像边缘提取效果较好,而没有考虑相邻点的距离远近对当前像素点的影响,与Sobel 算子类似,不同的是在平滑部分的权重大小有些差异。
【二】拉普拉斯算子的相关概念
拉普拉斯算子是一个二阶算子,比起一阶微分算子,二阶微分算子的边缘定位能力更强,锐化效果更好。
使用二阶微分算子的基本方法是定义一种二阶微分的离散形式,然后根据这个形式生成一个滤波模版,与图像进行卷积。
滤波器分各向同性滤波器和各向异性滤波器。各向同性滤波器与图像进行卷积时,图像旋转后响应不变,说明滤波器模版自身是对称的。如果是各向异性滤波器,当原图旋转90度时,原图某一点能检测出细节(突变)的,但是现在却检测不出来了,这说明滤波器不是对称的。由于拉普拉斯算子是最简单的各向同性微分算子,它具有旋转不变形。
对于二维图像,二阶微分最简单的定义(拉普拉斯算子定义):
对于任意阶微分算子都是线性算子,所以二阶微分算子和后面的一阶微分算子都可以用生成模版然后卷积的方式得出结果。
根据前面对二阶微分的定义有:
根据上面的定义,与拉普拉斯算子的定义相结合,我们可以得到:
也就是一个点的拉普拉斯的算子计算结果是上下左右的灰度和减去本身灰度的四倍。同样,可以根据二阶微分的不同定义,所有符号相反,也就是上式所有灰度值全加上负号,就是-1,-1,-1,-1,4。但是我们要注意,符号改变,锐化的时候与原图的加或减应当相对变化。上面是四临接的拉普拉斯算子,将这个算子旋转45度后与原算子相架,就变成了八邻域的算子了,也就是一个像素周围一圈8个像素的和与中间像素8倍的差,作为拉普拉斯计算结果。
因为要强调图像中突变(细节),所以平滑灰度的区域,无响应,即模版系数的和为0,也是二阶微分的必备条件。
最后的锐化公式:
其中,是输出,
为原始图像,
是系数,用来对细节添加的多少进行调节。
我们接下来用更加形象的图像来解释拉普拉斯算子的有效性。
在边缘部分,像素值出现”跳跃“或者较大的变化。下图(a)中灰度值的”跃升”表示边缘的存在。如果使用一阶微分求导我们可以更加清晰的看到边缘”跃升”的存在(这里显示为高峰值)图(b)。
如果在边缘部分求二阶导数会出现什么情况呢,图(c)所示。
我们会发现在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。但是,二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),但是我们可以过滤掉这些点。
为了更适合于数字图像处理,我们如上面的式子所示,将其表示成了离散形式。为了更好的进行变成,我们也可以将其表示成模版的形式:
上图(a)表示离散拉普阿拉斯算子的模版,(b)表示其扩展模版,(c)则分别表示其他两种拉普拉斯的实现模版。
从模版形式中容易看出,如果在图像中一个较暗的区域中出现了一个亮点,那么用拉普拉斯运算就会使这个亮点变得更亮。因为图像中的边缘就是那些灰度发生跳变的区域,所以拉普拉斯锐化模板在边缘检测中很有用。
一般增强技术对于陡峭的边缘和缓慢变化的边缘很难确定其边缘线的位置。但此算子却可用二次微分正峰和负峰之间的过零点来确定,对孤立点或端点更为敏感,因此特别适用于以突出图像中的孤立点、孤立线或线端点为目的的场合。同梯度算子一样,拉普拉斯算子也会增强图像中的噪声,有时用拉普拉斯算子进行边缘检测时,可将图像先进行平滑处理。
图像锐化处理的作用是使灰度反差增强,从而使模糊图像变得更加清晰。图像模糊的实质就是图像受到平均运算或积分运算,因此可以对图像进行逆运算,如微分运算能够突出图像细节,使图像变得更为清晰。由于拉普拉斯是一种微分算子,它的应用可增强图像中灰度突变的区域,减弱灰度的缓慢变化区域。因此,锐化处理可选择拉普拉斯算子对原图像进行处理,产生描述灰度突变的图像,再将拉普拉斯图像与原始图像叠加而产生锐化图像。
这种简单的锐化方法既可以产生拉普拉斯锐化处理的效果,同时又能保留背景信息,将原始图像叠加到拉普拉斯变换的处理结果中去,可以使图像中的各灰度值得到保留,使灰度突变处的对比度得到增强,最终结果是在保留图像背景的前提下,突现出图像中小的细节信息。但其缺点是对图像中的某些边缘产生双重响应。
最后我们来看看拉普拉斯算子的效果:
----【计算机基础】----
【一】POC验证测试的概念
POC(Proof of Concept),即概念验证。通常是企业进行产品选型时或开展外部实施项目前,进行的一种产品或供应商能力验证工作。主要验证内容:
- 产品的功能。产品功能由企业提供,企业可以根据自己的需求提供功能清单,也可以通过与多家供应商交流后,列出自己所需要的功能。
- 产品的性能。性能指标也是由企业提供,并建议提供具体性能指标所应用的环境及硬件设备等测试环境要求。
- 产品的API适用性。
- 产品相关技术文档的规范性、完整性。
- 涉及到自定义功能研发的,还需要验证API开放性,供应商实施能力。
- 企业资质规模及企业实施案例等。
验证内容归根结底,就是证明企业选择的产品或供应商能够满足企业提出的需求,并且提供的信息准确可靠。
POC测试工作的前提:
- 前期调研充分,并已经对产品或供应商有了比较深入的沟通了解。
- 企业对自己的产品需求比较清晰。
POC测试工作参与者:
使用用户代表、业务负责人、项目负责人、技术架构师、测试工程师、商务经理等。
POC测试工作准备文档:
- POC测试工作说明文档。内容包括测试内容、测试要求(如私有化部署)、测试标准、时间安排等。
- 功能测试用例。主要确认功能可靠性,准确性。内容包括功能名称、功能描述等。
- 场景测试用例。主要测试企业团队实施响应速度、实施能力、集成能力。这部分通常按照企业需求而定,不建议太复杂,毕竟需要供应商实施,拖的太久企业耐性受到影响,时间也会被拉长。
- 技术测评方案。主要验证产品的性能、功能覆盖情况、集成效率、技术文档的质量。
- 商务测评方案。主要包括企业实力、企业技术人才能力、版权验证、市场背景、产品报价等。
POC测试工作的主要流程:
第一阶段:工作启动
由商务或者对外代表对供应商发布正式邀请并附POC测试工作说明。
建立POC协同群。以满足快速沟通,应答。
涉及到私有化部署的,需要收集供应商部署环境要求,并与供应商一起进行部署工作,同时企业参与人员对部署工作情况做好记录。
第二阶段:产品宣讲及现场集中测试
供应商根据企业提供的POC测试工作说明及相应测试模块的用例或方案进行产品现场测试论证。
企业参与人员参与功能测试,并填写记录和意见。此阶段供应商往往需进行现场操作指导。
第三阶段:技术测评
供应商根据企业提供的技术要求给出相关支持技术文档,企业进行现场对比,根据实际情况进行统计记录。并保留供应商提供的资料和对比记录。
涉及到场景demo设计的,建议企业对实施人员能力、实施时长、实施准确性进行对比。
第四阶段:间歇性测试工作
该阶段是在第一阶段启动时,就可以开始了。测试功能外,还包括关键用户使用的体验心得、易用性评价。该部分允许企业用户主观评价,建议可以扩大范围组织间歇性测试,并做好测试用户记录。间歇时间1天或者多天根据实际情况安排。
第五阶段:商务验证
供应商根据企业提供的商务测评方案,积极配合工作。涉及到客户核实的,还需要企业进行考证。该部分工作也是从第一阶段启动时,就可以开始了。
第六阶段:背书归档、分析总结
每个阶段的工作都需要记录好参与人、时间、工作时间,并将测试过程中企业的、供应商的文档分类归档。对每个阶段进行分析对比,总结评价。最后进行整体工作分析总结。
POC工作按照不同企业和程度,测试的方式和投入力度不一样。但是目的都是相同的——验证产品或供应商能力是否满足企业需求。
【二】Docker的相关概念及常用命令
Docker简介
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。
Docker可以打包代码以及相关的依赖到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。
容器完全使用沙箱机制,相互之间不会有任何接口(类似iPhone的app),更重要的是容器性能开销极低。
Docker的应用场景:
- Web应用的自动化打包和发布。
- 自动化测试和持续集成、发布。
- 在服务器环境中部署/调整数据库或其它的后台应用。
Docker架构
Docker包括三个基本单元:
- 镜像(Image):Docker镜像(Image),就相当于是一个root文件系统。比如官方镜像ubuntu:16.04就包含了完整的一套Ubuntu16.04最简系统的root文件系统。
- 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
- 仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。
Docker容器使用
Docker客户端
Docker客户端非常简单,我们可以直接输入docker命令来查看到Docker客户端的所有命令选项。也可以通过命令docker command --help更深入的了解指定的Docker命令使用方法。
docker
容器使用
获取本地没有的镜像。如果我们本地没有我们想要的镜像,我们可以使用 docker pull 命令来载入镜像:
docker pull 镜像
启动容器。以下命令使用ubuntu镜像启动一个容器,参数为以命令行模式进入该容器:
docker run -it 镜像 /bin/bash
参数解释:
- -i:允许你对容器内的标准输入 (STDIN) 进行交互。
- -t:在新容器内指定一个伪终端或终端。
- /bin/bash:放在镜像名后的是命令,这里我们希望有个交互式Shell。
我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签。
要退出终端,直接输入exit或者CTRL+D。
启动已经停止运行的容器。查看所有的容器的命令如下:
docker ps -a
我们也可以用docker ps命令查看正在运行的容器。
docker ps
我们可以使用 docker start 启动一个已停止的容器:
docker start 容器
想要后台运行容器,我们可以过 -d 指定容器的运行模式:
docker run -itd --name 指定创建的容器名 容器 /bin/bash
加了 -d 参数默认不会进入容器,想要进入容器需要使用下面的指令进入容器:
- docker attach
- docker exec:推荐大家使用 docker exec 命令,因为使用此命令退出容器终端,不会导致容器的停止。
docker attach 容器 //如果从这个容器退出,会导致容器的停止。 docker exec -it 容器 /bin/bash //如果从这个容器退出,不会导致容器的停止。
想要停止容器,其命令如下:
docker stop 容器ID
停止的容器重启命令:
docker restart 容器ID
删除容器:
docker rm -f 容器ID
Docker镜像使用
列出镜像列表。我们可以使用 docker images 来列出本地主机上的镜像。
docker images
各个参数解释:
- REPOSITORY:表示镜像的仓库源
- TAG:镜像的标签
- IMAGE ID:镜像ID
- CREATED:镜像创建时间
- SIZE:镜像大小
查找镜像:
docker search 镜像
各个参数解释:
- NAME: 镜像仓库源的名称
- DESCRIPTION: 镜像的描述
- OFFICIAL: 是否 docker 官方发布
- stars: 类似 Github 里面的 star,表示点赞、喜欢的意思。
- AUTOMATED: 自动构建。
删除镜像:
docker rmi 镜像
Docker镜像的修改和自定义
docker镜像的更新
在启动docker镜像后,写入一些文件、代码、更新软件等等操作后,退出docker镜像,之后在终端输入如下命令:
docker commit -m="..." -a= "..." 容器ID 指定要创建的目标镜像名称
参数解释:
- commit:固定格式
- -m:提交的描述信息
- -a:指定镜像作者
接着可以用docker images查看镜像是否更新成功。(注意:不要创建名称已存在的镜像,这样会使存在的镜像名称为none,从而无法使用)
镜像名称修改和添加新标签
更改镜像名称(REPOSITORY):
docker tag 容器ID 新名称
更改镜像tag,不修改名称:
docker tag IMAGEID(镜像id) REPOSITORY:TAG(仓库:标签)
Docker容器和本机之间的文件传输
主机和容器之间传输文件的话需要用到容器的ID全称。
从本地传输到容器中:
docker cp 本地文件路径 容器name:/root/(容器路径)
从容器传输到本地上:
docker cp 容器name:/root/(容器路径) 本地文件路径
Docker挂载宿主机文件目录
docker可以支持把一个宿主机上的目录挂载到镜像的目录中。
在启动docker镜像时,输入如下命令:
docker run -it -v /宿主机绝对路径:/镜像内挂载绝对路径 容器REPOSITORY /bin/bash
通过-v参数,冒号前为宿主机目录,冒号后为镜像内挂载的路径,必须为绝对路径。
如果宿主机目录不存在,则会自动生成,镜像里也是同理。
默认挂载的路径权限为读写。如果指定为只读可以用:ro
docker run -it -v /宿主机绝对路径:/镜像内挂载绝对路径:ro 容器REPOSITORY /bin/bash
----【开放性问题】----
这些问题基于我的思考提出,希望除了能给大家带来面试的思考,也能给大家带来面试以外的思考。这些问题没有标准答案,我相信每个人心中都有自己灵光一现的创造,你的呢?
【一】对AI安全相关技术的发展前景的看法?
这是一个非常值得去思考的问题,在AI算法大范围落地之后,随之而来的就是安全风险,我相信AI安全将会是一个很大的课题。
【二】对GAN算法技术的发展前景的看法?
GAN的思想无疑让人眼前一亮,在目标检测,图像分割,图像分类等领域都有其辅助的身影,并在数据增强领域站在了舞台中央。
精致的结尾
最后,感谢大家读完这篇文章,希望能给大家带来帮助~后续Rocky会持续撰写“三年面试五年模拟”之独孤九剑的系列文章,大家敬请期待!
#面经##秋招##实习##面试八股文##面霸的自我修养#三年面试五年模拟之独孤九剑秘籍专注于分享算法工程师校招/社招/实习中会遇到的高价值知识点。关注本专栏,不仅能助你拿到心仪的offer,也能让你构建算法工程师的完整知识结构。