神经网络的创建

torch.nn模块

torch.nn是pytorch中自带的一个函数库,nn的全程为neural network,译为神经网络,torch.nn就是pytorch中用于构建神经网络的模块。

习惯上,我们使用以下方式引入torch.nn

1
from torch import nn

或者

1
import torch.nn as nn

nn.Parameter

torch.nn.Parameter是继承自torch.Tensor的子类,它是用于表示模型参数的张量。与普通张量不同的是,torch.nn.Parameter会被自动认为是torch.nn.Module的可训练参数,即加入到了torch.nn.Moduleparameter()这个迭代器中了,而在torch.nn.Module中定义的普通张量是不在parameter()中的。torch.nn.Parameter对象的requires_grad默认为True

1
2
nn.Parameter(data,
requires_grad = True)
  • data:传入的张量

返回的结果为torch.nn.parameter.Parameter类型的数据,因为继承了torch.Tensor类,因此可以将其当成一个张量

比如:

1
2
3
4
5
6
import torch
from torch import nn
a = nn.Parameter(torch.randn((3, 3)))
print(f"a = {a}\n\na.requires_grad = {a.requires_grad}\n")
print(f"a的数据类型为:{type(a)}\n")
print(f"a的张量为:{a.data}")

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
a = Parameter containing:
tensor([[-1.0653, 0.7777, 0.7859],
[ 1.0698, -1.4123, 1.2046],
[ 0.2529, 1.8005, 1.1463]], requires_grad=True)

a.requires_grad = True

a的数据类型为:<class 'torch.nn.parameter.Parameter'>

a的张量为:tensor([[-1.0653, 0.7777, 0.7859],
[ 1.0698, -1.4123, 1.2046],
[ 0.2529, 1.8005, 1.1463]])

nn.Module.register_parameter()

1
nn.Module.register_parameter(name: str, param: Optional[Parameter])
  • name:名字,通过使用给定的名字可以访问该参数
  • param:传入的张量,需要是nn.Parameter()类型的张量

register_parametertorch.nn.Module 类的一个方法,用于将一个已经创建的 Parameter 对象注册到模块中。用于将参数添加到模块的参数列表中,使其成为模块的一部分,从而可以在状态字典中保存、加载,并且被优化器更新。

在模块中使用register_parameter()注册参数后,可以直接使用self.注册名来访问该参数

使用方式:在模块内部调用此方法,传入参数的名称和 Parameter 对象。

比如:

1
2
3
4
5
6
import torch.nn as nn

class MyModel(nn.Module):
def __init__(self):
super(Mymodel, self).__init__()
self.register_parameter('weight', nn.Parameter(torch.randn(10, 10)))

torch.Parameter()的区别:

  • 对象与操作torch.Parameter 是一个对象,表示模型中的可学习参数;而 register_parameter 是一个操作,用于将 Parameter 对象注册到模块中。
  • 上下文torch.Parameter 通常在定义模型结构时使用,而 register_parameter 可以在模型的初始化或任何需要动态添加参数的时候使用。
  • 灵活性register_parameter 提供了更大的灵活性,允许在模块创建后动态地添加或删除参数。

在大多数情况下,直接使用 torch.Parameter 来定义模型参数更为常见和简洁。register_parameter 更多用于需要动态管理参数的场景,或者当我们需要以编程方式控制参数的注册时。

应用场景1:动态添加参数

比如根据输入数据的特征数量动态地创建权重矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import torch
import torch.nn as nn

class DynamicModel(nn.Module):
def __init__(self):
super().__init__()
# 初始化模型时没有参数

def add_parameter(self, name, param):
self.register_parameter(name, param)

def forward(self, x):
# 根据x的形状来决定权重矩阵的大小
if not hasattr(self, 'weight'):
weight = nn.Parameter(torch.randn(x.size[1], 1))
self.add_parameter('weight', weight)
return torch.matmul(x, self.weight)

# 使用示例
model = DynamicModel()
input_tensor = torch.randn(5, 20) # 5个样本,每个样本20个特征
output = model(input_tensor)

应用场景2:参数共享

在某些模型中,我们可能希望在不同的部分共享相同的参数,register_parameter 可以用来实现这一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ShareParameterModel(nn.Module):
def __init__(self, shared_param):
super().__init__()
self.register_parameter('shared_weight', shared_param)

def forward(self, x):
return torch.matmul(x, self.shared_weight)

