简介:这是一份面向目标检测任务与细胞图像分析的细胞活性检测数据集,适合计算机视觉学习者、生物医学图像研究者以及需要训练细胞分类/检测模型的数据科学人员使用。数据集围绕细胞活性判定场景,包含Dead与Live两类目标,共标注31332个矩形框,其中Dead类24315个、Live类7017个,标注工具为labelImg,框选规则清晰,可直接用于模型的训练与验证。压缩包共2000个文件,以VOC格式的XML标注文件和YOLO格式的TXT标签文件为主,并配套对应细胞图像,整体仅32.45MB,轻量易下载。目前已有197人学习下载。使用时可依据自己的框架需求读取XML或TXT标注,快速完成数据集划分与格式转换,节省手动整理标注的时间,适合作为目标检测入门练习或细胞活性识别实验的基础数据。
1. 细胞活性检测数据集值不值得下:1298 张图、2 类标签、双格式的关键不在数量而在标注密度
做细胞活性检测的人都知道,数据比模型金贵。这个目标检测数据集走的是双格式路线,1298 张 jpg 全部带 Pascal VOC 格式 xml 和 YOLO 格式 txt 两套标注,类别只有 Dead 和 Live 两类,但总框数有 31332 个,平均每张图 24 个框,密度不低。标注工具是 labelImg,矩形框规则统一,格式干净,拿到手不用再手工转格式,直接喂给 YOLO 系模型跑训练。适合做药物毒性筛选、细胞培养质检、生物医学图像检测这类任务的人。下载之前我建议你先想清楚一个问题:你要的是计数、定位还是死活形态分类,这决定后面的 anchor 聚类和损失函数要不要额外加权重。这篇笔记按实操顺序,把格式解析、数据划分、训练参数起点和踩坑点过一遍。
2. 读懂 VOC 与 YOLO 双格式:xml 字段映射、坐标换算和类别不平衡的真相
2.1 labelImg 生成的 VOC xml:框是怎么被记录下来的
Pascal VOC 格式是 labelImg 默认的保存形态,一个 xml 对应一张 jpg。xml 的根节点里先记录图片路径和尺寸(size 节点的 width、height、depth),然后每一个 object 节点挂一个类别名和 bndbox 的四个像素坐标。细胞检测场景里,Dead 和 Live 的判定通常依据明场或相差显微镜下的形态:活细胞边界清晰、轮廓圆润,死细胞边缘皱缩甚至破裂。所以 xml 里记录的是画框那一刻人对形态的判断,这是整个数据集最有价值的部分。
拿到数据集先别急着训练,第一件事是解析一个 xml 看看字段长什么样。下面的脚本可以打印任意一张图的标注信息:
import xml.etree.ElementTree as ET xml_path = "xyxr_cell_314.xml" tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) print(f"图片尺寸: {img_w} x {img_h}") for obj in root.findall("object"): name = obj.find("name").text bbox = obj.find("bndbox") xmin = int(bbox.find("xmin").text) ymin = int(bbox.find("ymin").text) xmax = int(bbox.find("xmax").text) ymax = int(bbox.find("ymax").text) print(f"类别: {name} 框: ({xmin}, {ymin}) -> ({xmax}, {ymax}), 宽{xmax - xmin} 高{ymax - ymin}")这段代码用 xml.etree.ElementTree 解析 xml。size 节点拿到原图的宽和高,后面转 YOLO 格式时这两个值是分母,拿错就会整批坐标漂移。object 节点里 name 是类别名,bndbox 是左上角和右下角两个点的像素坐标,坐标系原点在图片左上角,x 向右增大,y 向下增大。解析结果里如果出现 xmax <= xmin 或者 ymax <= ymin,说明原始标注里有退化框,这种 xml 对应的 txt 也要重点复查。
2.2 YOLO txt 的归一化坐标:四个浮点数到底怎么算出来的
YOLO 系列的标签文件是纯文本,每行代表一个目标,格式是“类别索引 x_center y_center width height”,四个坐标全部归一化到 0 到 1 之间。归一化的好处是训练时不管输入图片 resize 成 640 还是 1024,标签都不需要跟着改。但单位换算恰恰是翻车最多的地方。
从 VOC 的像素坐标换算到 YOLO 的归一化坐标,公式是死的:
def voc_to_yolo(xmin, ymin, xmax, ymax, img_w, img_h): # 中心点坐标 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h # 框的宽高 box_w = (xmax - xmin) / img_w box_h = (ymax - ymin) / img_h # 钳制到 [0, 1],防止画框时拖出画布带入越界数据 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) box_w = min(max(box_w, 0.0), 1.0) box_h = min(max(box_h, 0.0), 1.0) return round(x_center, 6), round(y_center, 6), round(box_w, 6), round(box_h, 6)换算逻辑不复杂,但有两个细节要注意。第一个,img_w 和 img_h 必须来自 xml 的 size 节点,也就是原图尺寸,不能用训练时 resize 后的尺寸,更不能写死成 416 或者 640,否则小目标框会整体偏移。第二个,钳制到 [0,1] 这步不是可选项,细胞图里密集区域容易把矩形框拖出图像边界,labelImg 不会阻止这种操作,越界的坐标如果不处理,训练时 loss 会出现异常跳变。round 保留 6 位小数是 Ultralytics 系工具的默认习惯,精度足够,txt 文件体积也不会变大。
2.3 31332 个框里的类别分布:Dead 24315 对 Live 7017,先看比例再定训练策略
这个数据集的标注统计里有一个很关键的数字:Dead 框 24315,Live 框 7017,总框 31332。两者比例大约是 3.46 比 1,Live 只占约 22.4%。这个比例直接决定了训练策略,而不是像分类任务那样简单设一个权重就完事。
| 类别 | 框数 | 占比 | 典型形态 |
|---|---|---|---|
| Dead | 24315 | 77.6% | 边缘皱缩、轮廓不完整、灰度偏低 |
| Live | 7017 | 22.4% | 边界清晰、呈圆形或椭圆形、胞内结构可见 |
从形态上讲,死细胞的框更容易画准,因为边缘破损后对比度高;活细胞边界柔和,在密集贴壁生长的区域两个相邻活细胞的框经常会挤在一起。所以训练时你大概率会遇到这样的情况:Dead 的 mAP 很快上 0.9,Live 的 recall 卡在 0.5 上下。这不是模型不行,是样本数量和形态难度双重的结果。我一般会在数据配置阶段就把类别权重调出来,YOLOv8 里是 cls 参数,配比超过 3:1 时把 cls 从默认的 0.5 提到 0.9 左右,简单有效。
要不要先验证一下手里这份 txt 和 xml 是否对得上?一份可靠的数据集,同一个图片名的 txt 行数应该等于 xml 里的 object 个数。我习惯先跑一遍校验:
import xml.etree.ElementTree as ET def count_xml_objects(xml_path): tree = ET.parse(xml_path) return len(tree.getroot().findall("object")) xml_cnt = count_xml_objects("xyxr_cell_314.xml") with open("xyxr_cell_314.txt") as f: txt_cnt = len([line for line in f if line.strip()]) print(f"xml 对象数: {xml_cnt}, txt 行数: {txt_cnt}")如果两个数对不上,优先以 xml 为准重新生成 txt,而不是手动改 txt。这一条在后面避坑章节还会再展开。
3. 把数据集跑通到训练流程:目录重排、按图划分和标注密度筛查
3.1 目录怎么摆:不建软链,直接用 images 和 labels 两个平级目录
这个数据集解压后是散装的 jpg、xml、txt 混在一起,文件名规律是 xyxr_cell_编号。直接开工之前,先把目录整理成 YOLO 训练约定俗成的结构。VOC 风格目录是 JPEGImages 放图、Annotations 放 xml、labels 放 txt;YOLO 风格是 images 和 labels 两个平级目录。对训练来说,YOLO 风格更省事,Ultralytics 的 data.yaml 默认就是找 images 和 labels 这两个名字。
mkdir -p dataset/images dataset/labels dataset/xml for f in *.jpg; do mv "$f" dataset/images/; done for f in *.txt; do mv "$f" dataset/labels/; done for f in *.xml; do mv "$f" dataset/xml/; done ls dataset/images | wc -l这几行命令把三类文件分别归位。注意一个细节:这份数据集是“不包含分割路径的 txt 文件”,意思是 labels 目录里只有每张图对应的标注 txt,不存在 ImageSets/Main 那种存储划分结果的清单。所以训练集和验证集的划分必须你自己做,这一步省不了。xml 目录建议保留,虽然 YOLO 训练不读 xml,但后面做标注复核、转成 COCO 或者其他格式时,xml 是唯一可靠的原始依据。
有人喜欢用 ln -s 软链把散文件链到目录里,我不推荐。Windows 解压和 Docker 挂载场景下软链经常失效,移动文件虽然占用一点 IO,但换来的是路径绝对干净,后面排查问题少一个变量。
3.2 按图划分训练集和验证集:8:2 是常用的起点,关键是别按框切
划分训练集和验证集有两个常见错误:一个是按 txt 行数切,另一个是直接用 random 切完不固定种子。按 txt 行数切会把同一个图片的标注拆到两个集合里,训练时模型等于提前见过验证集;不固定种子则会让实验结果无法复现。正确做法是按图片维度划分,一个图片的 jpg、xml、txt 必须同时去同一个集合。
import os import random import shutil from glob import glob random.seed(42) # 固定种子,保证每次划分结果一致 jpg_files = sorted(glob("dataset/images/*.jpg")) random.shuffle(jpg_files) val_ratio = 0.2 val_count = int(len(jpg_files) * val_ratio) val_files = jpg_files[:val_count] train_files = jpg_files[val_count:] for split_name, file_list in [("train", train_files), ("val", val_files)]: split_img_dir = f"dataset/{split_name}/images" split_lbl_dir = f"dataset/{split_name}/labels" os.makedirs(split_img_dir, exist_ok=True) os.makedirs(split_lbl_dir, exist_ok=True) for jpg_path in file_list: basename = os.path.splitext(os.path.basename(jpg_path))[0] shutil.move(jpg_path, os.path.join(split_img_dir, basename + ".jpg")) shutil.move(os.path.join("dataset/labels", basename + ".txt"), os.path.join(split_lbl_dir, basename + ".txt"))这段脚本先洗牌再按比例切片。val_ratio 取 0.2,1298 张图会分出约 260 张做验证、1038 张做训练,对细胞检测这个规模来说够用;如果数据量小到几百张,可以改成 0.15。seed 固定成 42 以后,不管跑多少次划分结果都一样,实验对比才有意义。两个 move 都在同一个脚本里执行,jpg 和 txt 按同名 basename 一起走,不会出现图片进了 train、标签留在原地的情况。
细胞检测场景下,划分还有个更严格的做法:如果图片来自不同培养孔、不同批次,随机划分会让同批次图片同时出现在 train 和 val,验证指标虚高。这个坑在第 5 章专门讲,这里先记住一个原则,划分的单位永远是“完整的实验样本”,不是单张图。
3.3 标注密度筛查:平均 24 框/张,哪些图不适合直接进训练
1298 张图 31332 个框,平均每张 24.1 个框,但平均分布不等于均匀分布。显微镜视野里有的图可能只有五六个细胞,有的图堆了五六十个。密集图对训练的影响是双刃剑:一方面模型需要见过密集场景才能学会在重叠区域区分 Dead 和 Live;另一方面,极度密集的图如果标注本身存在漏标,会向模型传递错误信号。
import os import statistics from glob import glob density = [] for txt_path in sorted(glob("dataset/labels/*.txt")): with open(txt_path) as f: line_count = len([line for line in f if line.strip()]) name = os.path.basename(txt_path) density.append((name, line_count)) mean_density = statistics.mean(d for _, d in density) print(f"平均每张图 {mean_density:.1f} 个框") # 找出最密集的 10 张 for name, cnt in sorted(density, key=lambda x: -x[1])[:10]: print(f"{name} {cnt} 框")这个脚本把每个 txt 的行数统计出来,行数就是这张图的标注框数。最密集的十张图值得单独打开原图看一眼:如果密集区域存在粘连细胞,光靠矩形框会有大量重叠,这类图对检测模型来说是高难度样本,但对细胞计数任务却是最有价值的部分。我的习惯是先跑一遍这个统计,再决定需不需要做图像切片,把大图切成 608 或 640 的小图再训练,小目标会好捡很多。
切片与否我给一个粗标准:如果单张图的标注框数超过 40,且大多数框的宽度小于图片宽度的 8%,就建议切成小图再训。切的时候注意不要跨边界切掉细胞,常见做法是重叠切片,相邻切片重叠 20%,跨切边界的框直接丢弃,或者只保留落在切片内部的完整框。这个决策直接影响第 4 章的 imgsz 参数选多少。
4. 训练参数起点:anchor 重聚类、imgsz 与 mosaic 取舍,以及一条能跑的 YOLOv8 命令
4.1 先重算 anchor 而不是先改模型:细胞框大多集中在中小尺寸
拿到新数据集先做的一件事,不是换骨干网络,而是先统计标注框的尺寸分布。目标检测模型里 anchor 的默认值是针对 COCO 这类自然图像设计的,COCO 里很多目标占图面积大、宽高比丰富,而细胞检测的框绝大多数是小尺寸、宽高比接近 1 的矩形。用默认 anchor 去训练小目标密集数据,前几个 epoch 会出现大量低置信度预测,收敛变慢。
import glob aspect_ratios = [] relative_sizes = [] # 相对原图宽高的归一化边长 for txt_path in glob.glob("dataset/labels/*.txt"): with open(txt_path) as f: for line in f: parts = line.strip().split() if len(parts) != 5: continue w = float(parts[3]) h = float(parts[4]) aspect_ratios.append(w / h if h > 0 else 0) relative_sizes.append((w + h) / 2) aspect_ratios.sort() relative_sizes.sort() mid_a = len(aspect_ratios) // 2 mid_s = len(relative_sizes) // 2 print(f"宽高比中位数: {aspect_ratios[mid_a]:.3f}") print(f"归一化边长中位数: {relative_sizes[mid_s]:.3f}")这段代码统计所有标注框的宽高比中位数和归一化边长中位数。如果宽高比中位数在 0.8 到 1.2 之间,说明细胞框接近正四方形,anchor 的尺寸不用做太多非正方形比例;如果归一化边长中位数小于 0.1,说明大部分目标是小目标,与其纠结 anchor 的具体数值,不如直接把输入分辨率提上去。Ultralytics 在训练时会自动做 anchor 的自适应重算,但前提是你跑的是标注规范的数据集;数据混乱的话自动重算也会被脏标签带偏,所以先自己过一遍统计,心里有底。
4.2 imgsz 与 mosaic:细胞小、密、边界模糊,不是越大越好
输入分辨率 imgsz 直接决定了小目标能不能被检出。对细胞这种典型的显微图像,640 是底线,显存够的话优先试 1024。但有个反直觉的地方:分辨率提上去以后,显存占用和训练时间都涨,mosaic 增强就需要降下来。mosaic 会把四张图拼成一张,小目标在拼接过程中被缩小甚至截断,对细胞检测这种目标本来就小的任务,mosaic 比例过高会适得其反。
我一般把 mosaic 设成 0.5,也就是一半概率做拼接增强,并在最后 10 个 epoch 关掉 mosaic,让模型在接近真实的分布上精调。close_mosaic 这个参数在 Ultralytics 里就是干这个用的。如果细胞图本身密集到每张图十几个目标,mosaic 的收益其实很小,直接设 0.3 也可以。与其花时间调 mosaic,不如先确认图片在训练时的缩放方式:默认 letterbox 会把原图等比例缩放到 imgsz,多出来的部分用灰边填充,这一步对归一化标签没有影响,因为标签本来就是相对于原图归一化的,训练时解算标签会按 letterbox 的实际变换关系回算。
细胞图和自然图像还有一个显著差别:活细胞在明场下的边缘是柔和的渐变,标注框的边界天然带模糊性,所以 box_loss 的收敛值通常比 COCO 要高一些,不要拿 COCO 的数值硬套自己的实验。只要曲线整体单调下降,就算 0.06 到 0.08 的收敛值也是正常的。
4.3 一条能直接下跑的 YOLOv8 训练命令
把目录和配置准备好之后,训练命令本身不长。先写 data.yaml:
# data_cell.yaml path: /绝对路径/dataset # 改成你本机实际路径 train: train val: val nc: 2 names: ['Dead', 'Live']names 的顺序必须和 txt 里的类别索引一致,0 对应 Dead,1 对应 Live,写反了训练不会报错,但评估结果完全乱套。
训练用 yolov8s 起步,显存小于 8G 就换 yolov8n:
yolo detect train \ model=yolov8s.pt \ data=data_cell.yaml \ imgsz=640 \ epochs=100 \ batch=16 \ lr0=0.01 \ mosaic=0.5 \ close_mosaic=10 \ cls=0.9 \ seed=42解释一下关键参数。imgsz 是输入边长,640 起步,显存有余先加到 1024 再谈其他。batch 按显存调,显存不够先降 batch 而不是降 imgsz。mosaic=0.5 表示 50% 概率做马赛克增强,close_mosaic=10 表示最后 10 个 epoch 把 mosaic 关掉。cls=0.9 是给分类损失的权重,这个数据集 Dead 与 Live 接近 3.5:1,所以把它从默认的 0.5 提到 0.9。训练结束后看 results.csv 里的 mAP50 和 mAP50-95,还要单独看每个类别的 recall,不是只看综合数字。
训练到 20 到 30 个 epoch 之间,box_loss 和 cls_loss 应该稳定下降。如果 cls_loss 出现锯齿形震荡,先看是不是学习率太大,把 lr0 降到 0.005 重跑;如果 box_loss 一直横盘不动,回头看 4.1 节的框尺寸统计,确认输入分辨率是不是太小导致小目标梯度信号太弱。细胞检测的收敛速度比自然图像快,前 50 个 epoch 基本能看出能不能打,没必要一上来就跑 300 轮。
5. 避坑:从解压到评估最容易翻车的五个环节
5.1 现象:训练一开始就报 image not found 或 labels not found
原因:最常见的是文件移动时后缀处理出错。比如用脚本移动时,jpg 和 txt 同名但后缀不同,如果 basename 取错,labels 目录里的文件就会变成 xyxr_cell_314.jpg.txt,训练时解析不到。还有一种情况是 Windows 下解压 zip 后,文件名里带着不可见字符或者全角空格,Linux 训练环境读不到。
解决:统一用英文和数字命名,训练前跑一遍清单核对脚本,确保 images 里每个 jpg 在 labels 里都有同名 txt:
from glob import glob import os jpg_set = {os.path.splitext(os.path.basename(p))[0] for p in glob("dataset/train/images/*.jpg")} txt_set = {os.path.splitext(os.path.basename(p))[0] for p in glob("dataset/train/labels/*.txt")} print("缺标签的图片:", jpg_set - txt_set) print("缺图片的标签:", txt_set - jpg_set)这段脚本在每次训练前跑一遍,能挡住九成路径问题。我碰到这类问题后,习惯把划分脚本和校验脚本做成一对,划分完立刻校验,不等训练时报错再回头查。
5.2 现象:xml 正常但转出 txt 后坐标出现负数或大于 1
原因:labelImg 画框时鼠标可以拖出画布范围,保存的 xmin 或 ymax 就越界了。xml 里这种越界不报错,但转成 txt 后如果没做归一化钳制,会出现坐标小于 0 或大于 1。训练时 ultralytics 解析标签会直接过滤这些框,表现为某个类别明明有 7000 多个标注,实际参与训练的只有 5000 多个。
解决:转换脚本里加钳制和面积过滤。面积过滤这条很关键,一个框如果归一化后的宽或高小于 0.005,多半是误点击产生的点状标注,直接丢弃比留着强。我在代码里写过一条规则:宽高任一小于 0.005 就丢掉,并且把丢弃的记录写到一个 log 文件,方便回头对照 xml 修订。
5.3 现象:Dead 的 mAP 很高,Live 的 recall 只有 40%
原因:两部分叠加。一是 Dead 框数是 Live 的 3.46 倍,模型天然偏向多数类;二是 Live 框经常聚集在细胞密集区,互相重叠,NMS 阶段高置信度的 Dead 框会把相邻的 Live 框挤掉。只看总的 mAP 是看不出这个问题的,必须按类别看。
解决:训练参数里把 cls 权重提到 0.9 甚至 1.2;验证时用 F1 而不是 mAP 做早停依据;如果还是要保 recall,可以把 val 阶段的 conf_thres 降 0.1 再看 PR 曲线。注意这里调的是评估阈值,不是训练损失,两件事别混在一起。
5.4 现象:验证集 mAP 虚高,换一批细胞图直接崩
原因:划分方式不对。这个数据集的图片名是 xyxr_cell_编号,如果随机打乱后划分,同一批培养条件下连续拍摄的几张图会同时进训练集和验证集。模型在训练时见过同一视野的相邻帧,验证分数自然虚高。这是细胞数据最常见的划分陷阱,比标签质量问题隐蔽得多。我自己碰到过一次,验证 mAP 刷到 0.9,换一个新的培养皿批次立刻掉到 0.6,折腾了两天才定位到是划分泄漏。
解决:按文件名前缀或拍摄批次划分。比如 xyxr_cell_314、xyxr_cell_315 这种编号,如果同一天采集的编号段相近,就按编号区间分桶,整桶进训练或验证,而不是单张随机切。具体做法是先用 3.3 节的统计脚本把全部文件名排个序,看编号有没有明显的批次分界,再决定桶的大小。宁可牺牲一点训练集数量,也要保证验证集是真正没见过的样本。
5.5 现象:密集区域预测框大量重叠,Live 被 NMS 吃掉
原因:细胞粘连区域里,模型对每个细胞位置输出多个置信度接近的框,默认的 NMS 阈值 0.5 会把相邻细胞的框合并掉一个,视觉上就是少检了。这不是模型过拟合,是密集目标检测的通病。
解决:推理阶段把 iou 阈值从 0.5 提到 0.7 或 0.75,max_det 调大到 300;如果重叠仍然严重,开启 TTA(Test-Time Augmentation)可以多捞回来 3 到 5 个点。但 TTA 会成倍增加推理时间,在线服务场景慎用。想要根上解决,得从训练数据入手,把 3.3 节筛出来的高密度图复制一份做小范围随机裁剪增强,让模型见过更多拥挤布局。
这五个坑按出现频率排,路径和越界问题在解压当天就能碰到,类别不平衡和划分泄漏要等训练跑到 30 个 epoch 以上才暴露,NMS 重叠则要看推理结果才察觉。排查顺序建议从前到后,不要在流程还没跑通时就怀疑 NMS 参数。
6. 验证标注质量的三个脚本技巧:框复查、混淆热区和训练前自检
6.1 全量框复查:xml 与 txt 对表
训练完一轮之后再回头做一次全量对表。拿 xml 里的 object 数量和 txt 行数逐张比较,差异来源基本是 5.2 说的越界框被过滤,或者原始漏标。对不上时统一以 xml 为准,重新跑一遍转换脚本,而不是手补 txt。数据集 1298 张图,全量扫描也就十几秒,值得跑。
6.2 混淆热区:把预测错误画回原图
找出验证集里所有把 Live 漏检或误检成 Dead 的样本,把模型预测框和 GT 框画在同一张图上,直接看错误集中在图像的什么位置。细胞图里错误位置往往有规律:有的集中在视野边缘,因为边缘光照不均;有的集中在细胞团正中间,因为重叠太严重。看到规律比看到数字有用,数字只能告诉你错了多少,规律能告诉你下一步改哪里。
6.3 训练前自检:十轮 mini 训练
拿到数据集先不跑完整训练,用一个小参数跑 10 个 epoch 验证流程通不通:
yolo detect train \ model=yolov8n.pt \ data=data_cell.yaml \ imgsz=640 \ epochs=10 \ batch=8 \ workers=2 \ seed=42这个配置把模型换成 yolov8n、epochs 压到 10、batch 降到 8,目的不是看精度,是验证从数据读取到 loss 回传整条链路。10 个 epoch 跑完要确认三件事:loss 在下降、每个类别都有预测输出、val 阶段能正常算 mAP。如果 10 轮后某个类别的预测框数量一直是 0,不是训练不够,是标签或类别索引有问题,回头查 names 顺序。
之前一次实验里,我没做批次划分就开训,验证 mAP 刷到 0.9,换一个新的培养皿批次立刻掉到 0.6。后来把划分策略改成按编号段分桶,评估数字才和实际表现对得上。从那以后我每次拿到新数据集都强制走一遍这套流程:先解析 xml 统计框数和类别比例,再按批次划分,然后跑 10 轮 mini 训练确认 loss 曲线,最后才上完整训练。希望帮到你。
本文还有配套的精品资源,点击获取