【深度学习】pytorch——神经网络工具箱nn

笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~

深度学习专栏链接:
http://t.csdnimg.cn/dscW7

简介

PyTorch神经网络工具箱nn是一个用于构建深度学习模型的核心模块。它提供了一组简单而灵活的API,可以轻松地定义、训练和评估各种类型的神经网络。

nn模块中包含了许多预定义的模块和方法,如线性层、卷积层、循环神经网络、损失函数等,可以直接调用这些模块来构建深度学习模型。以下是nn模块的主要功能:

  1. 定义神经网络模型:可以使用nn模块定义各种类型的神经网络。通过继承nn.Module类,可以创建一个自己的神经网络类,并在其中定义各个层的结构和参数。

  2. 易于使用的层:nn模块提供了各种类型的层,如线性层、卷积层、池化层、归一化层等。这些层已经默认实现了前向传播和反向传播的过程,可以直接使用。

  3. 非线性激活函数:nn模块提供了常见的激活函数,如ReLU、Sigmoid、Tanh等,可以在层之间添加非线性变换。

  4. 自动求导:nn模块基于PyTorch的自动求导机制,可以自动计算梯度并进行反向传播。这使得用户可以更加专注于模型的设计和实现,而不必手动计算梯度。

  5. 训练和优化:可以使用nn模块中的优化器(如SGD、Adam等)来训练模型,并使用预定义的损失函数(如交叉熵、均方误差等)来评估模型的性能。

  6. 序列化和保存:可以使用nn模块中的API将模型序列化为文件,并在需要时恢复模型。

PyTorch神经网络工具箱nn提供了一组简单而灵活的API,可以方便地定义、训练和评估各种类型的神经网络。同时也支持用户自定义的模型和层,使得用户可以根据自己的需求和应用场景创建更加复杂的深度学习模型。

nn.Module

nn.Module是PyTorch中所有神经网络模型的基类。所有用户自定义的神经网络模型都应该继承自nn.Module类,并实现其中的方法。

nn.Module提供了一些重要的属性和方法,使得用户可以方便地定义神经网络的结构和参数,并进行正向传播和反向传播的计算。

以下是nn.Module类的一些重要属性和方法:

  1. __init__(self):构造函数,用于初始化模型的结构和参数。在这个方法中,用户可以定义模型中的各个层,并指定它们的参数。

  2. forward(self, input):前向传播方法。在这个方法中,用户定义了模型的正向传播过程。通过调用各个层的前向传播方法,并对它们的输出进行组合和变换,最终生成模型的输出。

  3. parameters(self):返回模型中所有可学习的参数。这个方法返回一个迭代器,可以用于遍历模型中的所有参数,并进行优化和更新。

  4. to(self, device):将模型移动到指定的设备上,如CPU或GPU。通过这个方法,可以方便地将模型加载到合适的设备上进行计算。

  5. state_dict(self)load_state_dict(self, state_dict):这两个方法用于序列化和加载模型的状态。state_dict()方法返回一个包含模型所有状态的字典,可以将其保存到文件中。而load_state_dict()方法则从给定的字典中加载模型的状态。

  6. train()eval():这两个方法用于设置模型的训练模式和评估模式。在训练模式下,模型会保留一些特定的操作,如Dropout层,在评估模式下会关闭这些操作。

通过继承nn.Module类,并重写其中的方法,用户可以方便地定义自己的神经网络模型。同时,nn.Module还提供了一些实用的方法和功能,如参数管理、设备移动和状态保存等,使得模型的训练和部署变得更加简单和高效。

nn.Module实现全连接层

用nn.Module实现全连接层。全连接层,又名仿射层,输出 y \textbf{y} y和输入 x \textbf{x} x满足 y=Wx+b \textbf{y=Wx+b} y=Wx+b W \textbf{W} W b \textbf{b} b是可学习的参数。

import torch as t
from torch import nn

class Linear(nn.Module): # 继承nn.Module
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__() # 等价于nn.Module.__init__(self)
        self.w = nn.Parameter(t.randn(in_features, out_features))
        self.b = nn.Parameter(t.randn(out_features))
    
    def forward(self, x):
        x = x.mm(self.w) # x.@(self.w)
        return x + self.b.expand_as(x)
    
    
