第P1周:手写数字识别

一、前期准备

1.设置GPU

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision

device = torch.device("cuda" if torch.cuda.is_available() else "cpu" )

device

输出
device(type=‘cuda’)

2.导入数据

torchvision.datasets 是 PyTorch 的一个库,它提供了许多常用的数据集和相应的数据加载器。这些数据集包括用于图像识别、分割、检测等任务的数据集,如 CIFAR、ImageNet、COCO 等。这些数据集通常都有预定义的接口,使得加载数据变得非常简单。
torchvision.datasets 通常用于以下两种场景:

  1. 加载数据集:通过指定数据集的名称和相关的参数,你可以轻松地加载所需的数据集。
  2. 下载和加载数据集:如果数据集尚未下载,torchvision.datasets 还可以自动下载并加载数据集。 以下是一些常用的 torchvision.datasets 函数和参数:
  3. Dataset:这是所有数据集的基类,它提供了通用的接口和功能。其他数据集都是继承自这个类。
  4. ImageFolder:这是一个通用的数据加载器,用于加载以文件夹形式组织的图像数据集。每个文件夹代表一个类别,文件夹内的所有图像都属于该类别。
    • root:数据集的根目录。
    • transform:对图像进行变换的函数。
    • target_transform:对目标进行变换的函数。
  5. CIFAR10:用于加载 CIFAR-10 数据集。
    • root:数据集的根目录。
    • train:是否加载训练集或测试集。
    • download:如果为 True,则自动下载数据集。
    • transform:对图像进行变换的函数。
  6. MNIST:用于加载 MNIST 数据集。
    • root:数据集的根目录。
    • train:是否加载训练集或测试集。
    • download:如果为 True,则自动下载数据集。
    • transform:对图像进行变换的函数。
  7. ImageNet:用于加载 ImageNet 数据集。
    • root:数据集的根目录。
    • split:指定数据集的分割(‘train’、‘val’、‘test’)。
    • transform:对图像进行变换的函数。
    • target_transform:对目标进行变换的函数。
train_ds = torchvision.datasets.MNIST('data',
                                       train = True,
                                       transform=torchvision.transforms.ToTensor(),
                                       download = True)
test_ds = torchvision.datasets.MNIST('data',
                                       train = False,
                                       transform=torchvision.transforms.ToTensor(),
                                       download = True)

torch.utils.data.DataLoader 是 PyTorch 提供的一个数据加载器,它允许我们在训练模型时以批处理的形式加载数据。它通常与 torch.utils.data.Dataset 结合使用,后者是一个表示数据集的抽象类。DataLoader 负责在数据集上提供迭代功能,并支持多线程数据加载、批量处理、采样和混洗等操作。
以下是 DataLoader 的一些主要参数及其用法:

  1. dataset:这是 DataLoader 最重要的参数,它指定了要加载的数据集。这个数据集必须是 torch.utils.data.Dataset 的一个子类实例。
  2. batch_size:每个批处理包含的样本数量。默认值为 1。
  3. shuffle:如果设置为 True,那么在每个 epoch 开始时,DataLoader 会打乱数据集的数据。这对于随机梯度下降(SGD)等优化算法是有益的,因为它可以减少模型的过拟合。
  4. sampler:定义了从数据集中采样的策略。如果指定了 sampler,那么 shuffle 必须设置为 False
  5. batch_sampler:与 sampler 类似,但是它返回的是一批索引。如果指定了 batch_sampler,那么 batch_sizeshufflesamplerdrop_last 参数都会被忽略。
  6. num_workers:指定用于加载数据的工作进程数。默认值为 0,这意味着数据将在主进程中加载。如果设置为大于 0 的值,则使用多进程数据加载,这可以显著提高数据加载速度,尤其是在数据读取和处理较慢时。
  7. pin_memory:如果设置为 True,那么在数据传输到 GPU 时,数据加载器会使用固定的内存(页锁定内存)。这通常可以提高数据传输到 GPU 的速度。
  8. drop_last:如果设置为 True,那么当数据集的大小不能被 batch_size 整除时,最后一个不完整的批处理会被丢弃。默认值为 False
  9. timeout:如果为正数,那么在从工作进程中收集批次时,如果超过这个时间(秒),则工作进程会被杀死。默认值为 0,表示无限期等待。
  10. worker_init_fn:如果指定,它将在每个工作进程开始时调用,并接收工作进程的 ID 作为参数。这可以用于进行一些工作进程特定的初始化操作。
  11. prefetch_factor:每个工作进程在加载下一个批次之前预取的数据批次数量。默认值为 2。
  12. persistent_workers:如果设置为 True,那么数据加载器会保留工作进程在迭代结束后不被关闭,这可以提高性能,尤其是在多个 epoch 的情况下。
