• 第P8周—YOLOv5-C3模块实现


    >- **🍨 本文为[🔗365天深度学习训练营](https://mp.weixin.qq.com/s/Nb93582M_5usednAKp_Jtw) 中的学习记录博客**
    >- **🍖 原作者:[K同学啊 | 接辅导、项目定制](https://mtyjkh.blog.csdn.net/)**
    >- **🚀 文章来源:[K同学的学习圈子](https://www.yuque.com/mingtian-fkmxf/zxwb45)**

     一、前期工作

    1.1导入数据

    1. import torch
    2. import torch.nn as nn
    3. import torchvision.transforms as transforms
    4. import torchvision
    5. from torchvision import transforms, datasets
    6. import os,PIL,pathlib,warnings
    7. warnings.filterwarnings("ignore") #忽略警告信息
    8. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    9. print(device)
    10. import os,PIL,random,pathlib
    11. data_dir = 'D:/P8/weather_photos/'
    12. data_dir = pathlib.Path(data_dir)
    13. data_paths = list(data_dir.glob('*'))
    14. classeNames = [str(path).split("\\")[3] for path in data_paths]
    15. print(classeNames)

    1.2 数据集图片标准化处理

     

    1.3 划分数据集

    1. train_size = int(0.8 * len(total_data))
    2. test_size = len(total_data) - train_size
    3. train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
    4. print(train_dataset, test_dataset)

    1.4 设置数据加载器

    1. batch_size = 4
    2. train_dl = torch.utils.data.DataLoader(train_dataset,
    3. batch_size=batch_size,
    4. shuffle=True,
    5. num_workers=1)
    6. test_dl = torch.utils.data.DataLoader(test_dataset,
    7. batch_size=batch_size,
    8. shuffle=True,
    9. num_workers=1)

    二、搭建包含YOLOv5-C3模块的模型

    1. import torch.nn.functional as F
    2. def autopad(k, p=None): # kernel, padding
    3. # Pad to 'same'
    4. if p is None:
    5. p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
    6. return p
    7. class Conv(nn.Module):
    8. # Standard convolution
    9. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
    10. super().__init__()
    11. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
    12. self.bn = nn.BatchNorm2d(c2)
    13. self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
    14. def forward(self, x):
    15. return self.act(self.bn(self.conv(x)))
    16. class Bottleneck(nn.Module):
    17. # Standard bottleneck
    18. def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
    19. super().__init__()
    20. c_ = int(c2 * e) # hidden channels
    21. self.cv1 = Conv(c1, c_, 1, 1)
    22. self.cv2 = Conv(c_, c2, 3, 1, g=g)
    23. self.add = shortcut and c1 == c2
    24. def forward(self, x):
    25. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
    26. class C3(nn.Module):
    27. # CSP Bottleneck with 3 convolutions
    28. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
    29. super().__init__()
    30. c_ = int(c2 * e) # hidden channels
    31. self.cv1 = Conv(c1, c_, 1, 1)
    32. self.cv2 = Conv(c1, c_, 1, 1)
    33. self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
    34. self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
    35. def forward(self, x):
    36. return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
    37. class model_K(nn.Module):
    38. def __init__(self):
    39. super(model_K, self).__init__()
    40. # 卷积模块
    41. self.Conv = Conv(3, 32, 3, 2)
    42. # C3模块1
    43. self.C3_1 = C3(32, 64, 3, 2)
    44. # 全连接网络层,用于分类
    45. self.classifier = nn.Sequential(
    46. nn.Linear(in_features=802816, out_features=100),
    47. nn.ReLU(),
    48. nn.Linear(in_features=100, out_features=4)
    49. )
    50. def forward(self, x):
    51. x = self.Conv(x)
    52. x = self.C3_1(x)
    53. x = torch.flatten(x, start_dim=1)
    54. x = self.classifier(x)
    55. return x
    56. device = "cuda" if torch.cuda.is_available() else "cpu"
    57. print("Using {} device".format(device))
    58. model = model_K().to(device)
    59. print(model)

    上述代码定义了一个用于分类任务的PyTorch神经网络架构。

    1. autopad 函数:

     autopad 函数的目的是使卷积操作的输出尺寸与输入尺寸相同,从而方便构建神经网络模型时进行 "same" 填充设置。这对于保持特征图的尺寸不变通常很有用,特别是在卷积神经网络中。

    def autopad(k, p=None):  # kernel, padding

        # Pad to 'same'

        if p is None:

            p = k // 2 if isinstance(k, int) else [x // 2 for x in k]  # auto-pad

        return p

    这部分代码定义了一个名为 `autopad` 的函数,其功能和用途是为卷积操作计算填充(padding),以实现 "same" 填充效果。让我解释这个函数的功能和参数:

    - `k`:这是卷积核的大小,可以是整数或一个由两个整数组成的元组(高度和宽度)。
    - `p`:这是卷积操作的填充参数,它表示在输入图像周围添加的填充量。默认情况下,它被设置为 `None`,表示填充参数未提供。

    函数的主要目的是计算合适的填充值 `p`,以便在进行卷积操作时,输出的特征图尺寸与输入的特征图尺寸保持一致,实现 "same" 填充。

    工作原理如下:

    1. 首先检查是否提供了填充参数 `p`,如果没有提供(即 `p` 为 `None`),则执行以下操作:

    2. 如果 `k` 是整数(表示卷积核是正方形的),则计算填充值 `p` 为 `k` 的一半,这将使得卷积操作在每个边上都添加一半的填充,从而使输出特征图的大小与输入相同。这是实现 "same" 填充的常见方式。

    3. 如果 `k` 是一个由两个整数组成的元组,例如 `(3, 3)`(表示卷积核的高度和宽度),则计算填充值 `p` 分别为每个维度的半数。这确保了在每个维度上都应用一半的填充。

    4. 最后函数返回计算得到的填充参数 `p`。

       - 这个函数用于计算卷积操作所需的填充,以实现“same”填充效果。如果没有提供 `p`(填充),则根据卷积核大小 `k` 来计算填充。如果 `k` 是整数,它会计算卷积核大小的一半;如果 `k` 是一个元组,它会计算每个维度的一半。

    2. `Conv` 类:

    class Conv(nn.Module):

        # Standard convolution

        def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups

            super().__init__()

            self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)

            self.bn = nn.BatchNorm2d(c2)

            self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

        def forward(self, x):

            return self.act(self.bn(self.conv(x)))

    代码定义了一个名为 `Conv` 的自定义卷积层类(Custom Convolutional Layer),其功能是创建一个标准的卷积层,该卷积层包括卷积操作、批归一化(Batch Normalization),以及可选的激活函数。

    主要功能和参数:

    - `c1`:输入通道的数量(即输入特征图的通道数)。
    - `c2`:输出通道的数量(即输出特征图的通道数)。
    - `k`:卷积核的大小,可以是一个整数或一个由两个整数组成的元组,分别表示卷积核的高度和宽度。
    - `s`:卷积操作的步幅(stride)。
    - `p`:卷积操作的填充参数。这是一个可选参数,如果不提供,将根据卷积核大小 `k` 自动计算填充,以实现 "same" 填充效果。
    - `g`:分组卷积的组数(默认为1,表示普通的卷积操作)。
    - `act`:激活函数,这是一个可选参数。如果设置为 `True`,将使用 SiLU(Sigmoid Linear Unit)作为激活函数。如果设置为其他激活函数的名称或激活函数模块(`nn.Module`),则会使用指定的激活函数。如果设置为 `False` 或未提供,则不应用激活函数。

    该类的 `__init__` 方法在初始化过程中执行以下操作:

    1. 创建一个 `nn.Conv2d` 对象,这是PyTorch内置的二维卷积层。该层的配置基于传入的参数 `c1`、`c2`、`k`、`s`、`p`、`g` 和 `bias=False`(表示不使用偏差项)。
    2. 创建一个 `nn.BatchNorm2d` 对象,用于批归一化。这有助于加速训练过程并提高模型的稳定性。
    3. 创建一个激活函数对象,根据 `act` 参数的值。如果 `act` 设置为 `True`,则使用 SiLU 激活函数;如果 `act` 是其他激活函数的名称或模块,将使用指定的激活函数;如果 `act` 是 `False` 或未提供,则不应用激活函数。

     `forward` 方法,该方法用于执行前向传播操作。前向传播的过程如下:

    1. 输入 `x` 经过卷积操作 `self.conv`,得到卷积特征。
    2. 卷积特征经过批归一化操作 `self.bn`,以进一步规范化特征。
    3. 规范化后的特征经过激活函数 `self.act`,应用激活函数(如果已指定的话)。
    4. 最终的输出是经过卷积、批归一化和激活函数处理的特征。

    这个自定义卷积层可以嵌入到神经网络中,用于构建卷积神经网络(CNN)的不同层。通过设置不同的参数,您可以灵活地配置卷积核大小、步幅、填充、激活函数等,以满足特定的网络设计需求。

    3. `Bottleneck` 类:

    `Bottleneck` 模块是一个用于构建深度卷积神经网络中的瓶颈块的自定义模块。它包括了卷积操作、通道数量的缩减、快捷连接(可选)等组件,有助于增强网络的表达能力和训练效果。这种结构在许多先进的CNN架构中都得到了广泛应用。

    class Bottleneck(nn.Module):

        # Standard bottleneck

        def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, shortcut, groups, expansion

            super().__init__()

            c_ = int(c2 * e)  # hidden channels

            self.cv1 = Conv(c1, c_, 1, 1)

            self.cv2 = Conv(c_, c2, 3, 1, g=g)

            self.add = shortcut and c1 == c2

        def forward(self, x):

            return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

    上述代码定义了一个名为 `Bottleneck` 的自定义模块,用于创建标准的瓶颈块(bottleneck block),这是深度卷积神经网络(CNN)中常用的一种模块。

    主要功能和参数:

    - `c1`:输入通道的数量(即输入特征图的通道数)。
    - `c2`:输出通道的数量(即输出特征图的通道数)。
    - `shortcut`:一个布尔值,表示是否包括快捷连接(shortcut connection)。默认情况下,为 `True`,表示包括快捷连接。
    - `g`:分组卷积的组数(默认为1,表示普通的卷积操作)。
    - `e`:隐藏通道的扩展因子(默认为0.5),用于控制瓶颈块内部隐藏通道的数量。

    `Bottleneck` 模块的主要功能是构建一个标准的瓶颈块,通常用于深度卷积神经网络(如ResNet)。它的前向传播过程如下:

    1. 输入 `x` 首先通过一个 1x1 的卷积层 `self.cv1`,将输入通道数 `c1` 缩减为隐藏通道数 `c_`。这个 1x1 卷积层有助于减少计算复杂度,并且可以引入非线性变换。
    2. 缩减通道数后的特征图再经过一个 3x3 的卷积层 `self.cv2`,将其变换为输出通道数 `c2`。这个卷积层是瓶颈块的核心,它有助于提取特征。
    3. 如果 `shortcut` 为 `True` 且输入通道数 `c1` 等于输出通道数 `c2`,则执行快捷连接。在前向传播中,将输入 `x` 与 `self.cv2(self.cv1(x))` 相加,实现了跳跃连接。这有助于信息的直接传递,从而缓解了梯度消失问题。
    4. 如果 `shortcut` 为 `False` 或者输入通道数 `c1` 不等于输出通道数 `c2`,则不执行快捷连接,直接返回 `self.cv2(self.cv1(x))`。

    5.代码定义了一个神经网络模型,该模型包括卷积层、C3模块、全连接层等组件,用于图像分类任务。`C3` 模块是整个模型的关键组件,用于提取和融合多层特征,有助于提高模型的性能。可以使用此模型进行训练和图像分类。

    class C3(nn.Module):

        # CSP Bottleneck with 3 convolutions

        def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion

            super().__init__()

            c_ = int(c2 * e)  # hidden channels

            self.cv1 = Conv(c1, c_, 1, 1)

            self.cv2 = Conv(c1, c_, 1, 1)

            self.cv3 = Conv(2 * c_, c2, 1)  # act=FReLU(c2)

            self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

        def forward(self, x):

            return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))

    class model_K(nn.Module):

        def __init__(self):

            super(model_K, self).__init__()

           

            # 卷积模块

            self.Conv = Conv(3, 32, 3, 2)

           

            # C3模块1

            self.C3_1 = C3(32, 64, 3, 2)

           

            # 全连接网络层,用于分类

            self.classifier = nn.Sequential(

                nn.Linear(in_features=802816, out_features=100),

                nn.ReLU(),

                nn.Linear(in_features=100, out_features=4)

            )

           

        def forward(self, x):

            x = self.Conv(x)

            x = self.C3_1(x)

            x = torch.flatten(x, start_dim=1)

            x = self.classifier(x)

            return x

    这段代码定义了两个主要的PyTorch模块类:`C3` 和 `model_K`,以及它们的组件。

     `C3` 类:
    - `C3` 类代表一个具有3个卷积操作的CSP瓶颈块(CSP Bottleneck)。
    - 构造函数 `__init__` 接受以下参数:
      - `c1`:输入通道数(输入特征图的通道数)。
      - `c2`:输出通道数(输出特征图的通道数)。
      - `n`:要堆叠的Bottleneck块的数量。
      - `shortcut`:一个布尔值,表示是否包括快捷连接(shortcut connection)。
      - `g`:分组卷积的组数。
      - `e`:隐藏通道的扩展因子。
    - 在初始化过程中,它执行以下操作:
      - 计算隐藏通道数 `c_`,它是输出通道数 `c2` 乘以隐藏通道扩展因子 `e` 的整数部分。
      - 创建三个卷积层:`self.cv1`、`self.cv2` 和 `self.cv3`,其中 `self.cv1` 和 `self.cv2` 是1x1卷积层,`self.cv3` 是1x1卷积层,用于融合输出。
      - 创建一个包含 `n` 个 `Bottleneck` 模块的序列 `self.m`,这些模块通过堆叠来构成瓶颈块。
    - 前向传播方法 `forward` 执行以下操作:
      - 输入 `x` 首先经过 `self.cv1` 和 `self.cv2` 两个1x1卷积层。
      - 通过 `self.m` 中的 `Bottleneck` 模块进行特征提取和变换。
      - 最后,特征通过 `self.cv3` 进行1x1卷积融合,然后返回。

     `model_K` 类:
    - `model_K` 类代表整个神经网络模型。
    - 构造函数 `__init__` 创建了以下组件:
      - 一个卷积模块 `self.Conv`,包括一个3x3卷积层,用于提取图像特征。
      - 一个 `C3` 模块 `self.C3_1`,用于进行多层特征的提取和融合。
      - 一个全连接网络层 `self.classifier`,用于最终的图像分类。
    - 前向传播方法 `forward` 执行以下操作:
      - 输入 `x` 首先经过 `self.Conv` 进行卷积特征提取。
      - 通过 `self.C3_1` 进行多层特征的提取和融合。
      - 通过 `torch.flatten` 操作将特征展平,以便输入到全连接层。
      - 通过 `self.classifier` 进行分类,得到最终的输出。

    运行结果:

    model_K(
      (Conv): Conv(
        (conv): Conv2d(3, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
        (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
        (act): SiLU()
      )
      (C3_1): C3(
        (cv1): Conv(
          (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act): SiLU()
        )
        (cv2): Conv(
          (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act): SiLU()
        )
        (cv3): Conv(
          (conv): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
          (bn): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (act): SiLU()
        )
        (m): Sequential(
          (0): Bottleneck(
            (cv1): Conv(
              (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
            (cv2): Conv(
              (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
          )
          (1): Bottleneck(
            (cv1): Conv(
              (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
            (cv2): Conv(
              (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
          )
          (2): Bottleneck(
            (cv1): Conv(
              (conv): Conv2d(32, 32, kernel_size=(1, 1), stride=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
            (cv2): Conv(
              (conv): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
              (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
              (act): SiLU()
            )
          )
        )
      )
      (classifier): Sequential(
        (0): Linear(in_features=802816, out_features=100, bias=True)
        (1): ReLU()
        (2): Linear(in_features=100, out_features=4, bias=True)
      )
    )

    三、训练函数

    3.1编写训练函数

    1. # 训练循环
    2. def train(dataloader, model, loss_fn, optimizer):
    3. size = len(dataloader.dataset) # 训练集的大小
    4. num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
    5. train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
    6. for X, y in dataloader: # 获取图片及其标签
    7. X, y = X.to(device), y.to(device)
    8. # 计算预测误差
    9. pred = model(X) # 网络输出
    10. loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
    11. # 反向传播
    12. optimizer.zero_grad() # grad属性归零
    13. loss.backward() # 反向传播
    14. optimizer.step() # 每一步自动更新
    15. # 记录acc与loss
    16. train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
    17. train_loss += loss.item()
    18. train_acc /= size
    19. train_loss /= num_batches
    20. return train_acc, train_loss

    3.2编写测试函数

    1. def test (dataloader, model, loss_fn):
    2. size = len(dataloader.dataset) # 测试集的大小
    3. num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
    4. test_loss, test_acc = 0, 0
    5. # 当不进行训练时,停止梯度更新,节省计算内存消耗
    6. with torch.no_grad():
    7. for imgs, target in dataloader:
    8. imgs, target = imgs.to(device), target.to(device)
    9. # 计算loss
    10. target_pred = model(imgs)
    11. loss = loss_fn(target_pred, target)
    12. test_loss += loss.item()
    13. test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
    14. test_acc /= size
    15. test_loss /= num_batches
    16. return test_acc, test_loss

    3.3正式训练

    1. import copy
    2. optimizer = torch.optim.Adam(model.parameters(), lr= 1e-4)
    3. loss_fn = nn.CrossEntropyLoss() # 创建损失函数
    4. epochs = 20
    5. train_loss = []
    6. train_acc = []
    7. test_loss = []
    8. test_acc = []
    9. best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标
    10. for epoch in range(epochs):
    11. model.train()
    12. epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
    13. model.eval()
    14. epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
    15. # 保存最佳模型到 best_model
    16. if epoch_test_acc > best_acc:
    17. best_acc = epoch_test_acc
    18. best_model = copy.deepcopy(model)
    19. train_acc.append(epoch_train_acc)
    20. train_loss.append(epoch_train_loss)
    21. test_acc.append(epoch_test_acc)
    22. test_loss.append(epoch_test_loss)
    23. # 获取当前的学习率
    24. lr = optimizer.state_dict()['param_groups'][0]['lr']
    25. template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    26. print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,
    27. epoch_test_acc*100, epoch_test_loss, lr))
    28. # 保存最佳模型到文件中
    29. PATH = './best_model.pth' # 保存的参数文件名
    30. torch.save(model.state_dict(), PATH)
    31. print('Done')

    3.4完整训练代码:

    1. import torch
    2. import torch.nn as nn
    3. import torchvision.transforms as transforms
    4. import torchvision
    5. from torchvision import transforms, datasets
    6. import os,PIL,pathlib,warnings
    7. warnings.filterwarnings("ignore") #忽略警告信息
    8. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    9. print(device)
    10. def main():
    11. import os,PIL,random,pathlib
    12. data_dir = 'D:/P8/weather_photos/'
    13. data_dir = pathlib.Path(data_dir)
    14. data_paths = list(data_dir.glob('*'))
    15. classeNames = [str(path).split("\\")[3] for path in data_paths]
    16. print(classeNames)
    17. # 关于transforms.Compose的更多介绍可以参考:https://blog.csdn.net/qq_38251616/article/details/124878863
    18. train_transforms = transforms.Compose([
    19. transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
    20. # transforms.RandomHorizontalFlip(), # 随机水平翻转
    21. transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    22. transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
    23. mean=[0.485, 0.456, 0.406],
    24. std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
    25. ])
    26. test_transform = transforms.Compose([
    27. transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸
    28. transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    29. transforms.Normalize( # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
    30. mean=[0.485, 0.456, 0.406],
    31. std=[0.229, 0.224, 0.225]) # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
    32. ])
    33. total_data = datasets.ImageFolder("D:/P8/weather_photos/",transform=train_transforms)
    34. print(total_data)
    35. print(total_data.class_to_idx)
    36. train_size = int(0.8 * len(total_data))
    37. test_size = len(total_data) - train_size
    38. train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
    39. print(train_dataset, test_dataset)
    40. batch_size = 4
    41. train_dl = torch.utils.data.DataLoader(train_dataset,
    42. batch_size=batch_size,
    43. shuffle=True,
    44. num_workers=1)
    45. test_dl = torch.utils.data.DataLoader(test_dataset,
    46. batch_size=batch_size,
    47. shuffle=True,
    48. num_workers=1)
    49. import torch.nn.functional as F
    50. def autopad(k, p=None): # kernel, padding
    51. # Pad to 'same'
    52. if p is None:
    53. p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
    54. return p
    55. class Conv(nn.Module):
    56. # Standard convolution
    57. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
    58. super().__init__()
    59. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
    60. self.bn = nn.BatchNorm2d(c2)
    61. self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
    62. def forward(self, x):
    63. return self.act(self.bn(self.conv(x)))
    64. class Bottleneck(nn.Module):
    65. # Standard bottleneck
    66. def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
    67. super().__init__()
    68. c_ = int(c2 * e) # hidden channels
    69. self.cv1 = Conv(c1, c_, 1, 1)
    70. self.cv2 = Conv(c_, c2, 3, 1, g=g)
    71. self.add = shortcut and c1 == c2
    72. def forward(self, x):
    73. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
    74. class C3(nn.Module):
    75. # CSP Bottleneck with 3 convolutions
    76. def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
    77. super().__init__()
    78. c_ = int(c2 * e) # hidden channels
    79. self.cv1 = Conv(c1, c_, 1, 1)
    80. self.cv2 = Conv(c1, c_, 1, 1)
    81. self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
    82. self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
    83. def forward(self, x):
    84. return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
    85. class model_K(nn.Module):
    86. def __init__(self):
    87. super(model_K, self).__init__()
    88. # 卷积模块
    89. self.Conv = Conv(3, 32, 3, 2)
    90. # C3模块1
    91. self.C3_1 = C3(32, 64, 3, 2)
    92. # 全连接网络层,用于分类
    93. self.classifier = nn.Sequential(
    94. nn.Linear(in_features=802816, out_features=100),
    95. nn.ReLU(),
    96. nn.Linear(in_features=100, out_features=4)
    97. )
    98. def forward(self, x):
    99. x = self.Conv(x)
    100. x = self.C3_1(x)
    101. x = torch.flatten(x, start_dim=1)
    102. x = self.classifier(x)
    103. return x
    104. device = "cuda" if torch.cuda.is_available() else "cpu"
    105. print("Using {} device".format(device))
    106. model = model_K().to(device)
    107. print(model)
    108. # 训练循环
    109. def train(dataloader, model, loss_fn, optimizer):
    110. size = len(dataloader.dataset) # 训练集的大小
    111. num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
    112. train_loss, train_acc = 0, 0 # 初始化训练损失和正确率
    113. for X, y in dataloader: # 获取图片及其标签
    114. X, y = X.to(device), y.to(device)
    115. # 计算预测误差
    116. pred = model(X) # 网络输出
    117. loss = loss_fn(pred, y) # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
    118. # 反向传播
    119. optimizer.zero_grad() # grad属性归零
    120. loss.backward() # 反向传播
    121. optimizer.step() # 每一步自动更新
    122. # 记录acc与loss
    123. train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
    124. train_loss += loss.item()
    125. train_acc /= size
    126. train_loss /= num_batches
    127. return train_acc, train_loss
    128. def test (dataloader, model, loss_fn):
    129. size = len(dataloader.dataset) # 测试集的大小
    130. num_batches = len(dataloader) # 批次数目, (size/batch_size,向上取整)
    131. test_loss, test_acc = 0, 0
    132. # 当不进行训练时,停止梯度更新,节省计算内存消耗
    133. with torch.no_grad():
    134. for imgs, target in dataloader:
    135. imgs, target = imgs.to(device), target.to(device)
    136. # 计算loss
    137. target_pred = model(imgs)
    138. loss = loss_fn(target_pred, target)
    139. test_loss += loss.item()
    140. test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
    141. test_acc /= size
    142. test_loss /= num_batches
    143. return test_acc, test_loss
    144. import copy
    145. optimizer = torch.optim.Adam(model.parameters(), lr= 1e-4)
    146. loss_fn = nn.CrossEntropyLoss() # 创建损失函数
    147. epochs = 20
    148. train_loss = []
    149. train_acc = []
    150. test_loss = []
    151. test_acc = []
    152. best_acc = 0 # 设置一个最佳准确率,作为最佳模型的判别指标
    153. for epoch in range(epochs):
    154. model.train()
    155. epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
    156. model.eval()
    157. epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
    158. # 保存最佳模型到 best_model
    159. if epoch_test_acc > best_acc:
    160. best_acc = epoch_test_acc
    161. best_model = copy.deepcopy(model)
    162. train_acc.append(epoch_train_acc)
    163. train_loss.append(epoch_train_loss)
    164. test_acc.append(epoch_test_acc)
    165. test_loss.append(epoch_test_loss)
    166. # 获取当前的学习率
    167. lr = optimizer.state_dict()['param_groups'][0]['lr']
    168. template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    169. print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss,
    170. epoch_test_acc*100, epoch_test_loss, lr))
    171. # 保存最佳模型到文件中
    172. PATH = './best_model.pth' # 保存的参数文件名
    173. torch.save(model.state_dict(), PATH)
    174. print('Done')
    175. if __name__ == '__main__':
    176. main()

  • 相关阅读:
    Linux进阶-文件
    中小制造企业为什么要做MES智能化升级?看了本文你就知道了
    【经验分享】统计学算法大全及方法适用场景(必看)
    始祖双碳新闻 | 2022年8月22日-8月26日碳中和行业早知道
    AIGC-Animate Anyone阿里的图像到视频 角色合成的框架-论文解读
    2001-2020年全国31省城镇居民人均可支配收入/居民实际收入水平
    Spring依赖注入提示:Field injection is not recommended
    windows计划任务的配置文件
    springboot项目的配置和基本案例练习
    Mac上使用M1或M2芯片的设备安装Node.js时遇到一些问题,比如卡顿或性能问题
  • 原文地址:https://blog.csdn.net/qq_60245590/article/details/133591174