LeNet与AlexNet实战

eve2333 发布于 2024-10-25 108 次阅读


搭建神经网络模型的过程可以总结为以下步骤:

  1. 初始化网络层与参数:在模型的初始化阶段,需要定义各种网络层(如卷积层、全连接层)以及所需的参数。这相当于为搭建网络准备基础的组件,如砖头、水泥等资源。
  2. 前向传播过程:定义前向传播函数,用于将输入数据通过各个网络层逐步进行计算,从而生成输出。每一层的输出作为下一层的输入,直到最终获得模型的输出。该过程通过调用已初始化的层来实现各层间的连接。
  3. 激活函数与其他操作:在前向传播的过程中,通常需要使用激活函数(如 ReLU)对中间结果进行非线性变换,提升模型的表达能力。此外,可能还会使用池化层等操作来缩小特征图的尺寸,从而减少计算量并提取更有意义的特征。
  4. 反向传播与训练:模型搭建完成后,配合训练过程的反向传播算法,通过计算梯度和更新参数来优化模型。训练过程通常在独立的训练代码中完成,而前向传播则是网络模型的一部分。

这个流程类似于搭建一座建筑物,先准备好各类材料和工具(即初始化网络层和参数),然后根据设计逐步构建,直到模型能够通过输入生成有效输出。

初始化

新建一个LeNet文件夹,其中model.py如下

import torch
from torch import nn
from torchsummary import summary