layer = Linear(4,3)
input = t.randn(2,4)
output = layer(input)
print(output)

for name, parameter in layer.named_parameters():
    print(name, parameter) # w and b 

代码解释:
这段代码定义了一个自定义的线性层Linear,它继承自nn.Module类,并重写了其中的__init__forward方法。该层的输入维度为in_features,输出维度为out_features

class Linear(nn.Module): 
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__() 
        self.w = nn.Parameter(t.randn(in_features, out_features))
        self.b = nn.Parameter(t.randn(out_features))
    
    def forward(self, x):
        x = x.mm(self.w) 
        return x + self.b.expand_as(x)

__init__方法中,首先调用父类的构造函数来初始化模型,然后定义了两个参数self.wself.b,它们分别对应于该层的权重矩阵和偏置向量,并将它们标记为可学习的参数。

forward方法中,首先将输入张量x与权重矩阵self.w相乘,然后加上偏置向量self.b,最终得到该层的输出。

接下来,代码创建了一个Linear对象layer,并传入了输入的维度4和输出的维度3。然后定义了一个随机输入张量input,其形状为(2, 4)

layer = Linear(4,3)
input = t.randn(2,4)

最后,将输入张量传入layer对象的forward方法中,得到输出张量output

output = layer(input)

通过print(output)语句打印输出张量output的值,可以查看模型对给定输入的计算结果。

print(output)

此外,代码还使用named_parameters方法遍历了layer对象中的所有参数,并打印了它们的名称和值。可以获取到wb两个参数。

for name, parameter in layer.named_parameters():
    print(name, parameter)

nn.Module实现多层感知机

在这里插入图片描述

import torch as t
from torch import nn

class Linear(nn.Module): # 继承nn.Module
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__() # 等价于nn.Module.__init__(self)
        self.w = nn.Parameter(t.randn(in_features, out_features))
        self.b = nn.Parameter(t.randn(out_features))
    
    def forward(self, x):
        x = x.mm(self.w) # x.@(self.w)
        return x + self.b.expand_as(x)

class Perceptron(nn.Module):
    def __init__(self, in_features, hidden_features, out_features):
        nn.Module.__init__(self)
        self.layer1 = Linear(in_features, hidden_features) # 此处的Linear是自定义的全连接层
        self.layer2 = Linear(hidden_features, out_features)
    def forward(self,x):
        x = self.layer1(x)
        x = t.sigmoid(x)
        return self.layer2(x)
    
perceptron = Perceptron(3,4,1)
for name, param in perceptron.named_parameters():
    print(name, param.size())

代码解释:

首先,定义了一个名为Linear的类,它继承自nn.Module。这个类代表了一个线性层,包含权重参数w和偏置参数b

class Linear(nn.Module):
    def __init__(self, in_features, out_features):
        super(Linear, self).__init__()
        self.w = nn.Parameter(t.randn(in_features, out_features))
        self.b = nn.Parameter(t.randn(out_features))
    
    def forward(self, x):
        x = x.mm(self.w)
        return x + self.b.expand_as(x)

然后,定义了一个名为Perceptron的类,它也继承自nn.Module。这个类代表了一个多层感知机模型,由两个线性层组成。

class Perceptron(nn.Module):
    def __init__(self, in_features, hidden_features, out_features):
        nn.Module.__init__(self)
        self.layer1 = Linear(in_features, hidden_features)
        self.layer2 = Linear(hidden_features, out_features)
    
    def forward(self, x):
        x = self.layer1(x)
        x = t.sigmoid(x)
        return self.layer2(x)

在初始化方法中,创建了两个Linear对象self.layer1self.layer2作为感知机的两个层。在前向传播方法中,输入数据首先通过self.layer1进行计算,然后经过sigmoid激活函数,最终通过self.layer2得到输出。

最后,创建了一个Perceptron对象perceptron,并打印出其中的参数名称和大小。

perceptron = Perceptron(3, 4, 1)
for name, param in perceptron.named_parameters():
    print(name, param.size())

这段代码展示了如何使用自定义的线性层构建一个多层感知机模型,并打印出其中的参数信息。通过这个例子,我们可以理解如何使用PyTorch构建神经网络模型,并对其进行调试和优化。

