深度学习框架PyTorch——从入门到精通(5)构建神经网络

构建神经网络

神经网络是由一些层或者模块组成的,这些层和模块会对数据进行各种操作。

在 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=0dim=1dim=2 时的具体含义,并结合示例代码说明。

  1. 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])
  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])
  1. 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。
  • 对于第 2 个"二维切片":
    • 第 1 行([7, 8, 9]),最大值是 9,其索引为 2。
    • 第 2 行([3, 2, 1]),最大值是 3,其索引为 0。
      所以最终结果是 tensor([[1, 2], [2, 0]])
相关推荐
灏瀚星空4 小时前
在 Visual Studio Code 中高效使用 Pylance:配置、技巧与插件对比
ide·经验分享·vscode·python·编辑器·学习方法
丶21366 小时前
【AI】深度学习与人工智能应用案例详解
人工智能·深度学习
晴空对晚照6 小时前
[动手学习深度学习]26. 网络中的网络 NiN
网络·深度学习·学习
墨绿色的摆渡人7 小时前
pytorch小记(十):pytorch中torch.tril 和 torch.triu 详解
人工智能·pytorch·python
机器人之树小风8 小时前
3D视觉相机引导机器人的原理
经验分享·科技·机器人
紫雾凌寒8 小时前
自然语言处理|Top-K 采样如何解锁文本生成的多样性?
人工智能·深度学习·自然语言处理·贪心算法·top-k·采样原理·随机采样
xiangzhihong89 小时前
Hunyuan3D,腾讯推出的3D资产系统
人工智能·深度学习·机器学习
香菜95279 小时前
TikTok账号养成计划:从0到1打造高权重店铺
大数据·网络·经验分享·业界资讯
梦想画家9 小时前
PyTorch系列教程:基于LSTM构建情感分析模型
人工智能·pytorch·lstm