本文将以几种常见方法为例,介绍如何进行Pytorch神经网络的模型融合:

1. 子模型串联(Sequential Concatenation)

在这个方法中,输入数据x首先通过FeatureExtractor(即:子模型1),处理后的结果再传递给Classifier(即:子模型2)。最后,返回Classifier的输出。这种方式允许将两个子模型串联起来,形成一个组合模型:

import torch.nn as nn

# 定义FeatureExtractor子模型,主要负责提取输入图像的特征
class FeatureExtractor(nn.Module):
    def __init__(self):
        super(FeatureExtractor, self).__init__()
        # 定义第一个全连接层:输入特征维度为784(28x28图像拉平后的大小),输出维度为512
        self.dense_layer1 = nn.Linear(784, 512)
        # 定义ReLU激活函数以引入非线性,增强模型的表达能力
        self.activation = nn.ReLU()
        # 定义第二个全连接层:进一步转换特征到256维
        self.dense_layer2 = nn.Linear(512, 256)
 
    def forward(self, x):
        # 将输入图像数据从二维图像格式拉平为一维向量格式
        x = x.view(-1, 784)
        # 数据流经第一个全连接层后,应用ReLU激活函数
        x = self.dense_layer1(x)
        x = self.activation(x)
        # 经过第二个全连接层
        x = self.dense_layer2(x)
        return x
 
# 定义Classifier子模型,用于基于提取的特征进行分类
class Classifier(nn.Module):
    def __init__(self):
        super(Classifier, self).__init__()
        # 定义第三个全连接层:从256维特征降至128维
        self.dense_layer3 = nn.Linear(256, 128)
        # 再次使用ReLU激活函数
        self.activation = nn.ReLU()
        # 定义第四个全连接层:最终将特征映射到10个输出类别(假设为10类分类问题)
        self.dense_layer4 = nn.Linear(128, 10)
 
    def forward(self, x):
        # 数据流经第三个全连接层,再次应用ReLU激活
        x = self.dense_layer3(x)
        x = self.activation(x)
        # 最后经过第四个全连接层,得到分类输出
        x = self.dense_layer4(x)
        return x
 
# 定义ImageClassifier组合模型,整合FeatureExtractor和Classifier完成从图像到分类标签的过程
class ImageClassifier(nn.Module):
    def __init__(self, feature_extractor, classifier):
        super(ImageClassifier, self).__init__()
        # 特征提取模块
        self.feature_extractor = feature_extractor
        # 分类模块
        self.classifier = classifier
 
    def forward(self, x):
        # 首先通过特征提取模块提取特征
        x = self.feature_extractor(x)
        # 然后通过分类模块得到分类结果
        x = self.classifier(x)
        return x
 
# 实例化FeatureExtractor和Classifier
feature_extractor = FeatureExtractor()
classifier = Classifier()

# 创建ImageClassifier组合模型,结合上述两个模型
image_classifier = ImageClassifier(feature_extractor, classifier)

在上方的子模型串联示例中,两个子模型(FeatureExtractor 和 Classifier)被顺序连接。首先,输入数据x被送入FeatureExtractor进行特征提取,处理后的结果再传递给Classifier进行分类。这种方式下,数据流是线性的,即从一个模块流向下一个模块,直到最终输出。这种模型结构在处理流水线式任务时非常有效,比如先提取特征,然后再做分类。

image_classifier = ImageClassifier(feature_extractor, classifier)

通过ImageClassifier类将FeatureExtractor和Classifier串联起来,ImageClassifier的forward方法首先通过feature_extractor提取特征,然后将这些特征传递给classifier获得最终的分类结果。

通过实现__init__和forward方法,ImageClassifier能够初始化子模型并定义它们的串联方式,这样就可以在训练和推理时作为一个单一的组合模型使用。

2. 并行拼接 (Parallel Concatenation)

在某些情况下,我们可能想要在特定维度上将多个模块的输出合并起来,比如在GoogLeNet中的Inception模块。

import torch
import torch.nn as nn

class ParallelConcatModule(nn.Module):
    def __init__(self):
        super(ParallelConcatModule, self).__init__()
        # 定义第一个并行分支
        self.branch1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=1),
            nn.ReLU(),
        )
        # 定义第二个并行分支
        self.branch2 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(),
        )
    
    def forward(self, x):
        # 分别计算两个分支
        out1 = self.branch1(x)
        out2 = self.branch2(x)
        # 在特征维度上合并这两个分支的输出
        out = torch.cat((out1, out2), dim=1)  # dim=1 表示在通道维度上进行合并
        return out

# 实例化模型
model = ParallelConcatModule()
# 假设输入图片大小为 [N, C, H, W] = [1, 3, 32, 32]
input_tensor = torch.randn(1, 3, 32, 32)
# 通过模型得到输出
output = model(input_tensor)

上方并行拼接示例展示了如何在特定维度上合并多个模块的输出。在这个例子中,有两个并行的分支(branch1 和 branch2),它们分别对相同的输入数据x进行处理。处理完成后,这两个分支的输出在通道维度(dim=1)上合并,使用torch.cat实现。

out = torch.cat((out1, out2), dim=1)

这种结构(并行拼接)允许模型同时学习和提取不同的特征表示,并将它们合并起来,从而获得更丰富的信息。这在需要模型从多个角度学习输入数据的特征时特别有用,例如在GoogLeNet的Inception模块中看到的。

