GhostNet原理解析及pytorch实现

论文:https://arxiv.org/abs/1911.11907

源码:https://github.com/huawei-noah/ghostnet

简要论述GhostNet的核心内容。

Ghost Net

1、Introduction

在训练良好的深度神经网络的特征图中,丰富甚至冗余的信息通常保证了对输入数据的全面理解。

上图是ResNet-50中第一个残差组生成的一些特征图的可视化,其中三个相似的特征图对样例用相同颜色的方框标注。其中存在许多相似的特征图对,就像一个幽灵一样。其中一个特征映射可以通过简单的操作(用扳手表示)对另一个特征映射进行变换近似得到。

作者认为特征映射中的冗余是一个成功的深度神经网络的重要特征,而不是避免冗余的特征映射,更倾向于采用它们,但以一种经济有效的方式。

怎么以很小的代价生成许多能从原始特征发掘所需信息的幽灵特征图呢?这个便是整篇论文的核心思想。

2、Approach

主流CNN计算的中间特征映射存在广泛的冗余,比如上面的ResNet-50,依此提出了可以减少它所需的资源。

上面所对比的就是输出相同特征映射的卷积层与Ghost模块的对比,这里的\Phi表示的就是"很小的代价"。

Ghost模块的原理就是先进行Conv操作生成一些特征图,然后经过cheat生成一系列的冗余特征图,最后将Conv生成的特征图与cheap操作生成的特征图进行concat操作。

现有方法采用点向卷积跨通道处理特征,再采用深度卷积处理空间信息。相比之下,Ghost模块采用普通卷积先生成一些固有的特征映射,然后利用便宜的线性运算来增加特征和增加通道。而在以前的高效架构中,处理每个特征映射的操作仅限于深度卷积或移位操作,而Ghost模块中的线性操作具有较大的多样性。

3、GhostNet

Ghost bottleneck

上图是步幅分别为1和2的Ghost bottleneck,这个结构看起来很眼熟,很像是resnet里面的残差模块。

  • 左侧的G-bneck主要由两个堆叠的ghost模块组成,它的作用是作为扩展层增加通道的数量。
  • 右侧的G-bneck减少了通道的数量以匹配快捷路径。批归一化和ReLU非线性在每一层之后应用,但MobileNetV2建议在第二个Ghost模块之后不使用ReLU。

网络结构

G-bneck表示Ghost bottleneck。#exp表示扩展大小。#out表示输出通道的数量。SE表示是否使用SE模块。

这里的G-bneck适用于stride=1。对于stride=2的情况,快捷路径由下采样层实现,并在两个Ghost模块之间插入stride=2的深度卷积。在实践中,Ghost模块的主要卷积是点卷积,因为它的效率很高。

4、pytorch实现