# 创建一个共享的参数
shared_weight = nn.Parameter(torch.randn(20, 1))

# 创建两个模型实例,共享相同的参数
model1 = ShareParameterModel(shared_weight)
model2 = ShareParameterModel(shared_weight)

nn.Module

内容

torch.nn.Module是pytorch中定义神经网络的类,在类中实现了网络各层的定义以及前向传播与反向传播机制。

一般情况下,我们会自建一个类,继承torch.nn.Module类,把网络中具有可学参数的层(如全连接层、卷积层等)放在构造函数__init__()中,不具有可学参数的层(如ReLU、dropout、BatchNormalization等)也可以放在__init__()函数中,如果不放到构造函数中,可以在forward()方法中使用nn.functional来替代

forward()方法定义了神经网络前向传播的具体过程,需要传入tensor(训练样本)

比如,自定义一个全连接层:

1
2
3
4
5
6
7
8
9
10
11
import torch
import torch.nn as nn

class Linear(nn.Module):
def __init__(self, input_features, out_put_features): # input_feautures为输入样本的特征数,output_feautures为本层神经元个数
super().__init__()
self.weight = nn.Parameter(torch.randn(input_features, output_features))
self.bias = nn.Parameter(torch.zeros(output_features, 1))

def forward(self, x):
return torch.matmul(x, self.weight) + self.bias

用自定义的全连接层自定义一个全连接神经网络:

1
2
3
4
5
6
7
8
9
10
class MLPModel(nn.Module):
def __init__(self):
self.layers1 = Linear(1, 100) #隐藏层,输入样本只有一个特征,含有100个神经元
self.layers2 = Linear(100, 1) # 输出特征,每个样本对应一个输出,含有1个神经元

def forward(self, x): # x为输入的样本(Mini-batch)
x = self.layers1(x) # 经过隐藏层线性映射
x = torch.clamp_min(x, 0.1*x) # 自定义的PReLU激活函数
x = self.layers2(x) # 经过输出层的线性映射
return x # 返回经过了神经网络计算出的结果

使用自定义的网络:

1
2
3
4
5
6
7
8
mlp = MLPModel()

# 创建训练集
x = torch.linspace(1, 10, 20).reshape(-1, 1) # x的维度为(20, 1),20个样本,每个样本1个特征
y = x**2 + 1

# 实现一次前向传播:
y_hat = mlp(x) # x会直接传入到forward()方法中,这里不使用mlp.forward(x)

parameters()

在定义完模型后,对这个模型我们可以使用.parameters()方法获得一个生成器对象(generator object):

parameters()是PyTorch中nn.Module类的一个方法,所有继承自nn.Module的模型都可以调用这个方法。该方法返回一个迭代器,包含了模型中所有可训练的参数(即需要梯度计算的参数)。

1
2
3
4
5
import torch
import torch.nn as nn

mlp = nn.Linear(1, 100)
print(mlp.parameters())

输出结果:

1
<generator object Module.parameters at 0x0000023269114AC0>

对于生成器对象,我们可以使用for循环从中迭代提取数据:

1
2
3
params = mlp.parameters()
for param in params:
print(param)

需要注意的是,给params变量赋值为mlp.parameters(),那么一旦对params进行迭代提取,提取完后,params中将不再有数据,也就是说,我们无法对params进行第二次提取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.nn as nn

mlp = nn.Sequential(
nn.Linear(1, 10),
nn.ReLU(),
nn.Linear(10, 1)
)
params = mlp.parameters()
print("第一次提取:")
for param in params:
print(param)

print("第二次提取:")
for param in params:
print(param)

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第一次提取:
Parameter containing:
tensor([[ 0.6776],
[-0.7500],
[-0.7222],
[-0.0698],
[ 0.8810],
[ 0.9021],
[-0.1547],
[-0.9878],
[ 0.3697],
[-0.1426]], requires_grad=True)
Parameter containing:
tensor([-0.2900, 0.8597, -0.3136, -0.4627, 0.6810, -0.2507, -0.2172, -0.6585,
-0.9415, -0.7964], requires_grad=True)
Parameter containing:
tensor([[ 0.1674, -0.1763, 0.1269, -0.2610, 0.1509, -0.1233, -0.0620, 0.1090,
0.2079, 0.3091]], requires_grad=True)
Parameter containing:
tensor([-0.0058], requires_grad=True)
第二次提取:

我们可以调用.parameters()获取参数,然后对模型参数进行更新优化

