《深度学习》——调整学习率和保存使用最优模型

调整学习率

在使用 PyTorch 进行深度学习训练时,调整学习率是一个重要的技巧,合适的学习率调整策略可以帮助模型更好地收敛。
PyTorch 提供了多种调整学习率的方法,下面将详细介绍几种常见的学习率调整策略及实例代码:

  • torch.optim.lr_scheduler.StepLR(固定步长学习率调度器)
    StepLR 是一种简单的学习率调整策略,它会每隔一定的训练步数(epoch)将学习率乘以一个固定的衰减因子。

    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.optim.lr_scheduler import StepLR
    
    # 定义一个简单的模型
    class SimpleModel(nn.Module):
        def __init__(self):
            super(SimpleModel, self).__init__()
            self.fc = nn.Linear(10, 1)
    
        def forward(self, x):
            return self.fc(x)
    
    model = SimpleModel()
    optimizer = optim.SGD(model.parameters(), lr=0.1)
    
    # 定义 StepLR 调度器,每 3 个 epoch 将学习率乘以 0.1
    scheduler = StepLR(optimizer, step_size=3, gamma=0.1)
    
    # 模拟训练过程
    for epoch in range(10):
        # 训练代码...
        print(f'Epoch {epoch}: Learning rate = {optimizer.param_groups[0]["lr"]}')
        optimizer.step()
        scheduler.step()
    

在这里插入图片描述

  • torch.optim.lr_scheduler.MultiStepLR(多步学习率调度器)
    MultiStepLR 允许你在指定的训练步数(epoch)处将学习率乘以一个固定的衰减因子。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import MultiStepLR

model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 定义 MultiStepLR 调度器,在 epoch 为 3 和 7 时将学习率乘以 0.1
milestones = [3, 7]
scheduler = MultiStepLR(optimizer, milestones=milestones, gamma=0.1)

# 模拟训练过程
for epoch in range(10):
    # 训练代码...
    print(f'Epoch {epoch}: Learning rate = {optimizer.param_groups[0]["lr"]}')
    optimizer.step()
    scheduler.step()

在这里插入图片描述

  • torch.optim.lr_scheduler.ExponentialLR(指数学习率调度器)
    ExponentialLR 会在每个训练步数(epoch)将学习率乘以一个固定的衰减因子。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import ExponentialLR

model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 定义 ExponentialLR 调度器,每个 epoch 将学习率乘以 0.9
scheduler = ExponentialLR(optimizer, gamma=0.9)

# 模拟训练过程
for epoch in range(10):
    # 训练代码...
    print(f'Epoch {epoch}: Learning rate = {optimizer.param_groups[0]["lr"]}')
    optimizer.step()
    scheduler.step()

在这里插入图片描述

  • torch.optim.lr_scheduler.ReduceLROnPlateau(自适应学习率调度器)
    ReduceLROnPlateau 会根据验证集上的指标(如损失值)来动态调整学习率。当指标在一定的训练步数内没有改善时,它会将学习率乘以一个固定的衰减因子。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim.lr_scheduler import ReduceLROnPlateau

model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 定义 ReduceLROnPlateau 调度器,当验证损失在 2 个 epoch 内没有改善时,将学习率乘以 0.1
scheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=2)

# 模拟训练过程
for epoch in range(10):
    # 训练代码...
    # 假设这是验证集上的损失
    val_loss = torch.rand(1).item()
    print(f'Epoch {epoch}: Learning rate = {optimizer.param_groups[0]["lr"]}')
    optimizer.step()
    scheduler.step(val_loss)

在这里插入图片描述

保存最优模型

在深度学习训练过程中,保存最优模型是一个常见的需求,这样可以在训练结束后使用表现最好的模型进行预测或评估。通常,我们会根据验证集上的某个指标(如准确率、损失值等)来判断模型是否为最优。

  • 保存模型的状态字典(state_dict)到文件 best.pth 中。
  • 保存整个模型对象到文件 best1.pt 中。