"""
Creates a GhostNet Model as defined in:
GhostNet: More Features from Cheap Operations By Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu.
<https://arxiv.org/abs/1911.11907>
"""
import torch
import torch.nn as nn
import torch.nn.functional as Fimport math__all__ = ["ghostnet"]def _make_divisible(v, divisor, min_value=None):"""此函数取自TensorFlow代码库.它确保所有层都有一个可被8整除的通道编号在这里可以看到:https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py通过四舍五入和增加修正,确保通道编号是可被 divisor 整除的最接近的值,并且保证结果不小于指定的最小值。"""if min_value is None:min_value = divisornew_v = max(min_value, int(v + divisor / 2) // divisor * divisor)# 确保四舍五入的下降幅度不超过10%.if new_v < 0.9 * v:new_v += divisorreturn new_vdef hard_sigmoid(x, inplace: bool = False):"""实现硬切线函数(hard sigmoid)的函数。Args:x: 输入张量,可以是任意形状的张量。inplace: 是否原地操作(in-place operation)。默认为 False。Returns:处理后的张量,形状与输入张量相同。注意:ReLU6 函数是一个将小于 0 的值设为 0,大于 6 的值设为 6 的函数。clamp_ 方法用于限制张量的取值范围。"""if inplace:return x.add_(3.).clamp_(0., 6.).div_(6.)else:return F.relu6(x + 3.) / 6.class SqueezeExcite(nn.Module):def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None,act_layer=nn.ReLU, gate_fn=hard_sigmoid, divisor=4, **_):super(SqueezeExcite, self).__init__()self.gate_fn = gate_fnreduced_chs = _make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)self.avg_pool = nn.AdaptiveAvgPool2d(1)self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)self.act1 = act_layer(inplace=True)self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)def forward(self, x):x_se = self.avg_pool(x)x_se = self.conv_reduce(x_se)x_se = self.act1(x_se)x_se = self.conv_expand(x_se)x = x * self.gate_fn(x_se)return xclass ConvBnAct(nn.Module):def __init__(self, in_chs, out_chs, kernel_size,stride=1, padding=0 ,act_layer=nn.ReLU):super(ConvBnAct, self).__init__()self.conv = nn.Conv2d(in_chs, out_chs, kernel_size, stride, padding, bias=False)self.bn1 = nn.BatchNorm2d(out_chs)self.act1 = act_layer(inplace=True)def forward(self, x):x = self.conv(x)x = self.bn1(x)x = self.act1(x)return xclass GhostModule(nn.Module):def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):super(GhostModule, self).__init__()self.oup = oupinit_channels = math.ceil(oup / ratio)   # m = n / snew_channels = init_channels*(ratio-1)   # m * (s - 1) = n / s * (s - 1)self.primary_conv = nn.Sequential(nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),nn.BatchNorm2d(init_channels),nn.ReLU(inplace=True) if relu else nn.Sequential(),)self.cheap_operation = nn.Sequential(nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),nn.BatchNorm2d(new_channels),nn.ReLU(inplace=True) if relu else nn.Sequential(),)def forward(self, x):x1 = self.primary_conv(x)x2 = self.cheap_operation(x1)out = torch.cat([x1,x2], dim=1)return out[:,:self.oup,:,:]class GhostBottleneck(nn.Module):""" Ghost bottleneck w/ optional SE"""def __init__(self, in_chs, mid_chs, out_chs, dw_kernel_size=3,stride=1, act_layer=nn.ReLU, se_ratio=0.):super(GhostBottleneck, self).__init__()has_se = se_ratio is not None and se_ratio > 0.self.stride = stride# Point-wise expansionself.ghost1 = GhostModule(in_chs, mid_chs, relu=True)# Depth-wise convolutionif self.stride > 1:self.conv_dw = nn.Conv2d(mid_chs, mid_chs, dw_kernel_size, stride=stride,padding=(dw_kernel_size-1)//2,groups=mid_chs, bias=False)self.bn_dw = nn.BatchNorm2d(mid_chs)# Squeeze-and-excitationif has_se:self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)else:self.se = None# Point-wise linear projectionself.ghost2 = GhostModule(mid_chs, out_chs, relu=False)# shortcutif (in_chs == out_chs and self.stride == 1):self.shortcut = nn.Sequential()else:self.shortcut = nn.Sequential(nn.Conv2d(in_chs, in_chs, dw_kernel_size, stride=stride,padding=(dw_kernel_size-1)//2, groups=in_chs, bias=False),nn.BatchNorm2d(in_chs),nn.Conv2d(in_chs, out_chs, 1, stride=1, padding=0, bias=False),nn.BatchNorm2d(out_chs),)def forward(self, x):residual = x# 1st ghost bottleneckx = self.ghost1(x)# Depth-wise convolutionif self.stride > 1:x = self.conv_dw(x)x = self.bn_dw(x)# Squeeze-and-excitationif self.se is not None:x = self.se(x)# 2nd ghost bottleneckx = self.ghost2(x)x += self.shortcut(residual)return xclass GhostNet(nn.Module):def __init__(self, cfgs, num_classes=1000, width=1.0, dropout=0.2):super(GhostNet, self).__init__()# setting of inverted residual blocksself.cfgs = cfgsself.dropout = dropout# building first layeroutput_channel = _make_divisible(16 * width, 4)self.conv_stem = nn.Conv2d(3, output_channel, 3, 2, 1, bias=False)self.bn1 = nn.BatchNorm2d(output_channel)self.act1 = nn.ReLU(inplace=True)input_channel = output_channel# building inverted residual blocksstages = []block = GhostBottleneckfor cfg in self.cfgs:layers = []for k, exp_size, c, se_ratio, s in cfg:output_channel = _make_divisible(c * width, 4)hidden_channel = _make_divisible(exp_size * width, 4)layers.append(block(input_channel, hidden_channel, output_channel, k, s,se_ratio=se_ratio))input_channel = output_channelstages.append(nn.Sequential(*layers))output_channel = _make_divisible(exp_size * width, 4)stages.append(nn.Sequential(ConvBnAct(input_channel, output_channel, 1)))input_channel = output_channelself.blocks = nn.Sequential(*stages)# building last several layersoutput_channel = 1280self.global_pool = nn.AdaptiveAvgPool2d((1, 1))self.conv_head = nn.Conv2d(input_channel, output_channel, 1, 1, 0, bias=True)self.act2 = nn.ReLU(inplace=True)self.classifier = nn.Linear(output_channel, num_classes)def forward(self, x):x = self.conv_stem(x)x = self.bn1(x)x = self.act1(x)x = self.blocks(x)x = self.global_pool(x)x = self.conv_head(x)x = self.act2(x)x = x.view(x.size(0), -1)if self.dropout > 0.:x = F.dropout(x, p=self.dropout, training=self.training)x = self.classifier(x)return xdef ghostnet(**kwargs):"""Constructs a GhostNet model"""cfgs = [# k,  t,   c, SE, s# stage1[[3,  16,  16, 0, 1]],# stage2[[3,  48,  24, 0, 2]],[[3,  72,  24, 0, 1]],# stage3[[5,  72,  40, 0.25, 2]],[[5, 120,  40, 0.25, 1]],# stage4[[3, 240,  80, 0, 2]],[[3, 200,  80, 0, 1],[3, 184,  80, 0, 1],[3, 184,  80, 0, 1],[3, 480, 112, 0.25, 1],[3, 672, 112, 0.25, 1]],# stage5[[5, 672, 160, 0.25, 2]],[[5, 960, 160, 0, 1],[5, 960, 160, 0.25, 1],[5, 960, 160, 0, 1],[5, 960, 160, 0.25, 1]]]return GhostNet(cfgs, **kwargs)if __name__=='__main__':model = ghostnet()model.eval()print(model)input = torch.randn(32,3,320,256)y = model(input)print(y.size())

参考文章

CVPR 2020:华为GhostNet,超越谷歌MobileNet,已开源 - 知乎 (zhihu.com)

GhostNet网络详解_ghostnet网络结构-CSDN博客

GHostNet网络最通俗易懂的解读【不接受反驳】_ghost卷积_☞源仔的博客-CSDN博客

GhostNet 详解_ghostnet是什么-CSDN博客

GhostNet详解及代码实现_ghostnet代码_何如千泷的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/149433.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】红黑树(C++实现)

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【数据…

妙不可言的Python之旅----(二)

Python基础语法 什么是字面量 字面量&#xff1a;在代码中&#xff0c;被写下来的的固定的值&#xff0c;称之为字面量 常用的值类型 类型 描述 说明 数字&#xff08;Number&#xff09; 支持 • 整数&#xff08;int&#xff09; • 浮点数&#xff08;float&#xff…

手边酒店V2独立版小程序 1.0.21 免授权+小程序前端

手边酒店小程序独立版酒店宾馆订房系统支持创建多个小程序&#xff0c;让每一个客户单独管理属于自己的小程序。后台支持一键入住&#xff0c;一键退款、退押金、钟点房支持微信支付、模板消息。客服实时收到新的订单信息&#xff0c;可以在手机端处理订单。支持按日期维护房价…

在PHP8中使用instanceof操作符检测对象类型-PHP8知识详解

在PHP8中使用instanceof操作符可以检测当前对象属于哪个类。语法格式如下&#xff1a; objectName instanceof classname下面我们用一个实例来讲解使用instanceof操作符检测对象类型。 本实例将将创建3个类&#xff0c;其中有两个类是父类和子类的关系&#xff0c;然后实例化…

堆排序详解

堆排序 一.前言二.堆排序思路三.堆的创建1.堆的向上调整2.堆的向下调整3.向上建堆4.向下建堆5.两种建堆方式比较 四.堆排序五.复杂度分析六.Topk问题七.结语 一.前言 堆排序在生活中主要有两大应用场景&#xff1a;一是大数据排序&#xff0c;二是优先队列。其中典型的实例就是…

【算法与数据结构】归并排序的代码实现(详细图解)以及master公式的讲解

目录 1、归并排序 1.1、算法描述 1.2、图解说明 2、代码实现 3、master公式 3.1、公式以及结论 3.2、适用于某些特殊的递归 3.3、计算归并排序的时间复杂度 1、归并排序 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用递归或者说是分治法&#xff08;Di…

JAVA学习(5)-全网最详细~

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

第八章 Linux文件系统权限

目录 8.1 文件的一般权限 1.修改文件或目录的权限---chmod命令 2.对于文件和目录&#xff0c;r&#xff0c;w&#xff0c;x有不同的作用&#xff1a; 3.修改文件或目录的所属主和组---chown,chgrp 8.2 文件和目录的特殊权限 三种通过字符描述文件权限 8.3 ACL 权限 1.A…

redis高可用(主从复制,哨兵,集群)

目录 一、主从复制&#xff1a; 1.主从复制介绍&#xff1a; 2.主从复制的作用&#xff1a; 3.主从复制流程&#xff1a; 4.搭建Redis 主从复制&#xff1a; 4.1 环境准备&#xff1a; 4.2 安装redis&#xff1a; 4.3 master节点修改 Redis 配置文件&#xff1a; 4.4 slave节点…

JAVA面经整理(7)

一)什么是AQS&#xff1f; 1)AQS也被称之为是抽象同步队列&#xff0c;它是JUC包底下的多个组件的底层实现&#xff0c;Lock&#xff0c;CountDownLatch和Semphore底层都使用到了AQS AQS的核心思想就是给予一个等待队列和同步状态来实现的&#xff0c;它的内部使用一个先进先出…

