☰
场景语义分割毕设指南:从模型选型到mIoU调优实践
2026/10/7 18:33:53 网站建设 项目流程

简介:一份面向计算机视觉方向毕设与课程作业的深度学习场景语义分割项目资源包。内容聚焦图像像素级分类任务,覆盖从数据准备、模型设计、训练优化到评估部署的完整流程,适合需要搭建可复现语义分割系统的学生开发者参考。压缩包共二十四个文件,以Python脚本为主(约十个),包含模型训练与测试、数据加载及预处理工具;另有若干XML工程配置、TXT说明与PNG效果图,便于快速定位项目结构与运行结果,整套资源仅820KB,轻量易用。已有158人浏览学习。通过该资源可入手一套带工程配置的语义分割代码框架,包括数据迭代器、网络模块、训练入口与结果示例,帮助理解Encoder-Decoder等架构的落地实现,兼顾Python实验与C++部署的工程化思路,同时为毕设答辩或课程设计报告提供代码与结果支撑。

1. 毕设选场景语义分割:值得投入的像素级分类硬门槛

如果你的毕设和课程作业落在“深度学习+场景语义分割”,恭喜,这是一个下限不低、上限足够高的方向。所谓场景语义分割,就是把一张照片里每个像素都归类到某个语义类别里,比如道路、行人、车辆、天空,核心产出是一张和输入分辨率一致的分割图。相比图像分类只回答“这是什么”,语义分割回答的是“每个像素是什么”,这也是自动驾驶、遥感解译、医学影像、工业质检里真正能落地的技能点。

很多同学拿到这个选题的第一反应是“是不是只要把分类网络改成FCN就能跑”。确实能跑,但跑到能写进毕设的水平,中间隔着数据集组织、损失函数调整、mIoU指标计算、显存优化这些具体工程问题。这篇笔记我按自己做过几轮分割项目的流程来讲:先选型和确定指标,再写数据管道,然后跑训练并验证,最后给出一份避坑清单和答辩前的调优套路。适合把毕设从“能跑通”做到“能讲清楚、能演示、指标拿得出手”的从业者和学生。

2. 场景语义分割的模型选型与方法基底:从FCN到DeepLabV3+的权衡

2.1 先搞清楚任务边界:像素级分类和mIoU这个指标

场景语义分割本质上是一个逐像素分类任务:输入是 HxWx3 的彩色图,输出是 HxW 的类别索引图,每个位置上的数值代表该像素属于哪一类。所以模型结构的最后一层必然是空间维度不变、通道数等于类别数的卷积或上采样输出,配合 softmax 或 argmax 得到结果。

评价指标上,别被 Pixel Accuracy 误导。如果一张图里车辆只占 2% 像素,全预测成背景也能拿到 95% 以上的像素准确率,这在毕设答辩里一问就塌。要做就盯 mIoU:它是每个类别的交集除以并集的平均。公式上,对类别 i,计算预测为 i 且真实也为 i 的像素数(TP),预测为 i 但真实不是 i 的像素数(FP),真实为 i 但没被预测出来的像素数(FN),IoU_i = TP / (TP+FP+FN),最后对所有类别取平均。每类 IoU 都公平参与平均,小目标类和大背景类等权,这样你的模型才会真的去学“细小的行人和远处的交通灯”,而不是只学一个“大面积天空”。这也是为什么 mIoU 几乎成了语义分割论文和比赛的默认口径。

顺带说,多类别还有 per-class accuracy、F1 这些二级指标,但对毕设来说,主表放 mIoU,辅表放每类 IoU 看差异,就足够撑起分析章节了。指标计算我建议直接用混淆矩阵来求,间接又不易出错,第四章会给完整代码。

2.2 模型选型的四个梯队:不追新,选能解释的

语义分割模型迭代很快,但毕设场景我不建议一头扎进最新论文复现。选型标准依次是:现有预训练权重好不好找、训练成本能不能压住、网络结构能不能用一两页 PPT 讲清楚、复现别人的分数有多大把握。

常见选型梯队大概是这样:

