深度学习基础知识

卷积

普通卷积

优点:1. 参数量少,稀疏连接。 2. 直接将图像数据作为输入,不仅无需人工对图像进行预处理和额外的特征抽取等复杂操作

输入图像: W H C,卷积核大小:K K,输出图像大小:W1 H1 * C1

卷积核参数量计算: C C1 K * K

计算量计算: W1 H1 C1 K K * C

卷积后的图片大小计算:sup (h + 2*padding - (kerner_size -1) / stride) + 1

空洞卷积

P1: 在卷积位置中加入空洞,使得在保持参数量一样的情况下,感受野增大。

语义分割中,大感受野捕捉远距离之间的关系,小感受野捕捉近距离的关系

Pooling

Pooling的作用

  1. 减小feature map大小,减小参数量
  2. 提取主要特征
  3. 平移、旋转、尺度不变性

Max Pooling 和 Avg Pooling的作用

Max Pooling: 感觉更像是做了特征选择,选出了分类辨识度更好的特征,提供了非线性

Avg Pooling: 更强调对整体特征信息进行一层下采样,在减少参数维度的贡献上更大一点,更多的体现在信息的完整传递这个维度上。在网络最后一几层中比较常用

正则化

BN/LN/WN

P1: 解决什么问题?

在训练时,某一层的参数更新,会导致上层网络会改变数据的分布,层层叠加之后,数据分布变化剧烈,深层网络难以训练。

P2:公式

x是一个神经元一组输入向量:$x=(x_1,x_2,x_3,...,x_n)$

通用公式: $ h=f(g \frac{x-u}{\sigma}+b)$

先norm到N(0, 1)的正太分布,然后进行再缩放和再平移。

再缩放和再平移的原因:保持网络的表达能力不会因为norm而下降

Batch Normalization: 针对batch设计的,统计一个batch中数据的均值和方差。要求mini batch的数据和整个数据分布相近,也就是batch_size越大,越靠近整体分布。mini batch还引入了噪声,提供了一定正则化。

Layer Normalization: 针对一层所有维度的输入,计算该层的均值和方差。LN针对单个样本设计

Weight Normalization:针对神经元的权重进行norm,将权重分解成方向向量$v$和长度向量$g$:$w=g \frac{v}{||v||}$,推导如下:

$$f_{w}(WN(x))=w*WN(x)=g*\frac{v}{||v||}*x=f_v(g*\frac{x}{||v||})$$

这样就变成了与通用norm公式相似的形式了,$u=0$,$\sigma=||v||$,$b=0$,$g=g$。

P3: BatchNormlization在训练和测试时有什么区别

P4: 为什么有用?

  • 权重伸缩不变性:

    指将权重$w$ 按常量$\lambda$方向伸缩时,规范后的值不变

    $$Norm(W^{'} x)=Norm(g*\frac{w^{'}x-u^{'}}{\sigma^{'}}+b)=Norm(g*\frac{\lambda w x-\lambda w}{\lambda \sigma})=Norm(g*\frac{x-u}{\sigma}+b)=Norm(Wx)$$

  • 数据伸缩不变性:

    指将数据$x$按常量$\lambda$方向伸缩时,规范后的值不变。公式推导与上面一样

dropout

todo

L1、L2正则化

P1: 用来约束模型尽可能简单

P2: L1正则化为什么可以让权重稀疏

L1是L0范数的近似,且L0难以求解。(L0范数:向量中不为0的元素个数)

P2: L2范数

L2范数是指向量各元素的平方和然后求平方根。

让L2范数的规则项||W||2最小,可以使得W的每个元素都很小,都接近于0,但与L1范数不同,它不会让它等于0,而是接近于0,这里是有很大的区别的哦。而越小的参数说明模型越简单,越简单的模型则越不容易产生过拟合现象。

常见LOSS

L1 & L2 LOSS

P1: MSE LOSS基于高斯先验

在一定条件下,预测值和真实值的误差符合N(0, 1)的高斯分布。对N个样本的预测概率,使用极大似然估计,可以得到最后的结果就是MSE LOSS。

MSE LOSS会过滤掉高频信息。

P2: L1 LOSS基于拉普拉斯先验

在一定条件下,预测值和真实值的误差符合u=0,b=1的拉普拉斯分布。对N个样本的预测概率,使用极大似然估计,可以得到最后的结果就是L1 LOSS。

P3: Smooth L1 LOSS

L2 loss对与离群点很敏感。

l1 loss在预测结果和真实结果很接近时梯度还是1,并没有降低

$Smooth L1 =0.5*x^2,x<1 $

$Smooth L1 =|x|-0.5,|x|>=1 $

BCE LOSS

用于多标签分类,

Cross Entropy

loss = -1 sigma( y_i log f(x_i))

用于多类别分类