named_parameters()

.named_parameters()方法也是获取参数,与.parameters()不同的是,.named_parameters()可以获取参数的名称(包括我们自定义的名称和程序自动设定的名称),比如:

1
2
3
4
5
6
7
8
9
10
import torch
import torch.nn as nn

mlp = nn.Sequential(
nn.Linear(1, 10),
nn.PReLU(),
nn.Linear(10, 1)
)
for name, param in mlp.named_parameters():
print(f'name:{name}\nvalue:{param}')

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
name:0.weight
value:Parameter containing:
tensor([[-0.3769],
[ 0.1133],
[-0.3022],
[-0.0329],
[-0.7660],
[-0.9145],
[-0.3808],
[ 0.0829],
[ 0.3061],
[-0.2912]], requires_grad=True)
name:0.bias
value:Parameter containing:
tensor([ 0.1346, 0.0990, -0.9853, 0.4964, -0.6235, -0.8417, 0.7627, 0.6968,
-0.2094, -0.1252], requires_grad=True)
name:1.weight
value:Parameter containing:
tensor([0.2500], requires_grad=True)
name:2.weight
value:Parameter containing:
tensor([[-0.1916, 0.1769, 0.1311, -0.0594, -0.0919, 0.2423, 0.0083, -0.2936,
-0.1379, 0.1659]], requires_grad=True)
name:2.bias
value:Parameter containing:
tensor([0.1720], requires_grad=True)
  • named_parameters() 只会返回可训练的参数,即 requires_gradTrue 的参数。
  • 参数的名称是根据模型的层级和模块来确定的,因此不同的模型结构可能会有不同的参数命名方式。

nn.functional

torch.nn.functional提供了很多网络层与函数功能,但与nn.Module不同的是,利用nn.functional定义的网络层不可自动学习参数,还需要使用nn.Parameter封装。nn.functional的设计初衷是对于一些不需要学习的层,如激活函数层、BN(Batch Normalization)层、池化层等,可以使用nn.functional,这样这些层就不需要在nn.Module中定义了

常用函数:

函数名 pytorch代码
sigmoid函数 nn.functional.sigmoid(input)
ReLu函数 nn.functional.relu(input, inplace=False)
PReLU函数 nn.functional.prelu(input, weight)
tanh函数 nn.functional.tanh(input)
softplus nn.functional.softplus(input, beta=1, threshold=20)
softmax nn.functional.softmax(input, dim, _stacklevel=3, dytpe=None)
leaky_relu nn.functional.leaky_relu(input, negative_slope=0.01, inplace=False)

