如何使用 pytorch 创建一个神经网络

我已发布在:如何使用 pytorch 创建一个神经网络 SapientialM.Github.io

构建神经网络

1 导入所需包

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

2 检查GPU是否可用

device = ("cuda"if torch.cuda.is_available()else "mps"if torch.backends.mps.is_available()else "cpu"
)
print(f"Using {device} device")
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.version.cuda)
Using cuda device
2.2.2+cu121
True
12.1

如果发现GPU不可用,可能是因为torch版本问题(比如我),应该去下载GPU版本。

  • 在CUDA官网找到合适版本的cuda,一般是根据系统平台和显卡版本来选择所需CUDA
  • 查看安装完成是否
# 版本,看CUDA的版本,比如我的是cuda_11.2
nvcc --version
# 驱动,看Driver Version
nvidia-smi
  • 去PyTorch官网找到合适版本的PyTorch,一般是根据开发环境来选择,然后复制所给的Commond去shell下安装即可
# 比如我的命令就是
pip install torch==2.2.2 torchvision==0.17.2 torchaudio==2.2.2 --index-url https://download.pytorch.org/whl/cu121

3 定义我们的神经网络

pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的,你可以先不去深入了解这个类,但是要知道,我们一般都是通过继承和重构nn.Module来定义我们的神经网络。我们一般重构__init__forward这两个方法。根据PyTorch官网的说法:__init__初始化神经网络层;forward层之间的数据操作,也是整个网络的核心。__init__只会定义层,而forward负责将层连接起来。实际上类的初始化参数一般是一些固有属性,我们可以将一些带有训练参数的层放在__init__,而没有训练参数的层是可以加入到forward里面的,或者说我们将没有训练参数的层看作是层之间的数据操作。

当然直接这么说,肯定不是很清晰,我们来看一个官网给的例子:

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
model = NeuralNetwork().to(device) # 将网络移入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))
)

我们用torchviz可以将神经网络进行一个简单的可视化,当然很多参数可选,这里不一一列举

pip install torchviz
from torchviz import make_dot
X = torch.rand(1, 28, 28, device=device) # 需要对神经网络进行数据输入,才是一个完整的网络
y = model(X)
output = make_dot(y.mean(), params=dict(model.named_parameters())) # 开始绘制
output.format = "png"
output.directory = "."
output.render("torchviz", view=True) # 会在相对路径下保存一个可视化后的图片并打开
'torchviz.png'

看起来可能会比较复杂,也看不懂,我们得需要学会看懂神经网络的结构才能看懂结构图。当然,还有诸如draw_convnet、NNSVG、netron等可视化工具会更加优秀。

4 神经网络模型层

想要构建一个神经网络并进行训练和预测,我们需要去认识神经网络的构成。假定你已经了解过感知机、人工神经网络的基本概念,那么现在就是来了解一下神经网络的模型层。

我们直接分解一下官网所给出的这个模型,这是一个简单的前馈神经网络(Feedforward Neural Network),我们先不去了解它的作用,一点点的分解它,看看它最终实现了什么。

我们先根据官网所说的,取一个大小为 28x28 的 3 张图像的样本小批量作为输入(我们一般将数据的第一个维度看作批量维度并保留):

input_image = torch.rand(3,28,28)
print(input_image.size())
torch.Size([3, 28, 28])

4.1 nn.Flatten

虽然是PyTorch的nn.Flatten,但是Flatten层是神经网络中常见的组成部分。在神经网络的训练和预测过程中,输入数据通常需要经过一系列的处理和转换。在这个过程中,Flatten层能够将多维的输入数据转化为一维的线性形式,以便于神经网络的进一步处理。模型中的nn.Flatten,将我们所输入的2D 28*28 图像转换为一个包含 784 个像素值的连续数组,也就是和它表面的意思一样展平这个高维数组。

nn.Flatten()默认参数是start_dim=1end_dim=-1,如果你想展平所有维度,可以通过设置start_dim=0来实现)

flatten = nn.Flatten()
flat_image = flatten(input_image) # 将输入的图像展平
print(flat_image.size())
torch.Size([3, 784])

在卷积神经网络(CNN)中,Flatten层可以将卷积层提取到的特征图展平,便于进一步的特征处理或分类,也便于输入到全连接层(全连接层通常需要一维的输入,后面会讲到)。在构建复杂网络时,Flatten层可以帮助不同类型的层之间进行连接。总的来说,Flatten层起到了桥梁的作用,使得卷积神经网络的层次结构更加灵活和易于设计,并且确保了从卷积层到全连接层的数据传递顺畅,维持了网络的整体性能和效率。

4.2 nn.Linear

nn.Linear应该耳熟能详,我们称之为线性层(Linear Layer),也可以称为全连接层(Fully Connected Layer)或密集层(Dense Layer)。线性层是一个使用其存储的权重和偏差对输入应用线性变换的模块,也就是对输入数据进行线性变换。线性层对数据的处理方式基本上可以表示为:
y = W x + b y = Wx + b y=Wx+b
,其中 W 是权重矩阵,b 是偏置。向量都是可学习的参数。在神经网络的训练和预测过程中,Linear层的作用是将输入数据通过一组权重进行线性变换,然后添加一个偏置项。简单来说,它能够将输入特征映射到输出特征,从而实现对数据的线性组合和转换。如下图是一个单隐藏层的多层感知机(Multilayer Perceptron),一般称为MLP,隐藏层和输出层均是由线性层和激活函数组成:

MLP

# 定义一个线性层,将28*28维度的向量转换为20维度的向量
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image) 
print(hidden1.size())
torch.Size([3, 20])

在这个例子中,in_features=28*28表示输入特征的维度,out_features=20表示输出特征的维度。nn.Linear层会自动初始化权重和偏置,并在训练过程中通过反向传播算法进行调整。简单理解就是,该线性层的输入是784维,而输出是20维。

4.3 nn.ReLU

ReLU函数,全称Rectified Linear Unit,是人工神经网络中常用的一种激活函数.
讲到这里,我们就讲讲常见的激活函数及其作用。

Sigmoid 激活函数

数学表达式:
σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1 + e^{-x}} σ(x)=1+ex1

作用:

  • 将输入映射到 (0, 1) 之间。
  • 常用于输出层,尤其是在二分类问题中,输出概率值。

优点:

  • 输出范围在 (0, 1) 之间,可以解释为概率。
  • 平滑梯度,有助于梯度下降。

缺点:

  • 容易导致梯度消失问题。
  • 输出不是零中心的,会影响网络的训练效率。
import numpy as np
import matplotlib.pyplot as pltdef sigmoid(x):return 1 / (1 + np.exp(-x))x = np.linspace(-10, 10, 100)
y = sigmoid(x)plt.plot(x, y)
plt.title("Sigmoid Activation Function")
plt.xlabel("x")
plt.ylabel("Sigmoid(x)")
plt.grid()
plt.show()

sigmoid

Tanh 激活函数

数学表达式:
tanh ⁡ ( x ) = e x − e − x e x + e − x \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} tanh(x)=ex+exexex

作用:

  • 将输入映射到 (-1, 1) 之间。
  • 常用于隐藏层,提供零中心的输出,有助于训练。

优点:

  • 输出是零中心的,梯度消失问题较轻。

缺点:

  • 仍然存在梯度消失问题。
import numpy as np
import matplotlib.pyplot as pltdef tanh(x):return np.tanh(x)x = np.linspace(-10, 10, 100)
y = tanh(x)plt.plot(x, y)
plt.title("Tanh Activation Function")
plt.xlabel("x")
plt.ylabel("Tanh(x)")
plt.grid()
plt.show()

Tanh

ReLU (Rectified Linear Unit) 激活函数

数学表达式:
ReLU ( x ) = max ⁡ ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)

作用:

  • 将输入小于0的部分设为0,大于0的部分保持不变。
  • 常用于隐藏层,特别是深度神经网络。

优点:

  • 计算简单,收敛速度快。
  • 减少梯度消失问题。

缺点:

  • 输出不是零中心的。
  • 输入小于0时梯度为零,可能导致“神经元死亡”问题。
import numpy as np
import matplotlib.pyplot as pltdef relu(x):return np.maximum(0, x)x = np.linspace(-10, 10, 100)
y = relu(x)plt.plot(x, y)
plt.title("ReLU Activation Function")
plt.xlabel("x")
plt.ylabel("ReLU(x)")
plt.grid()
plt.show()

RELU

Leaky ReLU 激活函数

数学表达式:
Leaky ReLU ( x ) = { x if  x > 0 α x if  x ≤ 0 \text{Leaky ReLU}(x) = \begin{cases} x & \text{if } x > 0 \\ \alpha x & \text{if } x \leq 0 \end{cases} Leaky ReLU(x)={xαxif x>0if x0
其中 (\alpha) 通常是一个很小的常数,如 0.01。

作用:

  • 解决 ReLU 的“神经元死亡”问题。

优点:

  • 输入小于0时仍有较小梯度,避免神经元死亡。

缺点:

  • 计算稍复杂。
import numpy as np
import matplotlib.pyplot as pltdef leaky_relu(x, alpha=0.01):return np.where(x > 0, x, alpha * x)x = np.linspace(-10, 10, 100)
y = leaky_relu(x)plt.plot(x, y)
plt.title("Leaky ReLU Activation Function")
plt.xlabel("x")
plt.ylabel("Leaky ReLU(x)")
plt.grid()
plt.show()

Leaky RELU

Softmax 激活函数

数学表达式:
Softmax ( x i ) = e x i ∑ j e x j \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j} e^{x_j}} Softmax(xi)=jexjexi

作用:

  • 将输入向量转换为概率分布,总和为1。
  • 常用于多分类问题的输出层。

优点:

  • 输出可以解释为概率,便于分类。

缺点:

  • 计算相对复杂,容易导致数值不稳定。
import numpy as np
import matplotlib.pyplot as pltdef softmax(x):e_x = np.exp(x - np.max(x))return e_x / e_x.sum(axis=0)x = np.linspace(-10, 10, 100)
y = softmax(x)plt.plot(x, y)
plt.title("Softmax Activation Function")
plt.xlabel("x")
plt.ylabel("Softmax(x)")
plt.grid()
plt.show()

Softmax

4.4 nn.Sequential

nn.Sequential是 PyTorch 提供的一个容器模块,它按顺序包含其他子模块,便于构建和管理简单的神经网络结构。通过 nn.Sequential,可以方便地将一系列层(如线性层、激活函数、卷积层等)按顺序堆叠在一起,从而简化模型定义和前向传播的代码。简而言之就是一个包裹的顺序容器。