batch_size = 32

train_dl = torch.utils.data.DataLoader(train_ds,
                                       batch_size = batch_size,
                                       shuffle = True)
test_dl = torch.utils.data.DataLoader(test_ds,
                                       shuffle = True)

在 PyTorch 中,shape 函数通常指的是调用张量的 .shape 属性或
torch.Tensor.shape,它返回一个元组(tuple),表示张量(tensor)的尺寸。这个元组中的每个元素代表张量在不同维度上的大小。例如,一个图像张量可能有三个维度,分别代表颜色通道、高度和宽度。
假设我们有一个图像张量,其形状为 (3, 256, 256),这四个数(在这个例子中是三个数)的含义如下:

  1. 第一个数字(3):这通常代表颜色通道的数量。在 RGB 图像中,这个数字是 3,分别代表红色、绿色和蓝色通道。
  2. 第二个数字(256):这代表图像的高度,以像素为单位。
  3. 第三个数字(256):这代表图像的宽度,以像素为单位。

对于具有四个维度的张量,例如在批量处理图像时,其形状可能类似于 (batch_size, channels, height, width),其中:

  1. 第一个数字(batch_size):这代表批量中的样本数量。
  2. 第二个数字(channels):这代表颜色通道的数量,例如 RGB 图像的 3。
  3. 第三个数字(height):这代表图像的高度,以像素为单位。
  4. 第四个数字(width):这代表图像的宽度,以像素为单位。

对于其他类型的张量,维度的含义可能会有所不同,取决于数据的上下文和应用场景。例如,一个包含单词嵌入的批量可能具有形状
(batch_size, sequence_length, embedding_size),其中:

  1. batch_size:批量中样本的数量。
  2. sequence_length:每个样本中的序列长度(例如,句子中的单词数)。
  3. embedding_size:每个单词的嵌入维度大小。
imgs,labels = next(iter(train_dl))
imgs.shape

torch.Size([32, 1, 28, 28])
这里的四个数分别是batch_size,channel,height,width

3.数据可视化

在 PyTorch 中,enumerate 函数通常用于在遍历数据集的批次时同时获取批次索引和数据。enumerate 是 Python 的内置函数,它允许我们遍历一个序列(如列表、元组或字符串)并返回每个元素的索引和值。
在 PyTorch 的上下文中,当我们使用 DataLoader 迭代数据集时,enumerate 函数通常与 for 循环一起使用,以便在训练循环中跟踪 epoch 的编号,enumerate(data_loader) 会返回每个批次的索引 i 和数据对 (inputs, targets)。这样,我们可以在训练循环中使用 i来跟踪当前是第几个批次,这对于打印训练进度、记录日志或在特定批次时执行某些操作(如调整学习率)非常有用。 enumerate函数还可以接受一个 start 参数来指定索引的起始值,例如 enumerate(data_loader,start=1),这将使得批次的索引从 1 开始,而不是默认的 0。

np.squeeze() 是 NumPy 库中的一个函数,它的作用是从数组的形状中移除单维的条目,即维度大小为 1 的轴。这个函数的目的是为了减少数组的维度,使得数组的形状更加紧凑,便于进行某些操作或计算。
为什么要使用 np.squeeze()

  1. 数据形状适配:在处理图像数据时,我们通常希望图像的形状是 (height, width, channels)(batch_size, height, width, channels)。但是,有时候由于数据的来源或处理方式,图像数据的形状可能会包含不必要的单维轴,例如 (1, height, width, 1)。在这种情况下,使用 np.squeeze() 可以将数据形状变为 (height, width)
    (height, width, channels),这样就可以直接用于图像显示或其他图像处理函数。
  2. 简化计算:在机器学习模型中,某些操作(如卷积、池化等)要求输入数据的维度是固定的。如果一个数组有多余的单维轴,那么在进行这些操作之前,可能需要使用 np.squeeze() 来简化数组的形状。
  3. 避免广播错误:在 NumPy 中,当进行数组运算时,如果数组的形状不匹配,NumPy 会尝试通过广播机制来扩展数组。如果一个数组有不必要的单维轴,可能会导致意外的广播行为。使用 np.squeeze() 可以避免这种错误。
  4. 提高代码可读性:移除不必要的单维轴可以使数组的形状更加清晰,提高代码的可读性和可维护性。