if correct > best_acc:
    # 如果当前正确预测的样本数量大于之前记录的最优值
    best_acc = correct
    # 更新最优准确率记录为当前的正确样本数量
    
    print(model.state_dict().keys())
    # 打印模型状态字典中的所有键,这些键代表了模型中可学习参数的名称,
    # 例如各个层的权重(weight)和偏置(bias)等
    
    torch.save(model.state_dict(), 'best.pth')
    # 将模型的状态字典保存到名为 'best.pth' 的文件中。
    # 状态字典包含了模型中所有可学习参数的值,这种保存方式只保存参数,不保存模型结构,
    # 后续加载时需要先定义相同结构的模型,再加载参数
    
    torch.save(model, 'best1.pt')
    # 将整个模型对象保存到名为 'best1.pt' 的文件中。
    # 这种保存方式不仅保存了模型的参数,还保存了模型的结构信息,
    # 后续加载时可以直接加载整个模型对象

注意

    1. 保存状态字典与保存整个模型的区别
    • 保存状态字典(torch.save(model.state_dict(), ‘best.pth’)):
      • 优点:文件体积相对较小,适合在不同环境下共享模型参数,因为只需要模型结构定义代码一致,就可以加载参数。
      • 缺点:需要手动定义模型结构,再加载参数。如果模型结构发生变化,可能会导致加载失败。
    • 保存整个模型(torch.save(model, ‘best1.pt’)):
      • 优点:加载方便,直接加载整个模型对象,不需要重新定义模型结构。
      • 缺点:文件体积较大,并且依赖于保存时的 Python 环境和 PyTorch 版本,在不同环境下加载可能会出现兼容性问题。
    1. 准确率指标的选择
      代码中使用 correct(正确预测的样本数量)作为判断模型优劣的指标,在实际应用中,更常见的做法是使用准确率(correct / total),因为 correct 的数值会受到验证集样本数量的影响,而准确率是一个相对稳定的指标。
    1. 文件路径管理
      代码中直接使用相对路径保存模型文件,在实际项目中,建议使用绝对路径或者更规范的文件路径管理方式,避免文件保存位置混乱。

使用最优模型

  • 使用best.pth 中的状态字典(state_dict) 。
  • 使用best1.pt 中的模型对象 。
# 尝试从 'best.pth' 文件中加载模型的状态字典(state_dict),
# 状态字典包含了模型中所有可学习参数(如权重、偏置)的值,
# 但不包含模型的结构信息。所以在使用此方式加载前,
# 必须先定义好与保存时结构一致的模型实例。
# torch.load('best.pth') 用于从文件中读取状态字典数据,
# model.load_state_dict() 方法将读取到的状态字典数据赋值给当前的 model 实例。
model.load_state_dict(torch.load('best.pth'))

# 这行代码会直接从 'best.pt' 文件中加载整个模型对象,
# 该文件保存时使用了 torch.save(model, 'best.pt') 方式,
# 这种保存方式不仅保存了模型的参数,还保存了模型的结构信息。
# 加载后,model 变量将指向一个完整的、可直接使用的模型对象。
# 不过需要注意的是,如果之前使用的 Python 环境、PyTorch 版本不同,
# 或者模型定义代码发生了变化,可能会导致加载失败或出现兼容性问题。
# 并且这行代码与上一行 'model.load_state_dict(...)' 存在冲突,
# 因为这里会覆盖掉上一行代码对 model 实例参数的加载结果,
# 通常在实际使用中只会选择其中一种加载方式。
model = torch.load('best.pt')

# 将模型设置为评估模式。在评估模式下,
# 一些在训练时起作用的特殊层(如 Dropout、BatchNorm 等)
# 会改变其行为。例如,Dropout 层在训练时会随机丢弃部分神经元以防止过拟合,
# 但在评估时会正常使用所有神经元;BatchNorm 层在训练时会根据当前批次的数据
# 计算均值和方差,而在评估时会使用训练过程中统计的全局均值和方差。
# 因此,在进行模型推理、验证或测试时,需要将模型设置为评估模式,
# 以确保得到准确的结果。
model.eval()

