MobileNet网络结构详解

2023-11-13

下图展示了传统卷积与DW卷积的差异,在传统卷积中,每个卷积核的channel与输入特征矩阵的channel相等(每个卷积核都会与输入特征矩阵的每一个维度进行卷积运算)。而在DW卷积中,每个卷积核的channel都是等于1的(每个卷积核只负责输入特征矩阵的一个channel,故卷积核的个数必须等于输入特征矩阵的channel数,从而使得输出特征矩阵的channel数也等于输入特征矩阵的channel数)

如果想改变输出特征矩阵的channel,只需要在DW卷积后接上一个PW卷积即可,如下图所示,其实PW卷积就是普通的卷积而已(只不过卷积核大小为1)。通常DW卷积和PW卷积是放在一起使用的,一起叫做Depthwise Separable Convolution(深度可分卷积)

左侧是ResNet网络中的残差结构,右侧就是MobileNet v2中的倒残差结构。在残差结构中是1x1卷积降维->3x3卷积->1x1卷积升维,在倒残差结构中是1x1卷积升维->3x3DW卷积->1x1卷积降维。(注意倒残差结构中基本使用的都是ReLU6激活函数,但是最后一个1x1的卷积层使用的是线性激活函数)

 输入特征矩阵为h*w*k,经过1*1conv(卷积核个数为tk)后为h*w*tk,【t为一个扩展因子,对应倒残差结构中第一层1*1conv卷积核的扩展倍率】,再经过一个3*3步距为s的DW卷积后为h/s*w/s*tk,再经过1*1conv(卷积核个数为k')后为h/s*w/s*k'

 t为一个扩展因子,对应倒残差结构中第一层1*1conv卷积核的扩展倍率,

c代表输出特征矩阵的channel,

n代表bottlenect(倒残差结构)重复的次数,

s代表每一个block中第一层bottlenect(倒残差结构)所对应的步距,该block中其它层bottlenect所对应的步距都为1,步距指的是DW卷积的步距

model_v2.py

from torch import nn
import torch


def _make_divisible(ch, divisor=8, min_ch=None):
    """
    This function is taken from the original tf repo.
    It ensures that all layers have a channel number that is divisible by 8
    It can be seen here:
    https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
    """
    if min_ch is None:
        min_ch = divisor
    new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor) # 保证ch是divisor的整数倍
    # Make sure that round down does not go down by more than 10%.
    if new_ch < 0.9 * ch:
        new_ch += divisor
    return new_ch


class ConvBNReLU(nn.Sequential): # 定义普通卷积
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): # groups=1为普通卷积,groups=in_channel为depthwise卷积
        padding = (kernel_size - 1) // 2 # kernel_size=3则padding=1;kernel_size=1则padding=0
        super(ConvBNReLU, self).__init__(
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), # 如果要使用BN层,就不用使用偏置了
            nn.BatchNorm2d(out_channel),
            nn.ReLU6(inplace=True)
        )


class InvertedResidual(nn.Module): # 定义倒残差结构
    def __init__(self, in_channel, out_channel, stride, expand_ratio): # expand_ratio为扩展因子,就是表格中的t
        super(InvertedResidual, self).__init__()
        hidden_channel = in_channel * expand_ratio # 第一层卷积层的卷积核的个数
        self.use_shortcut = stride == 1 and in_channel == out_channel # 用于判断在正向传播过程中是否使用shortcut

        layers = []
        if expand_ratio != 1:
            # 倒残差结构的第一层  1x1 pointwise conv
            layers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size=1)) # 扩展因子等于1,这个卷积层可以省略
        layers.extend([     # .extend批量插入很多函数
            # 倒残差结构的第二层  3x3 depthwise conv
            ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel),
            # 倒残差结构的第三层  1x1 pointwise conv(linear) 线性激活函数就是不用添加激活函数(y=x)
            nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channel),
        ])

        self.conv = nn.Sequential(*layers) # 将一系列层结构打包成一个整体

    def forward(self, x): # 定义正向传播过程
        if self.use_shortcut: # 使用shortcut
            return x + self.conv(x)
        else: # 不使用shortcut
            return self.conv(x)


