卷积神经网络模型设计:深度、宽度、注意力、轻量化

本文详细介绍了卷积神经网络的发展,包括AlexNet、VGG、ResNet等经典模型,探讨了深度、宽度、残差连接等设计思路。同时,提到了数据预处理、增强、平衡和标准化的方法,以及如何利用预训练模型进行迁移学习。此外,还讨论了注意力模型如SENet和轻量化模型如MobileNetV1、ShuffleNet等,展示了在实际项目中的应用,如图像分类和安卓端部署的实时图片检测app。
摘要由CSDN通过智能技术生成

深度设计

经典网络AlexNet

AlexNet是由Alex Krizhevsky等人在2012年提出的卷积神经网络模型,它在ImageNet图像分类挑战赛中取得了突破性的成果。以下是AlexNet的网络结构和各个层的作用:

  • 输入层(Input Layer):接收输入图像数据。

  • 卷积层1(Convolutional Layer 1):使用96个11x11的卷积核(2份48个11x11区块),步长为4,进行特征提取。应用ReLU激活函数。

    输出特征图像大小:(227-11)/4+1 = 55,特征图大小5555,数量96个,即 5555*96

  • 池化层1(Pooling Layer 1):使用3x3的最大池化操作,步长为2,进行下采样。

    输出特征图像大小:(55-3)/2+1 = 27,特征图大小2727,数量96个,即 2727*96

  • 卷积层2(Convolutional Layer 2):使用256个5x5的卷积核,步长为1,进行特征提取。应用ReLU激活函数。

    扩展 4 个像素:27 + 4 = 31

    输出特征图像大小:(31-5)/1+1=27,即 2727256

  • 池化层2(Pooling Layer 2):使用3x3的最大池化操作,步长为2,进行下采样。

    输出特征图像大小:(27-3)/2+1=13,即 1313256

    卷积+池化模式 --> 卷积+卷积+卷积+池化模式:

  • 卷积层3(Convolutional Layer 3):使用384个3x3的卷积核,步长为1,进行特征提取。应用ReLU激活函数。

    扩展 2 个像素:13 + 2 = 15

    输出特征图像大小:(15-3)/1+1=13,即 1313384

  • 卷积层4(Convolutional Layer 4):使用384个3x3的卷积核,步长为1,进行特征提取。应用ReLU激活函数。

    扩展 2 个像素:13 + 2 = 15

    输出特征图像大小:(15-3)/1+1=13,即 1313384

  • 卷积层5(Convolutional Layer 5):使用256个3x3的卷积核,步长为1,进行特征提取。应用ReLU激活函数。

    扩展 2 个像素:13 + 2 = 15

    输出特征图像大小:(15-3)/1+1=13,即 1313384

  • 池化层3(Pooling Layer 3):使用3x3的最大池化操作,步长为2,进行下采样。

    输出特征图像大小:(13-3)/2+1=6,即 66256

  • 全连接层1(Fully Connected Layer 1):包含4096个神经元,对提取的特征进行分类。

  • Dropout层1:在全连接层1之后应用Dropout操作,以减少过拟合。

  • 全连接层2(Fully Connected Layer 2):包含4096个神经元,进一步对特征进行分类。

  • Dropout层2:在全连接层2之后应用Dropout操作。

  • 输出层(Output Layer):根据分类任务的类别数,使用softmax函数将特征映射到相应的概率分布。

AlexNet通过多个卷积层和池化层进行特征提取,然后通过全连接层进行分类。它采用了ReLU激活函数和Dropout操作,提高了模型的非线性表达能力和泛化能力。

卷积神经网络是在普通神经网络上的改进,改进思路是模仿人找东西的思路。

普通神经网络去找人脸,会把所有像素放一起找。

人在找东西的时候,潜意识通常会浮现人脸的特征、样子,再一个一个局部的去找(看的是一片一片的像素群,不是单个像素点或者整张图)。

引入几个特征提取层。

第一层把图像分成 n 个区块(避免结构被区块拆散,相邻区块要有很大的重叠),一个区块一个区块的找,从像素点中识别各种方向的线条/斑点/颜色变化的小结构

第二层在前一层基础上识别出更大更复杂的结构,如圆形、椭圆形等各种图形结构

第三层在前一层基础上识别出更大更复杂的结构,如眼、嘴等局部器官

第四层在前一层基础上识别出更大更复杂的结构,如人、车等

实现方式:卷积

大多数卷积神经网络会随着深度增加而减小卷积核的尺寸随着网络的深度增加,逐渐减小卷积核的尺寸。这是因为较小的卷积核可以更好地捕捉局部细节和局部特征,并通过参数共享减少模型的参数数量。

在一些特定的应用和网络结构中,也可以使用较大的卷积核来提取特征。例如,对于一些高分辨率图像或需要较大感受野的任务,较大的卷积核可能更适合提取多特征、全局、更宏观的特征、长程依赖关系。

在一些特殊的网络架构中,可能会使用不同尺寸的卷积核来同时捕捉不同尺度的特征。例如,Inception模块中的卷积层采用了不同大小的卷积核,并将它们的输出进行拼接,以获取多尺度的特征表示。

 

池化层(Pooling Layer)是卷积神经网络中的一种常见层,其作用是对特征图进行下采样,减少特征图的空间尺寸,并保留重要的特征信息。池化层通常紧跟在卷积层之后,可以用于多种计算机视觉任务。

池化层的主要作用包括:

降维和减少计算量:池化层通过将特征图的空间尺寸缩小,减少了后续层的参数数量和计算量。

特征不变性:池化操作具有一定的平移不变性,即不论目标在图像中的位置如何,经过池化后仍能保留重要的特征信息。

特征提取和压缩:池化层可以通过选择最显著的特征来提取图像的主要信息,丢弃次要的细节和噪声。

平移不变性:池化层的下采样操作使得特征图的尺寸减小,而对于输入图像的平移、旋转和缩放等变换操作,池化层通常保持不变。

常见的池化操作包括最大池化(Max Pooling)和平均池化(Average Pooling)。

最大池化从每个池化窗口中选择最大的值作为输出,用于提取主要特征。平均池化计算每个池化窗口中的平均值,用于获取特征的整体信息。

 

全局层对特征图进行降维,从而得到整体特征的表示。

全局层的主要作用是在保留重要特征的同时减少参数量和计算量。它通过对整个特征图进行全局的汇聚,捕捉到图像中的全局信息,并得到一个通道数不变的特征向量。这种操作有助于提取图像的整体特征,并减少后续全连接层的参数数量。

全局层常用于卷积神经网络中,用于将特征图进行全局的汇聚和降维。GoogleNet/InceptionNet、ResNet和DenseNet等模型都采用了全局层来提取图像的整体特征。

 

全连接层(Fully Connected Layer)也被称为普通神经网络层。

全连接层用于将特征映射到最终的输出结果。

Dropout层,以一定的概率(通常为0.5)随机将部分神经元的输出设置为0,意味着它们在该层的前向传播和反向传播过程中被丢弃。

