diff --git a/2020/01/19/King Back/index.html b/2020/01/19/King Back/index.html index 5d0d273a..3f60a6ad 100644 --- a/2020/01/19/King Back/index.html +++ b/2020/01/19/King Back/index.html @@ -508,7 +508,7 @@

  • - 编译原理 + 数据库系统
  • @@ -516,15 +516,15 @@

  • - Hexo + 编译原理
  • - 数据库系统 + Go
  • - Go + Hexo
  • diff --git "a/2020/03/15/\347\274\226\347\240\201/index.html" "b/2020/03/15/\347\274\226\347\240\201/index.html" index bee1aedf..d10c4ed5 100644 --- "a/2020/03/15/\347\274\226\347\240\201/index.html" +++ "b/2020/03/15/\347\274\226\347\240\201/index.html" @@ -211,12 +211,11 @@

    数值数据表示的三要素:
    1.进位计数制
    2.定点、浮点表示
    3.如何用二进制编码
    即:要确定一个数值数据的值必须先确定这三个要素。
    例如机器数 01011001的值是多少?答案是不知道

    -

    原码

    原码范围:-127~+127

    - -

    [+127]=01111111
    [-127]=11111111
    0有两种:
    [+0]=00000000
    [-0]=10000000

    +

    原码

    原码范围:-127~+127

    [+127]=01111111
    [-127]=11111111
    0有两种:
    [+0]=00000000
    [-0]=10000000

    反码

    $X原->X反$
    正数:X反=X原
    负数: X原符号位为1,数值为按位取反
    反码范围:-127~+127
    [+127]=01111111
    [-127]=10000000
    0有两种:
    [+0]=00000000
    [-0]=11111111

    -

    补码

    $X原->X补$
    正数:X补=X原
    负数:符号位为1,数值为按位取反,末尾加1
    补码范围:**-128~+127**
    [+127]=01111111
    [-128]=10000000
    [-1] = 11111111
    0只有一种表示[+0]补=[-0]补=00000000

    -

    移码

    移码通常表示浮点数的阶码
    移码一般为整数,故移码通常只用于表示整数
    对定点正数,阶码为n,他的移码是:
    $$X_移=2^{n-1}+X_原$$
    0的移码和补码一样
    $X原->X移$
    正数:将原码符号位一起变反
    负数:将原码连同符号位一起变反,末尾再加1
    $X补->X移$
    符号相反,数值位相同
    移码的表示范围与补码一致:-128~+127

    +

    补码

    $X原->X补$
    正数:X补=X原
    负数:符号位为1,数值为按位取反,末尾加1
    补码范围:-128~+127
    [+127]=01111111
    [-128]=10000000
    [-1] = 11111111
    0只有一种表示[+0]补=[-0]补=00000000

    +

    移码

    移码通常表示浮点数的阶码
    移码一般为整数,故移码通常只用于表示整数
    对定点正数,阶码为n,他的移码是:

    +

    0的移码和补码一样
    $X原->X移$
    正数:将原码符号位一起变反
    负数:将原码连同符号位一起变反,末尾再加1
    $X补->X移$
    符号相反,数值位相同
    移码的表示范围与补码一致:-128~+127

    定点数

    数的小数点固定在同一位置不变

    1.带符号的定点小数

    约定所有数的小数点的位置,固定在符号位之后
    符号位+“.”+数值位

    字长n+1位时,表示范围是$-(1-2^{-n})=>(1-2^{-n})$

    2.带符号的定点整数

    小数点固定在最低数值位之后
    符号位+数值位+“.”

    @@ -527,7 +526,7 @@
    - 编译原理 + 数据库系统

  • @@ -535,15 +534,15 @@
    - Hexo + 编译原理
  • - 数据库系统 + Go
  • - Go + Hexo
  • diff --git "a/2020/07/09/\344\272\272\345\210\260\345\244\247\344\272\214\344\272\272\347\224\237\350\277\267\350\214\253\357\274\210\344\270\223\344\270\232\346\225\231\350\202\262\350\242\253\350\277\253\345\206\231\346\226\207\357\274\211/index.html" "b/2020/07/09/\344\272\272\345\210\260\345\244\247\344\272\214\344\272\272\347\224\237\350\277\267\350\214\253\357\274\210\344\270\223\344\270\232\346\225\231\350\202\262\350\242\253\350\277\253\345\206\231\346\226\207\357\274\211/index.html" index 64955baa..29def302 100644 --- "a/2020/07/09/\344\272\272\345\210\260\345\244\247\344\272\214\344\272\272\347\224\237\350\277\267\350\214\253\357\274\210\344\270\223\344\270\232\346\225\231\350\202\262\350\242\253\350\277\253\345\206\231\346\226\207\357\274\211/index.html" +++ "b/2020/07/09/\344\272\272\345\210\260\345\244\247\344\272\214\344\272\272\347\224\237\350\277\267\350\214\253\357\274\210\344\270\223\344\270\232\346\225\231\350\202\262\350\242\253\350\277\253\345\206\231\346\226\207\357\274\211/index.html" @@ -519,7 +519,7 @@

  • - 编译原理 + 数据库系统
  • @@ -527,15 +527,15 @@

  • - Hexo + 编译原理
  • - 数据库系统 + Go
  • - Go + Hexo
  • diff --git "a/2020/07/10/Hexo\345\215\232\345\256\242\346\220\255\345\273\272/index.html" "b/2020/07/10/Hexo\345\215\232\345\256\242\346\220\255\345\273\272/index.html" index fdde794e..52251f4e 100644 --- "a/2020/07/10/Hexo\345\215\232\345\256\242\346\220\255\345\273\272/index.html" +++ "b/2020/07/10/Hexo\345\215\232\345\256\242\346\220\255\345\273\272/index.html" @@ -217,7 +217,7 @@

    -

    Hexo博客搭建

    鸽了半年的hexo博客搭建,阿里云都快过了半年了,把自己的一些踩坑和修改写一下吧,首先放一下我hexo博客搭建的时候的一些参考吧,这几个链接应该按道理没有很多踩坑的地方,我使用的主题是yilia主题所以说下面几个链接主要是关于yilia的,不过其实hexo博客框架的那个js和css名字都一样,配置文件的语法和格式也是一致的,所以说还是可以提供到一些帮助的,而且我自己在搭建一些操作的时候也学习了其他主题的一些方法。
    从0开始搭建一个hexo博客无坑教程,是真的无坑版本,一切顺利(这里需要感谢一位b站的良心up主codesheep,他其他的视频也不错哦):
    手把手教你从0开始搭建自己的个人博客 |无坑版视频教程| hexo
    美化相关链接:

    +

    Hexo博客搭建

    鸽了半年的hexo博客搭建,阿里云都快过了半年了,把自己的一些踩坑和修改写一下吧,首先放一下我hexo博客搭建的时候的一些参考吧,这几个链接应该按道理没有很多踩坑的地方,我使用的主题是yilia主题所以说下面几个链接主要是关于yilia的,不过其实hexo博客框架的那个js和css名字都一样,配置文件的语法和格式也是一致的,所以说还是可以提供到一些帮助的,而且我自己在搭建一些操作的时候也学习了其他主题的一些方法。
    从0开始搭建一个hexo博客无坑教程,是真的无坑版本,一切顺利(这里需要感谢一位b站的良心up主codesheep,他其他的视频也不错哦):
    手把手教你从0开始搭建自己的个人博客 |无坑版视频教程| hexo
    美化相关链接:

    • 参考链接1
    • 参考链接2
    • @@ -226,18 +226,10 @@

      安装git,nodejs

      安装git和nodejs这个我就不过多介绍了,大家去csdn找找最新的相关安装教程就行了,在codesheep的视频里面也有教大家怎么做,记得切换一下node源到淘宝镜像cnpm,有的时候npm下载特别慢或者是报错。2024-10-9更新:安装node现在最好使用nvm安装管理node版本,再来通过npm装hexo

      -

      官网下载hexo博客框架

      hexo官网,如果在hexo的使用过程有什么问题也可以去看hexo的官方文档
      在这里插入图片描述
      在git或者cmd下面都可以使用该命令完成hexo博客的安装

      -
      npm install -g hexo-cli#建议用这个全局安装
      -

      高级用户可能更喜欢安装和使用hexo软件包。

      -
      npm install hexo
      -

      初始化hexo博客

      安装Hexo后,输入以下命令以创建一个新文件夹初始化Hexo,注意这个文件夹就是我们的以后存放博客等各种主题等配置文件的文件夹了,如果配置过程博客出了问题,重新建一个新文件夹就行了,问题不大,注意信息备份。

      -
      hexo init <folder>
      cd <folder>
      npm install
      -

      npm instal以后目录中会出现这些文件

      +

      官网下载hexo博客框架

      hexo官网,如果在hexo的使用过程有什么问题也可以去看hexo的官方文档
      在这里插入图片描述
      在git或者cmd下面都可以使用该命令完成hexo博客的安装

      npm install -g hexo-cli#建议用这个全局安装

      高级用户可能更喜欢安装和使用hexo软件包。
      npm install hexo

      +

      初始化hexo博客

      安装Hexo后,输入以下命令以创建一个新文件夹初始化Hexo,注意这个文件夹就是我们的以后存放博客等各种主题等配置文件的文件夹了,如果配置过程博客出了问题,重新建一个新文件夹就行了,问题不大,注意信息备份。

      hexo init <folder>
      cd <folder>
      npm install

      npm instal以后目录中会出现这些文件

      ├── _config.yml  最重要的配置文件
      ├── package.json
      ├── scaffolds
      ├── source 这里会存放你的博客文件
      | ├── _drafts
      | └── _posts
      └── themes 这里存放各种主题
      - -

      这样完了以后其实我们的博客就已经搭建完成了,你可以在你初始化后的博客目录下输入一下命令,然后就会开启hexo服务,在浏览器输入http://localhost:4000
      就可以看到你生成的博客了,默认情况下hexo博客主题是landspace

      -
      hexo s
      -

      开启hexo
      默认主题

      +

      这样完了以后其实我们的博客就已经搭建完成了,你可以在你初始化后的博客目录下输入一下命令,然后就会开启hexo服务,在浏览器输入http://localhost:4000
      就可以看到你生成的博客了,默认情况下hexo博客主题是landspace

      hexo s

      开启hexo
      默认主题

      github部署

      对于github上进行部署的话我们首先需要新建一个repo,并且必须命名为:你的github账户名.github.io,建议大家最好配置好ssh这样就不用每次输入密码了
      然后我们需要在博客根目录下npm一个用于部署的模块deploy-git

      npm install hexo-deployer-git --save

      github对于hexo博客的部署是非常方便的,只需要在主配置文件_config.yml下添加一项配置就可以了,配置到服务器上也是如此
      这里我直接将github和服务器都配置好了提交,你如果没有服务器端的配置就直接删掉那一行就行。

      @@ -245,9 +237,7 @@

      #建议每次提交都执行这三个命令,
      #当然了你也可以在直接写成bat文件或者sh文件,就不用每次自己手动输入了
      hexo clean
      hexo g
      hexo d

      他就会自动将我们所写的博客和所需要展示的内容转成htmlcssjs等文件push到我们的github上去,所以说大家如果有配置和修改自己选择的主题的话,可能你需要备份一下,不然到时候就没了,在部署的网页上这些npmmodules和你修改的样式都是直接打包好了的,你的源代码是不会提交上去的。

      -

      常用操作

      好了其实到这里我们的博客搭建就已经完成了,这里我们介绍几个常用的命令和操作吧,

      -
      hexo clean 清除生成的文件
      hexo g 重新生成博客的文件
      hexo s 开启hexo服务,我们可以预览自己新写的博客在网页上的样子
      hexo d 提交部署到服务器上包括github

      hexo new "my first blog" 新建一个名为my first blog的博客,会显示在你的博客根目录的`source/_posts`下面
      -

      在这里插入图片描述
      大家可能会注意到我这里好像不仅生成了文件还生成了一个同名的文件夹,这个文件夹是用于引入同名博客的图片的。
      当然了其实我建议最好在csdn上写好自己的博客发表以后就变成网图了,然后直接复制csdn的博文在commit到hexo上面这样就不用存图片啥的,不过这里还是介绍一下本地图片引入吧

      +

      常用操作

      好了其实到这里我们的博客搭建就已经完成了,这里我们介绍几个常用的命令和操作吧,

      hexo clean 清除生成的文件
      hexo g 重新生成博客的文件
      hexo s 开启hexo服务,我们可以预览自己新写的博客在网页上的样子
      hexo d 提交部署到服务器上包括github

      hexo new "my first blog" 新建一个名为my first blog的博客,会显示在你的博客根目录的`source/_posts`下面

      在这里插入图片描述
      大家可能会注意到我这里好像不仅生成了文件还生成了一个同名的文件夹,这个文件夹是用于引入同名博客的图片的。
      当然了其实我建议最好在csdn上写好自己的博客发表以后就变成网图了,然后直接复制csdn的博文在commit到hexo上面这样就不用存图片啥的,不过这里还是介绍一下本地图片引入吧

      1. 把主页配置文件_config.yml 里的post_asset_folder:这个选项设置为true
      2. 在你的hexo目录下执行这样一句话npm install hexo-asset-image --save,这是下载安装一个可以上传本地图片的插件
      3. @@ -561,7 +551,7 @@

        - 编译原理 + 数据库系统

        最近在复习数据结构,顺便整理之前刷题的一些模板和技巧,希望对大家都有帮助,博客会侧重讲解的是OJ代码实现,理论部分偏少但也会写一些自己的理解。
        在之前大二上数据结构的时候我也有写过一个关于排序的专题介绍数据结构复习——内部排序

        快速排序

        快速排序主要就是通过选取一个基准点,将一个区间内的数分成大于和小于两个部分,然后对左右区间再进行上述操作,直到子区间的长度为空为止。快速排序是不稳定的排序,如果需要变成稳定排序通过双关键字排序即可,通过下标控制绝对大小就能得到稳定的排序结果。
        快速排序分三步走:

        • 确定分界点
        • 调整左右区间
        • 递归处理左右子区间

        在代码实现当中,我们一般选取中间分位点会比较好,这样划分的区间比较平均。在遍历的过程当中每次调整区间的时间是$O(n)$,而区间递归的深度类似二叉树是$O(logn)$
        在最好情况下,对于递归型的算法,我们利用主定理公式来计算快速排序时间复杂度得到$O(nlogn)$

        当然在最坏情况下,也就是数组是有序或者逆序的情况下,我们如果选择左右端点作为基准点,那么整个算法就相当于冒泡排序,递归的层数也就变成了$n$,则最坏时间复杂度为$O(n^2)$

        代码实现

        void quick_sort(int[] q,int l,int r)
        {
        if(l>=r) return;
        // 1.确定分界点
        int i = l - 1, j = r + 1, mid = q[l + r >> 1];
        // 2.调整左右区间
        while(i<j)
        {
        do i++; while(q[i]<mid);
        do j--; while(q[j]>mid);
        if(i<j) swap(q[i],q[j]);
        }
        //3.递归处理左右子区间
        quick_sort(l,j);
        quick_sort(j+1,r);
        }

        经典例题

        洛谷P1177 【模板】快速排序

        归并排序

        归并排序也是基于分治的思想,每次将区间对半分,逐步递归合并有序化子区间,最终实现所有的左右区间的有序归并,但是跟快速排序不同的是,我们需要开一个辅助数组来存储有序的部分,所以时间复杂度为$O(nlogn)$。归并排序是稳定的排序,在元素相等情况下我们总是放入数组下标较小的元素。
        归并排序分三步走:

        • 确定分界点
        • 递归处理子序列
        • 合并有序序列

        olor_FFFFFF,t_70)

        代码实现

        const int N=100010;
        int a[N],tmp[N];
        void merge_sort(int q[],int l,int r)
        {
        if(l>=r)
        return;
        //确定分界点
        int mid = l + r >> 1;

        //递归处理子序列
        merge_sort(q,l,mid);
        merge_sort(q,mid+1,r);

        //合并有序序列
        int k = l,i = l,j = mid + 1;
        while(i <= mid && j <= r)
        {
        if(q[i]<=q[j])// 取等号,保证归并排序的稳定性
        tmp[k++]=q[i++];
        else
        tmp[k++]=q[j++];
        }

        while(i<=mid)
        tmp[k++]=q[i++];
        while(j<=r)
        tmp[k++]=q[j++];

        for(i=l;i<=r;i++)
        q[i]=tmp[i];
        }

        经典例题

        AcWing 787. 归并排序
        AcWing 788. 逆序对的数量

        排序相关时间复杂度整理

        在这里插入图片描述

        ]]> - <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在复习数据结构,顺便整理之前刷题的一些模板和技巧,希望对大家都有帮助,博客会侧重讲解的是OJ代码实现,理论部分偏少但也会写一些自己的理解。<br>在之前大二上数据结构的时候我也有写过一个关于排序的专题介绍<a href="https://blog.csdn.net/Jack___E/article/details/102875285?spm=1001.2014.3001.5502" target="_blank" rel="noopener">数据结构复习——内部排序</a></p> + <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在复习数据结构,顺便整理之前刷题的一些模板和技巧,希望对大家都有帮助,博客会侧重讲解的是OJ代码实现,理论部分偏少但也会写一些自己的理解。<br>在之前大二上数据结构的时候我也有写过一个关于排序的专题介绍<a href="https://blog.csdn.net/Jack___E/article/details/102875285?spm=1001.2014.3001.5502" target="_blank" rel="noopener">数据结构复习——内部排序</a><br></p> @@ -332,14 +332,14 @@ 2021-05-09T09:44:00.000Z 2024-10-09T07:33:09.884Z - EfficientNet论文解读和pytorch代码实现

    传送门

    论文地址:https://arxiv.org/pdf/1905.11946.pdf
    官方github:https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet
    github参考:https://github.com/qubvel/efficientnet
    github参考:https://github.com/lukemelas/EfficientNet-PyTorch

    摘要

    谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!
    Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet.
    卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文系统研究(工作量很充足)了模型缩放,并且证明了细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。

    To go even further, we use neural architecture search to design a new baseline network and scale it up to obtain a family of models, called EfficientNets, which achieve much better accuracy and efficiency than previous ConvNets. In particular, our EfficientNet-B7 achieves state-of-the-art 84.3% top-1 accuracy on ImageNet, while being 8.4x smaller and 6.1x faster on inference than the best existing ConvNet. Our EfficientNets also transfer well and achieve state-of-the-art accuracy on CIFAR-100 (91.7%), Flowers (98.8%), and 3 other transfer learning datasets, with an order of magnitude fewer parameters. Source code is athttps://github.com/tensorflow/tpu/tree/
    master/models/official/efficientnet.

    更进一步,我们使用神经网络结构搜索设计了一个新的baseline网络架构并且对其进行缩放得到了一组模型,我们把他叫做EfficientNets。EfficientNets相比之前的ConvNets,有着更好的准确率和更高的效率。在ImageNet上达到了最好的水平即top-1准确率84.4%/top-5准确率97.1%,然而却比已有的最好的ConvNet模型小了8.4倍并且推理时间快了6.1倍。我们的EfficientNet迁移学习的效果也好,达到了最好的准确率水平CIFAR-100(91.7%),Flowers(98.8%),和其他3个迁移学习数据集合,参数少了一个数量级(with an order of magnitude fewer parameters)

    论文思想

    在之前的一些工作当中,我们可以看到,有的会通过增加网络的width即增加卷积核的个数(从而增大channels)来提升网络的性能,有的会通过增加网络的深度即使用更多的层结构来提升网络的性能,有的会通过增加输入网络的分辨率来提升网络的性能。而在Efficientnet当中则重新探究了这个问题,并提出了一种组合条件这三者的网络结构,同时量化了这三者的指标。
    在这里插入图片描述

    The intuition is that deeper ConvNet can capture richer and more complex features, and generalize well on new tasks. However, deeper networks are also more difficult to train due to the vanishing gradient problem
    增加网络的深度depth能够得到更加丰富、复杂的特征并且能够在其他新任务中很好的泛化性能。但是,由于逐渐消失的梯度问题,更深层的网络也更难以训练。

    wider networks tend to be able to capture more fine-grained features and are easier to train. However, extremely wide but shallow networks tend to have difficulties in capturing higher level features
    with更广的网络往往能够捕获更多细粒度的功能,并且更易于训练。但是,极其宽泛但较浅的网络在捕获更高级别的特征时往往会遇到困难

    With higher resolution input images, ConvNets can potentially capture more fine-grained pattern,but the accuracy gain diminishes for very high resolutions
    使用更高分辨率的输入图像,ConvNets可以捕获更细粒度的图案,但对于非常高分辨率的图片,准确度会降低。

    作者在论文中对整个网络的运算过程和复合扩展方法进行了抽象:
    首先定义了每一层卷积网络为$\mathcal{F}{i}\left(X{i}\right)$,而$X_i$是输入张量,$Y_i$是输出张量,而tensor的形状是$<H_i,W_i,C_i>$
    整个卷积网络由 k 个卷积层组成,可以表示为$\mathcal{N}=\mathcal{F}{k} \odot \ldots \odot \mathcal{F}{2} \odot \mathcal{F}{1}\left(X{1}\right)=\bigodot_{j=1 \ldots k} \mathcal{F}{j}\left(X{1}\right)$
    从而得到我们的整个卷积网络:
    $$
    \mathcal{N}=\bigodot_{i=1 \ldots s} \mathcal{F}{i}^{L{i}}\left(X_{\left\langle H_{i}, W_{i}, C_{i}\right\rangle}\right)
    $$

    下标 i(从 1 到 s) 表示的是 stage 的序号,$\mathcal{F}{i}^{L{i}}$表示第 i 个 stage ,它表示卷积层 $\mathcal{F}{i}$重复了${L{i}}$ 次。

    为了探究$d , r , w$这三个因子对最终准确率的影响,则将$d , r , w$加入到公式中,我们可以得到抽象化后的优化问题(在指定资源限制下),其中$s.t.$代表限制条件:
    在这里插入图片描述

    我们限制$\alpha \cdot \beta^{2} \cdot \gamma^{2} \approx 2$所以对于任意给定的$\phi$值,我们总共的运算量将大约增加$2^{\phi}$

    网络结构

    文中给出了Efficientnet-B0的基准框架,在Efficientnet当中,我们所使用的激活函数是swish激活函数,整个网络分为了9个stage,在第2到第8stage是一直在堆叠MBConv结构,表格当中MBConv结构后面的数字(1或6)表示的就是MBConv中第一个1x1的卷积层会将输入特征矩阵的channels扩充为n倍。Channels表示通过该Stage后输出特征矩阵的Channels。
    在这里插入图片描述

    MBConv结构

    MBConv的结构相对来说还是十分好理解的,这里是我自己画的一张结构图。
    在这里插入图片描述
    MBConv结构主要由一个1x1的expand conv(升维作用,包含BN和Swish激活函数)提升维度的倍率正是之前网络结构中看到的1和6,一个KxK的卷积深度可分离卷积Depthwise Conv(包含BN和Swish)k的具体值可看EfficientNet-B0的网络框架主要有3x3和5x5两种情况,一个SE模块,一个1x1的pointer conv降维作用,包含BN),一个Droupout层构成,一般情况下都添加上了残差边模块Shortcut,从而得到输出特征矩阵。
    首先关于深度可分离卷积的概念是是我们需要了解的,相当于将卷积的过程拆成运算和合并两个过程,分别对应depthwise和point两个过程,这里给一个参考连接给大家就不在详细描述了Depthwise卷积与Pointwise卷积
    在这里插入图片描述
    1.由于该模块最初是用tensorflow来实现的与pytorch有一些小差别,所以在使用pytorch实现的时候我们最好将tensorflow当中的samepadding操作重新实现一下
    2.在batchnormal时候的动量设置也是有一些不一样的,论文当中设置的batch_norm_momentum=0.99,在pytorch当中需要先执行1-momentum再设为参数与tensorflow不同。

    # Batch norm parameters
    momentum= 1 - batch_norm_momentum # pytorch's difference from tensorflow
    eps= batch_norm_epsilon
    nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)
    class Conv2dSamePadding(nn.Conv2d):
    """2D Convolutions like TensorFlow, for a dynamic image size.
    The padding is operated in forward function by calculating dynamically.
    """

    # Tips for 'SAME' mode padding.
    # Given the following:
    # i: width or height
    # s: stride
    # k: kernel size
    # d: dilation
    # p: padding
    # Output after Conv2d:
    # o = floor((i+p-((k-1)*d+1))/s+1)
    # If o equals i, i = floor((i+p-((k-1)*d+1))/s+1),
    # => p = (i-1)*s+((k-1)*d+1)-i

    def __init__(self, in_channels, out_channels, kernel_size,padding=0, stride=1, dilation=1,
    groups=1, bias=True, padding_mode = 'zeros'):
    super().__init__(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias)
    self.stride = self.stride if len(self.stride) == 2 else [self.stride[0]] * 2

    def forward(self, x):
    ih, iw = x.size()[-2:] # input
    kh, kw = self.weight.size()[-2:] # because kernel size equals to weight size
    sh, sw = self.stride # stride

    # change the output size according to stride
    oh, ow = math.ceil(ih/sh), math.ceil(iw/sw) # output
    """
    kernel effective receptive field size: (kernel_size-1) x dilation + 1
    """
    pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0)
    pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0)

    if pad_h > 0 or pad_w > 0:
    x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2])

    return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)

    MBConvBlock模块结构

    class ConvBNActivation(nn.Module):
    """Convolution BN Activation"""
    def __init__(self,in_channels,out_channels,kernel_size,stride=1,groups=1,
    bias=False,momentum=0.01,eps=1e-3,active=False):
    super().__init__()
    self.layer=nn.Sequential(
    Conv2dSamePadding(in_channels,out_channels,kernel_size=kernel_size,
    stride=stride,groups=groups, bias=bias),
    nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)
    )
    self.active=active
    self.swish = Swish()

    def forward(self, x):
    x = self.layer(x)
    if self.active:
    x=self.swish(x)
    return x




    class MBConvBlock(nn.Module):
    def __init__(self,in_channels,out_channels,kernel_size,stride,
    expand_ratio=1, momentum=0.01,eps=1e-3,
    drop_connect_ratio=0.2,training=True,
    se_ratio=0.25,skip=True, image_size=None):
    super().__init__()
    self.expand_ratio = expand_ratio
    self.se_ratio = se_ratio
    self.has_se = (se_ratio is not None) and (0 < se_ratio <= 1)
    self.skip = skip and stride == 1 and in_channels == out_channels

    # 1x1 convolution channels expansion phase
    expand_channels = in_channels * self.expand_ratio # number of output channels

    if self.expand_ratio != 1:
    self.expand_conv = ConvBNActivation(in_channels=in_channels,out_channels=expand_channels,
    momentum=momentum,eps=eps,
    kernel_size=1,bias=False,active=False)

    # Depthwise convolution phase
    self.depthwise_conv = ConvBNActivation(in_channels=expand_channels,out_channels=expand_channels,
    momentum=momentum,eps=eps,
    kernel_size=kernel_size,stride=stride,groups=expand_channels,bias=False,active=False)

    # Squeeze and Excitation module
    if self.has_se:
    sequeeze_channels = max(1,int(expand_channels*self.se_ratio))
    self.se = SEModule(expand_channels,sequeeze_channels)

    # Pointwise convolution phase
    self.project_conv = ConvBNActivation(expand_channels,out_channels,momentum=momentum,eps=eps,
    kernel_size=1,bias=False,active=True)

    self.drop_connect = DropConnect(drop_connect_ratio,training)

    def forward(self,x):
    input_x = x
    if self.expand_ratio != 1:
    x = self.expand_conv(x)
    x = self.depthwise_conv(x)
    if self.has_se:
    x = self.se(x)
    x = self.project_conv(x)
    if self.skip:
    x = self.drop_connect(x)
    x = x + input_x
    return x

    SE模块

    SE模块是通道注意力模块,该模块能够关注channel之间的关系,可以让模型自主学习到不同channel特征的重要程度。由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv特征矩阵channels的1/4 ,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv层输出的特征矩阵channels,且使用Sigmoid激活函数,这里引用一下别人画好的示意图。
    在这里插入图片描述
    在这里插入图片描述

    class SEModule(nn.Module):
    """Squeeze and Excitation module for channel attention"""
    def __init__(self,in_channels,squeezed_channels):
    super().__init__()
    self.global_avg_pool = nn.AdaptiveAvgPool2d(1)
    self.sequential = nn.Sequential(
    nn.Conv2d(in_channels,squeezed_channels,1), # use 1x1 convolution instead of linear
    Swish(),
    nn.Conv2d(squeezed_channels,in_channels,1),
    nn.Sigmoid()
    )

    def forward(self,x):
    x= self.global_avg_pool(x)
    y = self.sequential(x)
    return x * y

    swish激活函数

    Swish是Google提出的一种新型激活函数,其原始公式为:f(x)=x * sigmod(x),变形Swish-B激活函数的公式则为f(x)=x * sigmod(b * x),其拥有不饱和,光滑,非单调性的特征,Google在论文中的多项测试表明Swish以及Swish-B激活函数的性能即佳,在不同的数据集上都表现出了要优于当前最佳激活函数的性能。
    $$f(x)=x \cdot \operatorname{sigmoid}(\beta x)$$
    其中$\beta$是个常数或可训练的参数,Swish 具备无上界有下界、平滑、非单调的特性。

    class Swish(nn.Module):
    """ swish activation function"""
    def forward(self,x):
    return x * torch.sigmoid(x)

    droupout代码实现

    class DropConnect(nn.Module):
    """Drop Connection"""
    def __init__(self,ratio,training):
    super().__init__()
    assert 0 <= ratio <= 1
    self.keep_prob = 1 - ratio
    self.training=training

    def forward(self,x):
    if not self.training:
    return x
    batch_size = x.shape[0]

    random_tensor = self.keep_prob
    random_tensor += torch.rand([batch_size,1,1,1],dtype=x.dtype,device=x.device)
    # generate binary_tensor mask according to probability (p for 0, 1-p for 1)
    binary_tensor = torch.floor(random_tensor)

    output = x / self.keep_prob * binary_tensor
    return output

    总结

    这里我们只给出了b0结构的代码实现,对于其他结构的实现过程就是在b0的基础上对wdith,depth,resolution都通过倍率因子统一缩放,这个部分在这个博客里面有详细的介绍EfficientNet(B0-B7)参数设置
    在本文中,我们系统地研究了ConvNet的缩放比例,分析了各因素对网络结构的影响,并确定仔细平衡网络的宽度,深度和分辨率,这是目前网络训练最重要但缺乏研究的部分,也是我们无法获得更好的准确性和效率忽略的一个部分。为解决此问题,本文作者提出了一种简单而高效的复合缩放方法,通过一个倍率因子统一缩放各部分比例并添加上通道注意力机制和残差模块,从而改善网络架构得到SOTA的效果,同时对模型各成分因子进行量化十分具有创新性。

    参考连接
    EfficientNet网络详解
    神经网络学习小记录50——Pytorch EfficientNet模型的复现详解
    论文翻译
    令人拍案叫绝的EfficientNet和EfficientDet
    swish激活函数
    Depthwise卷积与Pointwise卷积
    CV领域常用的注意力机制模块(SE、CBAM)
    Global Average Pooling

    ]]> + EfficientNet论文解读和pytorch代码实现
  • 传送门

    论文地址:https://arxiv.org/pdf/1905.11946.pdf
    官方github:https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet
    github参考:https://github.com/qubvel/efficientnet
    github参考:https://github.com/lukemelas/EfficientNet-PyTorch

    摘要

    谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!
    Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet.
    卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文系统研究(工作量很充足)了模型缩放,并且证明了细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。

    To go even further, we use neural architecture search to design a new baseline network and scale it up to obtain a family of models, called EfficientNets, which achieve much better accuracy and efficiency than previous ConvNets. In particular, our EfficientNet-B7 achieves state-of-the-art 84.3% top-1 accuracy on ImageNet, while being 8.4x smaller and 6.1x faster on inference than the best existing ConvNet. Our EfficientNets also transfer well and achieve state-of-the-art accuracy on CIFAR-100 (91.7%), Flowers (98.8%), and 3 other transfer learning datasets, with an order of magnitude fewer parameters. Source code is athttps://github.com/tensorflow/tpu/tree/
    master/models/official/efficientnet.

    更进一步,我们使用神经网络结构搜索设计了一个新的baseline网络架构并且对其进行缩放得到了一组模型,我们把他叫做EfficientNets。EfficientNets相比之前的ConvNets,有着更好的准确率和更高的效率。在ImageNet上达到了最好的水平即top-1准确率84.4%/top-5准确率97.1%,然而却比已有的最好的ConvNet模型小了8.4倍并且推理时间快了6.1倍。我们的EfficientNet迁移学习的效果也好,达到了最好的准确率水平CIFAR-100(91.7%),Flowers(98.8%),和其他3个迁移学习数据集合,参数少了一个数量级(with an order of magnitude fewer parameters)

    论文思想

    在之前的一些工作当中,我们可以看到,有的会通过增加网络的width即增加卷积核的个数(从而增大channels)来提升网络的性能,有的会通过增加网络的深度即使用更多的层结构来提升网络的性能,有的会通过增加输入网络的分辨率来提升网络的性能。而在Efficientnet当中则重新探究了这个问题,并提出了一种组合条件这三者的网络结构,同时量化了这三者的指标。
    在这里插入图片描述

    The intuition is that deeper ConvNet can capture richer and more complex features, and generalize well on new tasks. However, deeper networks are also more difficult to train due to the vanishing gradient problem
    增加网络的深度depth能够得到更加丰富、复杂的特征并且能够在其他新任务中很好的泛化性能。但是,由于逐渐消失的梯度问题,更深层的网络也更难以训练。

    wider networks tend to be able to capture more fine-grained features and are easier to train. However, extremely wide but shallow networks tend to have difficulties in capturing higher level features
    with更广的网络往往能够捕获更多细粒度的功能,并且更易于训练。但是,极其宽泛但较浅的网络在捕获更高级别的特征时往往会遇到困难

    With higher resolution input images, ConvNets can potentially capture more fine-grained pattern,but the accuracy gain diminishes for very high resolutions
    使用更高分辨率的输入图像,ConvNets可以捕获更细粒度的图案,但对于非常高分辨率的图片,准确度会降低。

    作者在论文中对整个网络的运算过程和复合扩展方法进行了抽象:
    首先定义了每一层卷积网络为$\mathcal{F}{i}\left(X{i}\right)$,而$Xi$是输入张量,$Y_i$是输出张量,而tensor的形状是$$
    整个卷积网络由 k 个卷积层组成,可以表示为$\mathcal{N}=\mathcal{F}
    {k} \odot \ldots \odot \mathcal{F}{2} \odot \mathcal{F}{1}\left(X{1}\right)=\bigodot{j=1 \ldots k} \mathcal{F}{j}\left(X{1}\right)$
    从而得到我们的整个卷积网络:

    下标 i(从 1 到 s) 表示的是 stage 的序号,$\mathcal{F}{i}^{L{i}}$表示第 i 个 stage ,它表示卷积层 $\mathcal{F}{i}$重复了${L{i}}$ 次。

    为了探究$d , r , w$这三个因子对最终准确率的影响,则将$d , r , w$加入到公式中,我们可以得到抽象化后的优化问题(在指定资源限制下),其中$s.t.$代表限制条件:
    在这里插入图片描述

    网络结构

    文中给出了Efficientnet-B0的基准框架,在Efficientnet当中,我们所使用的激活函数是swish激活函数,整个网络分为了9个stage,在第2到第8stage是一直在堆叠MBConv结构,表格当中MBConv结构后面的数字(1或6)表示的就是MBConv中第一个1x1的卷积层会将输入特征矩阵的channels扩充为n倍。Channels表示通过该Stage后输出特征矩阵的Channels。
    在这里插入图片描述

    MBConv结构

    MBConv的结构相对来说还是十分好理解的,这里是我自己画的一张结构图。
    在这里插入图片描述
    MBConv结构主要由一个1x1的expand conv(升维作用,包含BN和Swish激活函数)提升维度的倍率正是之前网络结构中看到的1和6,一个KxK的卷积深度可分离卷积Depthwise Conv(包含BN和Swish)k的具体值可看EfficientNet-B0的网络框架主要有3x3和5x5两种情况,一个SE模块,一个1x1的pointer conv降维作用,包含BN),一个Droupout层构成,一般情况下都添加上了残差边模块Shortcut,从而得到输出特征矩阵。
    首先关于深度可分离卷积的概念是是我们需要了解的,相当于将卷积的过程拆成运算和合并两个过程,分别对应depthwise和point两个过程,这里给一个参考连接给大家就不在详细描述了Depthwise卷积与Pointwise卷积
    在这里插入图片描述
    1.由于该模块最初是用tensorflow来实现的与pytorch有一些小差别,所以在使用pytorch实现的时候我们最好将tensorflow当中的samepadding操作重新实现一下
    2.在batchnormal时候的动量设置也是有一些不一样的,论文当中设置的batch_norm_momentum=0.99,在pytorch当中需要先执行1-momentum再设为参数与tensorflow不同。

    # Batch norm parameters
    momentum= 1 - batch_norm_momentum # pytorch's difference from tensorflow
    eps= batch_norm_epsilon
    nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)

    class Conv2dSamePadding(nn.Conv2d):
    """2D Convolutions like TensorFlow, for a dynamic image size.
    The padding is operated in forward function by calculating dynamically.
    """

    # Tips for 'SAME' mode padding.
    # Given the following:
    # i: width or height
    # s: stride
    # k: kernel size
    # d: dilation
    # p: padding
    # Output after Conv2d:
    # o = floor((i+p-((k-1)*d+1))/s+1)
    # If o equals i, i = floor((i+p-((k-1)*d+1))/s+1),
    # => p = (i-1)*s+((k-1)*d+1)-i

    def __init__(self, in_channels, out_channels, kernel_size,padding=0, stride=1, dilation=1,
    groups=1, bias=True, padding_mode = 'zeros'):
    super().__init__(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias)
    self.stride = self.stride if len(self.stride) == 2 else [self.stride[0]] * 2

    def forward(self, x):
    ih, iw = x.size()[-2:] # input
    kh, kw = self.weight.size()[-2:] # because kernel size equals to weight size
    sh, sw = self.stride # stride

    # change the output size according to stride
    oh, ow = math.ceil(ih/sh), math.ceil(iw/sw) # output
    """
    kernel effective receptive field size: (kernel_size-1) x dilation + 1
    """
    pad_h = max((oh - 1) * self.stride[0] + (kh - 1) * self.dilation[0] + 1 - ih, 0)
    pad_w = max((ow - 1) * self.stride[1] + (kw - 1) * self.dilation[1] + 1 - iw, 0)

    if pad_h > 0 or pad_w > 0:
    x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2])

    return F.conv2d(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)

    MBConvBlock模块结构

    class ConvBNActivation(nn.Module):
    """Convolution BN Activation"""
    def __init__(self,in_channels,out_channels,kernel_size,stride=1,groups=1,
    bias=False,momentum=0.01,eps=1e-3,active=False):
    super().__init__()
    self.layer=nn.Sequential(
    Conv2dSamePadding(in_channels,out_channels,kernel_size=kernel_size,
    stride=stride,groups=groups, bias=bias),
    nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)
    )
    self.active=active
    self.swish = Swish()

    def forward(self, x):
    x = self.layer(x)
    if self.active:
    x=self.swish(x)
    return x




    class MBConvBlock(nn.Module):
    def __init__(self,in_channels,out_channels,kernel_size,stride,
    expand_ratio=1, momentum=0.01,eps=1e-3,
    drop_connect_ratio=0.2,training=True,
    se_ratio=0.25,skip=True, image_size=None):
    super().__init__()
    self.expand_ratio = expand_ratio
    self.se_ratio = se_ratio
    self.has_se = (se_ratio is not None) and (0 < se_ratio <= 1)
    self.skip = skip and stride == 1 and in_channels == out_channels

    # 1x1 convolution channels expansion phase
    expand_channels = in_channels * self.expand_ratio # number of output channels

    if self.expand_ratio != 1:
    self.expand_conv = ConvBNActivation(in_channels=in_channels,out_channels=expand_channels,
    momentum=momentum,eps=eps,
    kernel_size=1,bias=False,active=False)

    # Depthwise convolution phase
    self.depthwise_conv = ConvBNActivation(in_channels=expand_channels,out_channels=expand_channels,
    momentum=momentum,eps=eps,
    kernel_size=kernel_size,stride=stride,groups=expand_channels,bias=False,active=False)

    # Squeeze and Excitation module
    if self.has_se:
    sequeeze_channels = max(1,int(expand_channels*self.se_ratio))
    self.se = SEModule(expand_channels,sequeeze_channels)

    # Pointwise convolution phase
    self.project_conv = ConvBNActivation(expand_channels,out_channels,momentum=momentum,eps=eps,
    kernel_size=1,bias=False,active=True)

    self.drop_connect = DropConnect(drop_connect_ratio,training)

    def forward(self,x):
    input_x = x
    if self.expand_ratio != 1:
    x = self.expand_conv(x)
    x = self.depthwise_conv(x)
    if self.has_se:
    x = self.se(x)
    x = self.project_conv(x)
    if self.skip:
    x = self.drop_connect(x)
    x = x + input_x
    return x

    SE模块

    SE模块是通道注意力模块,该模块能够关注channel之间的关系,可以让模型自主学习到不同channel特征的重要程度。由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv特征矩阵channels的1/4 ,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv层输出的特征矩阵channels,且使用Sigmoid激活函数,这里引用一下别人画好的示意图。
    在这里插入图片描述
    在这里插入图片描述

    class SEModule(nn.Module):
    """Squeeze and Excitation module for channel attention"""
    def __init__(self,in_channels,squeezed_channels):
    super().__init__()
    self.global_avg_pool = nn.AdaptiveAvgPool2d(1)
    self.sequential = nn.Sequential(
    nn.Conv2d(in_channels,squeezed_channels,1), # use 1x1 convolution instead of linear
    Swish(),
    nn.Conv2d(squeezed_channels,in_channels,1),
    nn.Sigmoid()
    )

    def forward(self,x):
    x= self.global_avg_pool(x)
    y = self.sequential(x)
    return x * y

    swish激活函数

    Swish是Google提出的一种新型激活函数,其原始公式为:f(x)=x sigmod(x),变形Swish-B激活函数的公式则为f(x)=x sigmod(b * x),其拥有不饱和,光滑,非单调性的特征,Google在论文中的多项测试表明Swish以及Swish-B激活函数的性能即佳,在不同的数据集上都表现出了要优于当前最佳激活函数的性能。

    其中$\beta$是个常数或可训练的参数,Swish 具备无上界有下界、平滑、非单调的特性。

    class Swish(nn.Module):
    """ swish activation function"""
    def forward(self,x):
    return x * torch.sigmoid(x)

    droupout代码实现

    class DropConnect(nn.Module):
    """Drop Connection"""
    def __init__(self,ratio,training):
    super().__init__()
    assert 0 <= ratio <= 1
    self.keep_prob = 1 - ratio
    self.training=training

    def forward(self,x):
    if not self.training:
    return x
    batch_size = x.shape[0]

    random_tensor = self.keep_prob
    random_tensor += torch.rand([batch_size,1,1,1],dtype=x.dtype,device=x.device)
    # generate binary_tensor mask according to probability (p for 0, 1-p for 1)
    binary_tensor = torch.floor(random_tensor)

    output = x / self.keep_prob * binary_tensor
    return output

    总结

    这里我们只给出了b0结构的代码实现,对于其他结构的实现过程就是在b0的基础上对wdith,depth,resolution都通过倍率因子统一缩放,这个部分在这个博客里面有详细的介绍EfficientNet(B0-B7)参数设置
    在本文中,我们系统地研究了ConvNet的缩放比例,分析了各因素对网络结构的影响,并确定仔细平衡网络的宽度,深度和分辨率,这是目前网络训练最重要但缺乏研究的部分,也是我们无法获得更好的准确性和效率忽略的一个部分。为解决此问题,本文作者提出了一种简单而高效的复合缩放方法,通过一个倍率因子统一缩放各部分比例并添加上通道注意力机制和残差模块,从而改善网络架构得到SOTA的效果,同时对模型各成分因子进行量化十分具有创新性。

    参考连接
    EfficientNet网络详解
    神经网络学习小记录50——Pytorch EfficientNet模型的复现详解
    论文翻译
    令人拍案叫绝的EfficientNet和EfficientDet
    swish激活函数
    Depthwise卷积与Pointwise卷积
    CV领域常用的注意力机制模块(SE、CBAM)
    Global Average Pooling

    ]]> <h2 id="EfficientNet论文解读和pytorch代码实现"><a href="#EfficientNet论文解读和pytorch代码实现" class="headerlink" title="EfficientNet论文解读和pytorch代码实现"></a>EfficientNet论文解读和pytorch代码实现</h2><h2 id="传送门"><a href="#传送门" class="headerlink" title="传送门"></a>传送门</h2><blockquote> <p>论文地址:<a href="https://arxiv.org/pdf/1905.11946.pdf" target="_blank" rel="noopener">https://arxiv.org/pdf/1905.11946.pdf</a><br>官方github:<a href="https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet" target="_blank" rel="noopener">https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet</a><br>github参考:<a href="https://github.com/qubvel/efficientnet" target="_blank" rel="noopener">https://github.com/qubvel/efficientnet</a><br>github参考:<a href="https://github.com/lukemelas/EfficientNet-PyTorch" target="_blank" rel="noopener">https://github.com/lukemelas/EfficientNet-PyTorch</a></p> </blockquote> -<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!<br><code>Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet. </code><br>卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文<strong>系统研究(工作量很充足)</strong>了模型缩放,并且证明了<strong>细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果</strong>,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:<strong>使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度</strong>。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。</p> +<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!<br><code>Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet.</code><br>卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文<strong>系统研究(工作量很充足)</strong>了模型缩放,并且证明了<strong>细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果</strong>,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:<strong>使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度</strong>。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。<br></p> @@ -356,11 +356,11 @@ 2021-04-22T02:20:00.000Z 2024-10-09T07:33:09.898Z - yolov1-3论文解析

    最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。

    yolov1论文思想

    物体检测主流的算法框架大致分为one-stage与two-stage。two-stage算法代表有R-CNN系列,one-stage算法代表有Yolo系列。按笔者理解,two-stage算法将步骤一与步骤二分开执行,输入图像先经过候选框生成网络(例如faster rcnn中的RPN网络),再经过分类网络;one-stage算法将步骤一与步骤二同时执行,输入图像只经过一个网络,生成的结果中同时包含位置与类别信息。two-stage与one-stage相比,精度高,但是计算量更大,所以运算较慢。

    We present YOLO, a new approach to object detection. Prior work on object detection repurposes classifiers to perform detection. Instead, we frame object detection as a regression problem to spatially separated bounding boxes and associated class probabilities.

    yolo相比其他R-CNN等方法最大的一个不同就是他将这个识别和定位的过程转化成了一个空间定位并分类的回归问题,这也是他为什么能够利用网络进行端到端直接同时预测的重要原因。

    yolo的特点

    1.YOLO速度非常快。由于我们的检测是当做一个回归问题,不需要很复杂的流程。在测试的时候我们只需将一个新的图片输入网络来检测物体。
    First, YOLO is extremely fast. Since we frame detection as a regression problem we don’t need a complex pipeline
    2.Yolo会基于整张图片信息进行预测,与基于滑动窗口和候选区域的方法不同,在训练和测试期间YOLO可以看到整个图像,所以它隐式地记录了分类类别的上下文信息及它的全貌
    Second, YOLO reasons globally about the image when making predictions Unlike sliding window and region proposal-based techniques, YOLO sees the entire image during training and test time so it implicitly encodes contextual information about classes as well as their appearance
    3.第三,YOLO能学习到目标的泛化表征。作者尝试了用自然图片数据集进行训练,用艺术画作品进行预测,Yolo的检测效果更佳。
    Third, YOLO learns generalizable representations of objects. Since YOLO is highly generalizable it is less likely to break down when applied to new domains or unexpected inputs

    backbone-darknet

    在这里插入图片描述
    yolov1这块的话,由于是比较早期的网络所以在设计时没有使用batchnormal,激活函数上采用leaky relu,输入图像大小为448x448,经过许多卷积层与池化层,变为7x7x1024张量,最后经过两层全连接层,输出张量维度为7x7x30。除了最后一层的输出使用了线性激活函数,其他层全部使用Leaky Relu激活函数。
    $$\phi(x)= \begin{cases}
    x,& \text{if x>0} \
    0.1x,& \text{otherwise}
    \end{cases}$$

    输出维度

    yolov1最精髓的地方应该就是他的输出维度,论文当中有放出这样一张图片,看完以后我们能对yolo算法的特点有更加深刻的理解。在上图的backbone当中我们可以看到的是
    在这里插入图片描述yolo的输出相当于把原图片分割成了$S \times S$个区域,然后对预测目标标定他的boundingbox,boundingbox由中心点的坐标,长宽以及物体的置信分数组成$x,y,w,h,confidence$,图中的$B$表示的是boundingbox的个数。置信分数在yolo当中是这样定义的:$Pr(Object)IOU_{pred}^{truth}$对于$Pr(Object)=1 \text{ if detect object else 0}$,其实置信分数就是boudingbox与groundtruth之间的IOU,并对每个小网格对应的$C$个类别都预测出他的条件概率:$Pr(Class_i|Object)$。
    在推理时,我们可以得到当前网格对于每个类别的预测置信分数:$$Pr(Class_i)*IOU_{pred}^{truth} = Pr(Class_i|Object)*Pr(Object)*IOU_{pred}^{truth}$$
    这样一种严谨的条件概率的方式得到我们的最终概率是十分可行和合适的。
    YOLO imposes strong spatial constraints on bounding box predictions since each grid cell only predicts two boxes and can only have one class. This spatial constraint limits the number of nearby objects that our model can predict. Our model struggles with small objects that appear in groups, such as flocks of birds.
    *
    不过在原论文当中也有提到的一点是:YOLO给边界框预测强加空间约束,因为每个网格单元只预测两个框和只能有一个类别。这个空间约束限制了我们的模型可以预测的邻近目标的数量。我们的模型难以预测群组中出现的小物体(比如鸟群)。**

    损失函数

    在损失函数方面,由于公式比较长所以就直接截图过来了,损失函数的设计放方面作者也考虑的比较周全,
    在这里插入图片描述
    预测框的中心点$(x,y)$。预测框的宽高$w,h$,其中$\mathbb{1}_{i j}^{\text {obj }}$为控制函数,在标签中包含物体的那些格点处,该值为 1 ,若格点不含有物体,该值为 0

    在计算损失函数的时候作者最开始使用的是最简单的平方损失函数来计算检测,因为它是计算简单且容易优化的,但是它并不完全符合我们最大化平均精度的目标,原因如下:
    It weights localization error equally with classification error which may not be ideal.
    1.它对定位错误的权重与分类错误的权重相等,这可能不是理想的选择。
    Also, in every image many grid cells do not contain any object. This pushes the “confidence” scores of those cells towards zero, often overpowering the gradient from cells that do contain objects. This can lead to model instability, causing training to diverge early on.
    而且,在每个图像中,许多网格单元不包含任何对象。这就把这些网格的置信度分数推到零,往往超过了包含对象的网格的梯度。2.这可能导致模型不稳定,导致训练早期发散难以训练。

    解决方案:增加边界框坐标预测的损失,并且减少了不包含对象的框的置信度预测的损失。我们使用两个参数来实现这一点。$\lambda_{coord}=5,\lambda_{noobj}=0.5$,其实这也是yolo面临的一个样本数目不均衡的典型例子。主要目的是让含有物体的格点,在损失函数中的权重更大,让模型更加注重含有物体的格点所造成的损失。
    我们的损失函数则只会对那些有真实物体所属的格点进行损失计算,若该格点不包含物体,那么预测数值不对损失函数造成影响。

    这里对$w,h$在损失函数中的处理分别取了根号,原因在于,如果不取根号,损失函数往往更倾向于调整尺寸比较大的预测框。例如,20个像素点的偏差,对于800x600的预测框几乎没有影响,此时的IOU数值还是很大,但是对于30x40的预测框影响就很大。取根号是为了尽可能的消除大尺寸框与小尺寸框之间的差异。

    预测框的置信度$C_i$。当该格点不含有物体时,该置信度的标签为0;若含有物体时,该置信度的标签为预测框与真实物体框的IOU数值(。
    物体类别概率$P_i$,对应的类别位置,该标签数值为1,其余位置为0,与分类网络相同。

    yolov2:Better, Faster, Stronger

    yolov2的论文里面其实更多的是对训练数据集的组合和扩充,然后再添加了很多计算机视觉方面新出的trick,然后达到了性能的提升。同时yolov2方面所使用的trick也是现在计算机视觉常用的方法,我认为这些都是我们需要好好掌握的,同时也是面试和kaggle上都经常使用的一些方法。

    Batch Normalization

    BN是由Google于2015年提出,论文是《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》,这是一个深度神经网络训练的技巧,主要是让数据的分布变得一致,从而使得训练深层网络模型更加容易和稳定。
    这里有一些相关的链接可以参考一下

    https://www.cnblogs.com/itmorn/p/11241236.html
    https://zhuanlan.zhihu.com/p/24810318
    https://zhuanlan.zhihu.com/p/93643523
    莫烦python
    李宏毅yyds

    算法具体过程:
    在这里插入图片描述

    这里要提一下这个$\gamma和\beta$是可训练参数,维度等于张量的通道数,主要是为了在反向传播更新网络时使得标准化后的数据分布与原始数据尽可能保持一直,从而很好的抽象和保留整个batch的数据分布。

    Batch Normalization的作用:
    将这些输入值或卷积网络的张量在batch维度上进行类似标准化的操作,将其放缩到合适的范围,加快模型训练时的收敛速度,使得模型训练过程更加稳定,避免梯度爆炸或者梯度消失,并且起到一定的正则化作用。

    High Resolution Classifier

    在Yolov1中,网络的backbone部分会在ImageNet数据集上进行预训练,训练时网络输入图像的分辨率为224x224。在v2中,将分类网络在输入图片分辨率为448x448的ImageNet数据集上训练10个epoch,再使用检测数据集(例如coco)进行微调。高分辨率预训练使mAP提高了大约4%。

    Convolutional With Anchor Boxes

    predicts these offsets at every location in a feature map
    Predicting offsets instead of coordinates simplifies the problem and makes it easier for the network to learn.
    第一篇解读v1时提到,每个格点预测两个矩形框,在计算loss时,只让与ground truth最接近的框产生loss数值,而另一个框不做修正。这样规定之后,作者发现两个框在物体的大小、长宽比、类别上逐渐有了分工。在v2中,神经网络不对预测矩形框的宽高的绝对值进行预测,而是预测与Anchor框的偏差(offset),每个格点指定n个Anchor框。在训练时,最接近ground truth的框产生loss,其余框不产生loss。在引入Anchor Box操作后,mAP由69.5下降至69.2,原因在于,每个格点预测的物体变多之后,召回率大幅上升,准确率有所下降,总体mAP略有下降。

    Dimension Clusters

    Instead of choosing priors by hand, we run k-means clustering on the training set bounding boxes to automatically find good priors.
    这里算是作者数据处理上的一个创新点,这里作者提到之前的工作当中先Anchor Box都是认为设定的比例和大小,而这里作者时采用无监督的方式将训练数据集中的矩形框全部拿出来,用kmeans聚类得到先验框的宽和高。使用(1-IOU)数值作为两个矩形框的的距离函数,这个处理也十分的聪明。
    $$
    d(\text { box }, \text { centroid })=1-\operatorname{IOU}(\text { box }, \text { centroid })
    $$
    在这里插入图片描述

    Direct location prediction

    Yolo中的位置预测方法是预测的左上角的格点坐标预测偏移量。网络预测输出要素图中每个单元格的5个边界框。网络为每个边界框预测$t_x,t_y,t_h,t_w和t_o$这5个坐标。如果单元格从图像的左上角偏移了$(c_x,c_y)$并且先验边界框的宽度和高度为$p_w,p_h$,则预测对应于:
    $$
    \begin{array}{l}
    x=\left(t_{x} * w_{a}\right)-x_{a} \
    y=\left(t_{y} * h_{a}\right)-y_{a}
    \end{array}
    $$
    在这里插入图片描述
    $$
    \begin{aligned}
    b_{x} &=\sigma\left(t_{x}\right)+c_{x} \
    b_{y} &=\sigma\left(t_{y}\right)+c_{y} \
    b_{w} &=p_{w} e^{t_{w}} \
    b_{h} &=p_{h} e^{t_{h}} \
    \operatorname{Pr}(\text { object }) * I O U(b, \text { object }) &=\sigma\left(t_{o}\right)
    \end{aligned}
    $$
    由于我们约束位置预测,参数化更容易学习,使得网络更稳定使用维度集群以及直接预测边界框中心位置使YOLO比具有anchor box的版本提高了近5%的mAP。

    Fine-Grained Features

    在2626的特征图,经过卷积层等,变为1313的特征图后,作者认为损失了很多细粒度的特征,导致小尺寸物体的识别效果不佳,所以在此加入了passthrough层
    传递层通过将相邻特征堆叠到不同的通道而不是堆叠到空间位置,将较高分辨率特征与低分辨率特征相连,类似于ResNet中的标识映射。这将26×26×512特征映射转换为13×13×2048特征映射,其可以与原始特征连接。
    在这里插入图片描述

    Multi-Scale Training

    我觉得这个也是yolo为什么性能提升的一个很重要的点,解决了yolo1当中对于多个小物体检测的问题。
    原始的YOLO使用448×448的输入分辨率。添加anchor box后,我们将分辨率更改为416×416。然而,由于我们的模型只使用卷积层和池化层,相比yolo1删除了全连接层,它可以在运行中调整大小(应该是使用了1x1卷积不过我没看代码)。我们希望YOLOv2能够在不同大小的图像上运行,因此我们将其训练到模型中。
    instead of fixing the input image size we change the network every few iterations. Every 10 batches our network randomly chooses a new image dimension size.
    不固定输入图像的大小,我们每隔几次迭代就更改网络。在每10个batch之后,我们的网络就会随机resize成{320, 352, …, 608}中的一种。不同的输入,最后产生的格点数不同,比如输入图片是320320,那么输出格点是1010,如果每个格点的先验框个数设置为5,那么总共输出500个预测结果;如果输入图片大小是608608,输出格点就是1919,共1805个预测结果。
    YOLO’s convolutional layers downsample the image by a factor of 32 so by using an input image of 416 we get an output feature map of 13 × 13.
    We do this because we want an odd number of locations in our feature map so there is a single center cell.
    关于416×416也是有说法的,主要是下采样是32的倍数,而且最后的输出是13x13是奇数然后会有一个中心点更加易于目标检测。
    这种训练方法迫使网络学习在各种输入维度上很好地预测。这意味着相同的网络可以预测不同分辨率的检测。
    关于yolov2最大的一个提升还有一个原因就是WordTree组合数据集的训练方法,不过这里我就不再赘述,可以看参考资料有详细介绍,这里主要讲网络和思路

    yolov3论文改进

    yolov3的论文改进就有很多方面了,而且yolov3-spp的网络效果很不错,和yolov4,yolov5的效果差别也不是特别大,这也是为什么yolov3网络被广泛应用的原因之一。

    backbone:Darknet-53

    相比yolov1的网络,在网络上有了很大的改进,借鉴了Resnet、Densenet、FPN的做法结合了当前网络中十分有效的
    在这里插入图片描述
    1.Yolov3中,全卷积,对于输入图片尺寸没有特别限制,可通过调节卷积步长控制输出特征图的尺寸。
    2.Yolov3借鉴了金字塔特征图思想,小尺寸特征图用于检测大尺寸物体,而大尺寸特征图检测小尺寸物体。特征图的输出维度为$N \times N \times (3 \times (4+1+80))$, NxN为输出特征图格点数,一共3个Anchor框,每个框有4维预测框数值 $t_x,t_y,t_w,t_h$ ,1维预测框置信度,80维物体类别数。所以第一层特征图的输出维度为8x8x255
    3.Yolov3总共输出3个特征图,第一个特征图下采样32倍,第二个特征图下采样16倍,第三个下采样8倍。每个特征图进行一次3X3、步长为2的卷积,然后保存该卷积layer,再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果。三个特征层进行5次卷积处理,处理完后一部分用于输出该特征层对应的预测结果,一部分用于进行反卷积UmSampling2d后与其它特征层进行结合。
    4.concat和resiual加操作,借鉴DenseNet将特征图按照通道维度直接进行拼接,借鉴Resnet添加残差边,缓解了在深度神经网络中增加深度带来的梯度消失问题。
    5.上采样层(upsample):作用是将小尺寸特征图通过插值等方法,生成大尺寸图像。例如使用最近邻插值算法,将88的图像变换为1616。上采样层不改变特征图的通道数。

    对于yolo3的模型来说,网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类。然后对输出进行解码,解码过程也就是yolov2上面的Direct location prediction方法一样,每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。

    CIoU loss

    在yolov3当中我们使用的不再是传统的IoU loss,而是一个非常优秀的loss设计,整个发展过程也可以多了解了解IoU->GIoU->DIoU->CIoU,完整的考虑了重合面积,中心点距离,长宽比
    $$CIoU=IoU-(\frac{\rho(b,b^{gt}}{c^2}+\alpha v))$$
    $$v=\frac{4}{\pi^2}(\arctan\frac{w^{gt}}{h^{gt}}-\arctan\frac{w}{h})^2$$
    $$\alpha = \frac{v}{(1-IoU)+v}$$
    其中, $b$ , $b^{gt}$ 分别代表了预测框和真实框的中心点,且 $\rho$代表的是计算两个中心点间的欧式距离。 [公式] 代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。
    在介绍CIoU之前,我们可以先介绍一下DIoU loss
    在这里插入图片描述
    其实这两项就是我们的DIoU
    $$DIoU = IoU-(\frac{\rho(b,b^{gt}}{c^2})$$
    则我们可以提出DIoU loss函数
    $$
    L_{\text {DIoU }}=1-D I o U \
    0 \leq L_{\text {DloU}} \leq 2
    $$
    DloU损失能够直接最小化两个boxes之间的距离,因此收敛速度更快。
    而我们的CIoU则比DIoU考虑更多的东西,也就是长宽比即最后一项。而$v$用来度量长宽比的相似性,$\alpha$是权重
    在损失函数这块,有知乎大佬写了一下,其实就是对yolov1上的损失函数进行了一下变形。
    在这里插入图片描述

    Spatial Pyramid Pooling

    在yolov3的spp版本中使用了spp模块,实现了不同尺度的特征融合,spp模块最初来源于何凯明大神的论文SPP-net,主要用来解决输入图像尺寸不统一的问题,而目前的图像预处理操作中,resize,crop等都会造成一定程度的图像失真,因此影响了最终的精度。SPP模块,使用固定分块的池化操作,可以对不同尺寸的输入实现相同大小的输出,因此能够避免这一问题。
    同时SPP模块中不同大小特征的融合,有利于待检测图像中目标大小差异较大的情况,也相当于增大了多重感受野吧,尤其是对于yolov3一般针对的复杂多目标图像。
    在yolov3的darknet53输出到第一个特征图计算之前我们插入了一个spp模块。
    在这里插入图片描述
    SPP的这几个模块stride=1,而且会对外补padding,所以最后的输出特征图大小是一致的,然后在深度方向上进行concatenate,使得深度增大了4倍。
    在这里插入图片描述
    在这里插入图片描述
    可以看出随着输入网络尺度的增大,spp的效果会更好。多个spp的效果增大也就是spp3和spp1的效果是差不多的,为了保证推理速度就只使用了一个spp

    Mosaic图像增强

    在yolov3-spp包括yolov4当中我们都使用了mosaic数据增强,有点类似cutmix。cutmix是合并两张图片进行预测,mosaic数据增强利用了四张图片,对四张图片进行拼接,每一张图片都有其对应的框框,将四张图片拼接之后就获得一张新的图片,同时也获得这张图片对应的框框,然后我们将这样一张新的图片传入到神经网络当中去学习,相当于一下子传入四张图片进行学习了。论文中说这极大丰富了检测物体的背景,且在标准化BN计算的时候一下子会计算四张图片的数据。
    在这里插入图片描述
    作用:增加数据的多样性,增加目标个数,BN能一次性归一化多张图片组合的分布,会更加接近原数据的分布。

    focal loss

    最后我们提一下focal loss,虽然在原论文当中作者说focal loss效果反而下降了,但是我认为还是有必要了解一下何凯明的这篇bestpaper。作者提出focal loss的出发点也是希望one-stage detector可以达到two-stage detector的准确率,同时不影响原有的速度。作者认为造成这种情况的原因是样本的类别不均衡导致的
    对于二分类的CrossEntropy,我们有如下表示方法:
    $$
    \mathrm{CE}(p, y)=\left{\begin{array}{ll}
    -\log (p) & \text { if } y=1 \
    -\log (1-p) & \text { otherwise. }
    \end{array}\right.
    $$
    我们定义$p_t$:
    $$
    p_{\mathrm{t}}=\left{\begin{array}{ll}
    p & \text { if } y=1 \
    1-p & \text { otherwise }
    \end{array}\right.
    $$
    则重写交叉熵
    $$
    \operatorname{CE}(n, y)=C E(p_t)=-\log (p_t)
    $$
    接着我们提出改进思路就是在计算CE时添加了一个平衡因子,是一个可训练的参数
    $$
    \mathrm{CE}\left(p_{\mathrm{t}}\right)=-\alpha_{\mathrm{t}} \log \left(p_{\mathrm{t}}\right)
    $$
    其中
    $$
    \alpha_{\mathrm{t}}=\left{\begin{array}{ll}
    \alpha_{\mathrm{t}} & \text { if } y=1 \
    1-\alpha_{\mathrm{t}} & \text { otherwise }
    \end{array}\right.
    $$
    相当于给正负样本加上权重,负样本出现的频次多,那么就降低负样本的权重,正样本数量少,就相对提高正样本的权重。因此可以通过设定a的值来控制正负样本对总的loss的共享权重。a取比较小的值来降低负样本(多的那类样本)的权重。
    但是何凯明认为这个还是不能完全解决样本不平衡的问题,虽然这个平衡因子可以控制正负样本的权重,但是没法控制容易分类和难分类样本的权重

    As our experiments will show, the large class imbalance encountered during training of dense detectors overwhelms the cross entropy loss. Easily classified negatives comprise the majority of the loss and dominate the gradient. While
    α balances the importance of positive/negative examples, it does not differentiate between easy/hard examples. Instead, we propose to reshape the loss function to down-weight easy examples and thus focus training on hard negatives

    于是提出了一种新的损失函数Focal Loss
    $$
    \mathrm{FL}\left(p_{\mathrm{t}}\right)=-\left(1-p_{\mathrm{t}}\right)^{\gamma} \log \left(p_{\mathrm{t}}\right)
    $$
    在这里插入图片描述

    focal loss的两个重要性质

    1、当一个样本被分错的时候,pt是很小的,那么调制因子(1-Pt)接近1,损失不被影响;当Pt→1,因子(1-Pt)接近0,那么分的比较好的(well-classified)样本的权值就被调低了。因此调制系数就趋于1,也就是说相比原来的loss是没有什么大的改变的。当pt趋于1的时候(此时分类正确而且是易分类样本),调制系数趋于0,也就是对于总的loss的贡献很小。

    2、当γ=0的时候,focal loss就是传统的交叉熵损失,当γ增加的时候,调制系数也会增加。 专注参数γ平滑地调节了易分样本调低权值的比例。γ增大能增强调制因子的影响,实验发现γ取2最好。直觉上来说,调制因子减少了易分样本的损失贡献,拓宽了样例接收到低损失的范围。当γ一定的时候,比如等于2,一样easy example(pt=0.9)的loss要比标准的交叉熵loss小100+倍,当pt=0.968时,要小1000+倍,但是对于hard example(pt < 0.5),loss最多小了4倍。这样的话hard example的权重相对就提升了很多。这样就增加了那些误分类的重要性

    focal loss的两个性质算是核心,其实就是用一个合适的函数去度量难分类和易分类样本对总的损失的贡献。

    然后作者又提出了最终的focal loss形式
    $$
    \mathrm{FL}\left(p_{\mathrm{t}}\right)=-\alpha_{\mathrm{t}}\left(1-p_{\mathrm{t}}\right)^{\gamma} \log \left(p_{\mathrm{t}}\right)
    $$
    $$
    F L(p)\left{\begin{array}{ll}
    -\alpha(1-p)^{\gamma} \log (p) & \text { if } y=1 \
    -(1-\alpha) p^{\gamma} \log (1-p) & \text { otherwise }
    \end{array}\right.
    $$
    这样既能调整正负样本的权重,又能控制难易分类样本的权重
    在实验中a的选择范围也很广,一般而言当γ增加的时候,a需要减小一点(实验中γ=2,a=0.25的效果最好)

    总结

    yolo和yolov2的严谨数学推理和网络相结合的做法令人十分惊艳,而yolov3相比之前的两个版本不管是在数据处理还是网络性质上都有很大的改变,也使用了很多流行的tirck来实现目标检测,基本上结合了很多cv领域的精髓,博客中有很多部分由于时间关系没有太细讲,其实每个部分都可以去原论文中找到很多可以学习的地方,之后有时候会补上yolov4和yolov5的讲解,后面的网络添加了注意力机制模块效果应该是更加work的。

    参考文献

    yolov3-spp
    focal loss
    batch normal
    IoU系列
    yolov1论文翻译
    yolo1-3三部曲
    Bubbliiiing的教程里
    还有很多但是没找到之后会补上的

    ]]>
    + yolov1-3论文解析

    最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。

    yolov1论文思想

    物体检测主流的算法框架大致分为one-stage与two-stage。two-stage算法代表有R-CNN系列,one-stage算法代表有Yolo系列。按笔者理解,two-stage算法将步骤一与步骤二分开执行,输入图像先经过候选框生成网络(例如faster rcnn中的RPN网络),再经过分类网络;one-stage算法将步骤一与步骤二同时执行,输入图像只经过一个网络,生成的结果中同时包含位置与类别信息。two-stage与one-stage相比,精度高,但是计算量更大,所以运算较慢。

    We present YOLO, a new approach to object detection. Prior work on object detection repurposes classifiers to perform detection. Instead, we frame object detection as a regression problem to spatially separated bounding boxes and associated class probabilities.

    yolo相比其他R-CNN等方法最大的一个不同就是他将这个识别和定位的过程转化成了一个空间定位并分类的回归问题,这也是他为什么能够利用网络进行端到端直接同时预测的重要原因。

    yolo的特点

    1.YOLO速度非常快。由于我们的检测是当做一个回归问题,不需要很复杂的流程。在测试的时候我们只需将一个新的图片输入网络来检测物体。
    First, YOLO is extremely fast. Since we frame detection as a regression problem we don’t need a complex pipeline
    2.Yolo会基于整张图片信息进行预测,与基于滑动窗口和候选区域的方法不同,在训练和测试期间YOLO可以看到整个图像,所以它隐式地记录了分类类别的上下文信息及它的全貌
    Second, YOLO reasons globally about the image when making predictions Unlike sliding window and region proposal-based techniques, YOLO sees the entire image during training and test time so it implicitly encodes contextual information about classes as well as their appearance
    3.第三,YOLO能学习到目标的泛化表征。作者尝试了用自然图片数据集进行训练,用艺术画作品进行预测,Yolo的检测效果更佳。
    Third, YOLO learns generalizable representations of objects. Since YOLO is highly generalizable it is less likely to break down when applied to new domains or unexpected inputs

    backbone-darknet

    在这里插入图片描述
    yolov1这块的话,由于是比较早期的网络所以在设计时没有使用batchnormal,激活函数上采用leaky relu,输入图像大小为448x448,经过许多卷积层与池化层,变为7x7x1024张量,最后经过两层全连接层,输出张量维度为7x7x30。除了最后一层的输出使用了线性激活函数,其他层全部使用Leaky Relu激活函数。

    输出维度

    yolov1最精髓的地方应该就是他的输出维度,论文当中有放出这样一张图片,看完以后我们能对yolo算法的特点有更加深刻的理解。在上图的backbone当中我们可以看到的是
    在这里插入图片描述yolo的输出相当于把原图片分割成了$S \times S$个区域,然后对预测目标标定他的boundingbox,boundingbox由中心点的坐标,长宽以及物体的置信分数组成$x,y,w,h,confidence$,图中的$B$表示的是boundingbox的个数。置信分数在yolo当中是这样定义的:$Pr(Object)IOU_{pred}^{truth}$对于$Pr(Object)=1 \text{ if detect object else 0}$,其实置信分数就是boudingbox与groundtruth之间的IOU,并对每个小网格对应的$C$个类别都预测出他的条件概率:$Pr(Class_i|Object)$。
    在推理时,我们可以得到当前网格对于每个类别的预测置信分数:$$Pr(Class_i)
    IOU{pred}^{truth} = Pr(Class_i|Object)Pr(Object)IOU{pred}^{truth}$$
    这样一种严谨的条件概率的方式得到我们的最终概率是十分可行和合适的。
    YOLO imposes strong spatial constraints on bounding box predictions since each grid cell only predicts two boxes and can only have one class. This spatial constraint limits the number of nearby objects that our model can predict. Our model struggles with small objects that appear in groups, such as flocks of birds.
    不过在原论文当中也有提到的一点是:YOLO给边界框预测强加空间约束,因为每个网格单元只预测两个框和只能有一个类别。这个空间约束限制了我们的模型可以预测的邻近目标的数量。我们的模型难以预测群组中出现的小物体(比如鸟群)。

    损失函数

    在损失函数方面,由于公式比较长所以就直接截图过来了,损失函数的设计放方面作者也考虑的比较周全,
    在这里插入图片描述
    预测框的中心点$(x,y)$。预测框的宽高$w,h$,其中$\mathbb{1}_{i j}^{\text {obj }}$为控制函数,在标签中包含物体的那些格点处,该值为 1 ,若格点不含有物体,该值为 0

    在计算损失函数的时候作者最开始使用的是最简单的平方损失函数来计算检测,因为它是计算简单且容易优化的,但是它并不完全符合我们最大化平均精度的目标,原因如下:
    It weights localization error equally with classification error which may not be ideal.
    1.它对定位错误的权重与分类错误的权重相等,这可能不是理想的选择。
    Also, in every image many grid cells do not contain any object. This pushes the “confidence” scores of those cells towards zero, often overpowering the gradient from cells that do contain objects. This can lead to model instability, causing training to diverge early on.
    而且,在每个图像中,许多网格单元不包含任何对象。这就把这些网格的置信度分数推到零,往往超过了包含对象的网格的梯度。2.这可能导致模型不稳定,导致训练早期发散难以训练。

    解决方案:增加边界框坐标预测的损失,并且减少了不包含对象的框的置信度预测的损失。我们使用两个参数来实现这一点。$\lambda{coord}=5,\lambda{noobj}=0.5$,其实这也是yolo面临的一个样本数目不均衡的典型例子。主要目的是让含有物体的格点,在损失函数中的权重更大,让模型更加注重含有物体的格点所造成的损失。
    我们的损失函数则只会对那些有真实物体所属的格点进行损失计算,若该格点不包含物体,那么预测数值不对损失函数造成影响。

    这里对$w,h$在损失函数中的处理分别取了根号,原因在于,如果不取根号,损失函数往往更倾向于调整尺寸比较大的预测框。例如,20个像素点的偏差,对于800x600的预测框几乎没有影响,此时的IOU数值还是很大,但是对于30x40的预测框影响就很大。取根号是为了尽可能的消除大尺寸框与小尺寸框之间的差异。

    预测框的置信度$C_i$。当该格点不含有物体时,该置信度的标签为0;若含有物体时,该置信度的标签为预测框与真实物体框的IOU数值(。
    物体类别概率$P_i$,对应的类别位置,该标签数值为1,其余位置为0,与分类网络相同。

    yolov2:Better, Faster, Stronger

    yolov2的论文里面其实更多的是对训练数据集的组合和扩充,然后再添加了很多计算机视觉方面新出的trick,然后达到了性能的提升。同时yolov2方面所使用的trick也是现在计算机视觉常用的方法,我认为这些都是我们需要好好掌握的,同时也是面试和kaggle上都经常使用的一些方法。

    Batch Normalization

    BN是由Google于2015年提出,论文是《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》,这是一个深度神经网络训练的技巧,主要是让数据的分布变得一致,从而使得训练深层网络模型更加容易和稳定。
    这里有一些相关的链接可以参考一下

    https://www.cnblogs.com/itmorn/p/11241236.html
    https://zhuanlan.zhihu.com/p/24810318
    https://zhuanlan.zhihu.com/p/93643523
    莫烦python
    李宏毅yyds

    算法具体过程:
    在这里插入图片描述

    这里要提一下这个$\gamma和\beta$是可训练参数,维度等于张量的通道数,主要是为了在反向传播更新网络时使得标准化后的数据分布与原始数据尽可能保持一直,从而很好的抽象和保留整个batch的数据分布。

    Batch Normalization的作用:
    将这些输入值或卷积网络的张量在batch维度上进行类似标准化的操作,将其放缩到合适的范围,加快模型训练时的收敛速度,使得模型训练过程更加稳定,避免梯度爆炸或者梯度消失,并且起到一定的正则化作用。

    High Resolution Classifier

    在Yolov1中,网络的backbone部分会在ImageNet数据集上进行预训练,训练时网络输入图像的分辨率为224x224。在v2中,将分类网络在输入图片分辨率为448x448的ImageNet数据集上训练10个epoch,再使用检测数据集(例如coco)进行微调。高分辨率预训练使mAP提高了大约4%。

    Convolutional With Anchor Boxes

    predicts these offsets at every location in a feature map
    Predicting offsets instead of coordinates simplifies the problem and makes it easier for the network to learn.
    第一篇解读v1时提到,每个格点预测两个矩形框,在计算loss时,只让与ground truth最接近的框产生loss数值,而另一个框不做修正。这样规定之后,作者发现两个框在物体的大小、长宽比、类别上逐渐有了分工。在v2中,神经网络不对预测矩形框的宽高的绝对值进行预测,而是预测与Anchor框的偏差(offset),每个格点指定n个Anchor框。在训练时,最接近ground truth的框产生loss,其余框不产生loss。在引入Anchor Box操作后,mAP由69.5下降至69.2,原因在于,每个格点预测的物体变多之后,召回率大幅上升,准确率有所下降,总体mAP略有下降。

    Dimension Clusters

    Instead of choosing priors by hand, we run k-means clustering on the training set bounding boxes to automatically find good priors.
    这里算是作者数据处理上的一个创新点,这里作者提到之前的工作当中先Anchor Box都是认为设定的比例和大小,而这里作者时采用无监督的方式将训练数据集中的矩形框全部拿出来,用kmeans聚类得到先验框的宽和高。使用(1-IOU)数值作为两个矩形框的的距离函数,这个处理也十分的聪明。

    在这里插入图片描述

    Direct location prediction

    Yolo中的位置预测方法是预测的左上角的格点坐标预测偏移量。网络预测输出要素图中每个单元格的5个边界框。网络为每个边界框预测$t_x,t_y,t_h,t_w和t_o$这5个坐标。如果单元格从图像的左上角偏移了$(c_x,c_y)$并且先验边界框的宽度和高度为$p_w,p_h$,则预测对应于:

    在这里插入图片描述

    由于我们约束位置预测,参数化更容易学习,使得网络更稳定使用维度集群以及直接预测边界框中心位置使YOLO比具有anchor box的版本提高了近5%的mAP。

    Fine-Grained Features

    在2626的特征图,经过卷积层等,变为1313的特征图后,作者认为损失了很多细粒度的特征,导致小尺寸物体的识别效果不佳,所以在此加入了passthrough层
    传递层通过将相邻特征堆叠到不同的通道而不是堆叠到空间位置,将较高分辨率特征与低分辨率特征相连,类似于ResNet中的标识映射。这将26×26×512特征映射转换为13×13×2048特征映射,其可以与原始特征连接。
    在这里插入图片描述

    Multi-Scale Training

    我觉得这个也是yolo为什么性能提升的一个很重要的点,解决了yolo1当中对于多个小物体检测的问题。
    原始的YOLO使用448×448的输入分辨率。添加anchor box后,我们将分辨率更改为416×416。然而,由于我们的模型只使用卷积层和池化层,相比yolo1删除了全连接层,它可以在运行中调整大小(应该是使用了1x1卷积不过我没看代码)。我们希望YOLOv2能够在不同大小的图像上运行,因此我们将其训练到模型中。
    instead of fixing the input image size we change the network every few iterations. Every 10 batches our network randomly chooses a new image dimension size.
    不固定输入图像的大小,我们每隔几次迭代就更改网络。在每10个batch之后,我们的网络就会随机resize成{320, 352, …, 608}中的一种。不同的输入,最后产生的格点数不同,比如输入图片是320320,那么输出格点是1010,如果每个格点的先验框个数设置为5,那么总共输出500个预测结果;如果输入图片大小是608608,输出格点就是1919,共1805个预测结果。
    YOLO’s convolutional layers downsample the image by a factor of 32 so by using an input image of 416 we get an output feature map of 13 × 13.
    We do this because we want an odd number of locations in our feature map so there is a single center cell.
    关于416×416也是有说法的,主要是下采样是32的倍数,而且最后的输出是13x13是奇数然后会有一个中心点更加易于目标检测。
    这种训练方法迫使网络学习在各种输入维度上很好地预测。这意味着相同的网络可以预测不同分辨率的检测。
    关于yolov2最大的一个提升还有一个原因就是WordTree组合数据集的训练方法,不过这里我就不再赘述,可以看参考资料有详细介绍,这里主要讲网络和思路

    yolov3论文改进

    yolov3的论文改进就有很多方面了,而且yolov3-spp的网络效果很不错,和yolov4,yolov5的效果差别也不是特别大,这也是为什么yolov3网络被广泛应用的原因之一。

    backbone:Darknet-53

    相比yolov1的网络,在网络上有了很大的改进,借鉴了Resnet、Densenet、FPN的做法结合了当前网络中十分有效的
    在这里插入图片描述
    1.Yolov3中,全卷积,对于输入图片尺寸没有特别限制,可通过调节卷积步长控制输出特征图的尺寸。
    2.Yolov3借鉴了金字塔特征图思想,小尺寸特征图用于检测大尺寸物体,而大尺寸特征图检测小尺寸物体。特征图的输出维度为$N \times N \times (3 \times (4+1+80))$, NxN为输出特征图格点数,一共3个Anchor框,每个框有4维预测框数值 $t_x,t_y,t_w,t_h$ ,1维预测框置信度,80维物体类别数。所以第一层特征图的输出维度为8x8x255
    3.Yolov3总共输出3个特征图,第一个特征图下采样32倍,第二个特征图下采样16倍,第三个下采样8倍。每个特征图进行一次3X3、步长为2的卷积,然后保存该卷积layer,再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果。三个特征层进行5次卷积处理,处理完后一部分用于输出该特征层对应的预测结果,一部分用于进行反卷积UmSampling2d后与其它特征层进行结合。
    4.concat和resiual加操作,借鉴DenseNet将特征图按照通道维度直接进行拼接,借鉴Resnet添加残差边,缓解了在深度神经网络中增加深度带来的梯度消失问题。
    5.上采样层(upsample):作用是将小尺寸特征图通过插值等方法,生成大尺寸图像。例如使用最近邻插值算法,将88的图像变换为1616。上采样层不改变特征图的通道数。

    对于yolo3的模型来说,网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类。然后对输出进行解码,解码过程也就是yolov2上面的Direct location prediction方法一样,每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。

    CIoU loss

    在yolov3当中我们使用的不再是传统的IoU loss,而是一个非常优秀的loss设计,整个发展过程也可以多了解了解IoU->GIoU->DIoU->CIoU,完整的考虑了重合面积,中心点距离,长宽比

    其中, $b$ , $b^{gt}$ 分别代表了预测框和真实框的中心点,且 $\rho$代表的是计算两个中心点间的欧式距离。 [公式] 代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。
    在介绍CIoU之前,我们可以先介绍一下DIoU loss
    在这里插入图片描述
    其实这两项就是我们的DIoU

    则我们可以提出DIoU loss函数

    DloU损失能够直接最小化两个boxes之间的距离,因此收敛速度更快。
    而我们的CIoU则比DIoU考虑更多的东西,也就是长宽比即最后一项。而$v$用来度量长宽比的相似性,$\alpha$是权重
    在损失函数这块,有知乎大佬写了一下,其实就是对yolov1上的损失函数进行了一下变形。
    在这里插入图片描述

    Spatial Pyramid Pooling

    在yolov3的spp版本中使用了spp模块,实现了不同尺度的特征融合,spp模块最初来源于何凯明大神的论文SPP-net,主要用来解决输入图像尺寸不统一的问题,而目前的图像预处理操作中,resize,crop等都会造成一定程度的图像失真,因此影响了最终的精度。SPP模块,使用固定分块的池化操作,可以对不同尺寸的输入实现相同大小的输出,因此能够避免这一问题。
    同时SPP模块中不同大小特征的融合,有利于待检测图像中目标大小差异较大的情况,也相当于增大了多重感受野吧,尤其是对于yolov3一般针对的复杂多目标图像。
    在yolov3的darknet53输出到第一个特征图计算之前我们插入了一个spp模块。
    在这里插入图片描述
    SPP的这几个模块stride=1,而且会对外补padding,所以最后的输出特征图大小是一致的,然后在深度方向上进行concatenate,使得深度增大了4倍。
    在这里插入图片描述
    在这里插入图片描述
    可以看出随着输入网络尺度的增大,spp的效果会更好。多个spp的效果增大也就是spp3和spp1的效果是差不多的,为了保证推理速度就只使用了一个spp

    Mosaic图像增强

    在yolov3-spp包括yolov4当中我们都使用了mosaic数据增强,有点类似cutmix。cutmix是合并两张图片进行预测,mosaic数据增强利用了四张图片,对四张图片进行拼接,每一张图片都有其对应的框框,将四张图片拼接之后就获得一张新的图片,同时也获得这张图片对应的框框,然后我们将这样一张新的图片传入到神经网络当中去学习,相当于一下子传入四张图片进行学习了。论文中说这极大丰富了检测物体的背景,且在标准化BN计算的时候一下子会计算四张图片的数据。
    在这里插入图片描述
    作用:增加数据的多样性,增加目标个数,BN能一次性归一化多张图片组合的分布,会更加接近原数据的分布。

    focal loss

    最后我们提一下focal loss,虽然在原论文当中作者说focal loss效果反而下降了,但是我认为还是有必要了解一下何凯明的这篇bestpaper。作者提出focal loss的出发点也是希望one-stage detector可以达到two-stage detector的准确率,同时不影响原有的速度。作者认为造成这种情况的原因是样本的类别不均衡导致的
    对于二分类的CrossEntropy,我们有如下表示方法:

    我们定义$p_t$:

    则重写交叉熵

    接着我们提出改进思路就是在计算CE时添加了一个平衡因子,是一个可训练的参数

    其中

    相当于给正负样本加上权重,负样本出现的频次多,那么就降低负样本的权重,正样本数量少,就相对提高正样本的权重。因此可以通过设定a的值来控制正负样本对总的loss的共享权重。a取比较小的值来降低负样本(多的那类样本)的权重。
    但是何凯明认为这个还是不能完全解决样本不平衡的问题,虽然这个平衡因子可以控制正负样本的权重,但是没法控制容易分类和难分类样本的权重

    As our experiments will show, the large class imbalance encountered during training of dense detectors overwhelms the cross entropy loss. Easily classified negatives comprise the majority of the loss and dominate the gradient. While
    α balances the importance of positive/negative examples, it does not differentiate between easy/hard examples. Instead, we propose to reshape the loss function to down-weight easy examples and thus focus training on hard negatives

    于是提出了一种新的损失函数Focal Loss

    在这里插入图片描述

    focal loss的两个重要性质

    1、当一个样本被分错的时候,pt是很小的,那么调制因子(1-Pt)接近1,损失不被影响;当Pt→1,因子(1-Pt)接近0,那么分的比较好的(well-classified)样本的权值就被调低了。因此调制系数就趋于1,也就是说相比原来的loss是没有什么大的改变的。当pt趋于1的时候(此时分类正确而且是易分类样本),调制系数趋于0,也就是对于总的loss的贡献很小。

    2、当γ=0的时候,focal loss就是传统的交叉熵损失,当γ增加的时候,调制系数也会增加。 专注参数γ平滑地调节了易分样本调低权值的比例。γ增大能增强调制因子的影响,实验发现γ取2最好。直觉上来说,调制因子减少了易分样本的损失贡献,拓宽了样例接收到低损失的范围。当γ一定的时候,比如等于2,一样easy example(pt=0.9)的loss要比标准的交叉熵loss小100+倍,当pt=0.968时,要小1000+倍,但是对于hard example(pt < 0.5),loss最多小了4倍。这样的话hard example的权重相对就提升了很多。这样就增加了那些误分类的重要性

    focal loss的两个性质算是核心,其实就是用一个合适的函数去度量难分类和易分类样本对总的损失的贡献。

    然后作者又提出了最终的focal loss形式

    这样既能调整正负样本的权重,又能控制难易分类样本的权重
    在实验中a的选择范围也很广,一般而言当γ增加的时候,a需要减小一点(实验中γ=2,a=0.25的效果最好)

    总结

    yolo和yolov2的严谨数学推理和网络相结合的做法令人十分惊艳,而yolov3相比之前的两个版本不管是在数据处理还是网络性质上都有很大的改变,也使用了很多流行的tirck来实现目标检测,基本上结合了很多cv领域的精髓,博客中有很多部分由于时间关系没有太细讲,其实每个部分都可以去原论文中找到很多可以学习的地方,之后有时候会补上yolov4和yolov5的讲解,后面的网络添加了注意力机制模块效果应该是更加work的。

    参考文献

    yolov3-spp
    focal loss
    batch normal
    IoU系列
    yolov1论文翻译
    yolo1-3三部曲
    Bubbliiiing的教程里
    还有很多但是没找到之后会补上的

    ]]>
    - <h2 id="yolov1-3论文解析"><a href="#yolov1-3论文解析" class="headerlink" title="yolov1-3论文解析"></a>yolov1-3论文解析</h2><p>最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。</p> + <h2 id="yolov1-3论文解析"><a href="#yolov1-3论文解析" class="headerlink" title="yolov1-3论文解析"></a>yolov1-3论文解析</h2><p>最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。<br></p> @@ -377,7 +377,7 @@ 2021-04-12T00:39:00.000Z 2024-10-09T07:33:09.887Z - MTCNN人脸检测和pytorch代码实现解读

    传送门

    论文地址:https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf
    我的论文笔记:https://khany.top/file/paper/mtcnn.pdf
    本文csdn链接:https://blog.csdn.net/Jack___E/article/details/115601474
    github参考:https://github.com/Sierkinhane/mtcnn-pytorch
    github参考:https://github.com/GitHberChen/MTCNN_Pytorch

    abstract

    abstract—Face detection and alignment in unconstrained environment are challenging due to various poses, illuminations and occlusions. Recent studies show that deep learning approaches can achieve impressive performance on these two tasks. In this paper, we propose a deep cascaded multi-task framework which exploits the inherent correlation between detection and alignment
    to boost up their performance. In particular, our framework leverages a cascaded architecture with three stages of carefully designed deep convolutional networks to predict face and landmark location in a coarse-to-fine manner. In addition, we propose a new online hard sample mining strategy that further improves the performance in practice. Our method achieves superior accuracy over the state-of-the-art techniques on the challenging FDDB and WIDER FACE benchmarks for face detection, and AFLW benchmark for face alignment, while keeps real time performance.

    在无约束条件的环境下进行人脸检测和校准人脸检测和校准是非常具挑战性的,因为你要考虑复杂的姿势,光照因素以及面部遮挡问题的影响,最近的研究表明,使用深度学习的方法能够在这两项任务上有不错的效果。本文作者探索和研究了人脸检测和校准之间的内在联系,提出了一个深层级的多任务框架有效提升了网络的性能。我们的深层级联合架构包含三个阶段的卷积神经网络从粗略到细致逐步实现人脸检测和面部特征点标记。此外,我们还提出了一种新的线上进行困难样本的预测的策略可以在实际使用过程中有效提升网络的性能。我们的方法目前在FDDB和WIDER FACE人脸检测任务和AFLW面部对准任务上超越了最佳模型方法的性能标准,同时该模型具有不错的实时检测效果。

    my English is not so well,if there are some mistakes in translations, please contact me in blog comments.

    简介

    人脸检测和脸部校准任务对于很多面部任务和应用来说都是十分重要的,比如说人脸识别,人脸表情分析。不过在现实生活当中,面部特征时常会因为一些遮挡物,强烈的光照对比,复杂的姿势发生很大的变化,这使得这些任务变得具有很大挑战性。Viola和Jones 提出的级联人脸检测器利用Haar特征和AdaBoost训练级联分类器,实现了具有实时效率的良好性能。 然而,也有相当一部分的工作表明,这种分类器可能在现实生活的应用中效果显着降低,即使具有更高级的特征和分类器。,之后人们又提出了DPM的方式,这些年来基于CNN的一些解决方案也层出不穷,基于CNN的方案主要是为了识别出面部的一些特征来实现。作者认为这种方式其实是需要花费更多的时间来对面部进行校准来训练同时还忽略了面部的重要特征点位置和边框问题。
    对于人脸校准的话目前也有大致的两个方向:一个是基于回归的方法还有一个就是基于模板拟合的方式进行,主要是对特征识别起到一个辅助的工作。
    作者认为关于人脸识别和面部对准这两个任务其实是有内在关联的,而目前大多数的方法则忽略了这一点,所以本文所提出的方案就是将这两者都结合起来,构建出一个三阶段的模型实现一个端到端的人脸检测并校准面部特征点的过程。
    第一阶段:通过浅层CNN快速生成候选窗口。
    第二阶段:通过more complex的CNN拒绝大量非面部窗口来细化窗口。
    第三阶段:使用more powerful的CNN再次细化结果并输出五个面部标志位置。

    方法

    Image pyramid图像金字塔

    在进入stage-1之前,作者先构建了一组多尺度的图像金字塔缩放,这一块要理解起来还是有一点费力的,这里我们需要了解的是在训练网络的过程当中,作者都是把WIDER FACE的图片随机剪切成12x12size的大小进行单尺度训练的,不能满足任意大小的人脸检测,所以为了检测到不同尺寸的人脸,所以在推理时需要先生成图像金字塔生成一组不同scale的图像输入P-net进行来实现人脸的检测,同时也这是为什么P-net要使用FCN的原因。
    在这里插入图片描述

    def calculateScales(img):
    min_face_size = 20 # 最小的人脸尺寸
    width, height = img.size
    min_length = min(height, width)
    min_detection_size = 12
    factor = 0.707 # sqrt(0.5)
    scales = []
    m = min_detection_size / min_face_size
    min_length *= m
    factor_count = 0
    # 图像尺寸缩小到不大于min_detection_size
    while min_length > min_detection_size:
    scales.append(m * factor ** factor_count)
    min_length *= factor
    factor_count += 1

    $$\text{length} =\text{length} \times (\frac{\text{min detection size}}{\text{min face size}} )\times factor^{count} $$
    $\text{here we define in our code:} factor=\frac{1}{\sqrt{2}}, \text{min face size}=20,\text{min detection size}=12$

    min_face_size和factor对推理过程会产生什么样的影响?
    min_face_size越大,factor越小,图像最短边就越快缩放到接近min_detect_size,从而生成图像金字塔的耗时就越短,同时各尺度的跨度也更大。因此,加大min_face_size、减小factor能加速图像金字塔的生成,但同时也更易造成漏检。

    Stage 1 P-Net(Proposal Network)

    这是一个全卷积网络,也就是说这个网络可以兼容任意大小的输入,他的整个网络其实十分简单,该层的作用主要是为了获得人脸检测的大量候选框,这一层我们最大的作用就是要尽可能的提升recall,然后在获得许多候选框后再使用非极大抑制的方法合并高度重叠的候选区域。
    在这里插入图片描述
    P-Net的示意图当中我们也可以看到的是作者输入12x12大小最终的output为一个1x1x2的face label classification概率和一个1x1x4的boudingbox(左上角和右下角的坐标)以及一个1x1x10的landmark(双眼,鼻子,左右嘴角的坐标)
    注意虽然这里作者举例是12x12的图片大小,但是他的意思并不是说这个P-Net的图片输入大小必须是12,我们可以知道这个网络是FCN,这就意味着不同输入的大小都是可以输入其中的,最后我们所得到的featuremap$[m \times n \times (2+4+10) ]$每一个小像素点映射到输入图像所在的12x12区域是否包含人脸的分类结果,候选框左上和右下基准点以及landmark,然后根据图像金字塔的我们再根据scale和resize的逆过程将其映射到原图像上

    P-net structure

    class P_Net(nn.Module):
    def __init__(self):
    super(P_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    # 12x12x3
    nn.Conv2d(3, 10, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # PReLU1
    # 10x10x10
    nn.MaxPool2d(kernel_size=2, stride=2), # pool1
    # 5x5x10
    nn.Conv2d(10, 16, kernel_size=3, stride=1), # conv2
    # 3x3x16
    nn.PReLU(), # PReLU2
    nn.Conv2d(16, 32, kernel_size=3, stride=1), # conv3
    # 1x1x32
    nn.PReLU() # PReLU3
    )
    # detection
    self.conv4_1 = nn.Conv2d(32, 1, kernel_size=1, stride=1)
    # bounding box regresion
    self.conv4_2 = nn.Conv2d(32, 4, kernel_size=1, stride=1)
    # landmark localization
    self.conv4_3 = nn.Conv2d(32, 10, kernel_size=1, stride=1)
    # weight initiation with xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    det = torch.sigmoid(self.conv4_1(x))
    box = self.conv4_2(x)
    landmark = self.conv4_3(x)
    # det:[,2,1,1], box:[,4,1,1], landmark:[,10,1,1]
    return det, box, landmark

    这里要提一下nn.PReLU(),有点类似Leaky ReLU,可以看下面的博客深入了解一下
    在这里插入图片描述

    https://blog.csdn.net/qq_23304241/article/details/80300149
    https://blog.csdn.net/shuzfan/article/details/51345832

    然后在P-Net之后我们通过非极大抑制的方法和将所有的boudingbox都修正为框的最长边为边长的正方形框主要是避免后面的Rnet在resize的时候因为尺寸原因出现信息的损失。
    在这里插入图片描述

    Non-Maximum Suppression非极大抑制

    其实关于非极大抑制这个trick最初是在目标检测任务当中提出的来的,其思想是搜素局部最大值,抑制极大值,主要用在目标检测当中,最传统的非极大抑制所采用的评价指标就是交并比IoU(intersection-over-union)即两个groud truth和bounding box的交集部分除以它们的并集.
    在这里插入图片描述
    $$IoU = \frac{area(C) \cap area(G)}{area(C) \cup area(G)}$$

    def IoU(box, boxes):
    """Compute IoU between detect box and gt boxes

    Parameters:
    ----------
    box: numpy array , shape (5, ): x1, y1, x2, y2, score
    input box
    boxes: numpy array, shape (n, 4): x1, y1, x2, y2
    input ground truth boxes

    Returns:
    -------
    ovr: numpy.array, shape (n, )
    IoU
    """
    box_area = (box[2] - box[0] + 1) * (box[3] - box[1] + 1)
    area = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)
    xx1 = np.maximum(box[0], boxes[:, 0])
    xx2 = np.minimum(box[2], boxes[:, 2])
    yy1 = np.maximum(box[1], boxes[:, 1])
    yy2 = np.minimum(box[3], boxes[:, 3])

    # compute the width and height of the bounding box
    w = np.maximum(0, xx2 - xx1 + 1)
    h = np.maximum(0, yy2 - yy1 + 1)

    inter = w * h
    ovr = np.true_divide(inter,(box_area + area - inter))
    #ovr = inter / (box_area + area - inter)
    return ovr

    使用非极大抑制的前提是,我们已经得到了一组候选框和对应label的置信分数,以及groud truth的信息,通过设定阈值来删除重合度较高的候选框。
    算法流程如下:

    def nms(dets,threshod,mode="Union"):
    """
    greedily select boxes with high confidence
    keep boxes overlap <= thresh
    rule out overlap > thresh
    :param dets: [[x1, y1, x2, y2 score]]
    :param threshod: retain overlap <= thresh
    :return: indexes to keep
    """
    x1 = dets[:,0]
    y1 = dets[;,1]
    x2 = dets[:,2]
    y2 = dets[:,3]

    scores = dets[:,4]

    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    order = areas.argsort()[::-1] # reverse

    keep=[]

    while order.size()>0:
    i = order[0]
    keep.append(i)
    # A & B left top position
    xx1 = np.maximun(x1[i],x1[order[1,:]])
    yy1 = np.maximun(y1[i],y1[order[1,:]])
    # A & B right down position
    xx2 = np.minimum(x2[i],x2[order[1,:]])
    yy2 = np.minimum(y2[i], y2[order[1:]])

    w = np.maximum(0.0, xx2 - xx1 + 1)
    h = np.maximum(0.0, yy2 - yy1 + 1)

    inter = w * h

    # cacaulate the IOU between box which have largest score with other boxes
    if mode == "Union":
    # area[i]: the area of largest score
    ovr = inter / (areas[i] + areas[order[1:]] - inter)
    elif mode == "Minimum":
    ovr = inter / np.minimum(areas[i], areas[order[1:]])
    # delete the IoU that higher than threshod
    inds = np.where(ovr <= threshod)[0]
    order = order[inds + 1] # +1: eliminates the first element in order

    return keep

    边框修正

    以最大边作为边长将矩形修正为正方形,同时包含的信息也更多,以免在后面resize输入下一个网络时减少信息的损失。

    def convert_to_square(bboxes):
    """
    Convert bounding boxes to a square form.
    """
    # 将矩形对称扩大为正方形
    square_bboxes = np.zeros_like(bboxes)
    x1, y1, x2, y2 = [bboxes[:, i] for i in range(4)]
    h = y2 - y1 + 1.0
    w = x2 - x1 + 1.0
    max_side = np.maximum(h, w)
    square_bboxes[:, 0] = x1 + w * 0.5 - max_side * 0.5
    square_bboxes[:, 1] = y1 + h * 0.5 - max_side * 0.5
    square_bboxes[:, 2] = square_bboxes[:, 0] + max_side - 1.0
    square_bboxes[:, 3] = square_bboxes[:, 1] + max_side - 1.0
    return square_bboxes

    Stage 2 R-Net(Refine Network)

    R-net的输入是固定的,必须是24x24,所以对于P-net产生的大量boundingbox我们需要先进行resize然后再输入R-Net,在论文中我们了解到该网络层的作用主要是对大量的boundingbox进行有效过滤,获得更加精细的候选框。
    在这里插入图片描述

    R-net structure

    class R_Net(nn.Module):
    def __init__(self):
    super(R_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    # 24x24x3
    nn.Conv2d(3, 28, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # prelu1
    # 22x22x28
    nn.MaxPool2d(kernel_size=3, stride=2), # pool1
    # 10x10x28
    nn.Conv2d(28, 48, kernel_size=3, stride=1), # conv2
    nn.PReLU(), # prelu2
    # 8x8x48
    nn.MaxPool2d(kernel_size=3, stride=2), # pool2
    # 3x3x48
    nn.Conv2d(48, 64, kernel_size=2, stride=1), # conv3
    # 2x2x64
    nn.PReLU() # prelu3
    )
    # 2x2x64
    self.conv4 = nn.Linear(64 * 2 * 2, 128) # conv4
    # 128
    self.prelu4 = nn.PReLU() # prelu4
    # detection
    self.conv5_1 = nn.Linear(128, 1)
    # bounding box regression
    self.conv5_2 = nn.Linear(128, 4)
    # lanbmark localization
    self.conv5_3 = nn.Linear(128, 10)
    # weight initiation weih xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    x = x.view(x.size(0), -1)
    x = self.conv4(x)
    x = self.prelu4(x)
    det = torch.sigmoid(self.conv5_1(x))
    box = self.conv5_2(x)
    landmark = self.conv5_3(x)
    return det, box, landmark

    然后在P-Net之后我们通过非极大抑制的方法和将所有的boudingbox都修正为框的最长边为边长的正方形框也是避免后面的Onet在resize的时候出现因为尺寸原因出现信息的损失。
    在这里插入图片描述

    Stage 3 O-Net(Output?[作者未指出命名] Network)

    在这里插入图片描述
    Onet与Rnet工作流程类似。只不过输入的尺寸变成了48x48,对于R-net当中的框再次进行处理,得到的网络结构的输出则是最终的label classfication,boundingbox,landmark。
    O-net structure

    class O_Net(nn.Module):
    def __init__(self):
    super(O_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    nn.Conv2d(3, 32, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # prelu1
    nn.MaxPool2d(kernel_size=3, stride=2), # pool1
    nn.Conv2d(32, 64, kernel_size=3, stride=1), # conv2
    nn.PReLU(), # prelu2
    nn.MaxPool2d(kernel_size=3, stride=2), # pool2
    nn.Conv2d(64, 64, kernel_size=3, stride=1), # conv3
    nn.PReLU(), # prelu3
    nn.MaxPool2d(kernel_size=2, stride=2), # pool3
    nn.Conv2d(64, 128, kernel_size=2, stride=1), # conv4
    nn.PReLU() # prelu4
    )
    self.conv5 = nn.Linear(128 * 2 * 2, 256) # conv5
    self.prelu5 = nn.PReLU() # prelu5
    # detection
    self.conv6_1 = nn.Linear(256, 1)
    # bounding box regression
    self.conv6_2 = nn.Linear(256, 4)
    # lanbmark localization
    self.conv6_3 = nn.Linear(256, 10)
    # weight initiation weih xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    x = x.view(x.size(0), -1)
    x = self.conv5(x)
    x = self.prelu5(x)
    # detection
    det = torch.sigmoid(self.conv6_1(x))
    box = self.conv6_2(x)
    landmark = self.conv6_3(x)
    return det, box, landmark

    在这里插入图片描述
    注意,其实在之前的两个网络当中我们也预测了landmark的位置,只是在论文的图示当中没有展示而已,从作者描述的网络结构图的输出当中是详细指出了landmarkposition的卷积块的

    损失函数

    在研究完网络结构以后我们可以来看看这个mtcnn模型的一个损失函数,作者将损失函数分为了3个部分:Face classification、Bounding box regression、Facial landmark localization。

    Face classification

    对于第一部分的损失就是用的是最常用的交叉熵损失函数,就本质上来说人脸识别还是一个二分类问题,这里就使用Cross Entropy是最简单也是最合适的选择。
    $$L_i^{det} = -(y_i^{det} \log(p_i) + (1-y_i^det)(1-\log(p_i)))$$
    $p_i$是预测为face的可能性,$y_i^{det}$指的是真实标签也就是groud truth label

    Bounding box regression

    对于目标边界框的损失来说,对于每一个候选框我们都需要对与他最接近的真实目标边界框进行比较,the bounding box$(left, top, height, and width)$
    $$L_i^{box} = ||y_j^{box}-y_i^{box} ||_2^2$$

    Facial landmark localization

    而对于boundingbox和landmark来说整个框的调整过程其实可以看作是一个连续的变化过程,固使用的是欧氏距离回归损失计算方法。比较的是各个点的坐标与真实面部关键点坐标的差异。
    $$L_i^{landmark} = ||y_j^{landmark}-y_i^{landmark} ||_2^2$$

    total loss

    最终我们得到的损失函数是有上述这三部分加权而成
    $$\min{\sum_{i=1}^{N}{\sum_{j \in {det,box,landmark}} \alpha_j \beta_i^j L_i^j}}$$
    其中$\alpha_j$表示权重,$\beta_i^j$表示第i个样本的类型,也可以说是第i个样本在任务j中是否需要贡献loss,如果不存在人脸即label为0时则无需计算loss。
    对于不同的网络我们所设置的权重是不一样的
    in P-net & R-net(在这两层我们更注重classification的识别)
    $$alpha_{det}=1, alpha_{box}=0.5,alpha_{landmark}=0.5$$
    in O-net(在这层我们提高了对于注意关键点的预测精度)
    $$alpha_{det}=1, alpha_{box}=0.5,alpha_{landmark}=1 $$
    文中也有提到,采用的是随机梯度下降优化器进行的训练。

    OHEM(Online Hard Example Mining)

    作者对于困难样本的在线预测所做的trick也比较简单,就是挑选损失最大的前70%作为困难样本,在反向传播时仅使用这70%困难样本产生的损失,这样就剔除了很容易预测的easy sample对训练结果的影响,不过在我参考的这两个版本的代码里面似乎没有这么做。

    实验

    实验这块的话也比较充分,验证了每个修改部分对于实验结果的有效性验证,主要是讲述了实验过程对于训练数据集的处理和划分,验证了在线硬样本挖掘的有效性,联合检测和校准的有效性,分别评估了face detection和lanmark的贡献,面部检测评估,面部校准评估以及与SOTA的对比,这一块的话就没有细看了,分享的网站也有详细解释,论文原文也写了。
    在这里插入图片描述
    在这里插入图片描述

    总结

    这篇论文是中科院深圳先进技术研究院的乔宇老师团队所作,2016ECCV,看完这篇文章的话给我的感觉的话就是idea和实现过程包括实验都是很充分的,创新点这块的话主要是挖掘了人脸特征关键点和目标检测的一些联系,结合了人脸检测和对准两个任务来进行人脸检测,相比其他经典的目标检测网络如yolov3,R-CNN系列,在网络和损失函数上的创新确实略有不足,但是也让我收到了一些启发,看似几个简单的model和常见的loss联合,在对数据进行有效处理的基础上也是可以实现达到十分不错的效果的,不过这个方案的话在训练的时候其实是很花费时间的,毕竟需要对于不同scale的图片都进行输入训练,然后就是这种输入输出的结构其实还是存在一些局限性的,对于图像检测框和关键点的映射我个人觉得也比较繁杂或者说浪费了一些时间,毕竟是一篇2016年的论文之后应该会有更好的实现方式。

    参考资料

    参考链接: https://zhuanlan.zhihu.com/p/337690783
    参考链接:https://zhuanlan.zhihu.com/p/60199964?utm_source=qq&utm_medium=social&utm_oi=1221207556791963648
    参考链接: https://blog.csdn.net/weixin_44791964/article/details/103530206
    参考链接:https://blog.csdn.net/qq_34714751/article/details/85536669
    参考链接:https://www.bilibili.com/video/BV1fJ411C7AJ?from=search&seid=2691296178499711503

    ]]>
    + MTCNN人脸检测和pytorch代码实现解读

    传送门

    论文地址:https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf
    我的论文笔记:https://khany.top/file/paper/mtcnn.pdf
    本文csdn链接:https://blog.csdn.net/Jack___E/article/details/115601474
    github参考:https://github.com/Sierkinhane/mtcnn-pytorch
    github参考:https://github.com/GitHberChen/MTCNN_Pytorch

    abstract

    abstract—Face detection and alignment in unconstrained environment are challenging due to various poses, illuminations and occlusions. Recent studies show that deep learning approaches can achieve impressive performance on these two tasks. In this paper, we propose a deep cascaded multi-task framework which exploits the inherent correlation between detection and alignment
    to boost up their performance. In particular, our framework leverages a cascaded architecture with three stages of carefully designed deep convolutional networks to predict face and landmark location in a coarse-to-fine manner. In addition, we propose a new online hard sample mining strategy that further improves the performance in practice. Our method achieves superior accuracy over the state-of-the-art techniques on the challenging FDDB and WIDER FACE benchmarks for face detection, and AFLW benchmark for face alignment, while keeps real time performance.

    在无约束条件的环境下进行人脸检测和校准人脸检测和校准是非常具挑战性的,因为你要考虑复杂的姿势,光照因素以及面部遮挡问题的影响,最近的研究表明,使用深度学习的方法能够在这两项任务上有不错的效果。本文作者探索和研究了人脸检测和校准之间的内在联系,提出了一个深层级的多任务框架有效提升了网络的性能。我们的深层级联合架构包含三个阶段的卷积神经网络从粗略到细致逐步实现人脸检测和面部特征点标记。此外,我们还提出了一种新的线上进行困难样本的预测的策略可以在实际使用过程中有效提升网络的性能。我们的方法目前在FDDB和WIDER FACE人脸检测任务和AFLW面部对准任务上超越了最佳模型方法的性能标准,同时该模型具有不错的实时检测效果。

    my English is not so well,if there are some mistakes in translations, please contact me in blog comments.

    简介

    人脸检测和脸部校准任务对于很多面部任务和应用来说都是十分重要的,比如说人脸识别,人脸表情分析。不过在现实生活当中,面部特征时常会因为一些遮挡物,强烈的光照对比,复杂的姿势发生很大的变化,这使得这些任务变得具有很大挑战性。Viola和Jones 提出的级联人脸检测器利用Haar特征和AdaBoost训练级联分类器,实现了具有实时效率的良好性能。 然而,也有相当一部分的工作表明,这种分类器可能在现实生活的应用中效果显着降低,即使具有更高级的特征和分类器。,之后人们又提出了DPM的方式,这些年来基于CNN的一些解决方案也层出不穷,基于CNN的方案主要是为了识别出面部的一些特征来实现。作者认为这种方式其实是需要花费更多的时间来对面部进行校准来训练同时还忽略了面部的重要特征点位置和边框问题。
    对于人脸校准的话目前也有大致的两个方向:一个是基于回归的方法还有一个就是基于模板拟合的方式进行,主要是对特征识别起到一个辅助的工作。
    作者认为关于人脸识别和面部对准这两个任务其实是有内在关联的,而目前大多数的方法则忽略了这一点,所以本文所提出的方案就是将这两者都结合起来,构建出一个三阶段的模型实现一个端到端的人脸检测并校准面部特征点的过程。
    第一阶段:通过浅层CNN快速生成候选窗口。
    第二阶段:通过more complex的CNN拒绝大量非面部窗口来细化窗口。
    第三阶段:使用more powerful的CNN再次细化结果并输出五个面部标志位置。

    方法

    Image pyramid图像金字塔

    在进入stage-1之前,作者先构建了一组多尺度的图像金字塔缩放,这一块要理解起来还是有一点费力的,这里我们需要了解的是在训练网络的过程当中,作者都是把WIDER FACE的图片随机剪切成12x12size的大小进行单尺度训练的,不能满足任意大小的人脸检测,所以为了检测到不同尺寸的人脸,所以在推理时需要先生成图像金字塔生成一组不同scale的图像输入P-net进行来实现人脸的检测,同时也这是为什么P-net要使用FCN的原因。
    在这里插入图片描述

    def calculateScales(img):
    min_face_size = 20 # 最小的人脸尺寸
    width, height = img.size
    min_length = min(height, width)
    min_detection_size = 12
    factor = 0.707 # sqrt(0.5)
    scales = []
    m = min_detection_size / min_face_size
    min_length *= m
    factor_count = 0
    # 图像尺寸缩小到不大于min_detection_size
    while min_length > min_detection_size:
    scales.append(m * factor ** factor_count)
    min_length *= factor
    factor_count += 1

    $\text{here we define in our code:} factor=\frac{1}{\sqrt{2}}, \text{min face size}=20,\text{min detection size}=12$

    min_face_size和factor对推理过程会产生什么样的影响?
    min_face_size越大,factor越小,图像最短边就越快缩放到接近min_detect_size,从而生成图像金字塔的耗时就越短,同时各尺度的跨度也更大。因此,加大min_face_size、减小factor能加速图像金字塔的生成,但同时也更易造成漏检。

    Stage 1 P-Net(Proposal Network)

    这是一个全卷积网络,也就是说这个网络可以兼容任意大小的输入,他的整个网络其实十分简单,该层的作用主要是为了获得人脸检测的大量候选框,这一层我们最大的作用就是要尽可能的提升recall,然后在获得许多候选框后再使用非极大抑制的方法合并高度重叠的候选区域。
    在这里插入图片描述
    P-Net的示意图当中我们也可以看到的是作者输入12x12大小最终的output为一个1x1x2的face label classification概率和一个1x1x4的boudingbox(左上角和右下角的坐标)以及一个1x1x10的landmark(双眼,鼻子,左右嘴角的坐标)
    注意虽然这里作者举例是12x12的图片大小,但是他的意思并不是说这个P-Net的图片输入大小必须是12,我们可以知道这个网络是FCN,这就意味着不同输入的大小都是可以输入其中的,最后我们所得到的featuremap$[m \times n \times (2+4+10) ]$每一个小像素点映射到输入图像所在的12x12区域是否包含人脸的分类结果,候选框左上和右下基准点以及landmark,然后根据图像金字塔的我们再根据scale和resize的逆过程将其映射到原图像上

    P-net structure

    class P_Net(nn.Module):
    def __init__(self):
    super(P_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    # 12x12x3
    nn.Conv2d(3, 10, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # PReLU1
    # 10x10x10
    nn.MaxPool2d(kernel_size=2, stride=2), # pool1
    # 5x5x10
    nn.Conv2d(10, 16, kernel_size=3, stride=1), # conv2
    # 3x3x16
    nn.PReLU(), # PReLU2
    nn.Conv2d(16, 32, kernel_size=3, stride=1), # conv3
    # 1x1x32
    nn.PReLU() # PReLU3
    )
    # detection
    self.conv4_1 = nn.Conv2d(32, 1, kernel_size=1, stride=1)
    # bounding box regresion
    self.conv4_2 = nn.Conv2d(32, 4, kernel_size=1, stride=1)
    # landmark localization
    self.conv4_3 = nn.Conv2d(32, 10, kernel_size=1, stride=1)
    # weight initiation with xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    det = torch.sigmoid(self.conv4_1(x))
    box = self.conv4_2(x)
    landmark = self.conv4_3(x)
    # det:[,2,1,1], box:[,4,1,1], landmark:[,10,1,1]
    return det, box, landmark

    这里要提一下nn.PReLU(),有点类似Leaky ReLU,可以看下面的博客深入了解一下
    在这里插入图片描述

    https://blog.csdn.net/qq_23304241/article/details/80300149
    https://blog.csdn.net/shuzfan/article/details/51345832

    然后在P-Net之后我们通过非极大抑制的方法和将所有的boudingbox都修正为框的最长边为边长的正方形框主要是避免后面的Rnet在resize的时候因为尺寸原因出现信息的损失。
    在这里插入图片描述

    Non-Maximum Suppression非极大抑制

    其实关于非极大抑制这个trick最初是在目标检测任务当中提出的来的,其思想是搜素局部最大值,抑制极大值,主要用在目标检测当中,最传统的非极大抑制所采用的评价指标就是交并比IoU(intersection-over-union)即两个groud truth和bounding box的交集部分除以它们的并集.
    在这里插入图片描述

    def IoU(box, boxes):
    """Compute IoU between detect box and gt boxes

    Parameters:
    ----------
    box: numpy array , shape (5, ): x1, y1, x2, y2, score
    input box
    boxes: numpy array, shape (n, 4): x1, y1, x2, y2
    input ground truth boxes

    Returns:
    -------
    ovr: numpy.array, shape (n, )
    IoU
    """
    box_area = (box[2] - box[0] + 1) * (box[3] - box[1] + 1)
    area = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)
    xx1 = np.maximum(box[0], boxes[:, 0])
    xx2 = np.minimum(box[2], boxes[:, 2])
    yy1 = np.maximum(box[1], boxes[:, 1])
    yy2 = np.minimum(box[3], boxes[:, 3])

    # compute the width and height of the bounding box
    w = np.maximum(0, xx2 - xx1 + 1)
    h = np.maximum(0, yy2 - yy1 + 1)

    inter = w * h
    ovr = np.true_divide(inter,(box_area + area - inter))
    #ovr = inter / (box_area + area - inter)
    return ovr

    使用非极大抑制的前提是,我们已经得到了一组候选框和对应label的置信分数,以及groud truth的信息,通过设定阈值来删除重合度较高的候选框。
    算法流程如下:

    def nms(dets,threshod,mode="Union"):
    """
    greedily select boxes with high confidence
    keep boxes overlap <= thresh
    rule out overlap > thresh
    :param dets: [[x1, y1, x2, y2 score]]
    :param threshod: retain overlap <= thresh
    :return: indexes to keep
    """
    x1 = dets[:,0]
    y1 = dets[;,1]
    x2 = dets[:,2]
    y2 = dets[:,3]

    scores = dets[:,4]

    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    order = areas.argsort()[::-1] # reverse

    keep=[]

    while order.size()>0:
    i = order[0]
    keep.append(i)
    # A & B left top position
    xx1 = np.maximun(x1[i],x1[order[1,:]])
    yy1 = np.maximun(y1[i],y1[order[1,:]])
    # A & B right down position
    xx2 = np.minimum(x2[i],x2[order[1,:]])
    yy2 = np.minimum(y2[i], y2[order[1:]])

    w = np.maximum(0.0, xx2 - xx1 + 1)
    h = np.maximum(0.0, yy2 - yy1 + 1)

    inter = w * h

    # cacaulate the IOU between box which have largest score with other boxes
    if mode == "Union":
    # area[i]: the area of largest score
    ovr = inter / (areas[i] + areas[order[1:]] - inter)
    elif mode == "Minimum":
    ovr = inter / np.minimum(areas[i], areas[order[1:]])
    # delete the IoU that higher than threshod
    inds = np.where(ovr <= threshod)[0]
    order = order[inds + 1] # +1: eliminates the first element in order

    return keep

    边框修正

    以最大边作为边长将矩形修正为正方形,同时包含的信息也更多,以免在后面resize输入下一个网络时减少信息的损失。

    def convert_to_square(bboxes):
    """
    Convert bounding boxes to a square form.
    """
    # 将矩形对称扩大为正方形
    square_bboxes = np.zeros_like(bboxes)
    x1, y1, x2, y2 = [bboxes[:, i] for i in range(4)]
    h = y2 - y1 + 1.0
    w = x2 - x1 + 1.0
    max_side = np.maximum(h, w)
    square_bboxes[:, 0] = x1 + w * 0.5 - max_side * 0.5
    square_bboxes[:, 1] = y1 + h * 0.5 - max_side * 0.5
    square_bboxes[:, 2] = square_bboxes[:, 0] + max_side - 1.0
    square_bboxes[:, 3] = square_bboxes[:, 1] + max_side - 1.0
    return square_bboxes

    Stage 2 R-Net(Refine Network)

    R-net的输入是固定的,必须是24x24,所以对于P-net产生的大量boundingbox我们需要先进行resize然后再输入R-Net,在论文中我们了解到该网络层的作用主要是对大量的boundingbox进行有效过滤,获得更加精细的候选框。
    在这里插入图片描述

    R-net structure

    class R_Net(nn.Module):
    def __init__(self):
    super(R_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    # 24x24x3
    nn.Conv2d(3, 28, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # prelu1
    # 22x22x28
    nn.MaxPool2d(kernel_size=3, stride=2), # pool1
    # 10x10x28
    nn.Conv2d(28, 48, kernel_size=3, stride=1), # conv2
    nn.PReLU(), # prelu2
    # 8x8x48
    nn.MaxPool2d(kernel_size=3, stride=2), # pool2
    # 3x3x48
    nn.Conv2d(48, 64, kernel_size=2, stride=1), # conv3
    # 2x2x64
    nn.PReLU() # prelu3
    )
    # 2x2x64
    self.conv4 = nn.Linear(64 * 2 * 2, 128) # conv4
    # 128
    self.prelu4 = nn.PReLU() # prelu4
    # detection
    self.conv5_1 = nn.Linear(128, 1)
    # bounding box regression
    self.conv5_2 = nn.Linear(128, 4)
    # lanbmark localization
    self.conv5_3 = nn.Linear(128, 10)
    # weight initiation weih xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    x = x.view(x.size(0), -1)
    x = self.conv4(x)
    x = self.prelu4(x)
    det = torch.sigmoid(self.conv5_1(x))
    box = self.conv5_2(x)
    landmark = self.conv5_3(x)
    return det, box, landmark

    然后在P-Net之后我们通过非极大抑制的方法和将所有的boudingbox都修正为框的最长边为边长的正方形框也是避免后面的Onet在resize的时候出现因为尺寸原因出现信息的损失。
    在这里插入图片描述

    Stage 3 O-Net(Output?[作者未指出命名] Network)

    在这里插入图片描述
    Onet与Rnet工作流程类似。只不过输入的尺寸变成了48x48,对于R-net当中的框再次进行处理,得到的网络结构的输出则是最终的label classfication,boundingbox,landmark。
    O-net structure

    class O_Net(nn.Module):
    def __init__(self):
    super(O_Net, self).__init__()
    self.pre_layer = nn.Sequential(
    nn.Conv2d(3, 32, kernel_size=3, stride=1), # conv1
    nn.PReLU(), # prelu1
    nn.MaxPool2d(kernel_size=3, stride=2), # pool1
    nn.Conv2d(32, 64, kernel_size=3, stride=1), # conv2
    nn.PReLU(), # prelu2
    nn.MaxPool2d(kernel_size=3, stride=2), # pool2
    nn.Conv2d(64, 64, kernel_size=3, stride=1), # conv3
    nn.PReLU(), # prelu3
    nn.MaxPool2d(kernel_size=2, stride=2), # pool3
    nn.Conv2d(64, 128, kernel_size=2, stride=1), # conv4
    nn.PReLU() # prelu4
    )
    self.conv5 = nn.Linear(128 * 2 * 2, 256) # conv5
    self.prelu5 = nn.PReLU() # prelu5
    # detection
    self.conv6_1 = nn.Linear(256, 1)
    # bounding box regression
    self.conv6_2 = nn.Linear(256, 4)
    # lanbmark localization
    self.conv6_3 = nn.Linear(256, 10)
    # weight initiation weih xavier
    self.apply(weights_init)

    def forward(self, x):
    x = self.pre_layer(x)
    x = x.view(x.size(0), -1)
    x = self.conv5(x)
    x = self.prelu5(x)
    # detection
    det = torch.sigmoid(self.conv6_1(x))
    box = self.conv6_2(x)
    landmark = self.conv6_3(x)
    return det, box, landmark

    在这里插入图片描述
    注意,其实在之前的两个网络当中我们也预测了landmark的位置,只是在论文的图示当中没有展示而已,从作者描述的网络结构图的输出当中是详细指出了landmarkposition的卷积块的

    损失函数

    在研究完网络结构以后我们可以来看看这个mtcnn模型的一个损失函数,作者将损失函数分为了3个部分:Face classification、Bounding box regression、Facial landmark localization。

    Face classification

    对于第一部分的损失就是用的是最常用的交叉熵损失函数,就本质上来说人脸识别还是一个二分类问题,这里就使用Cross Entropy是最简单也是最合适的选择。

    $p_i$是预测为face的可能性,$y_i^{det}$指的是真实标签也就是groud truth label

    Bounding box regression

    对于目标边界框的损失来说,对于每一个候选框我们都需要对与他最接近的真实目标边界框进行比较,the bounding box$(left, top, height, and width)$

    Facial landmark localization

    而对于boundingbox和landmark来说整个框的调整过程其实可以看作是一个连续的变化过程,固使用的是欧氏距离回归损失计算方法。比较的是各个点的坐标与真实面部关键点坐标的差异。

    total loss

    最终我们得到的损失函数是有上述这三部分加权而成

    其中$\alpha_j$表示权重,$\beta_i^j$表示第i个样本的类型,也可以说是第i个样本在任务j中是否需要贡献loss,如果不存在人脸即label为0时则无需计算loss。
    对于不同的网络我们所设置的权重是不一样的
    in P-net & R-net(在这两层我们更注重classification的识别)

    in O-net(在这层我们提高了对于注意关键点的预测精度)

    文中也有提到,采用的是随机梯度下降优化器进行的训练。

    OHEM(Online Hard Example Mining)

    作者对于困难样本的在线预测所做的trick也比较简单,就是挑选损失最大的前70%作为困难样本,在反向传播时仅使用这70%困难样本产生的损失,这样就剔除了很容易预测的easy sample对训练结果的影响,不过在我参考的这两个版本的代码里面似乎没有这么做。

    实验

    实验这块的话也比较充分,验证了每个修改部分对于实验结果的有效性验证,主要是讲述了实验过程对于训练数据集的处理和划分,验证了在线硬样本挖掘的有效性,联合检测和校准的有效性,分别评估了face detection和lanmark的贡献,面部检测评估,面部校准评估以及与SOTA的对比,这一块的话就没有细看了,分享的网站也有详细解释,论文原文也写了。
    在这里插入图片描述
    在这里插入图片描述

    总结

    这篇论文是中科院深圳先进技术研究院的乔宇老师团队所作,2016ECCV,看完这篇文章的话给我的感觉的话就是idea和实现过程包括实验都是很充分的,创新点这块的话主要是挖掘了人脸特征关键点和目标检测的一些联系,结合了人脸检测和对准两个任务来进行人脸检测,相比其他经典的目标检测网络如yolov3,R-CNN系列,在网络和损失函数上的创新确实略有不足,但是也让我收到了一些启发,看似几个简单的model和常见的loss联合,在对数据进行有效处理的基础上也是可以实现达到十分不错的效果的,不过这个方案的话在训练的时候其实是很花费时间的,毕竟需要对于不同scale的图片都进行输入训练,然后就是这种输入输出的结构其实还是存在一些局限性的,对于图像检测框和关键点的映射我个人觉得也比较繁杂或者说浪费了一些时间,毕竟是一篇2016年的论文之后应该会有更好的实现方式。

    参考资料

    参考链接: https://zhuanlan.zhihu.com/p/337690783
    参考链接:https://zhuanlan.zhihu.com/p/60199964?utm_source=qq&utm_medium=social&utm_oi=1221207556791963648
    参考链接: https://blog.csdn.net/weixin_44791964/article/details/103530206
    参考链接:https://blog.csdn.net/qq_34714751/article/details/85536669
    参考链接:https://www.bilibili.com/video/BV1fJ411C7AJ?from=search&seid=2691296178499711503

    ]]>
    @@ -401,14 +401,12 @@ 2021-02-24T16:03:00.000Z 2024-10-09T07:33:09.891Z - commit镜像

    在这里插入图片描述

    数据卷操作实战:mysql同步

    mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置

    docker pull mysql:5.7

    docker运行,docker run的常用参数这里我们再次回顾一下

    -d 后台运行
    -p 端口映射
    -v 卷挂载
    -e 环境配置
    --name 环境名字

    通过docker hub我们找到了官方的命令:docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag,在修改一下得到我们最终的输入命令

    docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=12345678 --name mysql01 mysql:5.7
    CREATE DATABASE test_db;

    在这里插入图片描述

    在这里插入图片描述
    删除这个镜像后数据则依旧保存下来了
    在这里插入图片描述

    具名/匿名挂载

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    大多数情况下,为了方便,我们会使用具名挂载
    在这里插入图片描述

    Dockerfile

    Dockerfile就是用来构建docker镜像的文件,命令脚本,通过这个脚本可以生成镜像。
    构建步骤

    1. 编写一个Dockerfile

    2. docker build 构建成为一个镜像

    3. docker run 运行镜像

    4. docker push 发布镜像(DockerHub,阿里云镜像)

      这里我们可以先看看Docker Hub官方是怎么做的
      在这里插入图片描述
      在这里插入图片描述
      官方镜像是比较基础的,有很多命令和功能都省去了,所以我们通常需要在基础的镜像上来构建我们自己的镜像
      在这里插入图片描述

    Dockerfile命令

    常用命令用法
    FROM基础镜像,一切从这开始构建
    MAINTAINER镜像是谁写的,姓名+邮箱
    RUN镜像构建的时候需要运行的命令
    ADD添加内容,如tomcat压缩包
    WORKDIR镜像的工作目录
    VOLUME挂载的目录
    CMD指定这个容器启动时要运行的命令,只有最后一个会生效,可被替代
    ENTRYPOINT指定这个容器启动时要运行的命令,可以追加命令
    ONBUILD当构建一个被继承DockerFile这个时候就会执行ONBUILD命令
    COPY类似ADD将文件拷贝到镜像当中
    ENV构建的时候设置环境变量

    在这里插入图片描述

    创建一个自己的centos

    Dockerfile中99%的镜像都来自于这个scratch镜像,然后配置需要的软件和配置来进行构建。

    mkdir dockerfile
    cd dockerfile
    vim mydockerfile-centos

    编写mydockerfile-centos

    FROM centos
    MAINTAINER khan<khany@foxmail.com>
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    RUN yum -y install vim
    RUN yum -y install net-tools
    EXPOSE 80
    CMD echo $MYPATH
    CMD echo "---end---"
    CMD /bin/bash

    然后我们进入docker build,注意后面一定要有一个.号

    docker build -f mydockerfile-centos  -t mycentos:1.0 .

    然后我们通过docker run -it mycentos:1.0 命令进入我们自己创建的镜像测试运行我们新安装的包和命令是否能正常运行。
    在这里插入图片描述
    我们可以通过 docker history +容器名称/容器id看到这个容器的构建过程。
    在这里插入图片描述

    CMD和ENTRYPOINT区别

    在这里插入图片描述

    dockerfile-cmd-test:

    FROM centos
    CMD ["ls","-a"]

    dockerfile-entrypoint-test:

    FROM centos
    ENTRYPOINT ["ls","-a"]

    执行命令 docker run 容器名称 -l在CMD下会报错,命令会被后面追加的-l替代,而-l并不是有效的linux命令,所以报错,而ENTRYPOINT则是可以追加的则该命令会变为ls -al
    在这里插入图片描述

    发布镜像

    发布到Docker Hub

    1. 地址 https://hub.docker.com/ 注册自己的账号
    2. 确定这个账号可以登录
    3. 在我们服务器上提交自己的镜像
    4. 登录成功,通过push命令提交镜像,记得注意添加版本号
      在这里插入图片描述
      这里出了一点小问题:

    在build自己的镜像的时候添加tag时必须在前面加上自己的dockerhub的username,然后再push就可以了
    在这里插入图片描述

    docker tag 镜像id YOUR_DOCKERHUB_NAME/firstimage
    docker push YOUR_DOCKERHUB_NAME/firstimage

    在这里插入图片描述
    提交成功,可以在docker hub上找到你提交的镜像
    在这里插入图片描述

    阿里云镜像提交

    在阿里云的容器镜像服务里面,创建一个新的镜像仓库,然后就会有详细的教学,做法与docker hub基本一致,提交成功能在镜像版本当中查看到,这里就不再重复讲解了。
    在这里插入图片描述

    Docker镜像操作过程总结

    在这里插入图片描述
    在这里插入图片描述

    参考链接:【狂神说Java】Docker最新超详细版教程通俗易懂

    ]]>
    + commit镜像

    在这里插入图片描述

    数据卷操作实战:mysql同步

    mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置

    docker pull mysql:5.7

    docker运行,docker run的常用参数这里我们再次回顾一下

    -d 后台运行
    -p 端口映射
    -v 卷挂载
    -e 环境配置
    --name 环境名字

    通过docker hub我们找到了官方的命令:docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag,在修改一下得到我们最终的输入命令

    docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=12345678 --name mysql01 mysql:5.7
    CREATE DATABASE test_db;

    在这里插入图片描述

    在这里插入图片描述
    删除这个镜像后数据则依旧保存下来了
    在这里插入图片描述

    具名/匿名挂载

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    大多数情况下,为了方便,我们会使用具名挂载
    在这里插入图片描述

    Dockerfile

    Dockerfile就是用来构建docker镜像的文件,命令脚本,通过这个脚本可以生成镜像。
    构建步骤

    1. 编写一个Dockerfile
    2. docker build 构建成为一个镜像
    3. docker run 运行镜像
    4. docker push 发布镜像(DockerHub,阿里云镜像)

      这里我们可以先看看Docker Hub官方是怎么做的
      在这里插入图片描述
      在这里插入图片描述
      官方镜像是比较基础的,有很多命令和功能都省去了,所以我们通常需要在基础的镜像上来构建我们自己的镜像
      在这里插入图片描述

    Dockerfile命令

    常用命令用法
    FROM基础镜像,一切从这开始构建
    MAINTAINER镜像是谁写的,姓名+邮箱
    RUN镜像构建的时候需要运行的命令
    ADD添加内容,如tomcat压缩包
    WORKDIR镜像的工作目录
    VOLUME挂载的目录
    CMD指定这个容器启动时要运行的命令,只有最后一个会生效,可被替代
    ENTRYPOINT指定这个容器启动时要运行的命令,可以追加命令
    ONBUILD当构建一个被继承DockerFile这个时候就会执行ONBUILD命令
    COPY类似ADD将文件拷贝到镜像当中
    ENV构建的时候设置环境变量

    在这里插入图片描述

    创建一个自己的centos

    Dockerfile中99%的镜像都来自于这个scratch镜像,然后配置需要的软件和配置来进行构建。

    mkdir dockerfile
    cd dockerfile
    vim mydockerfile-centos

    编写mydockerfile-centos

    FROM centos
    MAINTAINER khan<khany@foxmail.com>
    ENV MYPATH /usr/local
    WORKDIR $MYPATH
    RUN yum -y install vim
    RUN yum -y install net-tools
    EXPOSE 80
    CMD echo $MYPATH
    CMD echo "---end---"
    CMD /bin/bash

    然后我们进入docker build,注意后面一定要有一个.号

    docker build -f mydockerfile-centos  -t mycentos:1.0 .

    然后我们通过docker run -it mycentos:1.0命令进入我们自己创建的镜像测试运行我们新安装的包和命令是否能正常运行。
    在这里插入图片描述
    我们可以通过 docker history +容器名称/容器id看到这个容器的构建过程。
    在这里插入图片描述

    CMD和ENTRYPOINT区别

    在这里插入图片描述

    dockerfile-cmd-test:

    FROM centos
    CMD ["ls","-a"]

    dockerfile-entrypoint-test:
    FROM centos
    ENTRYPOINT ["ls","-a"]

    执行命令docker run 容器名称 -l在CMD下会报错,命令会被后面追加的-l替代,而-l并不是有效的linux命令,所以报错,而ENTRYPOINT则是可以追加的则该命令会变为ls -al
    在这里插入图片描述

    发布镜像

    发布到Docker Hub

    1. 地址 https://hub.docker.com/ 注册自己的账号
    2. 确定这个账号可以登录
    3. 在我们服务器上提交自己的镜像
    4. 登录成功,通过push命令提交镜像,记得注意添加版本号
      在这里插入图片描述
      这里出了一点小问题:
      在build自己的镜像的时候添加tag时必须在前面加上自己的dockerhub的username,然后再push就可以了
      在这里插入图片描述
    docker tag 镜像id YOUR_DOCKERHUB_NAME/firstimage
    docker push YOUR_DOCKERHUB_NAME/firstimage

    在这里插入图片描述
    提交成功,可以在docker hub上找到你提交的镜像
    在这里插入图片描述

    阿里云镜像提交

    在阿里云的容器镜像服务里面,创建一个新的镜像仓库,然后就会有详细的教学,做法与docker hub基本一致,提交成功能在镜像版本当中查看到,这里就不再重复讲解了。
    在这里插入图片描述

    Docker镜像操作过程总结

    在这里插入图片描述
    在这里插入图片描述

    参考链接:【狂神说Java】Docker最新超详细版教程通俗易懂

    ]]>
    <h2 id="commit镜像"><a href="#commit镜像" class="headerlink" title="commit镜像"></a>commit镜像</h2><p><img src="https://img-blog.csdnimg.cn/20210224162521454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p> -<h2 id="数据卷操作实战:mysql同步"><a href="#数据卷操作实战:mysql同步" class="headerlink" title="数据卷操作实战:mysql同步"></a>数据卷操作实战:mysql同步</h2><p><strong>mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置</strong></p> -<figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker pull mysql:<span class="hljs-number">5.7</span><br></code></pre></td></tr></table></figure> - +<h2 id="数据卷操作实战:mysql同步"><a href="#数据卷操作实战:mysql同步" class="headerlink" title="数据卷操作实战:mysql同步"></a>数据卷操作实战:mysql同步</h2><p><strong>mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置</strong><br><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker pull mysql:<span class="hljs-number">5.7</span><br></code></pre></td></tr></table></figure></p> <p>docker运行,docker run的常用参数这里我们再次回顾一下</p> <figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell"><span class="hljs-literal">-d</span> 后台运行<br><span class="hljs-literal">-p</span> 端口映射<br><span class="hljs-literal">-v</span> 卷挂载<br><span class="hljs-literal">-e</span> 环境配置<br>-<span class="hljs-literal">-name</span> 环境名字<br></code></pre></td></tr></table></figure> @@ -427,41 +425,44 @@ 2021-02-24T07:30:00.000Z 2024-10-09T07:33:09.890Z - Docker 常用命令

    帮助命令

    docker version #显示docker版本信息
    docker info #显示docker的系统信息,包括镜像和容器的数量
    docker 命令 --help # 帮助命令

    镜像命令

    1.docker images查看所有本地的主机镜像

    docker images显示字段解释
    REPOSITORY镜像的仓库源
    TAG镜像的标签
    IMAGE ID镜像的id
    CREATED镜像的创建时间
    SIZE镜像的大小
    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    hello-world latest bf756fb1ae65 13 months ago 13.3kB
    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker images --help

    Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]

    List images

    Options:
    -a, --all Show all images
    -q, --quiet Only show image IDs

    在这里插入图片描述
    2.docker search命令搜索镜像
    搜索镜像可以去docker hub网站上直接搜索,也可以通过命令行来搜索,通过万能帮助命令能更快的看到他的一些用法,这两种方法结果是一样的
    在这里插入图片描述
    在这里插入图片描述

    我们也可以通过--filter来进行条件筛选
    比如docker search mysql --filter=STARS=3000

    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker search mysql --filter=STARS=3000
    NAME DESCRIPTION STARS OFFICIAL AUTOMATED
    mysql MySQL is a widely used, open-source relation… 10538 [OK]
    mariadb MariaDB is a community-developed fork of MyS… 3935 [OK]

    3.docker pull下载镜像
    这个命令其实信息量很大,这也是docker高明的地方,关于指定版本下载一定要是docker hub官网上面支持和提供的版本
    在这里插入图片描述
    我这里使用了

    docker pull mysql
    docker pull mysql:5.7

    在这里插入图片描述
    4.docker rmi删除镜像
    删除可以通过REPOSITORY来删,也可以通过IMAGE ID来删除
    在这里插入图片描述

    容器命令

    说明:我们有了镜像才可以创建容器,linux,下载一个centos镜像来测试学习

    docker pull centos

    1.新建容器并启动
    通过docker run命令进入下载的centos容器里面后我们可以发现的是,我们的rootname不一样了
    在这里插入图片描述在这里插入图片描述

    2.列出所有运行的容器
    docker ps命令
    在这里插入图片描述
    3.exit退出命令

    exit #直接容器停止并退出
    Ctrl + P + Q #容器不停止并退出

    在执行exit命令后,我们看到rootname又变回来了
    在这里插入图片描述

    4.删除容器

    docker rm 容器id #删除指定的容器,不能删除正在运行的容器,如果要强制删除,需要使用 rm -f
    docker rm $(docker ps -aq) #删除全部的容器
    docker ps -a -q|xargs docker rm #删除全部容器

    5.启动和停止容器
    在这里插入图片描述

    日志元数据进程查看


    在这里插入图片描述
    1.docker top 容器id查看容器中的进程

    在这里插入图片描述
    2.docker inspect 容器id查看元数据

    3.进入当前正在运行的容器

    方式1: docker exec -it 容器id bashshell并可通过ps -ef查看容器当中的进程

    方式2:docker attach 容器id进入容器,如果当前有正在执行的容器则会直接进入到当前正在执行的进程当中

    在这里插入图片描述

    从容器内拷贝到主机上

    即使容器已经停止也是可以进行拷贝的

    docker cp 容器id:容器内路径 目的主机路径

    在这里插入图片描述

    docker部署nginx

    $ docker search nginx
    $ docker pull nginx
    $ docker run -d --name nginx01 -p 8083:80 nginx
    $ docker ps
    $ curl localhost:8083

    docker stop 后则无法再访问
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    portainer可视化管理

    docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer

    在这里插入图片描述
    进入后选择local模式,然后就能看到这个版面了
    在这里插入图片描述
    参考链接:
    【狂神说Java】Docker最新超详细版教程通俗易懂

    ]]>
    + Docker 常用命令

    帮助命令

    docker version #显示docker版本信息
    docker info #显示docker的系统信息,包括镜像和容器的数量
    docker 命令 --help # 帮助命令

    镜像命令

    1.docker images查看所有本地的主机镜像

    docker images显示字段解释
    REPOSITORY镜像的仓库源
    TAG镜像的标签
    IMAGE ID镜像的id
    CREATED镜像的创建时间
    SIZE镜像的大小
    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    hello-world latest bf756fb1ae65 13 months ago 13.3kB
    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker images --help

    Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]

    List images

    Options:
    -a, --all Show all images
    -q, --quiet Only show image IDs

    在这里插入图片描述
    2.docker search命令搜索镜像
    搜索镜像可以去docker hub网站上直接搜索,也可以通过命令行来搜索,通过万能帮助命令能更快的看到他的一些用法,这两种方法结果是一样的
    在这里插入图片描述
    在这里插入图片描述

    我们也可以通过--filter来进行条件筛选
    比如docker search mysql --filter=STARS=3000

    (base) [root@iZuf69rye0flkbn4kbxrobZ ~]# docker search mysql --filter=STARS=3000
    NAME DESCRIPTION STARS OFFICIAL AUTOMATED
    mysql MySQL is a widely used, open-source relation… 10538 [OK]
    mariadb MariaDB is a community-developed fork of MyS… 3935 [OK]

    3.docker pull下载镜像
    这个命令其实信息量很大,这也是docker高明的地方,关于指定版本下载一定要是docker hub官网上面支持和提供的版本
    在这里插入图片描述
    我这里使用了

    docker pull mysql
    docker pull mysql:5.7

    在这里插入图片描述
    4.docker rmi删除镜像
    删除可以通过REPOSITORY来删,也可以通过IMAGE ID来删除
    在这里插入图片描述

    容器命令

    说明:我们有了镜像才可以创建容器,linux,下载一个centos镜像来测试学习

    docker pull centos

    1.新建容器并启动
    通过docker run命令进入下载的centos容器里面后我们可以发现的是,我们的rootname不一样了
    在这里插入图片描述在这里插入图片描述

    2.列出所有运行的容器
    docker ps命令
    在这里插入图片描述
    3.exit退出命令

    exit #直接容器停止并退出
    Ctrl + P + Q #容器不停止并退出

    在执行exit命令后,我们看到rootname又变回来了
    在这里插入图片描述

    4.删除容器

    docker rm 容器id #删除指定的容器,不能删除正在运行的容器,如果要强制删除,需要使用 rm -f
    docker rm $(docker ps -aq) #删除全部的容器
    docker ps -a -q|xargs docker rm #删除全部容器

    5.启动和停止容器
    在这里插入图片描述

    日志元数据进程查看


    在这里插入图片描述
    1.docker top 容器id查看容器中的进程

    在这里插入图片描述
    2.docker inspect 容器id查看元数据

    3.进入当前正在运行的容器

    方式1: docker exec -it 容器id bashshell并可通过ps -ef查看容器当中的进程

    方式2:docker attach 容器id进入容器,如果当前有正在执行的容器则会直接进入到当前正在执行的进程当中

    在这里插入图片描述

    从容器内拷贝到主机上

    即使容器已经停止也是可以进行拷贝的

    docker cp 容器id:容器内路径 目的主机路径

    在这里插入图片描述

    docker部署nginx

    $ docker search nginx
    $ docker pull nginx
    $ docker run -d --name nginx01 -p 8083:80 nginx
    $ docker ps
    $ curl localhost:8083

    docker stop 后则无法再访问
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    portainer可视化管理

    docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer

    在这里插入图片描述
    进入后选择local模式,然后就能看到这个版面了
    在这里插入图片描述
    参考链接:
    【狂神说Java】Docker最新超详细版教程通俗易懂

    ]]>
    <h1 id="Docker-常用命令"><a href="#Docker-常用命令" class="headerlink" title="Docker 常用命令"></a>Docker 常用命令</h1><h2 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">docker version <span class="hljs-comment">#显示docker版本信息</span><br>docker info <span class="hljs-comment">#显示docker的系统信息,包括镜像和容器的数量</span><br>docker 命令 --<span class="hljs-built_in">help</span> <span class="hljs-comment"># 帮助命令</span><br></code></pre></td></tr></table></figure> - <h2 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h2><p><strong>1.<code>docker images</code>查看所有本地的主机镜像</strong></p> +<div class="table-container"> <table> <thead> <tr> -<th align="left">docker images显示字段</th> -<th align="left">解释</th> +<th style="text-align:left">docker images显示字段</th> +<th style="text-align:left">解释</th> </tr> </thead> -<tbody><tr> -<td align="left">REPOSITORY</td> -<td align="left">镜像的仓库源</td> +<tbody> +<tr> +<td style="text-align:left">REPOSITORY</td> +<td style="text-align:left">镜像的仓库源</td> </tr> <tr> -<td align="left">TAG</td> -<td align="left">镜像的标签</td> +<td style="text-align:left">TAG</td> +<td style="text-align:left">镜像的标签</td> </tr> <tr> -<td align="left">IMAGE ID</td> -<td align="left">镜像的id</td> +<td style="text-align:left">IMAGE ID</td> +<td style="text-align:left">镜像的id</td> </tr> <tr> -<td align="left">CREATED</td> -<td align="left">镜像的创建时间</td> +<td style="text-align:left">CREATED</td> +<td style="text-align:left">镜像的创建时间</td> </tr> <tr> -<td align="left">SIZE</td> -<td align="left">镜像的大小</td> +<td style="text-align:left">SIZE</td> +<td style="text-align:left">镜像的大小</td> </tr> -</tbody></table> +</tbody> +</table> +</div> @@ -500,7 +501,7 @@ 2021-02-23T06:55:00.000Z 2024-10-09T07:33:09.889Z - 零基础入门语义分割-Task4 评价函数与损失函数

    本章主要介绍语义分割的评价函数和各类损失函数。

    4 评价函数与损失函数

    4.1 学习目标

    • 掌握常见的评价函数和损失函数Dice、IoU、BCE、Focal Loss、Lovász-Softmax;
    • 掌握评价/损失函数的实践;

    4.2 TP TN FP FN

    在讲解语义分割中常用的评价函数和损失函数之前,先补充一**TP(真正例 true positive) TN(真反例 true negative) FP(假正例 false positive) FN(假反例 false negative)**的知识。在分类问题中,我们经常看到上述的表述方式,以二分类为例,我们可以将所有的样本预测结果分成TP、TN、 FP、FN四类,并且每一类含有的样本数量之和为总样本数量,即TP+FP+FN+TN=总样本数量。其混淆矩阵如下:

    在这里插入图片描述

    上述的概念都是通过以预测结果的视角定义的,可以依据下面方式理解:

    • 预测结果中的正例 → 在实际中是正例 → 的所有样本被称为真正例(TP)<预测正确>

    • 预测结果中的正例 → 在实际中是反例 → 的所有样本被称为假正例(FP)<预测错误>

    • 预测结果中的反例 → 在实际中是正例 → 的所有样本被称为假反例(FN)<预测错误>

    • 预测结果中的反例 → 在实际中是反例 → 的所有样本被称为真反例(TN)<预测正确>

    这里就不得不提及精确率(precision)和召回率(recall):
    $$
    Precision=\frac{TP}{TP+FP} \
    Recall=\frac{TP}{TP+FN}
    $$
    $Precision$代表了预测的正例中真正的正例所占比例;$Recall$代表了真正的正例中被正确预测出来的比例。

    转移到语义分割任务中来,我们可以将语义分割看作是对每一个图像像素的的分类问题。根据混淆矩阵中的定义,我们亦可以将特定像素所属的集合或区域划分成TP、TN、 FP、FN四类。

    在这里插入图片描述

    以上面的图片为例,图中左子图中的人物区域(黄色像素集合)是我们真实标注的前景信息(target),其他区域(紫色像素集合)为背景信息。当经过预测之后,我们会得到的一张预测结果,图中右子图中的黄色像素为预测的前景(prediction),紫色像素为预测的背景区域。此时,我们便能够将预测结果分成4个部分:

    • 预测结果中的黄色无线区域 → 真实的前景 → 的所有像素集合被称为真正例(TP)<预测正确>

    • 预测结果中的蓝色斜线区域 → 真实的背景 → 的所有像素集合被称为假正例(FP)<预测错误>

    • 预测结果中的红色斜线区域 → 真实的前景 → 的所有像素集合被称为假反例(FN)<预测错误>

    • 预测结果中的白色斜线区域 → 真实的背景 → 的所有像素集合被称为真反例(TN)<预测正确>

    4.3 Dice评价指标

    Dice系数

    Dice系数(Dice coefficient)是常见的评价分割效果的方法之一,同样也可以改写成损失函数用来度量prediction和target之间的距离。Dice系数定义如下:

    $$
    Dice (T, P) = \frac{2 |T \cap P|}{|T| \cup |P|} = \frac{2TP}{FP+2TP+FN}
    $$
    式中:$T$表示真实前景(target),$P$表示预测前景(prediction)。Dice系数取值范围为$[0,1]$,其中值为1时代表预测与真实完全一致。仔细观察,Dice系数与分类评价指标中的F1 score很相似:

    $$
    \frac{1}{F1} = \frac{1}{Precision} + \frac{1}{Recall}
    $$

    $$
    F1 = \frac{2TP}{FP+2TP+FN}
    $$

    所以,Dice系数不仅在直观上体现了target与prediction的相似程度,同时其本质上还隐含了精确率和召回率两个重要指标。

    计算Dice时,将$|T \cap P|$近似为prediction与target对应元素相乘再相加的结果。$|T|$ 和$|P|$的计算直接进行简单的元素求和(也有一些做法是取平方求和),如下示例:
    $$
    |T \cap P| =
    \begin{bmatrix}
    0.01 & 0.03 & 0.02 & 0.02 \
    0.05 & 0.12 & 0.09 & 0.07 \
    0.89 & 0.85 & 0.88 & 0.91 \
    0.99 & 0.97 & 0.95 & 0.97 \
    \end{bmatrix} *
    \begin{bmatrix}
    0 & 0 & 0 & 0 \
    0 & 0 & 0 & 0 \
    1 & 1 & 1 & 1 \
    1 & 1 & 1 & 1 \
    \end{bmatrix} \stackrel{}{\rightarrow}
    \begin{bmatrix}
    0 & 0 & 0 & 0 \
    0 & 0 & 0 & 0 \
    0.89 & 0.85 & 0.88 & 0.91 \
    0.99 & 0.97 & 0.95 & 0.97 \
    \end{bmatrix} \stackrel{sum}{\rightarrow} 7.41
    $$

    $$
    |T| =
    \begin{bmatrix}
    0.01 & 0.03 & 0.02 & 0.02 \
    0.05 & 0.12 & 0.09 & 0.07 \
    0.89 & 0.85 & 0.88 & 0.91 \
    0.99 & 0.97 & 0.95 & 0.97 \
    \end{bmatrix} \stackrel{sum}{\rightarrow} 7.82
    $$

    $$
    |P| =
    \begin{bmatrix}
    0 & 0 & 0 & 0 \
    0 & 0 & 0 & 0 \
    1 & 1 & 1 & 1 \
    1 & 1 & 1 & 1 \
    \end{bmatrix} \stackrel{sum}{\rightarrow} 8
    $$

    Dice Loss

    Dice Loss是在V-net模型中被提出应用的,是通过Dice系数转变而来,其实为了能够实现最小化的损失函数,以方便模型训练,以$1 - Dice$的形式作为损失函数:
    $$
    L = 1-\frac{2 |T \cap P|}{|T| \cup |P|}
    $$
    在一些场合还可以添加上Laplace smoothing减少过拟合:
    $$
    L = 1-\frac{2 |T \cap P| + 1}{|T| \cup |P|+1}
    $$

    代码实现

    import numpy as np

    def dice(output, target):
    '''计算Dice系数'''
    smooth = 1e-6 # 避免0为除数
    intersection = (output * target).sum()
    return (2. * intersection + smooth) / (output.sum() + target.sum() + smooth)

    # 生成随机两个矩阵测试
    target = np.random.randint(0, 2, (3, 3))
    output = np.random.randint(0, 2, (3, 3))

    d = dice(output, target)
    # ----------------------------
    target = array([[1, 0, 0],
    [0, 1, 1],
    [0, 0, 1]])
    output = array([[1, 0, 1],
    [0, 1, 0],
    [0, 0, 0]])
    d = 0.5714286326530524

    4.4 IoU评价指标

    IoU(intersection over union)指标就是常说的交并比,不仅在语义分割评价中经常被使用,在目标检测中也是常用的评价指标。顾名思义,交并比就是指target与prediction两者之间交集与并集的比值:
    $$
    IoU=\frac{T \cap P}{T \cup P}=\frac{TP}{FP+TP+FN}
    $$
    仍然以人物前景分割为例,如下图,其IoU的计算就是使用$intersection / union$。

    targetprediction
    在这里插入图片描述在这里插入图片描述
    Intersection( $T \cap P$)union($T \cup P$)
    在这里插入图片描述在这里插入图片描述

    代码实现

    def iou_score(output, target):
    '''计算IoU指标'''
    intersection = np.logical_and(target, output)
    union = np.logical_or(target, output)
    return np.sum(intersection) / np.sum(union)

    # 生成随机两个矩阵测试
    target = np.random.randint(0, 2, (3, 3))
    output = np.random.randint(0, 2, (3, 3))

    d = iou_score(output, target)
    # ----------------------------
    target = array([[1, 0, 0],
    [0, 1, 1],
    [0, 0, 1]])
    output = array([[1, 0, 1],
    [0, 1, 0],
    [0, 0, 0]])
    d = 0.4

    4.5 BCE损失函数

    BCE损失函数(Binary Cross-Entropy Loss)是交叉熵损失函数(Cross-Entropy Loss)的一种特例,BCE Loss只应用在二分类任务中。针对分类问题,单样本的交叉熵损失为:
    $$
    l(\pmb y, \pmb{\hat y})=- \sum_{i=1}^{c}y_i \cdot log\hat y_i
    $$
    式中,$\pmb{y}={y_1,y_2,…,y_c,}$,其中$y_i$是非0即1的数字,代表了是否属于第$i$类,为真实值;$\hat y_i$代表属于第i类的概率,为预测值。可以看出,交叉熵损失考虑了多类别情况,针对每一种类别都求了损失。针对二分类问题,上述公式可以改写为:
    $$
    l(y,\hat y)=-[y \cdot log\hat y +(1-y)\cdot log (1-\hat y)]
    $$
    式中,$y$为真实值,非1即0;$\hat y$为所属此类的概率值,为预测值。这个公式也就是BCE损失函数,即二分类任务时的交叉熵损失。值得强调的是,公式中的$\hat y$为概率分布形式,因此在使用BCE损失前,都应该将预测出来的结果转变成概率值,一般为sigmoid激活之后的输出。

    代码实现

    在pytorch中,官方已经给出了BCE损失函数的API,免去了自己编写函数的痛苦:

    torch.nn.BCELoss(weight: Optional[torch.Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean')
    $$
    ℓ(y,\hat y)=L={l_1,…,l_N }^⊤,\ \ \ l_n=-w_n[y_n \cdot log\hat y_n +(1-y_n)\cdot log (1-\hat y_n)]
    $$
    参数:
    weight(Tensor)- 为每一批量下的loss添加一个权重,很少使用
    size_average(bool)- 弃用中
    reduce(bool)- 弃用中
    reduction(str) - ‘none’ | ‘mean’ | ‘sum’:为代替上面的size_average和reduce而生。——为mean时返回的该批量样本loss的平均值;为sum时,返回的该批量样本loss之和

    同时,pytorch还提供了已经结合了Sigmoid函数的BCE损失:torch.nn.BCEWithLogitsLoss(),相当于免去了实现进行Sigmoid激活的操作。

    import torch
    import torch.nn as nn

    bce = nn.BCELoss()
    bce_sig = nn.BCEWithLogitsLoss()

    input = torch.randn(5, 1, requires_grad=True)
    target = torch.empty(5, 1).random_(2)
    pre = nn.Sigmoid()(input)

    loss_bce = bce(pre, target)
    loss_bce_sig = bce_sig(input, target)

    # ------------------------
    input = tensor([[-0.2296],
    [-0.6389],
    [-0.2405],
    [ 1.3451],
    [ 0.7580]], requires_grad=True)
    output = tensor([[1.],
    [0.],
    [0.],
    [1.],
    [1.]])
    pre = tensor([[0.4428],
    [0.3455],
    [0.4402],
    [0.7933],
    [0.6809]], grad_fn=<SigmoidBackward>)

    print(loss_bce)
    tensor(0.4869, grad_fn=<BinaryCrossEntropyBackward>)

    print(loss_bce_sig)
    tensor(0.4869, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)

    4.6 Focal Loss

    Focal loss最初是出现在目标检测领域,主要是为了解决正负样本比例失调的问题。那么对于分割任务来说,如果存在数据不均衡的情况,也可以借用focal loss来进行缓解。Focal loss函数公式如下所示:

    $$
    loss = -\frac{1}{N} \sum_{i=1}^{N}\left(\alpha y_{i}\left(1-p_{i}\right)^{\gamma} \log p_{i}+(1-\alpha)\left(1-y_{i}\right) p_{i}^{\gamma} \log \left(1-p_{i}\right)\right)
    $$
    仔细观察就不难发现,它其实是BCE扩展而来,对比BCE其实就多了个
    $$
    \alpha(1-p_{i})^{\gamma}和(1-\alpha)p_{i}^{\gamma}
    $$
    为什么多了这个就能缓解正负样本不均衡的问题呢?见下图:

    在这里插入图片描述

    简单来说:$α$解决样本不平衡问题,$γ$解决样本难易问题。

    也就是说,当数据不均衡时,可以根据比例设置合适的$α$,这个很好理解,为了能够使得正负样本得到的损失能够均衡,因此对loss前面加上一定的权重,其中负样本数量多,因此占用的权重可以设置的小一点;正样本数量少,就对正样本产生的损失的权重设的高一点。

    那γ具体怎么起作用呢?以图中$γ=5$曲线为例,假设$gt$类别为1,当模型预测结果为1的概率$p_t$比较大时,我们认为模型预测的比较准确,也就是说这个样本比较简单。而对于比较简单的样本,我们希望提供的loss小一些而让模型主要学习难一些的样本,也就是$p_t→ 1$则loss接近于0,既不用再特别学习;当分类错误时,$p_t → 0$则loss正常产生,继续学习。对比图中蓝色和绿色曲线,可以看到,γ值越大,当模型预测结果比较准确的时候能提供更小的loss,符合我们为简单样本降低loss的预期。

    代码实现:

    import torch.nn as nn
    import torch
    import torch.nn.functional as F

    class FocalLoss(nn.Module):
    def __init__(self, alpha=1, gamma=2, logits=False, reduce=True):
    super(FocalLoss, self).__init__()
    self.alpha = alpha
    self.gamma = gamma
    self.logits = logits# 如果BEC带logits则损失函数在计算BECloss之前会自动计算softmax/sigmoid将其映射到[0,1]
    self.reduce = reduce

    def forward(self, inputs, targets):
    if self.logits:
    BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False)
    else:
    BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=False)
    pt = torch.exp(-BCE_loss)
    F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss

    if self.reduce:
    return torch.mean(F_loss)
    else:
    return F_loss

    # ------------------------

    FL1 = FocalLoss(logits=False)
    FL2 = FocalLoss(logits=True)

    inputs = torch.randn(5, 1, requires_grad=True)
    targets = torch.empty(5, 1).random_(2)
    pre = nn.Sigmoid()(inputs)

    f_loss_1 = FL1(pre, targets)
    f_loss_2 = FL2(inputs, targets)

    # ------------------------

    print('inputs:', inputs)
    inputs: tensor([[-1.3521],
    [ 0.4975],
    [-1.0178],
    [-0.3859],
    [-0.2923]], requires_grad=True)

    print('targets:', targets)
    targets: tensor([[1.],
    [1.],
    [0.],
    [1.],
    [1.]])

    print('pre:', pre)
    pre: tensor([[0.2055],
    [0.6219],
    [0.2655],
    [0.4047],
    [0.4274]], grad_fn=<SigmoidBackward>)

    print('f_loss_1:', f_loss_1)
    f_loss_1: tensor(0.3375, grad_fn=<MeanBackward0>)

    print('f_loss_2', f_loss_2)
    f_loss_2 tensor(0.3375, grad_fn=<MeanBackward0>)

    4.7 Lovász-Softmax

    IoU是评价分割模型分割结果质量的重要指标,因此很自然想到能否用$1-IoU$(即Jaccard loss)来做损失函数,但是它是一个离散的loss,不能直接求导,所以无法直接用来作为损失函数。为了克服这个离散的问题,可以采用lLovász extension将离散的Jaccard loss 变得连续,从而可以直接求导,使得其作为分割网络的loss function。Lovász-Softmax相比于交叉熵函数具有更好的效果。

    论文地址:
    paper on CVF open access
    arxiv paper

    首先明确定义,在语义分割任务中,给定真实像素标签向量$\pmb{y^*}$和预测像素标签$\pmb{\hat{y} }$,则所属类别$c$的IoU(也称为Jaccard index)如下,其取值范围为$[0,1]$,并规定$0/0=1$:

    $$J_c(\pmb{y^*},\pmb{\hat{y} })=\frac{|{\pmb{y^*}=c} \cap {\pmb{\hat{y} }=c}|}{|{\pmb{y^*}=c} \cup {\pmb{\hat{y} }=c}|}
    $$

    则Jaccard loss为:
    $$\Delta_{J_c}(\pmb{y^*},\pmb{\hat{y} }) =1-J_c(\pmb{y^*},\pmb{\hat{y} })
    $$

    针对类别$c$,所有未被正确预测的像素集合定义为:

    $$
    M_c(\pmb{y^*},\pmb{\hat{y} })={ \pmb{y^*}=c, \pmb{\hat{y} } \neq c} \cup { \pmb{y^*}\neq c, \pmb{\hat{y} } = c }
    $$

    则可将Jaccard loss改写为关于$M_c$的子模集合函数(submodular set functions):
    $$\Delta_{J_c}:M_c \in {0,1}^{p} \mapsto \frac{|M_c|}{|{\pmb{y^*}=c}\cup M_c|}
    $$

    方便理解,此处可以把${0,1}^p$理解成如图像mask展开成离散一维向量的形式。

    Lovász extension可以求解子模最小化问题,并且子模的Lovász extension是凸函数,可以高效实现最小化。在论文中作者对$\Delta$(集合函数)和$\overline{\Delta}$(集合函数的Lovász extension)进行了定义,为不涉及过多概念以方便理解,此处不再过多讨论。我们可以将$\overline{\Delta}$理解为一个线性插值函数,可以将${0,1}^p$这种离散向量连续化,主要是为了方便后续反向传播、求梯度等等。因此我们可以通过这个线性插值函数得到$\Delta_{J_c}$的Lovász extension$\overline{\Delta_{J_c} }$。

    在具有$c(c>2)$个类别的语义分割任务中,我们使用Softmax函数将模型的输出映射到概率分布形式,类似传统交叉熵损失函数所进行的操作:
    $$p_i(c)=\frac{e^{F_i(c)} }{\sum_{c^{‘}\in C}e^{F_i(c^{‘})} }  \forall i \in [1,p],\forall c \in C
    $$
    式中,$p_i(c)$表示了像素$i$所属类别$c$的概率。通过上式可以构建每个像素产生的误差$m(c)$:

    $$m_i(c)=\left {
    \begin{array}{c}
    1-p_i(c),\ \ if \ \ c=y^{*}_{i} \
    p_i(c),\ \ \ \ \ \ \ otherwise
    \end{array}
    \right.
    $$

    可知,对于一张图像中所有像素则误差向量为$m(c)\in {0, 1}^p$,则可以建立关于$\Delta_{J_c}$的代理损失函数:
    $$
    loss(p(c))=\overline{\Delta_{J_c} }(m(c))
    $$
    当我们考虑整个数据集是,一般会使用mIoU进行度量,因此我们对上述损失也进行平均化处理,则定义的Lovász-Softmax损失函数为:
    $$
    loss(\pmb{p})=\frac{1}{|C|}\sum_{c\in C}\overline{\Delta_{J_c} }(m(c))
    $$

    代码实现

    论文作者已经给出了Lovász-Softmax实现代码,并且有pytorch和tensorflow两种版本,并提供了使用demo。此处将针对多分类任务的Lovász-Softmax源码进行展示。

    Lovász-Softmax实现链接

    import torch
    from torch.autograd import Variable
    import torch.nn.functional as F
    import numpy as np
    try:
    from itertools import ifilterfalse
    except ImportError: # py3k
    from itertools import filterfalse as ifilterfalse

    # --------------------------- MULTICLASS LOSSES ---------------------------
    def lovasz_softmax(probas, labels, classes='present', per_image=False, ignore=None):
    """
    Multi-class Lovasz-Softmax loss
    probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1).
    Interpreted as binary (sigmoid) output with outputs of size [B, H, W].
    labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1)
    classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.
    per_image: compute the loss per image instead of per batch
    ignore: void class labels
    """
    if per_image:
    loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore), classes=classes)
    for prob, lab in zip(probas, labels))
    else:
    loss = lovasz_softmax_flat(*flatten_probas(probas, labels, ignore), classes=classes)
    return loss


    def lovasz_softmax_flat(probas, labels, classes='present'):
    """
    Multi-class Lovasz-Softmax loss
    probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1)
    labels: [P] Tensor, ground truth labels (between 0 and C - 1)
    classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.
    """
    if probas.numel() == 0:
    # only void pixels, the gradients should be 0
    return probas * 0.
    C = probas.size(1)
    losses = []
    class_to_sum = list(range(C)) if classes in ['all', 'present'] else classes
    for c in class_to_sum:
    fg = (labels == c).float() # foreground for class c
    if (classes is 'present' and fg.sum() == 0):
    continue
    if C == 1:
    if len(classes) > 1:
    raise ValueError('Sigmoid output possible only with 1 class')
    class_pred = probas[:, 0]
    else:
    class_pred = probas[:, c]
    errors = (Variable(fg) - class_pred).abs()
    errors_sorted, perm = torch.sort(errors, 0, descending=True)
    perm = perm.data
    fg_sorted = fg[perm]
    losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))
    return mean(losses)


    def flatten_probas(probas, labels, ignore=None):
    """
    Flattens predictions in the batch
    """
    if probas.dim() == 3:
    # assumes output of a sigmoid layer
    B, H, W = probas.size()
    probas = probas.view(B, 1, H, W)
    B, C, H, W = probas.size()
    probas = probas.permute(0, 2, 3, 1).contiguous().view(-1, C) # B * H * W, C = P, C
    labels = labels.view(-1)
    if ignore is None:
    return probas, labels
    valid = (labels != ignore)
    vprobas = probas[valid.nonzero().squeeze()]
    vlabels = labels[valid]
    return vprobas, vlabels


    def xloss(logits, labels, ignore=None):
    """
    Cross entropy loss
    """
    return F.cross_entropy(logits, Variable(labels), ignore_index=255)

    # --------------------------- HELPER FUNCTIONS ---------------------------
    def isnan(x):
    return x != x

    def mean(l, ignore_nan=False, empty=0):
    """
    nanmean compatible with generators.
    """
    l = iter(l)
    if ignore_nan:
    l = ifilterfalse(isnan, l)
    try:
    n = 1
    acc = next(l)
    except StopIteration:
    if empty == 'raise':
    raise ValueError('Empty mean')
    return empty
    for n, v in enumerate(l, 2):
    acc += v
    if n == 1:
    return acc
    return acc / n

    4.8 参考链接

    语义分割的评价指标IoU

    医学图像分割常用的损失函数

    What is “Dice loss” for image segmentation?

    pytorch loss-functions

    Submodularity and the Lovász extension

    4.9 本章小结

    本章对各类评价指标进行介绍,并进行具体代码实践。

    ]]>
    + 零基础入门语义分割-Task4 评价函数与损失函数

    本章主要介绍语义分割的评价函数和各类损失函数。

    4 评价函数与损失函数

    4.1 学习目标

    • 掌握常见的评价函数和损失函数Dice、IoU、BCE、Focal Loss、Lovász-Softmax;
    • 掌握评价/损失函数的实践;

    4.2 TP TN FP FN

    在讲解语义分割中常用的评价函数和损失函数之前,先补充一TP(真正例 true positive) TN(真反例 true negative) FP(假正例 false positive) FN(假反例 false negative)的知识。在分类问题中,我们经常看到上述的表述方式,以二分类为例,我们可以将所有的样本预测结果分成TP、TN、 FP、FN四类,并且每一类含有的样本数量之和为总样本数量,即TP+FP+FN+TN=总样本数量。其混淆矩阵如下:

    在这里插入图片描述

    上述的概念都是通过以预测结果的视角定义的,可以依据下面方式理解:

    • 预测结果中的正例 → 在实际中是正例 → 的所有样本被称为真正例(TP)<预测正确>

    • 预测结果中的正例 → 在实际中是反例 → 的所有样本被称为假正例(FP)<预测错误>

    • 预测结果中的反例 → 在实际中是正例 → 的所有样本被称为假反例(FN)<预测错误>

    • 预测结果中的反例 → 在实际中是反例 → 的所有样本被称为真反例(TN)<预测正确>

    这里就不得不提及精确率(precision)和召回率(recall):

    $Precision$代表了预测的正例中真正的正例所占比例;$Recall$代表了真正的正例中被正确预测出来的比例。

    转移到语义分割任务中来,我们可以将语义分割看作是对每一个图像像素的的分类问题。根据混淆矩阵中的定义,我们亦可以将特定像素所属的集合或区域划分成TP、TN、 FP、FN四类。

    在这里插入图片描述

    以上面的图片为例,图中左子图中的人物区域(黄色像素集合)是我们真实标注的前景信息(target),其他区域(紫色像素集合)为背景信息。当经过预测之后,我们会得到的一张预测结果,图中右子图中的黄色像素为预测的前景(prediction),紫色像素为预测的背景区域。此时,我们便能够将预测结果分成4个部分:

    • 预测结果中的黄色无线区域 → 真实的前景 → 的所有像素集合被称为真正例(TP)<预测正确>

    • 预测结果中的蓝色斜线区域 → 真实的背景 → 的所有像素集合被称为假正例(FP)<预测错误>

    • 预测结果中的红色斜线区域 → 真实的前景 → 的所有像素集合被称为假反例(FN)<预测错误>

    • 预测结果中的白色斜线区域 → 真实的背景 → 的所有像素集合被称为真反例(TN)<预测正确>

    4.3 Dice评价指标

    Dice系数

    Dice系数(Dice coefficient)是常见的评价分割效果的方法之一,同样也可以改写成损失函数用来度量prediction和target之间的距离。Dice系数定义如下:

    式中:$T$表示真实前景(target),$P$表示预测前景(prediction)。Dice系数取值范围为$[0,1]$,其中值为1时代表预测与真实完全一致。仔细观察,Dice系数与分类评价指标中的F1 score很相似:

    所以,Dice系数不仅在直观上体现了target与prediction的相似程度,同时其本质上还隐含了精确率和召回率两个重要指标。

    计算Dice时,将$|T \cap P|$近似为prediction与target对应元素相乘再相加的结果。$|T|$ 和$|P|$的计算直接进行简单的元素求和(也有一些做法是取平方求和),如下示例:

    Dice Loss

    Dice Loss是在V-net模型中被提出应用的,是通过Dice系数转变而来,其实为了能够实现最小化的损失函数,以方便模型训练,以$1 - Dice$的形式作为损失函数:

    在一些场合还可以添加上Laplace smoothing减少过拟合:

    代码实现

    import numpy as np

    def dice(output, target):
    '''计算Dice系数'''
    smooth = 1e-6 # 避免0为除数
    intersection = (output * target).sum()
    return (2. * intersection + smooth) / (output.sum() + target.sum() + smooth)

    # 生成随机两个矩阵测试
    target = np.random.randint(0, 2, (3, 3))
    output = np.random.randint(0, 2, (3, 3))

    d = dice(output, target)
    # ----------------------------
    target = array([[1, 0, 0],
    [0, 1, 1],
    [0, 0, 1]])
    output = array([[1, 0, 1],
    [0, 1, 0],
    [0, 0, 0]])
    d = 0.5714286326530524

    4.4 IoU评价指标

    IoU(intersection over union)指标就是常说的交并比,不仅在语义分割评价中经常被使用,在目标检测中也是常用的评价指标。顾名思义,交并比就是指target与prediction两者之间交集与并集的比值:

    仍然以人物前景分割为例,如下图,其IoU的计算就是使用$intersection / union$。

    targetprediction
    在这里插入图片描述在这里插入图片描述
    Intersection( $T \cap P$)union($T \cup P$)
    在这里插入图片描述在这里插入图片描述

    代码实现

    def iou_score(output, target):
    '''计算IoU指标'''
    intersection = np.logical_and(target, output)
    union = np.logical_or(target, output)
    return np.sum(intersection) / np.sum(union)

    # 生成随机两个矩阵测试
    target = np.random.randint(0, 2, (3, 3))
    output = np.random.randint(0, 2, (3, 3))

    d = iou_score(output, target)
    # ----------------------------
    target = array([[1, 0, 0],
    [0, 1, 1],
    [0, 0, 1]])
    output = array([[1, 0, 1],
    [0, 1, 0],
    [0, 0, 0]])
    d = 0.4

    4.5 BCE损失函数

    BCE损失函数(Binary Cross-Entropy Loss)是交叉熵损失函数(Cross-Entropy Loss)的一种特例,BCE Loss只应用在二分类任务中。针对分类问题,单样本的交叉熵损失为:

    式中,$\pmb{y}={y_1,y_2,…,y_c,}$,其中$y_i$是非0即1的数字,代表了是否属于第$i$类,为真实值;$\hat y_i$代表属于第i类的概率,为预测值。可以看出,交叉熵损失考虑了多类别情况,针对每一种类别都求了损失。针对二分类问题,上述公式可以改写为:

    式中,$y$为真实值,非1即0;$\hat y$为所属此类的概率值,为预测值。这个公式也就是BCE损失函数,即二分类任务时的交叉熵损失。值得强调的是,公式中的$\hat y$为概率分布形式,因此在使用BCE损失前,都应该将预测出来的结果转变成概率值,一般为sigmoid激活之后的输出。

    代码实现

    在pytorch中,官方已经给出了BCE损失函数的API,免去了自己编写函数的痛苦:

    torch.nn.BCELoss(weight: Optional[torch.Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean')

    参数:
    weight(Tensor)- 为每一批量下的loss添加一个权重,很少使用
    size_average(bool)- 弃用中
    reduce(bool)- 弃用中
    reduction(str) - ‘none’ | ‘mean’ | ‘sum’:为代替上面的size_average和reduce而生。——为mean时返回的该批量样本loss的平均值;为sum时,返回的该批量样本loss之和

    同时,pytorch还提供了已经结合了Sigmoid函数的BCE损失:torch.nn.BCEWithLogitsLoss(),相当于免去了实现进行Sigmoid激活的操作。

    import torch
    import torch.nn as nn

    bce = nn.BCELoss()
    bce_sig = nn.BCEWithLogitsLoss()

    input = torch.randn(5, 1, requires_grad=True)
    target = torch.empty(5, 1).random_(2)
    pre = nn.Sigmoid()(input)

    loss_bce = bce(pre, target)
    loss_bce_sig = bce_sig(input, target)

    # ------------------------
    input = tensor([[-0.2296],
    [-0.6389],
    [-0.2405],
    [ 1.3451],
    [ 0.7580]], requires_grad=True)
    output = tensor([[1.],
    [0.],
    [0.],
    [1.],
    [1.]])
    pre = tensor([[0.4428],
    [0.3455],
    [0.4402],
    [0.7933],
    [0.6809]], grad_fn=<SigmoidBackward>)

    print(loss_bce)
    tensor(0.4869, grad_fn=<BinaryCrossEntropyBackward>)

    print(loss_bce_sig)
    tensor(0.4869, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)

    4.6 Focal Loss

    Focal loss最初是出现在目标检测领域,主要是为了解决正负样本比例失调的问题。那么对于分割任务来说,如果存在数据不均衡的情况,也可以借用focal loss来进行缓解。Focal loss函数公式如下所示:

    仔细观察就不难发现,它其实是BCE扩展而来,对比BCE其实就多了个

    为什么多了这个就能缓解正负样本不均衡的问题呢?见下图:

    在这里插入图片描述

    简单来说:$α$解决样本不平衡问题,$γ$解决样本难易问题。

    也就是说,当数据不均衡时,可以根据比例设置合适的$α$,这个很好理解,为了能够使得正负样本得到的损失能够均衡,因此对loss前面加上一定的权重,其中负样本数量多,因此占用的权重可以设置的小一点;正样本数量少,就对正样本产生的损失的权重设的高一点。

    那γ具体怎么起作用呢?以图中$γ=5$曲线为例,假设$gt$类别为1,当模型预测结果为1的概率$p_t$比较大时,我们认为模型预测的比较准确,也就是说这个样本比较简单。而对于比较简单的样本,我们希望提供的loss小一些而让模型主要学习难一些的样本,也就是$p_t→ 1$则loss接近于0,既不用再特别学习;当分类错误时,$p_t → 0$则loss正常产生,继续学习。对比图中蓝色和绿色曲线,可以看到,γ值越大,当模型预测结果比较准确的时候能提供更小的loss,符合我们为简单样本降低loss的预期。

    代码实现:

    import torch.nn as nn
    import torch
    import torch.nn.functional as F

    class FocalLoss(nn.Module):
    def __init__(self, alpha=1, gamma=2, logits=False, reduce=True):
    super(FocalLoss, self).__init__()
    self.alpha = alpha
    self.gamma = gamma
    self.logits = logits# 如果BEC带logits则损失函数在计算BECloss之前会自动计算softmax/sigmoid将其映射到[0,1]
    self.reduce = reduce

    def forward(self, inputs, targets):
    if self.logits:
    BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False)
    else:
    BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=False)
    pt = torch.exp(-BCE_loss)
    F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss

    if self.reduce:
    return torch.mean(F_loss)
    else:
    return F_loss

    # ------------------------

    FL1 = FocalLoss(logits=False)
    FL2 = FocalLoss(logits=True)

    inputs = torch.randn(5, 1, requires_grad=True)
    targets = torch.empty(5, 1).random_(2)
    pre = nn.Sigmoid()(inputs)

    f_loss_1 = FL1(pre, targets)
    f_loss_2 = FL2(inputs, targets)

    # ------------------------

    print('inputs:', inputs)
    inputs: tensor([[-1.3521],
    [ 0.4975],
    [-1.0178],
    [-0.3859],
    [-0.2923]], requires_grad=True)

    print('targets:', targets)
    targets: tensor([[1.],
    [1.],
    [0.],
    [1.],
    [1.]])

    print('pre:', pre)
    pre: tensor([[0.2055],
    [0.6219],
    [0.2655],
    [0.4047],
    [0.4274]], grad_fn=<SigmoidBackward>)

    print('f_loss_1:', f_loss_1)
    f_loss_1: tensor(0.3375, grad_fn=<MeanBackward0>)

    print('f_loss_2', f_loss_2)
    f_loss_2 tensor(0.3375, grad_fn=<MeanBackward0>)

    4.7 Lovász-Softmax

    IoU是评价分割模型分割结果质量的重要指标,因此很自然想到能否用$1-IoU$(即Jaccard loss)来做损失函数,但是它是一个离散的loss,不能直接求导,所以无法直接用来作为损失函数。为了克服这个离散的问题,可以采用lLovász extension将离散的Jaccard loss 变得连续,从而可以直接求导,使得其作为分割网络的loss function。Lovász-Softmax相比于交叉熵函数具有更好的效果。

    论文地址:
    paper on CVF open access
    arxiv paper

    首先明确定义,在语义分割任务中,给定真实像素标签向量$\pmb{y^*}$和预测像素标签$\pmb{\hat{y} }$,则所属类别$c$的IoU(也称为Jaccard index)如下,其取值范围为$[0,1]$,并规定$0/0=1$:

    则Jaccard loss为:

    针对类别$c$,所有未被正确预测的像素集合定义为:

    则可将Jaccard loss改写为关于$M_c$的子模集合函数(submodular set functions):

    方便理解,此处可以把${0,1}^p$理解成如图像mask展开成离散一维向量的形式。

    Lovász extension可以求解子模最小化问题,并且子模的Lovász extension是凸函数,可以高效实现最小化。在论文中作者对$\Delta$(集合函数)和$\overline{\Delta}$(集合函数的Lovász extension)进行了定义,为不涉及过多概念以方便理解,此处不再过多讨论。我们可以将$\overline{\Delta}$理解为一个线性插值函数,可以将${0,1}^p$这种离散向量连续化,主要是为了方便后续反向传播、求梯度等等。因此我们可以通过这个线性插值函数得到$\Delta{J_c}$的Lovász extension$\overline{\Delta{J_c} }$。

    在具有$c(c>2)$个类别的语义分割任务中,我们使用Softmax函数将模型的输出映射到概率分布形式,类似传统交叉熵损失函数所进行的操作:

    式中,$p_i(c)$表示了像素$i$所属类别$c$的概率。通过上式可以构建每个像素产生的误差$m(c)$:

    可知,对于一张图像中所有像素则误差向量为$m(c)\in {0, 1}^p$,则可以建立关于$\Delta_{J_c}$的代理损失函数:

    当我们考虑整个数据集是,一般会使用mIoU进行度量,因此我们对上述损失也进行平均化处理,则定义的Lovász-Softmax损失函数为:

    代码实现

    论文作者已经给出了Lovász-Softmax实现代码,并且有pytorch和tensorflow两种版本,并提供了使用demo。此处将针对多分类任务的Lovász-Softmax源码进行展示。

    Lovász-Softmax实现链接

    import torch
    from torch.autograd import Variable
    import torch.nn.functional as F
    import numpy as np
    try:
    from itertools import ifilterfalse
    except ImportError: # py3k
    from itertools import filterfalse as ifilterfalse

    # --------------------------- MULTICLASS LOSSES ---------------------------
    def lovasz_softmax(probas, labels, classes='present', per_image=False, ignore=None):
    """
    Multi-class Lovasz-Softmax loss
    probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1).
    Interpreted as binary (sigmoid) output with outputs of size [B, H, W].
    labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1)
    classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.
    per_image: compute the loss per image instead of per batch
    ignore: void class labels
    """
    if per_image:
    loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(0), lab.unsqueeze(0), ignore), classes=classes)
    for prob, lab in zip(probas, labels))
    else:
    loss = lovasz_softmax_flat(*flatten_probas(probas, labels, ignore), classes=classes)
    return loss


    def lovasz_softmax_flat(probas, labels, classes='present'):
    """
    Multi-class Lovasz-Softmax loss
    probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1)
    labels: [P] Tensor, ground truth labels (between 0 and C - 1)
    classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.
    """
    if probas.numel() == 0:
    # only void pixels, the gradients should be 0
    return probas * 0.
    C = probas.size(1)
    losses = []
    class_to_sum = list(range(C)) if classes in ['all', 'present'] else classes
    for c in class_to_sum:
    fg = (labels == c).float() # foreground for class c
    if (classes is 'present' and fg.sum() == 0):
    continue
    if C == 1:
    if len(classes) > 1:
    raise ValueError('Sigmoid output possible only with 1 class')
    class_pred = probas[:, 0]
    else:
    class_pred = probas[:, c]
    errors = (Variable(fg) - class_pred).abs()
    errors_sorted, perm = torch.sort(errors, 0, descending=True)
    perm = perm.data
    fg_sorted = fg[perm]
    losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))
    return mean(losses)


    def flatten_probas(probas, labels, ignore=None):
    """
    Flattens predictions in the batch
    """
    if probas.dim() == 3:
    # assumes output of a sigmoid layer
    B, H, W = probas.size()
    probas = probas.view(B, 1, H, W)
    B, C, H, W = probas.size()
    probas = probas.permute(0, 2, 3, 1).contiguous().view(-1, C) # B * H * W, C = P, C
    labels = labels.view(-1)
    if ignore is None:
    return probas, labels
    valid = (labels != ignore)
    vprobas = probas[valid.nonzero().squeeze()]
    vlabels = labels[valid]
    return vprobas, vlabels


    def xloss(logits, labels, ignore=None):
    """
    Cross entropy loss
    """
    return F.cross_entropy(logits, Variable(labels), ignore_index=255)

    # --------------------------- HELPER FUNCTIONS ---------------------------
    def isnan(x):
    return x != x

    def mean(l, ignore_nan=False, empty=0):
    """
    nanmean compatible with generators.
    """
    l = iter(l)
    if ignore_nan:
    l = ifilterfalse(isnan, l)
    try:
    n = 1
    acc = next(l)
    except StopIteration:
    if empty == 'raise':
    raise ValueError('Empty mean')
    return empty
    for n, v in enumerate(l, 2):
    acc += v
    if n == 1:
    return acc
    return acc / n

    4.8 参考链接

    语义分割的评价指标IoU

    医学图像分割常用的损失函数

    What is “Dice loss” for image segmentation?

    pytorch loss-functions

    Submodularity and the Lovász extension

    4.9 本章小结

    本章对各类评价指标进行介绍,并进行具体代码实践。

    ]]>
    diff --git a/categories/index.html b/categories/index.html index a158ffc3..9f9e465c 100644 --- a/categories/index.html +++ b/categories/index.html @@ -481,7 +481,7 @@