基于Diffusion Model的数据增强方法应用——毕业设计 其二

题目简介

笔者个人的毕业设计课题如下:

简介:使用预训练的Diffusion Model图像生成模型生成图像,将这些生成的图像作为扩充训练集加入到2D目标检测器、2D图像分类器的训练过程。深度学习是数据驱动的,随着数据量的扩充,能够提高检测器、分类器的鲁棒性、准确性。
建议的baseline:
分类:ResNet
检测:YOLO

可以看到,给的题目难度还是比较轻松的;本次毕设的全过程会以周为单位采用博客的形式记录下来。

前言

本来本周的计划是搭建和运行跑通扩散模型的,但是由于个人原因,这周大部分的时间并不在学校;没法用实验室的服务器跑,只用我这张本子的2060跑想来还是有些吃力的,所以不得已临时修改了计划,改成先完成ResNet的部分,当然,由于ResNet比较简单,所以也会多一些原理上的说明。

从ResNet开始

在这里插入图片描述
如果谈及什么是ResNet,其实其核心就在于上面这张图里提到的残差模块的设计和应用。ResNet是为了解决“网络退化”问题,所谓的“网络退化”是指随着网络层次的加深,到达一定深度后,网络模型的性能不升反降,这被称为“网络退化”。随着网络层次的加深,网络变得难以训练,不易收敛,原因在于随着网络层次的加深,深层梯度难以反向传播到浅层,即使传播到浅层,浅层的梯度值也小的可怜。

谈到这里,我们不得不进行一些简单的数学推导;因为残差本身实际上也是在和损失函数作博弈
在这里插入图片描述
先将上面提到的残差模块简单的示意如上,我们将A和B的卷积层简单的看成函数A和B。那么我们也可以简单的推出:Y=x+B(A(X))
在这里插入图片描述
这里再考虑反向传播过程中,损失函数对X的偏导数如上图
可以看到如果没有残差结构,3个偏导数的乘积随着网络层次的加深会非常小,梯度到达饱和状态,网络不容易收敛。
反之,加入残差连接后,即使3个偏导数的乘积再小,但+1的操作使梯度值大大增加,非线性激活函数也到达非饱和区,这样做的好处在于:即使网络做的非常深,网络也是容易收敛、容易训练的

粗鲁的解释残差结构的贡献

如果还是不好理解的话,我们可以用一种粗鲁的方式来解释
在这里插入图片描述

在本来的模型算法中,我们只试图迭代训练左边的基础部分

但事实证明,如果只是这样的话,当网络的深度不断加深时,整个网络的性能反而不断的下降

这是由于实际上并非每个单元/节点都对不同的输入产生所需要的敏感性,事实上大部分的节点实际上是【收效甚微】的

而在过去,我们加深网络时;单纯的提高深度,会让这些【无效】的节点所占比例急剧上升,这时整体的性能反而下降了

所以我们现在加上右边的部分

我们在上面提到过,这时输出已经变成了F(x)+x,残差结构的伟大之处是在于

当网络的性能已经趋向于最好时,对于那些无用的节点,网络会尽可能的降低F(x)部分的权重,乃至于降低到0,只保留X,使得网络在保有性能的同时不受影响

回到实验部分

注意,接下来的环节建立在你已经搭建了win系统的Anconda的前提下进行

在这里插入图片描述
创建虚拟环境,非必要(图里单词拼写错误,这里勘误一下,是create)
在这里插入图片描述
确认安装
这一步因为网络原因提升失败或者提示找不到包,可以去搜索换源方法,因为各个镜像网站时不时存在扑街现象,一个源用不了时可以试试其他的,一般总有能用的源
在这里插入图片描述
激活虚拟环境并安装pytorch
注意,不注明版本号默认安装最新版本
在这里插入图片描述
同样,包的下载过慢时,也可以尝试换源解决。
在这里插入图片描述
验证pytorch安装完毕
注:按ctrl+z退出python
在这里插入图片描述
划分数据集
在这里插入图片描述
开始训练
在这里插入图片描述
测试分类

模型部分代码

import torch.nn as nn
import torch


# 定义ResNet18/34的残差结构,为2个3x3的卷积
class BasicBlock(nn.Module):
    # 判断残差结构中,主分支的卷积核个数是否发生变化,不变则为1
    expansion = 1

    # init():进行初始化,申明模型中各层的定义
    # downsample=None对应实线残差结构,否则为虚线残差结构
    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, padding=1, bias=False)
        # 使用批量归一化
        self.bn1 = nn.BatchNorm2d(out_channel)
        # 使用ReLU作为激活函数
        self.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
                               kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channel)
        self.downsample = downsample

    # forward():定义前向传播过程,描述了各层之间的连接关系
    def forward(self, x):
        # 残差块保留原始输入
        identity = x
        # 如果是虚线残差结构,则进行下采样
        if self.downsample is not None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)
        # -----------------------------------------
        out = self.conv2(out)
        out = self.bn2(out)
        # 主分支与shortcut分支数据相加
        out += identity
        out = self.relu(out)

        return out


