最近在面试,每天会被考到很多知识点,这些知识点有些我已经看了十几遍,还是会反应慢或者记不住。回想我在学习过程中,也是学了忘忘了学,没有重复个几十遍根本难以形成永久记忆。这次我复习和整理面试知识点的时候决定把CNN里面的关键创新点、容易疏忽的点都记录下来,方便快速查找回顾,于是就有了这篇像词典一样的永久更新的文章。
因为知识点过多,导致文章过长,目前已拆分为三篇:
1.基础知识篇
2.轻量化网络篇
3.
一.基础知识 1.1 BatchNorm/LayerNorm/InstanceNorm/GroupNorm 基础知识点 记忆点 备注解决covariate shift:如果ML系统实例集合<X,Y>中的输入值X的分布老是变,这不符合IID假设,网络模型很难稳定的学规律。
问题:分布变化=>非线性输出向两端移动=>梯度消失=>网络收敛慢
解决:BN=>将隐藏层的输入拉回到(0,1)正态分布=>使激活值落在非线性区域=>使得梯度变大=>加快网络收敛
BN为了保证非线性的获得,对变换后的满足均值为0方差为1的x又进行了scale加上shift操作(y=scale*x+shift)
核心思想应该是想找到一个线性和非线性的较好平衡点,既能享受非线性的较强表达能力的好处,又避免太靠非线性区两头使得网络收敛速度太慢。
推理的时候可能bs是1,那么α,γ,μ和σ从何而来呢?
参数α和γ是最后收敛的参数,而μ和σ则在训练的时候记住每个batch内的参数,然后求出平均值和方差的期望,这样在全局上估计的这组参数更加准确。
如何减少这些参数的存储量?
可以采用训练收敛最后几批mini batch的 μ和σ的期望,作为预测阶段的μ和σ

BN:固定C,对H,W和N求均值方差。
LN:固定N,对C和H,W求方差。
IN:固定C和N,对H,W求均值和方差。
GN:固定N,对C分组,每组内对C',H,W求均值和方差。
在每个训练批次中,神经元的激活值以一定的概率p停止工作

面试问到是冻结权重还是冻结神经元? 答冻结神经元。
为什么缓解过拟合?
为什么有人说dropout类似model ensemble的效果?
dropout每次隐藏部分神经元,就像是在训练不同的模型,因为每次网络的结构都不同。整个dropout就像是在对多个模型进行了ensemble。不同模型可能产生不同的过拟合,互相之间进行ensemble,就会起到平均效果更好。
dropout有可能减少密集的局部连接,迫使模型去学习更为全局更为robust的特征,而模型不应该因为局部的细节而产生大的变化。
小目标的over sampling。
针对同一张图片里面包含小目标数量少的问题,在图片内用分割的Mask抠出小目标图片再使用复制粘贴的方法(当然,也加上了一些旋转和缩放,另外要注意不要遮挡到别的目标)。
增加小anchor(配合FPN)。
降低小目标过滤阈值。

