☰
CNN与决策树结合的垃圾分类系统:从PyTorch训练到项目避坑指南
2026/9/28 8:38:55 网站建设 项目流程

简介:基于CNN图像分类与决策树算法的垃圾分类系统项目,面向计算机相关专业学生及机器学习入门开发者,可用于课程设计、大作业或实践项目参考。项目完整覆盖从图像数据集到模型训练与分类结果输出的流程,包含1985张垃圾分类图像数据、8个Python实现脚本、4份Word配套文档(如需求规格说明书、设计报告、测试方案)以及2份Markdown说明与1份txt说明,压缩包共2000个文件,整体大小约53MB。目前已有196人学习下载,代码均经过本地编译与严格调试,评审分达95分以上,运行稳定性较有保障。借助该系统,读者可以同时对比CNN图像分类和基于决策树的垃圾分类两种技术路线,并直接使用现成数据集与文档模板进行二次开发或毕设拓展。

1. 从解压到跑通:这份垃圾分拣压缩包到底该怎么上手?

如果你的U盘里躺着一个叫“Python基于CNN的图像分类算法、基于决策树的垃圾分类算法实现的垃圾分类系统(源码+数据集+说明文档).zip”的项目包,第一反应大概率是解压、找main.py、双击运行。实际结果通常是:根本没有main.py,或者报错多到怀疑人生。这个系统的核心不在打包方式,而在两个算法各司其职:CNN负责看懂垃圾图片里的物体,把图片归到“玻璃、纸、塑料、金属”这类物料类别;决策树则基于材质、重量、可弯曲性等人工特征,输出最终的“可回收/不可回收”判断。这种拆法比端到端单模型更容易在课程设计里自圆其说,因为每一步都有明确的输入输出,调试和答辩都更好讲。这篇笔记按我自己完善这类项目的顺序,从目录结构讲到训练细节,再讲到踩坑点,最后给你一套验证改进路径,适合正要交课程设计、或者想快速复现一个可演示系统的Python学习者。

2. 架构先行:CNN和决策树在系统里是怎么分工的

2.1 两个模型的分工:图像特征和人工特征不能只靠一个模型

很多人拿到这个标题会疑惑:既然CNN已经能做图像分类,为什么还要叠加一个决策树?直接让CNN端到端输出“可回收/不可回收”不是更省事吗?确实省事,但课程设计里真正的难点是图片质量不稳定、垃圾重叠、光线差,CNN经常把玻璃瓶认成塑料瓶,把纸巾认成纸板。这时候如果有一个“物体是什么”的图片分类结果,再配合重量、材质、能不能弯曲这类结构化特征,让决策树做二次判断,整体鲁棒性会高很多。

决策树在这里的价值是特征规则透明。比如“材质=金属”这个特征一旦出现,树的分支可以直接指向“可回收”;而CNN输出的置信度分布也可以作为额外特征喂进去。这种两段式结构在答辩时特别好讲:你先展示CNN学到的边缘和纹理特征,再展示决策树学到的if-then规则,整个系统就不再是黑匣子。需要注意,这里的决策树不是对图片像素做分类,而是吃结构化特征;CNN则专门负责把图片映射成物料类别,两者接口分离,互不干扰。

2.2 解压后的目录应该长什么样:一个可运行的清单

课程设计包的目录结构通常比较固定,即使你拿到的zip略有差异,按“数据、模型、脚本、文档”四个块去对应即可。典型结构如下:

garbage_sorting_system/ ├── data/ │ ├── train/ │ │ ├── glass/ │ │ ├── paper/ │ │ ├── plastic/ │ │ └── metal/ │ ├── val/ │ └── test/ ├── models/ │ ├── cnn_resnet18.pth │ └── decision_tree.pkl ├── src/ │ ├── train_cnn.py │ ├── train_dt.py │ ├── preprocess.py │ └── predict.py ├── docs/ │ └── 说明文档.md └── requirements.txt