module中parameter的命名规范:

  • 对于类似self.param_name = nn.Parameter(t.randn(3, 4)),命名为param_name
  • 对于子Module中的parameter,会其名字之前加上当前Module的名字。如对于self.sub_module = SubModel(),SubModel中有个parameter的名字叫做param_name,那么二者拼接而成的parameter name 就是sub_module.param_name

常用神经网络层

在神经网络中,有许多常用的层用于构建各种不同类型的模型。以下是几个常见的神经网络层及其用途:

  1. 全连接层(Fully Connected Layer):全连接层是最基本的层之一,也称为线性层或密集层。它将输入的每个元素与权重相乘,并通过添加偏差项得到输出。全连接层通常用于提取特征和进行分类。

  2. 卷积层(Convolutional Layer):卷积层是专门用于处理图像和空间数据的层。它使用卷积运算对输入数据进行滤波,以提取局部的空间特征。卷积层通常用于图像识别、目标检测和语音处理等任务。

  3. 池化层(Pooling Layer):池化层主要用于减少特征图的空间尺寸,并保留最重要的特征。常见的池化操作包括最大池化和平均池化,它们分别选择每个区域中的最大值或平均值作为输出。

  4. 递归层(Recurrent Layer):递归层用于处理序列数据,如自然语言处理和时间序列预测。它们具有记忆机制,可以在每个时间步骤上传递信息。常见的递归层包括循环神经网络(RNN)和长短期记忆网络(LSTM)。

  5. 嵌入层(Embedding Layer):嵌入层用于将离散型的符号或类别数据编码为连续型的低维向量表示。它在自然语言处理中广泛应用,将单词或字符映射到连续的向量空间中。

  6. 规范化层(Normalization Layer):规范化层用于在神经网络中对输入数据进行标准化处理,以提高模型的稳定性和收敛速度。常见的规范化操作包括批量归一化(Batch Normalization)和层归一化(Layer Normalization)。

  7. 激活函数层(Activation Layer):激活函数层对神经网络的输出应用非线性变换,以引入非线性能力。常见的激活函数包括ReLU(Rectified Linear Unit)、Sigmoid和Tanh等。

这只是一小部分常用的神经网络层,实际应用中还有许多其他类型的层,如注意力层、残差连接等。根据任务和问题的不同,选择合适的层组合可以有效地构建出强大的神经网络模型。

图像相关层

图像相关层主要包括卷积层(Conv)、池化层(Pool)等,这些层在实际使用中可分为一维(1D)、二维(2D)、三维(3D),池化方式又分为平均池化(AvgPool)、最大值池化(MaxPool)、自适应池化(AdaptiveAvgPool)等。而卷积层除了常用的前向卷积之外,还有逆卷积(TransposeConv)。

卷积层(Conv)实现锐化过滤器

# 导入所需的库
from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
import torch as t
import torch.nn as nn

# 创建将图像转换为张量的转换器
to_tensor = ToTensor()

# 创建将张量转换为图像的转换器
to_pil = ToPILImage()

# 打开图像文件
lena = Image.open('C:/图片路径/1.png')

# 将彩色图像转换为灰度图像,并将其通道数从3变为1。并将图像转换为张量,并添加一维作为batch_size
input = to_tensor(lena.convert('L')).unsqueeze(0) 

# 定义锐化卷积核
kernel = t.ones(3, 3)/-9.
kernel[1][1] = 1
conv = nn.Conv2d(1, 1, (3, 3), 1, bias=False)
conv.weight.data = kernel.view(1, 1, 3, 3)

# 对输入进行卷积操作
out = conv(input)

# 将输出张量转换为图像,并显示出来
to_pil(out.data.squeeze(0))

在这里插入图片描述
在这里插入图片描述