梯队代表作结构特点毕设适用度
全卷积祖辈FCN-32s/16s/8s分类骨干换成卷积输出,上采样回原尺寸适合讲原理,做 baseline
编码器-解码器U-Net、SegNet对称跳连或池化索引恢复细节U-Net 在医学/工业场景好用
空洞卷积家族DeepLabV2/V3/V3+、PSPNet空洞卷积扩大感受野,多尺度池化/ASPPDeepLabV3+ 最稳妥,论文和代码都多
新式混合体Swin-UNet、SegFormer、Mask2FormerTransformer 或掩码机制效果好,但训练和调参门槛高,解释性略难

我一般给环境一般、重点是毕业设计要出结果的建议是:baseline 用 FCN-8s 或 U-Net,主模型用 DeepLabV3+,backbone 选 ResNet50,这种组合在 PASCAL VOC 上能拿到不错的 mIoU,且加载 ImageNet 预训练的 ResNet50 后收敛快得多。如果你做的是遥感或者医疗数据这类小数据集,U-Net 家族反而更占优势,因为它们不需要超大预训练,训练所需 epoch 也少。

这里有个选型上的关键判断:DeepLabV3+ 的“好”主要来自 ASPP(空洞空间金字塔池化),它用多个不同膨胀率的空洞卷积并联,对同一特征图以不同感受野采样,能把多尺度物体的特征都抓一遍。这个模块在答辩时非常好讲,你把膨胀率画成几个并列分支,老师们一眼就明白。PSPNet 的思路类似,用的是金字塔池化做全局上下文融合,也很经典。选一个深入吃透,比两个都浅尝辄止强得多。

2.3 损失函数配置:交叉熵打底,类别不平衡再上Focal

默认损失函数就是交叉熵,PyTorch 里一句 CrossEntropyLoss 就够了。它在计算时对每个像素的预测概率求负对数,底层已经结合了 softmax,所以模型输出的 logits 直接传进去即可,不要画蛇添足自己再 softmax 一次,数值会过一遍再变差。

交叉熵在类别大体均衡时没问题,但场景里车辆、行人这类小目标天然像素占比少,损失会被天空、道路这类大类主导。如果你看一下训练日志发现小类别 IoU 特别低,常见做法是换 Focal Loss。它的核心改动是给每个像素的损失加一个调制因子 (1-p_t)^γ,预测置信度越高、损失贡献越低,让模型把注意力引向难分类的像素。在 Cityscapes 这种类别极不平衡的数据集上,γ 取 2.0 是比较常见的起点。

还有一个对症损失的加分项是 dice loss 或 Lovasz-Softmax,它们直接近似 IoU 这类指标做优化,对小目标和边缘有一定改善。代价是收敛波动更大,且对学习率敏感。我的个人建议是:课程作业用纯交叉熵;毕设如果分析点不够,可以加一组“交叉熵 vs 交叉熵+Focal”的对比实验,这比盲目切换损失函数更安全。

# 类别不均衡时,给CrossEntropyLoss传入类别权重也非常直观 import torch.nn as nn # 假设有21类,用训练集各类像素频次做负对数归一化作为权重 class_counts = torch.tensor([1000, 20, 50, 5], dtype=torch.float) # 示意数据 weights = 1.0 / (class_counts + 1e-6) weights = weights / weights.sum() * len(class_counts) # 归一化到数量级一致 criterion = nn.CrossEntropyLoss(weight=weights, ignore_index=255)

这段代码里ignore_index=255是分割任务的标配参数,它表示标注图中数值为 255 的像素不参与损失计算,通常是边界、裁切到图像外的区域或者不想监督的难标注像素。这个参数在 Dataloader、损失函数、mIoU 计算里要保持一致,否则你会在验证时发现分数不错但可视化里预测把边界全糊了。损失函数和指标计算的一致性,是语义分割里最容易翻车也最容易被马虎带过的点。

3. 数据集组织与Dataloader实现:标注图不能随便resize的细节

3.1 数据集选型:VOC与Cityscapes怎么选,目录怎么摆

场景语义分割最容易起步的公开数据集是 PASCAL VOC 2012 增强版,21 类(含背景),分辨率不高但类别典型,标注规范,网上预训练模型、调参资料最多。想挑战更高难度就上 Cityscapes,2975 张训练图,19 类街景,分辨率大、类别细,但训练速度慢很多。如果导师给的是自采数据或者遥感、医疗数据,那就按你自己的数据来,但格式最好也转化为下面这个目录结构,方便直接用现成 Dataloader。