注意:

  • PReLU函数中的weight参数就是当输入\(x\)元素小于0时候的斜率,是可以训练的参数,如果不使用nn.Parameter封装,那么其值固定。
  • softmax函数中的_stacklevel参数用于控制softmax函数中的错误跟踪和调试功能。它可以控制错误消息中包含的堆栈跟踪深度,以便我们可以更轻松地定位和调试程序中的问题。默认值为3表示错误消息中包含的堆栈跟踪层次为3。这意味着当发生错误时,错误消息将显示函数调用堆栈的最近3次调用。
  • leaky_relu中的negative_slope就是\(x\)小于0时的图像斜率(\(0.01x\)

当然,nn.functional也有常用的神经网络层:全连接(nn.functional.linear())、卷积、池化等等

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn as nn

class MLPModel(nn.Module):
def __init__(self):
self.layers1 = nn.Linear(1, 100)
self.layers2 = nn.Lieanr(100, 1)

def forward(self, x):
x = self.layers1(x)
x = nn.functional.leaky_relu(x) # 使用leaky_relu作为激活函数
x = self.layers2(x)
return x

常用的神经网络层

全连接层

1
torch.nn.Linear(in_features, out_features, bias=True)
  • in_features:每个输入样本的特征数量
  • out_features:每个输出样本的特征数量(本层神经元个数)
  • bias:如果为True,则有偏置;如果为False,则该层没有偏置

pytorch中全连接层线性计算应该为\(Z = XW + b\),输入的\(X\)矩阵每行为一个样本,假设一共有\(N\)个样本,输入\(X\)的维度为\((N, \mathrm{input\_features})\)\(W\)的维度为\((\mathrm{input\_features}, \mathrm{output\_features})\),全连接层输出的张量维度为\((N, \mathrm{output\_features})\)

查看全连接层的权重与偏置:使用.weight方法查看或修改权重,使用.bias方法查看或修改偏置

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn as nn

class MLPModel(nn.Module):
def __init__(self):
super().__init__()
self.layers1 = nn.Linear(1, 100)
self.layers2 = nn.Linear(100, 1)
def forward(self, x):
x = self.layers1(x)
x = nn.functional.leaky_relu(x)
x = self.layers2(x)
return x

卷积层

二维卷积

1
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
  1. in_channels (int):输入张量的通道数。对于彩色图像,这个值通常是3(RGB三通道)。
  2. out_channels (int):卷积操作后的输出通道数,即卷积核的数量。这个值决定了卷积层输出的特征图数量。
  3. kernel_size (int or tuple):卷积核的大小。可以是一个整数(表示方形卷积核)或一个元组(表示矩形卷积核的宽和高)。
  4. stride (int or tuple, optional):卷积核移动的步长。默认为1。可以是一个整数或一个元组。
  5. padding (int or tuple, optional):在输入张量的边界添加零填充的数量。默认为0。可以是一个整数或一个元组。padding的作用是控制输出特征图的大小。
  6. dilation (int or tuple, optional):卷积核元素之间的间距。默认为1。使用膨胀卷积可以增加感受野。
  7. groups (int, optional):分组卷积的组数。默认为1。当groups>1时,表示进行分组卷积,可以减少参数数量和计算量。
  8. bias (bool, optional):是否添加偏置项。默认为True。

nn.Conv2d 的工作机制如下:

  1. 输入张量:假设输入张量的形状为 (N, C_in, H_in, W_in),其中 N 是批量大小,C_in 是输入通道数,H_inW_in 分别是输入的高度和宽度。
  2. 卷积核:卷积核的形状为 (C_out, C_in / groups, kH, kW),其中 C_out 是输出通道数,kHkW 是卷积核的高度和宽度。
  3. 卷积操作:对于每个输出通道,卷积核与输入张量进行元素-wise乘积并求和,得到一个二维的特征图。这个过程在输入张量的每个位置上重复进行,步长由 stride 参数控制。
  4. 输出张量:输出张量的形状为 (N, C_out, H_out, W_out),其中 H_outW_out 是输出特征图的高度和宽度,计算公式如下:
1
2
H_out = floor((H_in + 2 * padding[0] - dilation[0] * (kernel_size[0] - 1) - 1) / stride[0] + 1)
W_out = floor((W_in + 2 * padding[1] - dilation[1] * (kernel_size[1] - 1) - 1) / stride[1] + 1)

以下是一些常用的 nn.Conv2d 配置:

  • 标准卷积nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0)
  • same卷积nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=(kernel_size//2, kernel_size//2)),输出特征图的大小与输入相同。
  • valid卷积nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0),不添加零填充,输出特征图的大小小于输入。
  • 步长卷积nn.Conv2d(in_channels, out_channels, kernel_size, stride>1, padding=0),增加步长以减少输出特征图的大小。
  • 分组卷积nn.Conv2d(in_channels, out_channels, kernel_size, groups>1),用于减少参数数量和计算量,常用于深度可分离卷积。

比如:

1
2
3
4
5
6
7
8
import torch
import torch.nn as nn

# 创建输入张量,形状为 (1, 3, 32, 32)
input_tensor = torch.randn(1, 3, 32, 32)

# 创建卷积层,输入通道数为3,输出通道数为16,卷积核大小为3x3
conv = nn.Conv2d(3, 16, kernel_size=3, padding=1)

池化层

最大池化

1
nn.MaxPool2d(kernel_size=None, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
  • kernel_size:池化窗口的大小,可以是一个整数或者一个元组 (kH, kW),表示池化窗口的高度和宽度。默认值为 None,此时等于 stride
  • stride:池化窗口移动的步长,可以是一个整数或者一个元组 (sH, sW),表示在高度和宽度方向上的步长。默认值为 None,此时等于 kernel_size
  • padding:在输入张量的边界添加零填充的数量,可以是一个整数或者一个元组 (padH, padW),表示在高度和宽度方向上的填充。默认值为 0。
  • dilation:池化窗口中元素的间距,默认值为 1。这个参数不常用,通常保持默认值。
  • return_indices:如果为 True,会返回池化操作的输出和最大值的位置索引。默认值为 False。
  • ceil_mode:如果为 True,会使用向上取整的方式计算输出大小。默认值为 False,使用向下取整。ceil_mode 只影响输出特征图的尺寸,而不影响池化操作后的数值结果。(超出图像区域不会填充,池化操作只考虑输入图像内部的有效像素)

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.nn as nn

# 创建一个输入张量,形状为 (1, 1, 4, 4)
input_tensor = torch.FloatTensor([[[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]]]])

