pytorch实现Resnet

pytorch实现Resnet

标签: pytorch resnet


网络结果及其基本单元

对于Resnet来说基本,只要把其基本结构抽离出来即可,其他的其实和以前我们的普通卷积神经网络很像。而Resnet中最基本的结构就是一个残差块如下:

image

可以看出一个残差块分为左右两部分,左边其实就是普通卷积操作,而右边什么都没有(不过在实际中会有一个卷积),然后输出就是两个的和。
所以一个对于一个输入x [batch,c,h,w] 来说经过左边可能变为了[batch,c1,h1,w1],同样右边也使用其他的卷积变为[batch,c1,h1,w1],然后加起来即可。

之所以要做一个短连接的作用是避免网络层数过深导致的梯度爆炸或者消失,对于一个残差块,我们能保证最坏的结果就是左边的卷积不起作用了,这样无论网络再深,其实只相当于还是x,所以就能保证不造成梯度问题。具体细节可以参考论文。

pytorch实现

定义残差块

    class ResidualBlock(nn.Module):
        """实现一个残差块"""
        def __init__(self,inchannel,outchannel,stride = 1,shortcut = None):

            super().__init__()
            self.left = nn.Sequential(
                nn.Conv2d(inchannel,outchannel,3,stride,1,bias=False),
                nn.BatchNorm2d(outchannel),
                nn.ReLU(),
                nn.Conv2d(outchannel,outchannel,3,1,1,bias=False), # 这个卷积操作是不会改变w h的
                nn.BatchNorm2d(outchannel)
            )
            self.right = shortcut

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

可以看出就是恩威左右两个部分,左边就是普通的卷积操作,而右边当前没有定义,可能是None,如果是None则相当于直接是原来的x不经过任何操作,也或者是经过一些操作。

在左边的定义中第二个卷积层的参数是3 1 1这个卷积操作是不会改变w h的。

Resnet

有了残差块,就可以直接定义Resnet了。

    class ResNet(nn.Module):
        """实现主reset"""

        def __init__(self,num_class=1000):
            super().__init__()
            # 前面几层普通卷积
            self.pre = nn.Sequential(
                nn.Conv2d(3,64,7,2,3,bias=False),
                nn.BatchNorm2d(64),
                nn.ReLU(),
                nn.MaxPool2d(3,2,1)
            )

            # 重复layer,每个layer都包含多个残差块 其中第一个残差会修改w和c,其他的残差块等量变换
            # 经过第一个残差块后大小为 w-1/s +1 (每个残差块包括left和right,而left的k = 3 p = 1,right的shortcut k=1,p=0)
            self.layer1 = self._make_layer(64,128,3) # s默认是1 ,所以经过layer1后只有channle变了
            self.layer2 = self._make_layer(128,256,4,stride=2) # w-1/s +1
            self.layer3 = self._make_layer(256,512,6,stride=2)
            self.layer4 = self._make_layer(512,512,3,stride=2)
            self.fc = nn.Linear(512,num_class)

        def _make_layer(self,inchannel,outchannel,block_num,stride = 1):

            # 刚开始两个cahnnle可能不同,所以right通过shortcut把通道也变为outchannel
            shortcut = nn.Sequential(
                # 之所以这里的k = 1是因为,我们在ResidualBlock中的k =3,p=1所以最后得到的大小为(w+2-3/s +1)
                # 即(w-1 /s +1),而这里的w = (w +2p-f)/s +1 所以2p -f = -1 如果p = 0则f = 1
                nn.Conv2d(inchannel,outchannel,1,stride,bias=False),
                nn.BatchNorm2d(outchannel)
            )

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

            # 之后的cahnnle同并且 w h也同,而经过ResidualBloc其w h不变,
            for i in range(1,block_num):
                layers.append(ResidualBlock(outchannel,outchannel))

            return nn.Sequential(*layers)


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

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

            x = F.avg_pool2d(x,7) # 如果图片大小为224 ,经过多个ResidualBlock到这里刚好为7,所以做一个池化,为1,
                                # 所以如果图片大小小于224,都可以传入的,因为经过7的池化,肯定为1,但是大于224则不一定
            print(x.shape)
            x = x.view(x.size(0),-1)
            return self.fc(x)

这段代码中关键在于_make_layer,其实就是多个残差块构成了一个大层,所以叫做_make_layer

这个网络刚开始是一些普通的卷积操作,可以看做一些预处理,然后开始做几个残差层。在做残差层的时候,关键在于如果大小不匹配怎么办,所以在我们的残差层里面,如果有多个残差块的时候,我们第一个残差块是带shorcut的,因为第一个ResidualBlock是传入了stride的,所以可能造成w和h的变化,而右边经过shortcut则通过计算是刚好能和左边保持一致的,所以能直接加,而后面的几个残差块是没有传入stride的,经过计算发现left的结果会保持输入的w h 都不变,所以shortcut可以为None。

其实对于这个代码主要是计算的问题,要学会如何计算卷积的w和h,其他的就没什么了,对于一个残差层,包括多个残差块,第一个残差块可以改变w h,而后面的残差块则是不变的。

以下是一个简单的PyTorch实现ResNet的代码示例: ```python import torch import torch.nn as nn # 定义ResNet基础块 class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.relu = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) ) def forward(self, x): residual = self.shortcut(x) x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.conv2(x) x = self.bn2(x) x += residual x = self.relu(x) return x # 定义ResNet网络 class ResNet(nn.Module): def __init__(self, block, layers, num_classes=10): super(ResNet, self).__init__() self.in_channels = 64 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self.make_layer(block, 64, layers[0], stride=1) 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.avg_pool = nn.AdaptiveAvgPool2d((1,1)) self.fc = nn.Linear(512, num_classes) def make_layer(self, block, out_channels, blocks, stride=1): layers = [] layers.append(block(self.in_channels, out_channels, stride)) self.in_channels = out_channels for i in range(1, blocks): layers.append(block(out_channels, out_channels)) return nn.Sequential(*layers) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.max_pool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avg_pool(x) x = x.view(x.size(0), -1) x = self.fc(x) return x # 实例化ResNet网络 def resnet18(): return ResNet(BasicBlock, [2, 2, 2, 2]) # 测试网络 model = resnet18() print(model) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值