当前位置:   article > 正文

pytorch神经网络实现_pytorch 神经网络

pytorch 神经网络

我们从PyTorch中经典的quickstart示例开始,从中学习神经网络构建和训练过程。

其中要学到并熟练掌握的是如下这些流程:

  • 数据预处理
  • 构建模型
  • 定制模型损失函数和优化器
  • 训练并观察超参数

下面我们就一步步分解这个过程,其中也会学习认识到一些pytorch为我们提供的框架内置对象和函数。初次接触可能还不是很适应,所以以先完成完整的模型训练流程为重。后续再根据任务需求,一步步的扩展pytorch的认知版图


数据预处理

模型训练用的样本,大部分都来自于外部文件系统。本次训练用的数据来自框架内置的数据集,所以代码没有泛化性。涉及到具体数据再做补充。

PyTorch 有两个用于处理数据的工具:torch.utils.data.DataLoadertorch.utils.data.DatasetDataset 存储的是数据样本和对应的标签,DataLoader 把Dataset包装成一个可迭代的对象。

  1. import torch
  2. from torch import nn
  3. from torch.utils.data import DataLoader
  4. from torchvision import datasets
  5. from torchvision.transforms import ToTensor, Lambda, Compose
  6. import matplotlib.pyplot as plt

本次的数据样本来自于Pytorch的TorchVision 数据集。

  1. # 下载的数据集文件会保存到当前用户工作目录的data子目录中。
  2. # 如果不想下载后就找不到了,建议修改root参数的值。
  3. # 例如"D:\\\\datasets\\\\fashionMNIST\\\\"一类的绝对路径
  4. training_data = datasets.FashionMNIST(
  5. root="data",
  6. train=True,
  7. download=True,
  8. transform=ToTensor(),
  9. )
  10. # 测试集也需要下载,代码和上面一样。但参数train=False代表不是训练集(逻辑取反,就是测试集)
  11. test_data = datasets.FashionMNIST(
  12. root="data",
  13. train=False,
  14. download=True,
  15. transform=ToTensor(),
  16. )

下一步就是对已加载数据集的封装,把Dataset 作为参数传递给 DataLoader。这样,就在我们的数据集上包装了一个迭代器(iterator),这个迭代器还支持自动批处理采样打乱顺序和多进程数据加载等这些强大的功能。这里我们定义了模型训练期间,每个批次的数据样本量大小为64,即数据加载器在迭代中,每次返回一批 64 个数据特征和标签。

  1. batch_size = 64
  2. # 创建数据加载器
  3. train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True)
  4. test_dataloader = DataLoader(test_data, batch_size=batch_size)
  5. # 测试数据加载器输出
  6. for X, y in test_dataloader:
  7. print("Shape of X [N, C, H, W]: ", X.shape)
  8. print("Shape of y: ", y.shape, y.dtype)
  9. break

Shape of X [N, C, H, W]: torch.Size([64, 1, 28, 28]) Shape of y: torch.Size([64]) torch.int64


构建模型

为了在 PyTorch 中定义神经网络,我们创建了一个继承自 nn.Module 的类。我们在 init 函数中定义网络层,并在 forward 函数中指定数据将如何通过网络。为了加速神经网络中的操作,我们将其移至 GPU(如果可用)。

  1. # 检验可以使用的设备
  2. device = "cuda" if torch.cuda.is_available() else "cpu"
  3. print(f"使用 {device} 设备")
  4. # 定义神经网络模型
  5. class NeuralNetwork(nn.Module):
  6. def __init__(self):
  7. super(NeuralNetwork, self).__init__()
  8. self.flatten = nn.Flatten()
  9. self.linear_relu_stack = nn.Sequential(
  10. nn.Linear(28*28, 512), # wx + b = [64,1,784] * [784,512] = 64,1,512
  11. nn.ReLU(),
  12. nn.Linear(512, 512),
  13. nn.ReLU(),
  14. nn.Linear(512, 10)
  15. )
  16. def forward(self, x):
  17. x = self.flatten(x)
  18. logits = self.linear_relu_stack(x)
  19. return logits
  20. model = NeuralNetwork().to(device)
  21. print(model)

使用 cuda 设备

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) ) )

pytorch是通过继承 nn.Module 父类来实现自定义的网络模型。

init 中初始化神经网络层。

在 forward 方法中实现对输入数据的操作。


模型层分解

