垃圾分类2.0


前言

上一篇文章我们把垃圾分类项目搭建起来后发现,有很多问题和需要改进的地方。
首先问题在于:
一、我们数据集只有四十类,但是在使用时我们用了五十类,为什么程序依然可以运行?
二、为什么单照片预测时总是不准,但是程序显示的准确值却很高?
三、我们如何精确的分析清楚直观的看到模型的效果?


一、我们数据集只有四十类,但是在使用时我们用了五十类,为什么程序依然可以运行?

import torch
import torch.nn as nn
import torch.nn.functional as F

class simpleconv3(nn.Module):
    def __init__(self, num_classes):
        super(simpleconv3, self).__init__()
        self.conv1 = nn.Conv2d(3, 12, 3, 2)  # 输入图片大小为3*48*48,输出特征图大小为12*23*23,卷积核大小为3*3,步长为2
        self.bn1 = nn.BatchNorm2d(12)
        self.conv2 = nn.Conv2d(12, 24, 3, 2)  # 输入图片大小为12*23*23,输出特征图大小为24*11*11,卷积核大小为3*3,步长为2
        self.bn2 = nn.BatchNorm2d(24)
        self.conv3 = nn.Conv2d(24, 48, 3, 2)  # 输入图片大小为24*11*11,输出特征图大小为48*5*5,卷积核大小为3*3,步长为2
        self.bn3 = nn.BatchNorm2d(48)
        self.fc1 = nn.Linear(48 * 5 * 5, 1200)  # 输入向量长为48*5*5=1200,输出向量长为1200
        self.fc2 = nn.Linear(1200, 128)  # 输入向量长为1200,输出向量长为128
        self.fc3 = nn.Linear(128, num_classes)  # 输入向量长为128,输出向量长为num_classes,等于类别数

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = x.view(x.size(0), -1)  # 动态调整批次大小
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