import numpy as np

plt.figure(figsize=(20, 5)) 
for i, imgs in enumerate(imgs[:20]):  # 提取前20个元素
   
    npimg = np.squeeze(imgs.numpy())
  
    plt.subplot(2, 10, i+1)
    plt.imshow(npimg, cmap=plt.cm.binary)
    plt.axis('off')    

输出
在这里插入图片描述

二、构建简单的CNN网络

解释一下模型传递过程中的参数变化
这段代码定义了一个简单的卷积神经网络模型,用于图像分类。

  1. self.conv1 = nn.Conv2d(1, 32, kernel_size=3):
    • 这一层定义了一个二维卷积层,它接受一个通道(例如灰度图)的输入,并输出 32 个特征图。卷积核的大小是 3x3。每个卷积核的权重是 3x3 = 9个参数,加上一个偏置项,共 10个参数。因此,总共是 32x10 = 320个参数。
  2. self.pool1 = nn.MaxPool2d(2):
    • 这一层定义了一个最大池化层,池化窗口大小为 2x2。池化层不引入任何参数,它只是对输入的特征图进行下采样。
  3. self.conv2 = nn.Conv2d(32, 64, kernel_size=3):
    • 这一层定义了第二个二维卷积层,它接受 32 个通道的输入,并输出 64 个特征图。卷积核的大小是 3x3。每个卷积核的权重是 32x3x3 = 288个参数,加上一个偏置项,共 289个参数。因此,总共是 64x289 = 18,496个参数。
  4. self.pool2 = nn.MaxPool2d(2):
    • 这是第二个最大池化层,同样使用 2x2 的池化窗口。池化层不引入任何参数。
  5. self.fcl = nn.Linear(1600, 64):
    • 这一层定义了一个全连接层,它接受 1600 维的输入,并输出 64 维的输出。这里的 1600 是通过计算前两层卷积和池化后的特征图尺寸得到的。对于 28x28 的输入图像,经过两次卷积和池化后,特征图的尺寸应该是5x5(假设没有使用填充)。因此,这里的 1600 应该是 5x5x64 = 1600。这层引入的参数包括权重和偏置,总共是 1600x64 + 64 = 102,464个参数。
  6. self.fc2 = nn.Linear(64, num_classes):
    • 这是第二个全连接层,它接受 64 维的输入,并输出 num_classes 维的输出(在这个例子中是 10,因为有 10 个类别)。这层引入的参数包括权重和偏置,总共是 64x10 + 10 = 650个参数。 在 forward 方法中,输入 x
      通过卷积层和池化层进行前向传播,然后被展平(flatten)成一个一维向量,再通过两个全连接层,最后得到每个类别的分数。在
      forward 方法中,F.relu 应用 ReLU 激活函数,它在每个卷积层和全连接层之后使用,以引入非线性。

为了解释特征图尺寸从 28x28 变为 5x5 的过程,我们需要考虑卷积层和池化层对输入图像尺寸的影响。以下是基于假设的卷积层和池化层的参数,对特征图尺寸变化的计算:

  1. 第一次卷积 (conv1): 假设输入图像大小为 28x28,使用 3x3 的卷积核,无填充(padding)和步幅(stride)为 1。这将导致特征图的尺寸减少。每个卷积核覆盖图像的一个 3x3
    的区域,因此输出特征图的尺寸将是输入尺寸减去卷积核大小加一。对于 28x28 的输入和 3x3 的卷积核,输出尺寸将是 26x26。
  2. 第一次池化 (pool1): 假设使用 2x2 的最大池化,步幅为 2。池化会减少特征图的尺寸,每个 2x2 的区域被减少到一个像素。因此,26x26 的特征图在池化后会变成 13x13。
  3. 第二次卷积 (conv2): 假设第二次卷积仍然使用 3x3 的卷积核,无填充和步幅为 1。对于 13x13 的输入,输出尺寸将是 11x11。
  4. 第二次池化 (pool2): 再次使用 2x2 的最大池化,步幅为 2。11x11 的特征图在池化后会变成 5.5x5.5。但是,特征图的尺寸必须是整数,所以我们需要向下取整,得到 5x5