class MobileNetV2(nn.Module): # 定义MobileNetV2结构
    def __init__(self, num_classes=1000, alpha=1.0, round_nearest=8): # alpha为一个超参数,卷积核的倍率
        super(MobileNetV2, self).__init__()
        block = InvertedResidual # 倒残差结构传给block
        input_channel = _make_divisible(32 * alpha, round_nearest) # 将卷积核的个数调整到8的整数倍
        last_channel = _make_divisible(1280 * alpha, round_nearest)

        inverted_residual_setting = [
            # t, c, n, s
            [1, 16, 1, 1],
            [6, 24, 2, 2],
            [6, 32, 3, 2],
            [6, 64, 4, 2],
            [6, 96, 3, 1],
            [6, 160, 3, 2],
            [6, 320, 1, 1],
        ]

        features = []
        # 定义第一层卷积层,输入为RGB三通道,输出为input_channel  conv1 layer
        features.append(ConvBNReLU(3, input_channel, stride=2))
        # 定义一系列bottleneck层  building inverted residual residual blockes
        for t, c, n, s in inverted_residual_setting:
            output_channel = _make_divisible(c * alpha, round_nearest)
            for i in range(n):
                stride = s if i == 0 else 1
                features.append(block(input_channel, output_channel, stride, expand_ratio=t))
                input_channel = output_channel
        # 定义倒数第三层的卷积层  building last several layers
        features.append(ConvBNReLU(input_channel, last_channel, kernel_size=1))
        # combine feature layers
        self.features = nn.Sequential(*features) # 将一系列层结构打包成一个整体
        #-----------------------以上是特征提取部分-------------------------

        # 定义分类器部分(表格中的最后两层)  building classifier
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 自适应的平均池化下采样操作
        self.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(last_channel, num_classes)
        )
        # ------------------------以上是分类器部分-----------------------

        # 权重初始化 weight initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.zeros_(m.bias)

    def forward(self, x): # 前向传播过程
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

train.py

import os
import sys
import json

import torch
import torch.nn as nn
import torch.optim as optim
from matplotlib import pyplot as plt
from torchvision import transforms, datasets
from torchvision.datasets import ImageFolder
from tqdm import tqdm

from model_v2 import MobileNetV2

# 下载预训练权重
import torchvision.models.mobilenet

import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


ROOT_TRAIN = r'D:/cnn/All Classfication/ResNet/data/train'
ROOT_TEST = r'D:/cnn/All Classfication/ResNet/data/val'


def main():
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    data_transform = {
        "train": transforms.Compose([transforms.RandomResizedCrop(224),
                                     transforms.RandomHorizontalFlip(),
                                     transforms.ToTensor(),
                                     transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]), # 这里的标准化参数是官网提供的,不做修改
        "val": transforms.Compose([transforms.Resize(256), # 将原图像长宽比固定,再将其最小边缩放到256
                                   transforms.CenterCrop(224), # 在使用中心裁剪到224 * 224大小
                                   transforms.ToTensor(),
                                   transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])}

    train_dataset = ImageFolder(ROOT_TRAIN, transform=data_transform["train"])  # 加载训练集
    train_num = len(train_dataset)  # 打印训练集有多少张图片
    animal_list = train_dataset.class_to_idx  # 获取类别名称以及对应的索引
    cla_dict = dict((val, key) for key, val in animal_list.items())  # 将上面的键值对位置对调一下

    json_str = json.dumps(cla_dict, indent=4)  # 把类别和对应的索引写入根目录下class_indices.json文件中
    with open('class_indices.json', 'w') as json_file:
        json_file.write(json_str)

    batch_size = 32
    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=batch_size, shuffle=True,
                                               num_workers=0)

    validate_dataset = ImageFolder(ROOT_TEST, transform=data_transform["val"])  # 载入测试集
    val_num = len(validate_dataset)  # 打印测试集有多少张图片
    validate_loader = torch.utils.data.DataLoader(validate_dataset,
                                                  batch_size=16, shuffle=False,
                                                  num_workers=0)
    print("using {} images for training, {} images for validation.".format(train_num, val_num))

    # create model
    net = MobileNetV2(num_classes=2) # 实例化模型,定义类别个数

    # load pretrain weights
    model_weight_path = "./mobilenet_v2.pth"
    assert os.path.exists(model_weight_path), "file {} dose not exist.".format(model_weight_path)
    pre_weights = torch.load(model_weight_path, map_location='cpu') # 通过torch.load载入预训练模型参数

    # delete classifier weights 便利权重字典,去除含classifier的层
    pre_dict = {k: v for k, v in pre_weights.items() if net.state_dict()[k].numel() == v.numel()}
    missing_keys, unexpected_keys = net.load_state_dict(pre_dict, strict=False)

    # freeze features weights 冻结特征提取部分的权重
    for param in net.features.parameters():
        param.requires_grad = False

    net.to(device)

    # define loss function
    loss_function = nn.CrossEntropyLoss()

    # construct an optimizer
    params = [p for p in net.parameters() if p.requires_grad]
    optimizer = optim.Adam(params, lr=0.0001)

    epochs = 10
    best_acc = 0.0
    save_path = './MobileNetV2.pth'
    train_steps = len(train_loader)
    for epoch in range(epochs):
        # train
        net.train()
        running_loss = 0.0
        train_bar = tqdm(train_loader, file=sys.stdout)
        for step, data in enumerate(train_bar):
            images, labels = data
            optimizer.zero_grad()
            logits = net(images.to(device))
            loss = loss_function(logits, labels.to(device))
            loss.backward()
            optimizer.step()

            # print statistics
            running_loss += loss.item()

            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
                                                                     epochs,
                                                                     loss)

        # validate
        net.eval()
        acc = 0.0  # accumulate accurate number / epoch
        with torch.no_grad():
            val_bar = tqdm(validate_loader, file=sys.stdout)
            for val_data in val_bar:
                val_images, val_labels = val_data
                outputs = net(val_images.to(device))
                # loss = loss_function(outputs, test_labels)
                predict_y = torch.max(outputs, dim=1)[1]
                acc += torch.eq(predict_y, val_labels.to(device)).sum().item()

                val_bar.desc = "valid epoch[{}/{}]".format(epoch + 1,
                                                           epochs)
        val_accurate = acc / val_num
        print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f' %
              (epoch + 1, running_loss / train_steps, val_accurate))

        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), save_path)

    print('Finished Training')