我们在net中定义了一个 simpleconv3 模型,且在train文件指定了 num_classes=50,但数据集只有四十类。程序依然可以运行:
1.模型架构:尽管你的模型定义了 50 个输出类(在最后一层 fc3 中),模型依然可以处理数据。这是因为模型的结构允许它生成 50 维的输出向量。(定义的层数由训练的参数确定

2.数据集标签:如果数据集中的标签范围是 [0, 39],那么在训练和评估过程中,模型的最后一层仍会产生一个 50 维的向量,尽管只有前 40 个维度有实际的标签对应。剩下的 10 个维度并不会被使用或对损失计算产生影响。

3.损失函数:交叉熵损失函数 nn.CrossEntropyLoss 可以处理这种情况。它会忽略没有标签对应的类别(即 40-49 类),因为在计算损失时,它只会考虑与数据集标签对应的预测值。

4.梯度更新:在训练过程中,梯度只会更新与实际标签相关的权重,即使模型有更多的输出类别。未使用的输出类别(即 40-49 类)不会影响模型的权重更新。

你的代码中定义了一个 simpleconv3 模型,且指定了 num_classes=50,但数据集只有四十类。程序依然可以运行的原因有以下几点:

模型架构:尽管你的模型定义了 50 个输出类(在最后一层 fc3 中),模型依然可以处理数据。这是因为模型的结构允许它生成 50 维的输出向量。

数据集标签:如果数据集中的标签范围是 [0, 39],那么在训练和评估过程中,模型的最后一层仍会产生一个 50 维的向量,尽管只有前 40 个维度有实际的标签对应。剩下的 10 个维度并不会被使用或对损失计算产生影响。

损失函数:交叉熵损失函数 nn.CrossEntropyLoss 可以处理这种情况。它会忽略没有标签对应的类别(即 40-49 类),因为在计算损失时,它只会考虑与数据集标签对应的预测值。

梯度更新:在训练过程中,梯度只会更新与实际标签相关的权重,即使模型有更多的输出类别。未使用的输出类别(即 40-49 类)不会影响模型的权重更新。

虽然程序可以运行,但使用 num_classes=50 会导致模型多余的计算和参数,这不是最佳实践。应将 num_classes 设置为数据集的实际类别数以优化计算资源并避免混淆。当然,问题很好解决,将类别改为40,结果出现了下面的错误。
在这里插入图片描述
具体报错:

train_model(model, train_dataloader, valid_dataloader, num_epochcheckpoint interval=1, final _model_path=“final_model.pth”)File “E:llajifenleillajifenleiltrain.py”, line 53, in train_modelrunning_loss += loss.item()RuntimeError:CUDA error:device-side assert triggeredCUDA kernel errors might be asynchronously reported at some other APcall,so the stacktrace below might be incorrect点For debugging consider passing CUDA_LAUNCH_BLOCKING=1.立Compile with TORCH USE CUDA DSA to enable device-side assertions.
RuntimeError: CUDA error: device-side assert triggered 可能是由于以下原因之一导致的:

1.标签超出范围:这通常是由于标签超出了模型输出的类别范围。例如,如果你的模型输出的类别数为 40,但标签中的某些值大于或等于 40,就会引发此错误。

2.数据不一致:数据集中的图像或标签存在不一致或损坏的情况。

为了探问题的原因,我使用了下面这段代码首先查看是否是标签范围的问题:

from dataset.dataset import train_dataloader, valid_dataloader, test_dataloader
for images, labels in train_dataloader:
    print(labels)  # 打印标签值以检查
    break
def get_all_labels(txt_file):
    labels = set()
    with open(txt_file, 'r') as file:
        for line in file:
            _, label = line.strip().split('\t')
            labels.add(int(label))
    return labels

train_labels = get_all_labels(r'E:\lajifenlei\lajifenlei\dataset\test.txt')
valid_labels = get_all_labels(r'E:\lajifenlei\lajifenlei\dataset\valid.txt')

print("Train labels:", train_labels)
print("Valid labels:", valid_labels)

最后输出的结果:
D:\anacondalenvslyolov8\python.exe E:lajifenleillajifenleil123.
pytensor([24,4,19,14,34,10,4,30,12,2,4.15,25,28. 4,20,39,206,19,12,4,40,8,36,17.15.28.37.6.38.41)
Train labels:{1,2 3 4 5, 6 7,8 9,10 11,12,13,14,15 16 17,18,19,,32,,33,34,35,36,37,38,39,40}
Valid labels::{1,2 3 4 5, 6 7,8 9,10 11,12,13,14,15 16 17,18,19,,32,,33,34,35,36,37,38,39,40}
果然,四十种类标签应该是0-39,而不是1-40,这说明在划分数据集时出现了问题,也与后面的准确率不低却总是预测错误的原因。

二、为什么单照片预测时总是不准,但是程序显示的准确值却很高?

这个问题和上面的标签值超值问题相关联
我们的原代码是这样的:

import os
import random

train_ratio = 0.9
rootdata = "D:/Administrator/Desktop/all/all"

train_list, test_list = [], []

class_flag = 0  # 从0开始标记类别

# 遍历根目录
for root, dirs, files in os.walk(rootdata):
    # 遍历每个类别文件夹中的所有文件
    for file in files:
        file_path = os.path.join(root, file)
        # 根据训练和测试比例划分数据
        if random.random() < train_ratio:
            train_list.append(f"{file_path}\t{class_flag}\n")
        else:
            test_list.append(f"{file_path}\t{class_flag}\n")

    # 处理完一个文件夹中的所有文件后,切换到下一个类别
    class_flag += 1

# 打乱列表顺序
random.shuffle(train_list)
random.shuffle(test_list)

# 将训练和测试数据写入文件
with open('train.txt', 'w', encoding='UTF-8') as f:
    f.writelines(train_list)

with open('test.txt', 'w', encoding='UTF-8') as f:
    f.writelines(test_list)


我们从这段代码:
class_flag = 0 # 从0开始标记类别
#遍历根目录
for root, dirs, files in os.walk(rootdata):
# 遍历每个类别文件夹中的所有文件
for file in files:
file_path = os.path.join(root, file)
# 根据训练和测试比例划分数据
if random.random() < train_ratio:
train_list.append(f"{file_path}\t{class_flag}\n")
else:
test_list.append(f"{file_path}\t{class_flag}\n")
# 处理完一个文件夹中的所有文件后,切换到下一个类别
class_flag += 1

发现,在遍历每个类别文件夹中的所有文件时,我们想当然的认为他是按照目录的顺序开始读取的,这就出现了问题,实际情况是电脑在读取文件时是随机而不按照秩序的(但我们的标签即种类就是文件夹名即标号),这也是我们出错的原因(并且最后类别的+1导致了我们标签范围的超值)。
修改的代码如下:

import os
import random

# 定义训练集、验证集和测试集比例
train_ratio = 0.8
valid_ratio = 0.1
rootdata = r"D:\Administrator\Desktop\all\all"

train_list, valid_list, test_list = [], [], []

# 获取所有类别目录并排序
all_dirs = sorted([d for d in os.listdir(rootdata) if os.path.isdir(os.path.join(rootdata, d))])

# 遍历根目录下的每个类别目录
for dir_name in all_dirs:
    dir_path = os.path.join(rootdata, dir_name)
    files = os.listdir(dir_path)

    # 遍历每个类别文件夹中的所有文件
    for file in files:
        file_path = os.path.join(dir_path, file)
        rand_val = random.random()
        # 根据训练、验证和测试比例划分数据
        if rand_val < train_ratio:
            train_list.append(f"{file_path},{dir_name}\n")
        elif rand_val < train_ratio + valid_ratio:
            valid_list.append(f"{file_path},{dir_name}\n")
        else:
            test_list.append(f"{file_path},{dir_name}\n")

# 打乱列表顺序
random.shuffle(train_list)
random.shuffle(valid_list)
random.shuffle(test_list)

# 将训练、验证和测试数据写入文件
train_file_path = 'train.txt'
valid_file_path = 'valid.txt'
test_file_path = 'test.txt'

with open(train_file_path, 'w', encoding='UTF-8') as f:
    f.writelines(train_list)

with open(valid_file_path, 'w', encoding='UTF-8') as f:
    f.writelines(valid_list)

with open(test_file_path, 'w', encoding='UTF-8') as f:
    f.writelines(test_list)

三、我们如何精确的分析清楚直观的看到模型的效果?

想要清晰且直观的看到模型的训练效果和结构,就要用到 tensorboard这个库。
TensorBoard 是一个用于可视化 TensorFlow 或 PyTorch 等深度学习框架训练过程的工具。它提供了多种图表和工具来帮助用户理解和优化模型。
主要功能
1.可视化训练过程中的指标
标量(Scalars):如损失(loss)和准确率(accuracy)等,可以随时间变化进行记录和显示。
图像(Images):训练过程中生成的图像数据。
直方图(Histograms):权重、偏差、梯度等随时间的变化。
文本(Text):可以记录任意文本信息。
图(Graphs):显示计算图,帮助了解模型结构。
嵌入(Embeddings):可视化高维数据的低维表示。
比较实验结果
2.可以同时加载多个实验的结果,方便对比不同超参数、模型架构等的效果。
我们首先在此环境终端下下载tensorboard:

pip install tensorboard

然后修改训练代码,使得显示我们模型在每一轮的训练集的损失率,训练集/验证集的准确率。

import torch
from torch import nn
import torch.optim as optim
from model.net import simpleconv3
from dataset.dataset import train_dataloader, valid_dataloader, test_dataloader
from torch.utils.tensorboard import SummaryWriter
from sklearn.metrics import accuracy_score

# 评估模型在给定数据加载器上的准确率
def evaluate_model(model, dataloader):
    model.eval()
    all_labels = []
    all_preds = []
    device = next(model.parameters()).device  # 获取模型所在的设备
    with torch.no_grad():
        for images, labels in dataloader:
            images, labels = images.to(device), labels.to(device)  # 将数据移动到 GPU
            outputs = model(images)
            _, preds = torch.max(outputs, 1)
            all_labels.extend(labels.cpu().numpy())
            all_preds.extend(preds.cpu().numpy())

    accuracy = accuracy_score(all_labels, all_preds)
    return accuracy

# 更新 train_model 函数
def train_model(model, train_dataloader, valid_dataloader, num_epochs=5, checkpoint_interval=1, final_model_path="final_model.pth"):
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model.to(device)

    writer = SummaryWriter(log_dir='./runs')

    for epoch in range(num_epochs):
        model.train()
        running_loss = 0.0

        for images, labels in train_dataloader:
            images, labels = images.to(device), labels.to(device)  # 将数据移动到 GPU

            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            running_loss += loss.item()

        avg_loss = running_loss / len(train_dataloader)
        print(f"Epoch [{epoch + 1}/{num_epochs}], Loss: {avg_loss:.4f}")

        # 记录训练损失
        writer.add_scalar('Loss/train', avg_loss, epoch)

        # 计算验证集准确率
        val_accuracy = evaluate_model(model, valid_dataloader)
        writer.add_scalar('Accuracy/valid', val_accuracy, epoch)

        # 计算训练集准确率
        train_accuracy = evaluate_model(model, train_dataloader)
        writer.add_scalar('Accuracy/train',train_accuracy, epoch)

        # 保存检查点
        #if (epoch + 1) % checkpoint_interval == 0:
        #   save_model(model, f"model_checkpoint_epoch_{epoch + 1}.pth")

    save_model(model, final_model_path)
    print("Training Finished")
    writer.close()

def save_model(model, path):
    torch.save(model.state_dict(), path)
    print(f"Model saved to {path}")

def load_model(model, path):
    model.load_state_dict(torch.load(path))
    model.eval()
    print(f"Model weights loaded from {path}")

# 示例用法
if __name__ == "__main__":
    model = simpleconv3(num_classes=40)
    train_model(model, train_dataloader, valid_dataloader, num_epochs=100, checkpoint_interval=1, final_model_path="final_model.pth")

然后我们在终端运行代码,打开可视化界面:

tensorboard --logdir=./runs

结果如下图所示(还未跑完)
在这里插入图片描述

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值