有了前面的基础,我么就可以搭建语义分割的主干网络,对他进行训练,我们需要Dataloerd来提供数据,下面就来一一介绍dataloder及主干网络的搭建
**

datasets生成

**

我们前面定义好dataset类之后,生成一个VOCdatasets就非常容易了,我们只需对他进行实例化,传入相应参数就可以了。这里我们直接把训练集和验证集的datasets全部实例化,root参数传入的是数据集路径,num_classes是最后分类的数量,crop_size是裁剪的大小。

resnet50每一层特征图大小_ide


**

dataloader生成

**

到这里可以发现,我们的datasest只能根据index一张一张的读取图片,这样效率太低了。所以我们还必须实例一个dataloader帮助我们以batch的形式读取图片,以此提升效率。创建这个也非常简单,只用导入Dataloader模块,把datasets传入进去,填写相应参数即可。其中train_datasets是我们之前生成的datases,batch_size是每个批次传入多少张图片,num_work是使用多线程进行读取图片(这里建议windows用户不要填大于1的数,可能会导致崩溃),shuffle表示是否打乱数据,drop_last这个参数是是否丢弃最后无法被batch_size整除的图片,这里最好填入True,不然有可能导致程序出错。

resnet50每一层特征图大小_ide_02

主干网搭建

PSPNet使用的主干网络为ResNet50,是一个非常经典的网络。由于我们进行的是语义分割任务,我们希提取更多浅层的纹理特征,所以在ResNet50前面部分增加了几个卷积层。下面我们依次介绍搭建ResNet50需要的模块。
Conv3x3就是将卷积封装了一下,在我们调用的时候可以省略一些参数。

resnet50每一层特征图大小_ide_03


残差块

下面搭建残差结构,它是ResNet50网络最基本的单元,由1x1 3x3 1x1三个卷积即相应的BN,ReLU构成。

值得注意的是,作者对输入与输出的通道数进行了巧妙设计,使其能够容易堆叠。每个残差块都有shorcut连接,即forward函数中的residual部分,如果输入通道数与输出通道数相等,且步长为1,那么residual直接与残差块的结果相加,如果不相等,且步长不等于1,那么residual升级为downsample模块,通过1x1卷积调整大小和通道数后在与残差块的输出相加。

resnet50每一层特征图大小_python_04


resnet50每一层特征图大小_python_05

ResNet50

resnet50每一层特征图大小_ide_06


有了上面的残差块,剩下要做的就是按照条件堆叠出ResNet50即可。关于不同层级的ResNet应该如何堆叠,何凯明的论文中有详细介绍,表中也出了详细的配置方式。前面提到了,我们这里使用加深的ResNet50,相比于原论文,我们使用的resnet50多了两个3x3的卷积层,并且将通道数增加了,更多的浅层特征是有利于语义分割任务的。

resnet50每一层特征图大小_ide_07


接下来就是网络的主体部分,通过调用maker_layer来搭建layer1,2,3,4。它们重复的次数也就是上表中不同残差块后的x3,x4,x6,x3。

resnet50每一层特征图大小_python_08


接下来我们进入make_layer,它需要传入4个参数,第一个参数是我们之前定义的残差块,第二个参数是一个通道数,第三个参数是该layer会将残差块重复记次,第四个参数是步长。由于四个layer的堆叠方式相同,只是次数不一样而已,我们就只以layer2的生成进行举例明。通过上面表可知layer1的输出的通道数是256,那么layer2的输入通道数应该和它一样,此时self.inplanes是等于256(644)的。
具体的,进入函数,第一步就是一个判断,我们layer的步长为2,所以这个判断是满足的,就会生成一个downsample模块。由于第一个残差块的第一个1x1卷积层的输入是128通道的,而最后一个1x1卷积的通道数是512通道的,无法直接使用shorcut,所以需要这个downsample模块对输入进行通道和大小的调整。
注意这个时候self.inplanes和plane是256和128 而block.expansion一直是4。
接着往下走,我们会把self.inplane,planes,stride,downsample传入残差模块生成第一个残差块,每个残差块的输入是它的第一个参数也就是self.inplanes(256),输出是inplane
4(1284=512)。同时slef.inplanes在这里就变成plane4=128*4=512。后面再通过for循环依次添加,每次只需要将我们传入的值代进去即可。这里由于我们添加了第一个块,所以for循环从1开始,其余的块都是完全一样,只需要我们跟着代码的思路进去理解一遍即可明白其中的奥秘。

resnet50每一层特征图大小_算法_09