if __name__ == '__main__':
    main()

 

reference 

MobileNet(v1、v2)网络详解与模型的搭建_太阳花的小绿豆的博客-CSDN博客

7.1 MobileNet网络详解_哔哩哔哩_bilibili

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MobileNet网络结构详解 的相关文章

  • 按每个元素中出现的数字对字符串列表进行排序[重复]

    这个问题在这里已经有答案了 我有一个脚本 其目的是对不断下载到服务器上的空间数据集文件进行排序和处理 我的列表目前大致如下 list file t00Z wrff02 grib2 file t00Z wrff03 grib2 file t0
  • 使用ideone时如何传入命令行参数?

    我正在使用 ideone 在线解释器 http ideone com http ideone com 来测试一些 C 和 Python 程序 如何指定命令行参数而不是使用 STDIN 输入 看起来你不能 但是快速破解应该做的伎俩 stati
  • 如何在Python + Selenium中获取元素的值

    我在我的 Python 3 6 3 代码中得到了这个 HTML 元素 作为 Selenium网页元素当然 span class ocenaCzastkowa masterTooltip style color 000000 alt 5 sp
  • 删除 tkinter 文本默认绑定

    我正在制作一个简单的 tkinter 文本编辑器 但我想要所有默认绑定文本小部件如果可能的话删除 例如当我按Ctrl i它默认插入一个制表符 我制作了一个事件绑定来打印文本框中有多少行 我将事件绑定设置为Ctrl i以及 当我运行它时 它会
  • Discord.py 斜线命令在 cogs 中不起作用

    我正在构建一个不和谐的机器人 并且想要在 cogs 内使用斜杠命令 但这些命令不显示或工作 这是代码 cog guild ids 858573429787066368 861507832934563851 class Slash comma
  • 无法使用Python请求会话模块登录网站

    我刚刚开始进行网络抓取 对于我的第一个项目 我尝试使用 requests Session 登录 artofproblemsolving com 并访问另一个用户的帐户 这是我的代码 import requests LOGIN URL htt
  • 将列表值转换为 pandas 中的行

    我有数据帧 其中一列具有相同长度的 numpy ndarray 值 df list 0 Out 92 array 0 0 0 0 29273096 0 30691767 0 27531403 我想将这些列表值转换为数据框并从 df iloc
  • 如何在Python中手动对数字列表进行排序?

    规格 Ubuntu 13 04 Python 3 3 1 背景 Python的初学者 遇到了这个 手动排序 问题 我被要求做的事情 让用户输入 3 个数值并将它们存储在 3 个不同的变量中 不使用列表或排序算法 手动将这 3 个数字从小到大
  • 使用 python 从 CSV 创建字典

    我有一个 CSV 格式的文件 其中 A B 和 C 是标题 我如何以Python方式将此CSV转换为以下形式的字典 A 1 B 4 C 7 A 2 B 5 C 8 A 3 B 6 C 9 到目前为止我正在尝试以下代码 import csv
  • 当元组列表中相同项目的值是字符串时,对它们的值求和

    如果我有这样的元组列表 my list books 5 books 10 ink 20 paper 15 paper 20 paper 15 我怎样才能把列表变成这样 books 15 ink 20 paper 50 即添加同一项目的费用
  • 如何在 python 中使用交叉验证执行 GridSearchCV

    我正在执行超参数调整RandomForest如下使用GridSearchCV X np array df features all features y np array df gold standard labels x train x
  • 增强迪基-富勒测试中的 BIC 在 Python 中到底是如何工作的?

    这个问题是关于 statsmodels tsa stattools python 库 adfuller 中的增强迪基 富勒测试实现 原则上 AIC 和 BIC 应该计算一组可用模型的信息标准 并选择最好的模型 信息损失最低的模型 但它们在增
  • Python GTK3 Treeview 向上或向下移动选择

    如何在树视图中向上或向下移动所选内容 我的想法是 我可以使用向上和向下按钮将选择向上移动一行或向下移动一行 我的 Treeview 使用 ListStore 不确定这是否重要 首先 我将使用我熟悉的 C 代码 如果您在将其翻译为 Pytho
  • 为什么我无法杀死 k8s pod 中的 python 进程?

    我试图杀死一个 python 进程 ps aux grep python root 1 12 6 2 1 2234740 1332316 Ssl 20 04 19 36 usr bin python3 batch run py root 4
  • 如何将 pandas DataFrame 转换为 TimeSeries?

    我正在寻找一种将 DataFrame 转换为 TimeSeries 而不拆分索引和值列的方法 有任何想法吗 谢谢 In 20 import pandas as pd In 21 import numpy as np In 22 dates
  • 在Python中随机交错2个数组

    假设我有两个数组 a 1 2 3 4 b 5 6 7 8 9 我想将这两个数组交错为变量 c 注意 a 和 b 不一定具有相同的长度 但我不希望它们以确定性的方式交错 简而言之 仅仅压缩这两个数组是不够的 我不想要 c 1 5 2 6 3
  • 如何将Python包从旧版本安装到新版本?

    我正在使用 python 3 7 最近在 Linux 中安装了 python 3 8 是否有任何 bash 命令或脚本可以获取 3 7 的所有软件包列表并在 3 8 版本中一一安装 我想避免每个包裹都手工完成 注意 我将它们安装在我的系统中
  • Django 中使用外键的抽象基类继承

    我正在尝试在 Django 支持的网站上进行模型继承 以遵守 DRY 我的目标是使用一个名为 BasicCompany 的抽象基类来为三个子类提供通用信息 Butcher Baker CandlestickMaker 它们位于各自的应用程序
  • 关闭正在运行代码的 IPython Notebook

    怎么运行的 我在 IPython Notebook 中运行了一些代码 一些迭代工作 我不小心关闭了正在运行的笔记本的浏览器 但回到 IPython 仪表板 我发现这个特定的笔记本尚未关闭 所以如果我再次打开笔记本 我会在它正在执行的代码前面
  • 如何仅读取 CSV 文件每行的第一列 [重复]

    这个问题在这里已经有答案了 如何在Python中读取CSV文件每行的第一列 我的数据是这样的 1 abc 2 bcd 3 cde 我只需要循环第一列的值 另外 当我在 calc 中打开 csv 文件时 每行中的数据都在同一个单元格中 这正常