常见的数据集根目录组织方式是这样的:

dataset/ ├── JPEGImages/ # 原始RGB图像,统一命名如 000001.jpg ├── SegmentationClass/ # 语义分割标签,PNG格式,像素值为类别索引 ├── ImageSets/ │ └── Segmentation/ │ ├── train.txt # 训练列表,每行一个不带后缀的文件名 │ ├── val.txt │ └── trainval.txt

一个容易踩的规矩是:原始图可以用 jpg 压缩,但标签图必须存成无损的 PNG。因为分割标注是逐像素类别索引,如果标签也用 jpg 压缩,压缩产生的伪影会在物体边缘产生大量错标像素,训练出的模型边缘全是脏的。另外,如果自己制作数据,用 LabelMe 或 Paddle 标注工具导出的 json 要转成 PNG 掩膜,转的时候确认类别索引从 0 开始连续编号,别出现空洞的类别序号,否则 CrossEntropyLoss 的类别数会多算,模型输出维度也对不上。

PDF 版数据集协议偶尔会把未标注区域标成 255,这就是前面说的 ignore_index 存在的意义。打开标签图后可以先统计像素值分布,看看有没有超出类别数的值,通常在 0 到num_classes - 1和 255 之间才是合法的。

3.2 手写一个同步变换的Dataloader:flip、random crop和resize的坑

语义分Dataloader 和分类任务最大的不同在于,对输入图像做的大小变换、翻转、旋转,必须对标签图做完全一致的变换。很多同学第一次写分割训练代码,拿现成分类 Dataloader 改,把图像的resize做了,标签还是原尺寸,于是 CrossEntropyLoss 形状不匹配直接报错,或者 torchvision 的某些 transform 随机操作在图像和标签上各随机一次,导致分割图和标签错位,模型在训练集上就学不出来,只涨 loss 不掉。这类问题防不胜防,我养成的好习惯是:图像和标签永远走同一个随机种子,在一个函数里完成同步变换。

import cv2 import numpy as np import torch from torch.utils.data import Dataset class SegDataset(Dataset): def __init__(self, img_dir, label_dir, img_list, num_classes, crop_size=(512, 512), is_train=True): self.img_dir = img_dir self.label_dir = label_dir self.img_list = [line.strip() for line in open(img_list)] self.num_classes = num_classes self.crop_size = crop_size self.is_train = is_train def __len__(self): return len(self.img_list) def sync_transform(self, img, label): # 两个图共用同一个随机种子,保证几何变换完全一致 seed = np.random.randint(0, 1_000_000) # 随机水平翻转 if self.is_train: np.random.seed(seed) if np.random.random() < 0.5: img = cv2.flip(img, 1) label = cv2.flip(label, 1) # 随机缩放,模拟远近变化 np.random.seed(seed + 1) scale = np.random.uniform(0.5, 2.0) h, w = img.shape[:2] new_h, new_w = int(h * scale), int(w * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR) label = cv2.resize(label, (new_w, new_h), interpolation=cv2.INTER_NEAREST) else: img = cv2.resize(img, self.crop_size[::-1], interpolation=cv2.INTER_LINEAR) label = cv2.resize(label, self.crop_size[::-1], interpolation=cv2.INTER_NEAREST) return img, label def __getitem__(self, idx): name = self.img_list[idx] img_path = f"{self.img_dir}/{name}.jpg" label_path = f"{self.label_dir}/{name}.png" img = cv2.imread(img_path, cv2.IMREAD_COLOR) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) label = cv2.imread(label_path, cv2.IMREAD_GRAYSCALE) img, label = self.sync_transform(img, label) # 若是训练,再做随机裁剪;若是验证,中心裁剪或直接resize亦可 if self.is_train: seed = np.random.randint(0, 1_000_000) np.random.seed(seed) h, w = img.shape[:2] crop_h, crop_w = self.crop_size if h >= crop_h and w >= crop_w: top = np.random.randint(0, h - crop_h) left = np.random.randint(0, w - crop_w) else: top, left = 0, 0 img = img[top:top+crop_h, left:left+crop_w] label = label[top:top+crop_h, left:left+crop_w] img = img.astype(np.float32) / 127.5 - 1.0 # 归一化到[-1,1] img = torch.from_numpy(img).permute(2, 0, 1).contiguous() label = torch.from_numpy(label).long() return img, label

