CNN基础论文 精读+复现---- ResNet(二)

准备工作

昨天把论文读完了,CNN基础论文 精读+复现---- ResNet(一)
,今天用pytorch复现一下。

之前论文中提到过ResNet有很多种,这里复现一下ResNet-18和ResNet34吧,这俩基本一样。

这两种残差块,左边是 18 和34层的,50,101,152用右边的残差快。

ResNet-18,只需要左边的残差块,这俩残差块都实现一下,整体网络实现ResNet-18。
在这里插入图片描述

BasicBlock块

按照上面左边的图, 结构很清晰: 卷积 -> BN -> Relu -> 卷积 -> BN。
这几层写出来先:

nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True),
nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(outchannel)

然后定义一下右路的恒等映射,这个其实就是个空就行了,里面什么层都不放。

self.shortcut = nn.Sequential()

之后还有个很重要的东西,就是之前说过的 残差F(X)与自身输入x维度必须一致。

这里直接通过1 * 1 卷积核进行卷积升降维就ok,也记得要加BN。

if stride != 1 or inchannel != outchannel:
            #shortcut,这里为了跟2个卷积层的维度结构一致。
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(outchannel)
            )

最后在汇总的时候是 两路相加然后再Relu:

out = self.left(x) 
out = out + self.shortcut(x)
out = nn.relu(out)

放到一起就有了BasicBlock块:


import torch
import torch.nn as nn
#残差块ResBlock
class ResBlock(nn.Module):
    def __init__(self, inchannel, outchannel, stride=1):
        super(ResBlock, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(outchannel)
        )
        self.shortcut = nn.Sequential()
        if stride != 1 or inchannel != outchannel:
            #shortcut,这里为了跟2个卷积层的维度结构一致。
            self.shortcut = nn.Sequential(
                nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(outchannel)
            )
    def forward(self, x):
        out = self.left(x)
        out = out + self.shortcut(x)
        out = nn.relu(out)

        return out

另外一个 BottleNeck块 就不写了,跟这个差不多 就是都了一层而已。

ResNet-18、34网络结构

根据论文中的这张图:
在这里插入图片描述
ResNet-18和34 一共6个部分: 开头的卷积层,然后中间4块残差块,最后一个全连接层。

先将各层堆叠起来:

	    # 一开始的卷积+池化层
self.pre = nn.Sequential(
	nn.Conv2d(3, 64, 7, 2, 3, bias=False),
	nn.BatchNorm2d(64),
	nn.ReLU(inplace=True),
	nn.MaxPool2d(3, 2, 1)
	)
 
        #各层的残差块
self.layer1 = self._make_layer(64, 64, blocks[0])
self.layer2 = self._make_layer(64, 128, blocks[1], stride=2)
self.layer3 = self._make_layer(128, 256, blocks[2], stride=2)
self.layer4 = self._make_layer(256, 512, blocks[3], stride=2)
 
        # 最后的全连接层
self.fc = nn.Linear(512, num_classes)

将重复的残差块放到一起去:

def _make_layer(self, inchannel, outchannel, block_num, stride=1):
       # 重复的残差块
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU()
        )
 
        layers = []
        layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))
 
        for i in range(1, block_num):
            layers.append(ResidualBlock(outchannel, outchannel))
        return nn.Sequential(*layers)

改写一下上面实现的BasicBlock残差块让他适应 18和34层的ResNet。

class ResidualBlock(nn.Module):
 

    def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),
            nn.BatchNorm2d(outchannel)
        )
        self.right = shortcut

    def forward(self, x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)

上面这段代码没事好说的,就是稍微改动了一下 最开始实现的残差块的参数。

整体forward一下:

def forward(self, x):
        x = self.pre(x)

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

        x = F.avg_pool2d(x, 7)
        x = x.view(x.size(0), -1)
        return self.fc(x)

完整代码:

将上面的汇总到一起看一下完整的 ResNet-18、34代码。
这里没有加数据集。

torchsummary 也是pytorch里的可视化方法。

import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from torchsummary import summary


class ResidualBlock(nn.Module):
    """
    实现子module: Residual Block
    """

    def __init__(self, inchannel, outchannel, stride=1, shortcut=None):
        super(ResidualBlock, self).__init__()
        self.left = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU(inplace=True),
            nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),
            nn.BatchNorm2d(outchannel)
        )
        self.right = shortcut

    def forward(self, x):
        out = self.left(x)
        residual = x if self.right is None else self.right(x)
        out += residual
        return F.relu(out)


class ResNet(nn.Module):
    """
    实现主module:ResNet34
    ResNet34包含多个layer,每个layer又包含多个Residual block
    用子module来实现Residual block,用_make_layer函数来实现layer
    """

    def __init__(self, blocks, num_classes=1000):
        super(ResNet, self).__init__()
        self.model_name = 'resnet34'

        # 前几层: 图像转换
        self.pre = nn.Sequential(
            nn.Conv2d(3, 64, 7, 2, 3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(3, 2, 1))

        # 重复的layer,分别有3,4,6,3个residual block
        self.layer1 = self._make_layer(64, 64, blocks[0])
        self.layer2 = self._make_layer(64, 128, blocks[1], stride=2)
        self.layer3 = self._make_layer(128, 256, blocks[2], stride=2)
        self.layer4 = self._make_layer(256, 512, blocks[3], stride=2)

        # 分类用的全连接
        self.fc = nn.Linear(512, num_classes)

    def _make_layer(self, inchannel, outchannel, block_num, stride=1):
        """
        构建layer,包含多个residual block
        """
        shortcut = nn.Sequential(
            nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),
            nn.BatchNorm2d(outchannel),
            nn.ReLU()
        )

        layers = []
        layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))

        for i in range(1, block_num):
            layers.append(ResidualBlock(outchannel, outchannel))
        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.pre(x)

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

        x = F.avg_pool2d(x, 7)
        x = x.view(x.size(0), -1)
        return self.fc(x)

def ResNet18():
    return ResNet([2, 2, 2, 2])

def ResNet34():
    return ResNet([3, 4, 6, 3])


if __name__ == '__main__':
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    model = ResNet34()
    model.to(device)
    summary(model, (3, 224, 224))

输出各层的信息:

在这里插入图片描述

小总结

其实使用的话完全没必要自己写,可以直接 用API调用ResNet的预训练模型,然后修改最后的全连接层做迁移学习就行了,像下面这样。

import torchvision
model = torchvision.models.resnet18(pretrained=True)

总的来说ResNet代码复现起来非常潦草,可以看到我都不像之前那样加入数据集和可视化结果了,代码写一半,感觉自己太菜了,差好多东西, 我得继续去学习了,后面论文先不看了。完整代码我放到Github上了一份:https://github.com/shitbro6/paper

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度不学习!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值