车内视角行人检测数据集构建:从数据采集到训练避坑全指南
简介车内视角行人识别数据集源自BDD100K仅保留“person”这一行人类别面向目标检测方向的高校学生、算法工程师和科研人员用于解决车内视角下行人识别训练数据难以获取、标注成本高的问题。压缩包共2000个文件几乎全部为txt格式的YOLO标签文件另含1个yaml类别配置文件整体约421.52MB数据集在原始图片基础上已完成标签标准化并预先划分为训练集、验证集和测试集可直接输入YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10等算法进行训练同时也适配Faster RCNN、SSD等常见检测框架。这份数据集省去了从完整BDD100K中筛选、转换和划分的时间拿到即可开展行人检测实验或教学演示配合yaml文件可快速完成类别配置与模型调参。目前已有207人学习下载对需要建立车内场景行人检测基线模型的开发者来说是一份直接可用的落地方案。1. 车内视角行人识别数据集为什么通用行人模型一上车就近视“车内视角行人识别数据集”这个标题看着直白实际做成过的人都知道它不是随便拉一批路人图打个框就能交货的。摄像头装在挡风玻璃后面行人以侧身、低头、被 A 柱遮挡、被雨刮扫过、逆光只剩轮廓的形式出现通用行人数据里那些“站得板正”的样本根本覆盖不住。拿 Caltech、CrowdHuman 这些经典行人集直接训练再拿到车载视频上一测漏检率能比预期高出十几个点。这篇文章按“数据特征 → 公开数据集选型 → 自建流程 → 训练避坑 → 评估迭代”的顺序给你一套可以直接落地的方案适合正在做辅助驾驶、行车预警、车内行为分析或车路协同感知的工程师和数据团队。2. 先定标签语义车内视角的 4 个视觉特征与 3 个必标字段动手采集前先把你眼里的“行人”转化成标注员和模型都能执行的语义。车内视角的数据集标签设计比画面数量更先决定上限。很多团队踩坑是因为拿到视频就开始画框画完发现没法回答“远处那个算不算行人”“被挡一半要不要框”。这一章先把数据特征讲清楚再给出我常用的标注字段和一套校验脚本。2.1 车内视角的行人为什么难认4 个分布差异第一个差异是尺度跨度极大。车内相机离行人最近可能不到 2 米行人头部占满画面三分之一离得远则可能只有 30×60 像素。YOLO 这类单阶段检测器对尺度很敏感训练集里如果 150 像素以上的近景占多数模型对 40 像素级别的远距离行人就基本失明。第二个差异是遮挡结构特殊。通用数据集里的遮挡大多是人群互相遮挡而车内视角里是 A 柱、后视镜、雨刮、前车车尾、路侧护栏这些“细长结构”切在行人身上。行人经常只剩半边身体、一条胳膊或一双脚而且可见部分在画面中的位置很不规律——中轴线被柱子遮住的情况很常见这会直接干扰基于框中心点回归的检测头。第三个差异是运动模糊和卷帘快门效应。车辆行驶中行人与相机存在相对运动1/30 秒曝光下 60 公里时速的拖影能到十几像素。CMOS 卷帘快门还会让快速走动的人形出现倾斜和扭曲闭眼一看像另一个物种。第四个差异是光照极性变化剧烈。逆光时行人背光面死黑夜间车灯直射时行人过曝成一团白色进出隧道有 1 到 2 秒的曝光跳变。这些在公开的街拍行人数据集里占比很低恰好是车内场景最常见的情况。2.2 标注前必须先定死的 3 个字段第一个是可见框与完整框分开存。可见框是画面里真正能看到的部分完整框是标注员根据上下文估计的全身范围。没有完整框后面算遮挡比例就没有基准没有可见框训练时模型会被框外的背景干扰尤其在 A 柱切掉半边身体时。第二个是遮挡等级离散化。我一般用 0 到 3 四级0 为无遮挡1 为遮挡约 10% 到 40%2 为遮挡 40% 到 80%3 为遮挡大于 80% 或只露出一截肢体。连续遮挡值标注员很难达成一致离散四级在多人协作时保持一致性的成本最低。第三个是跨帧 ID。只要你不是只做单帧检测而是想让检测结果支撑预警或跟踪就必须给同一个行人在连续帧里分配同一个 ID。另外一定要把行人和骑车人分成两个类别不要都归成“人”。电动车、摩托车骑手的轮廓和行人差异很大混在一起会让模型在尾部错分上一塌糊涂。字段取值说明full_boxx1, y1, x2, y2完整行人框需估计被遮挡部分vis_boxx1, y1, x2, y2可见部分框occ0 / 1 / 2 / 3遮挡等级diff0 / 1困难样本标记模糊、极暗id帧内唯一整数跨帧 ID跟踪任务必填2.3 用统计脚本校验标注分布拿到数据先看这几项标注完成先别急着训练用脚本看一眼标注分布。我习惯写一个读取 COCO 风格标注的统计脚本重点看四件事总框数、遮挡等级分布、行人高度中位数和 p90、单图目标数峰值。import json import sys import numpy as np def inspect_coco(json_path): with open(json_path, r, encodingutf-8) as f: data json.load(f) # 图片信息 id2img {img[id]: img for img in data[images]} print(images:, len(data[images]), annotations:, len(data[annotations])) occ_dist [0, 0, 0, 0] heights [] anns_per_img {} for ann in data[annotations]: # bbox: [x, y, w, h] w, h ann[bbox][2], ann[bbox][3] heights.append(h) occ ann.get(occ, 0) # 标注作业里定义的字段 occ_dist[occ] 1 img_id ann[image_id] anns_per_img[img_id] anns_per_img.get(img_id, 0) 1 heights np.array(heights) print(occ 0/1/2/3:, occ_dist) print(height p50 / p90: %.1f / %.1f % (np.percentile(heights, 50), np.percentile(heights, 90))) cnt np.array(list(anns_per_img.values())) print(anns per img p50 / max:, np.percentile(cnt, 50), int(cnt.max())) if __name__ __main__: inspect_coco(sys.argv[1])这段脚本的参数只有标注文件路径。输出里的 height p50 如果明显大于 150 像素说明近景行人占多数要去补中远距离样本occ3 数量如果接近 0说明严重遮挡样本严重缺失后续模型遇到 A 柱切半身的人大概率漏检。单图目标数最高只有五六个也要注意——车内视角十字路口高峰期一张图里十几个人很常见模型没见过这种拥挤程度就学不会密集场景的抑制。3. 公开数据集选型BDD100K、CityPersons、EuroCity Persons 怎么搭配才不白训公开自动驾驶数据集和行人专用数据集解决的是两个问题。前者教你“道路场景长什么样”后者教你“行人子类怎么分”。大部分项目不需要完全自建按比例组合公开数据就能跑出一个不错的底座再拿少量实车数据精调比自己从零标五千张省一半时间。3.1 BDD100K 是底料但别把它的标注当金标准BDD100K 是我做车内视角目标检测数据集时默认会用的第一份底料。它覆盖了白天、黑夜、黄昏、雨雪雾多种天气图像是从真实车载摄像头采集的前视画面类别里有 pedestrian 和 rider 可用的 2D 框标注量级在约十万张图足够让模型先把“车内视角的路面结构”学扎实。BDD100K 的标注质量是“够用但不细”的典型。小目标漏标很常见遮挡等级在不同片段之间不一致有些行人只给了可见框有些给了完整框。所以我的用法是用它做 YOLOv8 训练自己的数据集时的预训练底座让网络先熟悉道路场景和光照分布但不要拿它当测试基准否则会被标注噪声误导。Waymo Open Dataset 也有车载前视相机标注但授权流程和体量更适合大团队个人项目不一定划算。3.2 CityPersons、EuroCity Persons 与 CrowdHuman 各自补什么CityPersons 是基于 Cityscapes 的行人标注扩展视角同样是车载前视城市道路为主标注里有细粒度的遮挡比例和可见框质量在行人数据集里属于最稳的一档。它的缺点是规模不大只有几千张图场景偏欧洲城市风格适合当精调和评测集不适合当主要训练数据。EuroCity Persons 是更大规模的行人数据集多城市、多季节、多时段车辆前视视角密集和稀疏场景都有。如果只想用一份公开数据做主力训练我一般首选它。规模量级比 CityPersons 大一个数量级天气和时段的覆盖也明显更广。CrowdHuman 严格说不是车内视角它来自网络图片主体是密集人群遮挡关系复杂单图人数多。它解决的是另一个痛点十字路口、商圈附近的车内画面经常出现一群人挤在斑马线上CrowdHuman 里的稠密遮挡样本正好用来做难例补充防止模型在人多时召回崩掉。数据集视角规模量级遮挡标注车内前视适合用途BDD100K车载前视约 10 万张图较粗是场景预训练底座CityPersons车载前视数千张图细是精调 / 评测EuroCity Persons车载前视数万张图细是主训练集CrowdHuman网络图片约 2 万张图细否密集遮挡难例补充3.3 三路数据怎么组合一个 BDD100K 转 YOLO 格式的脚本我一般的组合方式是“底座 精调 难例”三段式。底座用 BDD100K 里抽出的 pedestrian 和 rider 类别约 3 到 5 万张精调用 EuroCity Persons 或自采数据五千到一万张难例从 CrowdHuman 里抽两三千张密集遮挡图。不建议把所有数据一次混合训练因为三份数据的标注噪声口径不同混在一起模型容易被噪声更大的那份带偏。分段训练反而稳定先用底座训一百个 epoch再用精调集训三四十个 epoch最后用难例集短训十几个 epoch。BDD100K 的检测标注是自定义 JSON 结构不是 COCO 标准格式。转 YOLO txt 时我一般写这样的脚本import json import os def bdd100k_to_yolo(json_path, out_dir, keep_cats(pedestrian, rider)): BDD100K det JSON 是 list of frames每个 frame 里有 labels。 只保留 pedestrian/rider转成 YOLO 格式的归一化中心点坐标。 os.makedirs(out_dir, exist_okTrue) with open(json_path, r, encodingutf-8) as f: frames json.load(f) cat2id {pedestrian: 0, rider: 1} # 后续训练按这个顺序配置 names for frame in frames: w, h frame[width], frame[height] img_name frame[name].rsplit(., 1)[0] lines [] for label in frame.get(labels, []): if label[category] not in keep_cats: continue box label.get(box2d) if not box: continue x1, y1, x2, y2 box[x1], box[y1], box[x2], box[y2] if x2 x1 or y2 y1: continue cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw (x2 - x1) / w bh (y2 - y1) / h lines.append(f{cat2id[label[category]]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) if lines: with open(os.path.join(out_dir, img_name .txt), w) as f: f.write(\n.join(lines))脚本有两个关键参数keep_cats 是保留的类别名out_dir 是输出目录。注意 BDD100K 里有些框的坐标反了代码里用x2 x1 or y2 y1直接丢掉避免给训练集喂脏数据。转换完成后还要核对一下输出文件数量和原标注里含行人的图片数量是否一致不一致就去查 frame 的 name 字段和图片文件对不上的问题。另外无论用哪份公开数据先确认授权是否允许你的商用场景这是最容易忽视的一步。4. 自建车内行人数据集采集、抽帧、标注、切分的完整流程公开集能打底但最后决定产品体验的往往是自采数据里那些公开集没有的细节你自己的安装位置、你跑的固定路线、你常见的那几类三轮车和外卖车。自建的目标不是量大而是用两三千张高质量标注覆盖公开集的盲区。下面按采集、抽帧清洗、标注、切分四步走。4.1 采集设备与场景矩阵设计先列一个 12 格表格再上车采集设备我用固定安装的行车记录仪1080p 起步能上 4K 更好因为 4K 抽帧后可以缩小成 1080p 训练等效于做了一次多尺度增强。手机支架拍的画面会增加无关的抖动和自动曝光漂移最好别用。安装位置尽量固定在前挡玻璃中央偏上这是大多数 ADAS 前视相机的标准位模型迁移到量产设备时损失最小。场景矩阵是采集前就要写好的。我习惯按两个维度铺时段白天、黄昏、夜晚加天气晴天、雨天、雾天再交叉道路类型城市、快速路、乡村。算下来至少十二个组合每个组合录三十分钟连续视频。如果项目只在特定区域跑就把该区域的路口和拥堵时段单独加量。车速也要覆盖0 到 20 公里时速的拥堵路段、30 到 60 的市区道路、80 以上的快速路各占三分之一左右因为车速直接影响运动模糊和行人尺度分布。时段天气城市道路快速路乡村道路白天晴30 分钟30 分钟30 分钟白天雨30 分钟30 分钟30 分钟黄昏晴30 分钟30 分钟30 分钟夜晚晴30 分钟30 分钟30 分钟夜晚雨30 分钟30 分钟30 分钟4.2 视频抽帧与自动清洗两张脚本解决“一堆废图”三十段视频按原始帧率全存下来既占空间又训练不动。我按 1 秒取 1 帧来抽高速路段可以放宽到每 0.5 秒一帧静止等红灯时连续帧几乎一样抽多了只会让数据集高度冗余。下面这个脚本按目标帧率抽帧并保留高质量 JPG 输出。import cv2 import os def extract_frames(video_path, out_dir, fps1.0): 按指定 fps 抽帧比如 fps1.0 表示每秒保留一帧。 video_fps 是视频原始帧率frame_interval 是隔多少帧取一帧。 cap cv2.VideoCapture(video_path) video_fps cap.get(cv2.CAP_PROP_FPS) or 30.0 frame_interval max(1, int(video_fps / fps 0.5)) idx, saved 0, 0 os.makedirs(out_dir, exist_okTrue) while True: ret, frame cap.read() if not ret: break if idx % frame_interval 0: out_path os.path.join(out_dir, f{saved:06d}.jpg) cv2.imwrite(out_path, frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) saved 1 idx 1 cap.release() print(fsaved {saved} frames from {idx} input frames)抽完帧要先洗一遍否则模糊帧和近乎重复的帧会稀释标注精力。我用两个指标Laplacian 方差判断模糊感知哈希判断内容重复。Laplacian 方差低于阈值的帧在训练里只会教会模型“模糊也能检测”但推理时会带来大量误检。import cv2 import os def is_blurry(img, threshold80.0): # 拉普拉斯方差越小图像越平越可能是模糊帧 return cv2.Laplacian(img, cv2.CV_64F).var() threshold def dhash(img, size8): # 感知哈希缩小到 9x8比较相邻像素亮度得到 64 位指纹 gray cv2.cvtColor(cv2.resize(img, (size 1, size)), cv2.COLOR_BGR2GRAY) diff gray[:, 1:] gray[:, :-1] return .join(1 if d else 0 for d in diff.flatten()) seen {} for name in sorted(os.listdir(frames)): path os.path.join(frames, name) img cv2.imread(path) if img is None: continue if is_blurry(img): os.remove(path) continue h dhash(img) if seen.get(h): os.remove(path) # 和前一张指纹完全一致删旧的 else: seen[h] path清洗脚本里阈值是关键参数。is_blurry 的 threshold 值在白天 80 左右好使夜间图像本来就暗方差天然偏低压到 40 以下才安全否则夜图会被全删。dhash 默认汉明距离为 0 才去重如果我想保留更多变化可以把条件放宽到汉明距离小于 3 再删旧帧。这个脚本是纯最近邻判重不做时间维度处理简单但够用。4.3 标注规范与工具配置遮挡等级和可见框一定要单独标清洗完剩下的图进标注工具LabelImg、X-AnyLabeling、CVAT 都可以关键是能导出 COCO 或带属性的格式。YOLO 原生的 txt 格式存不了遮挡等级和可见框所以我会先用 COCO 格式标导出后自己写脚本转 YOLO顺便把 occ 和 vis_box 存成额外的文件备用。人的判定标准要在标注规范里写死。我的口径是像素高度大于 20 且能看出人形轮廓就标高度在 10 到 20 像素之间且能看出“人在移动”的标成 diff1 的困难样本完全被车尾挡住、只露出一只脚的不标车窗倒影、广告牌上的人形照片不标。密集人群场景严格要求“看得到身体的一部分才标”不要靠脑补去框。标注过程最容易翻车的点是靠主观估遮挡等级。两个人标同一张被树挡了一半的行人一个给 occ1一个给 occ2分歧会很大。我在标注规范里给出的参考是被挡部分小于 1/3 为 1挡 1/3 到 2/3 为 2挡超过 2/3 或只露出一截肢体为 3。每个标注批次抽 10% 复核遮挡等级一致率低于 80% 就退回重标。4.4 按视频片段划分数据集防止帧级泄漏的切分脚本这是自制数据集最重要的一个步骤也是最常被忽略的。直接对图片随机划分训练验证集相邻帧会同时出现在两边验证指标虚高实车一跑就露馅。正确做法是先把图片按所属的视频片段分组再按组划分。import os import random def build_split(annot_dir, clip_key_len8, train_ratio0.8, seed42): annot_dir 下放着 xxx_000123.txt 这样的标注文件。 clip_key_len 取文件名前 N 位作为片段 ID。 返回训练、验证两个文件列表同片段只会落入一边。 files [f for f in os.listdir(annot_dir) if f.endswith(.txt)] clips {} for f in files: clip_id f[:clip_key_len] clips.setdefault(clip_id, []).append(f) ids list(clips.keys()) random.Random(seed).shuffle(ids) split int(len(ids) * train_ratio) train_ids set(ids[:split]) val_ids set(ids[split:]) train_files, val_files [], [] for clip_id, flist in clips.items(): if clip_id in train_ids: train_files.extend(flist) else: val_files.extend(flist) return train_files, val_files注意参数 clip_key_len 必须和文件名规则匹配。如果文件名里片段 ID 占 8 位就传 8如果片段里还有同一路段来回折返的画面光按文件名分还不够。这种情况我一般录制时就给每个方向单独命名比如routeA_out_xxx和routeA_back_xxx保证同一物理路段的两个方向被当成不同片段处理。切分完生成 YOLOv8 训练自己的数据集时用的 data.yaml把 train 和 val 指向两个图片目录names 顺序和转换脚本里 cat2id 保持一致path: /data/car_ped train: images/train val: images/val names: 0: pedestrian 1: rider5. 训练与评估避坑5 个让车内行人检测翻车的典型问题公开集选了、自建集也建了进入训练环节才是真正的血泪现场。下面五条都是我做车载行人项目时反复踩过的坑每一条都按现象、原因、解决展开照着排查能少走很多弯路。5.1 val 指标好看实车却漏检帧级泄漏和同场景过拟合现象训练完 val mAP 到 0.85看起来不错装到实车上一跑换一条没走过的路就漏检而且漏掉的全是远距离行人。原因第一是划分时按“帧”而不是按“片段”随机切同一段视频的相邻帧分别进了训练集和验证集val 指标本质上是让模型背答案。第二是采集阶段 80% 的时长都跑在同一条固定线路上场景单一模型把路边店铺、护栏、树形都记成了背景特征一旦场景变了特征不匹配召回直接掉。解决统一用 4.4 的按片段划分脚本重切数据。然后统计训练集中不同片段的数量分布把某一条路线的图像占比压到 60% 以下不够就去补其它路段。验证集至少要包含两个以上从未参与训练的片区才算真正验证泛化能力。5.2 夜间大量误检调阈值也压不干净现象夜间测试视频里路灯杆、隔离墩、树影被反复检测成行人置信度集中在 0.3 到 0.5 之间同时真正的夜间远距离行人置信度也在 0.3 上下阈值一调高就两头丢。原因夜间真样本太少模型没有学到“夜行人”的纹理和轮廓只能靠亮度和边缘猜。另一个原因是夜间图像信噪比低暗区的局部对比度结构跟小目标行人高度相似误检很难靠后处理消除。解决第一不要只靠 HSV 增强和 mosaic 造假夜图必须补真实夜图样本最少五百张三个月以上的夜晚采集更好。第二训练时用mosaic1.0但设close_mosaic10让最后十个 epoch 回到真实图像分布避免模型过度依赖拼接边界的伪特征。第三推理时把置信度阈值调到 0.35 到 0.4NMS 的 IoU 从 0.45 提到 0.5减少同一目标的多框残留yolo detect train data/data/car_ped/car_ped.yaml modelyolov8n.pt \ epochs100 imgsz1280 batch16 lr00.01 \ mosaic1.0 close_mosaic10 scale0.3 device0 yolo detect predict modelruns/detect/train/weights/best.pt \ source/data/night_clip/ imgsz1280 conf0.35 iou0.5训练命令里的 imgsz1280 和 scale0.3 是配套的大分辨率保小目标随机缩放幅度控制在 0.3 避免行人尺度被过度扭曲。YOLOv5 也是同一套思路只是参数写在 hyp 文件里。夜间误检如果仍然压不住去统计误检框的位置分布集中在画面上半部分就单独对下半部分 ROI 降阈值这种按区域给不同阈值的做法在车内场景很实用。5.3 远处行人永远不报警现象20 米外的行人只有 40 到 60 像素高模型几乎全漏但 100 像素以上的行人召回很好。单独看指标会发现整体 AP 还行因为中近景样本占了大头小目标把指标“稀释”了。原因小目标样本占比不够是主因其次是输入分辨率太低。640 分辨率下 40 像素的行人在下采样后只剩十几个像素特征图里基本没有可分辨信息。解决输入分辨率直接提到 1280推理时也要保持一致否则训练和推理尺度不一致会白训。再把高度在 20 到 80 像素之间的样本在训练列表里复制一份作为“小目标重采样”。如果还是漏说明是检测头对小目标不敏感可以考虑在推理时对图像上部区域单独裁图放大检测一遍——远距离行人通常出现在画面中上方这个区域放大后等效于提高了小目标的有效分辨率。这是一个工程上常见的做法增加的算力只覆盖局部区域延迟可控。5.4 雨天玻璃起雾模型几乎瘫痪现象雨刮刮过之后玻璃上留一层水膜画面整体发灰发白模型不仅漏检还会把雨滴高光区域当成行人轮廓误检率翻倍。原因车内视角的成像链路跟普通街拍不一样玻璃上的水膜和雨滴散射是公开数据集几乎不会出现的物理现象。很多人试图用图像特效合成水珠但贴图水珠没有折射和散射模型学到的特征在真实雨滴上完全不通用。解决雨天必须采真实数据这是最可靠的路径。如果真实雨天样本不够可以把玻璃水膜前的原始图像做全局对比度校正等效模拟“透过干净玻璃看到的场景”但不能延展成模型在真实雨天的表现。推理链路加一个轻量的 CLAHE 预处理会有帮助但不要上重的去雾模型车载场景对延迟敏感预处理超过 10 毫秒就不划算。5.5 行人和骑车人互相混淆类别不平衡拖垮召回现象骑电动车的人有一半被标成行人另一半直接漏检检查发现模型整体 mAP 不低但单独看 rider 这个类别的 AP 可能不到 pedestrian 的一半。原因标注阶段把部分骑手标成了 pedestrian模型学到的是“有人的轮廓就归类到样本量大的那类”。这是典型的类别不平衡加标注噪声叠加问题。解决第一从标注上把 rider 单独分出来不要合并。第二公开集里补 rider 样本BDD100K 的 rider 类别就可以CrowdHuman 全是行人补不了骑手。第三给 rider 类别做两倍过采样或者用 loss 权重把该类别的损失放大。评估时必须分两个类分别算 AP不要只看整体数字——整体数字会掩盖尾部类别的崩坏。我现在的固定动作是每轮训练完把两个类别的 PR 曲线单独画出来如果 rider 的 AP 比 pedestrian 低超过十五个点就停止调参回去补数据。6. 最后一公里按可见度分层评估和 hard set 迭代法训练指标好看不等于系统可用。真正决定车内视角行人识别项目能不能落地的是那些被遮挡、夜间、小尺度样本上的表现。整个数据集做完后还有一个容易被忽略的最后一公里。写一个按遮挡等级分组算 AP 的评估脚本比只看整体 mAP 有用得多。把验证集的预测结果按 occ 字段分成四组分别调用你现有的 AP 计算逻辑def per_occ_ap(preds, targets, occ_fieldocc): for occ_level in range(4): mask targets[occ_field] occ_level if mask.sum() 0: print(focc{occ_level}: no samples) continue ap compute_ap(preds[mask], targets[mask]) # 替换成你的评测函数 print(focc{occ_level}: AP{ap:.3f})如果 occ3 的 AP 比 occ0 低超过三十个点说明严重遮挡样本不足下一步就专门去拍行人在车尾、在站台、在绿化带后被遮挡的片段而不是继续调训练参数。同一份 hard set 每次训练更新后都跑一遍。我一般固定留两百张夜间、雨天、逆光的图集训练完先跑一遍记录漏检文件名对比上一轮是否引入新的回归。我现在每接一个新数据项目第一件事不是马上训练而是把 val 按可见度切开、把 hard set 固定下来先让模型在最难的样本上不崩再去追整体指标。这个习惯帮我挡过不少次翻车希望帮到你。本文还有配套的精品资源点击获取