这段代码的功能是实现一个锐化过滤器,对输入图像进行锐化处理。具体步骤如下:

  1. 创建将图像转换为张量的转换器to_tensor = ToTensor()和将张量转换为图像的转换器to_pil = ToPILImage()

  2. 打开图像文件lena = Image.open('图片路径')

  3. input = to_tensor(lena.convert('L')).unsqueeze(0),将彩色图像转换为灰度图像,并将其通道数从3变为1。并将图像转换为张量,并添加一维作为batch_size,即将它转换为输入大小为(1, channels, height, width)的模型输入。

  4. kernel = t.ones(3, 3)/-9.; kernel[1][1] = 1
    定义锐化卷积核:这个卷积核是一个3x3的矩阵,中心点为1,其它位置为-1/9,即偏移量为-1/9。

  5. conv = nn.Conv2d(1, 1, (3, 3), 1, bias=False),创建一个卷积层conv,该卷积层的输入通道数为1,输出通道数为1,卷积核大小为(3, 3),步长为1,偏置为False。

  6. conv.weight.data = kernel.view(1, 1, 3, 3),将之前创建的卷积核kernel转换为相同形状的张量,并将其赋给卷积层的权重。

  7. out = conv(input):对输入进行卷积操作,使用创建好的卷积核对输入进行卷积操作,得到一个输出大小为(1, 1, height, width)的张量。

  8. to_pil(out.data.squeeze(0)),将输出张量转换为图像,并显示出来。由于输入时添加了一维batch_size,因此需要先通过squeeze()去除这一维。

常见激活函数

函数 图像
sigmoid 在这里插入图片描述
tanh 在这里插入图片描述
ReLU 在这里插入图片描述
leaky ReLU 在这里插入图片描述

下面是一个PyTorch实现常见激活函数的例子:

import torch
import torch.nn as nn

# 定义输入张量x
x = torch.randn(1, 10)

# Sigmoid激活函数
sigmoid = nn.Sigmoid()
activated_x = sigmoid(x)
print("Sigmoid激活后的输出:", activated_x)

# Tanh(双曲正切)激活函数
tanh = nn.Tanh()
activated_x = tanh(x)
print("Tanh激活后的输出:", activated_x)

# ReLU激活函数
relu = nn.ReLU()
activated_x = relu(x)
print("ReLU激活后的输出:", activated_x)

# LeakyReLU激活函数
leaky_relu = nn.LeakyReLU(negative_slope=0.01)
activated_x = leaky_relu(x)
print("LeakyReLU激活后的输出:", activated_x)

# Softmax激活函数
softmax = nn.Softmax(dim=1)
activated_x = softmax(x)
print("Softmax激活后的输出:", activated_x)

在这个例子中,我们首先定义了一个大小为(1, 10)的输入张量x,然后分别使用PyTorch中的ReLU、Sigmoid、Tanh、LeakyReLU和Softmax激活函数对其进行处理,并输出处理后的结果。

请注意,每个激活函数都是通过创建一个相应的PyTorch模型来实现的。例如,ReLU激活函数是通过创建一个nn.ReLU的实例来实现的。在使用时,我们只需要将输入张量传递给模型即可。

输出结果如下:

Sigmoid激活后的输出: tensor([[0.6914, 0.3946, 0.2316, 0.3845, 0.6496, 0.7061, 0.3284, 0.4206, 0.8200, 0.6755]])
Tanh激活后的输出: tensor([[ 0.6678, -0.4035, -0.8334, -0.4386,  0.5492,  0.7046, -0.6141, -0.3099, 0.9080,  0.6250]])
ReLU激活后的输出: tensor([[0.8068, 0.0000, 0.0000, 0.0000, 0.6172, 0.8765, 0.0000, 0.0000, 1.5163,  0.7332]])
LeakyReLU激活后的输出: tensor([[ 0.8068, -0.0043, -0.0120, -0.0047,  0.6172,  0.8765, -0.0072, -0.0032, 1.5163,  0.7332]])
Softmax激活后的输出: tensor([[0.1407, 0.0409, 0.0189, 0.0392, 0.1164, 0.1508, 0.0307, 0.0456, 0.2860,  0.1307]])

ReLU函数有个inplace参数,如果设为True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能够推算出反向传播的梯度。但是只有少数的autograd操作支持inplace操作(如tensor.sigmoid_()),除非你明确地知道自己在做什么,否则一般不要使用inplace操作。

# ReLU激活函数
relu = nn.ReLU(inplace=True)
activated_x = relu(x)
print("ReLU激活后的输出:", activated_x)