对mask图使用opencv来标记轮廓。
对每个轮廓计算面积。
对面积小于阈值的标记为小物体,并且填充特殊的flag像素值。
针对得到的新mask做交叉熵加权损失。
import cv2 import numpy as np import random import torch import matplotlib.pyplot as plt AREA_THS = 30 def mask_small_object(mask, area_thresh, pixels=[0, 1, 2], weights=[0.5, 1.0, 10.0]): """ :param mask: 输入mask掩码 :param area_thresh: 小物体的阈值,面积小于该值认为是小物体 :param pixels: 背景 小物体 大物体的mask值 :param weight: 背景,前景大物体,前景小物体的权重 :return: 权重图 """ contours, hierarchy = cv2.findContours(mask,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) area = [] for i in range(len(contours)): area.append(cv2.contourArea(contours[i])) for i in range(len(area)): if area[i] < area_thresh: cv2.fillPoly(mask, [contours[i]], (2, )) for i in range(len(pixels)): mask[mask==[pixels[i]]] = weights[i] return mask def mask_cross_entropy(y, y_hat, weight): n = 1e-7 # return -np.sum(y * np.log(y_hat + n) + (1 - y) * np.log(1 - y_hat + n), axis=1) assert y.shape == y_hat.shape entro = (y * np.log(y_hat + n) + (1 - y) * np.log(1 - y_hat + n)) mask_entro = entro * weight res = -np.mean(mask_entro) return round(res, 5) if __name__ == '__main__': AREA_THS = 30 img = np.zeros(shape=(512, 512, 3), dtype=np.uint8) label = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for i in range(10): x, y = random.randint(10, 500), random.randint(10, 500) r = random.randint(1, 10) cv2.circle(label, (x, y), radius=r, color=(1), thickness=-1) weight = mask_small_object(label, area_thresh=AREA_THS) pred = np.abs(np.random.rand(512, 512)) print(mask_cross_entropy(label, pred, weight))三.常见loss函数 loss函数 记忆点 备注
1.信息量的表示

2.熵是信息量的期望

3.相对熵(KL散度)表示两个分布的差异

4.交叉熵是相对熵的数学变形

前面是p(x)的熵,是一个常量。后面就是交叉熵。


1.解决类别不平衡
2.难易样本分布不平衡

基于交叉熵演变而来。
γ用来调节样本难易程度,一般取2。γ提升了预测与GT差距大的样本对loss的贡献比(困难样本)。
α用来调节样本类别的比例,默认α=0.25,将前景的loss放大而背景的loss缩小。
Dice系数:

Dice loss:

Laplace Smoothing:

GIOU paper 提出给定优化指标本身与代理损失函数之间的选择,最优选择就是指标本身。所以Dice本身更贴合语义分割常用的评价指标iou。
Dice的问题是求导在极端情况下会导致梯度陡增,使训练难以收敛震荡,不稳定。
四.常见衡量指标 指标名称 记忆点 备注

按照模型给出的置信度,对每个类的所有预测框进行排序:
逐个计算Precision 和 Recall,绘制PR曲线,AP就是PR曲线上的Precision值求均值。
实际应用中就会对PR曲线最做平滑:

IOU
IOU的定义:计算真实值和预测值两个集合的交集和并集之比
IOU=TP/(FP+FN+TP)

pij表示真实值为i,被预测为j的数量, K+1是类别个数(包含空类)。pii是真正的数量。pij、pji则分别表示假正和假负。
补充篇一:opencv(python)知识点 0.1 图像基础知识点 图像&opencv基础知识点 基础知识点 记忆点 备注
常见的电磁波成像特点
(按照频率从高到低)
γ射线成像:波长最短,频率最高,是由原子核内发射出来的电磁波。放射性物质或者原子核反应中常有这种辐射。γ射线穿透力强,对生物破坏性大。
x射线成像:CT就是用x射线照射物体。由于生物组织或者工程组件的不同部位对x射线的吸收率不同,从而得到不同的衰减以成像(密度越高,吸收的越多)。
紫外线成像:具备化学效应和荧光效应,常用于生物医学。
可见光波段成像
红外线成像:一切物体都可以辐射出红外线,可利用探测仪测量目标本身与背景间的红外线差得到红外图像。
微波成像:用于雷达以及生成地表情报图。
射频成像:电视、无线电、手机的波段。也可以用于医学,比如磁共振。