这里有几个直接影响效果的参数。resize 标签图时插值必须用cv2.INTER_NEAREST,也就是最邻近插值,不能贪方便用 LINEAR,否则会在类别边界上插出不存在的类别值,相当于给标注图注入噪音。随机缩放的范围我设置为 0.5 到 2.0,对街景类场景比较合适;如果目标特别小、物体密集,可以把下限降到 0.3。另外,归一化用 ImageNet 统计(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])更规范,但一般配合预训练 backbone 使用;无预训练时可以随意一点。

if __name__ == "__main__": from torch.utils.data import DataLoader ds = SegDataset("dataset/JPEGImages", "dataset/SegmentationClass", "dataset/ImageSets/Segmentation/train.txt", num_classes=21, crop_size=(512, 512), is_train=True) loader = DataLoader(ds, batch_size=4, shuffle=True, num_workers=2) for img, label in loader: print(img.shape, label.shape, img.dtype, label.dtype) # 打印输出维度确认 break

运行这段检查脚本,你会看到torch.Size([4, 3, 512, 512])和torch.Size([4, 512, 512]),标签不带通道维,这一点在把输出 logits 与 label 做损失计算时尤其重要。至于shuffle,训练必开,验证集改成False;num_workers在 Windows 上建议设 0 或 1,Linux 上可以给到 4 以上,否则有时会莫名卡死。

4. 训练配置与mIoU验证代码:损失下降不等于指标上涨的完整流程

4.1 训练循环:从pretrained backbone加载到学习率设置

模型部分我用 DeepLabV3+ 的常见实现思路来说明,ResNet50 做 backbone,ASPP 模块接上采样。训练时有一个重要习惯:backbone 用 ImageNet 预训练,并且在前几个 epoch 冻结它,或者给不同模块分配不同学习率。原因在于,分割模型的 decoder 是随机初始化的,如果从一开始就全参数一起训练,随机初始化的 decoder 会产生很大梯度,把 backbone 的预训练特征直接冲乱。

import torch import torch.nn as nn from torch.utils.data import DataLoader # 以segmentation_models_pytorch为例,这是一个封装好的分割库,常见且易跑通 import segmentation_models_pytorch as smp model = smp.DeepLabV3Plus( encoder_name="resnet50", encoder_weights="imagenet", in_channels=3, classes=21, ) # 冻结backbone,先只训练decoder几个epoch,再解冻 for p in model.encoder.parameters(): p.requires_grad = False optimizer = torch.optim.AdamW( [{"params": model.encoder.parameters(), "lr": 1e-5}, {"params": model.decoder.parameters(), "lr": 3e-4, "weight_decay": 1e-4}], ) scheduler = torch.optim.lr_scheduler.PolynomialLR(optimizer, total_iters=50, power=0.9) criterion = nn.CrossEntropyLoss(ignore_index=255)

这段配置里,encoder 学习率给 1e-5,decoder 给 3e-4,这就是常见的差分学习率策略。冻结阶段跑 2 到 3 个 epoch 后把requires_grad全部置回True,再完整训练。PolynomialLR是许多分割项目常用的衰减方式,learning rate 从初始值按幂函数一路降到 0,比 StepLR 的断层式下降更容易把 mIoU 拉到高位。第一次跑分割任务,我建议直接用 AdamW 而不是 SGD,少操心动量参数,后面想刷分再换 SGD + Momentum。

显存方面,512×512 输入、batch_size=4、ResNet50 的 DeepLabV3+ 在 8GB 显存上勉强能跑,6GB 就要把 batch 降到 2 或者 crop 改成 480。如果显存还是爆,加梯度累积即可:optimizer.zero_grad()隔几步做一次,等价于变大 batch 的效果。

训练主循环本身不复杂,但要把验证穿插进来。

def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss = 0.0 for i, (imgs, labels) in enumerate(loader): imgs = imgs.to(device) labels = labels.to(device) logits = model(imgs) # 输出形状 [B, 21, H, W] loss = criterion(logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() if i % 20 == 0: batch_norm = labels.shape[0] print(f" step {i}, loss {loss.item():.4f}") return total_loss / len(loader)