ModuleList和Sequential

ModuleListSequential是PyTorch中用于构建神经网络模型的两个重要类。

ModuleList是一个容器类,它可以包含多个子模块(如层、激活函数等),并将它们作为一个整体来处理。通过使用ModuleList,可以方便地管理和组织多个子模块。

下面是一个使用ModuleList构建模型的示例:

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layers = nn.ModuleList([
            nn.Linear(10, 20),
            nn.ReLU(),
            nn.Linear(20, 30),
            nn.Tanh()
        ])
    
    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x

在这个示例中,MyModel类定义了一个包含四个子模块的模型。在模型的构造函数中,创建了一个ModuleList对象,并将四个子模块添加到其中。在模型的forward方法中,通过遍历ModuleList中的子模块,并将输入逐层传递给它们,以得到最终的输出。

相比于直接使用列表或元组来存储子模块,使用ModuleList的一个优点是,它会自动地注册子模块,使得子模块的参数可以被模型的其他部分识别和更新。

Sequential是另一个用于构建模型的类,它提供了一种更加简洁的方式来定义连续的层序列。使用Sequential,可以以序列的形式将多个层(如线性层、激活函数等)串联起来,形成一个完整的神经网络模型。

以下是一个使用Sequential构建模型的示例:

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(10, 20),
    nn.ReLU(),
    nn.Linear(20, 30),
    nn.Tanh()
)

在这个示例中,直接使用Sequential类来构造模型,并将每个层作为参数传递给Sequential的构造函数。这样,每个层都会按照顺序依次被添加到模型中。

使用Sequential的一个优点是,它提供了一种更加简洁和直观的方式来定义模型结构。然而,由于Sequential只适用于顺序连接的模型,因此无法灵活地处理一些复杂的网络结构,如跳跃连接或多路连接等。

ModuleListSequential都是用于构建神经网络模型的重要工具。ModuleList适用于管理和组织多个子模块,而Sequential适用于简洁地定义顺序连接的模型。选择使用哪个类取决于具体的需求和模型结构。

循环神经网络层(RNN)

循环神经网络层(RNN)是一种用于处理序列数据的神经网络层。与传统的前馈神经网络不同,RNN将一个序列中的每个元素作为输入,并通过记忆状态来传递信息,从而在序列中实现信息的持续传递。

在RNN中,当前时刻的输出不仅取决于当前时刻的输入,还取决于之前所有时刻的输入和记忆状态。具体地,RNN的计算可以表示为:

h t = f ( W ∗ x t + U ∗ h t − 1 ) h_t = f(W * x_t + U * h_{t-1}) ht=f(Wxt+Uht1)
其中,h_t表示当前时刻的记忆状态,x_t表示当前时刻的输入,W和U是权重矩阵。

RNN的基本结构如下所示:
在这里插入图片描述
RNN的优点是可以处理任意长度的序列数据,并且具有较强的记忆能力,可以学习到序列中的长期依赖关系。然而,由于RNN在计算过程中需要多次重复使用相同的权重矩阵,导致梯度消失或爆炸的问题,影响了其训练效果。为了解决这个问题,后来出现了一些改进的RNN结构,如LSTM和GRU等。

以下是使用PyTorch实现一个简单的RNN模型的示例:

import torch
import torch.nn as nn

class MyRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(MyRNN, self).__init__()
        self.hidden_size = hidden_size
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x):
        h0 = torch.zeros(1, x.size(0), self.hidden_size) # 初始化记忆状态
        out, hn = self.rnn(x, h0)
        out = self.fc(out[:, -1, :]) # 取最后一个时刻的输出作为模型输出
        return out

在这个示例中,我们创建了一个名为MyRNN的RNN模型类,并在其中使用nn.RNN类来定义RNN层。在模型的forward方法中,我们首先通过torch.zeros函数初始化记忆状态,并将输入张量和记忆状态传递给RNN层进行计算。然后,我们通过取最后一个时刻的输出,并使用一个全连接层来将其映射到输出空间中,得到最终的模型输出。

优化器

在PyTorch中,可以使用torch.optim模块来实现优化器的基本使用方法、对模型的不同部分设置不同的学习率以及调整学习率。下面我们分别介绍这些内容。

  1. 优化器的基本使用方法