5 理解我们的神经网络

看完这些,我们再来理解这个官网给的例子:

class NeuralNetwork(nn.Module):# 重构 __init__,定义“固有属性”def __init__(self):# 这一步操作是调用父类 nn.Module 的构造函数,确保继承自 nn.Module 的特性正确初始化super().__init__()# 定义一个展开层 flattenself.flatten = nn.Flatten()# 定义一个线性容器,可以简化在forward中的调用self.linear_relu_stack = nn.Sequential(# 容器内包含一个三层网络# 这里的512、10都是研究者根据具体任务和数据集进行调试和优化得到的结果# 熟悉的调参nn.Linear(28*28, 512),nn.ReLU(),nn.Linear(512, 512),nn.ReLU(),nn.Linear(512, 10),)# 重构forward,定义前向传播路径def forward(self, x):# 在这里定义各个层输入输出的顺序,即层在网络里的位置关系x = self.flatten(x)logits = self.linear_relu_stack(x)return logits
model = NeuralNetwork().to(device) # 将网络移入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))
)

6 使用我们的网络

主要步骤如下:

  • 定义模型
  • 数据载入
  • 损失函数和优化
  • 训练和评估
  • 预测与可视化

先导入所需包:

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt

6.1 定义模型

# 定义设备,如果有GPU则使用GPU,否则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定义神经网络模型
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# 创建神经网络模型实例,并移动到设备上
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))
)

6.2 数据载入

我们这次用的模型用于简单图像分类问题,所以可以使用MNIST数据集,导入用的是PyTorch的datasets。

# 加载MNIST数据集并进行预处理
transform = transforms.Compose([# 对图片的常用操作,将图像数据转换为形状为 (C, H, W) 的张量transforms.ToTensor(),# 因为数据集是灰度图像,所以只有单值标准化transforms.Normalize((0.5,), (0.5,))
])# 加载MNIST数据集,并划分训练集和测试集(这里会下载下来)
train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)# 定义批量大小和数据加载器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

6.3 损失函数和优化

损失函数选取交叉熵损失函数(Cross Entropy Loss),它是一种常用的损失函数,能够有效地衡量预测类别和真实类别之间的差异。它能够处理模型输出的logits,并且在计算过程中会自动应用Softmax操作,从而简化代码。

优化器选取随机梯度下降法(Stochastic Gradient Descent, SGD),它是一种简单而有效的优化方法,特别适用于大规模数据集和模型。结合Momentum算法,SGD优化器可以加速收敛并减小震荡,从而在一定程度上提高训练效率和模型性能。

# 定义损失函数
criterion = nn.CrossEntropyLoss()
# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

6.4 训练和评估

# 训练模型
# 训练的目的是通过多个训练周期和批次的数据,不断调整模型参数以最小化损失函数,从而提高模型的性能。
def train(model, train_loader, optimizer, criterion, epochs=5):# 某些层(如Dropout和BatchNorm)在训练和评估模式下的行为不同,所以需要显式地设置模型为训练模式。model.train()# 开始训练循环for epoch in range(epochs):# 初始化损失running_loss = 0.0# 遍历训练数据加载器中的每个批次for batch_idx, (data, target) in enumerate(train_loader):# 将数据移动到设备上data, target = data.to(device), target.to(device)# 梯度清零optimizer.zero_grad()# 前向传播,将数据输入模型进行预测output = model(data)# 计算损失loss = criterion(output, target)# 将损失反向传播loss.backward()# 使用优化器更新参数optimizer.step() # 累计损失running_loss += loss.item()if batch_idx % 100 == 99:    # 每100个批次打印一次训练状态print(f'Epoch [{epoch+1}/{epochs}], Step [{batch_idx+1}/{len(train_loader)}], Loss: {running_loss/100:.4f}')running_loss = 0.0# 测试模型
def test(model, test_loader):# 进入评估模式,原因与train同理model.eval()# 累计计数correct = 0total = 0# 测试过程不会进行优化,所以no_grad禁用梯度计算可以加快测试速度with torch.no_grad():for data, target in test_loader:data, target = data.to(device), target.to(device)# 前向传播,将数据输入模型进行预测outputs = model(data)# 获取预测结果_, predicted = torch.max(outputs.data, 1)total += target.size(0)correct += (predicted == target).sum().item()accuracy = 100 * correct / totalprint(f'Test Accuracy: {accuracy:.2f}%')

6.5 预测与可视化