flags=0代表灰度图,flages=1代表彩图,默认是三通道彩图。
cv2.imread()、PIL.Image.open()、matplotlib.imread()读取的默认shape都是【H,W,C】,与pytorch的默认顺序不同,需要转换为【C, H, W】。
opencv读取是默认的BGR,直接用opencv呈现是没问题的。但是后面处理或者用matplotlib呈现就需要转换为RGB。
灰度 = B * 0.114 + G * 0.587 + R * 0.299
图像直方图是以表示数字图像中亮度分布的直方图,表达了图像亮度的整体分布。常用来二值化。
img = cv2.imread("../test.jpg", flags=1) img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) hist = cv2.calcHist(img, channels=[0], mask=None, histSize=[256], ranges=[0, 256]) plt.figure(figsize=(12, 6)) plt.plot(hist) plt.xlabel("bins") plt.ylabel("num of pixels") plt.title("histgram of grayscale") plt.show()

RGB:依据人眼的颜色空间。有RGB三通道,范围都是0-255。
HSV:H(色调),代表色彩;S(饱和度),取值是0-100%,值越大,颜色越饱和;V(明度),从0%(黑)到100%(白)。
HSI:H(色调),S(饱和度),I(强度)。
CMYK:C(青)、M(品红)、Y(黄),常用于印刷行业。
opencv中颜色空间的改变也是cv2.cvtcolor()

常见的插值方法:
最近邻插值:效果不好,放大后有严重的马赛克,缩小后的图像有严重的失真。

线性插值

三次样条插值
LANCZOS插值(效果还不错)
基本原理:图像可以看作是二维的信号,像素点的灰度值代表着信号的强弱。图像中变化强烈的部分称为高频部分,图像中变化缓慢的部分称为低频部分。可以根据图像的高低频,设置高通或者低通滤波器。高通可以检测变化尖锐明显的部分,低通可以让图像变得平滑,消除噪声。高通滤波器常用于边缘检测,低通滤波器常用于图像平滑去噪。
kernel内元素全部是1。
注意进行normalize,否则图像会因为超过255,变白,另外kernel越大,模糊的效果越明显。
kernel内元素全部是1。
等同于加了normalize的方框滤波。
高斯滤波卷积核内参数并不相同,而是呈现高斯分布的,中间高,两边低。高斯滤波可有效去除高斯噪声,保留更多的图像细节。
中值滤波是一种非线性滤波,是用像素点邻域灰度的中值代替该点的灰度值。,中值滤波可以取出椒盐噪声和斑点噪声。
if control == 4: img = cv2.imread("../luna.jpg", flags=1) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) blur = cv2.medianBlur(img, ksize=3) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(blur) plt.show()
双边滤波是一种非线性滤波,是结合像素的空间邻近度和像素值相似度的一种折中处理。同时考虑空间信息和灰度的相似性,达到保边去噪的目的。具有简单、非迭代、局部处理的特点。双边滤波可以有效保证边缘信息。
# 双边滤波 if control == 5: img = cv2.imread("../luna.jpg", flags=1) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) blur = cv2.bilateralFilter(img, d=-1, sigmaColor=15, sigmaSpace=10) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(blur) plt.show()
直方图均匀化就是将原图像经过某种变化,得到一副灰度直方图均匀的图像。直方图均匀化的思想就是对图像中像素个数较多的灰度级进行展宽,从而对像素较少的灰度级进行缩减。从而达到清晰图像的目的。工业上常用来解决过曝的情况。
img = cv2.imread("../dark_girl.jpg",flags=0) print(img.shape) img_equal = cv2.equalizeHist(img) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.title("origin") plt.imshow(img) plt.subplot(122) plt.title("equalize") plt.imshow(img_equal) plt.show()
Gamma变化是对输入图像灰度值进行的非线性操作。使输出图像的灰度值和输入图像的灰度值呈现指数的关系。
Gamma变化用来进行图像增强,让图像从曝光强度的线性响应变得更接近人眼感受的响应。既对相机曝光或者光照不足的情况进行调节。
img = cv2.imread("../guobao.jpg") def adjust_gamma(image, gamma=1.0): invGamma = 1.0 / gamma table = [] for i in range(256): table.append(((i / 255) ** invGamma) * 255) table = np.array(table).astype("uint8") return cv2.LUT(image, table) def equalize(img): b, g, r = cv2.split(img) equal_r = cv2.equalizeHist(r) equal_g = cv2.equalizeHist(g) equal_b = cv2.equalizeHist(b) img_equal = cv2.merge((equal_b, equal_g, equal_r)) return img_equal img_gamma = adjust_gamma(image=img, gamma=0.35) img_equalize = equalize(img=img) cv2.imshow("original", img) cv2.imshow("gamma", img_gamma) cv2.imshow("equalize", img_equalize) cv2.waitKey(0) cv2.destroyAllWindows()
针对图像的白色部分,膨胀进行扩张,白色区域变大,腐蚀进行收缩,白色区域变小。
腐蚀:用模板B来腐蚀A就是用B来逐步卷积A,模板B有一个锚点和锚框,如果锚点落在前景内,且锚框都落在前景内,则保留锚点像素。如果锚点落在前景内,锚框没有完全包含在前景内,则去除锚点像素改为背景。

