ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

ResNet50+注意力机制:人脸表情识别消融实验从零实现

ResNet50+注意力机制:人脸表情识别消融实验从零实现 简介一套基于PyTorch 1.8构建的人脸表情识别消融实验项目源码面向深度学习初学者或研究人员非常适合用于对比VGG16、ResNet50、InceptionV3及自定义CNN在融合CBAM、SE、ECA三种注意力机制后的识别效果并已在FER2013与RAF数据集上得出ResNet50CBAM精度最高的结论。资源包内共包含有18个文件以7个Python脚本为主体涵盖模型定义、注意力模块、数据重写与训练全流程另含3张示意图、2个Markdown说明及若干keep占位文件整个压缩包体积仅244KB结构轻量清晰。项目按功能划分dataloader、dataset、logs、result等模块方便直接替换数据集并记录训练日志与测试结果日志与结果目录已预置便于快速复现实验并观察模型收敛情况。目前已有443人学习下载适合需要复现多模型组合实验、开展表情识别对比研究或作为课程设计的参考实现。1. 多模型消融实验表情识别项目里ResNet50和Attention到底谁在起作用人脸表情识别在真实场景里是个“看着简单、落地发愁”的问题拍糊了、转头了、光线偏了精度就掉。很多人第一反应是把ResNet50换成ResNet101或者干脆换更大模型结果训练时间翻倍准确率只涨零点几个点。这个项目标题给的思路是反过来——固定ResNet50这类骨干在它身上加Attention模块再用消融实验逐个验证“模块加了到底有没有用、提升来自哪一层”。它适合两类人一类是想在表情识别这个任务上拿到更高精度、并且能解释清楚提升来源的开发者另一类是正在做课程设计或毕业设计、需要一套严谨对比实验来支撑结论的同学。把这条技术路线拆开你会发现消融实验比单纯堆模型更能说明问题而且实现成本远低于换一个更大的backbone。2. 搭建实验基线数据预处理与ResNet50的基准实现2.1 数据集选择与预处理灰度表情图怎么喂给ImageNet预训练模型表情识别常用公开数据集大多是48×48的灰度图以FER2013这类数据集为例它包含生气、厌恶、恐惧、开心、悲伤、惊讶、中性七类。拿到原始CSV后pixels列是空格分隔的像素值字符串需要先还原成二维矩阵再转成图像。这里有个关键细节ResNet50的预训练权重是在ImageNet上训的输入是三通道RGB而表情数据集是单通道灰度图。常见做法是把灰度图复制成三通道再套用ImageNet的标准化参数这样能完整复用预训练特征。import numpy as np import cv2 from torch.utils.data import Dataset class FERDataset(Dataset): def __init__(self, df, transformNone): self.df df self.transform transform def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] # pixels列是空格分隔的字符串先转成uint8矩阵 arr np.array(row[pixels].split(), dtypenp.uint8).reshape(48, 48) # 灰度转三通道为了匹配ResNet50的预训练输入 img cv2.cvtColor(arr, cv2.COLOR_GRAY2RGB) label int(row[emotion]) if self.transform: img self.transform(img) return img, label这里把灰度图复制成三通道是最稳的写法不要去训练单通道的ResNet50变体那会丢掉ImageNet预训练的大部分价值。预处理阶段还需要注意数据划分FER2013这类数据集通常自带官方划分直接用就行如果数据集没有划分建议按训练:验证:测试8:1:1做分层切分保证每个子集里七类比例一致。分层切分很重要否则验证集里“开心”占比过高评估结果会虚高。数据增强部分要克制。表情识别任务里翻转和轻微仿射变换是安全的但不要用随机裁剪因为人脸居中的位置信息对表情判断有影响。我一般这样配from torchvision import transforms train_transform transforms.Compose([ transforms.ToTensor(), transforms.RandomHorizontalFlip(p0.5), transforms.RandomAffine(degrees10, translate(0.1, 0.1)), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) test_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])注意ToTensor一定要放在最前面先做张量转换再做增强和标准化。RandomAffine里的translate给0.1就够了给大了会把眼睛嘴巴移出人脸区域反而让模型学到错误的位置关联。2.2 搭建ResNet50基线改全连接层和训练循环基线模型用torchvision自带的ResNet50替换最后的全连接层把1000类输出改成7类。新版torchvision推荐用weights参数来加载预训练权重写法更明确。import torch import torch.nn as nn import torchvision.models as models def build_backbone(base_nameresnet50, num_classes7, pretrainedTrue): if base_name resnet50: model models.resnet50( weightsmodels.ResNet50_Weights.IMAGENET1K_V1 if pretrained else None ) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif base_name resnet18: model models.resnet18( weightsmodels.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None ) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model这个build_backbone函数后面做多模型对比时会反复用到第4章的消融实验也依赖它。注意ResNet50每个Bottleneck的输出通道数不是固定的所以替换fc层时一定要用model.fc.in_features动态读取不要硬编码2048否则切到ResNet18或ResNet101时会出错。预训练权重加载上去后前几轮训练要特别小心主干网络的梯度不要太大否则会破坏已经学好的底层特征。训练循环用固定的流程pytorch标准写法但有两个点值得强调一是优化器要区分主干和新增层的学习率二是loss要按类别分布加权。from torch.utils.data import DataLoader train_loader DataLoader(train_ds, batch_size64, shuffleTrue, num_workers4, pin_memoryTrue, drop_lastTrue) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue) optimizer torch.optim.AdamW([ {params: model.conv1.parameters(), lr: 1e-5}, {params: model.bn1.parameters(), lr: 1e-5}, {params: model.layer1.parameters(), lr: 1e-5}, {params: model.layer2.parameters(), lr: 1e-5}, {params: model.layer3.parameters(), lr: 1e-5}, {params: model.layer4.parameters(), lr: 1e-5}, {params: model.fc.parameters(), lr: 1e-3}, ], weight_decay1e-4) criterion nn.CrossEntropyLoss()这个配置背后的逻辑是预训练层的学习率给1e-5只做微调新加的fc层从零开始学给1e-3。如果所有层都用同一个学习率要么预训练特征被冲掉要么fc层学不动两种情况在表情识别上都表现过后者尤其常见。batch size在48×48这种小图上可以给64或128显存不够就降到32。2.3 评估协议只看准确率的项目走不远很多人跑完只看一个Top-1准确率就开始对比这是消融实验最容易翻车的习惯。FER2013这类表情数据集类别极不均衡“开心”样本可能接近“厌恶”样本的5倍以上模型只要把多数类学好了准确率就能到百分之六七十但“生气”“厌恶”“恐惧”这几个类可能完全被牺牲掉。我常用的评估指标有三个准确率、宏平均F1、每类Recall。准确率是给外行看的总分宏平均F1才是模型真实能力的度量。记录结果时三个都要存。每类Recall用来精确定位模型混淆在哪几类上表情识别里最常见的是“生气”和“厌恶”互相混。from sklearn.metrics import accuracy_score, f1_score, recall_score, confusion_matrix def evaluate(model, loader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in loader: imgs imgs.to(device) outputs model(imgs) preds outputs.argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) acc accuracy_score(all_labels, all_preds) macro_f1 f1_score(all_labels, all_preds, averagemacro) recall_each recall_score(all_labels, all_preds, averageNone) cm confusion_matrix(all_labels, all_preds) return acc, macro_f1, recall_each, cm注意评估阶段要关闭梯度计算省显存且速度快。每次跑完把acc、macro_f1、recall_each、cm四个结果都存档后面做消融实验分析时才有的放矢。基线模型的macro_f1一般会比准确率低三到五个点这就是类别不均衡的直接体现。看到这个差距先别慌这是正常现象后续加Attention能不能把这个差距拉小才是这个项目真正要回答的问题。提示无论后续做什么改造基线的训练轮数、数据增强、loss函数、优化器配置都必须先冻结。消融实验的全部意义在于“只改变一个变量”基线不稳定后面所有结论都不可信。3. 给ResNet50加Attention三种注意力机制的接入点与实现3.1 SE模块通道维度上的挤压激励最容易接入的注意力SE模块的思路很直接对特征图的每个通道做全局平均池化得到通道描述符再过两个全连接层学习通道之间的依赖关系最后用sigmoid生成0到1之间的通道权重乘回原特征图。它没有用到空间信息只关注哪些通道更重要对于表情识别来说深层网络里某个通道可能对应“嘴角上扬”还是“皱眉”这种局部模式SE能自动放大这些关键通道。import torch import torch.nn as nn class SEBlock(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels), nn.Sigmoid() ) def forward(self, x): b, c, _, _ x.shape w self.pool(x).view(b, c) # 压缩成通道描述符 w self.fc(w).view(b, c, 1, 1) # 生成通道权重 return x * w # 加权原特征图接入点推荐放在Bottleneck残差分支的末尾也就是最后一个1×1卷积的输出之后、与残差相加之前。这样SE作用在“本次卷积学到的新特征”上而不是作用在加和之后的混合特征上归因更清晰。reduction取16是默认值通道数为1024时先压到64再扩回来参数开销约等于一个全连接层的参数量对整体训练速度影响很小。取8会更激进保留更多通道信息但参数量翻倍取32则信息挤压过狠在表情这类小数据集上容易欠拟合。我建议两个都试放在消融实验矩阵里当一组对比。3.2 CBAM通道注意力加空间注意力位置怎么摆最关键CBAM比SE多了一个空间注意力分支通道注意力部分同时利用平均池化和最大池化两个描述符共享同一个MLP然后相加过sigmoid。空间注意力部分把通道维做平均和最大两种池化拼成两通道特征图过一个7×7卷积生成空间权重。对表情识别来说空间注意力有可能定位到眼部和嘴部区域理论上比纯通道注意力更契合任务特性。class ChannelAttention(nn.Module): def __init__(self, channels, reduction16): super().__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.max_pool nn.AdaptiveMaxPool2d(1) self.mlp nn.Sequential( nn.Linear(channels, channels // reduction), nn.ReLU(inplaceTrue), nn.Linear(channels // reduction, channels), ) def forward(self, x): b, c, _, _ x.shape avg_out self.mlp(self.avg_pool(x).view(b, c)).view(b, c, 1, 1) max_out self.mlp(self.max_pool(x).view(b, c)).view(b, c, 1, 1) return torch.sigmoid(avg_out max_out) class SpatialAttention(nn.Module): def __init__(self, kernel_size7): super().__init__() self.conv nn.Conv2d(2, 1, kernel_size, paddingkernel_size // 2, biasFalse) def forward(self, x): avg_out torch.mean(x, dim1, keepdimTrue) max_out torch.max(x, dim1, keepdimTrue).values x_cat torch.cat([avg_out, max_out], dim1) return torch.sigmoid(self.conv(x_cat))CBAM的接入点和SE不一样常见做法是放在残差加和之后、ReLU之前。也就是先让残差分支和恒等映射加起来再对融合后的特征做通道和空间加权最后过ReLU。这样注意力能看到完整的残差信息而不是只看分支单侧。kernel_size用7这是原作者在ImageNet上调出来的最优值在表情这种小分辨率图上我试过5和7差别很小直接用7就行。3.3 ECA和自注意力做对照组时怎么选ECA是SE的轻量化改良去掉全连接层的降维过程改用一维卷积直接在通道维上捕捉局部跨通道交互。它参数量只有SE的几十分之一但效果在很多任务上和SE打平甚至更好。放进消融实验里它能帮你判断提升到底来自“通道重标定”这个行为本身还是来自SE里那两个全连接层带来的额外参数。class ECABlock(nn.Module): def __init__(self, channels, kernel_size5): super().__init__() self.pool nn.AdaptiveAvgPool2d(1) # 一维卷积只在通道维上滑动感受野默认覆盖5个相邻通道 self.conv nn.Conv1d(1, 1, kernel_sizekernel_size, paddingkernel_size // 2, biasFalse) def forward(self, x): b, c, _, _ x.shape w self.pool(x).squeeze(-1).squeeze(-1) # b, c w self.conv(w.unsqueeze(1)).squeeze(1) # b, c w torch.sigmoid(w).view(b, c, 1, 1) return x * wkernel_size给5一维卷积的padding按kernel_size // 2来设置保证输出长度和输入一致。ECA没有降维操作所以不存在reduction这个超参。相比之下如果项目里有人用了multi-head self-attention来做视觉任务一般会在主干输出的特征图上做全局自注意力那个计算量在48×48输入下还能接受但放到ResNet50的中间层会非常吃显存训练速度下降明显。做消融实验时我建议第一优先级跑SE和CBAMECA作为轻量对照自注意力变体可以放到最后再试先确保前几个对比组的结论干净。注意三个注意力模块都能直接作用在ResNet50的每个Bottleneck上但不要一上来就全层加。先在layer4加跑通再扩展到layer3和layer2逐层做记录。全层同时加训练时间和显存开销翻倍出了问题还不好定位。4. 消融实验设计怎么对比结论才能站得住4.1 消融实验的核心原则一次只动一个变量消融实验ablation study的目的是归因——验证某个组件到底对最终性能有多大贡献。做法是拿掉或替换目标组件其他条件全部冻结观察指标变化。这里最大的风险是“改了不止一个变量”比如对比ResNet50和ResNet101时很多人顺手把学习率也调了那实验就脏了说不清提升来自更多层还是更好的学习率。变量冻结清单要写在代码注释或实验文档最前面我一般会强制检查这几项训练轮数固定batch size固定优化器类型和初始学习率固定数据增强和预处理固定评估协议和测试集固定随机种子固定。六个条件里任何一个变了实验结果都不能和其他组直接比较。特别是随机种子不固定的话同一次配置跑两次能差一到两个点的准确率这在高精度对比时足以掩盖真实的模块收益。import random import numpy as np def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic设为True会牺牲一点训练速度但换来的是结果可复现。benchmark要关掉否则cuDNN会根据输入尺寸自动选算法不同实验间的卷积算法可能不同引入额外变量。4.2 多模型对比矩阵backbone和attention的交叉组合这套消融实验矩阵是我的标准配置。A组固定ResNet50只换注意力模块回答“哪个注意力对这个任务有效”B组固定同样的注意力模块换backbone回答“模型容量和注意力机制之间怎么权衡”C组加一个不换backbone而直接加深模型的对照组用来判断加attention带来的提升到底是模块本身的价值还是参数量的价值。实验编号backboneattention改造位置想回答的问题A0ResNet50无基线基准线在哪A1ResNet50SE每个残差分支末尾纯通道注意力是否有效A2ResNet50ECA每个残差分支末尾轻量通道注意力是否够用A3ResNet50CBAM残差加和后ReLU前空间通道组合是否更强B0ResNet18无基线小模型基准在哪B1ResNet18CBAM同上小模型加注意力能否追平大模型基线C0ResNet101无基线参数翻倍能带来多少提升这个矩阵有七组实验每组训练三四十个epoch48×48的图像单卡训练很快。跑完后重点看三组对比A1对比A0看SE提升A3对比A1看空间注意力的边际贡献B1对比A0看小模型加注意力能不能打平大模型。第三组对比最有工程价值如果ResNet18加CBAM能追平ResNet50那部署时选谁就很明确了。C0存在的意义是防止你误判——假如A3比A0涨了2个点C0比A0涨了3个点那说明涨点主要来自多出来的注意力模块参数而不是CBAM的注意力机制本身这个结论完全不一样。4.3 从结果到结论准确率、宏F1和混淆矩阵怎么交叉验证每轮实验跑完把四个指标全部落盘最后汇总成一张总表。我通常在项目根目录建一个experiments目录每个实验一个文件夹里面放metrics.json和最后的checkpoint汇总表单独生成。先看准确率排序再看宏F1排序两张排序不一致的情况非常常见——某组准确率最高但宏F1不是最高说明它牺牲了少数类换来多数类的整体正确率这种方案在表情识别里是不可取的因为真实场景里什么表情都会出现你不能只把“开心”认好。混淆矩阵的观察重点在角对角的位置。愤怒、厌恶、恐惧、悲伤这几个负向情绪互相混淆是表情识别的主战场。加CBAM如果只把准确率提了1个点但愤怒和厌恶的互相误判明显减少那这个提升就是实质性的反过来如果提升全部落在中性、惊讶这类好分的类上那这个注意力模块就没解决核心矛盾。import json import os def save_metrics(exp_id, acc, macro_f1, recall_each, cm, note): records { exp_id: exp_id, accuracy: round(float(acc), 4), macro_f1: round(float(macro_f1), 4), recall_each: [round(float(v), 4) for v in recall_each], confusion_matrix: cm.tolist(), note: note, } os.makedirs(experiments, exist_okTrue) with open(fexperiments/{exp_id}/metrics.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2)最后做结论时不要只看最好成绩把每组实验跑两遍取平均是一个更稳的工程习惯尤其是宏F1差距在0.5个点以内时单次结果不足以支撑“某模块更好”的结论。如果时间紧张至少对最终胜出的组和基线各跑一次重复实验验证。提示任何一组实验的日志都不能删训练过程中的loss曲线截图、参数配置、跑的时间点都要留档。原因是消融实验的结论写到论文或技术报告里后别人会问“你这组为什么没有验证集loss曲线”到时候补跑的成本远高于当时随手存档。5. 人脸表情识别项目避坑训练发散、类别失衡与注意力加在错误位置的典型问题5.1 训练中期loss爆成NaN梯度爆炸的连锁反应现象前十个epoch正常loss稳定下降某个epoch开始loss突然变成nan训练中断重跑还是一样。原因表情数据集本身噪声大一张图被标成“愤怒”也是“厌恶”也说得过去这种高冲突样本会产生异常大的梯度。加了SE或CBAM之后问题更明显注意力模块的sigmoid输出靠近0时反向传播的梯度会成倍放大配合主干网络1e-5学习率一旦lr调度或数据顺序碰到硬样本预训练层参数可能直接被一步更新毁掉。解决在loss计算之后统一做梯度裁剪clip_norm给5.0到10.0之间同时确保出问题前lr没有因为ReduceLROnPlateau被台阶式调大。另外建议主干部分学习率给到1e-5新增的attention层给1e-4fc层给1e-3三层分开。从现象复盘来看几乎所有的nan事故都是fc层或attention层学习率过大、碰到硬样本导致的。5.2 训练集损失继续降、验证集指标来回跳过拟合还是类别失衡现象训练acc已经到百分之九十几验证acc卡在七十多每个epoch的验证结果上下浮动超过一两个点。加了attention模块后这个gap反而更大。原因attention层增加了模型容量参数变多之后拟合能力变强在表情这个小数据量上更容易记住训练集中的噪声标注。另外七类样本数差距大验证集里多数类占比也大模型偶发性偏向多数类时准确率就会跳。如果你的loss没有给class_weight多数类会对梯度形成主导等epoch末段少数类被大规模误判又会拖一下指标表现为震荡。解决给CrossEntropyLoss加类别权重权重按样本数的反比计算并归一化。同时把验证策略从每epoch评估一次改成每半个epoch评估一次观察早期是不是已经过拟合。如果确认过拟合先降fc层和attention层的学习率而不是加dropout盲目硬顶。数据增强的强度不要动因为增强一改和前几组实验就不再是同一变量体系。5.3 Add完Attention后GPU利用率不升反降现象换了显卡或者加大batch size后每个epoch耗时反而变多nvidia-smi看GPU利用率不到五成。有人怀疑是attention模块计算量太大其实不是。原因attention模块里有大量小张量的逐元素操作view、squeeze、sigmoid、乘法这些操作kernel launch开销占比高遇到batch size不足时GPU并行度撑不满。还有一个常见问题是dataloader的num_workers设成0或1表情数据集虽然单张图小但CSV解析加图像转换是CPU密集操作上一批的预处理没做完GPU就只能空转等数据。解决num_workers按CPU核心数的一半设置并加pin_memoryTrue。attention模块里的view操作尽量少用SE里的pool结果直接reshape为b, c, 1, 1即可。如果CPU还是跟不上可以尝试把灰度图像从字符串解析放到预处理阶段一次性完成存成npy文件训练时只做ToTensor这样能明显缩短dataloader耗时。5.4 预训练权重加载报错KeyError和size mismatch交替出现现象from_pretrained加载时要么报fc.weight size mismatch要么直接KeyError说某个层的键不存在。常见于换了不同的torchvision版本之后复现老代码。原因新版torchvision用weights...加载预训练权重旧版用pretrainedTrue两者的state_dict键名结构一致但不同版本ResNet50的bn层num_batches_tracked有可能多或少。另一个原因是我们改了fc层的输出维度torchvision的预训练权重里fc.weight是1000×2048和改成7类后的fc层结构必然不匹配。解决加载时用strictFalse让fc层不匹配的键被忽略其余所有层正常加载。fc层随机初始化即可因为从零训练它能学到表情类别映射。如果用的是自己下载的pth文件建议先load到CPU上再转移GPU避免文件路径或内存布局差异带来的问题。5.5 加Attention后效果退步先查接入位置和收敛状态现象A1组加了SE之后宏F1反而比A0基线低。很多人在这一步直接判定“Attention没用了”把整个模块删掉回到基线。原因大概率是注意力模块的训练量不足。SE和CBAM的初始化接近恒等映射需要额外的训练轮数才能发挥效果如果你的总epoch只有二十轮净训练量可能不够它收敛。另一种可能是SE接在了ReLU之后稀疏特征过了sigmoid被加权负值区域被直接抹掉破坏了原有激活分布这种接法在多数情况下效果是负的。还有reduction设置过大挤压过度丢信息。解决先补充或延长训练轮数把attention层训练得更充分后再对比。RELU之后的问题直接按照第3章给的接入位置改。如果延长训练后依旧退步把reduction从16调到8重跑一次再不行就换ECA对照看问题是出在“通道注意力”这个思想还是出在SE的具体实现上。退步本身也是消融实验的结论记录下来写清楚原因比强行让Attention匹配基线更有科研价值。注意消融实验里“负结果”非常有价值因为它能帮你筛掉无效的模块复杂度。不要因为某个attention加不加都差不多就软磨硬泡去调超参真实情况是如果训练配置合理、接入位置正确、训练轮数充足加一行结论“SE在此任务上无显著增益”反而能让你的对比报告更可信。6. 把消融实验做成可复用的实验脚本配置驱动加自动存档人脸表情识别的消融实验跑到后期最大的痛点是“组数多了之后对不上账”。哪个配置跑过、每个配置用了什么学习率、当时是什么seed单靠人肉记根本扛不住。我的做法是让每个实验自己背着自己的配置文件和全部日志跑起来就是一个独立目录。EXPERIMENTS { A0: {backbone: resnet50, attention: None, note: baseline}, A1: {backbone: resnet50, attention: se, note: SE branch}, A2: {backbone: resnet50, attention: eca, note: ECA branch}, A3: {backbone: resnet50, attention: cbam, note: CBAM after add}, B0: {backbone: resnet18, attention: None, note: baseline resnet18}, B1: {backbone: resnet18, attention: cbam, note: cbam resnet18}, }每个实验的配置先写进这个字典train.py启动时拉取对应配置创建experiments/{exp_id}/{timestamp}目录把配置json、日志文件、指标结果全部落进去。训练脚本里加上seed参数跑批的时候用shell直接串行执行每个实验可以重复跑多个seed最后汇总结果做均值分析。批跑时用bash更直观for exp in A0 A1 A2 A3 B0 B1; do for seed in 42 2024; do python train.py --exp_id $exp --seed $seed done done这份脚本的价值在于它把“实验管理”从代码里剥离出来了。加一组实验只需要在EXPERIMENTS字典里加一行不需要改训练代码不需要复制脚本。后端需求变少之后前端能专注在真正的模型设计和结果分析上。汇总时直接读取整个experiments目录的所有metrics.json按exp_id和seed分组生成总表。我现在养成的习惯是写模型代码之前先把metrics存档的接口写好跑第一个基线之前先把实验配置字典写好。这两个架子搭完后续所有对比实验只是在填写表格而不是在整理乱账。这个顺序救过我很多次也省掉过我在答辩前夜对着十几个没标seed的checkpoint懊恼的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表