首先,需要创建一个优化器对象以便于优化模型的参数。假设我们已经定义好了一个模型对象model,可以按照以下方式创建一个优化器对象:

import torch.optim as optim

optimizer = optim.Adam(model.parameters(), lr=0.01)

在这个示例中,我们使用Adam优化算法来更新模型参数。我们将模型对象model的所有可训练参数(即权重和偏置)传递给优化器的构造函数,并设置初始学习率为0.01

接下来,在训练循环中,可以按照以下方式进行参数更新:

optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播,计算梯度
optimizer.step() # 更新参数

在这个示例中,我们首先调用zero_grad()函数清零所有参数的梯度,然后调用backward()函数计算损失函数关于模型参数的梯度,最后调用step()函数更新参数。

  1. 对模型的不同部分设置不同的学习率

对模型的不同部分设置不同的学习率,以便更好地调整模型参数。在PyTorch中,可以通过以下方式实现:

optimizer = optim.Adam([
                {
    
    'params': model.conv1.parameters()},
                {
    
    'params': model.conv2.parameters(), 'lr': 0.01},
                {
    
    'params': model.fc.parameters(), 'lr': 0.001}
            ], lr=0.0001)

在这个示例中,将模型的三个部分(conv1、conv2和fc)分别放在不同的字典中,并为每个字典设置不同的学习率。最后,我们将所有字典传递给优化器的构造函数,并设置初始学习率为0.0001

  1. 调整学习率

有时候,在训练过程中需要调整学习率,以便更好地优化模型参数。在PyTorch中,可以按照以下方式调整学习率:

# 减小学习率到原来的1/10
for param_group in optimizer.param_groups:
    param_group['lr'] /= 10.0

在这个示例中,我们首先遍历所有参数组,然后将每个参数组的学习率除以10.0,从而减小学习率到原来的1/10。注意,optimizer.param_groups是一个列表,其中每个元素都是一个字典,包含了与该参数组相关的所有信息,例如学习率、权重衰减系数等。

nn.functional VS nn.Module

nn.functional和nn.Module都是PyTorch深度学习库中非常重要的模块。它们都有助于定义神经网络模型,但它们的使用方式和目的略有不同。

  1. nn.functional

nn.functional是PyTorch中一个包含各种非线性函数、池化函数、卷积函数等的模块。这些函数都被实现为纯函数(pure function),即它们的输出只取决于输入,而不依赖于任何外部状态。因此,它们通常用于构建没有参数的简单层或者复杂的自定义损失函数。

下面是一个使用nn.functional实现的简单的全连接层:

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

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

在这个示例中,使用了nn.Linear来创建两个全连接层,然后在forward方法中使用了F.relu函数作为激活函数。

  1. nn.Module

nn.Module是PyTorch中的一个基类,所有的神经网络模型都应该继承它。nn.Module提供了一些有用的方法,例如parameters()named_parameters()modules()等,可以方便地访问模型中的参数和子模块。在继承nn.Module类后,我们需要实现__init__forward方法来定义模型结构和前向传播过程。

下面是一个使用nn.Module实现的简单的全连接层:

import torch.nn as nn

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

在这个示例中,使用了nn.Linear来创建两个全连接层,并在__init__方法中将它们定义为模型的属性。然后在forward方法中使用了nn.functional.relu函数作为激活函数。

总的来说,nn.Module用于构建更复杂、带有参数的神经网络模型,而nn.functional则用于构建简单的不含参数的层或者自定义损失函数。当构建神经网络时,nn.Module往往是首选,因为它提供了更多的灵活性和可扩展性。

nn.init实现参数初始化

nn.init是PyTorch深度学习库中一个用于初始化神经网络权重的模块。在训练神经网络时,通常需要对权重进行初始化以提高模型的收敛速度和泛化能力。

nn.init提供了多种初始化方法,包括常见的正态分布初始化、均匀分布初始化和Xavier初始化等。下面以Xavier初始化为例进行讲解。

  1. Xavier初始化

Xavier初始化是一种常用的权重初始化方法,旨在使输入信号和输出信号的方差相等。这可以帮助加速模型的收敛,并提高模型的性能。Xavier初始化的具体公式如下:

W ∼ U [ − 6 n i n + n o u t , 6 n i n + n o u t ] W \sim U[-\frac{\sqrt{6}}{\sqrt{n_{in}+n_{out}}}, \frac{\sqrt{6}}{\sqrt{n_{in}+n_{out}}}] WU[nin+nout 6 ,nin+nout 6 ]

其中, U [ a , b ] U[a, b] U[a,b]表示从区间 [ a , b ] [a, b] [a,b]的均匀分布中随机采样, n i n n_{in} nin n o u t n_{out} nout分别表示权重的输入和输出维度。

  1. 使用nn.init进行Xavier初始化

使用nn.init进行Xavier初始化非常简单,只需要将需要初始化的参数传递给相应的初始化函数即可。例如,以下代码演示了如何使用nn.init进行Xavier初始化:

import torch.nn as nn
import torch.nn.init as init

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 10)

        # 对权重进行Xavier初始化
        init.xavier_uniform_(self.fc1.weight)
        init.xavier_uniform_(self.fc2.weight)

    def forward(self, x):
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

在这个示例中,使用nn.Linear创建了两个全连接层,并在__init__方法中将它们定义为模型的属性。然后使用init.xavier_uniform_函数对权重进行Xavier初始化。

总的来说,使用nn.init进行权重初始化可以帮助模型更快地收敛并提高模型的性能。但需要注意的是,权重初始化并不是万能的,有时候还需要调整学习率、正则化等超参数来优化模型。

nn.Module深入分析

nn.Module基类的构造函数

def __init__(self):
    self._parameters = OrderedDict()
    self._modules = OrderedDict()
    self._buffers = OrderedDict()
    self._backward_hooks = OrderedDict()
    self._forward_hooks = OrderedDict()
    self.training = True

nn.Module基类的构造函数__init__是所有PyTorch神经网络模型的基础构建块之一。其主要作用是初始化模型的各个属性,并创建一个空的有序字典来存储模型的参数、子模块、缓存、正向/反向传播钩子等。

下面对__init__函数中定义的各个属性进行解释:

  1. self._parameters:有序字典,用于存储模型的可学习参数(例如权重和偏置)。在模型中定义的nn.Parameter类型的变量会自动添加到该字典中。

  2. self._modules:有序字典,用于存储模型的子模块。在模型中定义的nn.Module类型的变量会自动添加到该字典中。

  3. self._buffers:有序字典,用于存储模型的缓存。例如,一些中间结果、滑动平均值等可以保存在这里。

  4. self._backward_hooks:有序字典,用于存储模型的反向传播钩子,即在反向传播过程中执行的函数。

  5. self._forward_hooks:有序字典,用于存储模型的正向传播钩子,即在正向传播过程中执行的函数。

  6. self.training:布尔变量,用于指示模型的训练状态。在训练过程中,该变量为True,在测试过程中,该变量为False。

总的来说,nn.Module基类的构造函数初始化了神经网络模型中的各个属性,并创建了一个有序字典来存储模型的参数、子模块、缓存、正向/反向传播钩子等。这些属性和有序字典提供了方便的机制来访问和管理模型的组件,并可以方便地进行序列化和反序列化。

示例代码:

import torch.nn as nn

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.fc1 = nn.Linear(784, 256)
        self.fc2 = nn.Linear(256, 10)

    def forward(self, x):
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

# 创建模型实例
model = MyNet()

# 打印 Module 属性
print("Parameters:")
for name, param in model.named_parameters():
    print(name, param.size())

print("\nModules:")
for name, module in model.named_modules():
    print(name, module)

print("\nBuffers:")
for name, buffer in model.named_buffers():
    print(name, buffer.size())

print("\nForward hooks:")
for hook in model._forward_hooks.values():
    print(hook)

print("\nBackward hooks:")
for hook in model._backward_hooks.values():
    print(hook)

在这个示例中,我们创建了一个继承自nn.Module基类的神经网络模型,并在其中定义了两个全连接层。然后,我们通过访问实例对象的属性和有序字典,打印了模型的ParameterModuleBuffer以及正向/反向传播钩子等属性。输出结果如下:

Parameters:
fc1.weight torch.Size([256, 784])
fc1.bias torch.Size([256])
fc2.weight torch.Size([10, 256])
fc2.bias torch.Size([10])

Modules:
 MyNet(
  (fc1): Linear(in_features=784, out_features=256, bias=True)
  (fc2): Linear(in_features=256, out_features=10, bias=True)
)
fc1 Linear(in_features=784, out_features=256, bias=True)
fc2 Linear(in_features=256, out_features=10, bias=True)

Buffers:

Forward hooks:

Backward hooks:

从输出结果中可以看出:

  • Parameters属性打印了模型的可学习参数,即两个全连接层的权重和偏置。
  • Modules属性打印了模型的子模块,包括整个模型本身和其两个全连接层。
  • Buffers属性为空,因为在模型中没有定义任何缓存。
  • Forward hooks属性为空,因为在模型中没有定义任何正向传播钩子。
  • Backward hooks属性为空,因为在模型中没有定义任何反向传播钩子。

访问模型中的子模块

import torch
import torch.nn as nn

class SubNet(nn.Module):
    def __init__(self):
        super(SubNet, self).__init__()
        self.conv = nn.Conv2d(3, 16, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        
    def forward(self, x):
        x = self.conv(x)
        x = self.pool(x)
        return x

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.subnet1 = SubNet()
        self.subnet2 = SubNet()
        self.fc = nn.Linear(16 * 8 * 8, 10)
        
    def forward(self, x):
        x1 = self.subnet1(x)
        x2 = self.subnet2(x)
        x = torch.cat((x1, x2), dim=1)
        x = x.view(-1, 16 * 8 * 8)
        x = self.fc(x)
        return x

# 创建模型实例
model = MyNet()

# 查看直接子模块
print("Children:")
for name, module in model.named_children():
    print(name, module)

# 查看所有子模块(包括当前模块)
print("\nModules:")
for name, module in model.named_modules():
    print(name, module)

# 查看命名直接子模块
print("\nNamed Children:")
for name, module in model.named_children():
    print(name, module)

# 查看命名所有子模块(包括当前模块)
print("\nNamed Modules:")
for name, module in model.named_modules():
    print(name, module)

在这个示例中,我们定义了一个SubNet子模块和一个MyNet模型,其中MyNet包含了两个SubNet子模块和一个全连接层。我们使用named_childrennamed_modules函数查看了直接子模块、所有子模块以及它们的命名版本。

输出结果如下:

Children:
subnet1 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet2 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

Modules:
 MyNet(
  (subnet1): SubNet(
    (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (subnet1.conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (subnet1.pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (subnet2): SubNet(
    (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (subnet2.conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (subnet2.pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (fc): Linear(in_features=1024, out_features=10, bias=True)
)
subnet1 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet1.conv Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
subnet1.pool MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
subnet2 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet2.conv Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
subnet2.pool MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
fc Linear(in_features=1024, out_features=10, bias=True)

Named Children:
subnet1 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet2 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

Named Modules:
 MyNet MyNet(
  (subnet1): SubNet(
    (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (subnet1.conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (subnet1.pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (subnet2): SubNet(
    (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (subnet2.conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (subnet2.pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (fc): Linear(in_features=1024, out_features=10, bias=True)
)
subnet1 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet1.conv Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
subnet1.pool MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
subnet2 SubNet(
  (conv): Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
subnet2.conv Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
subnet2.pool MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
fc Linear(in_features=1024, out_features=10, bias=True)

从输出结果可以看出:

  • 使用named_children函数可以返回直接子模块,并可以通过返回的元组中的第一个元素访问子模块的名称,第二个元素访问子模块本身。
  • 使用named_modules函数可以返回所有子模块(包括当前模块),同样可以通过返回的元组中的第一个元素访问模块的名称,第二个元素访问模块本身。
  • named_childrennamed_modules函数都有一个命名版本,分别为named_childennamed_modules。这两个函数返回的元组中,第一个元素是子模块的名称,第二个元素是子模块本身。

这些函数提供了一种方便的方式来管理、访问和控制复杂的神经网络模型中的子模块。

猜你喜欢

转载自blog.csdn.net/weixin_44319595/article/details/134230360