【C语言】循环结构程序设计(第二部分 -- 习题讲解)

前言:昨天我们学习了C语言中循环结构程序设计&#xff0c;并分析了循环结构的特点和实现方法&#xff0c;有了初步编写循环程序的能力&#xff0c;那么今天我们通过一些例子来进一步掌握循环程序的编写和应用。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &am…

提示msvcp140.dll丢失的5个解决方法,msvcp140.dll丢失问题全面分析

在我们的日常生活和工作中&#xff0c;电脑已经成为不可或缺的工具。然而&#xff0c;在使用电脑的过程中&#xff0c;我们经常会遇到各种问题&#xff0c;其中就包括提示 msvcp140.dll 丢失的问题。msvcp140.dll 是 Visual C Redistributable for Visual Studio 2015 的运行时…

动态内存管理<C语言>

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

微信小程序代驾系统源码(含未编译前端,二开无忧) v2.5

简介&#xff1a; 如今有越来越多的人在网上做代驾&#xff0c;打造一个代驾平台&#xff0c;既可以让司机增加一笔额外的收入&#xff0c;也解决了车主酒后不能开发的问题&#xff0c;代驾系统基于微信小程序开发的代驾系统支持一键下单叫代驾&#xff0c;支持代驾人员保证金…

Python的NumPy库(一)基础用法

