本篇主要介绍用Pytorch搭建神经网络的代码实现,在学习这些之前需要一些前置知识如下,如果不太了解的话可以先去B站看吴恩达系列的深度学习入门教程+CS231N课程。
Pytorch作为一款强大的深度学习框架,提供了大量的常用深度学习算子,本篇介绍一些常用的操作(其实完全可以看pytorch源码,里面的注释文档都做了很清晰的解释,我也只是翻译了一下,pytorch的源码写的非常清晰非常棒)。
卷积操作
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:是否有偏置量
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,记录训练过程中的均值和方差;
激活函数
torch.nn.ReLU(inplace=False)
inplace:是否将计算得到的值直接覆盖之前的值(可以加速运算)
torch.nn.LeakyReLU(negative_slope, inplace=False)
negative_slope:leaky的系数
torch.nn.Sigmoid()
没啥好说的
torch.nn.tanh()
也没啥好说的
池化层
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为向上取整,默认向上取整
全连接层
torch.nn.Linear(in_features, out_features, bias)
参考卷积,顾名思义即可
除此之外还有如softmax层、反卷积层、反池化层等操作这里没有介绍,只介绍了一部分基础操作,具体可以查看官方文档https://pytorch.org/docs/stable/nn.html
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类来实现,我们需要完成两个函数:
构造函数__init__:在这里规定网络里需要什么变量,什么算子
编程时注意模块化思想,比如ResNet中里经常重复的模块,我们将其模块化为ConvBlock类等,减少代码量。
推理函数 forward:规定网络的各个算子以什么方式计算
空间注意力以及通道注意力的实现
注意力有很多种,在目前的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)
定义网络的初始化
pytorch会自动对网络进行初始化,只是初始化的参数无规律且相差甚远。如果对于一个没有预训练的网络,有可能难以使网络收敛。因此,在声明网络后,有必要手动对网络进行权重初始化。而一般采用正态分布的方式初始化权重。
例如下面这段代码放到__init()__函数中,就可以将网络所有的卷积核以kaiming分布的形式进行初始化。
for m in self.modules():
if isinstance(m, nn.Conv2d):
kaiming_normal_(m.weight, 0.1)
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预训练过的,非常方便。