为了方便分解 FashionMNIST 模型中的各个层进行说明,我们取一个由 3 张大小为 28x28 的图像组成的小批量样本,看看当它们通过网络传递时会发生什么。

  1. input_image = torch.rand(3,28,28)
  2. print(input_image.size())

torch.Size([3, 28, 28])

nn.Flatten

我们初始化 nn.Flatten 层,将每个28x28 大小的二维图像转换为 784 个像素值的连续数组(保持小批量维度(dim=0))。

  1. flatten = nn.Flatten()
  2. flat_image = flatten(input_image)
  3. print(flat_image.size())

torch.Size([3, 784])

nn.Linear

linear layer线性层是一个模块,它使用其存储的权重和偏置对输入应用线性变换。

  1. layer1 = nn.Linear(in_features=28*28, out_features=20)
  2. hidden1 = layer1(flat_image)
  3. print(hidden1.size())

torch.Size([3, 20])

nn.ReLU

为了在模型的输入和输出之间创建复杂映射,我们使用非线性激活。激活函数在线性变换之后被调用,以便把结果值转为非线性,帮助神经网络学习到各种各样的关键特征值。 在这个模型中,线性层之间使用了 nn.ReLU,其实还有很多激活函数可以在模型中引入非线性。

  1. print(f"ReLU 之前的数据: {hidden1}\\n\\n")
  2. hidden1 = nn.ReLU()(hidden1)
  3. print(f"ReLU 之后的数据: {hidden1}")

ReLU 之前的数据: tensor([[-0.2496, 0.4432, -0.1536, 0.4439, 0.3256, 0.6594, -0.2536, 0.2348, -0.1113, 0.0732, -0.0658, 0.6014, -0.6135, -0.4709, 0.3016, 0.1500, 0.0801, 0.3644, -0.6113, 0.4129], [-0.7556, 0.1309, -0.2760, 0.3292, 0.5749, 0.6503, -0.1372, 0.3096, 0.1499, -0.0446, -0.1845, 0.2553, -0.6012, -0.3562, -0.0291, 0.1380, 0.2641, 0.2835, -0.5634, 0.1305], [-0.3772, -0.0354, -0.3879, 0.1846, 0.5425, 0.5019, 0.3323, 0.3478, 0.1171, 0.1153, -0.3414, 0.1688, -0.4068, 0.0950, -0.0322, 0.1272, 0.1653, 0.1538, -0.8849, 0.1446]], grad_fn=<AddmmBackward0>)

ReLU 之后的数据: tensor([[0.0000, 0.4432, 0.0000, 0.4439, 0.3256, 0.6594, 0.0000, 0.2348, 0.0000, 0.0732, 0.0000, 0.6014, 0.0000, 0.0000, 0.3016, 0.1500, 0.0801, 0.3644, 0.0000, 0.4129], [0.0000, 0.1309, 0.0000, 0.3292, 0.5749, 0.6503, 0.0000, 0.3096, 0.1499, 0.0000, 0.0000, 0.2553, 0.0000, 0.0000, 0.0000, 0.1380, 0.2641, 0.2835, 0.0000, 0.1305], [0.0000, 0.0000, 0.0000, 0.1846, 0.5425, 0.5019, 0.3323, 0.3478, 0.1171, 0.1153, 0.0000, 0.1688, 0.0000, 0.0950, 0.0000, 0.1272, 0.1653, 0.1538, 0.0000, 0.1446]], grad_fn=<ReluBackward0>)

nn.Sequential

nn.Sequential 是一个有序的模块容器。数据按照容器中定义的顺序通过所有模块。我们可以使用顺序容器来组合一个像 seq_modules 这样的快速处理网络。

  1. seq_modules = nn.Sequential(
  2. flatten,
  3. layer1,
  4. nn.ReLU(),
  5. nn.Linear(20, 10)
  6. )
  7. input_image = torch.rand(3,28,28)
  8. logits = seq_modules(input_image)

nn.Softmax

神经网络的最后一个线性层返回的是 logits类型的值,它们的取值是[-∞, ∞]。 把这些值传递给 nn.Softmax 模块。 logits的值将会被缩放到 [0, 1] 的取值区间,代表模型对每个类别的预测概率。 dim 参数指示我们在向量的哪个维度中计算softmax的值(和为1)。

  1. softmax = nn.Softmax(dim=1)
  2. pred_probab = softmax(logits)

模型参数