每次迭代打印 loss 而不是等整个 epoch 结束再打印,这样能尽早发现梯度爆炸或 loss 恒定的问题。如果你的 loss 一开始就在 0.7 附近不动,先确认是不是冻结策略生效了:全冻结时 decoder 随机初值学得慢,loss 下降缓慢是正常的;如果解冻后 loss 还是不降,多半是学习率太低或数据管道错位了。

4.2 验证与mIoU计算:混淆矩阵才是最可靠的算法

训练损失下降很快,不代表 mIoU 就好。原因很简单,交叉熵是逐像素求平均的,模型学会把大面积道路猜对,loss 就能显著下降,但小目标仍然错漏。所以验证代码必须自己写,把每个类别的 IoU 分开算,再求均值。

def compute_miou(logits, labels, num_classes, ignore_index=255): # logits: [B, C, H, W], labels: [B, H, W] preds = logits.argmax(dim=1) # 取概率最大的类别索引 mask = (labels != ignore_index) # 过滤掉不参与评价的像素 labels_masked = labels[mask] preds_masked = preds[mask] # 建立混淆矩阵: 真实行, 预测列 conf_mat = torch.zeros(num_classes, num_classes, dtype=torch.int64) idx = labels_masked * num_classes + preds_masked conf_mat = torch.bincount(idx, minlength=num_classes * num_classes).reshape( num_classes, num_classes ) ious = [] for cls in range(num_classes): tp = conf_mat[cls, cls].item() fp = conf_mat[:, cls].sum().item() - tp fn = conf_mat[cls, :].sum().item() - tp union = tp + fp + fn if union == 0: ious.append(float("nan")) # 该类别在验证集中没有像素 else: ious.append(tp / union) # 忽略nan类别,求所有存在类别的IoU均值 valid = [iou for iou in ious if not math.isnan(iou)] return sum(valid) / len(valid), ious

这段代码的核心是torch.bincount高效构建混淆矩阵,实际项目中几百张验证图、百万级像素,这个写法比两层 for 循环快得多。要说明的是nan处理:某个类别在验证图里没出现时,IoU 的union=0,直接跳过并参与后续平均。这个细节处理不当,会在验证集类别不全时拉低整体分数,造成“明明模型还行,报告里数值却很难看”的窘境。

验证流程建议每个 epoch 结束后完整跑一遍 val 集,把 mIoU、每类 IoU、像素准确率一起打印出来存档。如果你发现 mIoU 曲线在某个 epoch 后进入平台期,再去调学习率或数据增强,不要频繁改网络结构,因为一个改动的变量都没固定下来,后续分析很难写出有说服力的结论。

5. 语义分割五大翻车实录:数据、标签、显存与指标的现场排障

5.1 标签与图像异步变换:训练 loss 反复震荡,模型学不透

现象:训练 20 个 epoch 之后 loss 仍在 0.6 上下波动,验证集可视化结果里物体的轮廓与真实标注完全咬合不上,仿佛标签“慢半拍”。

原因:Dataloader 用 two-branch 方式分别处理图像和标签,RandomHorizontalFlip对图像随机翻转了,但标签没有同步;或者对标签做了普通 resize 而非最邻近插值。图像和实际监督信号错位,模型在不断学习冲突样本。

解决:把图像和标签放进同一个函数并用同一个随机种子做同步变换,且所有几何变换都在np.ndarray层面上完成,最后再转 Tensor。上面第三章的sync_transform设计就是为此而生的,别嫌封装麻烦,后期少排查好多问题。

5.2 显存不足:迁移学习在小显存卡上的常见解决方案

现象:batch_size=4、crop=512 时,模型加载即报 CUDA out of memory,或训练中间偶发 OOM 导致进程被杀。

原因:DeepLabV3+ 的 ASPP 并行分支带来足够大的中间激活,加上反向传播保留的梯度,显存峰值比前向计算高不少。

解决:最直接的降 batch_size 到 2 或 1。如果单卡只有 6GB,我会把 crop 缩到 480 或 400,这是语义分割训练里最有效的显存调节旋钮。另外,在torch.backends.cudnn.benchmark = True打开的情况下,很多固定输入尺寸的训练会有一定加速;还能顺手开启torch.cuda.amp混合精度,将梯度精度降到 fp16,显存占用能少三分之一左右。注意,BN 层在 batch_size=1 时统计量不稳定,这种情况要么换 GroupNorm,要么累加足够多的迭代再同步更新 BN 统计量。

