<filename>resnet18_rgb/model.py
"""
主要包括ResNet18的网络设置，模型等等。
关于ResNet18的网络结构，参照该文章：https://zhuanlan.zhihu.com/p/157134695
个人感觉其存在叙述不准确的地方，另有参考：https://www.jianshu.com/p/085f4c8256f1
"""


import torch.nn as nn
import torch.nn.functional as F
import torch


class MyResNet18(nn.Module):
    def __init__(self, class_num):
        super(MyResNet18, self).__init__()

        self.now_channels = 64

        # 第一层是独立于ResBlock的
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            # 这一层将输入从3*224*224变成64*112*112
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
            # to 64*56*56
            nn.BatchNorm2d(64),
            nn.ReLU()
        )
        self.resblock1 = ResBlock(64, 64, stride=1)
        self.resblock2 = ResBlock(64, 64, stride=1)

        # DownSampling
        self.resblock3 = ResBlock(64, 128, stride=2)
        self.resblock4 = ResBlock(128, 128, stride=1)

        self.resblock5 = ResBlock(128, 256, stride=2)
        self.resblock6 = ResBlock(256, 256, stride=1)

        self.resblock7 = ResBlock(256, 512, stride=2)
        self.resblock8 = ResBlock(512, 512, stride=1)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        # 512 * 1 * 1

        self.full_connect = nn.Linear(512, class_num)

    def forward(self, x):
        out = self.conv1(x)
        out = self.resblock1(out)
        out = self.resblock2(out)

        out = self.resblock3(out)
        out = self.resblock4(out)

        out = self.resblock5(out)
        out = self.resblock6(out)

        out = self.resblock7(out)
        out = self.resblock8(out)

        out = self.avgpool(out)
        out = torch.flatten(out, start_dim=1, end_dim=-1)
        # print(out.shape)

        out = self.full_connect(out)
        return out


class ResBlock(nn.Module):
    """
    该类代表基本的残差块。
    """
    def __init__(self, in_channels, out_channels, stride=1):
        """
        :param in_channels:
        :param out_channels:
        :param stride:
        需要定义一个基本的残差块，
        其中主要包含的是两个连续的Conv层。
        """
        super(ResBlock, self).__init__()
        self.learn_way = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(out_channels)
        )
        """
        这里需要注意，两个nn.Conv2d有一些区别
            首先是in_channels的不一样，这是因为第二个Conv2d直接承接了前一个的out_channels
            第二个是stride的区别，这是因为根据ResNet的规定，这两层应该是同样的out_channels，再根据Conv层的逻辑，只有padding=1，kernel_size=3，stride=1的时候，才能保持channels的大小不变，因此第二层Conv2d的stride恒为1
        """

        # 下面是残差中不经过变化的那一分支，简称shortcut
        self.shortcut = nn.Sequential()
        if in_channels != out_channels:
            """
            这里在参考实现中还加了一个判定or stride != 1
            但是个人认为该判定不是必须的
            """
            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):
        out = self.learn_way(x) + self.shortcut(x)
        out = F.relu(out)
        return out


<filename>resnet18_rgb/model.py
"""
主要包括ResNet18的网络设置，模型等等。
关于ResNet18的网络结构，参照该文章：https://zhuanlan.zhihu.com/p/157134695
个人感觉其存在叙述不准确的地方，另有参考：https://www.jianshu.com/p/085f4c8256f1
"""


import torch.nn as nn
import torch.nn.functional as F
import torch


class MyResNet18(nn.Module):
    def __init__(self, class_num):
        super(MyResNet18, self).__init__()

        self.now_channels = 64

        # 第一层是独立于ResBlock的
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            # 这一层将输入从3*224*224变成64*112*112
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
            # to 64*56*56
            nn.BatchNorm2d(64),
            nn.ReLU()
        )
        self.resblock1 = ResBlock(64, 64, stride=1)
        self.resblock2 = ResBlock(64, 64, stride=1)

        # DownSampling
        self.resblock3 = ResBlock(64, 128, stride=2)
        self.resblock4 = ResBlock(128, 128, stride=1)

        self.resblock5 = ResBlock(128, 256, stride=2)
        self.resblock6 = ResBlock(256, 256, stride=1)

        self.resblock7 = ResBlock(256, 512, stride=2)
        self.resblock8 = ResBlock(512, 512, stride=1)

        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        # 512 * 1 * 1

        self.full_connect = nn.Linear(512, class_num)

    def forward(self, x):
        out = self.conv1(x)
        out = self.resblock1(out)
        out = self.resblock2(out)

        out = self.resblock3(out)
        out = self.resblock4(out)

        out = self.resblock5(out)
        out = self.resblock6(out)

        out = self.resblock7(out)
        out = self.resblock8(out)

        out = self.avgpool(out)
        out = torch.flatten(out, start_dim=1, end_dim=-1)
        # print(out.shape)

        out = self.full_connect(out)
        return out


class ResBlock(nn.Module):
    """
    该类代表基本的残差块。
    """
    def __init__(self, in_channels, out_channels, stride=1):
        """
        :param in_channels:
        :param out_channels:
        :param stride:
        需要定义一个基本的残差块，
        其中主要包含的是两个连续的Conv层。
        """
        super(ResBlock, self).__init__()
        self.learn_way = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(out_channels)
        )
        """
        这里需要注意，两个nn.Conv2d有一些区别
            首先是in_channels的不一样，这是因为第二个Conv2d直接承接了前一个的out_channels
            第二个是stride的区别，这是因为根据ResNet的规定，这两层应该是同样的out_channels，再根据Conv层的逻辑，只有padding=1，kernel_size=3，stride=1的时候，才能保持channels的大小不变，因此第二层Conv2d的stride恒为1
        """

        # 下面是残差中不经过变化的那一分支，简称shortcut
        self.shortcut = nn.Sequential()
        if in_channels != out_channels:
            """
            这里在参考实现中还加了一个判定or stride != 1
            但是个人认为该判定不是必须的
            """
            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):
        out = self.learn_way(x) + self.shortcut(x)
        out = F.relu(out)
        return out


主要包括ResNet18的网络设置，模型等等。 关于ResNet18的网络结构，参照该文章：https://zhuanlan.zhihu.com/p/157134695 个人感觉其存在叙述不准确的地方，另有参考：https://www.jianshu.com/p/085f4c8256f1 # 第一层是独立于ResBlock的 # 这一层将输入从3*224*224变成64*112*112 # to 64*56*56 # DownSampling # 512 * 1 * 1 # print(out.shape) 该类代表基本的残差块。 :param in_channels:         :param out_channels:         :param stride:         需要定义一个基本的残差块，         其中主要包含的是两个连续的Conv层。 这里需要注意，两个nn.Conv2d有一些区别             首先是in_channels的不一样，这是因为第二个Conv2d直接承接了前一个的out_channels             第二个是stride的区别，这是因为根据ResNet的规定，这两层应该是同样的out_channels，再根据Conv层的逻辑，只有padding=1，kernel_size=3，stride=1的时候，才能保持channels的大小不变，因此第二层Conv2d的stride恒为1 # 下面是残差中不经过变化的那一分支，简称shortcut 这里在参考实现中还加了一个判定or stride != 1             但是个人认为该判定不是必须的