神经网络内的许多层都是包含可训练参数的,即具有在训练期间可以优化的相关权重(weight)和偏置(bias)。子类 nn.Module 可以自动跟踪模型对象中定义的所有参数字段。使用模型的 parameters()named_parameters() 方法可以访问模型中所有的参数。 下面的代码可以迭代模型中的每一个参数,并打印出它们的大小和它们的值。

  1. print("Model structure: ", model, "\\n\\n")
  2. for name, param in model.named_parameters():
  3. 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.0186, -0.0036, -0.0014, ..., -0.0294, -0.0217, 0.0293], [-0.0041, 0.0305, -0.0148, ..., 0.0036, -0.0100, 0.0239]], device='cuda:0', grad_fn=<SliceBackward0>)

Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values : tensor([-0.0168, -0.0071], device='cuda:0', grad_fn=<SliceBackward0>)

Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values : tensor([[ 0.0054, 0.0381, -0.0331, ..., 0.0163, -0.0087, -0.0287], [ 0.0252, 0.0025, -0.0126, ..., -0.0261, 0.0020, -0.0217]], device='cuda:0', grad_fn=<SliceBackward0>)

Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values : tensor([-0.0202, -0.0242], device='cuda:0', grad_fn=<SliceBackward0>)

Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values : tensor([[ 0.0142, -0.0315, -0.0046, ..., 0.0150, 0.0205, -0.0144], [ 0.0070, -0.0086, -0.0114, ..., -0.0193, 0.0310, 0.0325]], device='cuda:0', grad_fn=<SliceBackward0>)

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values : tensor([-0.0353, -0.0112], device='cuda:0', grad_fn=<SliceBackward0>)


定制模型损失函数和优化器

训练模型之前,我们需要为模型定制一个损失函数loss function和一个优化器 optimizer

  1. loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数
  2. optimizer = torch.optim.SGD(model.parameters(), lr=1e-3) # 使用随机梯度下降方法的优化器

训练并观察超参数

在单个训练循环中,模型对训练数据集进行预测(分批输入),并反向传播预测误差以调整模型参数。

  1. def train(dataloader, model, loss_fn, optimizer):
  2. size = len(dataloader.dataset) # 训练数据样本总量
  3. model.train() # 设置模型为训练模式
  4. for batch, (X, y) in enumerate(dataloader):
  5. X, y = X.to(device), y.to(device) # 张量加载到设备
  6. # 计算预测的误差
  7. pred = model(X) # 调用模型获得结果(forward时被自动调用)
  8. loss = loss_fn(pred, y) # 计算损失
  9. # 反向传播 Backpropagation
  10. model.zero_grad() # 重置模型中参数的梯度值为0
  11. loss.backward() # 计算梯度
  12. optimizer.step() # 更新模型中参数的梯度值
  13. if batch % 100 == 0:
  14. loss, current = loss.item(), batch * len(X)
  15. print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")

我们还要依赖测试数据集来检查模型的性能,以确保它的学习优化效果。

  1. def test(dataloader, model, loss_fn):
  2. size = len(dataloader.dataset)
  3. num_batches = len(dataloader)
  4. model.eval() # 模型设置为评估模式,代码等效于 model.train(False)
  5. test_loss, correct = 0, 0
  6. with torch.no_grad():
  7. for X, y in dataloader:
  8. X, y = X.to(device), y.to(device)
  9. pred = model(X)
  10. test_loss += loss_fn(pred, y).item()
  11. correct += (pred.argmax(1) == y).type(torch.float).sum().item()
  12. test_loss /= num_batches
  13. correct /= size
  14. print(f"Test Error: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n")

默认情况下,所有 requires_grad=True 属性值的张量都会被跟踪,以便于根据上一次的值来支持对梯度计算。但是,在某些情况下我们并不需要这样做,例如,当我们训练了模型但只想将其应用于某些输入数据的时。或者说白了就是我们只想通过网络进行前向计算时。我们可以把所有计算代码写在 torch.no_grad() 下面来停止跟踪计算。

训练过程在多轮迭代(epochs)中进行。在每个epoch中,模型通过学习更新内置的参数,以期做出更好的预测。我们在每个epochs打印模型的准确率和损失值;当然,最希望看到的,就是每个 epoch 过程中准确率的增加而损失函数值的减小。

  1. epochs = 5
  2. for t in range(epochs):
  3. print(f"Epoch {t+1}\\n-------------------------------")
  4. train(train_dataloader, model, loss_fn, optimizer)
  5. test(test_dataloader, model, loss_fn)
  6. print("训练完成!")
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/123543
推荐阅读
相关标签
  

闽ICP备14008679号