Huber Loss

优化算法

首先定义:待优化参数: ,目标函数: ,初始学习率

而后,开始进行迭代优化。在每个epoch

  1. 计算目标函数关于当前参数的梯度:
  2. 根据历史梯度计算一阶动量和二阶动量:
  3. 计算当前时刻的下降梯度:
  4. 根据下降梯度进行更新:

SGD: 只使用梯度进行更新

->SGD with Momentum:加入一阶动量,并进行指数平均

->AdaGrad:加入二阶动量,历史梯度之和

->RMSProp:改历史梯度之和为历史梯度的指数平均

->Adam:结合一阶动量和二阶动量

权重初始化方法

Kaiming初始化

针对卷积的,

样本不均衡时怎么处理

  1. 采样:过采样,欠采样
  2. 数据增强:插值,GAN生成
  3. 按样本所占比例给样本加权
  4. focal loss

其他小问题

add和concat的区别

  • concat计算量更大
  • add会对信息造成负面影响,并且需要chanel数量一致

基础网络

VGG

P1: 使用3x3小卷积核替代大卷积核

感受野一样,但参数更少,层数更深。

全部采用3x3的卷积核。感受野:两个3x3的卷积核=一个5x5的卷积核,并且参数量比5x5的少很多。

卷积核参数量计算: in_chanel x out_chanel x kernel_size x kernel_size

2个3x3卷积核参数量:2xCxCx3x3=18C^2 5x5卷积核参数量:CxCx5x5=25C^2

P2: 1x1卷积核的作用

对通道升维降维,降低参数量。

在不改变感受野的同时,还可以加深层数,增加非线性,增强网络表达能力。

P3: 常用类型VGG16

2个3x3的卷积层后接一个max pooling(k=2,s=2)

GoogLeNet

P1: V1使用Inception结构加深网络的宽度和深度

采用不同大小的卷积核:1x1,3x3,5x5,max pooling(k=2x2,s=1)。融合多尺度的特征(把所有结构concat到一起)。

由于3x3与5x5卷积参数量太大,所以使用1x1的卷积核进行降维,减少参数量。(先1x1降维,然后卷积。max pooling后也进行降维)

P2: V2使用两个3x3的卷积核替代5x5的卷积核

保持感受野不变的同时,减少参数量,增加非线性

P3: V2使用BN,加快网络收敛速度,提升收敛结果

  • 网络在训练时,每层网络输入的分布在变化,导致难以训练。使用BN可以将输入分布拉回N(0, 1)的正态分布。
  • BN有正则化效果,可以一定程度缓解过拟合问题。

P4: V3使用1xn和nx1的卷积来替代nxn的卷积

减少参数量,同时保持感受野不变。

