如何使用 PyTorch 在预训练模型上添加新层? (给出了 Keras 示例。)

Posted

技术标签:

【中文标题】如何使用 PyTorch 在预训练模型上添加新层? (给出了 Keras 示例。)【英文标题】:How can I add new layers on pre-trained model with PyTorch? (Keras example given.) 【发布时间】:2021-02-14 06:09:06 【问题描述】:

我正在与Keras 合作,并尝试分析由具有有意义权重的某些层和具有随机初始化的某些层构建的模型对准确性的影响。

Keras:

我在加载方法上使用include_top = False 参数加载VGG19 预训练模型。

model = keras.applications.VGG19(include_top=False, weights="imagenet", input_shape=(img_width, img_height, 3))

PyTorch:

我加载 VGG19 预训练模型,直到与之前加载 Keras 的模型处于同一层。

model = torch.hub.load('pytorch/vision:v0.6.0', 'vgg19', pretrained=True)
new_base =  (list(model.children())[:-2])[0]

加载模型后,以下图像显示它们的摘要。 (Pytorch, Keras)

到目前为止没有任何问题。之后,我想在这些预训练模型上添加一个 Flatten 层和一个全连接层。我用 Keras 做到了,但用 PyTorch 做不到。

new_model.summary() 的输出是:

我的问题是,如何在PyTorch 中添加新层?

【问题讨论】:

new_base.add_module ? 这能回答你的问题吗? How to remove the last FC layer from a ResNet model in PyTorch? 【参考方案1】:

如果您只想替换分类器部分,您可以这样做。那就是:

model = torch.hub.load('pytorch/vision:v0.6.0', 'vgg19', pretrained=True)
model.classifier = nn.Linear(model.classifier[0].in_features, 4096)
print(model)

会给你:

之前:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (17): ReLU(inplace=True)
    (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (24): ReLU(inplace=True)
    (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (26): ReLU(inplace=True)
    (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (31): ReLU(inplace=True)
    (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (33): ReLU(inplace=True)
    (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (35): ReLU(inplace=True)
    (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)

之后:

VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (17): ReLU(inplace=True)
    (18): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (19): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (24): ReLU(inplace=True)
    (25): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (26): ReLU(inplace=True)
    (27): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (31): ReLU(inplace=True)
    (32): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (33): ReLU(inplace=True)
    (34): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (35): ReLU(inplace=True)
    (36): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  (classifier): Linear(in_features=25088, out_features=4096, bias=True)
)

另请注意,当您想要更改现有架构时,您有两个阶段。您首先获得您想要的模块(这就是您在那里所做的),然后您必须将其包装在 nn.Sequential 中,因为您的列表没有实现 forward(),因此您无法真正提供任何东西。它只是一个模块的集合。

所以你通常需要做这样的事情(例如):

features = nn.ModuleList(your_model.children())[:-1]
model = nn.Sequential(*features) 
# carry on with what other changes you want to perform on your model

请注意,如果您想创建一个新模型并打算像这样使用它:

output = model(imgs)

您需要在第二个序列中包装您的功能和新层。也就是说,做这样的事情:

features = nn.ModuleList(your_model.children())[:-1]
model_features = nn.Sequential(*features) 
some_more_layers = nn.Sequential(Layer1,
                                 Layer2, 
                                 ... ) 
                                 
model = nn.Sequential(model_features, 
                      some_more_layers)
#
output = model(imgs)

否则你必须做类似的事情:

features_output = model.features(imgs)
output = model.classifier(features_output)

【讨论】:

其实我不想使用模型作为分类器,我将使用模型作为特征提取器,我需要为每个图像提取(1,4096)个特征向量(从第一个 FC 层) .另一方面,当我这样做时,我想添加没有有意义权重的 FC 层(不属于 imagenet),FC 层应该具有在 PyTorch 中定义的默认权重。例如,在 Keras 中的模型上添加的 FC 层具有使用 He_initialization 而不是 imagenet 初始化的权重。 我不是说你想把它用作分类器,我说如果你想替换分类器很容易。如果您需要分类器之前的功能,只需使用model.features。如果您需要添加新图层,请按照我的方式进行。只需添加一个新层。它的权重是未初始化的。层初始化见this。也请参阅我的最新更新。【参考方案2】:

来自 PyTorch 教程"Finetuning TorchVision Models":

Torchvision 提供了八种不同长度的 VGG 版本,其中一些版本具有批量标准化层。在这里,我们使用带有批量标准化的 VGG-11。输出层类似于Alexnet,即

(classifier): Sequential(
   ...
   (6): Linear(in_features=4096, out_features=1000, bias=True)
)

因此,我们使用相同的技术来修改输出层

model.classifier[6] = nn.Linear(4096,num_classes)

【讨论】:

以上是关于如何使用 PyTorch 在预训练模型上添加新层? (给出了 Keras 示例。)的主要内容,如果未能解决你的问题,请参考以下文章

在预训练模型前添加 Conv 层会产生 ValueError

在预训练的 pytorch 网络中加载单个图像

Huggingface 微调 - 如何在预训练的基础上构建自定义模型

pytorch如何给预训练模型添加新的层

如何加载部分预训练的 pytorch 模型?

向预先训练的说话人识别模型中添加新说话人