第J4周:ResNet与DenseNet结合探索
- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
方法 1:特征级融合
特征级融合通常是在 DenseNet 和 ResNet 的最后一层卷积之后,取各自的特征图,将它们拼接(concatenate)或相加,然后输入至一个全连接层进行最终分类。
方法 2:级联(Cascade)结构
在级联结构中,可以将一个模型的输出作为另一个模型的输入。例如,将 DenseNet 的特征传入 ResNet 的特征层继续处理,然后分类。这种方法要求两网络的输入和输出尺寸匹配。
方法 3:并联并投票决策
在这种方法中,DenseNet 和 ResNet 的分类输出独立进行预测,通过平均、加权平均或投票的方式得到最终分类结果。这种方法可行,但比较简单。
这里我们使用的方法1进行修改的
# @Date : 2024-10-30 18:13 # @Author : yjl
import torch
import torch.nn as nn
import torch.nn.functional as F
device = "cuda" if torch.cuda.is_available() else "cpu"
class Identity_block(nn.Module):
def __init__(self, in_channels, filters, kernel_size, stage, block):
super(Identity_block, self).__init__()
filters1, filters2, filters3 = filters
name_base = str(stage) + block + '_identity_block'
self.conv1 = nn.Conv2d(in_channels, filters1, kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(filters1)
self.conv2 = nn.Conv2d(filters1, filters2, kernel_size=kernel_size, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(filters2)
self.conv3 = nn.Conv2d(filters2, filters3, kernel_size=1, stride=1, padding=0, bias=False)
self.bn3 = nn.BatchNorm2d(filters3)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
identity = x
out = self.relu(self.bn1(self.conv1(x)))
out = self.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += identity
out = self.relu(out)
return out
class Conv_block(nn.Module):
def __init__(self, in_channels, filters, kernel_size, stage, block, strides=2):
super(Conv_block, self).__init__()
filters1, filters2, filters3 = filters
res_name_base = str(stage) + block + '_conv_block_res_'
name_base = str(stage) + block + '_conv_block_'
self.conv1 = nn.Conv2d(in_channels, filters1, kernel_size=1, stride=strides, bias=False)
self.bn1 = nn.BatchNorm2d(filters1)
self.conv2 = nn.Conv2d(filters1, filters2, kernel_size=kernel_size, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(filters2)
self.conv3 = nn.Conv2d(filters2, filters3, kernel_size=1, stride=1, bias=False)
self.bn3 = nn.BatchNorm2d(filters3)
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, filters3, kernel_size=1, stride=strides, bias=False),
nn.BatchNorm2d(filters3)
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
identity = self.shortcut(x)
out = self.relu(self.bn1(self.conv1(x)))
out = self.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += identity
out = self.relu(out)
return out
class Resnet50(nn.Module):
def __init__(self, input_shape=(3, 224, 224), classes=1000):
super(Resnet50, self).__init__()
self.in_channels = 64
self.conv1 = nn.Conv2d(input_shape[0], self.in_channels, kernel_size=7, padding=3, stride=2, bias=False)
self.bn1 = nn.BatchNorm2d(self.in_channels)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer([64, 64, 256], blocks=3, stage=2, stride=1)
self.layer2 = self._make_layer([128, 128, 512], blocks=4, stage=3, stride=2)
self.layer3 = self._make_layer([256, 256, 1024], blocks=6, stage=4, stride=2)
self.layer4 = self._make_layer([512, 512, 2048], blocks=3, stage=5, stride=2)
self.avgpool = nn.AvgPool2d((7, 7))
self.fc = nn.Linear(2048, classes)
def _make_layer(self, filters, blocks, stage, stride):
layers = []
# first block is a ConvBlock with stride
layers.append(Conv_block(self.in_channels, filters, kernel_size=3, stage=stage, block='a', strides=stride))
self.in_channels = filters[2]
# remaining is a IdentityBlocks
for b in range(1, blocks):
layers.append(Identity_block(self.in_channels, filters, kernel_size=3, stage=stage, block=chr(97 + b)))
return nn.Sequential(*layers) ##
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(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 = torch.flatten(x, 1)
x = self.fc(x)
return x
from collections import OrderedDict
import torch
import torch.nn as nn
import torch.nn.functional as F
class DenseLayer(nn.Sequential):
def __init__(self, in_channel, growth_rate, bn_size, drop_rate):
super(DenseLayer, self).__init__()
self.add_module('norm1', nn.BatchNorm2d(in_channel))
self.add_module('relu1', nn.ReLU(inplace=True))
self.add_module('conv1', nn.Conv2d(in_channel, bn_size * growth_rate,
kernel_size=1, stride=1, bias=False))
self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate))
self.add_module('relu2', nn.ReLU(inplace=True))
self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate,
kernel_size=3, stride=1, padding=1, bias=False))
self.drop_rate = drop_rate
def forward(self, x):
new_feature = super(DenseLayer, self).forward(x)
if self.drop_rate > 0:
new_feature = F.dropout(new_feature, p=self.drop_rate, training=self.training)
return torch.cat([x, new_feature], 1)
class DenseBlock(nn.Sequential):
def __init__(self, num_layers, in_channel, bn_size, growth_rate, drop_rate):
super(DenseBlock, self).__init__()
for i in range(num_layers):
layer = DenseLayer(in_channel + i * growth_rate, growth_rate, bn_size, drop_rate)
self.add_module('denselayer%d' % (i + 1,), layer)
''' Transition layer between two adjacent DenseBlock '''
class Transition(nn.Sequential):
def __init__(self, in_channel, out_channel):
super(Transition, self).__init__()
self.add_module('norm', nn.BatchNorm2d(in_channel))
self.add_module('relu', nn.ReLU(inplace=True))
self.add_module('conv', nn.Conv2d(in_channel, out_channel,
kernel_size=1, stride=1, bias=False))
self.add_module('pool', nn.AvgPool2d(2, stride=2))
class DenseNet(nn.Module):
def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16), init_channel=64,
bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):
super(DenseNet, self).__init__()
# first Conv2d
self.features = nn.Sequential(OrderedDict([
('conv0', nn.Conv2d(3, init_channel, kernel_size=7, stride=2, padding=3, bias=False)),
('norm0', nn.BatchNorm2d(init_channel)),
('relu0', nn.ReLU(inplace=True)),
('pool0', nn.MaxPool2d(3, stride=2, padding=1))
]))
# DenseBlock
num_features = init_channel
for i, num_layers in enumerate(block_config):
block = DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
self.features.add_module('denseblock%d' % (i + 1), block)
num_features += num_layers * growth_rate
if i != len(block_config) - 1:
transition = Transition(num_features, int(num_features * compression_rate))
self.features.add_module('transition%d' % (i + 1), transition)
num_features = int(num_features * compression_rate)
# final BN+ReLU
self.features.add_module('norm5', nn.BatchNorm2d(num_features))
self.features.add_module('relu5', nn.ReLU(inplace=True))
# 分类层
self.classifier = nn.Linear(num_features, num_classes)
# 参数初始化
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1)
elif isinstance(m, nn.Linear):
nn.init.constant_(m.bias, 0)
def forward(self, x):
x = self.features(x)
x = F.avg_pool2d(x, 7, stride=1).view(x.size(0), -1)
x = self.classifier(x)
return x
class DenseNetResNetCombined(nn.Module):
def __init__(self, densenet, resnet, num_classes):
super(DenseNetResNetCombined, self).__init__()
# 分离DenseNet和ResNet的特征提取部分
self.densenet_features = nn.Sequential(*list(densenet.features.children()))
self.resnet_features = nn.Sequential(
resnet.conv1,
resnet.bn1,
resnet.relu,
resnet.maxpool,
resnet.layer1,
resnet.layer2,
resnet.layer3,
resnet.layer4
)
# 初次前向传播,以获取拼接的实际维度
sample_input = torch.randn(1, 3, 224, 224)
densenet_out = self.densenet_features(sample_input)
resnet_out = self.resnet_features(sample_input)
# 获取拼接后的实际特征维度
self.combined_features_dim = densenet_out.shape[1] + resnet_out.shape[1]
# 调整分类层的输入维度为拼接后的通道数
self.fc = nn.Linear(self.combined_features_dim, num_classes)
def forward(self, x):
# 获取DenseNet和ResNet的特征
densenet_out = self.densenet_features(x)
densenet_out = F.avg_pool2d(densenet_out, kernel_size=7).view(densenet_out.size(0), -1)
resnet_out = self.resnet_features(x)
resnet_out = F.avg_pool2d(resnet_out, kernel_size=7).view(resnet_out.size(0), -1)
# 拼接特征
combined_features = torch.cat([densenet_out, resnet_out], dim=1)
# 分类
out = self.fc(combined_features)
return out
# 初始化DenseNet和ResNet模型
densenet = DenseNet(init_channel=64, growth_rate=32, block_config=(6, 12, 24, 16), num_classes=1000)
resnet = Resnet50(classes=1000)
# 创建联合模型
model = DenseNetResNetCombined(densenet, resnet, num_classes=2)
model.to(device)
from torchsummary import summary
summary(model, (3, 224, 224))
并在J3周乳腺癌数据集上进行了测试,因为只更换了模型,训练的框架都是一样的,这里就没有重复放代码了。只在上方将模型的融合放在了上面。
结果对比
可以发现我们改进的模型相比较单独的densenet121模型,可以增加1个百分点。