import torch.nn.functional as F

num_classes = 10


class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32,kernel_size = 3)
        self.pool1 = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(32,64,kernel_size = 3)
        self.pool2 = nn.MaxPool2d(2)
        #分类网络
        self.fcl = nn.Linear(1600,64)
        self.fc2 = nn.Linear(64, num_classes)
        #前向传播
        
    def forward(self,x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))
        
        x = torch.flatten(x, start_dim = 1)
        
        x = F.relu(self.fcl(x))
        x = self.fc2(x)
        
        return x
from torchinfo import summary
# 将模型转移到GPU中(我们模型运行均在GPU中进行)
model = Model().to(device)

summary(model)

输出
在这里插入图片描述

三、模型训练

1.设置超参数

loss_fn    = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-2 # 学习率
opt        = torch.optim.SGD(model.parameters(),lr=learn_rate)

2.编写训练函数

这段代码是一个简单的 PyTorch 训练循环,用于训练一个神经网络模型。

  1. def train(dataloader, model, loss_fn, optimizer)::
    • 定义了一个函数 train,它接受四个参数:dataloader(数据加载器,用于迭代数据集的批次)、model(神经网络模型)、loss_fn(损失函数,用于计算预测值和真实值之间的差异)和 optimizer(优化器,用于更新模型参数)。
  2. size = len(dataloader.dataset):
    • 计算训练集的大小,即数据加载器中数据集的长度。在这个例子中,假设数据加载器是从一个包含 60000 张图片的数据集构建的。
  3. num_batches = len(dataloader):
    • 计算批次数目,即数据加载器中的批次数量。这通常是通过将数据集的大小除以批大小来计算的。在这个例子中,假设批大小为 32,所以批次数目是 60000/32 = 1875。
  4. train_loss, train_acc = 0, 0:
    • 初始化训练损失和正确率,设置为 0。这两个变量用于在整个训练过程中记录总的损失和正确率。
  5. for X, y in dataloader::
    • 开始一个 for 循环,用于遍历数据加载器的所有批次。X 包含当前批次的输入数据,而 y 包含当前批次的标签。
  6. X, y = X.to(device), y.to(device):
    • 将当前批次的输入数据 X 和标签 y 移动到指定的设备上。device 通常是 ‘cuda’ 或 ‘cpu’,这取决于你的硬件配置。
  7. pred = model(X):
    • 计算模型的预测输出。model 是之前定义的神经网络模型,它接受输入 X 并输出预测 pred
  8. loss = loss_fn(pred, y):
    • 计算预测值 pred 和真实值 y 之间的差异,得到损失 lossloss_fn 是之前定义的损失函数,它接受预测和真实值作为输入,并返回损失值。
  9. optimizer.zero_grad():
    • 清除优化器中所有参数的梯度。这是为了确保在反向传播过程中,梯度不会累积,从而影响模型的训练。
  10. loss.backward():
    • 进行反向传播,计算损失 loss 关于模型参数的梯度。这个梯度会被存储在优化器中,以便在下一步更新模型参数时使用。
  11. optimizer.step():
    • 根据当前的梯度信息更新模型参数。优化器会根据每个参数的梯度和学习率来计算每个参数的新值,并更新模型参数。
  12. train_acc += (pred.argmax(1) == y).type(torch.float).sum().item():
    ● pred.argmax(1) 返回数组 pred 在第一个轴(即行)上最大值所在的索引。这通常用于多类分类问题中,其中 pred 是一个包含预测概率的二维数组,每行表示一个样本的预测概率分布。
    ● (pred.argmax(1) == y)是一个布尔值,其中等号是否成立代表对应样本的预测是否正确(True 表示正确,False 表示错误)。
    ● .type(torch.float)是将布尔数组的数据类型转换为浮点数类型,即将 True 转换为 1.0,将 False 转换为 0.0。
    ● .sum()是对数组中的元素求和,计算出预测正确的样本数量。
    ● .item()将求和结果转换为标量值,以便在 Python 中使用或打印。
    (pred.argmax(1) == y).type(torch.float).sum().item()表示计算预测正确的样本数量,并将其作为一个标量值返回。这通常用于评估分类模型的准确率或计算分类问题的正确预测数量。
  13. train_loss += loss.item():
    • 累加当前批次的损失到总损失 train_loss 中。
  14. train_acc /= size:
    • 计算平均准确率。将 train_acc 除以训练集的大小 size,得到整个训练过程中的平均准确率。
  15. train_loss /= num_batches:
    • 计算平均损失。将 train_loss 除以批次数目 num_batches,得到整个训练过程中的平均损失。
  16. return train_acc, train_loss:
    • 返回平均准确率和平均损失。这两个值可以用于评估模型的训练效果。
  1. optimizer.zero_grad()函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上一次的梯度记录被清空。

  2. loss.backward() PyTorch的反向传播(即tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。
    具体来说,torch.tensor是autograd包的基础类,如果你设置tensor的requires_grads为True,就会开始跟踪这个tensor上面的所有运算,如果你做完运算后使用tensor.backward(),所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。
    更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。
    如果没有进行tensor.backward()的话,梯度值将会是None,因此loss.backward()要写在optimizer.step()之前。

  3. optimizer.step() step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。

注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。

# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小,一共60000张图片
    num_batches = len(dataloader)   # 批次数目,1875(60000/32)

    train_loss, train_acc = 0, 0  # 初始化训练损失和正确率
    
    for X, y in dataloader:  # 获取图片及其标签
        X, y = X.to(device), y.to(device)
        
        # 计算预测误差
        pred = model(X)          # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
        
        # 反向传播
        optimizer.zero_grad()  # grad属性归零
        loss.backward()        # 反向传播
        optimizer.step()       # 每一步自动更新
        
        # 记录acc与loss
        train_acc  += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()
            
    train_acc  /= size
    train_loss /= num_batches

    return train_acc, train_loss

3.编写测试函数

这段代码使用了 PyTorch 的 with torch.no_grad()
上下文管理器,用于在不需要计算梯度时执行代码。通常,这发生在一些不需要梯度信息的操作中,例如在验证或测试阶段,或者在某些预处理步骤中。

  1. with torch.no_grad()::
    • 这里使用了一个上下文管理器 with torch.no_grad(),它会在代码块执行期间禁用梯度计算。这意味着在这个代码块中进行的任何操作都不会更新模型参数,因为梯度不会被计算。
  2. for imgs, target in dataloader::
    • 开始一个 for 循环,遍历数据加载器中的每个批次。dataloader 是之前定义的数据加载器,它负责从数据集中加载批次数据。
  3. imgs, target = imgs.to(device), target.to(device):
    • 将当前批次的输入图像 imgs 和标签 target 移动到指定的设备上。device 通常是 ‘cuda’ 或 ‘cpu’,这取决于你的硬件配置。 在这个上下文管理器中,imgs.to(device)target.to(device)操作不会计算梯度,因为它们在 with torch.no_grad() 上下文管理器内部。这通常用于在不需要梯度信息的操作中,例如在验证或测试阶段,或者在某些预处理步骤中。
def test (dataloader, model, loss_fn):
    size        = len(dataloader.dataset)  # 测试集的大小,一共10000张图片
    num_batches = len(dataloader)          # 批次数目,313(10000/32=312.5,向上取整)
    ‘’
‘’ ‘’ ‘’  test_loss, test_acc = 0, 0
    
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
            
            # 计算loss
            target_pred = model(imgs)
            loss        = loss_fn(target_pred, target)
            
            test_loss += loss.item()
            test_acc  += (target_pred.argmax(1) == target).type(torch.float).sum().item()

    test_acc  /= size
    test_loss /= num_batches

    return test_acc, test_loss

4.正式训练

这段代码是一个简单的 PyTorch 训练循环,用于训练和评估一个神经网络模型。

  1. for epoch in range(epochs)::
    • 开始一个 for 循环,遍历预定的 epochs(通常表示训练周期或迭代次数)。epochs 是一个整数,表示训练过程中要进行的完整迭代次数。
  2. model.train():
    • 将模型设置为训练模式。在训练模式下,模型会使用正确的激活函数和损失函数,以及任何其他训练相关的设置。
  3. epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt):
    • 调用 train 函数,使用训练数据加载器 train_dl、模型 model、损失函数 loss_fn 和优化器 opt 来计算当前 epoch 的训练准确率和损失。train_dl是之前定义的训练数据加载器,它负责从训练数据集中加载批次数据。opt 是之前定义的优化器,用于更新模型参数。
  4. model.eval():
    • 将模型设置为评估模式。在评估模式下,模型会使用正确的激活函数和损失函数,但不进行反向传播和参数更新。
  5. epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn):
    • 调用 test 函数,使用测试数据加载器 test_dl、模型 model 和损失函数 loss_fn 来计算当前 epoch 的测试准确率和损失。test_dl 是之前定义的测试数据加载器,它负责从测试数据集中加载批次数据。
  6. train_acc.append(epoch_train_acc):
    • 将当前 epoch 的训练准确率 epoch_train_acc 追加到 train_acc 列表中。train_acc 是一个列表,用于存储每个 epoch 的训练准确率。
  7. train_loss.append(epoch_train_loss):
    • 将当前 epoch 的训练损失 epoch_train_loss 追加到 train_loss 列表中。train_loss 是一个列表,用于存储每个 epoch 的训练损失。
  8. test_acc.append(epoch_test_acc):
    • 将当前 epoch 的测试准确率 epoch_test_acc 追加到 test_acc 列表中。test_acc 是一个列表,用于存储每个 epoch 的测试准确率。
  9. test_loss.append(epoch_test_loss):
    • 将当前 epoch 的测试损失 epoch_test_loss 追加到 test_loss 列表中。test_loss 是一个列表,用于存储每个 epoch 的测试损失。
  10. template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}'):
    • 定义了一个字符串模板,用于格式化打印信息。{:2d} 表示整数,占位符宽度为 2,不足部分用空格填充;{:.1f}% 表示浮点数,保留一位小数,并转换为百分比形式;{:.3f} 表示浮点数,保留三位小数。
  11. print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss)):
    • 使用定义的模板格式化并打印当前 epoch 的训练和测试准确率以及损失。epoch+1 表示当前 epoch 的编号,epoch_train_acc*100epoch_test_acc*100分别将准确率转换为百分比形式,epoch_train_lossepoch_test_loss 保持原样。
  12. print('Done'):
    • 打印字符串 ‘Done’,表示训练过程已经完成。 这个循环会重复执行,直到达到预定的 epochs 次数。每个 epoch 都会执行一次训练和一次测试,并将结果存储在列表中。最后,打印出每个 epoch 的训练和测试准确率以及损失,以及训练过程完成的提示。