通过随机丢弃神经元,Dropout层迫使网络在训练过程中学习到多个互不相同的子模型,避免对单个神经元的依赖。

VGG 模型

通过不断增加卷积层和池化层的重复次数来增加网络的深度。

加深网络层数,可以提升性能。

VGG模型的一个典型实现是VGG16,其中16表示网络的层数(包括卷积层和全连接层)。

VGG16模型共包含13个卷积层和3个全连接层,其中卷积层都使用3x3的卷积核(卷积核变小),池化层使用2x2的最大池化操作(变小且偶数)。

通过小卷积核代替大卷积核,减少运算量。

通过使用两个3x3的卷积核来代替一个5x5的卷积核,可以带来以下优势:

增加网络的非线性能力:两个3x3的卷积核可以引入更多的非线性变换,从而增加网络的表达能力。每个3x3的卷积核在进行卷积操作时,可以通过激活函数引入非线性变换,增加模型对输入数据的拟合能力。

减少参数数量:两个3x3的卷积核的参数数量要少于一个5x5的卷积核。对于一个具有C个输入通道和K个输出通道的卷积层,两个3x3的卷积核总共需要2CK个参数,而一个5x5的卷积核需要C*K个参数。通过减少参数数量,可以减少模型的计算和存储开销。

增加感受野的非线性响应:两个3x3的卷积核可以增加网络的感受野(receptive field)大小。通过堆叠多个3x3的卷积层,可以逐步增加卷积核对输入图像的感受野,从而提高对输入图像更大范围的特征提取能力。

并不是所有情况下都适合使用两个3x3的卷积核代替一个5x5的卷积核。这种技巧主要适用于卷积层之间的连续堆叠,以增加非线性能力和感受野的响应。在具体的网络设计中,需要根据任务需求和性能要求来确定是否使用这种技巧。

VGG结构:

  • 输入层(Input Layer):接收输入图像数据。
  • 卷积层(Convolutional Layer):由多个卷积层组成,每个卷积层都使用3x3的卷积核,并进行特征提取。通常使用ReLU激活函数。
  • 池化层(Pooling Layer):使用2x2的最大池化操作,进行下采样。
  • 全连接层(Fully Connected Layer):将特征映射转换为最终的分类结果。通常包含多个全连接层和ReLU激活函数。
  • 输出层(Output Layer):根据分类任务的类别数,使用softmax函数将特征映射到相应的概率分布。

ResNet

虽然加深网络层数能提高模型性能,但模型变深,错误率也会增长。

这不是过拟合引起的,而是梯度爆炸和梯度消失,导致越深的神经网络越难训练。

梯度爆炸:层数越多,激活值可能越大,最终无穷大。

梯度消失:层数越多,激活值可能越小,最终都是0。

一般的神经网络 激活值 a3 > a2 > a1,就有可能造成梯度爆炸,反之梯度消失。

为防止这种情况,我们让 a3 == a1,使得 a1 的值跳过2层,直接给a3。

ResNet的思路是,将前面的激活值跳过中间的网络层而直接传递到更后面的网络层,新增一条跳跃连接的路,不用计算直达后2层。

如果输入和输出特征非常相似,那么它们的差异就会很小,残差就接近于零。

这意味着网络只需学习到一个非常小的残差,而不是学习整个输入特征。

能很大程度上减缓梯度爆炸、消失,从原来19层提升到152层,都不会影响性能,且层数越多性能越好。

池化层减少矩阵的宽、高,1*1卷积减少矩阵的深度。

ResNet结构:

  • 输入层(Input Layer):接收输入图像数据。
  • 卷积层(Convolutional Layer):一系列堆叠的卷积层用于特征提取。通常使用3x3的卷积核,可以设置不同的输出通道数。
  • 残差块(Residual Block):每个残差块由两个或更多卷积层组成,其中包含跳跃连接(Skip Connection)。跳跃连接是将输入特征直接添加到块内的输出特征中。
  • 全局平均池化层(Global Average Pooling Layer):将最后一个残差块的输出特征进行全局平均池化,将特征图的空间尺寸降为1x1。
  • 全连接层(Fully Connected Layer):对全局平均池化层的输出进行分类或回归预测。
  • 输出层(Output Layer):根据任务的类别数,使用softmax函数将特征映射到相应的概率分布。

ResNet的关键特点是残差连接,这种连接方式允许梯度更快地在网络中传播,避免梯度消失和梯度爆炸的问题。通过残差连接,ResNet可以训练非常深的神经网络,获得更好的特征表示和更高的性能。

在实践中,ResNet的主要变种有ResNet-18、ResNet-34、ResNet-50、ResNet-101和ResNet-152等,它们的网络深度和参数数量不同,适用于不同规模和复杂度的任务。

项目:ResNet 垃圾分类

基于Pytorch的ResNet垃圾图片分类实战.

数据样本分析

'''
遍历给定数据集根路径下的所有图像文件,获取每张图像的宽度和高度,并保存在width_list和height_list列表中。

然后,使用matplotlib.pyplot库绘制散点图,横轴表示图像的宽度,纵轴表示图像的高度,每个点代表一张图像的分辨率。

通过绘制图像分辨率散点图,我们可以直观地了解数据集中图像的分辨率分布情况,帮助我们了解数据集的特征和结构。
'''
def plot_resolution(dataset_root_path):    # 绘制数据样本中图像分辨率的散点图
    img_size_list = []                     # 承接所有图片的长宽数据
    for root, dirs, files in os.walk(dataset_root_path):  # 通过os.walk()遍历给定的数据集根路径下的所有文件
        for file_i in files:                              # 获取每个文件的完整路径
            file_i_full_path = os.path.join(root, file_i)
            img_i = Image.open(file_i_full_path)          # 使用Image.open()打开图像文件
            img_i_size = img_i.size                       # 获取单张图像的长宽
            img_size_list.append(img_i_size)              # 作为元组(宽度, 高度)添加到img_size_list列表中

    print(img_size_list)    # 打印所有图像的尺寸

    width_list = [img_size_list[i][0] for i in range(len(img_size_list))]  # 从img_size_list中提取所有图像的宽度
    height_list = [img_size_list[i][1] for i in range(len(img_size_list))] # 从img_size_list中提取所有图像的高度

    plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置中文字体
    plt.rcParams["font.size"] = 8
    plt.rcParams["axes.unicode_minus"] = False    # 该语句解决图像中的“-”负号的乱码问题

    plt.scatter(width_list, height_list, s=1)
    plt.xlabel("宽")
    plt.ylabel("高")
    plt.title("图像宽高分布")
    plt.show()


