Pytorch教程(二)—— 网络搭建篇

前置知识

本篇主要介绍用Pytorch搭建神经网络的代码实现,在学习这些之前需要一些前置知识如下,如果不太了解的话可以先去B站看吴恩达系列的深度学习入门教程+CS231N课程。

  1. 基本神经网络知识
  2. 卷积网络基本知识

1. 基本操作

Pytorch作为一款强大的深度学习框架,提供了大量的常用深度学习算子,本篇介绍一些常用的操作(其实完全可以看pytorch源码,里面的注释文档都做了很清晰的解释,我也只是翻译了一下,pytorch的源码写的非常清晰非常棒)。

  1. 卷积操作
    torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

    • in_channels:输入通道数
    • out_channels:输出通道数
    • kernel_size: 卷积核大小
    • stride:卷积核滑动步长
    • padding: zero-padding大小
    • dilation:空洞卷积的空洞大小
    • groups:分组卷积的分组数
    • bias:是否有偏置量
  2. BN层
    torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

    • num_features:输入BN层的通道数;
    • eps: 分母中添加的一个值,目的是为了计算的稳定性,默认为:1e-05,避免分母为0
    • momentum:动态均值和动态方差所使用的动量。默认为0.1。一个用于运行过程中均值和方差的一个估计参数(我的理解是一个稳定系数,类似于SGD中的momentum的系数);
    • affine: 俺也不懂,也没用到过。
    • track_running_stats:布尔值,当设为true,记录训练过程中的均值和方差;
  3. 激活函数
    torch.nn.ReLU(inplace=False) inplace:是否将计算得到的值直接覆盖之前的值(可以加速运算)
    torch.nn.LeakyReLU(negative_slope, inplace=False) negative_slope:leaky的系数
    torch.nn.Sigmoid() 没啥好说的
    torch.nn.tanh() 也没啥好说的

  4. 池化层
    torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)

    • kernel_size:卷积核
    • stride:步长
    • padding: 填充
    • dilation:空洞率,同空洞卷积
    • return_indices:是否返回池化结果的位置(可用于反池化)
    • ceil_mode:如何处理形状,True为向上取整,默认向上取整
  5. 全连接层
    torch.nn.Linear(in_features, out_features, bias)参考卷积,顾名思义即可

除此之外还有如softmax层、反卷积层、反池化层等操作这里没有介绍,只介绍了一部分基础操作,具体可以查看官方文档https://pytorch.org/docs/stable/nn.html

2. 网络实现(ResNet为例)