# 可视化预测结果
def visualize_predictions(model, test_loader, num_images=10):# 这里和上面定义的test相似,主要是在执行过程中添加了可视化代码和限制了测试数量model.eval()images_so_far = 0plt.figure(figsize=(12, 8))with torch.no_grad():for i, (inputs, labels) in enumerate(test_loader):inputs = inputs.to(device)labels = labels.to(device)# 获取每张图的预测结果,并将数据绘制出来进行比对outputs = model(inputs)_, preds = torch.max(outputs, 1)for j in range(inputs.size(0)):images_so_far += 1ax = plt.subplot(num_images // 5, 5, images_so_far)ax.axis('off')ax.set_title(f'Predicted: {preds[j]} (Label: {labels[j]})')# imshow用于在绘图窗口中显示图像ax.imshow(inputs.cpu().data[j].numpy().squeeze(), cmap='gray')if images_so_far == num_images:model.train()returnmodel.train()# 执行训练
train(model, train_loader, optimizer, criterion)# 执行测试
test(model, test_loader)# 可视化预测结果
visualize_predictions(model, test_loader, num_images=10)
plt.suptitle('Model Predictions')
plt.tight_layout()
plt.show()
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))
)
Epoch [1/5], Step [100/938], Loss: 1.1812
Epoch [1/5], Step [200/938], Loss: 0.4243
Epoch [1/5], Step [300/938], Loss: 0.3437
Epoch [1/5], Step [400/938], Loss: 0.3305
Epoch [1/5], Step [500/938], Loss: 0.2820
Epoch [1/5], Step [600/938], Loss: 0.2634
Epoch [1/5], Step [700/938], Loss: 0.2482
Epoch [1/5], Step [800/938], Loss: 0.2131
Epoch [1/5], Step [900/938], Loss: 0.2161
Epoch [2/5], Step [100/938], Loss: 0.1853
Epoch [2/5], Step [200/938], Loss: 0.1658
Epoch [2/5], Step [300/938], Loss: 0.1766
Epoch [2/5], Step [400/938], Loss: 0.1507
Epoch [2/5], Step [500/938], Loss: 0.1606
Epoch [2/5], Step [600/938], Loss: 0.1347
Epoch [2/5], Step [700/938], Loss: 0.1407
Epoch [2/5], Step [800/938], Loss: 0.1371
Epoch [2/5], Step [900/938], Loss: 0.1283
Epoch [3/5], Step [100/938], Loss: 0.1027
Epoch [3/5], Step [200/938], Loss: 0.1169
Epoch [3/5], Step [300/938], Loss: 0.1150
Epoch [3/5], Step [400/938], Loss: 0.1077
Epoch [3/5], Step [500/938], Loss: 0.0986
Epoch [3/5], Step [600/938], Loss: 0.1139
Epoch [3/5], Step [700/938], Loss: 0.1110
Epoch [3/5], Step [800/938], Loss: 0.0986
Epoch [3/5], Step [900/938], Loss: 0.0927
Epoch [4/5], Step [100/938], Loss: 0.0908
Epoch [4/5], Step [200/938], Loss: 0.0834
Epoch [4/5], Step [300/938], Loss: 0.0957
Epoch [4/5], Step [400/938], Loss: 0.0742
Epoch [4/5], Step [500/938], Loss: 0.0873
Epoch [4/5], Step [600/938], Loss: 0.0786
Epoch [4/5], Step [700/938], Loss: 0.0901
Epoch [4/5], Step [800/938], Loss: 0.0828
Epoch [4/5], Step [900/938], Loss: 0.0810
Epoch [5/5], Step [100/938], Loss: 0.0682
Epoch [5/5], Step [200/938], Loss: 0.0729
Epoch [5/5], Step [300/938], Loss: 0.0601
Epoch [5/5], Step [400/938], Loss: 0.0684
Epoch [5/5], Step [500/938], Loss: 0.0755
Epoch [5/5], Step [600/938], Loss: 0.0706
Epoch [5/5], Step [700/938], Loss: 0.0733
Epoch [5/5], Step [800/938], Loss: 0.0579
Epoch [5/5], Step [900/938], Loss: 0.0621
Test Accuracy: 97.45%

在这里插入图片描述