'''
统计数据样本中每个类别的数量,并绘制数据分布图,通过柱状图展示各个类别的数据量,并用红色线表示平均值。

通过该图可以直观地了解数据集中不同类别的数据分布情况。
'''
def plot_bar(dataset_root_path):
    file_name_list = []     # file_name_list是一个列表,用于存储数据集中的文件夹名称。
    file_num_list = []      # file_num_list是一个列表,用于存储每个文件夹中的文件数量。
    for root, dirs, files in os.walk(dataset_root_path):
        if len(dirs) != 0:
            for dir_i in dirs:  
                file_name_list.append(dir_i)  # 将文件夹名称添加到file_name_list列表中。
        file_num_list.append(len(files))   # 将每个文件夹中的文件数量添加到file_num_list列表中。

    file_num_list = file_num_list[1:]  # 从file_num_list中剔除第一个元素(根目录下的文件数量)
    mean = np.mean(file_num_list)  # 求均值,并把均值以横线形式显示出来
    print("mean = ", mean)

    bar_positions = np.arange(len(file_name_list))

    fig, ax = plt.subplots()  # 定义画的区间和子画
    ax.bar(bar_positions, file_num_list, 0.5)  # 画柱图,参数:柱间的距离,柱的值,柱的宽度

    ax.plot(bar_positions, [mean for i in bar_positions], color="red")  # 显示平均值

    plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置中文字体
    plt.rcParams["font.size"] = 8
    plt.rcParams["axes.unicode_minus"] = False  # 该语句解决图像中的“-”负号的乱码问题

    ax.set_xticks(bar_positions)  # 设置x轴的刻度
    ax.set_xticklabels(file_name_list, rotation=90)  # 设置x轴的标签
    ax.set_ylabel("类别数量")
    ax.set_title("数据分布图")
    plt.show()

根据数据集长宽,选出合适的

from PIL import Image
import os

dataset_root_path = "dataset"

min = 200   # 短边
max = 2000  # 长边
ratio = 0.5 # 短边 / 长边

delete_list = [] # 用于存储不满足要求的图像路径
for root,dirs,files in os.walk(dataset_root_path):
    for file_i in files:
        file_i_full_path = os.path.join(root, file_i)
        img_i = Image.open(file_i_full_path)
        img_i_size = img_i.size  # 获取单张图像的长宽

        # 删除单边过短的图片
        if img_i_size[0] < min or img_i_size[1] < min:
            print(file_i_full_path, " 不满足要求")
            delete_list.append(file_i_full_path)

        # 删除宽高比例不当的图片
        long = img_i_size[0] if img_i_size[0] > img_i_size[1] else img_i_size[1]
        short = img_i_size[0] if img_i_size[0] < img_i_size[1] else img_i_size[1]

        if short / long < ratio:
            print(file_i_full_path, " 不满足要求",img_i_size[0],img_i_size[1])
            delete_list.append(file_i_full_path)

for file_i in delete_list:
    try:
        print("正在删除",file_i)
        os.remove(file_i)
    except:
        pass

数据增强

import os
import cv2
import numpy as np

# 水平翻转
def Horizontal(image):
    return cv2.flip(image,1,dst=None) #水平镜像

# 垂直翻转
def Vertical(image):
    return cv2.flip(image,0,dst=None) #垂直镜像

if __name__ == '__main__':
    from_root = r"dataset"
    save_root = r"enhance_dataset"

    threshold = 200

    for a,b,c in os.walk(from_root):
        for file_i in c:
            file_i_path = os.path.join(a,file_i)

            split = os.path.split(file_i_path)
            dir_loc = os.path.split(split[0])[1]
            save_path = os.path.join(save_root,dir_loc)

            print(file_i_path)
            print(save_path)

            if os.path.isdir(save_path) == False:
                os.makedirs(save_path)

            img_i = cv2.imdecode(np.fromfile(file_i_path, dtype=np.uint8),-1)   # 读取图片

            cv2.imencode('.jpg', img_i)[1].tofile(os.path.join(save_path, file_i[:-5] + "_original.jpg"))    # 保存图片

            if len(c) < threshold:   # 如果c列表(文件列表)的长度小于阈值threshold,则进行数据增强。
                img_horizontal = Horizontal(img_i)
                cv2.imencode('.jpg', img_horizontal)[1].tofile(os.path.join(save_path, file_i[:-5] + "_horizontal.jpg"))    # 保存图片

                img_vertical = Vertical(img_i)
                cv2.imencode('.jpg', img_vertical)[1].tofile(os.path.join(save_path, file_i[:-5] + "_vertical.jpg")) # 保存图片
            else:
                pass

数据集平衡处理

import os
import random

img_root  = r"enhance_dataset"
threshold = 300

for a,b,c in os.walk(img_root):       # 使用os.walk()遍历给定的img_root目录及其子目录下的文件和文件夹
    if len(c) > threshold:            # 如果文件数量超过阈值,则进行文件删除操作。
        delete_list = []
        for file_i in c:
            file_i_full_path = os.path.join(a,file_i)
            delete_list.append(file_i_full_path)             # 将文件路径添加到delete_list中

        random.shuffle(delete_list)    # 使用random.shuffle()函数对delete_list中的文件路径进行随机排序,以打乱文件顺序。

        print(delete_list)
        delete_list = delete_list[threshold:]   # 从delete_list中根据阈值threshold选择需要保留的文件路径。
        for file_delete_i in delete_list:
            os.remove(file_delete_i)
            print("将会删除",file_delete_i)

图像均值和方差

from torchvision.datasets import ImageFolder
import torch
from torchvision import transforms as T
from tqdm import tqdm

transform = T.Compose([           # 定义了一个数据转换的组合transform,包含随机裁剪和转换为张量的操作。
     T.RandomResizedCrop(224),
     T.ToTensor(),
])

def getStat(train_data):          # 计算数据集的均值和标准差
    train_loader = torch.utils.data.DataLoader(
        train_data, batch_size=1, shuffle=False, num_workers=0, pin_memory=True)

    mean = torch.zeros(3)
    std = torch.zeros(3)
    for X, _ in tqdm(train_loader):
        for d in range(3):            # 使用循环遍历train_loader,对每个图像计算各个通道的均值和标准差。
            mean[d] += X[:, d, :, :].mean() # N, C, H ,W
            std[d] += X[:, d, :, :].std()
    mean.div_(len(train_data))
    std.div_(len(train_data))
    return list(mean.numpy()), list(std.numpy())


if __name__ == '__main__':
    train_dataset = ImageFolder(root=r'enhance_dataset', transform=transform)
    print(getStat(train_dataset))

生成数据集

import os
import random

train_ratio = 0.9
test_ratio = 1-train_ratio

rootdata = r"enhance_dataset"

train_list, test_list = [],[]

class_flag = -1
for a,b,c in os.walk(rootdata):
    for i in range(0, int(len(c)*train_ratio)):    # 对于前len(c)*train_ratio个文件,构建训练数据的路径train_data,并添加到train_list中。
        train_data = os.path.join(a, c[i])+'\t'+str(class_flag)+'\n'
        train_list.append(train_data)

    for i in range(int(len(c) * train_ratio), len(c)):   # 对于后len(c)*train_ratio个文件,构建测试数据的路径test_data,并添加到test_list中。
        test_data = os.path.join(a, c[i]) + '\t' + str(class_flag)+'\n'
        test_list.append(test_data)

    class_flag += 1   # class_flag变量用于标记不同类别,每遍历一个子目录,增加class_flag的值。

random.shuffle(train_list)  # 使用random.shuffle()函数对train_list和test_list中的数据进行随机打乱,以增加数据的随机性。
random.shuffle(test_list)