NumPy库并不是Python的标准库&#xff0c;但其在机器学习、大数据等很多领域有非常广泛的应用&#xff0c;NumPy本身就有比较多的内容&#xff0c;全部的学习可能涉及许多的内容&#xff0c;但我们在这里仅学习常见的使用&#xff0c;这些内容对于我们日常使用NumPy是足够的。 …

2023.10.5 文件操作IO 经典例题

目录 例题一 例题二 例题一 扫描指定目录&#xff0c;并找到名称中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09;&#xff0c;并且后续询问用户是否删除该文件 代码如下&#xff1a; package io;import java.io.File; import java.util.Scanner;//扫描指定目…

RSA攻击:模数分解

目录 一、模数分解总览 1.1直接分解法 1.2费马分解与Pollard_rho分解 1.3公约数分解 1.4其他模数分解 二、实战特训 2.1[黑盾杯 2020]Factor 2.2[GWCTF 2019]babyRSA 2.3[LitCTF 2023]yafu (中级) 2.4[RoarCTF 2019]RSA 2.5[CISCN 2022 西南]rsa 三、总结 一、模数分解总览 …

使用idea 中的rest 将 git 合并部分分支代码到主分支

需求&#xff1a;当要将dev的分支中的部分代码合并到test分支时&#xff0c;又不想把dev的全部代码合并到test分支 例如dev分支已经提交了 demo1到4&#xff0c;到想把demo1-3的代码合并到test分支&#xff0c;demo4暂时不合并 可以使用idea的reset 功能满足以上需求 1首先切…

Seata 源码篇之AT模式启动流程 - 中 - 03

Seata 源码篇之AT模式启动流程 - 中 - 03 数据源代理会话代理锁定查询执行器本地事务提交本地事务回滚 更新执行器删除执行器插入执行器 小节 本系列文章: Seata 源码篇之核心思想 - 01Seata 源码篇之AT模式启动流程 - 上 - 02 数据源代理 当我们的数据源被代理后&#xff0c…