神经网络(尤其是深度神经网络)的一个非常吸引人的特点就是:它们具有很强的通用性,可以通过不同的数据集进行训练,以解决各种不同的任务。我们可以将该模型使用另外的数据集进行训练和测试,仍然有不低的准确率。

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import numpy as np
import matplotlib.pyplot as plt# 定义设备,如果有GPU则使用GPU,否则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 定义神经网络模型
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# 创建神经网络模型实例,并移动到设备上
model = NeuralNetwork().to(device)
print(model)# 加载FashionMNIST数据集并进行预处理
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,))
])train_dataset = datasets.FashionMNIST(root='./data', train=True, transform=transform, download=True)
test_dataset = datasets.FashionMNIST(root='./data', train=False, transform=transform)# 定义批量大小和数据加载器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# 训练模型
def train(model, train_loader, optimizer, criterion, epochs=5):model.train()for epoch in range(epochs):running_loss = 0.0for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()running_loss += loss.item()if batch_idx % 100 == 99:    # 每100个批次打印一次训练状态print(f'Epoch [{epoch+1}/{epochs}], Step [{batch_idx+1}/{len(train_loader)}], Loss: {running_loss/100:.4f}')running_loss = 0.0# 测试模型
def test(model, test_loader):model.eval()correct = 0total = 0with torch.no_grad():for data, target in test_loader:data, target = data.to(device), target.to(device)outputs = model(data)_, predicted = torch.max(outputs.data, 1)total += target.size(0)correct += (predicted == target).sum().item()accuracy = 100 * correct / totalprint(f'Test Accuracy: {accuracy:.2f}%')# 可视化预测结果
def visualize_predictions(model, test_loader, num_images=10):model.eval()images_so_far = 0plt.figure(figsize=(12, 8))with torch.no_grad():for i, (inputs, labels) in enumerate(test_loader):inputs = inputs.to(device)labels = labels.to(device)outputs = model(inputs)_, preds = torch.max(outputs, 1)for j in range(inputs.size(0)):images_so_far += 1ax = plt.subplot(num_images // 5, 5, images_so_far)ax.axis('off')ax.set_title(f'Predicted: {preds[j]} (Label: {labels[j]})')ax.imshow(inputs.cpu().data[j].numpy().squeeze(), cmap='gray')if images_so_far == num_images:model.train()returnmodel.train()# 执行训练
train(model, train_loader, optimizer, criterion)# 执行测试
test(model, test_loader)# 可视化预测结果
visualize_predictions(model, test_loader, num_images=10)
plt.suptitle('Model Predictions')
plt.tight_layout()
plt.show()
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))
)
Epoch [1/5], Step [100/938], Loss: 1.1111
Epoch [1/5], Step [200/938], Loss: 0.6047
Epoch [1/5], Step [300/938], Loss: 0.5097
Epoch [1/5], Step [400/938], Loss: 0.4919
Epoch [1/5], Step [500/938], Loss: 0.4808
Epoch [1/5], Step [600/938], Loss: 0.4519
Epoch [1/5], Step [700/938], Loss: 0.4558
Epoch [1/5], Step [800/938], Loss: 0.4473
Epoch [1/5], Step [900/938], Loss: 0.4138
Epoch [2/5], Step [100/938], Loss: 0.3960
Epoch [2/5], Step [200/938], Loss: 0.3889
Epoch [2/5], Step [300/938], Loss: 0.4075
Epoch [2/5], Step [400/938], Loss: 0.3719
Epoch [2/5], Step [500/938], Loss: 0.3819
Epoch [2/5], Step [600/938], Loss: 0.3858
Epoch [2/5], Step [700/938], Loss: 0.3838
Epoch [2/5], Step [800/938], Loss: 0.3564
Epoch [2/5], Step [900/938], Loss: 0.3616
Epoch [3/5], Step [100/938], Loss: 0.3488
Epoch [3/5], Step [200/938], Loss: 0.3507
Epoch [3/5], Step [300/938], Loss: 0.3522
Epoch [3/5], Step [400/938], Loss: 0.3363
Epoch [3/5], Step [500/938], Loss: 0.3375
Epoch [3/5], Step [600/938], Loss: 0.3445
Epoch [3/5], Step [700/938], Loss: 0.3378
Epoch [3/5], Step [800/938], Loss: 0.3208
Epoch [3/5], Step [900/938], Loss: 0.3163
Epoch [4/5], Step [100/938], Loss: 0.3189
Epoch [4/5], Step [200/938], Loss: 0.3005
Epoch [4/5], Step [300/938], Loss: 0.3071
Epoch [4/5], Step [400/938], Loss: 0.3240
Epoch [4/5], Step [500/938], Loss: 0.3147
Epoch [4/5], Step [600/938], Loss: 0.2946
Epoch [4/5], Step [700/938], Loss: 0.3150
Epoch [4/5], Step [800/938], Loss: 0.3024
Epoch [4/5], Step [900/938], Loss: 0.3152
Epoch [5/5], Step [100/938], Loss: 0.2723
Epoch [5/5], Step [200/938], Loss: 0.2969
Epoch [5/5], Step [300/938], Loss: 0.2963
Epoch [5/5], Step [400/938], Loss: 0.2835
Epoch [5/5], Step [500/938], Loss: 0.2910
Epoch [5/5], Step [600/938], Loss: 0.2990
Epoch [5/5], Step [700/938], Loss: 0.2990
Epoch [5/5], Step [800/938], Loss: 0.3039
Epoch [5/5], Step [900/938], Loss: 0.3005
Test Accuracy: 87.60%

在这里插入图片描述

7 优化与调参

显然,同样的模型对于不同的数据集的适配程度是不一样的。对于MNIST数据集准确率可以达到97%,但对于FashionMNIST只能达到86%。所以我们可以来探索一下为什么会有这样的偏差,以及如何优化该模型才能让FashionMNIST也可以达到90%以上的准确率。

7.1 可能的问题

  1. 数据集的复杂性
  • MNIST 数据集:包含手写数字的灰度图像(0-9),这些图像相对简单,特征明显,模式较少。
  • FashionMNIST 数据集:包含服装物品的灰度图像(例如 T 恤、裤子、鞋子等),这些图像的特征更加复杂,类别之间的差异较小。
  1. 模型的复杂性
  • 我们使用的是一个简单的全连接神经网络,它可能足以在 MNIST 数据集上达到高准确率,但在处理更复杂的 FashionMNIST 数据集时会表现不佳。
  1. 超参数调整:
  • 我们的模型可能需要在不同的数据集上进行不同的超参数调整。例如,学习率、批量大小、正则化参数等可能需要重新调整以适应 FashionMNIST 的复杂性。
  1. 数据预处理:
  • 数据的预处理步骤(如标准化、归一化、数据增强等)对不同的数据集可能有不同的效果。我们可能需要针对 FashionMNIST 数据集尝试不同的预处理方法。

7.2 解决方案

7.2.1 增加神经网络层数和神经元容量

通过增加模型的容量,模型能够学习更多的特征。如下,我们添加两个线性层进一步提高模型对复杂特征的处理能力。

