神经网络的创建
torch.nn
模块
torch.nn
是pytorch中自带的一个函数库,nn
的全程为neural
network,译为神经网络,torch.nn
就是pytorch中用于构建神经网络的模块。
习惯上,我们使用以下方式引入torch.nn
:
1 |
|
或者
1 |
|
nn.Parameter
torch.nn.Parameter
是继承自torch.Tensor
的子类,它是用于表示模型参数的张量。与普通张量不同的是,torch.nn.Parameter
会被自动认为是torch.nn.Module
的可训练参数,即加入到了torch.nn.Module
的parameter()
这个迭代器中了,而在torch.nn.Module
中定义的普通张量是不在parameter()
中的。torch.nn.Parameter
对象的requires_grad
默认为True
1 |
|
data
:传入的张量
返回的结果为torch.nn.parameter.Parameter
类型的数据,因为继承了torch.Tensor
类,因此可以将其当成一个张量
比如:
1 |
|
输出结果:
1 |
|
nn.Module.register_parameter()
1 |
|
name
:名字,通过使用给定的名字可以访问该参数param
:传入的张量,需要是nn.Parameter()
类型的张量
register_parameter
是 torch.nn.Module
类的一个方法,用于将一个已经创建的 Parameter
对象注册到模块中。用于将参数添加到模块的参数列表中,使其成为模块的一部分,从而可以在状态字典中保存、加载,并且被优化器更新。
在模块中使用register_parameter()
注册参数后,可以直接使用self.注册名
来访问该参数
使用方式:在模块内部调用此方法,传入参数的名称和
Parameter
对象。
比如:
1 |
|
与torch.Parameter()
的区别:
- 对象与操作:
torch.Parameter
是一个对象,表示模型中的可学习参数;而register_parameter
是一个操作,用于将Parameter
对象注册到模块中。 - 上下文:
torch.Parameter
通常在定义模型结构时使用,而register_parameter
可以在模型的初始化或任何需要动态添加参数的时候使用。 - 灵活性:
register_parameter
提供了更大的灵活性,允许在模块创建后动态地添加或删除参数。
在大多数情况下,直接使用 torch.Parameter
来定义模型参数更为常见和简洁。register_parameter
更多用于需要动态管理参数的场景,或者当我们需要以编程方式控制参数的注册时。
应用场景1:动态添加参数
比如根据输入数据的特征数量动态地创建权重矩阵
1 |
|
应用场景2:参数共享
在某些模型中,我们可能希望在不同的部分共享相同的参数,register_parameter
可以用来实现这一点。
1 |
|
nn.Module
内容
torch.nn.Module
是pytorch中定义神经网络的类,在类中实现了网络各层的定义以及前向传播与反向传播机制。
一般情况下,我们会自建一个类,继承torch.nn.Module
类,把网络中具有可学参数的层(如全连接层、卷积层等)放在构造函数__init__()
中,不具有可学参数的层(如ReLU、dropout、BatchNormalization等)也可以放在__init__()
函数中,如果不放到构造函数中,可以在forward()
方法中使用nn.functional
来替代
forward()
方法定义了神经网络前向传播的具体过程,需要传入tensor
(训练样本)
比如,自定义一个全连接层:
1 |
|
用自定义的全连接层自定义一个全连接神经网络:
1 |
|
使用自定义的网络:
1 |
|
parameters()
在定义完模型后,对这个模型我们可以使用.parameters()
方法获得一个生成器对象(generator object
):
parameters()
是PyTorch中nn.Module
类的一个方法,所有继承自nn.Module
的模型都可以调用这个方法。该方法返回一个迭代器,包含了模型中所有可训练的参数(即需要梯度计算的参数)。
1 |
|
输出结果:
1 |
|
对于生成器对象,我们可以使用for
循环从中迭代提取数据:
1 |
|
需要注意的是,给params
变量赋值为mlp.parameters()
,那么一旦对params进行迭代提取,提取完后,params
中将不再有数据,也就是说,我们无法对params
进行第二次提取:
1 |
|
输出结果:
1 |
|
我们可以调用.parameters()
获取参数,然后对模型参数进行更新优化
named_parameters()
.named_parameters()
方法也是获取参数,与.parameters()
不同的是,.named_parameters()
可以获取参数的名称(包括我们自定义的名称和程序自动设定的名称),比如:
1 |
|
输出结果:
1 |
|
named_parameters()
只会返回可训练的参数,即requires_grad
为True
的参数。- 参数的名称是根据模型的层级和模块来确定的,因此不同的模型结构可能会有不同的参数命名方式。
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 |
|
常用的神经网络层
全连接层
1 |
|
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 |
|
卷积层
二维卷积
1 |
|
- in_channels (int):输入张量的通道数。对于彩色图像,这个值通常是3(RGB三通道)。
- out_channels (int):卷积操作后的输出通道数,即卷积核的数量。这个值决定了卷积层输出的特征图数量。
- kernel_size (int or tuple):卷积核的大小。可以是一个整数(表示方形卷积核)或一个元组(表示矩形卷积核的宽和高)。
- stride (int or tuple, optional):卷积核移动的步长。默认为1。可以是一个整数或一个元组。
- padding (int or tuple, optional):在输入张量的边界添加零填充的数量。默认为0。可以是一个整数或一个元组。padding的作用是控制输出特征图的大小。
- dilation (int or tuple, optional):卷积核元素之间的间距。默认为1。使用膨胀卷积可以增加感受野。
- groups (int, optional):分组卷积的组数。默认为1。当groups>1时,表示进行分组卷积,可以减少参数数量和计算量。
- bias (bool, optional):是否添加偏置项。默认为True。
nn.Conv2d
的工作机制如下:
- 输入张量:假设输入张量的形状为
(N, C_in, H_in, W_in)
,其中N
是批量大小,C_in
是输入通道数,H_in
和W_in
分别是输入的高度和宽度。 - 卷积核:卷积核的形状为
(C_out, C_in / groups, kH, kW)
,其中C_out
是输出通道数,kH
和kW
是卷积核的高度和宽度。 - 卷积操作:对于每个输出通道,卷积核与输入张量进行元素-wise乘积并求和,得到一个二维的特征图。这个过程在输入张量的每个位置上重复进行,步长由
stride
参数控制。 - 输出张量:输出张量的形状为
(N, C_out, H_out, W_out)
,其中H_out
和W_out
是输出特征图的高度和宽度,计算公式如下:
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 |
|
池化层
最大池化
1 |
|
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 |
|
输出结果:
1 |
|
平均池化
1 |
|
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 |
|
输出结果:
1 |
|
自适应平均池化
1 |
|
- output_size:指定输出特征图的大小。可以是一个元组
(H, W)
,表示输出特征图的高度和宽度;也可以是一个整数,表示输出特征图的高度和宽度都将是这个值。
nn.AdaptiveAvgPool2d
是 PyTorch
中的一种自适应平均池化层,它用于将输入的特征图池化到指定的输出大小。与普通的平均池化层(如
nn.AvgPool2d
)不同,nn.AdaptiveAvgPool2d
不需要指定池化核的大小和步长,而是直接指定输出特征图的大小。
nn.AdaptiveAvgPool2d
会自动计算合适的池化核大小和步长,以便将输入特征图池化到指定的输出大小。具体来说,它会根据输入特征图的尺寸和指定的输出尺寸来动态调整池化核的大小和步长。
比如:
假设我们有一个 8x8 的输入特征图,并希望将其池化到 2x2 的输出特征图:
1 |
|
自适应最大池化
1 |
|
- output_size:指定输出特征图的大小。可以是一个元组
(H, W)
,表示输出特征图的高度和宽度;也可以是一个整数,表示输出特征图的高度和宽度都将是这个值。
nn.AdaptiveMaxPool2d
是 PyTorch
中的一种自适应最大池化层,它与 nn.AdaptiveAvgPool2d
类似,但是执行的是最大池化操作而不是平均池化。自适应最大池化的目的是将输入的特征图池化到指定的输出大小,而不需要指定池化核的大小和步长。
nn.AdaptiveMaxPool2d
会自动计算合适的池化核大小和步长,以便将输入特征图池化到指定的输出大小。具体来说,它会根据输入特征图的尺寸和指定的输出尺寸来动态调整池化核的大小和步长,并在每个池化区域中取最大值。
比如:
假设我们有一个 8x8 的输入特征图,并希望将其池化到 2x2 的输出特征图:
1 |
|
RNN
1 |
|
input_size
:输入特征的维度,即每个时间步输入向量的长度hidden_size
:隐藏状态的维度,即每个时间步隐藏状态向量的长度num_layers
:RNN的层数,默认为1
。如果想构建深层RNN网络,可以增加该参数的值。多层RNN会将前一层的输出作为下一层的输入nonlinearity
:激活函数类型,可以是tanh
或relu
,默认为tanh
bias
:是否使用偏置项,默认为True
batch_first
:如果为True
,则输入和输出的张量形状为(batch_size, seq_len, input_size)
;否则为(seq_len, batch_size, input_size)
,默认为False
dropout
:如果大于0,则在除最后一层外的每一层的输出上应用Dropout层,Dropout概率为该值,默认为0bidirectional
:是否使用双向RNN,默认为False
。双向RNN会同时考虑序列的正向和反向信息
注:seq_len
表示序列长度(时间步数),batch_size
为批次大小,hidden_size
为隐层维度
使用与输出:
1 |
|
对于一个隐层的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>1
,output
则是纵向多层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 |
|
注意,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 |
|
输出结果:
1 |
|
使用PReLU激活函数:
1 |
|
torch.nn.Sequential()
直接使用
torch.nn.Sequential()
是一个序列容器模块,可以将神经网络模块按顺序写入torch.nn.Sequential()
的参数中,构建序列化的神经网络层。模型接收的输入首先被传入torch.nn.Sequential()
包含的第一个网络模块中,然后,第一个网络模块的输出传入第二个网络模块作为输入,按照顺序依次计算并传播,直到torch.nn.Sequential()
里的最后一个模块输出结果。
比如:
1.直接传递模块列表:
1 |
|
2.使用OrderedDict传递命名模块:
1 |
|
使用torch.nn.Sequential()
构建神经网络:
1 |
|
访问内部模块:
我们可以通过索引或者名称来访问nn.Sequential()
中的内部模块:
1 |
|
自定义Sequential()
我们可以通过自定义类来实现类似功能:
1 |
|
nn.Module._modules
是 PyTorch 中nn.Module
类的一个私有属性,用于存储模块的子模块。这个属性是一个有序字典(collections.OrderedDict
),其键是子模块的名称,值是子模块本身。其键是子模块的名称,值是子模块本身。_modules
的主要用途是管理模块的层次结构,确保在调用如state_dict()
、parameters()
等方法时,能够递归地包含所有子模块的状态和参数。
混合搭配使用
1 |
|