这个结构里,data/train下的每个子文件夹名就是CNN的类别标签,ImageFolder会直接按这些名字生成标签。models里的两个文件是整个系统的核心,推理脚本只依赖它们。如果你发现包里的目录不完全是这样的,不要急着删改,先找训练脚本里引用的路径,再对照说明文档调整。最容易翻车的是类别顺序:CNN类别列表和决策树特征列都讲究顺序,顺序不一致时预测结果会张冠李戴。

2.3 推理流程的代码骨架:模型接力要怎么设计接口

我见过很多学生写推理脚本时,把加载模型、读图片、调模型、调决策树的代码全部塞在一个函数里,最后连自己都看不懂。更稳的做法是定义一个Predictor类,把两个模型当作两个可插拔组件。下面这段骨架可以直接复用到大多数课程设计里:

import torch import joblib import numpy as np from PIL import Image from torchvision import transforms from sklearn.tree import DecisionTreeClassifier class GarbagePredictor: def __init__(self, cnn_path, dt_path, class_names, feature_names): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.model = self.load_cnn(cnn_path).to(self.device) self.model.eval() self.dt = self.load_dt(dt_path) self.class_names = class_names self.feature_names = feature_names # 图片预处理:统一缩放、归一化,必须和训练时保持一致 self.transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def load_cnn(self, path): # torch.save保存整个模型对象时,这里直接load # 如果保存的是state_dict,需要先实例化模型结构再load_state_dict model = torch.load(path, map_location='cpu') return model def load_dt(self, path): return joblib.load(path) def predict(self, image_path, manual_features): # 第一步:CNN识别图片属于哪一类 img = Image.open(image_path).convert('RGB') tensor = self.transform(img).unsqueeze(0).to(self.device) with torch.no_grad(): logits = self.model(tensor) pred_idx = torch.argmax(logits, dim=1).item() cnn_class = self.class_names[pred_idx] # 第二步:决策树根据人工特征判断最终垃圾桶 manual_features = np.array([manual_features]).reshape(1, -1) final_category = self.dt.predict(manual_features)[0] return cnn_class, final_category def predict_proba(self, image_path): # 输出每个类的置信度,调试和答辩时很有用 img = Image.open(image_path).convert('RGB') tensor = self.transform(img).unsqueeze(0).to(self.device) with torch.no_grad(): logits = self.model(tensor) probs = torch.softmax(logits, dim=1).squeeze().cpu().numpy() return dict(zip(self.class_names, np.round(probs, 4)))

逻辑说明:这个类的init方法在初始化时就把两个模型加载进内存,避免每次预测重复读盘。predict方法串起两个模型:先让CNN输出物料类别,再让决策树基于manual_features输出最终是否可回收。predict_proba则把CNN的softmax结果转成字典,便于查看模型到底在犹豫什么。

参数说明:cnn_path和dt_path分别是两个模型的存放位置,class_names的顺序要和torchvision.datasets.ImageFolder读取类别时的顺序一致;manual_features是长度为特征数量的数组,顺序必须和决策树训练用的CSV列顺序一致。map_location='cpu'是一个保护性设置,即使模型是在GPU上训练的,也能在无CUDA的机器上加载。

3. CNN图像分类:用迁移学习快速训练一个可用模型

3.1 环境准备:先确认Python和PyTorch版本一致

CNN部分最大的坎不是算法,而是环境。很多同学卡在import torch直接报错,或者装了torch却和numpy版本冲突。如果你还在找python安装教程,这里直接给你一套稳妥的命令:用conda建独立环境,不要在系统Python里乱装,不然一个项目搞崩全局环境是常事。

# 创建独立环境,Python 3.8在旧课程设计代码里兼容性最好 conda create -n garbage python=3.8 conda activate garbage # 安装CPU版PyTorch,不需要GPU也能跑通整个项目 pip install torch torchvision # 决策树和数据处理需要的库 pip install scikit-learn pandas matplotlib joblib

参数说明:Python 3.8不是越新越好,很多课程设计旧代码在3.10以上会遇到dataloader或torchvision API变动问题;3.8最稳。torch和torchvision版本要匹配,如果自己装,建议直接参考PyTorch官网给的pip命令。如果机器有NVIDIA显卡,把pip换成对应CUDA版本即可,但课程设计项目用CPU版就够了,训练ResNet18十几分钟能跑完一个epoch,10轮也就两小时,完全等得起。