实例

数据集:照片文件:在这里插入图片描述
在这里插入图片描述

test.txt:
在这里插入图片描述

train.txt:
在这里插入图片描述

test_true.txt:
在这里插入图片描述

保存最优模型

对食物分类中food dataset2目录下的test、train中的照片进行卷积神经训练,进行食物分类并保存最优模型,并调整学习率。
代码:

import os
from PIL import Image

# 定义函数用于生成训练集和测试集的文件列表及对应的标签
def train_test_file(root, dir):
    # 打开一个以dir命名的txt文件,用于写入图像路径和标签
    file_txt = open(dir + '.txt', 'w')
    # 拼接完整的路径
    path = os.path.join(root, dir)
    # 遍历指定路径下的所有文件和文件夹
    for roots, directories, files in os.walk(path):  # os.list_dir()
        # 如果存在子文件夹,将子文件夹名称存储在dirs列表中
        if len(directories) != 0:
            dirs = directories
        else:
            # 获取当前文件夹的名称
            now_dir = roots.split('\\')
            # 遍历当前文件夹下的所有文件
            for file in files:
                # 拼接图像文件的完整路径
                path_1 = os.path.join(roots, file)
                print(path_1)
                # 将图像路径和对应的标签写入txt文件,标签为当前文件夹在dirs列表中的索引
                file_txt.write(path_1 + ' ' + str(dirs.index(now_dir[-1])) + '\n')
    # 关闭文件
    file_txt.close()

# 数据集根目录
root = r'.\食物分类\food_dataset'
# 训练集文件夹名称
train_dir = 'train'
# 测试集文件夹名称
test_dir = 'test'
# 生成训练集的文件列表及标签
train_test_file(root, train_dir)
# 生成测试集的文件列表及标签
train_test_file(root, test_dir)

# 自定义类,用于演示__getitem__和__len__方法
class USE_getitem():
    def __init__(self, text):
        # 初始化传入的文本
        self.text = text

    def __getitem__(self, index):
        # 获取指定索引位置的字符并转换为大写
        result = self.text[index].upper()
        return result

    def __len__(self):
        # 返回文本的长度
        return len(self.text)

# 创建USE_getitem类的实例
p = USE_getitem('pytorch')
# 打印索引为0和1的字符
print(p[0], p[1])
# 调用__len__方法获取文本长度
len(p)

import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
from torchvision import transforms
from torch import nn

