ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

蘑菇类型识别检测数据集:VOC+YOLO双格式详解与YOLOv8训练实践

蘑菇类型识别检测数据集:VOC+YOLO双格式详解与YOLOv8训练实践 简介面向蘑菇识别与目标检测的深度学习标注数据包压缩包内共2000个文件主体为1999个Pascal VOC格式的XML标注文件并附1份使用说明MD整体大小约193.56MB。标注覆盖21个蘑菇类别每个XML文件均按VOC标准记录目标边界框和类别标签可直接导入YOLO、Faster R-CNN等主流检测框架训练也便于进行数据清洗、格式转换与数据增强蘑菇品种间外观接近对模型细粒度特征辨别能力提出较高要求适合用于算法研究、模型调优及对比实验。该数据集整体上包含8430张JPG图片及对应YOLO格式TXT标注本次压缩包主要以VOC格式XML标注为主样本示例与详细说明可参考作者公开博文。目前已有430人学习下载适用于食品毒菌识别、生态调查等目标检测应用场景的开发者与研究人员选用。1. 蘑菇类型识别检测数据集VOCYOLO格式8430张21类别.7z双格式标注到底解决了什么当我第一次看到“蘑菇类型识别检测数据集VOCYOLO格式8430张21类别.7z”这个名字时最在意的不是 21 个类别而是“VOCYOLO”这五个字。很多公开检测数据集只给一种格式拿到之后还得自己写一堆 XML 转 txt 的脚本边转边担心坐标除错分母。这份数据集把两条路都铺好了VOC 的 XML 标注用来人工核对和二次加工YOLO 的 txt 标注可以直接喂给 YOLOv5、YOLOv8 这些主流框架。8430 张图对应 21 个类别图量不算大但类别粒度比“可食用/不可食用”这种二分类细得多放在迁移学习、细粒度识别和数据增强对比这三类任务上都很适用。它的价值不在于规模大而在于格式规整和类别划分清晰。新手能从零完整走一遍目标检测的数据流程老手可以拿它做长尾分布分析和模型鲁棒性实验不用再从标注开始折腾。2. 解压与格式解剖VOC 的 XML、YOLO 的 txt 以及两者间的换算关系处理这份数据的关键动作是先解压再校验后解剖。格式本身不复杂但很多人卡在解压完就急着训练后面对不上的坑全是前期埋下的。下面按顺序把目录结构、标签字段和换算公式一次说清。2.1 拿到 .7z 后的标准动作解压命令与完整性校验很多人拿到数据集压缩包第一件事就是双击解压然后在训练时报出各种莫名其妙的路径错误。我一般建议先在命令行里列出压缩包内部结构再动手解压这一步有现成命令可用# 1. 安装 7z 工具Debian/Ubuntu 系 sudo apt update sudo apt install -y p7zip-full # 2. 列出压缩包内部结构确认根目录层级 7z l 蘑菇类型识别检测数据集VOCYOLO格式8430张21类别.7z # 3. 完整解压保留目录层级 7z x 蘑菇类型识别检测数据集VOCYOLO格式8430张21类别.7z -o./mushroom_data“l”参数会打印压缩包内所有文件路径能提前看到是不是有一个总目录还是多个目录散在一层。解压的“x”参数和“-o”之间有个细节-o后面直接接输出目录路径中间加空格会被 7z 当成另一个参数这一步在 Windows 命令行上也是一样的规则。如果你在 Windows 上用图形界面 7-Zip建议还是切到 PowerShell 调命令行版本方便和后续训练脚本衔接 C:\Program Files\7-Zip\7z.exe x 蘑菇类型识别检测数据集VOCYOLO格式8430张21类别.7z -oD:\datasets\mushroom解压完成后不要急着删压缩包先跑一遍测试模式。7z t会逐个文件校验 CRC输出 Done 才说明压缩包完整如果中途出现某个文件 CRC 校验失败说明压缩源文件已经损坏重新解压多少次都没有用只能重新从下载源头拿一份。这一步很多人跳过后面训练到一半才发现图片解码报错再回头补数据就浪费不少时间。解压之后我习惯把三个目录的文件数量统计一下记录成一个文本文件。后面如果做数据增强或类别裁剪这个清单就是版本管理的依据。VOC 和 YOLO 两份标注必须分别统计两份数量对不上说明转换过程里有文件丢失属于高危信号。2.2 VOC 部分的目录与 XML 字段拆解VOC 格式沿用 PASCAL VOC 的根布局常见目录名是 Annotations、JPEGImages 以及可选的 ImageSets/Main。JPEGImages 装的是 8430 张图Annotations 装的是同名 XML。如果 ImageSets/Main 存在train.txt、val.txt 每行是不带扩展名的图片名训练划分由这些 txt 决定而不是由目录名决定。打开一个 XML 看核心信息其实只有几块size 里的 width 和 height 决定归一化分母object/name 是类别名bndbox 提供四个绝对坐标。其余字段多为冗余信息训练时用不到annotation filenamemushroom_0042.jpg/filename size width1280/width height720/height depth3/depth /size object nameclass_7/name bndbox xmin220/xmin ymin110/ymin xmax540/xmax ymax400/ymax /bndbox /object /annotation这段 XML 有两个核心注意点。第一一张图里出现多个目标就有多个 object 块顺序和 YOLO txt 的行顺序可以不一致两个格式之间不要按行号做关联。第二name 字段在 VOC 里是人读的字符串在 YOLO 里必须映射成 0 到 20 的整数。映射表一旦建好就要固定下来中途改排序会让模型学到的类别语义整体错位。因此拿到 VOC 目录后第一件事是把所有 XML 里出现的 name 去重并排序和 YOLO 的类别编号逐一核对。数据集如果自带 YOLO 格式直接拿 YOLO 的 names 顺序反向核对 VOC 更省事。我习惯先执行一行命令统计名称集合防止两个格式间出现错位grep -h name Annotations/*.xml | sort | uniq -c上面这条命令会列出全部类别名和出现次数。如果应该是 21 类这里多一个少一个都要查原因尤其是拼写差异比如“Amanita”和“amanita”会被当成两个类YOLO 标签里也会对应成两个编号造成类别数量虚增。2.3 YOLO 部分的目录结构与 VOC 到 YOLO 的换算公式YOLO 格式的目录和 VOC 不太一样没有 XML 和 ImageSets只有 images 和 labels 两棵子树。labels/train 下的 txt 通常每行只有五个数字看起来很简单但含义必须弄清7 0.418750 0.352083 0.250000 0.402778一行数据对应一个目标。五个数依次是类别 id、目标框中心点 x 坐标、目标框中心点 y 坐标、框宽度 w、框高度 h。注意最后两个是宽高不是右下角坐标不少人在自写可视化时把这个细节搞混画出来的框整体偏移。四个坐标都除以了图片宽高取值范围在 0 到 1 之间这样不同分辨率的图片才能共享同一套标签。从 VOC 的绝对坐标到 YOLO 归一化坐标换算公式固定为x_center ((xmin xmax) / 2) / image_width y_center ((ymin ymax) / 2) / image_height box_width (xmax - xmin) / image_width box_height (ymax - ymin) / image_height真正的细节在分母。x 方向除以图片宽y 方向除以图片高。看到有些历史脚本会把宽高互换造成标注整体错位训练时 mAP 一直卡在某个奇怪的值排查半天才发现是分母写反。转换脚本我一般这样写import xml.etree.ElementTree as ET from pathlib import Path def single_voc_to_yolo(xml_file: Path, class_map: dict) - list: tree ET.parse(xml_file) root tree.getroot() img_w int(root.findtext(size/width)) img_h int(root.findtext(size/height)) boxes [] for obj in root.findall(object): cls_name obj.findtext(name) if cls_name not in class_map: continue bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) x_center ((xmin xmax) / 2.0) / img_w y_center ((ymin ymax) / 2.0) / img_h box_w (xmax - xmin) / img_w box_h (ymax - ymin) / img_h boxes.append( f{class_map[cls_name]} f{min(max(x_center, 0.0), 1.0):.6f} f{min(max(y_center, 0.0), 1.0):.6f} f{min(max(box_w, 0.0), 1.0):.6f} f{min(max(box_h, 0.0), 1.0):.6f} ) return boxes脚本逻辑不复杂先从 size 节点读图宽高再遍历每个 object 块把 bndbox 的绝对坐标按公式归一化。class_map 控制最终类别编号建议在脚本外统一维护不要在函数里硬编码。这里做了越界钳制目的是防止边缘目标因为标注时的浮点误差出现 1.000001 这类越界值虽然 YOLO 训练时容忍度较高但验证脚本会把它当异常抛出来。未知类别名直接跳过而不是抛异常同时打印一行警告方便事后核对。3. 用这份数据集跑通 YOLOv8目录规范、数据切分和训练参数格式看清之后下一步就是让它跑起来。YOLOv8 是当前用这份数据最顺手的框架预训练权重自动下载命令行参数直观损失曲线和混淆矩阵都自动输出。下面按目录规范、切分脚本、训练参数三块讲。3.1 数据目录规范YOLOv8 如何读取图片与标签YOLOv8 加载自定义数据时只认一套规则images/train 下的图片与 labels/train 下的 txt 通过同名前缀对应目录结构关系靠 data.yaml 描述不再依赖 .names 文件或额外配置。所以最稳妥的做法是新建一个顶层目录把解压出来的图片和标签按 images、labels 两棵子树放好mushroom_yolo/ ├── images/ │ ├── train/ │ │ ├── mushroom_0042.jpg │ │ └── ... │ └── val/ │ ├── mushroom_0017.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── mushroom_0042.txt │ │ └── ... │ └── val/ │ ├── mushroom_0017.txt │ └── ... └── data.yaml标签和图片在 train 下必须一一对应多了或少了都会造成静默跳过多出的 txt 没有对应图片YOLOv8 不会报错只是不会用到少 txt 的图片训练时也不会被读进来。两种异常都很难从损失曲线上直接看出来所以切分脚本里一定要加数量和空文件双重校验。data.yaml 的写法如下path: /home/user/datasets/mushroom_yolo # 数据集绝对路径 train: images/train val: images/val nc: 21 names: 0: class_0 1: class_1 # 按数据集实际提供的 21 个类名顺序补全 # 顺序一旦固定训练和推理都要沿用同一份path 指向数据集根目录train 和 val 是相对路径。nc 是类别总数names 的索引和顺序必须和 labels txt 里的类别 id 完全一致。顺序错一位模型学到的类别语义就整体错位最终混淆矩阵看起来会有一整条副对角线异常这是这类双格式数据集最容易踩的隐形坑。3.2 训练集与验证集切分用脚本一次完成复制与校验有些打包好的数据自带划分有些没有。不管有没有我建议自己重新切一次按 8:1:1 分成 train、val、test 三段test 留到最后做盲测。这样训练过程中反复调参不会污染最终评估结果。切分脚本如下from pathlib import Path import random, shutil SEED 42 RATIO (0.8, 0.1, 0.1) # train, val, test random.seed(SEED) image_dir Path(mushroom_data/JPEGImages) label_dir Path(mushroom_data/labels_all) # 所有 txt 先平铺在一个目录 out_dir Path(mushroom_splitted) for split_name in [train, val, test]: for sub in [images, labels]: (out_dir / sub / split_name).mkdir(parentsTrue, exist_okTrue) images sorted(image_dir.glob(*.jpg)) random.shuffle(images) total len(images) n_train int(total * RATIO[0]) n_val int(total * RATIO[1]) split_map { train: images[:n_train], val: images[n_train:n_train n_val], test: images[n_train n_val:], } for split_name, imgs in split_map.items(): for img_path in imgs: txt label_dir / (img_path.stem .txt) if not txt.exists() or txt.stat().st_size 0: continue shutil.copy2(img_path, out_dir / images / split_name / img_path.name) shutil.copy2(txt, out_dir / labels / split_name / txt.name) print(f[{split_name}] {img_path.name})脚本逻辑先用固定随机种子打乱图片列表再按比例切成三段对每张图片找同名 txt缺失或空文件一律跳过。这里我用复制而不是移动原始数据保留一份后面重切或者补标注还有后悔药。固定种子保证两次运行结果一致调参时可以排除数据切分带来的随机性。如果希望验证集更充分把 RATIO 改成 (0.7, 0.2, 0.1) 即可。切完后最好再跑一次集合比对确认 images/train 和 labels/train 的 stem 集合完全相等。我一般用 Python 一行集合运算完成a {p.stem for p in imgs.glob(*)}, b {p.stem for p in labels.glob(*)}对比两个集合的差集。这个动作能挡住静默跳过避免训练集有效样本量悄悄缩水。3.3 训练参数yolov8n 起步imgsz、batch 与损失解读第一次跑这份数据我建议用 yolov8n 而不是 x。n 模型参数量小单卡训练速度快适合先验证标签和目录有没有问题确认流程通了再换大模型提精度。训练命令yolo train \ modelyolov8n.pt \ data/home/user/datasets/mushroom_yolo/data.yaml \ epochs150 \ imgsz640 \ batch32 \ device0 \ workers8 \ projectruns/mushroom \ namebase参数含义epochs150 对这个小数据集足够模型通常在 80 轮附近收敛imgsz640 是目标检测默认值如果数据里小目标占比高可以改成 960但显存占用会按平方上涨batch32 是否可行取决于显卡显存跑不起来降到 16workers8 是数据读取线程数Linux 下按 CPU 核数取 4、8、16 都可以。model 参数写 yolov8n.pt 时程序会从官方源自动下载预训练权重不需要手动准备。训练日志里每轮会输出三个损失值box_loss、cls_loss、dfl_loss。box_loss 代表框位置回归质量cls_loss 代表分类置信度dfl_loss 是边界框分布损失。前三轮出现大幅下降是正常的如果第一个 epoch 时 cls_loss 就停滞在某个值优先怀疑标签类别编号不连续或 names 顺序错位而不是模型结构问题。650 张图的量级150 轮跑完大概需要小半天具体耗时看 GPU 型号。提示如果只求快速验证数据流程可以先用 epochs20 跑一发损失能正常下降再拉长到 150 轮。这样能尽早发现标签问题避免浪费算力。4. 21 个类别的长尾分布样本不平衡和相似类别的处理8430 张图听起来不少但 21 个类别平摊每类只有约 400 张。现实里不会这么均匀多数数据集呈现明显的长尾分布最强类和最弱类可能差到十倍以上。如果不先统计训练时模型会在头部类别上过拟合尾部类别几乎学不到特征。这一章讲统计、增强和类别折叠三个动作。4.1 先用统计脚本看清各类别实例数量统计时要注意只看图片数量会骗人一张图里可能标了十个蘑菇另一张只标一个实例级统计才是建模依据。脚本如下from collections import Counter from pathlib import Path train_label_dir Path(mushroom_yolo/labels/train) counter Counter() empty_txt [] for txt_file in train_label_dir.glob(*.txt): lines [ l.strip() for l in txt_file.read_text(encodingutf-8).splitlines() if l.strip() ] if not lines: empty_txt.append(txt_file.name) continue for line in lines: parts line.split() if len(parts) 5: counter[int(parts[0])] 1 print(class_id: count) for cls_id in range(21): print(cls_id, counter.get(cls_id, 0)) print(empty txt count:, len(empty_txt))这段脚本遍历训练集所有 txt按第一列类别编号计数空文件单独记录。输出结果后重点看两类问题某个 class_id 数量极少甚至为零说明该类别在训练集中近乎缺席空 txt 数量如果超过总数的 1%说明数据清理不彻底。数量明显偏少的类别后续训练基本注定欠拟合要不要补数据、补多少都以这份统计为基准。统计完成后把数量少于 100 的类别标记为低资源类别。处理优先级最高的是这些类不是数量最多的类因为模型精度通常被尾部类拖累。4.2 针对蘑菇类别相似性的增强策略控制翻转与颜色扰动蘑菇检测和通用物体检测有个明显差异颜色和纹理是鉴别关键。YOLOv8 自带的增强默认会大幅扰动 HSV 通道对汽车和行人问题不大对蘑菇却很容易把红色菌盖调成灰褐色让模型只能靠形状分辨。所以训练参数里要手动压低颜色扰动保留 Mosaic 增强yolo train \ modelyolov8n.pt \ datadata.yaml \ epochs150 \ imgsz640 \ batch32 \ hsv_h0.005 \ hsv_s0.2 \ hsv_v0.2 \ degrees10 \ fliplr0.5 \ flipud0.0 \ scale0.3这里几个参数是用来控制数据增强强度的hsv_h 是色相偏移幅度设为 0.005 几乎不让颜色发生偏移hsv_s 和 hsv_v 各留 0.2允许轻微饱和度和亮度变化degrees10 只允许小角度旋转因为蘑菇菌盖形态随拍摄角度变化明显旋转过大反而制造错误样本flipud0.0 不做垂直翻转真实拍摄很少出现上下颠倒的蘑菇。scale0.3 控制尺度缩放模拟远近距离变化。如果某些低资源类别实在太少可以离线做复制粘贴增强把高资源类别目标抠出来粘贴到低资源类别的背景图中。对蘑菇这类近似椭圆的目标拷贝时要保留原始边缘并做 5% 到 10% 的随机缩放让粘贴后的目标大小呈现真实变化。不过这种操作容易产生边缘阴影和不自然像素过度使用会让模型学到粘贴痕迹我一般控制在新增样本总量不超过原样本的 30%。4.3 类别重映射把 21 类折叠成业务需要的粒度部署时 21 类往往过细常见需求是合并成 10 类或者直接变成二分类。类别重映射必须同步修改标签 txt 里的类别 id不能只改 data.yaml 的 names。如果只改 names 不改标签模型会把旧 id 对应到新位置训练完得到的是一张错位混淆矩阵。重映射脚本from pathlib import Path # 旧类别 id - 新类别 id按实际业务映射补全所有 0..20 MERGE_MAP { 0: 0, 1: 0, 2: 0, # 三个旧类合并为新 0 3: 1, 4: 1, 5: 1, # 三个旧类合并为新 1 6: 2, 7: 2, 8: 3, 9: 4, 10: 5, 11: 6, 12: 7, 13: 8, 14: 9, 15: 10, 16: 11, 17: 12, 18: 13, 19: 14, 20: 15, } def merge_label_files(source_dir: Path, target_dir: Path): target_dir.mkdir(parentsTrue, exist_okTrue) for txt in source_dir.glob(*.txt): new_lines [] for line in txt.read_text(encodingutf-8).splitlines(): parts line.split() if len(parts) ! 5: continue old_id int(parts[0]) if old_id not in MERGE_MAP: continue # 舍弃未映射类别 new_lines.append( f{MERGE_MAP[old_id]} f{parts[1]} {parts[2]} {parts[3]} {parts[4]} ) (target_dir / txt.name).write_text( \n.join(new_lines), encodingutf-8 ) merge_label_files(Path(labels/train), Path(labels_merged/train)) merge_label_files(Path(labels/val), Path(labels_merged/val))脚本逐行读取 txt把第一个字段替换成映射后的新 id其余四个坐标原样保留。MERGE_MAP 必须覆盖全部旧 id否则未映射的目标被丢弃训练集目标总数会减少。更新 data.yaml 时nc 从 21 改为新类别数names 列表同步变更。train 和 val 要同时执行同一个映射防止训练和验证使用不同标签体系。这里有个风险点需要特别提醒如果想折叠成“可食用/不可食用”二分类前提是 21 类确实能严格对应到这两种属性。如果原始类别是形态学分类而非毒性分类强行合并会产生无法解释的误召回。我的习惯是先让 21 类模型跑出结果再在推理端做类别分组而不是一开始就压缩标签信息这样至少保留了下游调整的空间。5. 避坑清单7z、VOC、YOLO 三者交接时最容易翻车的五个点以下五条是我在处理同类双格式数据集时踩过或者帮别人排查过的真实故障按高发顺序列出。每条都按现象、原因、解决的思路讲可以直接对照排查。5.1 解压时“密码正确但一直报错”其实是工具版本或包损坏现象输入正确密码后 7z 报数据错误或者解出部分文件后中断重试多次结果一致。 原因多数情况下不是密码错误而是压缩包在传输过程中损坏或者当前 p7zip/7-Zip 版本太旧不支持压缩包使用的新算法扩展。 解决先跑7z t测试若某个文件 CRC 校验失败直接重新下载源文件同时把 7-Zip 升级到较新版本再用7z x解压。不要抱着“多试几次就能成功”的念头损坏的文件重试多少次都一样。测试命令跑完显示 Done才说明压缩包完整。5.2 换机器后训练报找不到标签原因是 data.yaml 里的绝对路径失效现象同一套数据在 A 机器训练正常拷到 B 机器后训练集样本数变成 0或者直接报路径错误。 原因data.yaml 中 path 字段写的是原机器绝对路径换机器或换目录后路径不存在。 解决把 data.yaml 中的 path 改成相对当前工作目录的相对路径或者固定使用统一英文目录例如/data/mushroom或D:/datasets/mushroom。尽量避开带中文和空格的路径YOLO 在部分环境下对中文路径支持不稳定这个坑最容易在 Windows 上出现。5.3 标签第一列出现 21 或越界数字现象训练启动时报类别 id 越界相关错误或者 loss 起步就异常高mAP 一直是 0。 原因VOC 转 YOLO 时 class_map 没包含某个类别名导致转换脚本写入了错误 id也可能是 txt 文件被手工编辑过类别编号从 1 开始而不是从 0 开始。 解决写一次全量扫描把标签第一列的最大值和最小值打印出来。21 类数据集的合法范围是 0 到 20最大值超过 20 的文件要隔离出来检查。扫描脚本可以复用 4.1 里的 Counter 脚本把 range(21) 改成统计所有出现过的 id再打印 max 值。5.4 空标签文件让损失曲线异常现象训练时损失在下降但验证集 mAP 纹丝不动甚至验证阶段出现 NaN。 原因空标签 txt 被当成负样本参与训练模型从中学不到任何目标信息若验证集中空文件比例高mAP 计算会变得稀疏指标曲线噪声很大。 解决把空 txt 从 labels 目录移走对应图片也移走不要留在目录里。这类问题在切分脚本里已经做了过滤但如果直接使用原始目录训练必须单独清理一遍。统计脚本会把空文件数量直接打印出来训练前看一眼就知道有没有这个问题。5.5 推理画框时发现框和菌盖中心偏移现象用自写可视化脚本画框时框位置基本正确但整体向某一方向偏移。 原因txt 里后四个值是中心点坐标和宽高不是左上角坐标和右下角坐标。自绘时如果直接当成左上角用框会整体偏移。 解决推理端使用官方预测命令保存图片最省事如果自写 OpenCV 可视化按xmin int((x_center - w / 2) * img_w)还原左上角ymin int((y_center - h / 2) * img_h)还原左上角纵坐标并截断到图片边界。最容易的低级错误是把 width 对应到 y 轴方向height 对应到 x 轴方向查的时候先看坐标轴方向是否写反。6. 验证阶段的三个关键动作混淆矩阵、mAP50-95 和低置信度压力测试训练跑完不算结束验证阶段做对三个动作才知道下一步该补数据还是调参数。6.1 用混淆矩阵和 mAP50-95 找出最像的两个类YOLOv8 训练结束后会在 runs/mushroom/base 下输出 confusion_matrix.png。先看对角线是否明显强于副对角线。如果某两个类别互相误检比例都高说明这两个类外观接近或样本数量差距悬殊。我看矩阵时还会加一个动作把归一化后副对角线上数值超过 0.1 的类别对列出来回到数据集里挑二十张两类图像对比确认是标注边界不一致还是原始数据里两个类天然混淆。这里不要只看 mAP50mAP50-95 才是对框定位精度更严格的度量两个指标差 0.5 以上多半是目标框边缘标注不够精细。6.2 用 0.05 置信度做一次压力测试推理时把默认置信度阈值调低强迫模型输出更多低置信度框能暴露出平时看不到的误报模式。命令yolo predict \ modelruns/mushroom/base/weights/best.pt \ sourceextra_real_world \ imgsz640 \ conf0.05 \ save_txtTrue \ save_confTruesource 指向一批没进过训练集和验证集的实拍照片最好是俯拍、侧拍、暗光环境混在一起。conf0.05 让模型把所有把握不大的目标都吐出来然后重点看两类现象误报集中在背景草木说明原始标注里远距离小蘑菇漏标严重误报集中在某个类别说明这个类特征没学够。这个测试通常能定位到需要补数据的具体方向比盯着 mAP 数字有用得多。我现在的固定流程是切分时把 test 目录完整封存不动只用 train 和 val 反复调参确认效果稳定之后才在 test 上跑最终结论。靠这个习惯我避免了很多次“验证集没输过一次部署上线翻车”的尴尬。希望帮到你。本文还有配套的精品资源点击获取
返回列表