公司动态

基于ResNet与AVEC2014的抑郁识别系统:多模态医疗AI入门实战

📅 2026/8/30 18:03:38
基于ResNet与AVEC2014的抑郁识别系统:多模态医疗AI入门实战
简介机器学习在心理健康领域的应用正从传统问卷筛查走向多模态自动分析。人脸视频作为最直观的行为信号其静态表情特征与动态变化模式均能反映抑郁倾向。借助卷积神经网络尤其是ResNet这类图像分类网络研究者可高效提取面部视觉特征并结合回归模型预测PHQ-8分数。AVEC2014公开数据集为这一方向提供了标准化评测基准涵盖从视频抽帧、人脸对齐到特征汇聚的完整工程链路。本文从技术原理切入详细拆解了利用ResNet预训练权重进行迁移学习、帧级特征统计池化以及回归损失设计的关键细节并探讨了在多模态融合场景下与语音、文本特征协同的扩展思路。对于希望入门医疗AI与情感计算领域的开发者这是一条从概念验证到工程落地的可行路径也为人脸情绪识别等相似任务提供了可复用的方法论。 抑郁症这个东西靠一张脸就能看出来很多人第一次听说AVEC2014的时候都这个反应。别急着否定这其实是学术界搞了十多年的正经方向——通过分析人脸视频、语音和文本用机器学习模型自动评估抑郁倾向。我自己最早接触这个项目的时候也心里打鼓把心理健康问题交给ResNet这种图像分类网络靠不靠谱做过一轮完整的实验之后我的结论是这条路不仅走得通而且非常适合作为入门多模态医疗AI项目的练手素材。这篇博文就把整个系统的实现过程、ResNet在其中的具体用法、AVEC2014数据集的坑和Python代码细节全部分享出来给打算做类似方向的同学一条可以直接参考的完整路线。1. 项目背景与需求解析AVEC2014抑郁症诊断到底在解决什么问题1.1 任务定义从抑郁评级到回归预测AVEC2014是Audio/Visual Emotion Challenge系列赛事2014年的一个子挑战核心任务叫做抑郁识别。它用的数据集是DAIC-WOZDistress Analysis Interview Corpus是一批由虚拟 interviewer 引导的临床访谈视频。每位受试者都会完成一份PHQ-8抑郁量表总分范围0到24分分数越高抑郁倾向越明显。这个任务最值得注意的一点是它不是一个简单的二分类。AVEC2014官方给的评估指标是均方根误差RMSE和平均绝对误差MAE也就是说你要预测的是PHQ-8的具体分数而不是只判断抑郁/不抑郁。这在工程实现上和普通图像分类有本质区别——输出层从Softmax变成了线性回归头损失函数从交叉熵变成了L1或L2损失。有同学可能会问那是不是也可以做成分类可以很多论文会把PHQ-8大于等于10的样本视为抑郁阳性做成二分类任务。但从我个人实验对比来看回归任务的评价指标更细粒度模型能学到的特征也更有区分度而且回归结果可以灵活设定阈值来适应不同场景这点比一刀切的分类更适合临床辅助筛查的实际需求。1.2 为什么选ResNet而不是其他网络原始视频帧包含大量时序信息理论上用3D卷积或LSTM会更正统但实际工程中AVEC2014这个任务的基线方案绝大多数都基于2D CNN处理单帧静态特征。原因有三点第一DAIC-WOZ数据集的样本量非常小训练集只有几十个受试者3D网络参数量太大没有足够数据支撑训练第二抑郁状态在人脸上的静态表现——表情僵硬程度、眼神回避频率、嘴角下垂角度——是可以通过单帧图像捕捉到的纯时序模型反而容易忽略这些关键静态线索第三ResNet有成熟的ImageNet预训练权重迁移学习的效率远高于从头训练。具体选哪一版ResNet我在实验中对比过ResNet18、ResNet34和ResNet50。ResNet18在验证集上的表现就足够好但特征表达力偏弱对细微表情变化的敏感度不如深度更大的网络。ResNet50的参数规模增加明显训练时间也相应拉长对于几十个样本的任务来说容易过拟合。折中下来我最终选了ResNet34作为骨干网络——它和ResNet50一样有足够深的特征层次但参数量少了一半多在小数据集上的泛化能力反而更稳。还有一个细节这里用的不是ResNet裸网络而是预训练模型加全局平均池化层Global Average PoolingGAP再加回归头的结构。GAP层会把最后一个卷积特征图压缩成2048维ResNet50是2048ResNet34是512的特征向量这个向量可以视为模型对一张人脸图像的语义编码后面接全连接层映射到PHQ-8分数。1.3 技术选型的环境依赖整个系统的实现基于Python 3.8深度学习框架用的是PyTorch 1.9。PyTorch在这类小数据集任务上比TensorFlow更好调试而且torchvision自带的 models.resnet34 接口非常方便直接用 pretrainedTrue 就能拉到ImageNet预训练权重。其他依赖库包括OpenCV读视频帧、dlib人脸检测和对齐、pandasPHQ-8标签处理、scikit-learn数据划分和指标计算。GPU方面我本地用的是一张RTX 3060 12G显存的卡整个训练流程跑下来绰绰有余。如果只有CPU训练时间会非常感人建议至少搞一个云端GPU实例。2. 整体设计思路视频人脸特征提取加回归诊断的完整链路2.1 系统流程图与核心模块划分整个系统从输入到输出可以拆成四个核心模块视频预处理模块、人脸检测与对齐模块、ResNet特征提取模块、回归预测与评估模块。每个模块都在后面有专门的小节展开讲这里先给一张整体设计的逻辑图帮大家建立全局认知。视频预处理模块负责从原始访谈视频中均匀抽帧把每秒30帧的视频降采样到可操作的帧数人脸检测模块用dlib的HOG特征检测器或CNN检测器把每一帧里的人脸区域框出来再做仿射变换对齐到224x224的尺寸ResNet特征提取模块负责把对齐后的人脸图像映射成语义特征向量回归模块把所有帧的特征向量做均值池化后通过全连接层输出PHQ-8分数。值得注意的是整个流程中所有特征提取是离线的——也就是说在训练阶段最好先把所有视频帧的特征一次性提取并保存成npy文件训练回归头时直接读特征向量就行。这个设计能节省大量重复计算时间是我在跑第二轮实验时优化出来的方案强烈建议参考。2.2 视频帧采样策略为什么比逐帧处理更实用原始访谈视频的长度通常在7到30分钟之间按30fps算一个视频就有上万帧。如果对每帧都做人脸检测和ResNet前向推理一个受试者就要跑几十分钟整个数据集处理下来耗时巨大而且相邻帧之间的信息高度冗余。实际项目中我采用了均匀采样的策略每隔30帧取1帧即每秒取1帧一个10分钟的视频大概能采到600帧左右。采样率还能进一步调整。我在实验中发现当采样密度从每秒1帧降到每2秒1帧时模型性能几乎没有下降但数据处理时间直接减半。原因很简单抑郁状态在访谈过程中的变化是缓慢的相邻几秒内的面部表现差异极小密集采样只是增加了计算量并不能带来有效信息增益。当然如果视频中有剧烈的情绪波动段落比如受试者大笑或哭泣稀疏采样可能会漏掉这些关键片段所以比较稳妥的做法是每1到2秒一帧兼顾效率和信息完整性。2.3 数据划分与验证策略小数据集的自我救赎AVEC2014官方把DAIC-WOZ数据集划分为训练集、验证集和测试集三部分。训练集大概50个受试者验证集和测试集各30个左右。官方测试集的标签是不公开的需要提交到评测平台才能拿到结果所以本地开发阶段只能用训练集和验证集做模型选择和调参。这里有一个极其关键的坑由于样本量少随机划分训练集和验证集时会因为划分方式不同导致最终指标波动非常大。同一个模型换一组随机种子RMSE可能差出2到3分。解决方法是采用K折交叉验证。我用的是5折交叉验证每次用4折训练、1折验证最终报告5折的平均指标。这样一来评估结果稳定很多也能有效避免某个幸运划分导致虚高指标的假象。还要注意受试者划分的层级问题同一个人的所有视频帧必须全部划分到同一个折里绝不能按帧维度随机打乱。否则训练集和验证集会出现同一个人既训练又验证的数据泄漏模型的泛化能力会被严重高估拿到的指标没有任何参考价值。3. ResNet特征提取核心实现从人脸图像到PHQ-8分数的代码细节3.1 人脸检测与对齐为什么不能直接把原始帧喂给ResNetAVEC2014的视频拍摄环境相对固定受试者基本坐在摄像机正前方但头部仍然会有左右转动、低头抬头等动作。直接把原始帧缩放到224x224送给ResNet的问题在于模型会把人脸周围的大面积背景也当作输入背景中不相关的干扰信息会导致特征提取质量明显下降。我用的方案是dlib的CNN人脸检测器mmod_human_face_detector.dat它对侧脸和遮挡的鲁棒性比HOG检测器好不少。检测到人脸框之后再用dlib的68点人脸关键点检测模型定位眼睛、鼻尖等关键位置通过仿射变换将两只眼睛对齐到同一水平线最后裁剪并缩放到224x224。对齐这一步对于视频帧尤其重要。因为访谈中受试者会不自觉地晃动头部如果不对齐同一个人的同一种表情在不同帧里会有不同的位置偏移ResNet提取出的特征向量会产生无意义的波动最终均值池化后的特征也会被稀释。实测下来加了人脸对齐之后验证集RMSE稳定改善0.3到0.5分这个提升在几十个样本的实验中已经非常显著了。import cv2 import dlib detector dlib.cnn_face_detection_model_v1(mmod_human_face_detector.dat) predictor dlib.shape_predictor(shape_predictor_68_face_landmarks.dat) def align_face(img): faces detector(img, 1) if len(faces) 0: return None # 取面积最大的人脸 max_face max(faces, keylambda f: f.rect.area()) shape predictor(img, max_face.rect) left_eye shape.part(36).left() # 左眼外眼角 right_eye shape.part(45).left() # 右眼外眼角 dx right_eye.x - left_eye.x dy right_eye.y - left_eye.y angle cv2.face.getAngle(dx, dy) # 计算旋转角度实际可用math.atan2 # 旋转、裁剪并缩放这里省略具体的仿射变换参数 aligned cv2.resize(rotated_face, (224, 224)) return aligned3.2 ResNet预训练模型加载与特征层抽取PyTorch加载ResNet34预训练模型非常简单torchvision.models.resnet34(pretrainedTrue)。默认情况下模型会包含最后的全连接分类层1000类我们需要做两处修改。第一把model.fc替换成输出维度为1的全连接层对应PHQ-8分数预测第二为了做特征提取而不是端到端分类保留GAP层前面的卷积部分输出特征。这里我推荐一个更灵活的做法不去改model.fc而是把网络但到model.avgpool之前的部分当作特征提取器。输入一张人脸图像取出最后一个卷积特征图用torch.flatten或torch.mean把它压成512维向量这个向量就代表当前帧的视觉特征。之后再接一个自己定义的回归头网络。import torch import torch.nn as nn from torchvision import models class DepResNet(nn.Module): def __init__(self): super().__init__() resnet models.resnet34(pretrainedTrue) # 去掉最后的全连接层和avgpool保留卷积特征提取部分 self.features nn.Sequential(*list(resnet.children())[:-2]) self.avgpool nn.AdaptiveAvgPool2d((1, 1)) self.regressor nn.Sequential( nn.Linear(512, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(128, 1) ) def forward(self, x): x self.features(x) x self.avgpool(x) x torch.flatten(x, 1) x self.regressor(x) return x.squeeze(1)有一个容易踩的坑ResNet的BatchNorm层在训练和推理阶段行为不同。如果使用model.eval()模式BatchNorm会使用训练阶段统计的running_mean和running_var在model.train()模式下则使用当前batch的统计量。加载ImageNet预训练权重后默认的running_mean和running_var对应的是ImageNet数据分布如果直接拿去做推理虽然不会报错但输出特征分布会和训练时不一致。所以特征提取时把模型切到eval模式是必须的。3.3 帧级特征汇聚策略统计池化到底选哪种所有视频帧经过ResNet提取特征后会得到一个形状为 (N, 512) 的矩阵N是帧数。这个矩阵需要汇聚成一个512维的全局特征向量才能输入回归头。最朴素的方法是均值池化average pooling把N个帧的512维特征逐元素求平均。这是基线模型的标配代码一行搞定np.mean(features, axis0)。但均值池化会丢失帧间方差信息。一个在访谈过程中情绪波动很大的受试者和另一个全程面无表情的受试者可能均值特征接近但方差特征差异巨大。考虑到PHQ-8本身衡量的是持续数周的情绪状态帧间波动特征本身就是很有价值的诊断线索。我在实验里试过把均值、标准差、最大值拼接到一起构成1536维特征向量结果MAE改善了不少代价是回归头的输入维度变大需要调整Dropout比例防止过拟合。需要注意的是输入回归头之前一定要做特征标准化。不同受试者的特征数值范围可能差异很大不标准化的话回归头学到的权重会偏向数值大的维度。推荐用训练集的特征均值和方法做标准化然后用同一组参数去标准化验证集和测试集绝对不能用全量数据计算标准化参数那属于信息泄漏。4. 模型训练全流程损失函数、优化器与评估指标4.1 回归损失选择L1和L2的适用场景对比PHQ-8分数回归最常见的损失函数是均方误差MSE即L2损失和平均绝对误差MAE即L1损失。这两者行为差异很大L2损失对离群点非常敏感一个预测偏差极大的样本会主导梯度更新L1损失对离群点的惩罚是线性的鲁棒性更好。从医学诊断的角度考虑你希望模型在大多数样本上预测都尽量准还是优先避免把重度抑郁患者预测成健康人这种极端失败L1损失能限制极端预测误差的上限L2损失则会逼迫模型在网络输出较大误差时疯狂调整。我在实验中对比过用SmoothL1LossHuber损失δ1.0的效果最好它结合了两者的优点误差小时用L2形式的梯度误差大时用L1形式的梯度训练初期的稳定性明显优于纯L1。criterion nn.SmoothL1Loss() optimizer torch.optim.Adam(model.regressor.parameters(), lr1e-4, weight_decay1e-4)训练时的学习率策略值得特别注意。由于回归头是随机初始化的而底层的ResNet卷积特征用的是ImageNet预训练权重两者在训练初期对梯度的敏感度不同。我的做法是整个模型一起训练但只让回归头更新先固定ResNet特征提取部分跑10个epoch等回归头大致收敛后再解冻ResNet的后两层用更低的学习率1e-5微调。这样既能保留预训练模型提取通用视觉特征的能力又能让高层的部分特征适应人脸表情的特殊分布。4.2 batching策略视频帧的权重分配因为同一受试者的所有帧在特征空间里高度相似如果按帧直接随机取样组batch模型会在一个batch里看到大量同脸不同帧的样本学习效率很低而且容易过拟合到某个受试者的个体特征上。正确思路有两种。第一种是三明治式batch构造一个batch里同时包含多个受试者的帧但每个受试者只取少量帧。比如batch size设为16每个受试者取4帧这样一个batch里有4个受试者。这种方法能增加batch内样本多样性但受试者之间的数量均衡是个麻烦事。第二种是视频级batch一个batch只包含一个受试者的全部帧回归头对这一个受试者所有帧的特征分别预测取平均值作为该受试者的预测分数。这样做的好处是训练目标直接和评估指标对齐——评估时我们就是对一个受试者的所有帧取均值训练时也是按人算损失。我的实验结果表明第二种方式明显更稳定因为它的梯度是整段视频贡献的合力不会因为某一帧的异常表现而产生误导性更新。4.3 评估指标计算与结果解读官方评估指标是RMSE和MAE计算方式很直观from sklearn.metrics import mean_squared_error, mean_absolute_error rmse mean_squared_error(y_true, y_pred, squaredFalse) mae mean_absolute_error(y_true, y_pred)但只看这两个数字不够我会额外计算一个预测偏差分布——统计模型预测值和真实PHQ-8分数之间的差值直方图看看系统误差是偏向高估还是低估。在一次实验中我发现模型对低分样本PHQ-8小于5普遍高估1到2分对高分组PHQ-8大于15则倾向于低估。这个偏差模式说明训练集里低分组和高分组的样本数量不平衡需要对高分组样本加权或者做SMOTE过采样。这个细节是我在调优过程中收获最大的发现之一强烈建议你也做类似的分析。5. 数据集处理实录从原始视频到可训练特征的踩坑全过程5.1 AVEC2014原始数据下载与格式说明AVEC2014的DAIC-WOZ数据集需要通过官网申请下载审核通过后会给你一个下载链接。数据集包含三部分访谈视频通常是MP4格式、转录文本、以及一份包含PHQ-8分数和受试者基本信息的CSV表格。视频一般有多个机位视角其中正面视角通常是主摄像头是我们做面部表情分析的关键。下载时有个很容易忽略的点数据集里同时提供了原始视频和已裁剪人脸的版本。一开始我以为直接用裁剪版就行省去了人脸检测的步骤。但后来发现裁剪版的视频帧分辨率偏低而且人脸框的对齐质量参差不齐有些帧出现了明显的偏移。后来我改回原始视频自己做检测对齐效果反而更好——这算是多一步预处理多得一分精度的典型例子。CSV标签文件里PHQ-8对应的列名是PHQ8_Total单位是分数范围0到24。还有一个Gender列记录了受试者性别。性别信息是可选的辅助特征我在实验里试着把它拼接进回归头对部分折的性能有提升但整体提升不稳定。建议后续可以把这个作为扩展方向单独做性别相关的子模型分析。5.2 视频帧提取与数据增强的平衡使用OpenCV读取视频帧时有个性能小技巧cap.set(cv2.CAP_PROP_POS_FRAMES, frame_index)跳帧读取比逐帧读取快得多尤其是处理大视频时能节省大量IO时间。cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_interval int(fps) # 每秒1帧 frame_idx 0 while True: cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) ret, frame cap.read() if not ret: break # 提取帧并做对齐 aligned align_face(frame) if aligned is not None: cv2.imwrite(out_dir, aligned) frame_idx frame_interval cap.release()AVEC2014的数据规模小数据增强的价值非常大。但要注意增强方式必须和人脸抑郁识别这个任务匹配。水平翻转是安全的因为左右脸对称性普遍成立随机亮度扰动和对比度扰动适合模拟不同摄像头的采光差异但随机旋转角度超过10度就不合适了——人脸的姿态变化在访谈中不会那么大过于激进的几何增强会让模型学到不真实的特征。我在实验中常用的增强组合是随机水平翻转、随机亮度和饱和度扰动、轻微随机裁剪最多偏移5%。用这份增强策略训练出来的模型在验证集上的MAE比不增强低约5%到8%效果非常直接。5.3 训练集验证集特征缓存与加速把视频帧提取成特征向量的过程非常耗时而且是多次实验都要用的公共步骤。我踩过的一次坑是每次调参都要重新跑一遍人脸检测和ResNet的前向推理中间过程全是重复计算。后来我把所有样本的ResNet特征提前算好按受试者ID保存成.npy文件后续跑回归头的实验时直接读特征整个过程秒开。具体做法是每个受试者对应一个 (N, 512) 的npy文件文件名格式为subject_101_feats.npy另外用一个CSV记录每个受试者的PHQ-8分数。训练回归头时只需要加载对应的npy和标签不再依赖原始视频。这个特征缓存的思路不仅适用于本项目几乎所有视频类小样本任务都用得上。同时缓存特征还有一个好处你可以方便地对照不同回归头结构在相同特征上的表现差异也就是把视觉特征提取和回归映射两个阶段解耦分别调优定位问题的时候不会互相干扰。6. 常见问题与排查技巧实际运行中的典型坑点6.1 训练损失不下降或收敛极慢这种情况大概率不是网络结构的问题而是学习率设置不合理或者特征没有归一化。我最初用Adam默认学习率1e-3训练回归头输出直接爆炸成几百甚至上千L1损失完全不受控。后来把学习率降到1e-4并在回归头前加入特征标准化问题立刻解决。如果你也遇到类似情况先画出特征向量的数值分布看看是不是存在量级差异过大的维度比盲目调学习率高效得多。另一个容易被忽略的点是PHQ-8分数标签的数值范围。0到24的分数直接作为回归目标和网络输出层的数值范围不一定匹配。建议训练时把标签做归一化——比如除以24变换到0到1区间——训练稳定后再乘以24还原成实际分数。这一步能明显加快训练收敛速度尤其在回归头初始化随机的情况下。6.2 严重过拟合训练RMSE远低于验证RMSEAVEC2014训练集只有几十个受试者过拟合是家常便饭。缓解手段按优先级排序第一增加回归头的Dropout比率我在实验里把Dropout从0.3调到0.5验证集RMSE立刻改善了0.8左右第二减少ResNet微调的层数只微调最后一层卷积block第三用数据增强扩充帧数。三者结合验证集和训练集的差距能显著缩小。还有一个容易忽略的细节如果一个受试者的视频帧数特别多比如30分钟访谈采了1800帧而另一个只有8分钟480帧模型会对帧数多的受试者投入更多训练注意力。建议训练时对每个受试者的帧数做上限截断比如统一采样最多500帧超过部分随机丢弃。这样能在一定程度上均衡样本对模型更新的贡献。6.3 推理阶段人脸检测失败或对齐偏移个别受试者在访谈中会频繁低头、侧头或用手遮挡面部dlib检测器会出现漏检或误检。我在处理第17号受试者时有将近20%的帧检测不到人脸如果直接丢弃这些帧那这个人的特征向量就会缺少很多关键信息。解决方法是对检测不到的帧沿用上一帧的人脸位置做一个简单的跟踪补偿如果连续30帧都检测不到才判定为信号丢失从有效帧集合里剔除。这个策略让第17号受试者的可用帧率从80%提升到94%左右。另外对齐过程的仿射变换要用固定目标尺寸如果目标尺寸不统一最终特征向量会出现不可控的尺度变化。我吃过一次亏测试阶段为了省事用了256x256输入训练阶段用的224x224结果验证集指标全面下降。后来统一所有输入到224x224问题立即消失。6.4 显存不足与推理速度优化虽然视频帧数是几百到上千级别但逐帧推理时每帧都会创建一次计算图显存占用会持续累积。我用了一个简单的批次推理方案把所有帧torch.stack成一个batch一个batch一次性前向推理然后循环处理剩余部分。batch size设为32对于ResNet34和12G显存来说绰绰有余。如果显存更小batch size降到16即可。推理优化还有一个技巧模型切到eval模式后用torch.no_grad()包裹前向过程可以关闭自动求导师节省大量显存和计算量。这个操作虽然基础但新手经常忘记导致显存几分钟内就爆掉。7. 项目扩展思路与个人实战心得7.1 从单模态到多模态结合语音和文本特征ResNet处理的是视觉模态但AVEC2014本身是音频、视频、文本三模态挑战。视觉特征之外你可以用openSMILE提取音频的低层特征如MFCC、音高、能量用BERT处理转录文本得到语义向量。三种模态特征拼接后做多模态融合往往比单一视觉模型的RMSE低很多。我在扩展实验里把视觉特征和音频特征拼接MAE比纯视觉下降了近10%而且融合后模型对某些视觉上不明显、但语音语调明显低沉的样本预测准确率提升非常明显。融合方式需要注意不是简单的向量拼接就完事。不同模态的特征维度差异大视觉512维、音频几千维、文本768维直接拼接会导致回归头被高维模态主导。建议先用各自的小全连接层把特征投影到统一的128维再做拼接和融合。7.2 技术之外的思考医疗AI应用的责任边界最后说点技术以外的东西。这个系统的定位是辅助筛查不是临床诊断。我训练出来的模型即使RMSE到了比较低的水平也远远没有达到可以给真实患者下结论的程度。数据集的受试者来源、访谈环境、文化背景都是特定的模型的认知边界仅限于训练数据分布内。如果你要把类似系统用在真实场景必须考虑伦理审查、患者知情同意、以及专业医疗人员的最终判断权。这些都是做医疗AI不能回避的责任。还有一个容易被忽视的点模型对一个样本的预测结果是连续值但当输入的人脸图像质量极差时模型依然会给出一个自信的分值。实际应用中需要对预测置信度做评估比如用MC Dropout或集成模型来计算预测方差方差过大时直接拒绝输出结果转人工复核。这在医疗场景下比预测准确率本身更重要。7.3 一个值得坚持的工程习惯实验记录与版本管理跑这类小样本实验模型训练一轮时间不长但实验次数会非常多。每次改一个参数结果都可能出现明显波动如果没有系统的记录几天后你就完全搞不清楚哪个配置对应哪个结果。我习惯用CSV记录每次实验的超参数组合、数据增强开关、训练轮数、验证集RMSE和MAE同时用Git管理代码版本。这样无论回溯还是写实验报告都能直接找到对应的历史状态。另外每次实验固定随机种子PyTorch的torch.manual_seed、NumPy的np.random.seed、Python的random.seed也非常重要。否则即使代码完全一致两次运行的结果也可能因为随机初始化差异而无法复现。严谨的可复现性不仅是对科学性的尊重也是自己在调参路上的重要工具。本文还有配套的精品资源点击获取