构建神经网络
神经网络是由一些层或者模块组成的,这些层和模块会对数据进行各种操作。
在 PyTorch 里,torch.nn
这个命名空间提供了你搭建自己神经网络所需要的所有基础组件。PyTorch 里的每一个模块都是 nn.Module
类的子类。
一个神经网络本身就是一个模块,而这个模块又由其他的模块(也就是层)构成。这种一层套一层的嵌套结构,让我们能够轻松地搭建和管理复杂的神经网络架构。
在以下示例中,我们将构建一个神经网络来对时尚MNIST数据集中的图像进行分类。
python
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
获取训练设备
在训练开始之前,我们需要检查我们的训练设备。
我们希望能够在CUDA、MPS、MTIA或XPU等加速器上训练我们的模型。所有如果有加速器就用加速器,没有的话只能考虑CPU。(CPU训练慢)
python
device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu"
print(f"Using {device} device")
# 输出
Using cuda device
定义类
我们通过继承 PyTorch 的nn.Module类来定义自己的神经网络。在__init__
这个特殊方法中,我们会初始化网络中需要用到的各个神经层(比如全连接层、卷积层等)。每个继承自·nn.Module·的子类都必须在·forward·方法中定义如何对输入数据进行处理(即实现前向传播逻辑)。
python
class NeuralNetwork(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
# 我们创建一个NeuralNetwork的实例,并将其移动到device和printits结构。
model = NeuralNetwork().to(device)
print(model)
# 输出
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
要使用模型,我们将输入数据传递给它。这将执行模型的forward,以及一些后台操作。
不要直接调用model.forward()!
在输入上调用模型返回一个二维张量,dim=0对应于每个类的10个原始预测值的每个输出,dim=1对应于每个输出的单个值。我们通过将其传递给nn.Softmax模块的实例来获得预测概率。
python
# 生成一个随机的三维张量,并将其放置在指定的设备上 (1, 28, 28):指定生成张量的形状。
# 这里生成的是一个三维张量,第一个维度 1 通常表示批量大小(batch size),即一次处理的样本数量为 1;
# 后面两个维度 28 和 28 分别表示图像的高度和宽度。在深度学习里,这可能模拟一张单通道的 28x28 像素的图像
X = torch.rand(1, 28, 28, device=device)
# 将输入张量 X 传入已经定义好的 model 中进行前向传播,得到模型的输出 logits。
logits = model(X)
# 将输入张量 X 传入已经定义好的 model 中进行前向传播,得到模型的输出 logits。
pred_probab = nn.Softmax(dim=1)(logits)
# 找出每个样本概率最大的类别索引,作为模型的预测结果。
# argmax 是 PyTorch 张量的一个方法,用于返回指定维度上最大值的索引。dim=1 表示在第二个维度上进行操作,即找出每个样本中概率最大的类别对应的索引
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")
# 输出
Predicted class: tensor([7], device='cuda:0')
补充说明:argmax :请看 这里
模型层
让我们分解时尚MNIST模型中的层。为了说明它,我们将取一个包含3个大小为28x28的图像的小批量样本,看看当我们通过网络时它会发生什么。
python
input_image = torch.rand(3,28,28)
print(input_image.size())
# 输出
torch.Size([3, 28, 28])
nn.Flatten
我们对nn.Flatten
层进行初始化,目的是把每一张28×28的二维图像,转换成一个包含784个像素值的连续数组,同时小批量数据的维度(即维度dim=0
)会保持不变。
nn.Flatten 是一个扁平化层,其功能是把多维的输入数据展平为一维。在处理图像数据时会经常用到,因为全连接层要求输入是一维向量。
python
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
# 输出
torch.Size([3, 784])
nn.Linear
nn.Linear 是全连接层,也被叫做线性层。它使用其存储的权重和偏差对输入应用线性变换,(就是对输入数据做线性变换)
公式为 y = x A T + b y = xA^T + b y=xAT+b,这里的 x x x 是输入, A A A 是权重矩阵, b b b 是偏置项, y y y 是输出。全连接层能学习输入和输出之间的线性关系,是神经网络的基础组成部分。
python
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
# 输出
torch.Size([3, 20])
nn.ReLU
nn.ReLU是激活函数层,使用的激活函数是修正线性单元(Rectified Linear Unit,ReLU)。ReLU 的公式是 f ( x ) = max ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x),也就是输入小于 0 时输出为 0,输入大于等于 0 时输出等于输入。ReLU 能给神经网络引入非线性因素,让网络可以学习更复杂的模式。
非线性激活使得模型能够在的输入和输出之间创建出复杂的映射关系。在线性变换后应用以引入非线性,是为了帮助神经网络学习各种现象。
在本示例的模型中,我们在线性层之间使用nn. ReLU,但还有其他激活可以在模型中引入非线性。
python
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
# 输出
Before ReLU: tensor([[ 0.4158, -0.0130, -0.1144, 0.3960, 0.1476, -0.0690, -0.0269, 0.2690,
0.1353, 0.1975, 0.4484, 0.0753, 0.4455, 0.5321, -0.1692, 0.4504,
0.2476, -0.1787, -0.2754, 0.2462],
[ 0.2326, 0.0623, -0.2984, 0.2878, 0.2767, -0.5434, -0.5051, 0.4339,
0.0302, 0.1634, 0.5649, -0.0055, 0.2025, 0.4473, -0.2333, 0.6611,
0.1883, -0.1250, 0.0820, 0.2778],
[ 0.3325, 0.2654, 0.1091, 0.0651, 0.3425, -0.3880, -0.0152, 0.2298,
0.3872, 0.0342, 0.8503, 0.0937, 0.1796, 0.5007, -0.1897, 0.4030,
0.1189, -0.3237, 0.2048, 0.4343]], grad_fn=<AddmmBackward0>)
After ReLU: tensor([[0.4158, 0.0000, 0.0000, 0.3960, 0.1476, 0.0000, 0.0000, 0.2690, 0.1353,
0.1975, 0.4484, 0.0753, 0.4455, 0.5321, 0.0000, 0.4504, 0.2476, 0.0000,
0.0000, 0.2462],
[0.2326, 0.0623, 0.0000, 0.2878, 0.2767, 0.0000, 0.0000, 0.4339, 0.0302,
0.1634, 0.5649, 0.0000, 0.2025, 0.4473, 0.0000, 0.6611, 0.1883, 0.0000,
0.0820, 0.2778],
[0.3325, 0.2654, 0.1091, 0.0651, 0.3425, 0.0000, 0.0000, 0.2298, 0.3872,
0.0342, 0.8503, 0.0937, 0.1796, 0.5007, 0.0000, 0.4030, 0.1189, 0.0000,
0.2048, 0.4343]], grad_fn=<ReluBackward0>)
nn.Sequential
nn. Sequential是模块的有序容器。它能按顺序组装多个层,数据按照定义的相同顺序通过所有模块。
使用nn.Sequential
可以更方便地构建神经网络,按顺序执行各个层的操作。
可以使用顺序容器来组装一个快速网络,如seq_modules。
python
seq_modules = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
nn.Softmax
nn.Softmax也是激活函数层,使用的是 Softmax 函数。
神经网络的最后一个线性层会输出 "对数几率"(logits)------ 也就是取值范围在负无穷到正无穷之间的原始数值,这些数值会被送到nn.Softmax模块里
Softmax 函数会把输入的每个元素转换为 0 到 1 之间的值,这些数值代表了模型对每个类别的预测概率。dim参数指明了沿着哪个维度。
另外,这些所有数值相加的和必须为 1。所以它通常用于多分类问题的输出层,输出每个类别的概率。
模型参数
神经网络里的很多层都带有可学习的参数,也就是说,这些层有对应的权重和偏置项,在训练过程中会不断优化这些参数。
当你继承nn.Module
来创建自己的模型类时,程序会自动记录模型对象里定义的所有字段,这样你就能通过模型的parameters()
或者named_parameters()
方法来获取所有参数。
在此示例中,我们遍历每个参数,并打印其大小和值的预览。
python
print(f"Model structure: {model}\n\n")
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")
# 输出
Model structure: NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)
)
)
Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values : tensor([[ 0.0273, 0.0296, -0.0084, ..., -0.0142, 0.0093, 0.0135],
[-0.0188, -0.0354, 0.0187, ..., -0.0106, -0.0001, 0.0115]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0155, -0.0327], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 0.0116, 0.0293, -0.0280, ..., 0.0334, -0.0078, 0.0298],
[ 0.0095, 0.0038, 0.0009, ..., -0.0365, -0.0011, -0.0221]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([ 0.0148, -0.0256], device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[-0.0147, -0.0229, 0.0180, ..., -0.0013, 0.0177, 0.0070],
[-0.0202, -0.0417, -0.0279, ..., -0.0441, 0.0185, -0.0268]],
device='cuda:0', grad_fn=<SliceBackward0>)
Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([ 0.0070, -0.0411], device='cuda:0', grad_fn=<SliceBackward0>)
更多详细内容可以看: torch.nn API
这是 PyTorch 官方稳定版中torch.nn的文档页面,它介绍了构建神经网络的各类基础组件,包括容器、卷积层、池化层、激活函数、损失函数等,还涵盖相关的实用工具函数,帮助开发者搭建和训练神经网络模型。
补充说明
argmax
在 PyTorch 中,argmax
方法的 dim
参数用于指定在哪个维度上寻找最大值的索引。dim
参数的值不同,代表的操作维度也不同,下面为你详细解释 dim=0
、dim=1
和 dim=2
时的具体含义,并结合示例代码说明。
dim = 0
当dim = 0
时,表示在第 0 个维度(即最外层维度)上寻找最大值的索引。对于一个多维张量,这意味着在每一列(对于二维张量)或者每一个"切片"(对于更高维张量)上进行比较,找出最大值所在的行(或相应维度)的索引。
python
import torch
# 创建一个二维张量
tensor_2d = torch.tensor([[1, 5, 3],
[4, 2, 6]])
# 在第 0 个维度上寻找最大值的索引
result_0 = tensor_2d.argmax(dim=0)
print("dim = 0 时的结果:", result_0)
在这个二维张量 tensor_2d
中,有 2 行 3 列。当 dim = 0
时,会在每一列上进行比较:
- 对于第 1 列(
[1, 4]
),最大值是 4,其索引为 1。 - 对于第 2 列(
[5, 2]
),最大值是 5,其索引为 0。 - 对于第 3 列(
[3, 6]
),最大值是 6,其索引为 1。
所以最终结果是tensor([1, 0, 1])
。
dim = 1
当dim = 1
时,表示在第 1 个维度上寻找最大值的索引。对于二维张量,这意味着在每一行上进行比较,找出最大值所在的列的索引。
python
import torch
# 创建一个二维张量
tensor_2d = torch.tensor([[1, 5, 3],
[4, 2, 6]])
# 在第 1 个维度上寻找最大值的索引
result_1 = tensor_2d.argmax(dim=1)
print("dim = 1 时的结果:", result_1)
在这个二维张量 tensor_2d
中,当 dim = 1
时,会在每一行上进行比较:
- 对于第 1 行(
[1, 5, 3]
),最大值是 5,其索引为 1。 - 对于第 2 行(
[4, 2, 6]
),最大值是 6,其索引为 2。
所以最终结果是tensor([1, 2])
。
dim = 2
当dim = 2
时,适用于三维及以上的张量,表示在第 2 个维度上寻找最大值的索引。对于三维张量,这意味着在每个"二维切片"的每一行上进行比较,找出最大值所在的列的索引。
python
import torch
# 创建一个三维张量
tensor_3d = torch.tensor([[[1, 5, 3],
[4, 2, 6]],
[[7, 8, 9],
[3, 2, 1]]])
# 在第 2 个维度上寻找最大值的索引
result_2 = tensor_3d.argmax(dim=2)
print("dim = 2 时的结果:", result_2)
在这个三维张量 tensor_3d
中,有 2 个"二维切片",每个切片是一个 2x3 的二维张量。当 dim = 2
时,会在每个"二维切片"的每一行上进行比较:
- 对于第 1 个"二维切片":
- 第 1 行(
[1, 5, 3]
),最大值是 5,其索引为 1。 - 第 2 行(
[4, 2, 6]
),最大值是 6,其索引为 2。
- 第 1 行(
- 对于第 2 个"二维切片":
- 第 1 行(
[7, 8, 9]
),最大值是 9,其索引为 2。 - 第 2 行(
[3, 2, 1]
),最大值是 3,其索引为 0。
所以最终结果是tensor([[1, 2], [2, 0]])
。
- 第 1 行(