具体的细节部分,我们可以打印网络的结果显示出来,验证我们想法。下图为layer2的前两个block,我们可以清晰的发现只用第一个残差块有downsample,为什么其它的都没有呢?这是因为,我们residual没有写在初始化函数里,只存在于forward函数中,所以这里是不会显示的,单其实还是存在的。

resnet50每一层特征图大小_算法_10

分类层

resnet50每一层特征图大小_ide_11


虽然PSPNet是一个端到端的网络,并没有使用分类层,但是我们这里还是顺便搭建完整,避免网络出现错误的时候可以调用这个网络进行测试。它的结构非常简单,就是将最后一层全局平均池化,再通过线性层即可。

整个代码

import math
import os
import torch
import torch.nn as nn
import torch.utils.model_zoo as model_zoo
BatchNorm2d = nn.BatchNorm2d



def conv3x3(in_planes, out_planes, stride=1):
    "定义带padding的3x3卷积"
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
                     padding=1, bias=False)

class Bottleneck(nn.Module):
    "搭建resnet使用的残差模块,如果输入通道数不等于输出通道数,会使用1x1卷积调整通道相加"
    "其它情况直接通过residual相加"
    expansion = 4
    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
        self.bn1 = BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
                               padding=1, bias=False)
        self.bn2 = BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
        self.bn3 = BatchNorm2d(planes * 4)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

    def forward(self, x):
        residual = 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)
        if self.downsample is not None:
            residual = self.downsample(x)
        out += residual
        out = self.relu(out)
        return out


class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes=1000):
        "使用加深的ResNet,在最开始的地方增加了几个卷积层,提取更多浅层特征"
        self.inplanes = 128
        super(ResNet, self).__init__()
        self.conv1 = conv3x3(3, 64, stride=2)
        self.bn1 = BatchNorm2d(64)
        self.relu1 = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(64, 64)
        self.bn2 = BatchNorm2d(64)
        self.relu2 = nn.ReLU(inplace=True)
        self.conv3 = conv3x3(64, 128)
        self.bn3 = BatchNorm2d(128)
        self.relu3 = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        "调用_maker_layer函数 循环生成多个残差结构"
        self.layer1 = self._make_layer(block, 64, layers[0])
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(512 * block.expansion, num_classes)
        # 给模型进行权值初始化
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
    def _make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        # # 判断是否使用1x1卷积升维,默认不采用 只有在输入与输出通道不相等  或者步长不为1的情况下使用
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride, bias=False),
                BatchNorm2d(planes * block.expansion),
            )
        layers = []
        # 添加第一个残差块,因为第一个残差块是比较特殊的,可能存在步长为2或者downsample模块,所以单独添加
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        # 跟新self.inplanes 参数,后面的残差块结构相同,直接利用for循环添加
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))
        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.relu1(self.bn1(self.conv1(x)))
        x = self.relu2(self.bn2(self.conv2(x)))
        x = self.relu3(self.bn3(self.conv3(x)))
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x


# 下载预训练权重的链接
model_urls = {
    'resnet50': 'http://sceneparsing.csail.mit.edu/model/pretrained_resnet/resnet50-imagenet.pth',
}

def load_url(url, model_dir='./model_data', map_location=None):
    if not os.path.exists(model_dir):
        os.makedirs(model_dir)
    filename = url.split('/')[-1]
    cached_file = os.path.join(model_dir, filename)
    if os.path.exists(cached_file):
        return torch.load(cached_file, map_location=map_location)
    else:
        return model_zoo.load_url(url,model_dir=model_dir)


def resnet50(pretrained=True, **kwargs):
    root = r'D:\1Apython\Pycharm_pojie\Semantic_segmentation\pytorch_segmentation-master\pretrained\resnet50s-a75c83cf.pth'
    model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs)
    if pretrained:
        # dict = torch.load(root)
        # for k,v in dict.items():
        #     print(k)
        # model.load_state_dict()
        # print('权值已加载')
        model.load_state_dict(load_url(model_urls['resnet50']))
    return model


if __name__ == '__main__':
    r = resnet50()
    print(r)

Dataloader

# 实例化 daloader
train_datasets = VOCDataset(root=voc_root,split='train',num_classes=num_classes,base_size=520,crop_size=crop_size)
val_datasets = VOCDataset(root=voc_root,split='val',num_classes=num_classes,base_size=520,crop_size=crop_size)
train_dataloader = DataLoader(train_datasets,batch_size=batch_size,num_workers=1,shuffle=True,drop_last=True)
val_dataloader = DataLoader(train_datasets,batch_size=batch_size,num_workers=1,shuffle=True,drop_last=True)

resnet50每一层特征图大小_深度学习_12