with open('train.txt','w',encoding='UTF-8') as f:
    for train_img in train_list:      # 循环遍历train_list中的数据,将其写入文件。
        f.write(str(train_img))

with open('test.txt','w',encoding='UTF-8') as f:
    for test_img in test_list:
        f.write(test_img)

训练 Resnet18 模型

import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData,WriteData

from torchvision.models import resnet18

def train(dataloader, model, loss_fn, optimizer,device):   # 训练函数train。dataloader是数据加载器,model是模型,loss_fn是损失函数,optimizer是优化器,device是设备。
    size = len(dataloader.dataset)   # size是数据集的大小
    avg_loss = 0    # avg_loss用于累积平均损失。
    for batch, (X, y) in enumerate(dataloader):# 使用enumerate(dataloader)遍历数据加载器,获得每个batch的X(图片数据),y(图片真实标签)。
        X, y = X.to(device), y.to(device)   # 将数据存到显卡
        pred = model(X)   # 得到预测的结果pred
        loss = loss_fn(pred, y)
        avg_loss += loss  # 计算损失并累加到avg_loss中
        
        # 反向传播,更新模型参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 每训练10次,输出一次损失和已经处理的样本数量。
        if batch % 10 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

    # 当一个epoch完了后返回平均 loss
    avg_loss /= size
    avg_loss = avg_loss.detach().cpu().numpy()  # 将avg_loss除以数据加载器的长度,得到平均损失,并返回该值。
    return avg_loss


def validate(dataloader, model, loss_fn, device):    # 验证函数validate。dataloader是数据加载器,model是模型,loss_fn是损失函数,device是设备。
    size = len(dataloader.dataset)
    model.eval()   # 将模型转为验证模式
    test_loss, correct = 0, 0   # 初始化test_loss 和 correct。用于累积测试误差和正确预测的个数。
    with torch.no_grad():       # 在验证过程中,不需要更新模型参数,所以使用torch.no_grad()上下文管理器,将梯度计算关闭。
        for X, y in dataloader: # 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
            X, y = X.to(device), y.to(device)  # 将数据转到GPU
            pred = model(X)     # 将输入数据传入模型,得到预测结果pred。
            test_loss += loss_fn(pred, y).item()   # 计算预测值pred和真实值y的差距
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()   # 统计预测正确的个数(针对分类)
    test_loss /= size  # 将累加的test_loss和correct除以数据集的大小,得到平均测试误差和准确率。
    correct /= size
    print(f"correct = {correct}, Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
    return correct, test_loss   # 返回准确率和平均测试误差。


if __name__=='__main__':
    batch_size = 128

    # 给训练集和测试集分别创建一个数据集加载器
    train_data = LoadData("train.txt", True)
    valid_data = LoadData("test.txt", False)

    train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True)
    valid_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)

    # 根据是否可用的GPU,选择设备进行计算。
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")

    # 创建ResNet-18模型,并将其移动到设备上。
    model = resnet18(num_classes=55)
    model = model.to(device)

    # 定义损失函数,计算相差多少,交叉熵,
    loss_fn = nn.CrossEntropyLoss()

    # 定义优化器,用来训练时候优化模型参数,随机梯度下降法
    learning_rate = 1e-3
    optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

	# 设置训练的总轮数、初始最小损失和保存路径。
    epochs = 50
    loss_ = 10
    save_root = "output/"

    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        time_start = time.time()
        avg_loss = train(train_dataloader, model, loss_fn, optimizer, device)
        time_end = time.time()
        print(f"train time: {(time_end - time_start)}")
    
        val_accuracy, val_loss = validate(valid_dataloader, model,loss_fn, device)
        
        # 将训练过程中的损失和准确率写入文件。
        WriteData(save_root + "resnet18_no_pretrain.txt",
                  "epoch", t,
                  "train_loss", avg_loss,
                  "val_loss", val_loss,
                  "val_accuracy", val_accuracy)
        if t % 5 == 0:     # 每隔一定轮次,保存模型参数到文件中
            torch.save(model.state_dict(), save_root + "resnet18_no_pretrain_epoch" + str(t) + "_loss_" + str(avg_loss) + ".pth")

        torch.save(model.state_dict(), save_root + "resnet18_no_pretrain_last.pth")

        if avg_loss < loss_:    # 最后,保存最佳模型和最后一个模型到文件中。
            loss_ = avg_loss
            torch.save(model.state_dict(), save_root + "resnet18_no_pretrain_best.pth")

单图测试

import torch
from torchvision.models import resnet18
from PIL import Image
import torchvision.transforms as transforms
import os

transform_BZ= transforms.Normalize(
    mean=[0.46402064, 0.45047238, 0.37801373],  # 取决于数据集
    std=[0.2007732, 0.196271, 0.19854763]
)