# 创建一个最大池化层,池化窗口大小为 2x2,步长为 2
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)

# 应用最大池化层到输入张量
output_tensor = max_pool(input_tensor)

print(output_tensor)

输出结果:

1
2
tensor([[[[ 6.,  8.],
[14., 16.]]]])

平均池化

1
nn.AvgPool2d(kernel_size=None, stride=None, padding=0, ceil_mode=False, count_include_pad=False)
  • kernel_size:池化窗口的大小,可以是一个整数或者一个元组 (kH, kW),表示池化窗口的高度和宽度。这是必须指定的参数。
  • stride:池化窗口移动的步长,可以是一个整数或者一个元组 (sH, sW),表示在高度和宽度方向上的步长。默认值为 None,此时等于 kernel_size
  • padding:在输入张量的边界添加零填充的数量,可以是一个整数或者一个元组 (padH, padW),表示在高度和宽度方向上的填充。默认值为 0。
  • ceil_mode:如果为 True,会使用向上取整的方式计算输出大小。默认值为 False,使用向下取整。ceil_mode 只影响输出特征图的尺寸,而不影响池化操作后的数值结果。
  • count_include_pad:如果为 True,会在计算平均值时包括零填充。默认值为 True。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch
import torch.nn as nn

# 创建一个输入张量,形状为 (1, 1, 4, 4)
input_tensor = torch.FloatTensor([[[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]]]])

# 创建一个平均池化层,池化窗口大小为 2x2,步长为 2
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)

# 应用平均池化层到输入张量
output_tensor = avg_pool(input_tensor)

print(output_tensor)

输出结果:

1
2
tensor([[[[ 3.5000,  5.5000],
[11.5000, 13.5000]]]])

自适应平均池化

1
nn.AdaptiveAvgPool2d(output_size)
  • output_size:指定输出特征图的大小。可以是一个元组 (H, W),表示输出特征图的高度和宽度;也可以是一个整数,表示输出特征图的高度和宽度都将是这个值。

nn.AdaptiveAvgPool2d 是 PyTorch 中的一种自适应平均池化层,它用于将输入的特征图池化到指定的输出大小。与普通的平均池化层(如 nn.AvgPool2d)不同,nn.AdaptiveAvgPool2d 不需要指定池化核的大小和步长,而是直接指定输出特征图的大小。

nn.AdaptiveAvgPool2d 会自动计算合适的池化核大小和步长,以便将输入特征图池化到指定的输出大小。具体来说,它会根据输入特征图的尺寸和指定的输出尺寸来动态调整池化核的大小和步长。

比如:

假设我们有一个 8x8 的输入特征图,并希望将其池化到 2x2 的输出特征图:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn as nn

# 创建输入张量
input_tensor = torch.randn(1, 1, 8, 8)

# 创建自适应平均池化层
adaptive_avg_pool = nn.AdaptiveAvgPool2d((2, 2))

# 应用自适应平均池化
output_tensor = adaptive_avg_pool(input_tensor)

print(output_tensor.shape) # 输出: torch.Size([1, 1, 2, 2])

自适应最大池化

1
nn.AdaptiveMaxPool2d(output_size)
  • output_size:指定输出特征图的大小。可以是一个元组 (H, W),表示输出特征图的高度和宽度;也可以是一个整数,表示输出特征图的高度和宽度都将是这个值。

nn.AdaptiveMaxPool2d 是 PyTorch 中的一种自适应最大池化层,它与 nn.AdaptiveAvgPool2d 类似,但是执行的是最大池化操作而不是平均池化。自适应最大池化的目的是将输入的特征图池化到指定的输出大小,而不需要指定池化核的大小和步长。

nn.AdaptiveMaxPool2d 会自动计算合适的池化核大小和步长,以便将输入特征图池化到指定的输出大小。具体来说,它会根据输入特征图的尺寸和指定的输出尺寸来动态调整池化核的大小和步长,并在每个池化区域中取最大值。

比如:

假设我们有一个 8x8 的输入特征图,并希望将其池化到 2x2 的输出特征图:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
import torch.nn as nn