class LeNet(nn. Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.c1=nn. Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
        self.sig=nn.Sigmoid()
        self.s2 =nn. AvgPool2d(kernel_size=2, stride=2)
        self.c3 =nn. Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.s4=nn.AvgPool2d(kernel_size=2, stride=2)

        self. flatten =nn. Flatten()
        self. f5 =nn. Linear( 400,  120)
        self. f6=nn. Linear( 120,84)
        self. f7=nn. Linear(84,10)

在构建神经网络时,首先需要定义网络的各层及其参数。在初始化阶段,我们根据 LeNet 的结构定义卷积层、激活函数、池化层、展平层以及全连接层:

  • 卷积层 (Convolutional Layer):通过 nn.Conv2d 定义卷积层,指定输入通道、输出通道、卷积核的大小,以及填充(padding)等参数。LeNet 中的第一层卷积接收灰度图(通道数为 1),输出 6 个特征图,卷积核大小为 5x5,使用 padding 为 2。
  • 激活函数 (Activation Function):使用 nn.Sigmoid() 来增加网络的非线性表示能力。
  • 池化层 (Pooling Layer):通过 nn.AvgPool2d 定义池化层,用于下采样特征图,减少计算量。池化核大小为 2x2,步幅为 2。
  • 全连接层 (Fully Connected Layer):通过 nn.Linear 定义全连接层,用于将卷积层的输出映射到具体的类别标签。LeNet 中有三层全连接层,第一层输入 400 个特征,输出 120 个神经元,接着是 84 个神经元,最后输出为 10 类。

前向传播

import torch
from torch import nn
from torchsummary import summary

class LeNet(nn. Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.c1=nn. Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
        self.sig=nn.Sigmoid()
        self.s2 =nn. AvgPool2d(kernel_size=2, stride=2)
        self.c3 =nn. Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.s4=nn.AvgPool2d(kernel_size=2, stride=2)

        self. flatten =nn. Flatten()
        self. f5 =nn. Linear( 400,  120)
        self. f6=nn. Linear( 120,84)
        self. f7=nn. Linear(84,10)
    def forward(self,x):
    x = self.sig(self.c1(x))   # Pass input through the first convolution layer (c1), followed by a sigmoid activation function (sig).
    x = self.s2(x)             # Pass the result through the first pooling layer (s2).
    x = self.sig(self.c3(x))   # Pass the output through the second convolution layer (c3) and apply sigmoid activation again.
    x = self.s4(x)             # Pass the result through the second pooling layer (s4).
    x = self.flatten(x)        # Flatten the output to prepare it for fully connected layers.
    x = self.f5(x)             # Pass the flattened output through the first fully connected layer (f5).
    x = self.f6(x)             # Pass through the second fully connected layer (f6).
    x = self.f7(x)             # Pass through the final fully connected layer (f7) to produce the output.
    return x                   # Return the final result as the output of the forward pass.

接下来,我们已经确定了模型的架构,并且初始化了所有必要的信息。现在,我们需要利用这些初始化好的信息来搭建神经网络的前向传播过程。一旦我们将数据输入到这个过程中,我们的模型结构就完整了。接下来,我们需要定义一个名为forward的函数,这个函数将负责整个前向传播的过程。

当你在类中定义一个函数时,比如我们的forward函数,Python会自动遵循一定的语法规则,这些规则是我们在学习过程中需要掌握的。即使你没有Python基础,只要跟着我的步骤来,也能成功构建出神经网络。我会详细解释每一步,因为我的课程是面向初学者的,目的是确保大家都能理解并学到东西。有时候可能会显得有些啰嗦,但这是必要的,以确保每个人都能跟上。

在这个过程中,我们首先定义输入接口X。现在,我们已经拥有了所有的神经网络层,我们需要搭建网络模型,模型需要输入X并得出我们的输出Y。在这个过程中,我们可能会定义一个X2作为输入接口。我们把X输入到第一层,也就是我们的卷积层c1。然后,数据会通过卷积层,接着通过激活函数,再通过池化层,然后再次通过卷积层。这里的X会再次输入到我们的激活函数中,然后输出到池化层,最后通过全连接层。

在这个过程中,我们可以将每一层的输出命名为A1A2等,这只是我们定义的一个名字,你也可以用A来表示。在搭建网络模型时,每一层的输出都会作为下一层的输入。例如,卷积层的输出会再次输入到激活函数中,然后通过池化层,最后通过全连接层。这个过程非常流畅,每一层的操作都是有序的。

最后,我们通过平展层将数据展平,然后输入到全连接层。在这个过程中,我们可能会有多个全连接层,比如f5f6f7。最终,我们得到输出Y,这是我们的前向传播结果,也是我们返回的值。这样,我们的模型就搭建好了,输入X应该得出我们的Y,这个Y是我们的返回值,也就是神经网络的最终结果。

所以,你会发现神经网络的定义和我们之前讲的原理是相扣的。我们之前花了很多时间去讲解神经网络的输入特征图和输出特征图,这是为了让大家更清晰地理解前向传播的过程和具体的参数。在搭建神经网络时,这个过程是非常清晰的。无论是什么类型的神经网络,基本上都是这样的流程,只是可能会更复杂一些。所以,我们只需要定义好前向传播,然后按照这个逻辑一层一层地传递数据,直到最后一层,然后返回神经网络的前向传播结果。

这个过程就是我们所说的前向传播,它是神经网络在给定输入数据时进行预测或推断的步骤。在这个过程中,每一层的输出都成为下一层的输入,直到最后一层产生最终的输出结果。这个结果可以是分类标签、回归值或其他类型的预测输出,具体取决于网络的设计和任务目标。

通过这种方式,LeNet类定义了一个完整的前向传播流程,使得我们可以将输入数据通过网络结构,最终得到预测结果。这个流程是构建任何神经网络模型的基础,无论是用于图像识别、自然语言处理还是其他机器学习任务。

import torch
from torch import nn
from torchsummary import summary

class LeNet(nn. Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.c1=nn. Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
        self.sig=nn.Sigmoid()
        self.s2 =nn. AvgPool2d(kernel_size=2, stride=2)
        self.c3 =nn. Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.s4=nn.AvgPool2d(kernel_size=2, stride=2)

        self. flatten =nn. Flatten()
        self. f5 =nn. Linear( 400,  120)
        self. f6=nn. Linear( 120,84)
        self. f7=nn. Linear(84,10)
    def forward(self,x):
        x = self.sig(self.c1(x))
        x = self.s2(x)
        x=self.sig(self.c3(x))
        x=self.s4(x)
        x=self.flatten(x)
        x=self.f5(x)
        x= self.f6(x)
        x=self.f7(x)
        return x
if __name__=="__main__":
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = LeNet().to(device)
    print(summary(model, (1,28,28)))

数据集

由于手写数字识别已经做烂了,所以使用衣服分类的数据集,新建plot.py

from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt

train_data = FashionMNIST(root='./data',
                          train=True,
                          transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]),
                          download=True)

train_loader = Data.DataLoader(dataset=train_data,
                               batch_size=64,
                               shuffle=True,
                               num_workers=0)

# 获得一个Batch的数据
for step, (b_x, b_y) in enumerate(train_loader):
    if step > 0:
        break
batch_x = b_x.squeeze().numpy()  # 将四维张量移除第1维,并转换成Numpy数组
batch_y = b_y.numpy()  # 将张量转换成Numpy数组
class_Label = train_data.classes  # 训练集的标签
# print(class_label)
print("The size of batch in train data:", batch_x.shape)  # 每个mini-batch的维度是64*224*224

plt.figure(figsize=(12, 5))
for ii in np.arange(len(batch_y)):
    plt.subplot(4, 16, ii + 1)

数据加载函数

model_train.py

from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from model import LeNet


def train_val_data_process():
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
                              download=True)
    train_data, val_data = Data.random_split(train_data, [round(0.8 * len(train_data)), round(0.2 * len(train_data))])
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=128,
                                       shuffle=True,
                                       num_workers=8)

    val_dataloader = Data.DataLoader(dataset=val_data,
                                     batch_size=128,
                                     shuffle=True,
                                     num_workers=8)

    return train_dataloader, val_dataloader