(与3d卷积网络中的将3d卷积核拆成2d+1d卷积核的思想一样,典型例子P3D

P5: 在第一层使用了大卷积核

to do

ResNet

P1: ResNet要解决的问题

在实验中发现,过于深层的神经网络反而会不如浅层神经网络的结果。这是不合逻辑的,深层网络的效果应该大于等于浅层网络。因为深层网络的拟合能力比浅层网络强,作者也证明这不是由于过拟合导致的,如果过拟合的话,网络在训练集上的效果应该很好。(训练时网络梯度消失的原因,skip connection类似于highway network的作用,加快网络梯度的传导。所以后面有了DesNet,暴力使用skip connection)

如果深层网络=浅层网络+恒等映射(后面层),那么网络的效果至少能保证不下降。但这种恒等映射$H(x)=x$是网络很难以学习到的东西,所以Resnet把要学习的函数改成: $H(x)=F(x)+x$==>$F(x)=H(x)-x$。网络要学习到$F(x)=0$是一个很简单的问题,并且$H(x)-x$也叫叫做残差。

于是就有了Residual Block的由来。

并且Residual Block至少要有两层,不然会出现:$y=F(x,W)+x=Wx+x=(W+1)x$,这会导致和没加差不多。

P2: Block内部堆叠顺序

ReLU((Conv+BN+ReLU+Conv+BN)+x)

P3: 遇到前后block的空间维度和通道深度不一致时怎么处理?

空间维度上加个线性映射:$y=F(x,W_1)+W_{2}x$

通道深度不一致时:使用1x1的卷积核进行升维降维

P4: pytorch代码中出现的bottleneck结构

bottleneck:1x1卷积降维,然后3x3卷积,然后1x1卷积升维。

主要是应用在resnet50和更深的网络中,目的是减少参数量

DenseNet

P1: DenseNet要解决什么问题?

深层网络中的梯度消失问题。

前面介绍过ResNet中的skip connection还可以当作highway network,便于梯度传导。

DenseNet则强化了这一做法,直接将每一层的输入设置为之前所有的层的输出,强行创建了很多skip connection。

这种做法使的梯度可以直接通过highway(skip connection)传导部分梯度,使得深层网络也能良好的训练。

与ResNet的区别:$x_l = H_l(x_{l-1})+x_{l-1}$与$x_l=H_l([x_0,x_1,...,x_{l-1}])$

DenseNet的参数量更少

P2:DenseBlock的实现

DenseBlock内部:将之前的层的输出concat成当前层的输入。

同一Block中feature size统一,便于处理。

在3x3的卷积前都使用1x1的卷积进行降维。

Object Detection

RCNN

RCNN结构

原图->Region proposals (使用Selective Search算法提取)->CNN提取区域特征->K个类别的SVM二分类器

相对于之前算法的改进:

  • 之前的算法使用滑窗提取Region proposal,文中提出了Selective Search的启发式搜索算法
  • 使用CNN来提取区域特征

Selective Search

启发式搜索,对于一张图,输出1k-2k个

  • 使用一种分割算法,将图片分割成很多小区域
  • 按以下规则将两个区域合并小区域,直到只剩最后一个大区域:

    • 颜色相近合并(颜色直方图,为了避免遗漏,在多个颜色空间进行合并)
    • 纹理相近合并(梯度直方图)
    • 合并后总面积(使生成的图大小较为均匀)
    • 合并后,总面积在bbox中占比大的(按位置合并)
  • 输出所有合并过程中存在的区域

缺点

  • 检测速度慢,生成大量的Region Proposal,并且提取特征时Region Proposal大量重叠
  • 训练慢,同上
  • 需要k(类别数)个分类器

Fast RCNN

Fast RCNN流程

主要针对RCNN的缺点提出了以下三点改进:

  • 使用统一的CNN网络提取整张图片的feature map,避免对重复区域提取浅层特征
  • 使用ROI Pooling对所有ROI Pooling到统一大小,便于给后面的fc层使用
  • 同时输出分类和回归的结果,一个分支输出softmax的分类结果,一个分支输出regressor的回归结果

ROI Pooling

  • 将每个Region Proposal在feature map上的对应区域统一划分成固定的网格大小H*W
  • 对网格中的每个小区域进行max pooling,统一得到H*W个特征

Faster RCNN

网络结构

Faster RCNN = RPN + Fast RCNN

就是把(Selective Search,Feature Extraction,Classification,Bounding Box Regression)中的Selective Search换成RPN,把整个Object Detection整合成一个DNN。

Region Proposal Network

  • 特征提取:和Fast RCNN共享底层Feature Map
  • 候选区域(Anchor):对于Feature Map(5139256)上的每个位置,考虑9个候选框(面积不同,比例不同)
  • 使用两个分支,分别输出每个位置9个ancho有目标的概率,和每个位置9个ancho的平移缩放系数

Non-max Suppression

  • 根据置信度得分进行排序
  • 选择置信度最高的比边界框添加到最终输出列表中,将其从边界框列表中删除
  • 计算所有边界框的面积
  • 计算置信度最高的边界框与其它候选框的IoU。
  • 删除IoU大于阈值的边界框
  • 重复上述过程,直至边界框列表为空

Yolo系列

Yolo v1

Yolo v2

Yolo V3

SSD

Todo

Segmentation

Mask-RCNN

网络结构

Mask-RCNN = Faster R-CNN + FPN + Mask

ROI Align

ROI的缺点:需要做两次取整(从原图到中间层feature map一次,pooling划分格子时一次),导致最终的bbox与原图差距较大

ROI Align做法:

  1. 将bbox等分成n*n的区域。
  2. 在每个子区域取四个点,对于其中的每个点的值,由离它最近的四个点的进行双线性插值得到
  3. 在每个子区域的四个点中进行max pooling

GAN

原始的GAN

原始GAN的loss

原始GANloss存在的问题:判别器越好,生成器梯度消失越严重

在(近似)最优判别器下,最小化生成器的loss等价于最小化$P_r$与$P_g$之间的JS散度,而由于$P_r$与$P_g$几乎不可能有不可忽略的重叠,所以无论它们相距多远JS散度都是常数$2 log 2$,最终导致生成器的梯度(近似)为0,梯度消失。

改进的GAN loss

改进的GAN loss的存在的问题:生成器loss面临优化目标荒谬、梯度不稳定、对多样性与准确性惩罚不平衡导致mode collapse

最小化第二种生成器loss函数,会等价于最小化一个不合理的距离衡量,导致两个问题,一是梯度不稳定,二是collapse mode即多样性不足

WGAN

原始GAN存在的问题

  1. d和g 的loss无法指示训练的进度
  2. 模型崩塌,生成样本缺乏多样性
  3. 需要小心平衡D和G的训练程度。当D训练的太好的时候,D无法给G提供好的梯度。(因为D能将真实样本和生成样本分开,两者分布没有交集,JS散度就没有意义

过渡的解决方案

就是对生成样本和真实样本加噪声,直观上说,使得原本的两个低维流形“弥散”到整个高维空间,强行让它们产生不可忽略的重叠。

WGAN的解决方案

采用Wasserstein距离来计算分布之间的距离。Wasserstein距离相比KL散度、JS散度的优越性在于,即便两个分布没有重叠,Wasserstein距离仍然能够反映它们的远近。

实际改进

  • 判别器最后一层去掉sigmoid
  • 生成器和判别器的loss不取log
  • 每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c
  • 不要用基于动量的优化算法(包括momentum和Adam),推荐RMSProp,SGD也行

以上参考自: https://zhuanlan.zhihu.com/p/25071913(写的特别好,强烈推荐去看原文)

WGAN-GP

To do

Batch Normlization在GAN中的问题

To do

手写代码题

注意!以下所有代码未验证,仅供参考。

IOU计算,实现

int cal(int x1, int x2, int x3, int x4)
{
   if(x1 > x3)
   {
     swap(x1, x3);
     swap(x2, x4);
   }
  return x3 > x2 ? 0 : min(x2 - x3, x4 - x3);
}

int cal_IOU(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
    int x = cal(x1, x2, x3, x4);
  int y = cal(y1, y2, y3, y4);
  int area_i = x * y;
  int area_u = (x1 - x2) * (y1 - y2) + (x3 - x4) * (y3 - y4) - area_i;
  return area_i / area_u;
}

NMS做法和实现

import numpy as np

class Bounding_box:
    def __init__(self, x1, y1, x2, y2, score):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.score = score

def get_iou(boxa, boxb):
    max_x = max(boxa.x1, boxb.x1)
    max_y = max(boxa.y1, boxb.y1)
    min_x = min(boxa.x2, boxb.x2)
    min_y = min(boxa.y2, boxb.y2)
    if min_x <= max_x or min_y <= max_y:
        return 0
    area_i = (min_x - max_x) * (min_y - max_y)
    area_a = (boxa.x2 - boxa.x1) * (boxa.y2 - boxa.y1)
    area_b = (boxb.x2 - boxb.x1) * (boxb.y2 - boxb.y1)
    area_u = area_a + area_b - area_i
    return float(area_i) / float(area_u)

def NMS(box_lists, k):
    box_lists = sorted(box_lists, key=lambda x: x.score, reverse=True)
    NMS_lists = [box_lists[0]]
    temp_lists = []
    for i in range(k):
        for j in range(1, len(box_lists)):
            iou = get_iou(NMS_lists[i], box_lists[j])
            if iou < 0.7:
                temp_lists.append(box_lists[j])
        if len(temp_lists) == 0:
            return NMS_lists
        box_lists = temp_lists
        temp_lists = []
        NMS_lists.append(box_lists[0])
    return NMS_lists

box1 = Bounding_box(13, 22, 268, 367, 0.124648176)
box2 = Bounding_box(18, 27, 294, 400, 0.35818103)
box3 = Bounding_box(234, 123, 466, 678, 0.13638769)
box_lists = [box1, box2, box3]
NMS_list = NMS(box_lists, 2)
print NMS_list
print NMS_list[0].x1

线性插值的做法和实现

思想:通过点(x0, y0), (x1, y2), 求两点在点x=x处的y插值结果,加权平均。假设x0<x<x1。

根据斜率写出如下公式:

Code:

### 写完发现写出错来,中间插值算的结果应该是点的权值,而不是点的坐标。
### 不过思想一样,代码改改就好了。
float linear(float x, float x1, float y1, float x2, float y2)
{
  if(x1 > x2)
  {
    swap(x1, x2);
    swap(y1, y2);
  }
  return ((x - x1) * y2 + (x2 - x) * y1) / (x2 - x1);
}

双线性差值的做法和实现

利用周围四个点插值当前点的结果,在两个方向做线性插值。

Code:

### 写完发现写出错来,中间插值算的结果应该是点的权值,而不是点的坐标。
### 不过思想一样,代码改改就好了。
float linear(float x, float x1, float y1, float x2, float y2)
{
  if(x1 > x2)
  {
    swap(x1, x2);
    swap(y1, y2);
  }
  return ((x - x1) * y2 + (x2 - x) * y1) / (x2 - x1);
}
void bilinear(int &x, int &y, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4)
{
  float px, py, qx, qy;
  px = linear(y, y1, x1, y3, x3);
  py = linear(x, x1, y1, x3, y3);
  qx = linear(y, y2, x2, y4, x4);
  qy = linear(x, x2, y2, x4, y4);
  tx = linear(x, px, py, qx, qy);
  x = tx, y = ty;
}

参考文献