# 创建输入张量
input_tensor = torch.randn(1, 1, 8, 8)

# 创建自适应最大池化层
adaptive_max_pool = nn.AdaptiveMaxPool2d((2, 2))

# 应用自适应最大池化
output_tensor = adaptive_max_pool(input_tensor)

print(output_tensor.shape) # 输出: torch.Size([1, 1, 2, 2])

RNN

1
nn.RNN(input_size, hidden_size, num_layers=1, nonlinearity='tanh', bias=True, batch_first=False, dropout=0, bidirectional=False)
  • input_size:输入特征的维度,即每个时间步输入向量的长度
  • hidden_size:隐藏状态的维度,即每个时间步隐藏状态向量的长度
  • num_layers:RNN的层数,默认为1。如果想构建深层RNN网络,可以增加该参数的值。多层RNN会将前一层的输出作为下一层的输入
  • nonlinearity:激活函数类型,可以是tanhrelu,默认为tanh
  • bias:是否使用偏置项,默认为True
  • batch_first:如果为True,则输入和输出的张量形状为(batch_size, seq_len, input_size);否则为(seq_len, batch_size, input_size),默认为False
  • dropout:如果大于0,则在除最后一层外的每一层的输出上应用Dropout层,Dropout概率为该值,默认为0
  • bidirectional:是否使用双向RNN,默认为False。双向RNN会同时考虑序列的正向和反向信息

注:seq_len表示序列长度(时间步数),batch_size为批次大小,hidden_size为隐层维度

使用与输出:

1
2
3
4
5
6
7
8
9
rnn = nn.RNN(input_size, hidden_size)

# 前向传播,不传入初始隐藏状态,自动将初始隐藏状态初始化为全零张量
output, h_n = rnn(input_tensor)

# 前向传播,传入初始隐藏状态
# 手动初始化隐藏层状态
h_0 = torch.randn(num_layers, batch_size, hidden_size)
output, h_n = rnn(input_tensor, h_0)

对于一个隐层的RNN,隐层参数\(H_t\in\mathbb{R}^{n\times h}\),即维度(batch_size, hidden_size),对于多个隐层,隐层参数\(H_t\in\mathbb{R}^{l\times n\times h}\)\(l\)即隐层的数量),即维度(num_layers, batch_size, hidden_size)

对于双向RNN(bidirectional=True),隐层参数\(H_t\in\mathbb{R}^{2l\times n\times h}\),即维度(num_layers*2, batch_size, hidden_size)

隐层参数维度与batch_first无关,因为RNN内部计算逻辑不受batch_first影响

输出的output形状为(seq_len, batch_size, hidden_size)(如果batch_first=False),包含每个时间步的隐藏状态。注意,如果num_layers>1output则是纵向多层RNN中最后一个RNN在所有时间步的隐藏状态。

输出的h_n形状为(num_layers, batch_size, hidden_size),包含最后一个时间步的隐藏状态,如果为双向RNN,维度为(num_layers*2, batch_size, hidden_size)

激活函数

pytorch中常用的激活函数:

层对应的类 功能
torch.nn.Sigmoid Sigmoid激活函数
torch.nn.Tanh Tanh激活函数
torch.nn.ReLU ReLU激活函数
torch.nn.Softplus Softplus激活函数
torch.nn.PReLU PReLU激活函数
torch.nn.LeakyReLU LeakReLU激活函数

之前我们使用的是nn.functional中的激活函数,现在我们使用nn中的激活函数:(需要先在__init__()中定义,再在forward()中使用)

1
2
3
4
5
6
7
8
9
10
11
12
class MLPModel(nn.Module):
def __init__(self):
super(MLPModel, self).__init__()
self.layers1 = nn.Linear(1, 100) #隐藏层,输入样本只有一个特征,含有100个神经元
self.active1 = nn.LeakyReLU()
self.layers2 = nn.Linear(100, 1) # 输出特征,每个样本对应一个输出,含有1个神经元

def forward(self, x): # x为输入的样本(Mini-batch)
x = self.layers1(x) # 经过隐藏层线性映射
x = self.active1(x) # 使用leaky_relu作为激活函数
x = self.layers(x) # 经过输出层的线性映射
return x # 返回经过了神经网络计算出的结果