def padding_black(img,img_size = 512):  # 用于将图像进行黑色填充,使其达到指定的大小。函数的输入参数为图像对象img和目标尺寸img_size
    w, h = img.size                     # 获取图像的宽度w和高度h
    scale = img_size / max(w, h)        # 计算缩放比例scale,使图像的最大边缩放到目标尺寸img_size
    img_fg = img.resize([int(x) for x in [w * scale, h * scale]])  # 使用resize()方法将图像按照缩放比例进行调整
    size_fg = img_fg.size               # 获取调整后的图像大小size_fg
    size_bg = img_size                  # 创建一个大小为img_size的全黑图像img_bg
    img_bg = Image.new("RGB", (size_bg, size_bg))
    img_bg.paste(img_fg, ((size_bg - size_fg[0]) // 2,  # 使用paste()方法将调整后的图像粘贴到全黑图像中心位置
                          (size_bg - size_fg[1]) // 2))
    img = img_bg                        # 将粘贴后的图像赋值给img     
    return img

if __name__=='__main__':
    img_path = r'enhance_dataset/可回收物_鼠标/img_鼠标_1_original.jpg'
    val_tf = transforms.Compose([  # 简单把图片压缩了变成Tensor模式
        transforms.Resize(512),
        transforms.ToTensor(),
        transform_BZ  # 标准化操作
    ])

    # 如果显卡可用,则用显卡进行训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")

    finetune_net = resnet18(num_classes=55).to(device)  # 创建一个finetune_net模型,使用resnet18作为基础网络,输出类别数为55,并将模型移动到设备上。

    state_dict = torch.load(r"output/resnet18_e_1e-3_best.pth")
    finetune_net.load_state_dict(state_dict)
    finetune_net.eval()        # 加载预训练模型的参数,并将其应用到finetune_net模型中
    with torch.no_grad():
        img = Image.open(img_path)  # 打开图片
        img = img.convert('RGB')  # 转换为RGB 格式
        img = padding_black(img) # 进行黑色填充和其他预处理操作
        img = val_tf(img)
        img_tensor = torch.unsqueeze(img, 0)    # N,C,H,W, ; C,H,W。将图像转换为张量并添加一个维度,以适应模型的输入要求
        img_tensor = img_tensor.to(device)
        result = finetune_net(img_tensor)  # 通过模型进行推理,得到预测结果
        id = result.argmax(1).item()    # 提取预测结果中的最大值所对应的索引,作为预测的类别标签

        file_list=[]
        for a,b,c in os.walk("dataset"):  # 遍历数据集文件夹,找到对应的类别标签,并打印预测结果
            if len(b) != 0:
                file_list = b
                print("预测结果为:",file_list[id])

迁移学习

'''
加载pytorch自带的模型,从头训练自己的数据
'''
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData,WriteData
from torchvision.models import resnet18, resnet34,resnet50, resnet101, resnet152 ,mobilenet_v2   # ResNet系列

def train(dataloader, model, loss_fn, optimizer,device):
    size = len(dataloader.dataset)
    avg_loss = 0
    # 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。
    for batch, (X, y) in enumerate(dataloader):#固定格式:batch:第几批数据,不是批次大小,(X,y):数值用括号
        # 将数据存到显卡
        X, y = X.to(device), y.to(device)
        # 得到预测的结果pred
        pred = model(X)
        loss = loss_fn(pred, y)
        avg_loss += loss
        # 反向传播,更新模型参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 每训练10次,输出一次当前信息
        if batch % 10 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

    # 当一个epoch完了后返回平均 loss
    avg_loss /= size
    avg_loss = avg_loss.detach().cpu().numpy()
    return avg_loss

def validate(dataloader, model, loss_fn, device):
    size = len(dataloader.dataset)
    # 将模型转为验证模式
    model.eval()
    # 初始化test_loss 和 correct, 用来统计每次的误差
    test_loss, correct = 0, 0
    # 测试时模型参数不用更新,所以no_gard()
    # 非训练, 推理期用到
    with torch.no_grad():
        # 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
        for X, y in dataloader:
            # 将数据转到GPU
            X, y = X.to(device), y.to(device)
            # 将图片传入到模型当中就,得到预测的值pred
            pred = model(X)
            # 计算预测值pred和真实值y的差距
            test_loss += loss_fn(pred, y).item()
            # 统计预测正确的个数(针对分类)
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= size
    correct /= size
    print(f"correct = {correct}, Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
    return correct, test_loss

if __name__=='__main__':
    batch_size = 32

    # 给训练集和测试集分别创建一个数据集加载器
    train_data = LoadData("train.txt", True)
    valid_data = LoadData("test.txt", False)

    train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True)
    valid_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)

    # 如果显卡可用,则用显卡进行训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")

    finetune_net = resnet18(pretrained=True)   # 使用预训练的ResNet-18模型
    finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 55)  # 通过修改全连接层finetune_net.fc的输出特征数,将模型调整为输出55个类别
    nn.init.xavier_normal_(finetune_net.fc.weight)  # 使用nn.init.xavier_normal_()方法对全连接层的权重进行初始化

    # 创建两个参数列表parms_1x和parms_10x,分别存储除全连接层以外的模型参数和全连接层的参数
    parms_1x = [value for name, value in finetune_net.named_parameters()
                if name not in ["fc.weight", "fc.bias"]]
    # 最后一层10倍学习率
    parms_10x = [value for name, value in finetune_net.named_parameters()
                 if name in ["fc.weight", "fc.bias"]]

    finetune_net = finetune_net.to(device)
    loss_fn = nn.CrossEntropyLoss()  # 定义损失函数loss_fn为交叉熵损失

    # 定义优化器,用来训练时候优化模型参数,随机梯度下降法
    learning_rate = 1e-4
    optimizer = torch.optim.Adam([
        {
            'params': parms_1x
        },
        {
            'params': parms_10x,
            'lr': learning_rate * 10
        }], lr=learning_rate)

    epochs = 50
    loss_ = 10
    save_root = "output/"

    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        time_start = time.time()
        avg_loss = train(train_dataloader, finetune_net, loss_fn, optimizer, device)
        time_end = time.time()
        print(f"train time: {(time_end - time_start)}")

        val_accuracy, val_loss = validate(valid_dataloader, finetune_net,loss_fn, device)
        # 将训练过程中的损失和准确率写入文件
        WriteData(save_root + "resnet18_e.txt",
                  "epoch", t,
                  "train_loss", avg_loss,
                  "val_loss", val_loss,
                  "val_accuracy", val_accuracy)
        if t % 5 == 0:
            torch.save(finetune_net.state_dict(), save_root + "resnet18_e_epoch" + str(t) + "_loss_" + str(avg_loss) + ".pth")
        torch.save(finetune_net.state_dict(), save_root + "resnet18_e_last.pth")
        if avg_loss < loss_:
            loss_ = avg_loss
            torch.save(finetune_net.state_dict(), save_root + "resnet18_e_best.pth")

画loss曲线

from matplotlib import pyplot as plt
import numpy as np

def ReadData(data_loc):
    epoch_list = []
    train_loss_list = []
    test_loss_list = []
    test_accuracy_list = []

    with open(data_loc, "r") as f:     # 使用open()函数打开数据文件
        linedata = f.readlines()       # 使用readlines()方法读取文件的每一行数据

        for line_i in linedata: 
            data = line_i.split('\t')  # 遍历每行数据,使用split('\t')将每行数据分割为多个字段
            print("data = ", data)
            epoch_i , train_loss_i,test_loss_i,test_accuracy_i =data[1], data[3],data[5],data[7] # 将每个字段的值分别提取出来,并转换为对应的数据类型
            epoch_list.append(int(epoch_i))
            train_loss_list.append(float(train_loss_i))
            test_loss_list.append(float(test_loss_i))
            test_accuracy_list.append(float(test_accuracy_i))

    return epoch_list, train_loss_list  ,test_loss_list,test_accuracy_list


def DrawLoss(train_loss_list,train_loss_list_2):  # train_loss_list和train_loss_list_2分别是两个模型训练过程中的损失列表
    plt.style.use('dark_background')   # 使用plt.style.use()设置绘图的样式为黑色背景
    plt.title("Loss")
    plt.xlabel("epoch")
    plt.ylabel("loss")

    train_loss_list = train_loss_list[:10]    # 限制train_loss_list的长度为10,只绘制前10个epoch的损失

    epoch_list = [i for i in range(len(train_loss_list))]  # 创建一个表示epoch的列表epoch_list,长度与train_loss_list相同

    p1, = plt.plot(epoch_list, train_loss_list, linewidth=3)  # 使用plt.plot()方法绘制两个曲线,分别表示两个模型的训练损失
    p2, = plt.plot(epoch_list, train_loss_list_2, linewidth=3)

    plt.legend([p1, p2], ["with pretrain", "no pretrain"])  # 使用plt.legend()方法添加图例,表示两个模型的名称
    plt.show()