训练模板代码

model_train.py

import time

import torch.optim
from torch import nn
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from model import LeNet


def train_val_data_process():
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
                              download=True)
    train_data, val_data = Data.random_split(train_data, [round(0.8 * len(train_data)), round(0.2 * len(train_data))])
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=128,
                                       shuffle=True,
                                       num_workers=8)

    val_dataloader = Data.DataLoader(dataset=val_data,
                                     batch_size=128,
                                     shuffle=True,
                                     num_workers=8)

    return train_dataloader, val_dataloader


def train_model(model, train_dataloader, val_dataloader, num_epochs):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # 优化器,梯度下降法进一步拓展是SGD,Adam和SGDM等等等等的

    criterion = nn.CrossEntropyLoss()
    # 均方损失,分类中我们一般用交叉熵损失来更新损失值,然后是w,b
    # 将模型放到训练设备中
    model = model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数
    # 最高准确度
    best_acc = 0.0
    # 训练集损失列表
    train_loss_all = []
    # 验证集损失列表
    val_loss_all = []
    # 训练集准确度列表
    train_acc_all = []
    # 验证集准确度列表
    val_acc_all = []
    # 当前时间
    since = time.time()
  1. 参数初始化:我们将 train_loss, train_accuracy, val_loss, val_accuracy 初始化为 0,并在每个 epoch 中累积这些值。
  2. 训练数据加载:每次从 train_loader 中加载一批数据,并将输入 inputs 和标签 labels 放入设备(比如 GPU)。
  3. 梯度清零:为了防止梯度累积,每个 batch 都需要调用 optimizer.zero_grad() 来将梯度重置。
  4. 前向传播:通过模型的前向传播计算输出 outputs
  5. 损失计算:通过定义的损失函数 loss_fn 计算损失。
  6. 反向传播:通过 loss.backward() 计算梯度。
  7. 参数更新:使用优化器 optimizer.step() 进行参数更新。
  8. 计算训练准确度:通过比较预测结果 preds 与真实标签 labels,计算准确预测的数量,并更新累计准确率。

你可以在每个 epoch 后面添加验证的部分和相应的统计。

这样我们已经把一个完整的训练流程框架整理出来,接下来你可以根据需要继续完善验证部分或者添加细节。

1. 模型训练的初始化

我们已经定义了模型加载到合适的设备(如GPU或CPU)、设置了优化器(如Adam)以及损失函数(如交叉熵)。接下来我们进入实际的训练步骤。

2. 训练轮次(Epoch)

首先,我们在训练函数里会有一个 for 循环,循环的次数就是训练的轮次(epochs)。每轮训练的目的是通过反向传播更新模型的参数(WB),以便使损失函数的值逐渐下降,模型的性能不断提高。

3. 批次训练(Mini-batch Training)

对于每轮训练,我们需要将训练数据分成小批次(mini-batches),这样可以节省内存并提高训练速度。常用的方式是用 DataLoader 来迭代批次数据。在每个批次中,我们会:

  • 将数据和标签传入模型。
  • 前向传播:模型计算输出。
  • 计算损失值:利用损失函数计算模型输出和真实标签之间的差异。
  • 反向传播:通过损失值的梯度更新模型参数。
  • 优化器 step():更新模型的权重。

4. 验证集评估

在每个epoch结束时,我们通常会使用验证集来评估模型的性能。验证集不参与模型参数的更新,只用于衡量模型的泛化能力。这里需要关闭梯度计算(torch.no_grad()),以减少内存消耗和加速评估。