串/并联对比

串联:模型按顺序执行,每个子模型的输出直接成为下一个子模型的输入。这种方式适用于任务可以分解为顺序子任务的场景。
并联:模型并行执行,多个子模型独立处理相同的输入,然后在某个维度上合并它们的输出。这种方式适用于需要模型从多个维度或尺度理解输入数据时。

3. 常用torch.nn方法

1. torch.nn.Sequential

torch.nn.Sequential是一种顺序容器。模块会按照它们在构造函数中传递的顺序添加到其中。对于Sequential,输入会按照定义的顺序通过所有模块:

import torch
import torch.nn as nn

# 使用Sequential定义一个简单的前馈神经网络
model = nn.Sequential(
    nn.Linear(784, 256), # 将输入的784维特征映射到256维
    nn.ReLU(),           # 256维特征通过ReLU激活函数
    nn.Linear(256, 128), # 再将256维映射到128维
    nn.ReLU(),           # 通过ReLU激活函数
    nn.Linear(128, 10)   # 最后将128维特征映射到10个输出类别
)

# 为每一层添加命名
model = nn.Sequential(
    ('input_to_hidden', nn.Linear(784, 256)),
    ('activation1', nn.ReLU()),
    ('hidden_to_hidden', nn.Linear(256, 128)),
    ('activation2', nn.ReLU()),
    ('hidden_to_output', nn.Linear(128, 10))
)

# 演示输入如何通过模型
input_tensor = torch.randn(1, 784) # 假设有一个1x784的随机输入向量
output = model(input_tensor)       # 获取模型的输出

上面模型用类定义的等价形式如下:

import torch
import torch.nn as nn

class SimpleFeedForwardNN(nn.Module):
    def __init__(self):
        super(SimpleFeedForwardNN, self).__init__()
        # 初始化网络层
        self.input_to_hidden = nn.Linear(784, 256)  # 将输入的784维特征映射到256维
        self.activation1 = nn.ReLU()                # 256维特征通过ReLU激活函数
        self.hidden_to_hidden = nn.Linear(256, 128) # 再将256维映射到128维
        self.activation2 = nn.ReLU()                # 通过ReLU激活函数
        self.hidden_to_output = nn.Linear(128, 10)  # 最后将128维特征映射到10个输出类别
    
    def forward(self, x):
        # 定义前向传播路径
        x = self.input_to_hidden(x)
        x = self.activation1(x)
        x = self.hidden_to_hidden(x)
        x = self.activation2(x)
        x = self.hidden_to_output(x)
        return x

# 实例化模型
model = SimpleFeedForwardNN()

# 演示输入如何通过模型
input_tensor = torch.randn(1, 784) # 假设有一个1x784的随机输入向量
output = model(input_tensor)       # 获取模型的输出

print(output) # 打印输出以确认模型工作正常

2. torch.nn.ModuleList

torch.nn.ModuleList是一个持有子模块的列表,可以像常规Python列表一样进行索引,但是在注册和管理子模块方面,它会被PyTorch认为是一个模块。

class CustomModel(nn.Module):
    def __init__(self):
        super(CustomModel, self).__init__()
        # 使用ModuleList存储多个卷积层
        self.conv_layers = nn.ModuleList([nn.Conv2d(1, 20, 5), nn.Conv2d(20, 40, 5)])
        '''
        ModuleList的等价形式:
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 40, 5)
        '''
        self.flatten = nn.Flatten()  # Flatten层,用于将卷积层输出扁平化处理
        self.fc_layer = nn.Linear(40, 10)  # 全连接层

    def forward(self, x):
        # 依次通过所有卷积层
        for layer in self.conv_layers:
            x = nn.functional.relu(layer(x))
        x = self.flatten(x)  # 扁平化处理
        x = self.fc_layer(x) # 通过全连接层
        return x

3. torch.nn.ModuleDict

torch.nn.ModuleDict类似于ModuleList,但是模块以字典形式存储,这使得根据名字来访问特定模块更加方便。

class CustomModelDict(nn.Module):
    def __init__(self):
        super(CustomModelDict, self).__init__()
        self.layers = nn.ModuleDict({
            'conv1': nn.Conv2d(1, 20, 5),
            'conv2': nn.Conv2d(20, 40, 5),
            'flatten': nn.Flatten(),
            'fc': nn.Linear(40, 10)
        })

    def forward(self, x):
        x = nn.functional.relu(self.layers['conv1'](x))
        x = nn.functional.relu(self.layers['conv2'](x))
        x = self.layers['flatten'](x)
        x = self.layers['fc'](x)
        return x

4. torch.nn.functional

torch.nn.functional提供了许多函数式接口,这些接口允许构建更加灵活和动态的模型。在需要对模型的行为进行精细控制的情况下,这个模块非常有用。

import torch.nn.functional as F

class CustomFunctionalModel(nn.Module):
    def __init__(self):
        super(CustomFunctionalModel, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5)
        self.conv2 = nn.Conv2d(20, 40, 5)
        self.fc = nn.Linear(40, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))  # 使用functional接口进行激活
        x = F.relu(self.conv2(x))
        x = F.relu(self.fc(x))
        return x

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