img = cv2.imread("../erode.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_erode = cv2.erode(img, kernel=(3, 3)) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show()
腐蚀的对偶运算。

同样使用模板B来卷积A,模板内包含锚点和锚框。如果锚框和前景有交集,如果此时锚点对应着背景,那么就把背景膨胀为前景。
先腐蚀后膨胀,把细微连在一起的物体分开,把物体的表面进行平滑。
换句话说开运算能够去除孤立的小点,毛刺和小桥,但总的位置和形状不变。
if control == 3: img = cv2.imread("../mor-open.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(5, 5)) img_erode = cv2.morphologyEx(img, op=cv2.MORPH_OPEN, kernel=kernel, iterations=1) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show()
先膨胀运算再腐蚀运算。
闭运算能够将两个细微连接的图封闭到一起,可以去除孔洞和弥补小裂缝,并且保持整体的位置形状不变。
if control == 4: img = cv2.imread("../mor-close.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 7)) img_erode = cv2.morphologyEx(img, op=cv2.MORPH_CLOSE, kernel=kernel, iterations=3) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show()

顶帽:原图像与开运算的插值,突出原图像中比周围亮的区域。
黑帽:闭运算与原图像差值,突出原图像比周围暗的区域。
# 形态学梯度 膨胀-腐蚀 if control == 5: img = cv2.imread("../haizei.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(7, 7)) img_erode = cv2.morphologyEx(img, op=cv2.MORPH_GRADIENT, kernel=kernel, iterations=1) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show() # 顶帽 原图-开运算 突出原图中亮的地方 if control == 6: img = cv2.imread("../haizei.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(9, 9)) img_erode = cv2.morphologyEx(img, op=cv2.MORPH_TOPHAT, kernel=kernel, iterations=2) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show() # 黑帽 闭运算-原图 突出原图中暗的地方 if control == 7: img = cv2.imread("../haizei.jpg") img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) kernel = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(9, 9)) img_erode = cv2.morphologyEx(img, op=cv2.MORPH_BLACKHAT, kernel=kernel, iterations=3) plt.figure(figsize=(12, 6)) plt.subplot(121) plt.imshow(img) plt.subplot(122) plt.imshow(img_erode) plt.show()

自适应阈值法会每次取出图像的一小部分计算阈值,这样不同区域的阈值就不尽相同,适用于明暗分布不均的图片。
# 自适应阈值分割 if control == 2: img = cv2.imread("../black_white.jpg", flags=0) print(img.shape) # 分成blockSize的小区域,其中使用均值来计算阈值 th1 = cv2.adaptiveThreshold( img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=11, C=4) # 分成blockSize的小区域,其中使用高斯加权来计算阈值 th2 = cv2.adaptiveThreshold( img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=17, C=6) ths = [img, th1, th2] plt.figure(figsize=(12, 6)) for i in range(3): plt.subplot(1, 3, i+1) plt.imshow(ths[i], cmap=plt.get_cmap("gray")) plt.show()
求出图像的最大灰度和最小灰度值,分别记为Zmax和Zmin,令初始阈值T0 = 1/2(Zmax + Zmin)。
根据阈值Tk,将图像分为前景和背景,分别求出两者的平均灰度值ZO和ZB。
求出新阈值Tk+1 = 1/2(ZO + ZB)。
若TK = TK + 1,则得到最终阈值,否则继续迭代2。
使用最后的阈值。

# 大津法阈值分割 if control == 3: img = cv2.imread("../dark_girl.jpg", flags=0) print(type(img)) # 阈值法 _, th1 = cv2.threshold(img, 100, maxval=255, type=cv2.THRESH_BINARY) # 大津法 _, th2 = cv2.threshold(img, 0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 高斯滤波 + 大津法(常用套路) blur = cv2.GaussianBlur(img, ksize=(7, 7), sigmaX=0) _, th3 = cv2.threshold(blur, 0, maxval=255, type=cv2.THRESH_BINARY + cv2.THRESH_OTSU) plt.figure(figsize=(12, 6)) plt.subplot(221) plt.title("origin") plt.imshow(img, cmap=plt.get_cmap("gray")) plt.subplot(222) plt.title("thresh") plt.imshow(th1, cmap=plt.get_cmap("gray")) plt.subplot(223) plt.title("otsu") plt.imshow(th2, cmap=plt.get_cmap("gray")) plt.subplot(224) plt.title("gussian + otsu") plt.imshow(th3, cmap=plt.get_cmap("gray")) plt.show()
梯度:梯度是一个向量,梯度方向指向函数变化最快的方向,梯度的大小是最大的变化率。
边缘:边缘就是梯度变化很快的点的集合。
梯度算子:梯度算子是一阶导数的算子,是水平梯度模板和竖直梯度模板的组合,也有对角线的方向。
常见的梯度算子:
Roberts交叉算子

优点:边缘定位较为准确,适用于边缘明显且噪声少的图像。
缺点:没有描述水平和竖直方向的灰度变化,只关注了对角线方向,容易造成边缘的遗漏;鲁棒性差,因为点本身参与梯度的计算,不能有效抑制噪声的干扰。
Prewitt算子


Prewitt算子引入了类似局部平均的运算,对噪声更有平滑作用,可以抑制噪声。
Sobel算子


canny边缘检测算法


if control == 2: img = cv2.imread("../road.png", 0) img_mask = cv2.imread("../road_mask.png", 0) img_mask = 1 - img_mask #print(img_mask) v1 = cv2.Canny(img, threshold1=60, threshold2=150, edges=(5, 5)) v1_ = v1 * img_mask v2 = cv2.Canny(img, threshold1=50, threshold2=100, edges=(5, 5)) v2_ = v2 * img_mask v_mask = cv2.Canny(img_mask, threshold1=0, threshold2=1, edges=(3, 3)) plt.figure(figsize=(9, 18)) plt.subplot(321) plt.title("origin") plt.imshow(img, cmap=plt.get_cmap("gray")) plt.subplot(322) plt.title("mask") plt.imshow(img_mask, cmap=plt.get_cmap("gray")) plt.subplot(323) plt.title("v1") plt.imshow(v1, cmap=plt.get_cmap("gray")) plt.subplot(324) plt.title("v2") plt.imshow(v2, cmap=plt.get_cmap("gray")) plt.subplot(325) plt.title("v1_") plt.imshow(v1_, cmap=plt.get_cmap("gray")) plt.subplot(326) plt.title("v_mask") plt.imshow(v_mask, cmap=plt.get_cmap("gray")) plt.show()
连通区域:一般指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域。连通区域分析是将图像中各自联通区域找到并且标记。
Two-pass算法:


区域生长:是一种串行区域分割的方法。从某个像素出发,按照一定的准则,逐步加入邻近像素。当满足一定的条件的时候,区域停止生长。