5. 保存最佳模型

训练过程中会保存当前最佳模型的参数(即使验证集上的损失最小),用于后续的模型测试或部署。保存的模型通常包括网络结构和权重。

6. 记录训练过程

为了可视化训练进度,我们会保存每个epoch的训练损失、验证损失、训练精度、验证精度等信息,供后续分析和绘图。

总结流程:

  1. 初始化
    • 确定设备、定义优化器和损失函数。
    • 将模型和数据加载到设备上。
  2. 训练循环
    • 对于每个epoch:
      • 对每个mini-batch进行前向传播、计算损失、反向传播、更新参数。
    • 使用验证集评估模型性能。
    • 保存最佳模型。
  3. 记录和输出
    • 保存每轮训练和验证的损失、精度,记录训练时间。

这样,整个训练函数的结构和思路就清晰了,你可以通过实现它来进行模型训练。

 训练反向传播

import time

import torch.optim
from torch import nn
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from model import LeNet


def train_val_data_process():
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
                              download=True)
    train_data, val_data = Data.random_split(train_data, [round(0.8 * len(train_data)), round(0.2 * len(train_data))])
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=128,
                                       shuffle=True,
                                       num_workers=8)

    val_dataloader = Data.DataLoader(dataset=val_data,
                                     batch_size=128,
                                     shuffle=True,
                                     num_workers=8)

    return train_dataloader, val_dataloader


def train_model(model, train_dataloader, val_dataloader, num_epochs):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # 优化器,梯度下降法进一步拓展是SGD,Adam和SGDM等等等等的

    criterion = nn.CrossEntropyLoss()
    # 均方损失,分类中我们一般用交叉熵损失来更新损失值,然后是w,b
    # 将模型放到训练设备中
    model = model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数
    # 最高准确度
    best_acc = 0.0
    # 训练集损失列表
    train_loss_all = []
    # 验证集损失列表
    val_loss_all = []
    # 训练集准确度列表
    train_acc_all = []
    # 验证集准确度列表
    val_acc_all = []
    # 当前时间
    since = time.time()

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch + 1, num_epochs))
        print("-" * 10)

        train_loss = 0.0
        train_correct = 0.0
        val_loss = 0.0
        val_correct = 0.0

        train_num = 0
        val_num = 0

        # 对每一个mini-batch训练和计算
        for step, (b_x, b_y) in enumerate(train_dataloader):
            # 将特征放入到训练设备中
            b_x = b_x.to(device)
            # 将标签放入到训练设备中
            b_y = b_y.to(device)
            # 设置模型为训练榄式
            model.train()
            
            # 前向传播过程,输入为一个batch,输出为一个batch中对应的预测
            output = model(b_x)

首先train_dataloader是训练的所有数据, 加入60000*0.8的数据,其中每个批次有128个意味着,那可能有N个批次的数据一批次一批次就取嘛,直到把这第N个数据取完,假设这里是第一次循环,那么就取第一个批次的数据,如果第二次循环的话,是第二批次的数据,128个向量图,128乘以什么吧,28×28可能还有个通道嘛,这里的什么BX等于这个的,那么很显然BY是什么那BY就是128乘以它的标签嘛,就128个label,放到我们的设备当中这里进行一个训练,进行前向传播,得出这样的一个结果

# 对每一个mini-batch训练和计算
        for step, (b_x, b_y) in enumerate(train_dataloader):
            # 将特征放入到训练设备中
            b_x = b_x.to(device)
            # 将标签放入到训练设备中
            b_y = b_y.to(device)
            # 设置模型为训练榄式
            model.train()

            # 前向传播过程,输入为一个batch,输出为一个batch中对应的预测
            output = model(b_x)

            pre_lab=torch.argmax(output,dim=1)
            #model已经输出来一个值了输出来的值要经过什么经过这个代码啊torch.arg
            #还记得前面我们说我们最后的输出是个神经元是10个值,把这个输出十个值输入到这个soft max里面
            #取概率最大的一个值作为标签,因为它的输出的值是十个值,所以查找每一行当中最大值对应的行标
            #最大对应下标的一个数值

            loss = criterion(output, b_y)#b_y是标签啊,看你的输出和标签利用交叉熵损失函数做对比,output就是你训练的模型训练出来的值,然后和标签值去算损失
            #这里不了解问什么用output和b_y做交叉熵损失函数的可以去看一下交叉熵损失函数的计算公式,计算过程需要用到每一个标签的预测概率

            #梯度值化为0
            optimizer.zero_grad()
            #反向传播计算
            loss.backward()
            #根据网络反向传播的梯度信息更新网路参数,起到降低loss函数计算值的作用
            optimizer.step()
            #对损失函数进行累加
            train_loss += loss.item()*b_x.size()
            #如果预测正确,准确度加1
            train_correct +=torch.sum(pre_lab==b_y.data)
            #当前用于训练的样本数量
            train_num += b_x.size

