共享CNN骨干是深度学习中使用的一种技术,在多个相关任务之间共享卷积神经网络(CNN)骨干。在许多场景中,我们有几个相关的任务,如物体检测、语义分割和实例分割,它们需要不同的输出头和损失,但共享一个共同的特征提取骨干(可以是预训练好的CNN模型,如ResNet没有后两层)。
共享CNN骨干的想法是只训练一次CNN骨干,然后在所有相关任务中共享其参数。这可以大大减少需要训练的参数数量,提高模型的泛化能力。
下面是一个在物体检测和语义分割任务中共享CNN骨干的例子。
首先,我们创建一个CNN骨干,如ResNet或VGG网络,并在大型数据集(如ImageNet)上进行预训练,以学习对多种任务有用的通用特征。
然后,我们在CNN主干上添加一个检测头,用于物体检测,其中包括一组全连接层,预测图像中物体的边界框和类别概率。
接下来,我们在CNN主干上添加了一个用于语义分割的分割头,其中包括一组卷积层,预测图像的密集像素级标签。
在训练过程中,我们首先在一个较小的数据集上对共享的CNN主干进行微调,该数据集与两个任务都有关,例如COCO,以学习对物体检测和语义分割都有用的特定任务特征。
在PyTorch中共享CNN骨干可以通过定义一个CNN骨干模型并将其输出特征重用于多个相关任务来实现。下面是一个在两个相关任务(即物体检测和语义分割)之间共享CNN主干的例子。
import torch
import torch.nn as nn
import torchvision.models as models
class CNNBackbone(nn.Module):
def __init__(self, backbone_name='resnet50', pretrained=True):
super(CNNBackbone, self).__init__()
self.backbone = getattr(models, backbone_name)(pretrained=pretrained)
# Remove the last classification layer from the backbone
# 移除了上一步与训练好的模型的最后一个分类层(resnet用来分类的)
self.backbone = nn.Sequential(*list(self.backbone.children())[:-2])
def forward(self, x):
# Forward pass through the CNN backbone
out = self.backbone(x)
return out
class ObjectDetectionHead(nn.Module):
def __init__(self, num_classes=2):
super(ObjectDetectionHead, self).__init__()
self.num_classes = num_classes
# Add detection head layers on top of the CNN backbone
self.fc1 = nn.Linear(2048, 256)
self.fc2 = nn.Linear(256, self.num_classes + 4) # num_classes + 4 for bounding box regression
def forward(self, x):
# Forward pass through the detection head
out = nn.functional.relu(self.fc1(x))
out = self.fc2(out)
return out
class SemanticSegmentationHead(nn.Module):
def __init__(self, num_classes=2):
super(SemanticSegmentationHead, self).__init__()
self.num_classes = num_classes
# Add segmentation head layers on top of the CNN backbone
self.conv1 = nn.Conv2d(2048, 512, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(512, self.num_classes, kernel_size=1, padding=0)
def forward(self, x):
# Forward pass through the segmentation head
out = nn.functional.relu(self.conv1(x))
out = self.conv2(out)
return out
# Create a shared CNN backbone model
backbone = CNNBackbone()
# Create an object detection head model that shares the CNN backbone
detection_head = ObjectDetectionHead()
detection_head.fc1 = backbone.backbone[-1][-1] # Reuse the last CNN layer from the backbone
detection_head.fc2 = nn.Linear(256, detection_head.num_classes + 4) # Add the last detection head layer
# Create a semantic segmentation head model that shares the CNN backbone
segmentation_head = SemanticSegmentationHead()
segmentation_head.conv1 = backbone.backbone[-1][-1] # Reuse the last CNN layer from the backbone
segmentation_head.conv2 = nn.Conv2d(512, segmentation_head.num_classes, kernel_size=1, padding=0) # Add the last segmentation head layer
# Train the detection and segmentation heads separately while sharing the CNN backbone
# ...
在这个例子中,我们定义了一个CNNBackbone模型,它从预先训练好的ResNet-50模型中移除最后一个分类层,并输出最后一个卷积层的特征图。然后,我们定义了两个特定任务的头,即ObjectDetectionHead和SemanticSegmentationHead,它们从CNNBackbone中获取输出特征并分别产生物体检测和语义分割预测。
为了在两个任务之间共享CNN主干,我们重新使用CNNBackbone的最后一个卷积层作为检测和分割头的输入。我们更新检测和分割头模型,将最后一个检测头和分割头层分别添加到骨干网的最后一个CNN层之上。这样一来,CNN骨干层只需训练一次,其学到的特征可以在两个相关的任务之间共享,使训练更有效率,并防止过度拟合。
self.backbone = getattr(models, backbone_name)(pretrained=pretrained)
在代码 self.backbone = getattr(models, backbone_name)(pretrained=pretrained)中,getattr 被用来根据 backbone_name 参数的字符串值,从 torchvision.models 模块动态加载一个预训练模型。
backbone_name 参数指定了应该被加载的预训练模型的名称。例如,如果 backbone_name 被设置为 'resnet50',ResNet-50 模型将被加载。
getattr(models, backbone_name) 相当于调用 models.backbone_name,但是使用 getattr 允许代码将属性名称指定为一个字符串。
pretrained 参数用于指定是否加载指定模型的预训练权重。如果 pretrained=True,预训练的权重将被加载,否则模型将以随机权重初始化。
然后,预训练的模型被分配给类的self.backbone属性,它可以被用作各种下游任务的特征提取器,如物体检测或图像分割。
self.num_classes = num_classes
在代码self.num_classes = num_classes中,self.num_classes是该类的一个实例变量,用于存储模型应该预测的类的数量。
num_classes是传递给类的构造函数的一个参数,它指定了模型应该预测的类的数量。例如,如果该模型在一个有10个对象类的数据集上进行对象检测的训练,那么num_classes将被设置为10。
通过将num_classes赋值给self.num_classes,类的数量被存储为类的一个实例变量,可以被类的任何方法访问。这个变量可以被模型的各个部分使用,比如分类头,来决定输出单元的数量。
self.backbone = nn.Sequential(*list(self.backbone.children())[:-2])
在我提供的PyTorch代码片段中,self.backbone = nn.Sequential(*list(self.backbone.children())[:-2])被用来删除CNNBackbone类中预训练的CNN模型的最后一个平均池化层和分类层。下面是它的工作原理。
self.backbone指的是定义在CNNBackbone类中的一个预先训练好的CNN模型。
self.backbone.children() 返回CNN模型各层的一个迭代器。
list(self.backbone.children()) 将该迭代器转换为CNN模型层的列表。
list(self.backbone.children())[:-2] 对列表进行切分,删除CNN模型的最后一层和倒数第二层,这通常是一个分类层和 (avgpool): AdaptiveAvgPool2d层,它将最后一个卷积层的输出映射到一个固定长度的向量。
nn.Sequential(*list(self.backbone.children())[:-2])创建一个新的PyTorch Sequential对象,其中包含预训练的CNN模型的所有层,除了最后一个分类层。这实际上是删除了CNN模型的最后一层。
通过移除预训练的CNN模型的最后一个分类层,我们可以使用最后一个卷积层的输出作为其他神经网络的输入,如物体检测头或分割头。当我们想在不同的计算机视觉任务中重复使用预先训练好的CNN模型的学习特征时,这可能很有用。
在我之前提供的示例代码中,我们使用self.backbone来创建一个用于物体检测和语义分割的共享CNN主干。通过移除CNN模型的最后一个分类层,我们可以在物体检测头和分割头中重复使用CNN骨干的学习特征。这可以使训练更有效率,并有助于防止过度拟合。
detection_head.fc1 = backbone.backbone[-1][-1]
在我提供的PyTorch代码片段中,detection_head.fc1 = backbone.backbone[-1][-1]被用来与物体检测头共享CNN骨干网的最后一个卷积层。下面是它的工作原理。
backbone是CNNBackbone类的一个实例,它定义了一个预先训练好的CNN模型,并删除了最后一个分类层。backbone对象有一个backbone属性,指的是预训练的CNN模型。
backbone.backbone是一个PyTorch Sequential对象,它包含了预训练的CNN模型的所有层。
backbone.backbone[-1]指的是预训练的CNN模型的最后一层。在这个例子中,这是一个全连接层,将最后一个卷积层的输出映射到一个特征向量。
backbone.backbone[-1][-1]指的是CNN模型最后一层中全连接层的最后一个权重层。在这个例子中,这是连接最后一个卷积层的输出到特征向量的权重矩阵。
detection_head.fc1是物体检测头中的一个全连接层,它将CNN骨干层最后一个卷积层产生的特征图作为输入。
通过将backbone.backbone[-1][-1]分配给detection_head.fc1,我们将检测头中全连接层的权重矩阵替换为CNN模型最后一层中全连接层的权重矩阵。这意味着CNN模型最后一个卷积层的输出被直接传递给检测头,允许检测头重新使用CNN骨干的学习特征。
通过这种方式,物体检测头可以利用CNN骨干网的学习特征,而不必重新训练整个CNN模型。这可以使训练更加有效,并有助于防止过度拟合。
检测头
在计算机视觉中,物体检测和语义分割是两项重要的任务,涉及分析图像以识别物体及其边界。
对象检测是识别和定位图像中的对象的任务。物体检测模型的输出通常是一组包围图像中的物体的边界框,以及一个识别物体类别的标签(例如,"人"、"车"、"狗 "等)。
另一方面,语义分割是为图像中的每个像素分配一个类别标签的任务。分割模型的输出是一个像素级的分类图,表明每个像素的类别(例如,"道路"、"天空"、"建筑"、"人 "等等)。
在我之前提供的示例代码中,我们使用一个预先训练好的CNN模型作为物体检测和语义分割的共享骨干。对象检测头和分割头是附加在共享主干上的神经网络模块。这些模块将共享骨干网的输出作为输入,并执行物体检测或语义分割的具体任务。
对象检测头通常由一个或多个全连接层组成,将共享骨干网提取的特征映射到一组边界框和类别标签。另一方面,分割头通常由一个或多个卷积层组成,将特征映射到像素级的分类图。
通过在物体检测头和分割头之间共享CNN主干,我们可以利用CNN模型的学习特征来完成这两项任务,而不必从头开始重新训练整个模型。这可以使训练更有效率,并有助于防止过度拟合。
resnet50
在PyTorch中,您可以使用torchvision库中的torchvision.models.resnet50()函数创建一个ResNet-50模型。下面是一个例子的代码片段。
import torch
import torchvision.models as models
# Create a ResNet-50 model
resnet50 = models.resnet50(pretrained=True)
# Print the model architecture
print(resnet50)
这段代码创建了一个具有预训练权重的ResNet-50模型,然后将模型架构打印到控制台。
PyTorch中的ResNet-50模型架构由多个层组成,包括卷积层、批量归一化层、ReLU激活层和最大池层。该模型的具体结构如下。
colab输出结果
ResNet(
(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)
(layer1): Sequential(
(0): Bottleneck(
(conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(layer2): Sequential(
(0): Bottleneck(
(conv1): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(3): Bottleneck(
(conv1): Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(128, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(layer3): Sequential(
(0): Bottleneck(
(conv1): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(512, 1024, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(3): Bottleneck(
(conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(4): Bottleneck(
(conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(5): Bottleneck(
(conv1): Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(256, 1024, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(layer4): Sequential(
(0): Bottleneck(
(conv1): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
(downsample): Sequential(
(0): Conv2d(1024, 2048, kernel_size=(1, 1), stride=(2, 2), bias=False)
(1): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
)
)
(1): Bottleneck(
(conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
(2): Bottleneck(
(conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
(bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)
(bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(relu): ReLU(inplace=True)
)
)
(avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
(fc): Linear(in_features=2048, out_features=1000, bias=True)
)
getattr怎么用
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self, num_classes):
super(MyModel, self).__init__()
self.fc1 = nn.Linear(512, num_classes)
self.fc2 = nn.Linear(256, num_classes)
要使用 getattr 访问 MyModel 实例的 fc1 属性,你可以这样做
model = MyModel(num_classes=10)
fc1_layer = getattr(model, 'fc1')
这将返回对应于模型的 fc1 层的 nn.Linear 对象。
注意 getattr 的第一个参数是对象(在这里是模型实例),第二个参数是属性的字符串名称('fc1')。
也可以从另外的角度理解
import torch.nn as nn
import torchvision.models as models
# Define the shared CNN backbone
backbone = models.resnet50(pretrained=True)
backbone = nn.Sequential(*list(backbone.children())[:-2])
# Define the object detection head
detection_head = nn.Sequential(
nn.Conv2d(2048, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 4 + num_classes, kernel_size=3, stride=1, padding=1),
)
# Define the segmentation head
segmentation_head = nn.Sequential(
nn.Conv2d(2048, 512, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, num_classes, kernel_size=3, stride=1, padding=1),
)
# Connect the shared backbone to the two heads
detection_model = nn.Sequential(
backbone,
detection_head,
)
segmentation_model = nn.Sequential(
backbone,
segmentation_head,
)
在这个例子中,我们创建了一个ResNet50骨干网的共享实例,并删除了最后两层,即全局平均集合层和全连接层。然后我们定义了两个头,一个用于物体检测,一个用于分割,并使用nn.Sequential将它们连接到共享主干上。这就创建了两个模型,检测模型和分割模型,它们共享同一个CNN主干,但执行不同的任务。
通过共享CNN主干,我们可以减少模型中的参数数量,提高训练效率,同时在物体检测和分割任务上仍然取得良好的性能。