随机推荐

  • 统计学常用概念:T检验、F检验、卡方检验、P值、自由度

    常用检验公示表 自由度概念 在统计模型中 自由度指样本中可以自由变动的变量的个数 当有约束条件时 自由度减少 自由度计算公式 自由度 样本个数 样本数据受约束条件的个数 即df n k df自由度 n样本个数 k约束条件个数 例 一组数据
  • QT发布软件

    Qt Creator 完成对release版本编译完成之后 就需要将exe文件发布出来 单纯的只拷贝exe文件是不能运行的 exe的运行需要依赖很多的Qt库 1 生成可以执行的exe文件 这里需要将exe文档放在一个单独创建的test文件夹
  • dos命令大全

    DOS命令 是DOS操作系统的命令 是一种面向磁盘的操作命令 主要包括目录操作类命令 磁盘操作类命令 文件操作类命令和其它命令 DOS命令不区分大小写 比如C盘的Program Files 在dos命令中完全可以用 progra 1 代替
  • log4c cmakelist.txt config.h

    cmake minimum required VERSION 2 8 12 project log4c add definitions DHAVE CONFIG H add definitions D CRT SECURE NO WARNI
  • 【pybind11入门】Windows下为Python创建C++扩展

    在Windows下使用pybind11为python添加C 扩展 这篇文章记录下整个安装 测试 使用流程 主要内容 1 安装编译工具 2 测试pybind11编译是否正常 3 使用pybind11创建C 扩展 4 在python中调用 1
  • 迈拓 kvm 切换热键

    4台电脑之间切换的时候 可以按KVM上面的开关 也可以用热键切换 热键的切换方法如下 1 切换到第一台电脑 Scroll Lock 1 第1台电脑 2 切换到第二台电脑 Scroll Lock 2 第2台电脑 3 切换到第三台电脑 Scro
  • JLink和ST-Link接口引脚介绍

    STM32F1系列 STM8S系列 PY32F003系列都用过好久了 但是对JLink和ST Link下载器认识 还是很肤浅的 有时候 需要自己接线 却不知道引脚定义 特整理如下 1 ST Link ST Link适合对象是STM8和STM
  • Markdown学习笔记

    这个是源代码 由于无法在markdown下直接显示 所以这里采用富文本格式 Markdown学习笔记 你好 2020 7 28 段落 间隔一或多行行表示一个回车 两者没有区别 这是没有产生的效果 天王盖地虎 宝塔镇河妖 这是有回车的效果 天
  • 若依框架修改Vue请求超时时间

    ruoyi ui gt src gt utils gt request js 修改request js下的 timeout 10000 单位 毫秒
  • 软件设计师笔记 2021年下半年

    软件设计师笔记 1 第一章 计算机知识 控制器包含 地址寄存器 S single M multiple I 指令流 Data 数据流 2 第二章
  • 【状态估计】基于UKF、AUKF的电力系统负荷存在突变时的三相状态估计研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码及数据 1 概述 基于UKF和AUKF的电力系统负荷存在突
  • ARM发布Cortex-X1,是为了向苹果自研A系列处理器发起冲击吗?

    对于Arm来说 2019年是伟大的一年 这一年ARM的Cortex内核依然是手机CPU领域的佼佼者 特别是Cortex A77 红极一时的高通骁龙865处理器采用的就是Cortex A77 据说采用骁龙865处理器的手机有70款之多 其中就
  • c语言文件处理中ab,C语言文件处理中wt是什么操作方式?

    匿名用户 1级 2013 04 25 回答 最常用的文件使用方式及其含义如下 1 r 为读而打开文本文件 不存在则出错 2 rb 为读而打开二进制文件 3 w 为写而打开文本文件 若不存在则新建 反之 则从文件起始位置写 原内容将被覆盖 4
  • 【中间件】Redis如何解决BigKey

    BigKey 的弊端 BigKey 需要解决 根源就在于 BigKey 会带来的问题 占用内存 因为 Redis 数据结构的底层数据结构 大 Key 会占用更多的内存空间 造成更大的内存消耗 单线程模型 因为 Redis 的通信依赖于 So
  • 一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系

    我们知道 不同肤色的人外貌差别很大 而双胞胎的辨识很难 有意思的是Web服务器 Web容器 Web应用程序服务器 反向代理有点像四胞胎 在网络上经常一起出现 本文将带读者对这四个相似概念如何区分 Web服务器概念与基本原理 Web服务器的历
  • CSS基础之CSS文本属性

    文章目录 前言 1 color 2 text align 3 font size 4 text decoration 5 text indent 6 line height 7 文本属性总结 前言 CSS 文本属性可以设置文本的 外观 比如
  • 从同源政策到跨域解决方法

    一 同源政策 同源政策的目的 是为了保证用户信息的安全 防止恶意的网站窃取数据 所谓同源指的是协议 域名 端口相同 否则就会产生跨域问题 二 跨域 跨域问题主要分为三类 1 Cookie LocalStorage 和 IndexDB 无法读
  • 记一次jQuery EasyUI使用-Easyui combobox的使用方法

    开局附上最最最有用的官方文档 划重点 easyui使用手册 进入正题 现象 有这样一段代码 浏览器请求getSystemSignList方法有返回数据并且严格符合easyui的应答规范 一个json格式的list对象 tr td class
  • 大模型讲习班丨第四范式黄世宇:强化学习的发展历程与基于人类反馈的强化学习...

    人工智能研究与应用范式正经历一场剧变 越来越多的顶级团队和杰出人才纷纷加入这一变革浪潮 作为AI大模型科研先锋 智源研究院携手一批卓越的学者与工程师 致力于将尖端技术与经验传授给有潜力的学习者 通过高效的学习方式 让更多人能迅速融入这一重要
  • MobileNet网络结构详解

    下图展示了传统卷积与DW卷积的差异 在传统卷积中 每个卷积核的channel与输入特征矩阵的channel相等 每个卷积核都会与输入特征矩阵的每一个维度进行卷积运算 而在DW卷积中 每个卷积核的channel都是等于1的 每个卷积核只负责输