模型验证

train_loss += loss.item() * b_x.size(0)
            # 如果预测正确,准确度加1
            train_correct += torch.sum(pre_lab == b_y.data)
            # 当前用于训练的样本数量
            train_num += b_x.size(0)

还有我们的样本数量,对不对,我们来解释一下这里的loss值,它获取我们loss值,这个值是什么,是每个样本的,平均值,平均loss值,然后这里为什么要乘什么,乘以我们该样本的数量,乘以我们该样本的数量呢,因为你该你们一批轮次又一批,就假设是100个样本,它的一个平均值,假设平均平均loss乘以乘以100,是不是该批次的累累加,碾压loss对不对,然后你该批次的一个累加loss,是不是等于我们那个train loss,对不对,没有问题吧,因为应该是初始化它是零嘛,你现在把它累加在一起嘛,然后在第二个P4的时候,你再把什么,把把什么这个平均loss乘以什么乘以,我们对那个批次再累加到这个值上面,所以你不断的累加完之后,你会发现你该干嘛,这个train loss这样的一个什么一个值,它是我们我们假设5万个样本的loss值的累加,这里没有问题吧,你发现这里是什么,这里是我们当前训练样本的数量吗,就是每这是什么,这是每批次的数值吗,我们假设一批次是100,假设五个数就500人之后是吧,五五百人之后他就是什么,他就相当于就是我们所有的样本数量,不就已经获得了吗,这里肯定是没有问题,对不对,然后的话这里是什么,是我们所有样本的loss值的累加,对不对,我把那样的loss累加再除以什么,除以我们的level是不是就是什么,我们该轮次的根嘛,平均那个loss值能明白我的意思吗,所以所以我们那个什么,我们验证机不也是一样吗,所以你会发现就是我们loss值除以什么,除以我们的样本数量,就是该轮次的一个平均的一个loss值,加到我们这样的一个列表里面啊,我们所以该该列表里面就获取了什么,该列表一开始是空的嘛,我们定义这个是空的嘛,获取第一个什么第一次训练的一个什么,第一次训练轮次的一个什么loss值,能明白我的意思吗,这里再讲一下啊,这里有点晦涩难懂啊,这里的一个什么loss值,是我们该批次样本的一个什么,该批次比如说100个100个100个样本,它的平均loss,然后乘以对应的嘛,该批次的一个样本数量乘以对应P4,该样本的数量就是什么,就该轮次的就是不该就该批次样本的数量,就是train loss,是不是是吧,Train loss,然后我们一直在循环嘛是吧,把直到把所有的数据是吧,呃训练完之后,是不是到最后最后一个补的,就是我们就是最后一个批次,对不对,最后一个批次的时候啊,计算完之后,这里的值是不是包含了我们所有样本的,该样本的该轮该轮次,该样本的所有的loss值了,所有loss值,对不对啊,所有的loss值,然后这里获取的是什么呃,该轮次的所有的样本的数量,也就是我们训练样本的总数量嘛,都懂我意思吧,实际上你这里不这么写,你你在前面其实也能算出来吗,那我们前面这里已经已经已经算过了,对不对,这个已经算过了,只是为了方便嘛,跟大家统一嘛,你这里都初始化了,我这里初始化一下,对不对是吧,然然后的话,你你你这个这里面那个值,就是我们所有样本的一个loss值的总和,除以什么我们样本数量,那么就该轮次平均的一个loss值了吗是吧

import time

import torch.optim
from torch import nn
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
from model import LeNet


def train_val_data_process():
    train_data = FashionMNIST(root='./data',
                              train=True,
                              transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
                              download=True)
    train_data, val_data = Data.random_split(train_data, [round(0.8 * len(train_data)), round(0.2 * len(train_data))])
    train_dataloader = Data.DataLoader(dataset=train_data,
                                       batch_size=128,
                                       shuffle=True,
                                       num_workers=8)

    val_dataloader = Data.DataLoader(dataset=val_data,
                                     batch_size=128,
                                     shuffle=True,
                                     num_workers=8)

    return train_dataloader, val_dataloader