def DrawAcc(train_loss_list,train_loss_list_2):  # train_loss_list和train_loss_list_2分别是两个模型训练过程中的准确率列表
    plt.style.use('dark_background')
    plt.title("Accuracy")    # 设置绘图的标题、横轴标签和纵轴标签
    plt.xlabel("epoch")
    plt.ylabel("accuracy")

    train_loss_list = train_loss_list[:10]

    epoch_list = [i for i in range(len(train_loss_list))]

    p1, = plt.plot(epoch_list, train_loss_list, linewidth=3)
    p2, = plt.plot(epoch_list, train_loss_list_2, linewidth=3)

    plt.legend([p1, p2], ["with pretrain", "no pretrain"])
    plt.show()

if __name__ == '__main__':
    data_1_loc = "output/resnet18.txt"
    data_2_loc = "output/resnet18_no_pretrain.txt"

    # 使用ReadData()函数读取data_1_loc和data_2_loc中的数据,得到训练损失、测试损失和测试准确率列表。
    _, train_loss_list  ,test_loss_list,test_accuracy_list = ReadData(data_1_loc)
    _, train_loss_list_2  ,test_loss_list_2,test_accuracy_list_2 = ReadData(data_2_loc)

    DrawLoss(train_loss_list,train_loss_list_2)  # 调用DrawLoss()函数绘制训练损失的曲线,将train_loss_list和train_loss_list_2作为参数传入
    DrawAcc(test_accuracy_list,test_accuracy_list_2) # 调用DrawAcc()函数绘制测试准确率的曲线,将test_accuracy_list和test_accuracy_list_2作为参数传入

模型验证

import torch
from torch.utils.data import DataLoader
from utils import LoadData,WriteData
import torch.nn as nn
from torchvision.models import resnet18
from tqdm import tqdm
import os
import pandas as pd

def test(dataloader, model, device):
    pred_list = []
    # 将模型转为验证模式
    model.eval()
    # 测试时模型参数不用更新,所以no_gard()
    # 非训练, 推理期用到
    with torch.no_grad():
        # 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
        for X, y in tqdm(dataloader):
            # 将数据转到GPU
            X, y = X.to(device), y.to(device)
            # 将图片传入到模型当中就,得到预测的值pred
            pred = model(X)
            pred_softmax = torch.softmax(pred, 1).cpu().numpy()
            pred_list.append(pred_softmax.tolist()[0])
        return pred_list

if __name__=='__main__':
    batch_size = 1

    # 给训练集和测试集分别创建一个数据集加载器
    test_data = LoadData("test.txt", False)

    test_dataloader = DataLoader(dataset=test_data, num_workers=4, pin_memory=True, batch_size=batch_size)

    # 如果显卡可用,则用显卡进行训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")

    model = resnet18(num_classes=55)
    model.load_state_dict(torch.load("output/resnet18_e_best.pth"))
    model.to(device)

    # 定义损失函数,计算相差多少,交叉熵,
    # loss_fn = nn.CrossEntropyLoss()

    '''
        获取结果
    '''
    # 获取模型输出
    pred_list = test(test_dataloader, model,device)
    print("pred_list = ", pred_list)

    '''
        获取文件夹列表
    '''
    file_name_list = []
    data_root = r"enhance_dataset"
    for a,b,c in os.walk(data_root):
        if len(b) != 0:
            print(b)
            file_name_list = b

    df_pred = pd.DataFrame(data=pred_list, columns=file_name_list)
    print(df_pred)
    df_pred.to_csv('pred_result.csv', encoding='gbk', index=False)
from sklearn.metrics import *  # pip install scikit-learn
import matplotlib.pyplot as plt # pip install matplotlib
import pandas as pd # pip install pandas
import os

'''
读取数据

需要读取模型输出的标签(predict_label)以及原本的标签(true_label)
'''
target_loc = "./test.txt"     # 真实标签所在的文件
target_data = pd.read_csv(target_loc, sep="\t", names=["loc","type"])
true_label = [i for i in target_data["type"]]

predict_loc = "./pred_result.csv"     # 3.ModelEvaluate.py生成的文件

predict_data = pd.read_csv(predict_loc,encoding="GBK")#,index_col=0)

predict_label = predict_data.to_numpy().argmax(axis=1)

predict_score = predict_data.to_numpy().max(axis=1)

'''
    常用指标:精度,查准率,召回率,F1-Score
'''
# 精度,准确率, 预测正确的占所有样本种的比例
accuracy = accuracy_score(true_label, predict_label)
print("精度: ",accuracy)

# 查准率P(准确率),precision(查准率)=TP/(TP+FP)

precision = precision_score(true_label, predict_label, labels=None, pos_label=1, average='macro') # 'micro', 'macro', 'weighted'
print("查准率P: ",precision)

# 查全率R(召回率),原本为对的,预测正确的比例;recall(查全率)=TP/(TP+FN)
recall = recall_score(true_label, predict_label, average='macro') # 'micro', 'macro', 'weighted'
print("召回率: ",recall)

# F1-Score
f1 = f1_score(true_label, predict_label, average='macro')     # 'micro', 'macro', 'weighted'
print("F1 Score: ",f1)


'''
混淆矩阵
'''
label_names = []
data_root = r"./enhance_dataset"
for a,b,c in os.walk(data_root):
    if len(b) != 0:
        print(b)
        label_names = b

confusion = confusion_matrix(true_label, predict_label, labels=[i for i in range(len(label_names))])


plt.matshow(confusion, cmap=plt.cm.Oranges)   # Greens, Blues, Oranges, Reds

plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置中文字体
plt.rcParams["font.size"] = 8
plt.rcParams["axes.unicode_minus"] = False  # 该语句解决图像中的“-”负号的乱码问题

plt.colorbar()
for i in range(len(confusion)):
    for j in range(len(confusion)):
        plt.annotate(confusion[j,i], xy=(i, j), horizontalalignment='center', verticalalignment='center')
plt.ylabel('True label')
plt.xlabel('Predicted label')


plt.xticks(range(len(label_names)), label_names,rotation=270)
plt.yticks(range(len(label_names)), label_names)
plt.title("Confusion Matrix")
plt.show()

宽度设计

通道数量调整

模型通道数随深度增加而增加

多分支网络结构

通道补偿技术

多通道网络 Inception-v1

拓宽残差网络 ResNeXt

项目:InceptionNet花卉分类

注意力模型

注意力机制,是聚焦于局部信息的机制,如图像中的某一个图像区域。

输入是一张图,输出是一张概率图,概率越大的地方,代表是图像中重要目标的概率越大,即人眼关注的重点。

注意力模型:

  • 空间注意力模型:赋予图像不同区域不同权重,提取图片中的重要特征。如Spatial Transformer networks、Dynamic Capacity Networks

  • 通道注意力模型:用多个卷积核调整不同通道权重,提取重要通道特征。如SENet、SKNet、ResNeSt

  • 混合注意力模型:空间注意力模型 + 通道注意力模型。如CBAM、BAM、Residual Attention、Dual Attention Network

Spatial Transformer networks

空间变换网络是一种深度学习的网络模块,它的目标是让神经网络可以自动学习对输入数据进行几何变换(例如旋转、缩放、平移)的能力。

假设我们有一个用于手写数字识别的图像分类网络。传统的神经网络在训练过程中只接收正常方向的数字图像作为输入。