class NeuralNetwork_v1(nn.Module):def __init__(self):super().__init__()self.flatten = nn.Flatten()self.linear_relu_stack = nn.Sequential(nn.Linear(28*28, 1024),nn.ReLU(),nn.Linear(1024, 1024),nn.ReLU(),nn.Linear(1024, 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

可能因为线性层在处理FashionMNIST数据集时,难以处理和学习更多的特征,在添加了线性层后预测准确率没有明显的提高,仍然是87%左右。所以需要尝试其他的方法。

7.2.2 使用卷积神经网络(CNN)

FashionMNIST 数据集涉及到服装和配件的图像分类,每个图像都是单通道的灰度图像,分辨率为 28x28 像素。尽管 FashionMNIST 数据集相对于真实世界的图像数据集如 CIFAR-10 或 ImageNet 来说较为简单,但仍然涉及到一定程度的空间特征。且如纹理图案、形状轮廓等复杂特征,线性层更加难以处理和识别。所以在局部相关性和空间结构处理占优的情况下,使用卷积神经网络(CNN)来处理是更优的选择。

import torch
import torch.nn as nn
import torch.nn.functional as Fclass ConvNeuralNetwork(nn.Module):def __init__(self):super(ConvNeuralNetwork, self).__init__()# 卷积层定义self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)# 扁平化层self.flatten = nn.Flatten()# 全连接层self.fc1 = nn.Linear(128*3*3, 512)self.fc2 = nn.Linear(512, 512)self.fc3 = nn.Linear(512, 10)# Dropout 正则化层,用于随机丢弃一定比例的神经元,防止过拟合self.dropout = nn.Dropout(0.4)# 批量归一化层,对每个卷积层的输出进行归一化,有助于加速收敛和提高模型泛化能力self.batch_norm1 = nn.BatchNorm2d(32)self.batch_norm2 = nn.BatchNorm2d(64)self.batch_norm3 = nn.BatchNorm2d(128)# 批量归一化层,对全连接层的输出进行归一化self.batch_norm_fc1 = nn.BatchNorm1d(512)self.batch_norm_fc2 = nn.BatchNorm1d(512)def forward(self, x):# 卷积层和激活函数# 依次进行卷积、ReLU 激活函数、批量归一化和最大池化操作x = self.conv1(x)x = F.relu(self.batch_norm1(x))x = F.max_pool2d(x, 2)x = self.conv2(x)x = F.relu(self.batch_norm2(x))x = F.max_pool2d(x, 2)x = self.conv3(x)x = F.relu(self.batch_norm3(x))x = F.max_pool2d(x, 2)# 进行全连接和正则化x = self.flatten(x)x = self.fc1(x)x = F.relu(self.batch_norm_fc1(x))x = self.dropout(x)x = self.fc2(x)x = F.relu(self.batch_norm_fc2(x))x = self.dropout(x)# 输出层logits = self.fc3(x)return logits# 创建CNN实例,并移动到设备上
model_v1 = ConvNeuralNetwork().to(device)# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model_v1.parameters(), lr=0.01, momentum=0.9)# train_loader、test_loader 在之前已初始化# 训练模型
def train_v1(model, train_loader, optimizer, criterion, epochs=5):model.train()for epoch in range(epochs):running_loss = 0.0for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()running_loss += loss.item()if batch_idx % 100 == 99:    # 每100个批次打印一次训练状态print(f'Epoch [{epoch+1}/{epochs}], Step [{batch_idx+1}/{len(train_loader)}], Loss: {running_loss/100:.4f}')running_loss = 0.0# 获取类别名称
class_names_v1 = train_dataset.classes# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用SimHei字体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号'-'显示为方块的问题def visualize_predictions_v1(model, dataloader, class_names):model.eval()num_classes = len(class_names)confusion_matrix = np.zeros((num_classes, num_classes), dtype=np.int32)with torch.no_grad():for inputs, labels in dataloader:inputs, labels = inputs.to(device), labels.to(device)  # 将数据移动到设备上outputs = model(inputs)_, preds = torch.max(outputs, 1)for t, p in zip(labels.view(-1), preds.view(-1)):confusion_matrix[t.long(), p.long()] += 1# 归一化混淆矩阵confusion_matrix = confusion_matrix.astype('float') / confusion_matrix.sum(axis=1)[:, np.newaxis]# 绘图fig, ax = plt.subplots(figsize=(10, 8))im = ax.imshow(confusion_matrix, interpolation='nearest', cmap=plt.cm.Blues)ax.figure.colorbar(im, ax=ax)ax.set(xticks=np.arange(confusion_matrix.shape[1]),yticks=np.arange(confusion_matrix.shape[0]),xticklabels=class_names, yticklabels=class_names,title='归一化混淆矩阵',ylabel='真实标签',xlabel='预测标签')# 旋转标签并设置对齐plt.setp(ax.get_xticklabels(), rotation=45, ha="right",rotation_mode="anchor")# 遍历数据维度并创建文本注释fmt = '.2f'thresh = confusion_matrix.max() / 2.for i in range(confusion_matrix.shape[0]):for j in range(confusion_matrix.shape[1]):ax.text(j, i, format(confusion_matrix[i, j], fmt),ha="center", va="center",color="white" if confusion_matrix[i, j] > thresh else "black")fig.tight_layout()plt.show()# 执行训练
train_v1(model_v1, train_loader, optimizer, criterion)# 执行测试
test(model_v1, test_loader)# 可视化预测结果
visualize_predictions_v1(model_v1, test_loader, class_names_v1)
plt.suptitle('Model Predictions_v1')
plt.tight_layout()
plt.show()
Epoch [1/5], Step [100/938], Loss: 0.7875
Epoch [1/5], Step [200/938], Loss: 0.4787
Epoch [1/5], Step [300/938], Loss: 0.4455
Epoch [1/5], Step [400/938], Loss: 0.3960
Epoch [1/5], Step [500/938], Loss: 0.3657
Epoch [1/5], Step [600/938], Loss: 0.3557
Epoch [1/5], Step [700/938], Loss: 0.3363
Epoch [1/5], Step [800/938], Loss: 0.3495
Epoch [1/5], Step [900/938], Loss: 0.3140
Epoch [2/5], Step [100/938], Loss: 0.2842
Epoch [2/5], Step [200/938], Loss: 0.3065
Epoch [2/5], Step [300/938], Loss: 0.2671
Epoch [2/5], Step [400/938], Loss: 0.2750
Epoch [2/5], Step [500/938], Loss: 0.2874
Epoch [2/5], Step [600/938], Loss: 0.2722
Epoch [2/5], Step [700/938], Loss: 0.2639
Epoch [2/5], Step [800/938], Loss: 0.2840
Epoch [2/5], Step [900/938], Loss: 0.2630
Epoch [3/5], Step [100/938], Loss: 0.2359
Epoch [3/5], Step [200/938], Loss: 0.2461
Epoch [3/5], Step [300/938], Loss: 0.2350
Epoch [3/5], Step [400/938], Loss: 0.2337
Epoch [3/5], Step [500/938], Loss: 0.2453
Epoch [3/5], Step [600/938], Loss: 0.2247
Epoch [3/5], Step [700/938], Loss: 0.2354
Epoch [3/5], Step [800/938], Loss: 0.2351
Epoch [3/5], Step [900/938], Loss: 0.2333
Epoch [4/5], Step [100/938], Loss: 0.2045
Epoch [4/5], Step [200/938], Loss: 0.2206
Epoch [4/5], Step [300/938], Loss: 0.2161
Epoch [4/5], Step [400/938], Loss: 0.2125
Epoch [4/5], Step [500/938], Loss: 0.2003
Epoch [4/5], Step [600/938], Loss: 0.2060
Epoch [4/5], Step [700/938], Loss: 0.1919
Epoch [4/5], Step [800/938], Loss: 0.2012
Epoch [4/5], Step [900/938], Loss: 0.2138
Epoch [5/5], Step [100/938], Loss: 0.1789
Epoch [5/5], Step [200/938], Loss: 0.1724
Epoch [5/5], Step [300/938], Loss: 0.1737
Epoch [5/5], Step [400/938], Loss: 0.1883
Epoch [5/5], Step [500/938], Loss: 0.1921
Epoch [5/5], Step [600/938], Loss: 0.1982
Epoch [5/5], Step [700/938], Loss: 0.2055
Epoch [5/5], Step [800/938], Loss: 0.1865
Epoch [5/5], Step [900/938], Loss: 0.1930
Test Accuracy: 91.53%

在这里插入图片描述

<Figure size 640x480 with 0 Axes>
7.2.3 调整超参数

超参数是模型训练过程中需要预先设定的参数,学习率、批次大小和迭代次数等都称之为超参数。通过调整这些超参数,我们可以提高模型的性能和准确性。

  • 学习率是最重要的超参数之一。我们可以尝试不同的学习率,观察其对模型性能的影响:
learning_rates = [0.1, 0.01, 0.001]for lr in learning_rates:optimizer = optim.SGD(model.parameters(), lr=lr)# 训练模型并记录性能
  • 当然我们还可以通过学习率调度器在训练过程中动态调整学习率
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)for epoch in range(num_epochs):# 训练模型scheduler.step()
  • 批量大小会影响训练的稳定性和速度:
batch_sizes = [32, 64, 128]for batch_size in batch_sizes:train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)# 训练模型并记录性能
  • 不同的优化器可能会对模型的收敛速度和最终性能产生影响。我们可以尝试不同的优化器,如SGD和Adam:
optimizers = {'SGD': optim.SGD(model.parameters(), lr=0.01),'Adam': optim.Adam(model.parameters(), lr=0.01)
}

参考

  • PyTorch-构建神经网络
  • PyTorch中文文档
  • d2L-多层感知机

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/42016.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Yolov10训练,转化onnx,推理

yolov10对于大目标的效果好&#xff0c;小目标不好 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 目录 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 二、配置好后就可以配置文件…

前端JS特效第24集:jquery css3实现瀑布流照片墙特效

jquery css3实现瀑布流照片墙特效&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8" /> <title>jquerycss3实现瀑…

Nginx:负载均衡小专题

运维专题 Nginx&#xff1a;负载均衡小专题 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/…

【专项刷题】— 位运算

常见类型介绍&#xff1a; & &#xff1a;有 0 就是 0 | &#xff1a;有 1 就是 1 ^ &#xff1a;相同为 0 &#xff0c;相异为 1 或者 无进位相加给定一个数确定它的二进制位的第x个数是0还是1&#xff1a;将一个数的二进制的第x位改成1&#xff1a;将一个数的二进制的第x…

Windows10/11家庭版开启Hyper-V虚拟机功能详解

Hyper-V是微软的一款虚拟机软件&#xff0c;可以使我们在一台Windows PC上&#xff0c;在虚拟环境下同时运行多个互相之间完全隔离的操作系统&#xff0c;这就实现了在Windows环境下运行Linux以及其他OS的可能性。和第三方虚拟机软件&#xff0c;如VMware等相比&#xff0c;Hyp…