# 定义ResNet50/101/152的残差结构,为1x1+3x3+1x1的卷积
class Bottleneck(nn.Module):
    # expansion是指在每个小残差块内,减小尺度增加维度的倍数,如64*4=256
    # Bottleneck层输出通道是输入的4倍
    expansion = 4

    # init():进行初始化,申明模型中各层的定义
    # downsample=None对应实线残差结构,否则为虚线残差结构,专门用来改变x的通道数
    def __init__(self, in_channel, out_channel, stride=1, downsample=None,
                 groups=1, width_per_group=64):
        super(Bottleneck, self).__init__()

        width = int(out_channel * (width_per_group / 64.)) * groups

        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,
                               kernel_size=1, stride=1, bias=False)
        # 使用批量归一化
        self.bn1 = nn.BatchNorm2d(width)
        # -----------------------------------------
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,
                               kernel_size=3, stride=stride, bias=False, padding=1)
        self.bn2 = nn.BatchNorm2d(width)
        # -----------------------------------------
        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel * self.expansion,
                               kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(out_channel * self.expansion)
        # 使用ReLU作为激活函数
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    # forward():定义前向传播过程,描述了各层之间的连接关系
    def forward(self, x):
        # 残差块保留原始输入
        identity = x
        # 如果是虚线残差结构,则进行下采样
        if self.downsample is not None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)
        # 主分支与shortcut分支数据相加
        out += identity
        out = self.relu(out)

        return out


# 定义ResNet类
class ResNet(nn.Module):
    # 初始化函数
    def __init__(self,
                 block,
                 blocks_num,
                 num_classes=1000,
                 include_top=True,
                 groups=1,
                 width_per_group=64):
        super(ResNet, self).__init__()
        self.include_top = include_top
        # maxpool的输出通道数为64,残差结构输入通道数为64
        self.in_channel = 64

        self.groups = groups
        self.width_per_group = width_per_group

        self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,
                               padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(self.in_channel)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 浅层的stride=1,深层的stride=2
        # block:定义的两种残差模块
        # block_num:模块中残差块的个数
        self.layer1 = self._make_layer(block, 64, blocks_num[0])
        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)
        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)
        self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)
        if self.include_top:
            # 自适应平均池化,指定输出(H,W),通道数不变
            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
            # 全连接层
            self.fc = nn.Linear(512 * block.expansion, num_classes)
        # 遍历网络中的每一层
        # 继承nn.Module类中的一个方法:self.modules(), 他会返回该网络中的所有modules
        for m in self.modules():
            # isinstance(object, type):如果指定对象是指定类型,则isinstance()函数返回True
            # 如果是卷积层
            if isinstance(m, nn.Conv2d):
                # kaiming正态分布初始化,使得Conv2d卷积层反向传播的输出的方差都为1
                # fan_in:权重是通过线性层(卷积或全连接)隐性确定
                # fan_out:通过创建随机矩阵显式创建权重
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')

    # 定义残差模块,由若干个残差块组成
    # block:定义的两种残差模块,channel:该模块中所有卷积层的基准通道数。block_num:模块中残差块的个数
    def _make_layer(self, block, channel, block_num, stride=1):
        downsample = None
        # 如果满足条件,则是虚线残差结构
        if stride != 1 or self.in_channel != channel * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(channel * block.expansion))

        layers = []
        layers.append(block(self.in_channel,
                            channel,
                            downsample=downsample,
                            stride=stride,
                            groups=self.groups,
                            width_per_group=self.width_per_group))
        self.in_channel = channel * block.expansion

        for _ in range(1, block_num):
            layers.append(block(self.in_channel,
                                channel,
                                groups=self.groups,
                                width_per_group=self.width_per_group))
        # Sequential:自定义顺序连接成模型,生成网络结构
        return nn.Sequential(*layers)

    # forward():定义前向传播过程,描述了各层之间的连接关系
    def forward(self, x):
        # 无论哪种ResNet,都需要的静态层
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        # 动态层
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        if self.include_top:
            x = self.avgpool(x)
            x = torch.flatten(x, 1)
            x = self.fc(x)

        return x

# ResNet()中block参数对应的位置是BasicBlock或Bottleneck
# ResNet()中blocks_num[0-3]对应[3, 4, 6, 3],表示残差模块中的残差数
# 34层的resnet
def resnet34(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet34-333f7ec4.pth
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)


# 50层的resnet
def resnet50(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet50-19c8e357.pth
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)


# 101层的resnet
def resnet101(num_classes=1000, include_top=True):
    # https://download.pytorch.org/models/resnet101-5d3b4d8f.pth
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)

总结

本周完成了ResNet相关部分的理论解释和实验内容运行,下周预计会把diffusion Model部分的实验内容完成配置并尝试跑通;希望环境不要是太奇怪的那种,不然配置和查报错查缺漏就得搞个一天。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值