epochs     = 5
train_loss = []
train_acc  = []
test_loss  = []
test_acc   = []

for epoch in range(epochs):
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)
    
    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
    
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%,Test_loss:{:.3f}')
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')

输出
Epoch: 1, Train_acc:59.8%, Train_loss:1.585, Test_acc:86.4%,Test_loss:0.491
Epoch: 2, Train_acc:88.3%, Train_loss:0.396,
Test_acc:91.1%,Test_loss:0.303 Epoch: 3, Train_acc:91.5%,
Train_loss:0.286, Test_acc:93.2%,Test_loss:0.233 Epoch: 4,
Train_acc:93.4%, Train_loss:0.223, Test_acc:94.7%,Test_loss:0.183
Epoch: 5, Train_acc:94.6%, Train_loss:0.180,
Test_acc:95.5%,Test_loss:0.149 Epoch: 6, Train_acc:95.6%,
Train_loss:0.150, Test_acc:96.3%,Test_loss:0.128 Epoch: 7,
Train_acc:96.2%, Train_loss:0.130, Test_acc:96.5%,Test_loss:0.117
Epoch: 8, Train_acc:96.6%, Train_loss:0.114,
Test_acc:97.2%,Test_loss:0.095 Epoch: 9, Train_acc:97.0%,
Train_loss:0.103, Test_acc:97.3%,Test_loss:0.090 Epoch:10,
Train_acc:97.2%, Train_loss:0.095, Test_acc:97.6%,Test_loss:0.083 Done

