极市导读
总结分享了一些CV算法与机器学习相关的经典面试知识点。 >>加入极市CV技术交流群,走在计算机视觉的最前沿
----【目录先行】----
深度学习基础:
经典模型&&热门模型:
机器学习基础:
Python/C/C++知识:
模型部署:
图像处理基础:
计算机基础:
开放性问题:
----【深度学习基础】----
随机梯度下降的优化算法在科研和工业界是很常用的。
很多理论和工程问题都能转化成对目标函数进行最小化的数学问题。
举个例子:梯度下降(Gradient Descent)就好比一个人想从高山上奔跑到山谷最低点,用最快的方式奔向最低的位置。
SGD的公式:
动量(Momentum)公式:
基本的mini-batch SGD优化算法在深度学习取得很多不错的成绩。然而也存在一些问题需解决:
AdaGrad优化算法(Adaptive Gradient,自适应梯度),它能够对每个不同的参数调整不同的学习率,对频繁变化的参数以更小的步长进行更新,而稀疏的参数以更大的步长进行更新。
AdaGrad公式:
表示时刻的 梯度。
表示t时刻参数 的梯度平方和。
与SGD的核心区别在于计算更新步长时, 增加了分母: 梯度平方累积和的平方根。 此项能够累积各个参数 的历史梯度平方, 频繁更新的梯度, 则累积的分母逐渐偏大, 那么更新的步长相对就会变小, 而稀疏的梯度, 则导致累积的分母项中对应值比较小, 那么更新的步长则相对比较大。
AdaGrad能够自动为不同参数适应不同的学习率 (平方根的分母项相当于对学习率 进进 行了自动调整, 然后再乘以本次梯度), 大多数的框架实现采用默认学习率 即可 完成比较好的收敛。
优势: 在数据分布稀疏的场景,能更好利用稀疏梯度的信息,比标准的SGD算法更有效地收敛。
缺点: 主要缺陷来自分母项的对梯度平方不断累积,随时间的增加,分母项越来越大,最终导致学习率收缩到太小无法进行有效更新。
RMSProp结合梯度平方的指数移动平均数来调节学习率的变化。能够在不稳定的目标函数情况下进行很好地收敛。
计算t时刻的梯度:
计算梯度平方的指数移动平均数(Exponential Moving Average), 是遗忘因子(或称为指数衰减率),依据经验,默认设置为0.9。
梯度更新的时候,与AdaGrad类似,只是更新的梯度平方的期望(指数移动均值),其中 , 避免除数为 0 。默认学习率 。
优势: 能够克服AdaGrad梯度急剧减小的问题,在很多应用中都展示出优秀的学习率自适应能力。尤其在不稳定(Non-Stationary)的目标函数下,比基本的SGD、Momentum、AdaGrad表现更良好。
Adam优化器结合了AdaGrad和RMSProp两种优化算法的优点。对梯度的一阶矩估计(First Moment Estimation,即梯度的均值)和二阶矩估计(Second Moment Estimation,即梯度的未中心化的方差)进行综合考虑,计算出更新步长。
Adam的优势:
Adam的实现原理:
计算t时刻的梯度:
然后计算梯度的指数移动平均数, 初始化为 0 。
类似于Momentum算法, 综合考虑之前累积的梯度动量。
系数为指数衰减率, 控制动量和当前梯度的权重分配, 通常取接近于 1 的值。默认为 。
接着, 计算梯度平方的指数移动平均数, 初始化为 0 。
系数为指数衰减率, 控制之前的梯度平方的影响情况。默认为 。
类似于RMSProp算法, 对梯度平方进行加权均值。
由于 初始化为 0 , 会导致 偏向于 0 , 尤其在训练初期阶段。
所以, 此处需要对梯度均值 进行偏差纠正, 降低偏差对训练初期的影响。
同时 也要进行偏差纠正:
最后总的公式如下所示:
其中默认学习率 ,避免除数变为0。
从表达式中可以看出,对更新的步长计算,能够从梯度均值和梯度平方两个角度进行自适应地调节,而不是直接由当前梯度决定。
Adam的不足:
虽然Adam算法目前成为主流的优化算法,不过在很多领域里(如计算机视觉的图像识别、NLP中的机器翻译)的最佳成果仍然是使用带动量(Momentum)的SGD来获取到的。
Normalize非常重要,没有处理过的图片是没办法收敛的。图片Normalize一种简单的方法是(images-127.5)/127.5,然后送到判别器去训练。同理生成的图片也要经过判别器,即生成器的输出也是-1到1之间,所以使用Tanh激活函数更加合适。
最好去掉整个Pooling逻辑,因为使用Pooling会损失信息,这对于GAN训练没有益处。
对判别器和生成器使用不同的学习速度。使用较低的学习率更新生成器,判别器使用较高的学习率进行更新。
使用梯度惩罚机制可以极大增强 GAN 的稳定性,尽可能减少mode collapse问题的产生。
Spectral normalization可以用在判别器的weight normalization技术,可以确保判别器是K-Lipschitz连续的。
可以使用多个GAN/多生成器/多判别器结构来让GAN训练更稳定,提升整体效果,解决更难的问题。
U-Net网络结构如下所示:
U-Net网络的特点:
U-Net在医疗图像,缺陷检测以及交通场景中有非常丰富的应用,可以说图像分割实际场景,U-Net是当仁不让的通用Baseline。
U-Net的论文地址:https://arxiv.org/pdf/1505.04597.pdf
RepVGG模型的基本架构由20多层 卷积组成,分成5个stage,每个stage的第一层是stride=2的降采样,每个卷积层用ReLU作为激活函数。
RepVGG的主要特点:
那么是什么让RepVGG能在上述情形下达到SOTA效果呢?
答案就是结构重参数化(structural re-parameterization)。
在训练阶段,训练一个多分支模型,并将多分支模型等价转换为单路模型。在部署阶段,部署单路模型即可。这样就可以同时利用多分支模型训练时的优势(性能高)和单路模型推理时的好处(速度快、省内存)。
更多结构重参数化细节知识将在后续的篇章中展开介绍,大家尽情期待!
首先Rocky介绍一下相关名词:
Accuracy、Precision、Recall、F1 Scores的公式如下所示:
Accuracy(准确率):分类正确的样本数占样本总数的比例。
Precision(精准度/查准率):当前预测为正样本类别中被正确分类的样本比例。
Recall(召回率/查全率):预测出来的正样本占正样本总数的比例。
F1-score是Precision和Recall的综合。F1-score越高,说明分类模型越稳健。
一般在深层神经网络中,我们需要预防梯度爆炸和梯度消失的情况。
梯度消失(gradient vanishing problem)和梯度爆炸(gradient exploding problem)一般随着网络层数的增加会变得越来越明显。
例如下面所示的含有三个隐藏层的神经网络,梯度消失问题发生时,接近输出层的hiden layer3的权重更新比较正常,但是前面的hidden layer1的权重更新会变得很慢,导致前面的权重几乎不变,仍然接近初始化的权重,这相当于hidden layer1没有学到任何东西,此时深层网络只有后面的几层网络在学习,而且网络在实际上也等价变成了浅层网络。
我们来看看看反向传播的过程:
(假设网络每一层只有一个神经元,并且对于每一层 )
可以推导出:
而sigmoid的导数 如下图所示:
可以知道, 的最大值是 , 而我们初始化的权重 通常都小于 1 , 因此 , 而且链式求导层数非常多, 不断相乘的话, 最后的结果越来越小, 趋向 于0, 就会出现梯度消失的情况。
梯度爆炸则相反, 时, 不断相乘结果变得很大。
梯度爆炸和梯度消失问题都是因为网络太深,网络权重更新不稳定造成的,本质上是梯度方向传播的连乘效应。
None是一个特殊的常量,表示空值,其和False,0以及空字符串不同,它是一个特殊Python对象, None的类型是NoneType。
None和任何其他的数据类型比较返回False。
>>> None == 0
False
>>> None == ' '
>>> False
>>> None == None
>>> True
>>> None == False
>>> False
>>>
我们可以将None复制给任何变量,也可以给None赋值。
和 主要用于函数定义。我们可以将不定数量的参数传递给一个函数。这里的不定的意思是:预先并不知道函数使用者会传递多少个参数, 所以在这个场景下使用这两个关键字。
是用来发送一个非键值对的可变数量的参数列表给一个函数。
我们直接看一个例子:
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)
test_var_args('hello', 'python', 'ddd', 'test')
-----------------结果如下-----------------------
first normal arg: hello
another arg through *argv: python
another arg through *argv: ddd
another arg through *argv: test
允许我们将不定长度的键值对, 作为参数传递给一个函数。如果我们想要在一个函数里处理带名字的参数, 我们可以使用 。
我们同样举一个例子:
def greet_me(**kwargs):
for key, value in kwargs.items():
print("{0} == {1}".format(key, value))
greet_me(name="yasoob")
-----------结果如下-------------
name == yasoob
面向对象(Object Oriented Programming,OOP)编程模型首先抽象出各种对象(各种类),并专注于对象与对象之间的交互,对象涉及的方法和属性都封装在对象内部。
面向对象的编程思想是一种依赖于类和对象概念的编程方式,一个形象的例子是将大象装进冰箱:
面向过程(Procedure Oriented Programming,POP)编程模型是将问题分解成若干步骤(动作),每个步骤(动作)用一个函数来实现,在使用的时候,将数据传递给这些函数。
面向过程的编程思想通常采用自上而下、顺序执行的方式进行,一个形象的例子依旧是将大象装进冰箱:
OpenCV的一些常用操作:
import cv2 # 导入OpenCV库
import numpy as np
img = cv2.imread('xxx.jpg', 0) # 读取图片:灰度模式
img = cv2.imread('xxx.jpg', -1) # 读取图片:BGRA模式(BGR+Alpha通道)
img = cv2.imread('xxx.jpg', 1) # 读取图片:BGR模式
img = cv2.imread('xxx.jpg') # 读取图片:第二参数默认为1,BGR模式
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 将颜色通道从BGR转为RGB
if img == None: # 读取图片失败
print('image failed to load')
cv2.imshow('src', img) # 图片源src为img
print(img.shape) # 输出图片(高度h,宽度w,通道c)
print(img.size) # 像素总数目
print(img.dtype) # 输出图片类型,uint8为[0-255]
print(img) # 输出所有像素的RGB值
cv2.waitKey() # 按键关闭窗口
# waitKey(delay)函数的功能是不断刷新图像,频率时间为delay,单位为ms,返回值为当前键盘按键值
# waitKey() 是在一个给定的时间内(单位ms)等待用户按键触发; 如果用户没有按下键,则接续等待(循环)
imgL = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 读取img灰度图
cv2.imshow('gray',imgL) # 图片源gray为imgL
cv2.imwrite('imgL.jpg',imgL) # 将imgL储存名为imgL.jpg的图片
img = img.transpose(2,0,1) # 图片矩阵变换为(通道c,高度h,宽度w)
img = np.expand_dims(img, axis=0) # 图片矩阵扩展维度添加在第一维
print(img.shape) # (1,通道c,高度h,宽度w)
print(img[10,10]) # 访问图片img像素[10,10],输出 [0-255 0-255 0-255]
print(imgL[10,10]) # 访问灰色图片img像素[10,10],输出 0-255
img[10,10] = [255,255,255] # 修改图片img像素点[10,10]为[255,255,255]
imgL[10,10] = 255 # 修改灰色图片img像素点[10,10]为255
img[:,:,2] = 0 # 将R通道全部修改为0
roi = img[200:550,100:450,:] # ROI操作,坐标(高度范围,宽度范围,通道范围)
cv2.imshow('roi',roi) # 图片源roi为roi
PIL的一些常用操作:
from PIL import Image #导入PIL库
import numpy as np
img = Image.open('../xx.jpg') # 读取图片
imgL = Image.open('../xx.jpg').convert('L') # 读取图片灰度图
imgL.show() # 展示灰度图
img1 = img.copy() # 复制图片
print(img.format) # 输出图片格式
print(img.size) # 输出图片(宽度w,高度h)
print(img.mode) # 输出图片类型,L为灰度图,RGB为真彩色,RGBA为RGB+Alpha透明度
im.show() # 展示画布
imgData = np.array(img) # 将对象img转化为RGB像素值矩阵
print(imgData.shape) # 输出图片(宽度w,高度h,通道c)
print(imgData.dtype) # 输出图片类型,uint8为[0-255]
print(imgData) # 输出所有像素的RGB值
imgN = Image.fromarray(imgData) # 将RGB像素值矩阵转化为对象imgN
imgN.save('xxx.jpg') # 储存为文件xxx.jpg
r ,g ,b = img.split() # 分离通道
img = Image.merge("RGB", (b, g, r)) # 合并通道
# ROI(region of interest),只对ROI区域操作
roi = img.crop((0, 0, 300, 300)) # (左上x,左上y,右下x,右下y)坐标
roi.show() # 展示ROI区域
#捕捉异IOError,为读取图片失败
try:
img = Image.open('xxx.jpg')
except IOError:
print('image failed to read')
Git是当前主流的一种开源分布式版本控制系统,可以有效、快速的进行项目版本管理。
Git没有中央服务器,不同于SVN这种需要中央服务器的集中式版本控制系统。
Git的功能:版本控制(版本管理,远程仓库,分支协作)
Git的工作流程:
Git的常用命令:
git init 创建仓库
git clone 克隆github上的项目到本地
git add 添加文件到缓存区
git commit 将缓存区内容添加到仓库中
GitLab是一个基于Git实现的在线代码仓库软件,可以基于GitLab搭建一个类似于GitHub的仓库,但是GitLab有完善的管理界面和权限控制,有较高的安全性,可用于企业和学校等场景。
SVN全名Subversion,是一个开源的版本控制系统。不同于Git,SVN是集中式版本控制系统。
SVN只有一个集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
SVN的特点是安全,效率,资源共享。
SVN的常用操作:
Checkout 检出代码
Update 更新代码
Commit 提交代码
Add 提交新增文件
Revert to this version + commit 撤销已经提交的代码
协程(Coroutine,又称微线程)运行在线程之上,更加轻量级,协程并没有增加线程总数,只是在线程的基础之上通过分时复用的方式运行多个协程,大大提高工程效率。
协程的特点:
协程适用于有大量I/O操作业务的场景,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。
在协程中尽量不要调用阻塞I/O的方法,比如打印,读取文件等,除非改为异步调用的方式,并且协程只有在I/O密集型的任务中才会发挥作用。
这些问题基于我的思考提出,希望除了能给大家带来面试的思考,也能给大家带来面试以外的思考。这些问题没有标准答案,我相信每个人心中都有自己灵光一现的创造,你的呢?
在“CV兵器”基本上都是开源的情况下,数据成为了支持业务迭代最重要的一部分,如何建立数据护城河,形成业务与数据双向正反馈,是AI行业从业者必须要面对的课题。
这个问题不仅可以考察面试者,面试者也可以用来反向判断面试官及其背后公司的运行逻辑。陷入demo业务,一次性业务以及外包业务的循环中是无法成长的,也不利于建立业务/产品的护城河。知道了这一点,那么如何去选择部门,如何去选择公司,如何去看需求,就变成了非常值得研究的事情。
# CV技术社群邀请函 #
备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)
即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群
极市&深大CV技术交流群已创建,欢迎深大校友加入,在群内自由交流学术心得,分享学术讯息,共建良好的技术交流氛围。
“
点击阅读原文进入CV社区
收获更多技术干货