失误点,建议重复阅读,加强记忆!!

声明

此篇内容,只作学习用途,部分内容涉及引用(会标注引用来源)


++++++++++


++++++++++


++++++++++


1. 代码复现过程积累的经验

  1. 要看论文的代码实现细节
    • 多卡/单卡训练:多卡就涉及到很多东西,如果你深入了解多卡当我没说。
    • 显卡配置:比如3060支持的CUDA最低11.x,你跑10.x的项目就不兼容,
    • 看看项目的config:是否针对某一数据集,别下载完某一数据集才发现,没写用这数据集训练的代码
    • 注意模型的配置:比如train_loader的num_works表示进程数,如果电脑没有cpu多核心,num_works设置为0,如果不设置为0,则会出现管道破裂问题。

2. 建议阅读文章

  1. 卷积原理:几种常用的卷积(标准卷积、深度卷积、组卷积、扩展卷积、反卷积)
  2. pytorch的conv2d函数groups分组卷积使用及理解
  3. pytorch的conv2d函数groups分组卷积使用及理解
  4. 卷积神经网络参数量和计算量的计算方法
  5. 卷积层参数量计算

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

3. 复现的论文中一些补充内容

  1. Ultra-Fast-Lane-Detection-V2
    • CULane评估工具的安装:需要安装opencv c++,然后根据要求安装了。之后就是重点了,如何cmake一系列包括缺失的操作。

    • opencv C++安装:

      # Install minimal prerequisites (Ubuntu 18.04 as reference)
      sudo apt update && sudo apt install -y cmake g++ wget unzip
       
      # Download and unpack sources
      wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip
      unzip opencv.zip
       
      # Create build directory
      mkdir -p build && cd build
       
      # Configure
      cmake ../opencv-4.x
       
      # Build
      cmake --build .
      
    • 利用cmake构建完项目后,关键的一点,也没写,就是在build目录下更新依赖库:sudo make install。至此,opencv c++完成。

      sudo make install之后.so内的依赖库会发生更新,更新到系统默认动态默认库目录下;

      ubuntu中sudo make install 的含义

      1. 安装sudo make install:将程序安装至系统中。如果make原始码编译无误,且执行结果正确,便可以把程序安装至系统预设的可执行文件存放路径。默认/usr/local/bin
      2. sudo make install 这一步是用来安装的,它也从Makefile中读取指令,安装到指定的位置 这条命令来进行安装,一般需要你有 root 权限(因为要向系统写入文件),所以前面用了 sudo

CMake

CMake是一个开源、跨平台的工具系列,是用来构建、测试和打包软件。
CMake使用平台无关的配置文件来控制软件编译过程,并生成可在您选择的编译器环境中使用项目文件,比如可以生成vs项目文件或者makefile。CMake工具套件由Kitware公司创建,以满足ITK和VTK等开源项目对跨平台构建环境的需求。Kitware是一家从事医疗计算,高性能的可视化和计算,数据和分析,计算机视觉的公司。该公司成立于1998年。

CMake编译原理

cmake编译(Linux系统构建示例)

  1. 安装cmake

  2. 源码准备:C++源码+cmake配置文件(CMakeLists.txt)
    示例
    CMakeLists.txt文件在根目录 ,c++源码first_cmake.cpp和其同一个目录。

  3. Linux平台编译
    安装g++ ,等依赖指令sudo apt update && sudo apt install -y cmake g++ wget unzip

    1. 创建build目录,并进入build目录;
    2. 生成makefile文件,通过cmake .. # ..指向了CMakeLists.txt所在的目录
    3. 编译项目,cmake --build . #.指build目录,该命令表示编译项目在build目录下。

通过cmake ./cmake .. 命令创建Makefile文件后,一般使用make命令编译文件。这里的cmake --build .就与make一样的效果

2.cross-attentino module

对于一些通道问题的计算细节模糊,通过如下图完成理解。同时给出一些好的原文章

图片

4. 三次样条插值(cubic spline)

三次样条(cubic spline)插值

当已知某些点而不知道具体方程时候,最经常遇到的场景就是做实验,采集到数据的时候,我们通常有两种做法:拟合或者插值。拟合不要求方程通过所有的已知点,讲究神似,就是整体趋势一致。插值则是形似,每个已知点都必会穿过,但是高阶会出现龙格库塔现象,所以一般采用分段插值。今天我们就来说说这个分段三次样条插值。