四、结果可视化

import matplotlib.pyplot as plt
#隐藏警告
import warnings
warnings.filterwarnings("ignore")               #忽略警告信息
plt.rcParams['font.sans-serif']    = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False      # 用来正常显示负号
plt.rcParams['figure.dpi']         = 100        #分辨率

epochs_range = range(epochs)

plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)

plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在这里插入图片描述

五、神经网络构架解构

在神经网络构建中,网络层是实现特定功能的关键组成部分。以下是对核心层、嵌入层、卷积层、池化层、局部连接层、循环层、融合层、高级激活层、噪声层、标准化层等常见网络层的详细解释:

  1. 核心层(Core Layer):
    核心层通常指的是神经网络中的隐藏层,它们位于输入层和输出层之间。核心层可以包含多个隐藏层,每个隐藏层由一定数量的神经元组成。核心层的作用是从输入数据中学习特征,并将这些特征传递到下一层。
  2. 嵌入层(Embedding Layer):
    嵌入层通常用于处理离散型输入数据,如文本或类别特征。嵌入层将这些离散型数据映射到连续的数值空间,使得神经网络可以更好地学习这些特征。嵌入层通常用于自然语言处理任务中的词向量表示。
  3. 卷积层(Convolutional Layer):
    卷积层主要用于处理图像数据,它通过卷积操作提取图像的局部特征。卷积层中的神经元只与输入数据的一部分连接,这样可以减少参数的数量,同时保持对图像局部结构的敏感性。卷积层在图像识别和计算机视觉任务中非常重要。
  4. 池化层(Pooling Layer):
    池化层用于减小数据的维度,同时保留重要的特征信息。它通过对输入数据进行下采样操作,例如最大池化或平均池化,来降低数据的空间分辨率。池化层可以减少过拟合的风险,提高模型的泛化能力。
  5. 局部连接层(Locally Connected Layer):
    局部连接层与卷积层类似,但它为输入数据的每个局部区域分别学习一组独立的过滤器。这意味着每个局部区域都有自己的权重和偏置参数,从而增加了模型的复杂度和参数数量。局部连接层在某些特定的任务中可能有用,但通常不如卷积层常用。
  6. 循环层(Recurrent Layer):
    循环层用于处理序列数据,如时间序列数据或文本数据。循环层中的神经元不仅与输入数据连接,还与前一时刻的自身状态连接。这种循环结构使得循环层能够捕捉序列中的时间依赖关系,常用于自然语言处理和语音识别任务。
  7. 融合层(Fusion Layer):
    融合层用于将来自不同来源或不同类型的数据进行整合。例如,在多模态学习任务中,融合层可以将图像和文本数据融合在一起,以便更好地学习跨模态的特征表示。
  8. 高级激活层(Advanced Activation Layer):
    高级激活层用于引入非线性变换,以提高神经网络的表示能力。常见的激活函数包括ReLU、Sigmoid和Tanh等。高级激活层可以堆叠在核心层或卷积层之后,以增加模型的非线性。
  9. 噪声层(Noise Layer):
    噪声层用于向输入数据添加噪声,以提高模型的鲁棒性和泛化能力。常见的噪声层包括Dropout层和Drop Connect层等。噪声层通过随机丢弃一部分神经元或连接,减少模型的过拟合风险。
  10. 标准化层(Normalization Layer):
    标准化层用于对输入数据进行归一化处理,以加速神经网络的训练过程。常见的标准化方法包括批量归一化(Batch Normalization)和层归一化(Layer Normalization)等。标准化层有助于稳定梯度下降过程,提高模型的收敛速度。
    这些网络层可以根据具体任务和需求进行组合和调整,以构建适合特定问题的神经网络模型。
  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值