class ConvBlock(nn.Module):
    def __init__(self, in_channel, f, filters, s):
        super(ConvBlock,self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel,F1,1,stride=s, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1,F2,f,stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2,F3,1,stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.shortcut_1 = nn.Conv2d(in_channel, F3, 1, stride=s, padding=0, bias=False)
        self.batch_1 = nn.BatchNorm2d(F3)
        self.relu_1 = nn.ReLU(True)
        
    def forward(self, X):
        X_shortcut = self.shortcut_1(X)
        X_shortcut = self.batch_1(X_shortcut)
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X    
    
class IndentityBlock(nn.Module):
    def __init__(self, in_channel, f, filters):
        super(IndentityBlock,self).__init__()
        F1, F2, F3 = filters
        self.stage = nn.Sequential(
            nn.Conv2d(in_channel,F1,1,stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F1),
            nn.ReLU(True),
            nn.Conv2d(F1,F2,f,stride=1, padding=True, bias=False),
            nn.BatchNorm2d(F2),
            nn.ReLU(True),
            nn.Conv2d(F2,F3,1,stride=1, padding=0, bias=False),
            nn.BatchNorm2d(F3),
        )
        self.relu_1 = nn.ReLU(True)
        
    def forward(self, X):
        X_shortcut = X
        X = self.stage(X)
        X = X + X_shortcut
        X = self.relu_1(X)
        return X
    
class ResNet(nn.Module):
    def __init__(self, n_class):
        super(ResNet,self).__init__()
        self.stage1 = nn.Sequential(
            nn.Conv2d(3,64,7,stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            nn.MaxPool2d(3,2,padding=1),
        )
        self.stage2 = nn.Sequential(
            ConvBlock(64, f=3, filters=[64, 64, 256], s=1),
            IndentityBlock(256, 3, [64, 64, 256]),
            IndentityBlock(256, 3, [64, 64, 256]),
        )
        self.stage3 = nn.Sequential(
            ConvBlock(256, f=3, filters=[128, 128, 512], s=2),
            IndentityBlock(512, 3, [128, 128, 512]),
            IndentityBlock(512, 3, [128, 128, 512]),
            IndentityBlock(512, 3, [128, 128, 512]),
        )
        self.stage4 = nn.Sequential(
            ConvBlock(512, f=3, filters=[256, 256, 1024], s=2),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
            IndentityBlock(1024, 3, [256, 256, 1024]),
        )
        self.stage5 = nn.Sequential(
            ConvBlock(1024, f=3, filters=[512, 512, 2048], s=2),
            IndentityBlock(2048, 3, [512, 512, 2048]),
            IndentityBlock(2048, 3, [512, 512, 2048]),
        )
        self.pool = nn.AvgPool2d(2,2,padding=1)
        self.fc = nn.Sequential(
            nn.Linear(8192,n_class)
        )
    
    def forward(self, X):
        out = self.stage1(X)
        out = self.stage2(out)
        out = self.stage3(out)
        out = self.stage4(out)
        out = self.stage5(out)
        out = self.pool(out)
        out = out.view(out.size(0),8192)
        out = self.fc(out)
        return out

实现神经网络参考上述代码,搭建神经网络通过继承nn.Module类来实现,我们需要完成两个函数:

  1. 构造函数__init__:在这里规定网络里需要什么变量,什么算子

    编程时注意模块化思想,比如ResNet中里经常重复的模块,我们将其模块化为ConvBlock类等,减少代码量。

  2. 推理函数 forward:规定网络的各个算子以什么方式计算

3. 拓展

  1. 空间注意力以及通道注意力的实现
    注意力有很多种,在目前的CV领域也非常流行,基本大同小异,下面给出一种代码仅供参考

    class ChannelAttention(nn.Module):
        def __init__(self, in_planes, ratio=16):
            super(ChannelAttention, self).__init__()
            self.avg_pool = nn.AdaptiveAvgPool2d(1)
            self.max_pool = nn.AdaptiveMaxPool2d(1)
    
            self.fc1   = nn.Conv2d(in_planes, in_planes // 16, 1, bias=False)
            self.relu1 = nn.ReLU()
            self.fc2   = nn.Conv2d(in_planes // 16, in_planes, 1, bias=False)
    
            self.sigmoid = nn.Sigmoid()
    
        def forward(self, x):
            avg_out = self.fc2(self.relu1(self.fc1(self.avg_pool(x))))
            max_out = self.fc2(self.relu1(self.fc1(self.max_pool(x))))
            out = avg_out + max_out
            return self.sigmoid(out)
    
    
    class SpatialAttention(nn.Module):
        def __init__(self, kernel_size=7):
            super(SpatialAttention, self).__init__()
    
            assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
            padding = 3 if kernel_size == 7 else 1
    
            self.conv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
            self.sigmoid = nn.Sigmoid()
    
        def forward(self, x):
            avg_out = torch.mean(x, dim=1, keepdim=True)
            max_out, _ = torch.max(x, dim=1, keepdim=True)
            x = torch.cat([avg_out, max_out], dim=1)
            x = self.conv1(x)
            return self.sigmoid(x)
    
    
  2. 定义网络的初始化
    pytorch会自动对网络进行初始化,只是初始化的参数无规律且相差甚远。如果对于一个没有预训练的网络,有可能难以使网络收敛。因此,在声明网络后,有必要手动对网络进行权重初始化。而一般采用正态分布的方式初始化权重。
    例如下面这段代码放到__init()__函数中,就可以将网络所有的卷积核以kaiming分布的形式进行初始化。

    for m in self.modules():
    	if isinstance(m, nn.Conv2d):
        	kaiming_normal_(m.weight, 0.1)
    
  3. timm库的使用
    可能中文圈很少会介绍这个库,本人也是在kaggle比赛上看到有很多人使用这个库才发现了这个宝藏。
    对于日常使用的模型如ResNet、VGG、MobileNet等,我们可以使用pytorch自带的torchvision.models来直接实现,但是对于EfficientNet、ResNest、ResNext、ViT等较新的backbone并不支持,这时候我们就需要用到这个库。
    安装很简单——pip install timm
    比如我们需要实现一个resnext网络:
    model = timm.create_model('gluon_resnext101_32x4d', pretrained=True, num_classes=NUM_FINETUNE_CLASSES)
    这样我们就不需要费力的实现网络,并且拿到的模型权重全部都是提前在ImageNet预训练过的,非常方便。

你可能感兴趣的:(深度学习)