顾名思义,分段就是把区间[a,b]分成n个区间\([(x_{0},x_{1}),(x_{1},x_{2}),...,(x_{n-1},x_{n})]\),共有n+1个点,其中两个端点\(x_{0}=a,x_{n}=b\)。三次样条就是说每个小区间的曲线是一个三次方程,三次样条方程满足以下条件:

1,在每个分段小区间\([x_{i-1},x_{i}]\)上,\(S(x)=S_{i}(x)\)都是一个三次方程

2,满足插值条件,即\(S(x_{i})=y_{i} (i=0,1,...,n)\)

3, 曲线光滑,即\(S(x),S'(x),S''(x)\)连续

则这个三次方程可以构造成如下形式:\(y=a_{i}+b_{i}x+c_{i}x^{2}+d_{i}x^{3}\)这种形式,我们称这个方程为三次样条函数\(S_{i}(x)\)。从\(S_{i}(x)\)可以看出每个小区间有四个未知数\((a_{i},b_{i},c_{i},d_{i})\),有n个小区间,则有4n个未知数,要解出这些未知数,则我们需要4n个方程来求解。

5. 完整的模型训练流程:

  1. 准备数据集

    • 下载并准备训练集和测试集数据。
    • 对数据进行预处理,例如调整大小、标准化、转换为张量等。
  2. 定义模型

    • 选择适当的模型架构,例如预训练的模型(如 ResNet、VGG、等)或自定义模型。
    • 在模型的最后一层添加适当的输出层以适应任务需求。
  3. 定义损失函数和优化器

    • 选择适当的损失函数,例如交叉熵损失函数。
    • 选择适当的优化器,例如随机梯度下降 (SGD) 或 Adam。
  4. 训练模型

    • 对训练集进行迭代,每次迭代都要完成以下步骤:
      • 将输入传递给模型并计算输出。
      • 计算损失函数值。
      • 根据损失函数计算梯度。
      • 使用优化器更新模型参数。
    • 可以选择性地在每个 epoch 结束时评估模型性能,以便保存最佳模型。
  5. 评估模型

    • 对测试集进行迭代,每次迭代都要完成以下步骤:
      • 将输入传递给模型并计算输出。
      • 比较输出和真实标签以计算性能指标,例如准确率、精确率、召回率等。
  6. 保存模型(可选):

    • 可以选择保存训练后的模型权重,以便日后使用或部署。
  7. 可视化结果(可选):

    • 可以选择性地使用图表或其他工具可视化模型的性能指标、损失函数值等。
  8. 调优模型(可选):

    • 根据评估结果调整模型架构、超参数或数据预处理方法,以改善模型性能。
  9. 部署模型(可选):

    • 如果模型表现良好且满足要求,可以将其部署到实际应用中,用于实际任务解决或服务。

以上就是训练和评估模型的一般流程。具体流程可能会根据任务的特点、数据集的特点和模型的选择而有所不同。

6. 广播机制

Pytorch广播机制是处理两个形状不相同向量的一种手段
pytorch中的广播机制=numpy``中的广播机制一样 (因为都是数组的广播机制)

广播机制的使用前提
从右往左顺序看两个张量的每一个维度,x和y每个对应着的两个维度都需要能够匹配上。什么情况下算是匹配上了?满足下面的条件就可以:
1. 这两个维度的大小相等
2. 某个维度 一个张量有,一个张量没有
3. 某个维度 一个张量有,一个张量也有但大小是1

具体:

x = torch.empty((4,3,2,1))
y = torch.empty((3,1,1))

  4 3 2 1
+   3 1 1
---------
  4 3 2 1   :可以广播、

x = torch.empty((4,3,3,1))
y = torch.empty((3,2,1))

  4 3 3 1
+   3 2 1
---------
  4 3 ?1   :不可以广播

广播机制的细节:
从空间上去理解:

7. 卷积

卷积类型不一

标准卷积

  • 标准卷积可划分为same卷积,valid卷积和full卷积

    1. Same卷积:通过Padding填充0运算保证卷积前后特征图大小不变,即W_in=W_out、H_in=H_out。
      公式为:\((W_{in}-K+2P)/S+1 = W_{out}\) (通常用该公式计算padding)
    • 保证了输入输出的特征尺寸不变
    1. valid卷积:不加入Padding运算,直接进行卷积运算,特征图会变小。W_in>W_out、H_in>H_out
      公式为:\((W_{in}-K)/S+1 =W_{out}\)
    • 不加填充,特征图会变小
    1. full卷积:padding 0过多,通过卷积运算使得特征图变大。
      公式为:\((W_{in}-K+2P)/S + 1=W_{out}\)
    • 特征图变大

空洞卷积|扩张卷积

  • 标准的卷积操作中,卷积核的元素之间都是相邻的。但是,在空洞卷积中,卷积核的元素是间隔的,间隔的大小取决于空洞率。 如下图所示使用卷积核大小为33、空洞率(dilated rate)为2、卷积步长为1的空洞卷积操作对输入为77的特征图进行卷积生成3*3的特征图

  • 下图中显示了正常卷积和空洞率为2和4的卷积。图(a)是正常卷积,(b)是空洞率为2的空洞卷积,(c)是空洞率为4的空洞卷积,可以看出,(b),(c)的感受野为别是77和1515明显得到了提升。空洞卷积的作用就是再不增加计算量的同时,增加了模型的感受野,对图像分割任务中获取上下文的信息非常有用。

  • 空洞卷积的相关计算公式:
    • \(感受野尺寸= 2×(rate_{dilated}-1)×(K-1)+K\)
    • \(带空洞卷积的输出特征图尺寸的计算: F_{out}=(F_{in}-k+2P)/S+1\)

可形变卷积

  • 卷积本身的操作是非常固定的几何形状,标准的卷积操作是一个非常规矩的采样。通常是正方形的,如果卷积采用非规矩的采样,形状不在是标准的正方形,而是任意形状这样的卷积核称为可变形卷积,如下图所示:

  • 图中展示了卷积核大小为 3x3 的正常卷积和可变形卷积的采样方式,
    (a) 所示的正常卷积规律的采样 9 个点(绿点)
    (b)(c)(d) 为可变形卷积,在正常的采样坐标上加上一个位移量(蓝色箭头),其中(c)(d) 作为 (b) 的特殊情况,展示了可变形卷积可以作为尺度变换,比例变换和旋转变换的特殊情况
  • 可变形卷积,不仅包含了权重系数还需要每个点的偏移量。举例说明,例如一个简单的3*3的卷积,可变形卷积仅仅做平移变换,就会包含18个偏移量系数(X.Y方向各9个偏移系量)
    1. 通道共享偏移系数的时候,偏移系数和通道数无关。
    2. 对于输入是M通道,输出时N通道,核为3*3的核来说,由于通道权重共享,所以权重 的参数是M*N*3*3,偏移参数是2*3*3,因此权重的参数远远大于偏移参数。
    3. 而在后续的改进使用中,每个通道不在共享偏移量,其偏移量的参数变为2*3*3*M,但该参数量依旧比权重参数少,所以依旧不会大幅度增加模型的大小。且在实际应用中可以使用对通道组的分组共享偏移参数来降低参数
  • 可变形卷积具有更加灵活的感受野,如下图所示:展示了两层结构,拥有标准固定感受野的卷积层 (a) 与拥有自适应感受野的可变性卷积层(b)。最上方是两个在不同大小的物体上的激活单元,中间是该单元所需的采样位置,最下方是中间的采样点分别所需的采样位置。
  • 事实上,可变形卷积单元中增加的偏移量是网络结构的一部分,通过另外一个平行的标准卷积单元计算得到,进而也可以通过梯度反向传播进行端到端的学习。加上该偏移量的学习之后,可变形卷积核的大小和位置可以根据当前需要识别的图像内容进行动态调整,其直观效果就是不同位置的卷积核采样点位置会根据图像内容发生自适应的变化,从而适应不同物体的形状、大小等几何形变。下图展示了可变形卷积框架,首先通过一个小卷积层(绿色)的输出得到可变形卷积所需要的位移量,然后将其作用在卷积核(蓝色)上,达到可变形卷积的效果。
  • 可变形卷积带来的好处
    1,增加了网络的空间变形适应性,对复杂的任务效果提升明显;
    2,不增加额外的标注信息和训练代价,依旧使用原来的数据进行训练,并且可以同时训练卷积系数和偏移量,学习过程比较简单。对于增加的参数,可以通过分组进行控制;

深度卷积

  • 深度可分离卷积(Depthwise Separable Convolution)可分为深度卷积(Depthwise convolution,DC)和点卷积(Pointwise convolution,PC)。相比较与常规卷积操作,其参数量和运算成本较低。

  • Depthwise Convolution:一个卷积核负责一个通道,一个通道只被一个卷积核卷积。

  • Pointwise Convolution:其卷积核的尺寸为1×1×M,M为上一层的通道数。

  • Depthwise,对不同的channel使用不同的卷积核提取特征;Pointwise,针对某点或某一像素,所以kernel_size=(1,1)

组卷积

组卷积是把输入特征图在通道方向分成若干组,对每一组的特征分别做卷积后再拼接起来,以减少参数数量提高运算速度。该方法在AlexNet中就被用来将特征图分解成两部分以分别部署到两个GPU上来处理内存消耗的问题。depthwise separable convolution可视作一种特殊的组卷积,使每一个分组只包含一个通道。

组卷积可视作一种稀疏卷积连接的方式,即每一个输出通道只与输入通道中的某一个组相连,可能会丢失全局通道的信息。为了克服这个问题,微软推出了一种交错式组卷积(interleaved group convolutions,使输出通道能与所有输入通道相连

交错式组卷积如上图所示。假设输入由L×M个通道,这里L = 2, M = 3。先将输入通道分成L个组,每个组包含M个通道,使用组卷积后将通道重新排序,分成M个组,每个组包含L个通道,再次进行组卷积。得到的输出即可捕捉到每个输入通道中的信息。

反卷积/转置卷积

  • FCN结构使用的卷积方法, 反卷积运算方式则不同于以上的Full卷积方式,而是首先对特征图各神经元之间进行0填充,即上池化;然后再进行卷积运算。
    公式为:\((W_{in}-1)×S+K-2×P=W_{out}\)
  • 通常卷积会造成分辨率降低,反卷积是卷积的逆向过程,就如卷积将4*4的变为2*2,反卷积的作用就是将2*2通过反卷积的作用,转换为4*4

非局部卷积

又可分为:

  • 静态卷积
  • 动态卷积

! 卷积参数量的计算

\[ 参数量 = ((C_{in}*H_{kernel}*W_{kernel})+1)*C_{out} \]

! 为什么卷积核的尺寸都是奇数?

  1. 更容易padding:
    在卷积时,我们有时候需要卷积前后的尺寸不变。这时候我们就需要用到padding。假设图像的大小,也就是被卷积对象的大小为n×n,卷积核大小为k,padding设定为 (k-1)/2时,我们由计算公式可知 out=(n-k+2((k-1)/2))/S +1 = n ,即保证了卷积输出也为n×n。保证了卷积前后尺寸不变。
    但是如果k是偶数的话,(k-1)/2就不是整数了。
  2. 更容易找到卷积锚点
    在CNN中,进行卷积操作时一般会以卷积核模块的一个位置为基准进行滑动,这个基准通常就是卷积核模块的中心。若卷积核为奇数,卷积锚点很好找,自然就是卷积模块中心,但如果卷积核是偶数,这时候就没有办法确定了,让谁是锚点似乎都不怎么好。

参考文章:

8. Pytorch内容

torch.expand/expand_as

有两点需要注意,无论是 expand() 还是 expand_as():

  1. 只能在第0维扩展一个维数,比如原来是是(1,3,4)》(2,1,3,4),而在其他维度扩展不可以(1,3,4)》(1,2,3,4)【错误】
  2. 如果不增加维数,只是增加维度,要增加的原维度必须是1才可以在该维度增加维度,其他值均不可以
import torch
# 1
# 维数扩展
x = torch.randn(2, 1, 1)#为1可以扩展为3和4
x = x.expand(2, 3, 4)
print('x :', x.size())
>>> x : torch.Size([2, 3, 4])
# 2 
# 维度扩展
x = torch.randn(2, 2, 2)#原维度为其他不是1的值不可以扩展为其他维度
y = torch.randn(2, 3, 4)
x = x.expand_as(y)
print('x :', x.size())
>>> RuntimeError: The expanded size of the tensor (4) must match the existing size (2) at non-singleton dimension 2.  Target sizes: [2, 3, 4].
# 3
#扩展一个新的维度必须在最前面,否则会报错
x = x.expand(2, 3, 4, 6)
>>> RuntimeError: The expanded size of the tensor (3) must match the existing size (2) at non-singleton dimension 1.
x = x.expand(6, 2, 3, 4)
>>> x : torch.Size([6, 2, 3, 4])
#4 
#某一个维度为-1表示不改变该维度的大小
x = x.expand(6, -1, -1, -1)
>>> x : torch.Size([6, 2, 1, 1])

Conv2d

Conv2d包括:torch.nn.Conv2d / torch.nn.functional.Conv2d

torch.nn.Conv2d

import torch
import torch.nn as nn
torch.nn.Conv2d(in_channels=0,
                out_channels=0,
                kernel_size=0,
                stride=1,
                padding=0,
                padding_mode='zeros',
                dilation=1,
                groups=1,
                bias=True)

9个参数:

  • in_channels:参数代表输入特征矩阵的通道数
  • out_channels:参数代表卷积核的个数,输出的通道数即使用的卷积核的个数
  • kernel_size:参数代表卷积核的尺寸
    • int类型:表示一个正方形的卷积核,卷积核的宽即输入的int值
    • tuple类型:可以设置期望的卷积核的形状,(height, weight)
  • stride:参数代表卷积核的步距,默认为1
    • int类型:在水平与竖直方向上具有相同的步长,通常不大于2
    • tuple类型:分别设置水平和竖直方向上的步长,(x, y)
  • padding:参数代表在输入特征矩阵四周补值的情况,默认为0
    • int类型:在上下左右各补对应的输入的值的列的像素
    • tuple类型:分别设置上下和左右方向上的补充的像素的列,(x, y)
  • padding_mode:补值的类型,默认为’zeros’
    • zeros:常量填充
    • reflect:镜像填充,即以矩阵中的某个行或列为轴,中心对称的padding到最外围。
    • replicate:重复填充,即直接使用边缘的像素值来填充
    • circular:循环填充,即从上到下进行无限的重复延伸
  • dilation:参数代表kernel内的点(卷积核点)的间距,默认为1
  • groups:参数代表filter的组数,默认为1
  • bias:是否设置偏差值

示例

# With square kernels and equal stride
m = nn.Conv2d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
input = torch.randn(20, 16, 50, 100)
output = m(input)

torch.nn.functional.Conv2d


import torch
import torch.nn.functional as F
torch.nn.functional.conv2d(
  input, 
  weight, 
  bias=None, 
  stride=1,
  padding=0, 
  dilation=1, 
  groups=1)
  • input: 输入张量的形状\((minibatch,in\_channels,iH,iW)\)
  • weight:滤波器的形状\((out\_channels,\frac{in\_channels}{groups},kH,kW)\)
  • bias: 输出通道out_channels的可选偏差张量
  • stride:卷积内核的步长。可以是一个数字或一个元组(sH、sW).默认值:1
  • padding:输入两边的隐式填充。可以是字符串 {'valid'、'same'}、单个数字或元组(padH、padW)
  • dilation: 内核元素之间的间距。可以是一个数字,也可以是一个元组(dH、dW)。
  • groups:将输入分组。in_channels和out_channels同样也分组。

示例:

# With square kernels and equal stride
filters = torch.randn(8, 4, 3, 3)
inputs = torch.randn(1, 4, 5, 5)
F.conv2d(inputs, filters, padding=1)

两者差别
Pytorch中torch.nn.conv2d和torch.nn.functional.conv2d的区别

  • nn.Conv2d 是一个类,而 nn.functional.conv2d是一个函数。
  • 两者的调用方式不同:调用 nn.xxx 时要先在里面传入超参数,然后再将数据以函数调用的方式传入 nn.xxx
# torch.nn
inputs =  torch.randn(64, 3, 244, 244)
self.conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1)
outputs = self.conv(inputs)

# torch.nn.functional    需要同时传入数据和 weight,bias等参数
inputs =  torch.randn(64, 3, 244, 244)
weight = torch.randn(64, 3, 3, 3)
bias = torch.randn(64)
outputs = nn.functinoal.conv2d(inputs, weight, bias, padding=1)

  • nn.xxx 能够放在 nn.Sequential里,而 nn.functional.xxx 就不行
  • nn.functional.xxx 需要自己定义 weight,每次调用时都需要手动传入 weight,而 nn.xxx 则不用
import torch
import torch.nn as nn
import torch.nn.functional as F

# torch.nn 定义的CNN
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.conv_1 = nn.Conv2d(1, 16, krenel_size=5, padding=0)
        self.relu_1 = nn.ReLU(inplace=True)
        self.maxpool_1 = nn.MaxPool2d(kernel_size=2)

        self.conv_2 = nn.Conv2d(16, 32, krenel_size=5, padding=0)
        self.relu_2 = nn.ReLU(inplace=True)
        self.maxpool_2 = nn.MaxPool2d(kernel_size=2)   

        self.linear = nn.Linear(4*4*32, 10)

    def forward(self, x):
        x = x.view(x.size(0), -1)
        out = self.maxpool_1(self.relu_1(self.conv_1(x)))
        out = self.maxpool_2(self.relu_2(self.conv_2(out)))
        out = self.linear(out.view(x.size(0), -1))
        return out

# torch.nn.functional 定义一个相同的CNN
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.conv_1_weight = nn.Parameter(torch.randn(16, 1, 5, 5))
        self.bias_1_weight = nn.Parameter(torch.randn(16))

        self.conv_2_weight = nn.Parameter(torch.randn(32, 16, 5, 5))
        self.bias_2_weight = nn.Parameter(torch.randn(32))

        self.linear_weight = nn.Parameter(torch.randn(4 * 4 * 32, 10))
        self.bias_weight = nn.Parameter(torch.randn(10))

    def forward(self, x):
        x = x.view(x.size(0), -1)
        out = F.conv2d(x, self.conv_1_weight, self.bias_1_weight)
        out = F.conv2d(out, self.conv_2_weight, self.bias_2_weight)
        out = F.linear(out.view(x.size(0), -1), self.linear_weight, self.bias_weight)

  • 在使用Dropout时,推荐使用 nn.xxx。因为一般只有训练时才使用 Dropout,在验证或测试时不需要使用 Dropout。使用 nn.Dropout时,如果调用 model.eval() ,模型的 Dropout 层都会关闭;但如果使用 nn.functional.dropout,在调用 model.eval() 时,不会关闭 Dropout。
  • 当我们想要自定义卷积核时,是不能使用torch.nn.ConvNd 的,因为它里面的权重都是需要学习的参数,没有办法自行定义。但是,我们可以使用 torch.nn.functional.conv2d() 。

9. labelimg教程

labelImg命令

usage: labelImg.py [-h] [image_dir] [class_file] [save_dir]

positional arguments:
  image_dir
  class_file
  save_dir

optional arguments:
  -h, --help  show this help message and exit

使用期间出现的问题

  1. C:\Users\USERNAME\.labelImgSettings.pkl存放了labelImg相应的历史记录
    删除该文件,可初始化|解决闪退问题。
  2. labelImg\data\predefined_classes.txt中,保存了预定义的类别(labelImg默认的)。
  3. 新增的标签,会出现在\images\classes.txt文件中
  4. 出现闪退的问题时,不妨先尝试一次标注,便可解决问题。
  5. 数据目录结构
    data
      -images
      -annotations
      predefined_classes.txt
    
  6. 最好在labelImg项目目录下操作,一般不会报路径,标签问题。

9. YOLOv5 训练结果性能分析:

参考:

  1. YOLOv5训练结果性能分析
  2. 深度学习之常用模型评估指标(一)—— 分类问题和目标检测

10. 批归一化(Batch Normalization,BN)

介绍:BN是google于2015年提出的一个深度神经网络训练的技巧,它不仅可以加快了模型的收敛速度,而且更重要的是在一定程度缓解了深层网络中“梯度弥散”的问题,从而使得训练深层网络模型更加容易和稳定

具体来说:神经网络一旦训练起来,除了输入层的数据外,后面网络每一层的输入数据分布一直在发生变化。我们将训练期间,在网络中间层的数据分布变化称之为"Internal Covariate Shift"。BN的提出,就是要解决网络中间层数据分布改变的情况

BN算法:

如上图所示,BN步骤主要分为4步:
  1. 求每一个训练批次数据的均值 \(μ\)
  2. 求每一个训练批次数据的方差 \(σ\)
  3. 使用求得的均值和方差对该批次的训练数据做归一化,获得0-1分布。其中ε是为了避免除数为0时所使用的微小正数。
  4. 尺度变换和偏移:将\(x_i\)乘以γ调整数值大小,再加上β增加偏移后得到\(y_i\),这里的γ是尺度因子β是平移因子。这一步是BN的精髓,由于归一化后的\(x_i\)基本会被限制在正态分布下,使得网络的表达能力下降。为解决该问题,我们引入两个新的参数:γ,β。 γ和β是在训练时网络自己学习得到的。

BN做了什么?

  • a图是一个标准的归一化,减均值除方差;
  • b图是一个BN;

a中左图是没有经过任何处理的输入数据,曲线是sigmoid函数,如果数据在梯度很小的区域,那么学习率就会很慢甚至陷入长时间的停滞。减均值除方差后,数据就被移到中心区域如右图所示,对于大多数激活函数而言,这个区域的梯度都是最大的或者是有梯度的(比如ReLU),这可以看做是一种对抗梯度消失的有效手段。对于一层如此,如果对于每一层数据都那么做的话,数据的分布总是在随着变化敏感的区域,相当于不用考虑数据分布变化了,这样训练起来更有效率。

那么为什么要有第4步,不是仅使用减均值除方差操作就能获得目的效果吗?我们思考一个问题,减均值除方差得到的分布是正态分布,我们能否认为正态分布就是最好或最能体现我们训练样本的特征分布呢?不能,比如数据本身就很不对称,或者激活函数未必是对方差为1的数据最好的效果,比如Sigmoid激活函数,在-1~1之间的梯度变化不大,那么非线性变换的作用就不能很好的体现,换言之就是,减均值除方差操作后可能会削弱网络的性能!针对该情况,在前面三步之后加入第4步完成真正的batch normalization。

BN的本质就是利用优化变一下方差大小和均值位置,使得新的分布更切合数据的真实分布,保证模型的非线性表达能力。BN的极端的情况就是这两个参数等于mini-batch的均值和方差,那么经过batch normalization之后的数据和输入完全一样,当然一般的情况是不同的。

训练阶段,BN位置:

关于BN的使用位置,在CNN中一般应作用与非线性激活函数之前,s型函数s(x)的自变量x是经过BN处理后的结果。因此前向传导的计算公式就应该是:\(Z=g(BN(wx+b))\)

其实因为偏置参数b经过BN层后其实是没有用的,最后也会被均值归一化,当然BN层后面还有个β参数作为偏置项,所以b这个参数就可以不用了。因此最后把BN层+激活函数层就变成了:\(Z=g(BN(wx))\)

预测时的均值和方差:
预测阶段所使用的均值和方差,也来自训练集。比如我们在模型训练时我们就记录下每个batch下的均值和方差,待训练完毕后,我们求整个训练样本的均值和方差期望值,作为我们进行预测时进行BN的的均值和方差:

测试阶段BN

11. Tensor和图片相互转换

为什么要转换呢?当是查看转换效果,毕竟人是感官动物。

  1. Tensor==>Image :利用transforms.ToPILImage()
  2. Image ==>Tensor:利用transforms.ToTensor()
import torch
from PIL import Image
from torchvision import transforms

loader = transforms.Compose([transforms.ToTensor()]) # 加载图片为Tensor
unloader = transforms.ToPILImage()  # 将Tensor转换为图片

def image2tensor(image_path):
    image = Image.open(image_path).convert('RGB')
    image = loader(image)
    return image.to(torch.float)

def tensor2image(tensor):
    # 确保图像尺寸为(C,H,W)
    image = tensor.cpu().clone()
    image = unloader(image)
    image.save() # 保存
    image.show() # 显示

! 更多参考 Pytorch之浅入torchvision.transforms.ToTensor与ToPILImage

待补充

# 特征图之Mask输出
# Pytorch 模型搭建,训练,验证,测试(评估)的步骤

12. PTQ:Post-training Quantization:训练后量化

posted @ 2024-03-28 09:36  AlexanderOscar  阅读(16)  评论(0编辑  收藏  举报