如果我们在测试时输入一个旋转了一定角度的手写数字图像,传统的神经网络可能无法正确识别这个旋转后的图像。

但是,通过引入空间变换网络,网络可以学习到如何对输入数据进行旋转,并在训练过程中优化旋转参数。

这样,在测试时,网络可以自适应地对输入图像进行旋转调整,从而使得网络能够正确地识别旋转后的手写数字图像。

举个具体例子,如果我们将一个手写数字图像旋转了90度,传统的神经网络可能会将其错误地识别为其他数字。

但是,通过空间变换网络,网络可以自动学习到将输入图像旋转回正常方向的变换,并在预测时对图像进行相应的旋转调整,从而正确识别旋转后的手写数字。

通过学习输入的形变,从而完成适合任务的预处理操作,是一种基于空间的注意力模型。

Dynamic Capacity Networks

由于在大部分情况下我们感兴趣/重要/显著的区域只是图像中的一小部分,那么我们忽略其他部分,就可以提高效率。

粗调选出显著区域,精调选出区域特征。

Dynamic Capacity Networks 分成俩部分:

  • 粗调的子网络(coarse model)用于对全图进行处理,定位显著区域

  • 精调的子网络(fine model)则对显著区域进行精细化处理

两者共同使用,可以获得更低的计算代价和更高的精度。

SENet

使用全局信息增强有用信息,同时抑制无用信息。

传统的神经网络,如卷积神经网络(CNN)或残差网络(ResNet),在每个通道的特征处理中通常是平等对待的,缺乏对通道间重要性的明确建模。

这可能导致网络在处理输入数据时没有充分利用不同通道之间的相关性和重要性。

SENet通过引入Squeeze和Excitation操作,通过自适应地调整通道特征的重要性,增强模型对重要特征的关注能力。

举个例子,以图像分类任务为例,假设我们有一个基于SENet的网络和一个传统的CNN网络(如ResNet)进行对比。

对于传统的CNN网络,每个通道的特征图在整个网络中都是平等对待的,没有明确的机制来区分通道的重要性。

这可能会导致网络在对图像进行分类时无法充分关注到重要的特征。

而对于基于SENet的网络,通过Squeeze和Excitation操作,网络可以自适应地调整通道特征的重要性。

这意味着网络可以有选择性地关注和强调对分类任务更重要的特征,同时减弱对不重要特征的关注。这样可以提高网络的表达能力和分类性能。

总而言之,SENet相对于传统的神经网络在于它引入了自适应的注意力机制,通过对通道特征进行重标定,增强模型对重要特征的关注能力。

这使得SENet在图像分类任务中具有更好的性能和表现。

  • Squeeze(压缩)操作:首先,SENet通过全局平均池化操作将每个通道的特征图压缩为一个标量值。这个标量值表示每个通道的特征的整体重要性。

例如,我们可以将图像的每个通道的特征图大小从 256x256 压缩为一个大小为 1x1 的特征向量。

  • Excitation(激励)操作:接下来,SENet引入一个学习的全连接层,以学习每个通道特征的权重。这个全连接层接收Squeeze操作产生的特征向量作为输入,并输出一个与通道数相同的权重向量,用于调整每个通道特征的重要性。

例如,假设我们的图像有三个通道:红色、绿色和蓝色。在Excitation操作中,SENet学习到的权重向量可以为 [0.8, 0.9, 0.7],表示红色通道的特征权重较高,绿色通道的特征权重较高,蓝色通道的特征权重较低。

最后,通过将每个通道特征与其对应的权重相乘,SENet生成了经过调整的通道特征。这些调整后的通道特征反映了每个通道的重要性和对分类任务的贡献。

SKNet

ResNet网络,它使用残差块来构建网络,并通过堆叠多个残差块来提高特征的建模能力。

然而,ResNet的每个残差块对所有感受野的特征都是平等对待的,缺乏对不同感受野特征的选择性融合能力。

相比之下,基于SKNet的图像分类网络引入了Selective Kernel模块。这个模块可以选择性地调整每个感受野特征的权重,使得网络能够自适应地融合不同尺度和层次的特征。

这样,SKNet可以更好地捕捉图像中的全局和局部信息,提高图像分类的准确性。

  • 感受野(Receptive Field)是指在神经网络中,每个神经元对输入数据的局部区域的感知范围。

卷积核的大小为 KxK,步幅为 S,那么在第 n 层中,每个神经元的感受野大小(r_n)可以通过以下公式计算:

r_n = (r_{n-1} - 1) * S + K

其中,r_{n-1} 表示前一层神经元的感受野大小。

第一个卷积层(Conv1)来说,假设卷积核的大小为 3x3,那么每个神经元的感受野大小就是 3x3。

第二个卷积层(Conv2):假设卷积核的大小为3x3,步幅为1。感受野 = (3-1)*1+3 = 5,即为5x5。这是因为每个神经元的输入来自前一层的特征图。

在前一层中,每个神经元的感受野为3x3,所以在当前层中,每个神经元的感受野可以覆盖3x3的区域,再加上自身的3x3感受野,所以感受野扩大为5x5。

第三个卷积层(Conv3):假设卷积核的大小为3x3,步幅为1。在这一层中,每个神经元的感受野大小为7x7。

同样地,每个神经元的输入来自前一层的特征图,在前一层中,每个神经元的感受野为5x5,所以在当前层中,每个神经元的感受野可以覆盖5x5的区域,再加上自身的3x3感受野,所以感受野扩大为7x7。

ResNeSt

ResNeSt是一种改进的残差神经网络,它结合了两个主要的设计思想:残差连接和通道注意力机制。

对于传统的ResNet网络,每个残差块都包含了两个卷积层和一个跳跃连接,通过将输入特征与残差块的输出相加来传递信息。

这种残差连接可以帮助减轻梯度消失问题,并允许信息在网络中的不同层级进行传递。

而在ResNeSt中,除了残差连接之外,还引入了通道注意力机制。

具体来说,ResNeSt中的每个残差块包含了一个通道注意力模块,用于自适应地调整通道特征的重要性。

  • 通道注意力机制:ResNeSt引入了通道注意力机制,这是由SENet中的Squeeze和Excitation(SE)模块提出的。

这举例来说,在ResNeSt中,当网络接收到一张猫的图片时,残差块中的通道注意力模块可以自适应地调整不同通道的特征的重要性。

如果某些通道的特征对于分类猫的任务非常重要,通道注意力模块会增加它们的权重,以更好地捕捉和强调这些特征。

这样,ResNeSt可以更准确地分类猫的图片,相较于传统的ResNet等经典神经网络,具有更强的特征表达和自适应关注能力。

CBAM

BAM

Residual Attention

Dual Attention Network

项目:SENet 人种分类

轻量化模型

专门部署在手机上的模型。

Xception

Xception是一种卷积神经网络模型,它在经典的卷积神经网络结构中进行了改进,以提高特征提取和表达能力。

Xception的设计思想主要是通过深度可分离卷积(Depthwise Separable Convolution)来实现更有效的特征学习。

通常的卷积操作是在输入特征图上同时进行卷积核的卷积操作和通道的混合。