3.2 从文件夹直接加载数据:torchvision.datasets.ImageFolder

CNN训练的第一步是把图片文件夹变成PyTorch的数据集。torchvision提供的ImageFolder就是干这个的:它把每个子文件夹当成一个类别,自动用文件夹名生成标签。下面是标准的加载和增强配置:

from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform = transforms.Compose([ transforms.Resize((224, 224)), # 统一尺寸,ResNet18输入要求224x224 transforms.RandomHorizontalFlip(p=0.5), # 轻量数据增强,减少过拟合 transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) # 训练集:data/train 下每个子文件夹名就是类别 train_dataset = datasets.ImageFolder(root='data/train', transform=train_transform) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=0) val_dataset = datasets.ImageFolder(root='data/val', transform=val_transform) val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)

逻辑说明:ImageFolder会扫描data/train下的子目录,按目录名的字母顺序排列类别。train_transform里的RandomHorizontalFlip和ColorJitter是做数据增强:垃圾图片翻转之后依然是原来的垃圾,所以这种变换不会破坏语义。ColorJitter调整亮度和对比度,是为了让模型对光照不敏感。val_transform则不做增强,只做缩放和归一化,保证验证集分布和实际测试一致。

参数说明:batch_size=32是ResNet18在CPU上比较稳妥的值,显卡不够或内存紧张时可调到16或8。num_workers=0在Windows上是安全选项,设成大于0经常触发DataLoader worker报错。Normalize用的mean和std是ImageNet预训练模型的标准值,迁移学习时不要改,否则预训练权重的输入分布就对不上了。

3.3 训练最小闭环:预训练ResNet18上手快

垃圾图片数据集一般不大,每个类别几十到几百张图,从零训练一个CNN容易过拟合,训练时间也长。常见做法是加载在ImageNet上预训练好的ResNet18,把最后一层全连接替换成自己的类别数,然后只做少量轮次的微调。这个办法收敛快、效果稳,答辩时也好解释。

import torch import torch.nn as nn import torch.optim as optim from torchvision import models device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) num_classes = len(train_dataset.classes) # 类别数 model.fc = nn.Linear(model.fc.in_features, num_classes) # 替换最后的全连接层 model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) epochs = 10 for epoch in range(epochs): model.train() running_loss = 0.0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}") # 保存整个模型对象,推理时直接load即可 torch.save(model, 'models/cnn_resnet18.pth')

逻辑说明:ResNet18的weights参数使用DEFAULT会加载ImageNet预训练权重。替换fc层时,in_features是ResNet18最后一层卷积输出的维度,新输出的维度等于类别数。CrossEntropyLoss内部自带softmax,所以前向传播得到的logits不需要再单独接softmax。训练循环里每一轮打一次平均loss,用来判断模型是否收敛。torch.save保存整个模型对象,好处是推理脚本不需要重建类定义,坏处是换机器时缺少模型定义文件会报错,后面避坑部分会细讲。

参数说明:lr=0.001是迁移学习的常见起始值,如果loss波动大可以把lr降到0.0001。epochs设为10,一般数据集到第5轮后准确率就趋于稳定。如果训练到最后一轮loss还在明显下降,说明模型还没收敛,可以加大epochs到20或30,但要关注验证集是否开始过拟合。batch_size=32配合Adam,在CPU上每个epoch大约十几分钟,整体时间可控。

3.4 训练时如何判断模型有没有学好

训练时只看loss曲线是不够的。loss下降说明模型在学习,但学习到的是不是“垃圾的特征”就不一定了:比如背景地板反光也可能被当成特征。所以每个epoch结束跑一次验证集准确率,比盯着loss实在得多。

def evaluate(model, val_loader): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() acc = 100 * correct / total print(f"Val Acc: {acc:.2f}%") return acc # 每个epoch结束后调用 # acc = evaluate(model, val_loader)

逻辑说明:验证时必须调用model.eval(),它把BatchNorm和Dropout层切换到推理行为。torch.no_grad()让反向传播不再计算梯度,显著降低显存和内存占用,同时防止推理状态被污染。torch.max(outputs, 1)返回每个样本的预测索引,和labels比较得到正确数。

参数说明:evaluate函数返回的是百分制准确率,建议训练时每个epoch都调用一次并记录,最后画到一张图上。如果训练集准确率接近100%而验证集明显低,说明过拟合了;如果两者都低,说明数据预处理或学习率有问题。垃圾图像数据量少时,验证集准确率在60%到70%之间也不稀奇,因为背景干扰太严重,这种情况下面拦截bug部分有对应的处理思路。

4. 决策树分类:特征选择比调参更值钱

4.1 为什么选决策树:答辩时能画出来,调参才有抓手

在垃圾分类场景里,决策树处理的是结构化特征,不是图片本身。你可以把“材质、重量、可弯曲性、是否金属、是否厨余”这些字段看作一份垃圾体检表,决策树会学习出一组if-then规则。这正好弥补CNN的可解释性缺口:CNN给出的答案像黑匣子,而决策树可以画成一棵明明白白的树,每一步分裂都有业务含义。

对比KNN或随机森林,决策树在课程设计里更受欢迎,理由是它的分类边界能被可视化,答辩老师看到树图就能理解你的特征设计思路。随机森林虽然精度更高,但看不到单棵树的规则,解释性就弱了。如果你的说明文档能输出一棵可视化决策树,再附上几个分支上的规则文字,整个项目的完成度会直接上一层。

4.2 特征表怎么构造:定义一份你自己的“垃圾体检表”

决策树输入的特征必须是人能看懂、能手动测量的字段。如果特征只有图片像素,决策树基本学不动。下表是一份常见特征定义,你可以根据源码里的CSV文件调整列名。

特征名类型取值范围/示例说明
material数值0=塑料, 1=玻璃, 2=纸, 3=金属, 4=厨余物料编码,通常由CNN提供或人工输入
weight数值单位克,如20垃圾重量
can_bend数值0=不能, 1=能可弯曲性,塑料瓶能弯,玻璃不能
is_metal数值0/1是否金属材质
is_food数值0/1是否厨余/食物残渣
recyclable标签0=不可回收, 1=可回收目标变量,决策树要预测的结果

特征不要贪多。课程设计数据量小,特征太多反而让决策树分裂出很多噪音分支。五六个肉眼可辨的特征足够了。如果你拿到的源码里还有“透明度”“气味”这种列,也要保证特征值方便填写,否则演示时没法快速输入。特征列的顺序必须写死在说明文档里,推理时传给manual_features的数组顺序必须和这里一致。

4.3 训练一棵可见的树:参数设置与可视化

决策树的训练代码很短,但过拟合是最大问题。我用sklearn的DecisionTreeClassifier训练时,几乎每次都要限制树深,否则训练集准确率永远100%。下面是标准训练和可视化流程:

import pandas as pd from sklearn.tree import DecisionTreeClassifier, plot_tree from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt import joblib # 假设garbage_features.csv里有已经清洗好的特征表 df = pd.read_csv('data/garbage_features.csv') X = df[['material', 'weight', 'can_bend', 'is_metal', 'is_food']] y = df['recyclable'] # 决策树非常容易过拟合,必须限制深度和叶子节点规模 clf = DecisionTreeClassifier( criterion='gini', # gini是默认分裂标准,一般不用改 max_depth=5, # 最大深度5层,防过拟合 min_samples_split=5, # 内部节点分裂所需最少样本数 min_samples_leaf=3, # 叶子节点最少样本数 random_state=42 # 固定随机种子,保证可复现 ) clf.fit(X, y) # 可视化:这是决策树模型的核心卖点 plt.figure(figsize=(12, 8)) plot_tree(clf, filled=True, feature_names=X.columns.tolist(), class_names=['不可回收', '可回收']) plt.savefig('docs/decision_tree.png', dpi=150) # 保存模型,推理时用joblib.load加载 joblib.dump(clf, 'models/decision_tree.pkl')

逻辑说明:fit之前用train_test_split拆出测试集也可以,但课程设计数据量小,常见做法是直接把全部数据fit进去,再用测试集单独验证。plot_tree会把分裂条件、样本数、类别纯度画在树上,filled=True用颜色区分类别,保存的PNG直接贴进文档。joblib.dump保存的是模型文件,比pickle更快更简洁。

参数说明:max_depth是决策树防过拟合的第一道防线,设为5意味着最多4次分裂,对于五六个特征的数据已经足够。min_samples_split和min_samples_leaf从样本数角度约束分裂:内部节点至少要有5个样本才允许分裂,叶子节点至少要有3个样本,防止树把每条数据都记住。random_state设为42是个人习惯,固定后每次训练得到同一棵树,答辩复现时不会翻车。

4.4 把CNN输出变成决策树输入:特征怎么拼

系统演示时,用户很可能只上传一张图片,不会手动填特征表。这时候需要把CNN的输出转成决策树的输入。最简单的做法是让CNN识别出物料类别,然后把这个类别转成material特征,其他特征设默认值。更高级一点的版本是CNN输出一个概率分布,把每个类别的置信度都作为决策树特征,但那样会让树图变得难懂,课程设计一般不做。

predictor = GarbagePredictor( cnn_path='models/cnn_resnet18.pth', dt_path='models/decision_tree.pkl', class_names=['glass', 'paper', 'plastic', 'metal'], feature_names=['material', 'weight', 'can_bend', 'is_metal', 'is_food'] ) cnn_class, final = predictor.predict( 'data/test/plastic_bottle.jpg', manual_features=[1, 20, 1, 0, 0] # 材质=塑料,20g,可弯曲,非金属,非厨余 ) print('CNN识别为:', cnn_class) print('系统判定为:', '可回收' if final == 1 else '不可回收')

逻辑说明:这里手动给了material=1,表示塑料,和CNN识别结果可以相互印证。如果CNN把图判成metal而manual_features里material=1,就出现矛盾,系统最终以决策树输出为准。这种“图片粗分类+人工特征细判”的方案演示起来很直观:你先给老师看CNN的概率条形图,再说决策树依据哪些特征给出了最终结论。

参数说明:manual_features的五个值对应feature_names里的五列。第一个1是material编码,第二个20是重量,第三个1表示可弯曲,第四个0是非金属,第五个0是非厨余。实际使用中,如果只有图片没有人工特征,一种常见做法是把CNN预测类别对应的编码填到第一个位置,其他四个位置设为默认值,让决策树正常输出。

5. 避坑与排查:5个让新手翻车的真实场景

5.1 训练CNN时loss纹丝不动,准确率一直在25%附近

现象:训练十几个epoch,loss在2.0到2.3之间波动,准确率维持在随机水平。

原因:最常见的是类别标签和数据对不上。比如data/train下建了“可回收”“不可回收”两个文件夹,但ImageFolder认为每个文件夹是第一层类别,却不管文件夹里的图片是什么材质。另一个高频原因是图片中有大量背景噪声,模型学到的是背景边缘而不是垃圾本体。

解决:先打印train_dataset.classes,确认文件夹名是不是你想要的“glass、paper、plastic、metal”。然后随机抽查每个类别下有没有混进其他类别的图片。如果数据量小,优先减小数据增强强度,把ColorJitter去掉只保留Resize。背景干扰严重时,用torchvision的RandomResizedCrop替代Resize,强行让模型关注物体主体而不是四周环境。

5.2 中文路径导致图片读取不到

现象:在Windows下运行,PIL的Image.open报FileNotFoundError,但文件路径看起来完全正常。

原因:很多课程设计数据集是从网上下载的,文件夹名带中文,比如data/训练/玻璃/001.jpg。Windows的Python默认文件编码不是UTF-8,torchvision和PIL在读取中文路径时经常失败。

解决:把项目根目录、数据集所有目录改成英文,例如glass、paper、plastic、metal。如果你拿到的zip里已经全是中文文件夹,写一个重命名脚本一次性改掉,别在Windows资源管理器里手动改名,容易漏掉嵌套目录。改完路径后检查predict.py里的模型路径和图片路径是否同步更新,否则还是白搭。

5.3 决策树训练集100分,测试集却不及格

现象:fit之后训练集准确率100%,自己拿几张新图片测试,结果一半都错。

原因:决策树把每个训练样本都记成了叶子节点,完全没有泛化。特征数量少、树深度没限制的时候,树会长到几十层深,每个分支只覆盖一个样本。

解决:三个参数必调:max_depth=5、min_samples_split=5、min_samples_leaf=3。如果调整后训练集准确率降到80%左右,而测试集准确率恢复正常,这就说明泛化能力回来了。上面提到的交叉验证也值得做,但课程设计里只要让测试集准确率说得过去就行。还压不住过拟合时,可以把决策树换成随机森林做对比实验,在文档里多一段“改进方向”本身就是加分项。

5.4 加载pth时报错:No module named / Can't get attribute

现象:推理脚本里torch.load('models/cnn_resnet18.pth')报错,说找不到某个类,或者提示Can't get attribute 'GarbageCNN'。

原因:保存时用了torch.save(model),把整个模型对象和它所在的模块引用一起存进去了。如果训练脚本里的模型类定义没有被推理脚本引入,Python就找不到这个类,无法反序列化。

解决:优先改用state_dict方式保存。训练脚本里写成torch.save(model.state_dict(), 'models/cnn_weights.pth'),推理脚本里先用同样结构的模型实例化,再model.load_state_dict(torch.load('models/cnn_weights.pth', map_location='cpu'))。这样模型文件不携带模块信息,换机器、换目录结构都不会因模块名报错。如果拿到的包已经是旧版整个对象保存,你需要把定义模型的代码片段找出来,在推理脚本里原样复制一份,再import一下。

5.5 运行predict.py提示ModuleNotFoundError

现象:双击运行或命令行执行predict.py,报No module named 'torch'、No module named 'joblib'。

原因:鬼知道你用的是哪个Python解释器。最常见的是conda环境没激活,或者pip装到了系统Python而IDE用的是虚拟环境。还有这种情况:项目里的requirements.txt没列全依赖,别人复制给你时漏掉了关键库。

解决:先在项目根目录执行pip install -r requirements.txt,没有这个文件就按前面环境清单逐个安装。装完再在运行前确认解释器路径,Windows下可以在IDE右下角切换解释器,命令行里用conda activate garbage激活环境。GPU训练过的模型在CPU机器上加载时还要注意map_location参数,否则会报CUDA device相关错误。

6. 进阶:用验证集和混淆矩阵,把“能跑”变成“能讲”

课程设计里的“能跑”只算基础分,答辩时的加分项是你能说出模型哪里好、哪里不好。建议把CNN和决策树都放到测试集上做一次正式评估,并生成混淆矩阵和分类报告。下面这段代码适用于CNN部分:

from sklearn.metrics import classification_report, confusion_matrix y_true, y_pred = [], [] test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False) for images, labels in test_loader: images = images.to(device) with torch.no_grad(): outputs = model(images) _, preds = torch.max(outputs, 1) y_true.extend(labels.cpu().numpy()) y_pred.extend(preds.cpu().numpy()) print(classification_report(y_true, y_pred, target_names=test_dataset.classes)) print(confusion_matrix(y_true, y_pred))

这段代码输出每个类别的精确率、召回率,以及一个行列对应的混淆矩阵。如果纸类被大量误判成塑料,说明这两类在纹理和颜色上太接近,你可以回到数据增强,把ColorJitter的饱和度变化调大一点,或者增加几类合成图片。决策树那边直接用sklearn的classification_report打印就行,注意把类别名传进去。评估完之后,保存CNN的概率条形图和决策树树图,答辩时把这两张图放上去,比讲一堆loss曲线更有说服力。

我个人的习惯是,每做完一个项目都留一个可复现的目录结构,所有模型文件统一用英文路径保存,所有加载代码统一写map_location='cpu',所有随机种子统一固定。这套习惯帮我少踩了很多坑,尤其是ppt演示当场报错的时候。希望这次的拆解和踩坑记录能让你少走弯路,把这份课程设计真正做成自己的东西。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询