def train_model(model, train_dataloader, val_dataloader, num_epochs):
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # 优化器,梯度下降法进一步拓展是SGD,Adam和SGDM等等等等的

    criterion = nn.CrossEntropyLoss()
    # 均方损失,分类中我们一般用交叉熵损失来更新损失值,然后是w,b
    # 将模型放到训练设备中
    model = model.to(device)
    # 复制当前模型的参数
    best_model_wts = copy.deepcopy(model.state_dict())

    # 初始化参数
    # 最高准确度
    best_acc = 0.0
    # 训练集损失列表
    train_loss_all = []
    # 验证集损失列表
    val_loss_all = []
    # 训练集准确度列表
    train_acc_all = []
    # 验证集准确度列表
    val_acc_all = []
    # 当前时间
    since = time.time()

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch + 1, num_epochs))
        print("-" * 10)

        train_loss = 0.0
        train_correct = 0.0
        val_loss = 0.0
        val_correct = 0.0

        train_num = 0
        val_num = 0

        # 对每一个mini-batch训练和计算
        for step, (b_x, b_y) in enumerate(train_dataloader):
            # 将特征放入到训练设备中
            b_x = b_x.to(device)
            # 将标签放入到训练设备中
            b_y = b_y.to(device)
            # 设置模型为训练榄式
            model.train()

            # 前向传播过程,输入为一个batch,输出为一个batch中对应的预测
            output = model(b_x)

            pre_lab = torch.argmax(output, dim=1)
            # model已经输出来一个值了输出来的值要经过什么经过这个代码啊torch.arg
            # 还记得前面我们说我们最后的输出是个神经元是10个值,把这个输出十个值输入到这个soft max里面
            # 取概率最大的一个值作为标签,因为它的输出的值是十个值,所以查找每一行当中最大值对应的行标
            # 最大对应下标的一个数值

            loss = criterion(output, b_y)  # b_y是标签啊,看你的输出和标签利用交叉熵损失函数做对比,output就是你训练的模型训练出来的值,然后和标签值去算损失
            # 这里不了解问什么用output和b_y做交叉熵损失函数的可以去看一下交叉熵损失函数的计算公式,计算过程需要用到每一个标签的预测概率

            # 梯度值化为0
            optimizer.zero_grad()
            # 反向传播计算
            loss.backward()
            # 根据网络反向传播的梯度信息更新网路参数,起到降低loss函数计算值的作用
            optimizer.step()
            # 对损失函数进行累加
            train_loss += loss.item() * b_x.size(0)
            # 如果预测正确,准确度加1
            train_correct += torch.sum(pre_lab == b_y.data)
            # 当前用于训练的样本数量
            train_num += b_x.size(0)

            for step, (b_x, b_y) in enumerate(val_dataloader):
                # 将特征放入到验证设备中
                b_x = b_x.to(device)
                # 将标签放入到验证设备中
                b_y = b_y.to(device)
                # 设置模型为评估模式
                model.eval()
                # 前向传播过程,输入为一个patch,输出为一个batch中对应的预测
                output = model(b_x)
                # 查找每一行中最大值对应的行标
                pre_lab = torch.argmax(output, dim=1)
                # 计算每一个batch的损失函数
                loss = criterion(output, b_y)
                # 对损失函数进行累加
                val_loss + loss.item() * b_x.size(0)
                # 如果预测正确,则准确度train.-corrects加1
                val_correct + torch.sum(pre_lab == b_y.data)
                # 当前用于验证的样本数量
                val_num + b_x.size(0)

        #计算并保存每一次迭代的loss值和准确率:其实就是每次求完loss之后叠加,没啥难理解的,无非多了个平均操作
        train_loss_all.append(train_loss/train_num)
        train_acc_all.append(train_correct.double().item()/train_num)

        #
        val_loss_all.append(val_loss/val_num)
        val_acc_all.append(val_correct.double().item()/val_num)

        print('{} Train Loss:{:.4f} Train Accuracy:{:4f}'.format(epoch, train_loss, train_acc_all[-1]))
        print('{} Val Loss:{:.4f} Val Accuracy:{:4f}'.format(epoch, val_loss_all[-1], val_acc_all[-1]))

P50-P60跳了 ​