注意,PReLU中含有可以学习的参数\(a\)\[ \mathrm{PReLU}(x) = \max(0, x) + a\times\min(0, x) \] 也就是: \[ \mathrm{PReLU}(x) = \left\{ \begin{aligned} &x\quad &x\geq0\\\ &ax\quad&x<0 \end{aligned} \right. \] 如果直接使用nn.PReLU(),那么在所有通道中使用单个参数\(a\),如果使用nn.PReLU(nChannels),则每个输入通道使用单独的\(a\),需要保证nChannels与通道数相等,否则会报错。\(a\)的默认值为\(0.25\),可以使用.weight方法查看:

1
2
3
4
5
6
import torch
import torch.nn as nn
P = nn.PReLU()
print(f"{P.weight}\n")
P1 = nn.PReLU(10)
print(P1.weight)

输出结果:

1
2
3
4
5
6
Parameter containing:
tensor([0.2500], requires_grad=True)

Parameter containing:
tensor([0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500, 0.2500,
0.2500], requires_grad=True)

使用PReLU激活函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MLPModel(nn.Module):
def __init__(self):
super(MLPModel, self).__init__()
self.layers1 = nn.Linear(1, 100)
self.active1 = nn.PReLU()
self.layers2 = nn.Linear(100, 1)
self.active2 = nn.PReLU()
def forward(self, x):
x = self.layers1(x)
x = self.active1(x)
x = self.layers2(x)
x = self.active2(x)
return x

torch.nn.Sequential()

直接使用

torch.nn.Sequential()是一个序列容器模块,可以将神经网络模块按顺序写入torch.nn.Sequential()的参数中,构建序列化的神经网络层。模型接收的输入首先被传入torch.nn.Sequential()包含的第一个网络模块中,然后,第一个网络模块的输出传入第二个网络模块作为输入,按照顺序依次计算并传播,直到torch.nn.Sequential()里的最后一个模块输出结果。

比如:

1.直接传递模块列表:

1
2
3
4
5
6
7
layers = nn.Sequential(
nn.Linear(1, 100),
nn.PReLU(),
nn.Linear(100, 10),
nn.PReLU(),
nn.Linear(10, 1)
)

2.使用OrderedDict传递命名模块:

1
2
3
4
5
6
7
8
import torch.nn as nn
from collections import OrderedDict

layers = nn.Sequential(OrderedDict[
('linear1', nn.Linear(1, 100)),
('relu', nn.ReLU()),
('linear2', nn.Linear(100, 1))
])

使用torch.nn.Sequential()构建神经网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch
import torch.nn as nn

class MLPModel(nn.Module):
def __init__(self):
super().__init__()
self.layers1 = nn.Sequential(
nn.Linear(1, 100),
nn.PReLU()
)
self.layers2 = nn.Seqential(
nn.Linear(100, 10),
nn.PReLU()
)
self.layers3 = nn.Linear(10, 1)

def forward(self, x):
x = self.layers1(x)
x = self.layers2(x)
x = self.layers3(x)
return x

访问内部模块:

我们可以通过索引或者名称来访问nn.Sequential()中的内部模块:

1
2
3
4
5
6
7
8
9
10
11
import torch.nn as nn
from collections import OrderedDict

model = nn.Sequential(OrderedDict[
('linear1', nn.Linear(1, 100)),
('prelu', nn.PReLU()),
('linear2', nn.Linear(100, 1))
])

first_layer = model[0] # 通过索引访问
second_layer = model.linear2 # 通过名称访问(如果使用OrderDict定义)

自定义Sequential()

我们可以通过自定义类来实现类似功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for block in args:
self._modules[block] = block

def forward(self, x):
for block in self._modules.values():
x = block(x)
return x

net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = ......
net(X)
  • nn.Module._modules 是 PyTorch 中 nn.Module 类的一个私有属性,用于存储模块的子模块。这个属性是一个有序字典(collections.OrderedDict),其键是子模块的名称,值是子模块本身。其键是子模块的名称,值是子模块本身。_modules 的主要用途是管理模块的层次结构,确保在调用如 state_dict()parameters() 等方法时,能够递归地包含所有子模块的状态和参数。

混合搭配使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch
import torch.nn as nn
import torch.nn.functional as F

class NestMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU
nn.Linear(64, 32), nn.ReLU)
self.linear = nn.Linear(32, 16)

def forward(self, x):
return self.linear(self.net(x))

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20))
x = ......
chimera(x)

神经网络的创建
https://blog.shinebook.net/2025/02/26/人工智能/pytorch/神经网络的创建/
作者
X
发布于
2025年2月26日
许可协议