5.3 类别不均衡导致小目标类 IoU 为 0,但整体 mIoU 看起来还行

现象:21 类里,公交车、自行车的 IoU 常年是 0,但模型总体 mIoU 有 0.6 以上,曲线图看起来还挺正常。

原因:这类像素在训练集里占比太少,交叉熵的梯度被背景和大类别淹没,甚至有些类别模型从未成功预测过一次。

解决:先统计训练集各类像素频次,确认哪些类像素占比低于 1%。处理顺序是:给损失函数加类别权重或者换 Focal Loss;如果两类目标实在太稀疏,考虑在数据增强里做“类别平衡采样”,也就是专门裁切那些包含目标小类的区域作为训练样本。在验证统计时,保留每类 IoU 单独打印而不要只输出平均 mIoU,这样才能定位是哪几类拖后腿。

5.4 ignore_index 漏传导致 mIoU 虚高或非法预测

现象:验证时 mIoU 很高,但可视化图中物体边缘像覆盖了一层白噪声;或者某些像素预测出了超高索引值,在源码中找到空白色块。

原因:标签里存在 255 的 ignored 区域,但计算 IoU 时没有过滤这些像素;或者标签里有空洞类序号,导致网络输出的类别数与损失函数类别数错位。

解决:在compute_miou里对标签和预测做掩膜过滤,保证混淆矩阵只统计有效类别;同时每次数据集加载后打印标签像素值分布,确认 min/max 都在[0, num_classes-1] ∪ {255}区间内。这个问题写进毕设的话可能被答辩老师当场指出来,所以务必在实验记录里存一张标注意外值统计图作证。

5.5 验证集上 mIoU 上升停滞,但训练集 IoU 很高

现象:训练集 mIoU 到 0.75,验证集只在 0.68 停滞;epoch 增加后验证不再上升,甚至下降一点。

原因:训练过拟合是常见原因,另外还有数据增强不足导致模型在验证集新场景上泛化弱。

解决:先提升数据增强强度,比如引入 color jitter、随机遮挡(CutMix 对分割不常用来,但随机擦除是可行的)、多尺度测试。然后适当增加 dropout 或在 decoder 中加入 Spatial Dropout。最后,如果验证集是不同分布的拍摄数据,这时再考虑换更大 backbone 或加入简单域适应策略,但毕设阶段把数据增强和正则做足,多数情况能撑住。

6. 毕设答辩前的调优三件事:可视化、多尺度推理与bad case分析

到了最后阶段,别再盲目调参了。拿已在验证集上跑好的模型,做三件看得见、能写进论文的事情。

第一件是把预测结果叠加到原图上,生成红绿蓝或随机彩色掩膜,以半透明方式与原始图混合。这比单独展示一张分割图更能说明模型“找到物体了”。用cv2.addWeighted(pred_color, 0.6, img, 0.4, 0)一行实现,注意要先把类别索引映射成 BGR 色盘,再与原图融合。

第二件是多尺度推理的验证。把输入缩放到 0.75、1.0、1.5 倍分别送入模型取预测,把各尺度预测的 softmax 得分插值回原尺寸后取平均,再 argmax。这一操作在 Cityscapes 类高分辨率数据上通常能带来 1 到 3 个点 mIoU 的提升,且完全不需要重新训练。代价是推理时间倍增,所以毕设报告里可以把它作为“测试时增强”策略单独描述。

第三件是 bad case 分析。在验证集预测结果中按每类 IoU 从低到高排序,挑选 IoU 最低的两三类,把原图、标注图、预测图、错误像素热力图并列放成一张图,分析错误是出现在物体边界、小尺寸目标、还是遮挡区域。这类分析是毕设论文里的常见加分项,比全文贴一堆结构图更接地气。

最后想提醒的是,不要把整个项目处处都追求最优。我的习惯是固定一个能跑的配置后就不再频繁改动模型结构,把实验记录表、超参记录、对比曲线做好,这比反复刷一个指标更能撑住答辩席上的提问。希望这份方案能帮你把毕设的每一步都走扎实,也希望你真学到一个能带进工作的技能,而不是只留下一个解压后的文件夹。

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

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

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

立即咨询