大模型知识问答: 文本分块要点总结

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、算法项目落地经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接如…

C++ 信号量和锁的区别

网上关于信号量和锁的区别&#xff0c;写的比较官方晦涩难懂&#xff0c;对于这个知识点吸收难&#xff0c;通过示例&#xff0c;我们看到信号量&#xff0c;可以控制同一时刻的线程数量&#xff0c;就算同时开启很多线程&#xff0c;依然可以的达到线程数可控 #include <i…

初识c++(命名空间,缺省参数,函数重载)

一、命名空间 1、namespace的意义 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在于全 局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;以避免命名 冲突…

世界商用飞机机型大全-使用Java抓取FlightAware后的答案

目录 前言 一、数据说明 1、实时航班飞机机型数据 2、网页结构分析 二、使用Java进行信息抓取 1、定义页面PageVO对象 2、爬取属性定义 3、启动信息抓取组件 三、成果分析 1、商业飞行的飞机机型的种类 2、飞机种类排名前十名 3、航班数排名后十名 4、看中国国产大飞…

你真的会信息收集嘛,4k字渗透测试信息收集10大技巧

前言 在渗透测试中&#xff0c;信息收集是非常关键的一步&#xff0c;它为后续的漏洞发现和利用提供了重要的基础。以下是非常详细的信息收集方式&#xff1a; 一、被动信息收集 被动信息收集是指在不与目标系统直接交互的情况下&#xff0c;通过公开渠道获取目标系统的相关…

基于51单片机的四路抢答器Protues仿真设计

一、设计背景 近年来随着科技的飞速发展&#xff0c;单片机的应用正在不断的走向深入。本文阐述了基于51单片机的八路抢答器设计。本设计中&#xff0c;51单片机充当了核心控制器的角色&#xff0c;通过IO口与各个功能模块相连接。按键模块负责检测参与者的抢答动作&#xff0c…

力扣-贪心算法4

406.根据身高重建队列 406. 根据身高重建队列 题目 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高大于或…

MyBatis的简介与使用

Mybatis JDBC操作数据库的缺点 存在大量的冗余代码。手工创建 Connection、Statement 等&#xff0c;效率低下。手工将结果集封装成实体对象。查询效率低&#xff0c;没有对数据访问进行优化。 Mybatis框架 简介 MyBatis 本是 apache 的一个开源项目 iBatis, 2010年这个项目由…

imx6ull/linux应用编程学习(14) MQTT基础知识

什么是mqtt&#xff1f; 与HTTP 协议一样&#xff0c; MQTT 协议也是应用层协议&#xff0c;工作在 TCP/IP 四层模型中的最上层&#xff08;应用层&#xff09;&#xff0c;构建于 TCP/IP协议上。 MQTT 最大优点在于&#xff0c;可以以极少的代码和有限的带宽&#xff0c;为连接…

网络资源模板--Android Studio 外卖点餐App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 原创外卖点餐&#xff1a;基于Android studio 实现外卖(点)订餐系统 非原创奶茶点餐&#xff1a;网络资源模板--基于 Android Studio 实现的奶茶点餐App报告 一、项目演示 网络资源模板--基于Android …

在AvaotaA1全志T527开发板上使用AvaotaOS 部署 Docker 服务

Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中&#xff0c;然后发布到任何流行的 Linux或Windows操作系统的机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口。 准备…

信息技术课上的纪律秘诀:营造有序学习环境

信息技术课是学生们探索数字世界的乐园&#xff0c;但同时也是课堂纪律管理的挑战场。电脑、网络、游戏等元素可能分散学生的注意力&#xff0c;影响学习效果。本文将分享一些有效的策略&#xff0c;帮助教师在信息技术课上维持课堂纪律&#xff0c;确保教学活动顺利进行。 制…

几何建模基础-样条曲线和样条曲面介绍

1.概念介绍 1.1 样条曲线的来源 样条的英语单词spline来源于可变形的样条工具&#xff0c;那是一种在造船和工程制图时用来画出光滑形状的工具&#xff1a;富有弹性的均匀细木条/金属条/有机玻璃条&#xff0c;它围绕着按指定位置放置的重物或者压铁做弹性弯曲&#xff0c;以…

基于LangChain的RAG开发教程(二)

v1.0官方文档&#xff1a;https://python.langchain.com/v0.1/docs/get_started/introduction/ 最新文档&#xff1a;https://python.langchain.com/v0.2/docs/introduction/ LangChain是一个能够利用大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;能…

植物大战僵尸融合嫁接版 MAC 版本下载安装详细教程

继植物大战僵尸杂交版火了之后&#xff0c;PVZ改版可谓是百花齐放&#xff0c;最近又有一个非常好玩的模式被开发出来了&#xff0c;他们称为《植物大战僵尸融合嫁接版》 该版本并没有对植物卡牌做改动&#xff0c;而是可以将任意两种植物叠放到一起进行融合&#xff0c;产生新…