而深度可分离卷积将这两个操作拆分成两个独立的步骤:

  1. 深度卷积(Depthwise Convolution):首先,对输入特征图的每个通道分别进行卷积操作。这意味着每个通道都有一个独立的卷积核,用于捕捉通道内的特征。

  2. 逐点卷积(Pointwise Convolution):然后,对深度卷积的输出进行逐点卷积操作。逐点卷积使用1x1的卷积核,用于将通道之间的特征进行混合和整合。

通过这种深度可分离卷积的结构,Xception可以更有效地学习和表示图像中的特征。它减少了参数数量和计算量,提高了模型的效率和性能。

举个例子来说明Xception的作用,假设我们要对一张图片进行猫与狗的分类。

在传统的卷积神经网络中,卷积操作会同时考虑不同通道的特征,并在特征图的每个位置进行卷积运算。

而在Xception中,深度可分离卷积将卷积操作拆分成深度卷积和逐点卷积。

深度卷积会分别对每个通道进行卷积操作,以捕捉通道内的特征。

然后,逐点卷积将不同通道之间的特征进行混合和整合。

通过这种方式,Xception可以更准确地提取和学习猫和狗图片中的特征,从而提高分类的准确性。

MobileNet V1

MobileNet是一种轻量级的卷积神经网络模型,旨在在计算资源有限的设备(手机)上实现高效的图像识别和处理。

相对于其他神经网络,MobileNet具有更小的模型大小和更低的计算成本,但仍能保持较高的准确率。

MobileNet的设计思想主要有两个方面:

  1. 深度可分离卷积:MobileNet采用了深度可分离卷积结构,将传统的卷积操作分解为深度卷积和逐点卷积。

深度卷积在每个通道上进行卷积操作,而逐点卷积通过1x1的卷积核进行通道间的特征整合。

这种设计可以大幅减少参数数量和计算量,从而实现轻量级的模型。

  1. 宽度乘积因子:MobileNet引入了宽度乘积因子(Width Multiplier)的概念,用于调整模型的通道数。

通过减少通道数,可以进一步减小模型的大小和计算复杂度。

宽度乘积因子可以在不同任务和设备上进行调整,以在满足性能要求的同时节省计算资源。

举个例子来说明MobileNet的作用,假设我们要对一张图片进行图像分类任务,将其划分为不同的类别,如猫和狗。

在传统的卷积神经网络中,通常需要较大的模型和复杂的计算才能达到较高的准确率。

而在MobileNet中,通过深度可分离卷积和宽度乘积因子的设计,可以实现一个轻量级的模型,既能在计算资源有限的设备上运行,又能保持较高的准确率。

举例来说,在图像分类任务中,当我们使用MobileNet来对一张图片进行猫和狗的分类时,MobileNet的轻量级结构可以在较小的模型和较低的计算成本下,有效地学习和表示猫和狗的特征。这样,我们可以在计算资源有限的设备上实现高效的图像分类,同时减少了模型的大小和计算开销。

总而言之,MobileNet是一种轻量级的卷积神经网络,通过深度可分离卷积和宽度乘积因子的设计,实现了在计算资源有限的设备上高效的图像识别和处理。

它可以在保持较高准确率的同时减小模型大小和计算成本。

MobileNet V2

shufflenetv1

ShuffleNet是一种高效的卷积神经网络模型,旨在在计算资源受限的设备上实现高性能的图像分类和目标检测。相对于其他神经网络,ShuffleNet具有更小的模型大小和更低的计算成本,同时保持较高的准确率。

ShuffleNet的设计思想主要包括两个关键要素:

  1. 分组卷积:ShuffleNet采用了分组卷积的结构,将卷积操作分为多个组。

在每个组内,输入特征图被分成多个通道,然后进行卷积操作。这样可以减少卷积操作的计算复杂度。

  1. 通道混洗:为了增强特征的交互和信息流动,ShuffleNet引入了通道混洗的操作。通道混洗将分组卷积后的特征图进行通道交换,使得不同组之间的特征可以相互交流和融合。

这种操作有助于提高特征的表达能力和准确性。

举个例子来说明ShuffleNet的作用,假设我们要对一张图片进行目标检测任务,检测图像中的汽车。在传统的卷积神经网络中,通常需要较大的模型和复杂的计算才能达到较高的准确率。

而在ShuffleNet中,通过分组卷积和通道混洗的设计,可以实现一个轻量级的模型,既能在计算资源有限的设备上运行,又能保持较高的准确率。

举例来说,在图像目标检测任务中,当我们使用ShuffleNet来对一张图片进行汽车的检测时,ShuffleNet的轻量级结构可以在较小的模型和较低的计算成本下,有效地学习和表示汽车的特征。

通过分组卷积和通道混洗的操作,ShuffleNet可以使不同组之间的特征进行交流和融合,从而提高目标检测的准确性。

总而言之,ShuffleNet是一种高效的卷积神经网络,通过分组卷积和通道混洗的设计,实现了在计算资源有限的设备上高性能的图像分类和目标检测。

它可以在保持较高准确率的同时减小模型大小和计算成本。

shufflenetv2

squeezenet

SqueezeNet是一种轻量级的卷积神经网络模型,旨在在计算资源受限的设备上实现高效的图像分类和目标检测。

相对于其他神经网络,SqueezeNet具有更小的模型大小和更低的计算成本,同时保持较高的准确率。

SqueezeNet的设计思想主要包括以下几个关键要素:

  1. 火焰模块(Fire Module):SqueezeNet使用了火焰模块,它由一个称为squeeze层和一个称为expand层组成。

squeeze层使用1x1的卷积核来压缩通道数,减少参数数量;而expand层使用1x1和3x3的卷积核来扩展通道数,增加特征的多样性。

这样的设计既减少了参数,又保持了较高的特征表达能力。

  1. 小卷积核:相比于传统的卷积神经网络,SqueezeNet使用较小的卷积核(如1x1)来减少参数数量和计算复杂度。

这样可以在保持准确率的同时,大幅减小模型的大小和计算开销。

举个例子来说明SqueezeNet的作用,假设我们要对一张图片进行图像分类任务,将其划分为不同的类别,如猫和狗。

在传统的卷积神经网络中,通常需要较大的模型和复杂的计算才能达到较高的准确率。

而在SqueezeNet中,通过火焰模块和小卷积核的设计,可以实现一个轻量级的模型,既能在计算资源有限的设备上运行,又能保持较高的准确率。

举例来说,在图像分类任务中,当我们使用SqueezeNet来对一张图片进行猫和狗的分类时,SqueezeNet的轻量级结构可以在较小的模型和较低的计算成本下,有效地学习和表示猫和狗的特征。

这样,我们可以在计算资源有限的设备上实现高效的图像分类,同时减少了模型的大小和计算开销。

总而言之,SqueezeNet是一种轻量级的卷积神经网络,通过火焰模块和小卷积核的设计,实现了在计算资源有限的设备上高效的图像识别和处理。

它可以在保持较高准确率的同时减小模型大小和计算成本。

项目:安卓端部署实时图片检测app

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值