# 定义训练集和验证集的数据预处理转换
data_transforms = {
    'train':
        transforms.Compose([
            # 将图像大小调整为300x300
            transforms.Resize([300, 300]),
            # 随机旋转图像,旋转角度范围为-45到45度
            transforms.RandomRotation(45),
            # 从图像中心裁剪出256x256的区域
            transforms.CenterCrop(256),
            # 以0.5的概率随机水平翻转图像
            transforms.RandomHorizontalFlip(p=0.5),
            # 以0.5的概率随机垂直翻转图像
            transforms.RandomVerticalFlip(p=0.5),
            # 随机调整图像的亮度、对比度、饱和度和色相
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            # 以0.1的概率将图像转换为灰度图
            transforms.RandomGrayscale(p=0.1),
            # 将图像转换为Tensor
            transforms.ToTensor(),
            # 对图像进行归一化处理
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    'valid':
        transforms.Compose([
            # 将图像大小调整为256x256
            transforms.Resize([256, 256]),
            # 将图像转换为Tensor
            transforms.ToTensor(),
            # 对图像进行归一化处理
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
}

# 自定义数据集类,继承自torch.utils.data.Dataset
class food_dataset(Dataset):
    def __init__(self, file_path, transform=None):
        # 初始化文件路径
        self.file_path = file_path
        # 用于存储图像路径的列表
        self.imgs = []
        # 用于存储图像标签的列表
        self.labels = []
        # 初始化数据预处理转换
        self.transform = transform
        # 打开文件并读取每一行
        with open(self.file_path) as f:
            # 将每一行按空格分割成图像路径和标签
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path, label in samples:
                # 将图像路径添加到imgs列表中
                self.imgs.append(img_path)  # 图像路径
                # 将标签添加到labels列表中
                self.labels.append(label)  # 标签

    def __len__(self):
        # 返回图像的数量
        return len(self.imgs)

    def __getitem__(self, idx):
        # 打开指定索引位置的图像
        image = Image.open(self.imgs[idx])
        # 如果存在数据预处理转换,对图像进行转换
        if self.transform:
            image = self.transform(image)
        # 获取指定索引位置的标签
        label = self.labels[idx]
        # 将标签转换为torch.Tensor类型
        label = torch.from_numpy(np.array(label, dtype=np.int64))
        return image, label

# 创建训练集数据集对象
training_data = food_dataset(file_path='./train.txt', transform=data_transforms['train'])
# 创建测试集数据集对象
test_data = food_dataset(file_path='./test.txt', transform=data_transforms['valid'])

# 创建训练集数据加载器,批量大小为64,数据打乱
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
# 创建测试集数据加载器,批量大小为64,数据打乱
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

# 判断是否支持GPU或MPS,如果支持则使用,否则使用CPU
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(f'Using {device} device')

# 定义卷积神经网络类,继承自torch.nn.Module
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一个卷积块
        self.conv_block1 = nn.Sequential(
            # 输入通道数为3,输出通道数为32,卷积核大小为3,填充为1
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            # 批量归一化
            nn.BatchNorm2d(32),
            # ReLU激活函数
            nn.ReLU(inplace=True),
            # 最大池化层,池化核大小为2
            nn.MaxPool2d(2)
        )
        # 第二个卷积块
        self.conv_block2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第三个卷积块
        self.conv_block3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第四个卷积块
        self.conv_block4 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第五个卷积块
        self.conv_block5 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )

        # 计算经过卷积和池化后特征图的尺寸
        # 每次池化操作将尺寸缩小一半,经过5次池化,256 / 2^5 = 8
        self.fc1 = nn.Linear(512 * 8 * 8, 2048)
        self.relu = nn.ReLU(inplace=True)
        # 随机丢弃50%的神经元,防止过拟合
        self.dropout = nn.Dropout(0.5)
        # 输出层,输出20个类别
        self.fc2 = nn.Linear(2048, 20)

    def forward(self, x):
        # 前向传播,依次通过各个卷积块
        x = self.conv_block1(x)
        x = self.conv_block2(x)
        x = self.conv_block3(x)
        x = self.conv_block4(x)
        x = self.conv_block5(x)

        # 将多维的特征图展平为一维向量
        x = x.view(-1, 512 * 8 * 8)

        # 通过第一个全连接层
        x = self.fc1(x)
        x = self.relu(x)
        x = self.dropout(x)
        # 通过输出层
        output = self.fc2(x)
        return output

# 创建CNN模型实例并将其移动到指定设备上
model = CNN().to(device)

# 定义训练函数
def train(dataloader, model, loss_fn, optimizer):
    # 将模型设置为训练模式
    model.train()  
    # 记录当前批次的编号
    batch_size_num = 1
    # 遍历数据加载器中的每个批次
    for x, y in dataloader:  # 其中batch为每一个数据的编号
        # 将训练数据和标签移动到指定设备上
        x, y = x.to(device), y.to(device)  
        # 前向传播,计算模型的预测结果
        pred = model.forward(x)  
        # 通过交叉熵损失函数计算损失值
        loss = loss_fn(pred, y)  
        # 梯度值清零
        optimizer.zero_grad()  
        # 反向传播计算每一个参数的梯度值
        loss.backward()  
        # 根据梯度更新网络参数
        optimizer.step()  

        # 从tensor数据中提取损失值
        loss_value = loss.item()  
        # 每10个批次打印一次损失值
        if batch_size_num % 10 == 0:
            print(f'loss:{loss_value:7f}  [number:{batch_size_num}]')
        # 批次编号加1
        batch_size_num += 1

# 记录最佳准确率
best_acc = 0

# 定义测试函数
def test(dataloader, model, loss_fn):
    global best_acc
    # 数据集的总样本数
    size = len(dataloader.dataset)  
    # 数据加载器中的批次数量
    num_batches = len(dataloader)  
    # 将模型设置为评估模式
    model.eval()  
    # 初始化测试损失和正确预测的样本数
    test_loss, correct = 0, 0
    # 关闭梯度计算
    with torch.no_grad():  
        for x, y in dataloader:
            # 将测试数据和标签移动到指定设备上
            x, y = x.to(device), y.to(device)
            # 前向传播,计算模型的预测结果
            pred = model.forward(x)
            # 累加每个批次的损失值
            test_loss += loss_fn(pred, y).item()  
            # 计算正确预测的样本数
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
            a = (pred.argmax(1) == y)  # dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
            b = (pred.argmax(1) == y).type(torch.float)
    # 计算平均测试损失
    test_loss /= num_batches  
    # 计算平均准确率
    correct /= size  
    # 如果当前准确率高于最佳准确率,更新最佳准确率并保存模型
    if correct > best_acc:
        best_acc = correct
        print(model.state_dict().keys())
        # 保存模型的参数
        torch.save(model.state_dict(), 'best.pth')
        # 保存整个模型
        torch.save(model, 'best1.pt')
    print(f'Test result: \n Accuracy:{(100 * correct)}%,Avg loss:{test_loss}')

# 创建交叉熵损失函数对象
loss_fn = nn.CrossEntropyLoss()  
# 创建Adam优化器,用于更新模型参数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  

# 训练的轮数
epochs = 150
# 循环训练多个轮次
for t in range(epochs):
    print(f'epoch{t + 1}\n--------------------')
    # 训练模型
    train(train_dataloader, model, loss_fn, optimizer)
    # 测试模型
    test(test_dataloader, model, loss_fn)
print('Done!')

在这里插入图片描述
保存模型: 在这里插入图片描述

使用最优模型

对数据中的测试数据进行测试训练,路径文件为test_true.txt。

from PIL import Image
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
from torchvision import transforms
from torch import nn

# 定义数据预处理转换,这里只定义了验证集的转换
data_transforms = {
    'valid':
        transforms.Compose([
            # 将图像调整为 256x256 的大小
            transforms.Resize([256, 256]),
            # 将图像转换为 PyTorch 的张量
            transforms.ToTensor(),
            # 对图像进行归一化处理,使用 ImageNet 的均值和标准差
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
}

# 自定义食物数据集类,继承自 torch.utils.data.Dataset
class food_dataset(Dataset):
    def __init__(self, file_path, transform=None):
        # 保存数据集文件路径
        self.file_path = file_path
        # 用于存储图像路径的列表
        self.imgs = []
        # 用于存储图像标签的列表
        self.labels = []
        # 保存数据预处理转换
        self.transform = transform
        # 打开数据集文件
        with open(self.file_path) as f:
            # 读取文件的每一行,并按空格分割成图像路径和标签
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path, label in samples:
                # 将图像路径添加到 imgs 列表中
                self.imgs.append(img_path)
                # 将标签添加到 labels 列表中
                self.labels.append(label)

    def __len__(self):
        # 返回数据集的长度,即图像的数量
        return len(self.imgs)

    def __getitem__(self, idx):
        # 打开指定索引的图像
        image = Image.open(self.imgs[idx])
        # 如果存在数据预处理转换,则对图像进行转换
        if self.transform:
            image = self.transform(image)
        # 获取指定索引的标签
        label = self.labels[idx]
        # 将标签转换为 PyTorch 的张量
        label = torch.from_numpy(np.array(label, dtype=np.int64))
        # 返回图像和标签
        return image, label

# 判断是否支持 GPU 或 MPS,如果支持则使用,否则使用 CPU
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_available() else 'cpu'
print(f'Using {device} device')

# 定义卷积神经网络类,继承自 torch.nn.Module
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一个卷积块
        self.conv_block1 = nn.Sequential(
            # 输入通道数为 3,输出通道数为 32,卷积核大小为 3,填充为 1
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            # 批量归一化层
            nn.BatchNorm2d(32),
            # ReLU 激活函数
            nn.ReLU(inplace=True),
            # 最大池化层,池化核大小为 2
            nn.MaxPool2d(2)
        )
        # 第二个卷积块
        self.conv_block2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第三个卷积块
        self.conv_block3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第四个卷积块
        self.conv_block4 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 第五个卷积块
        self.conv_block5 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.BatchNorm2d(512),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2)
        )
        # 全连接层的输入维度,经过 5 次池化后,特征图大小为 8x8,通道数为 512
        self.fc1 = nn.Linear(512 * 8 * 8, 2048)
        # ReLU 激活函数
        self.relu = nn.ReLU(inplace=True)
        # Dropout 层,防止过拟合
        self.dropout = nn.Dropout(0.5)
        # 输出层,输出 20 个类别
        self.fc2 = nn.Linear(2048, 20)

    def forward(self, x):
        # 依次通过各个卷积块
        x = self.conv_block1(x)
        x = self.conv_block2(x)
        x = self.conv_block3(x)
        x = self.conv_block4(x)
        x = self.conv_block5(x)
        # 将多维的特征图展平为一维向量
        x = x.view(-1, 512 * 8 * 8)
        # 通过第一个全连接层
        x = self.fc1(x)
        # 通过 ReLU 激活函数
        x = self.relu(x)
        # 通过 Dropout 层
        x = self.dropout(x)
        # 通过输出层
        output = self.fc2(x)
        return output

# 创建 CNN 模型实例,并将其移动到指定设备上
model = CNN().to(device)

# 加载之前保存的模型参数
model.load_state_dict(torch.load('best.pth'))
# 也可以直接加载整个模型
# model = torch.load('best.pt')
# 将模型设置为评估模式
model.eval()

# 创建测试数据集实例
test_data = food_dataset(file_path='test_true.txt', transform=data_transforms['valid'])
# 创建测试数据加载器,批量大小为 1,数据打乱
test_dataloader = DataLoader(test_data, batch_size=1, shuffle=True)
# 用于存储模型的预测结果
result = []
# 用于存储真实标签
lables = []

# 定义测试函数,用于获取每个样本的预测结果和真实标签
def test_true(dataloader, model):
    # 关闭梯度计算,减少内存消耗
    with torch.no_grad():
        for x, y in dataloader:
            # 将输入数据和标签移动到指定设备上
            x, y = x.to(device), y.to(device)
            # 进行前向传播,得到模型的预测结果
            pred = model.forward(x)
            # 获取预测结果中概率最大的类别索引,并添加到 result 列表中
            result.append(pred.argmax(1).item())
            # 获取真实标签,并添加到 lables 列表中
            lables.append(y.item())

# 调用测试函数
test_true(test_dataloader, model)
# 打印模型的预测结果
print('预测值:\t', result)
# 打印真实标签
print('真实值:\t', lables)

# 定义测试函数,用于计算模型的准确率和平均损失
def test(dataloader, model, loss_fn):
    global best_acc
    # 数据集的总样本数
    size = len(dataloader.dataset)
    # 数据加载器中的批次数量
    num_batches = len(dataloader)
    # 将模型设置为评估模式
    model.eval()
    # 初始化测试损失和正确预测的样本数
    test_loss, correct = 0, 0
    # 关闭梯度计算,减少内存消耗
    with torch.no_grad():
        for x, y in dataloader:
            # 将输入数据和标签移动到指定设备上
            x, y = x.to(device), y.to(device)
            # 进行前向传播,得到模型的预测结果
            pred = model.forward(x)
            # 计算当前批次的损失,并累加到 test_loss 中
            test_loss += loss_fn(pred, y).item()
            # 统计正确预测的样本数
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
            a = (pred.argmax(1) == y)  # dim=1 表示每一行中的最大值对应的索引号,dim=0 表示每一列中的最大值对应的索引号
            b = (pred.argmax(1) == y).type(torch.float)
    # 计算平均测试损失
    test_loss /= num_batches
    # 计算准确率
    correct /= size
    # 打印测试结果
    print(f'Test result: \n Accuracy:{(100 * correct)}%,Avg loss:{test_loss}')

# 创建交叉熵损失函数实例
loss_fn = nn.CrossEntropyLoss()
# 创建 Adam 优化器实例
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
# 调用测试函数,计算模型的准确率和平均损失
test(test_dataloader, model, loss_fn)

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RocketMQ和Kafka如何实现顺序写入和顺序消费?

0 前言 先说明kafka,顺序写入和消费是Kafka的重要特性,但需要正确的配置和使用方式才能保证。本文需要解释清楚Kafka如何通过分区来实现顺序性,以及生产者和消费者应该如何配合。   首先,顺序写入。Kafka的消息是按分区追加写入…

场外个股期权下单后多久成交?场外个股期权对投资组合的影响

对普通老板们而言,它如同精密手术刀——用得好可精准优化投资组合,用不好则可能伤及本金。记住两个关键:一是永远用"亏得起的钱"参与,二是把合约条款当"药品说明书"逐字研读。 场外个股期权下单后多久成交&am…

Android13-包安装器PackageInstaller-之apk安装流程

目的 我们最终是为了搞明白安装的整个流程通过安卓系统自带的包安装器来了解PMS 安装流程实现需求定制:静默安装-安装界面定制-安装拦截验证。【核心目的】 安装流程和PMS了解不用多说了; 安装定制相关: 如 手机上安装时候弹出锁屏界面需要输入密码;安…

UART(一)——UART基础

一、定义 UART(Universal Asynchronous Receiver/Transmitter)是一种广泛使用的串行通信协议,用于在设备间通过异步方式传输数据。它无需共享时钟信号,而是依赖双方预先约定的参数(如波特率)完成通信。 功能和特点 基本的 UART 系统只需三个信号即可提供稳健的中速全双工…

mysql 学习16 视图,存储过程,存储函数,触发器

视图, 视图是一张虚拟存在的表,以方便我们查询。 创建视图 如下的语句中,我们将select 后的数据,作为一个视图 , 视图名字为view_tb_user_1 create or replace view view_tb_user_1 as select tb_user.name,tb_user.a…

大疆无人机需要的kml文件如何制作kml导出(大疆KML文件)

大疆无人机需要的轨迹kml文件,是一种专门的格式,这个kml里面只有轨迹点,其它的属性信息都不需要。 BigemapPro提供了专门的大疆格式输出, 软件这里下载 www.bigemap.com 安装后,kml导入如下图: 然后选择…

人工智能技术-基于长短期记忆(LSTM)网络在交通流量预测中的应用

人工智能技术-基于长短期记忆(LSTM)网络在交通流量预测中的应用 基于人工智能的智能交通管理系统 随着城市化进程的加快,交通问题日益严峻。为了解决交通拥堵、减少交通事故、提高交通管理效率,人工智能(AI&#xff…

Mysql一行数据如何存储(操作系统)

1. 数据的文件名及作用 每创建一个数据库,都会在/var/lib/mysql/目录中创建一个database名的目录,其中表结构和表数据都存放在找个目录中 这个目录中会有三种文件类型 opt: 存储数据库的默认字符集字符校验规则frm:存储表结构ib…

vue3项目实践心得-多次渲染同一svg + 理解v-if、transition、dom加载之间的顺序

🧡🧡需求🧡🧡 未点击查看答案按钮时,步骤3面板未展示内容(v-if控制) 点击查看答案按钮后,通过graphviz绘制并展示状态转换图,渲染在步骤2中,同时步骤3的v-…

Redis(高阶篇)02章——BigKey

一、面试题 阿里广告平台,海量数据里查询某一个固定前缀的key小红书,你如何生产上限制 keys* /flushdb/flushall等危险命令以防止阻塞或误删数据?美团,memory usage命令你用过吗?BigKey问题,多大算big&…

Vue 3最新组件解析与实践指南:提升开发效率的利器

目录 引言 一、Vue 3核心组件特性解析 1. Composition API与组件逻辑复用 2. 内置组件与生命周期优化 3. 新一代UI组件库推荐 二、高级组件开发技巧 1. 插件化架构设计 2. 跨层级组件通信 三、性能优化实战 1. 惰性计算与缓存策略 2. 虚拟滚动与列表优化 3. Tree S…

Vulhub靶机 ActiveMQ任意 文件写入(CVE-2016-3088)(渗透测试详解)

一、开启vulhub环境 docker-compose up -d 启动 docker ps 查看开放的端口 漏洞版本:ActiveMQ在5.14.0之前的版本(不包括5.14.0) 二、访问靶机IP 8161端口 默认账户密码都是admin 1、利用bp抓包,修改为PUT方法并在fileserver…

【CubeMX-HAL库】STM32F407—无刷电机学习笔记

目录 简介: 学习资料: 跳转目录: 一、工程创建 二、板载LED 三、用户按键 四、蜂鸣器 1.完整IO控制代码 五、TFT彩屏驱动 六、ADC多通道 1.通道确认 2.CubeMX配置 ①开启对应的ADC通道 ②选择规则组通道 ③开启DMA ④开启ADC…

【医学影像AI】50个眼科影像数据集(1)分类任务

【医学影像】50个眼科影像数据集(1)分类任务 【医学影像】50个眼科影像数据集(2)分割任务 【医学影像】50个眼科影像数据集(3)其它任务 【医学影像AI】50 个眼科影像数据集(1)分类任…

飞算JavaAI:一款改变编程行业格局的智能助手

在快速发展的数字化时代,编程已成为推动各行各业创新的核心动力。然而,编程行业也面临着诸多挑战。 1.人才短缺问题日益突出。随着技术的不断进步,对编程人才的需求急剧增加,但合格的程序员却供不应求。这不仅导致招聘难度加大&a…

javaIO流专题

前言 文件流   文件在程序中是以流的形式来操作的.   流:数据在数据源(文件)和程序(内存)之间经历的路径   输入流:数据从数据源(文件)到程序(内存)的路径   输出流:数据从程序(内存)到数据源(文件)的路径   Java IO 流是处理输入和输出的核心机制,用于…

假面与演员:到底是接口在使用类,还是类在使用接口?编程接口与物理接口的区别又是什么?

前言:本篇文章解释了接口学习过程中的2个常见问题,一个是“为什么是类在使用接口”,另一个一个是“编程接口与物理接口的差异源于所处的抽象层次和交互模式的不同”,旨在揭示编程接口的本质。 Part1.是类在使用接口 当学习接口时…

大语言模型入门

大语言模型入门 1 大语言模型步骤1.1 pre-training 预训练1.1.1 从网上爬数据1.1.2 tokenization1.1.2.1 tokenization using byte pair encoding 1.3 预训练1.3.1 context1.3.2 training1.3.3 输出 1.2 post-training1:SFT监督微调1.2.1 token 1.3 强化学习1.3.1 基…

Web后端 Tomcat服务器

一 Tomcat Web 服务器 介绍: Tomcat是一个开源的Java Servlet容器和Web服务器,由Apache软件基金会开发。它实现了Java Servlet和JavaServer Pages (JSP) 技术,用于运行Java Web应用程序。Tomcat轻量、易于配置,常作为开发和部署…

Ubuntu 22.04.5 LTS 安装企业微信,(2025-02-17安装可行)

一、依赖包(Ubuntu 20.04/Debian 11) 点击下载https://www.spark-app.store/download_dependencies_latest 1、 下载最新的依赖包。 请访问星火应用商店依赖包下载页面, 下载最新的依赖包。2、解压依赖包 </