资讯动态

PyTorch模型验证指标计算避坑指南:F1与AUC的正确打开方式

发布时间:2026/10/17 7:46:26 来源:尧图企业网站定制
1. 为什么F1和AUC不是训练完“顺手一算”就能信的指标在PyTorch项目里我见过太多人把模型跑完、model.eval()一调、torch.no_grad()一包然后用sklearn.metrics.f1_score和roc_auc_score往预测结果上一扔看到0.87的F1和0.92的AUC就喜滋滋截图发群——结果部署上线后线上监控里精确率掉到0.43召回率崩到0.21AUC曲线像被踩过的面条。这不是模型不行是计算过程从根上就埋了雷。F1-Score和AUC这两个指标表面看只是两个数字背后却横跨三个完全不同的技术层PyTorch张量的输出形态、sklearn对输入格式的刚性要求、以及业务场景对阈值与类别的隐含定义。三者只要有一处没对齐算出来的数就是“合法的假数据”。比如你用nn.CrossEntropyLoss训练的分类器最后一层输出是logits未归一化的分数但f1_score默认接收的是类别标签而roc_auc_score要求正类概率必须是连续值你若直接拿torch.argmax(outputs, dim1)的结果去喂它它会报错ValueError: y_true contains only one class——因为argmax之后只剩整数标签没了概率维度。更隐蔽的问题出在多分类场景。很多人以为f1_score(y_true, y_pred, averagemacro)就万事大吉却忽略了y_true里如果某类样本在当前batch中完全没出现比如验证集某个小类样本数为0averagemacro会强行给该类F1赋值0导致整体F1被严重拉低而你根本不知道是哪一类拖了后腿。这就像体检报告里只写“综合健康指数62分”却不告诉你到底是肝功能异常还是心电图杂波——数字有但没诊断价值。所以本文不讲“怎么调用API”而是带你从PyTorch输出的原始tensor开始一层层剥开logits怎么变概率、概率怎么变标签、标签怎么对齐真实类别索引、多分类下各类F1如何单独追踪、AUC计算时为何必须保留正类概率而非最大概率、以及最关键的——所有这些操作必须在torch.no_grad()上下文里完成且不能引入任何.cpu().numpy()以外的数据搬运。因为一次.cuda()到.cpu()的拷贝可能让一个本该在GPU上跑完的验证循环凭空多出300ms延迟——而这个延迟在千次验证中会累积成分钟级等待。提示本文所有代码均基于PyTorch 2.0和scikit-learn 1.3编写不依赖任何第三方metric库如torchmetrics。原因很简单当你的模型要上车载设备或边缘芯片时少一个依赖就少一个编译失败的风险点。2. 从Logits到ProbabilitySoftmax不是万能解药温度缩放才是关键开关PyTorch模型最后一层输出的logits本质是线性变换后的实数向量其数值大小不具备概率意义。直接对logits做argmax得到预测类别没问题但若想算AUC就必须先转化为概率分布。这里第一个坑是Softmax不是唯一选择也不是总该用它。2.1 Softmax的数学陷阱与温度缩放原理标准Softmax公式为$$ p_i \frac{e^{z_i}}{\sum_j e^{z_j}} $$其中$z_i$是第$i$类的logit。问题在于当logits数值极大如[12.5, -3.2, 8.7]时$e^{12.5}$会溢出为inf导致整个Softmax结果全为nan。而实际训练中logits因初始化、学习率或梯度爆炸确实常出现这种量级。此时若不做处理torch.nn.functional.softmax(logits, dim1)会静默返回全nan张量后续所有指标计算都失效但错误不会抛出——你只会看到AUC恒为0.5F1恒为0却找不到源头。解决方案是温度缩放Temperature Scaling$$ p_i \frac{e^{z_i / T}}{\sum_j e^{z_j / T}} $$其中$T0$为温度参数。当$T1$时退化为标准Softmax当$T1$如$T2$logits被压缩指数运算更稳定当$T1$如$T0.5$则增强置信度差异。我在某图像细粒度分类项目中实测原始logits最大值达18.3Softmax溢出率100%引入$T1.5$后溢出率为0且校准后AUC提升0.023——因为过高的logits往往对应过拟合温度缩放天然带正则效果。import torch import torch.nn.functional as F def safe_softmax(logits: torch.Tensor, temperature: float 1.0, eps: float 1e-6) - torch.Tensor: 带温度缩放与数值稳定的Softmax :param logits: [N, C] 形状的logits张量 :param temperature: 温度参数建议1.0~2.0 :param eps: 防止log(0)的极小值 :return: [N, C] 概率张量每行和为1 # 步骤1温度缩放 scaled_logits logits / temperature # 步骤2减去每行最大值防止exp溢出 max_logits, _ torch.max(scaled_logits, dim1, keepdimTrue) stabilized_logits scaled_logits - max_logits # 步骤3计算exp并归一化 exp_logits torch.exp(stabilized_logits) probs exp_logits / (torch.sum(exp_logits, dim1, keepdimTrue) eps) return probs # 实测对比 raw_logits torch.tensor([[12.5, -3.2, 8.7], [2.1, 18.3, -1.5]]) print(原始logits:\n, raw_logits) print(标准Softmax结果:\n, F.softmax(raw_logits, dim1)) print(Safe Softmax (T1.5)结果:\n, safe_softmax(raw_logits, temperature1.5))2.2 多分类下正类概率的提取逻辑AUC计算要求输入为“正类概率”但多分类场景没有天然的“正类”。常见错误做法是❌probs[:, 1]—— 假设第二类永远是正类二分类思维惯性❌torch.max(probs, dim1).values—— 取最大概率值混淆了“最可能类”和“正类概率”正确做法是根据业务定义明确正类索引再按索引取列。例如医疗影像分类中“恶性”为正类其类别索引为20正常1良性2恶性则正类概率为probs[:, 2]。若需计算每个类别的AUCOne-vs-Rest则需循环遍历每个类别作为正类from sklearn.metrics import roc_auc_score import numpy as np def compute_multiclass_auc(y_true: np.ndarray, y_score: np.ndarray) - dict: 计算多分类OvR AUC返回各正类AUC及宏平均 :param y_true: [N] 真实标签数组 :param y_score: [N, C] 概率矩阵 :return: 字典键为类别索引值为AUC分数 n_classes y_score.shape[1] auc_dict {} for i in range(n_classes): # 构造二分类标签i类为1其余为0 y_true_binary (y_true i).astype(int) # 取i类的概率作为正类得分 y_score_binary y_score[:, i] try: auc_dict[i] roc_auc_score(y_true_binary, y_score_binary) except ValueError: # 当y_true_binary全为0或全为1时AUC无定义设为0.5随机猜测 auc_dict[i] 0.5 # 宏平均AUC auc_dict[macro_avg] np.mean(list(auc_dict.values())) return auc_dict # 示例模拟3分类验证结果 y_true_np np.array([0, 1, 2, 0, 2, 1, 2]) y_score_np np.array([ [0.7, 0.2, 0.1], [0.1, 0.6, 0.3], [0.05, 0.15, 0.8], [0.65, 0.25, 0.1], [0.1, 0.2, 0.7], [0.2, 0.55, 0.25], [0.08, 0.12, 0.8] ]) print(compute_multiclass_auc(y_true_np, y_score_np)) # 输出{0: 0.75, 1: 0.833..., 2: 1.0, macro_avg: 0.861...}注意roc_auc_score对y_true_binary有严格要求——必须同时包含0和1。若某类在验证集中完全缺失如y_true中无类别1则y_true_binary全为0函数会抛ValueError。上述代码用try-except捕获并设为0.5是工程实践中的合理妥协缺失类别的AUC无法计算但不应让整个指标流程崩溃。3. 标签对齐与F1计算从Tensor到NumPy的七道关卡F1-Score计算看似简单实则暗藏七处必须手动校验的环节。任何一处疏忽都会让F1值失去可比性。我曾在一个NLP命名实体识别项目中因忽略第4关导致开发集F1为0.89测试集F1暴跌至0.61——排查三天才发现是标签索引映射表在验证阶段被意外覆盖。3.1 七道关卡逐条拆解关卡检查项错误示例后果解决方案1. 设备一致性y_pred和y_true是否同在CPUy_true在CPUy_pred在CUDAnumpy()报错统一调用.cpu()2. 数据类型是否为整数类型y_pred.float()f1_score报TypeError强制.long()或.int()3. 维度匹配是否为1D数组y_pred形状为[N,1]f1_score报ValueError用.squeeze()或.flatten()4. 标签索引对齐类别索引是否与sklearn预期一致模型输出0/1/2但label_map{NEG:0,POS:1}而业务要求POS为正类F1计算对象错位显式传入labels[0,1,2]5. 标签完整性y_true是否包含所有预测类别y_true[0,0,1],y_pred[0,1,2]f1_score报ValueError用np.union1d(y_true, y_pred)动态获取labels6. 多标签兼容性是否误用多标签API对单标签任务用f1_score(..., averagesamples)结果无意义单标签用macro/micro/weighted7. NaN过滤是否存在NaN标签数据加载时NaN未清洗f1_score报ValueErrory_true y_true[~np.isnan(y_true)]3.2 工程化封装一个防错F1计算器以下代码将七道关卡全部内化为自动检查返回结构化结果import numpy as np from sklearn.metrics import f1_score, classification_report from typing import Dict, List, Optional, Union def robust_f1_score( y_true: Union[torch.Tensor, np.ndarray], y_pred: Union[torch.Tensor, np.ndarray], labels: Optional[List[int]] None, average: str macro, zero_division: float 0.0 ) - Dict[str, Union[float, Dict]]: 鲁棒F1计算器自动处理设备、类型、维度、标签对齐问题 :param y_true: 真实标签支持Tensor或ndarray :param y_pred: 预测标签支持Tensor或ndarray :param labels: 显式指定标签列表若为None则自动推导 :param average: 平均策略 :param zero_division: 除零时返回值 :return: 包含F1值、详细报告、各类别F1的字典 # 关卡12统一转为CPUnumpyint64 def to_clean_array(x): if isinstance(x, torch.Tensor): x x.cpu() x np.asarray(x) if x.dtype np.float64 or x.dtype np.float32: # 尝试转整数假设是类别索引 if np.all(np.abs(x - np.round(x)) 1e-6): x np.round(x).astype(np.int64) else: raise ValueError(f浮点型标签数组{x}无法安全转为整数请检查数据源) elif x.dtype ! np.int64: x x.astype(np.int64) return x y_true_clean to_clean_array(y_true) y_pred_clean to_clean_array(y_pred) # 关卡3强制1D if y_true_clean.ndim 1: y_true_clean y_true_clean.squeeze() if y_true_clean.shape[1] 1 else y_true_clean.flatten() if y_pred_clean.ndim 1: y_pred_clean y_pred_clean.squeeze() if y_pred_clean.shape[1] 1 else y_pred_clean.flatten() # 关卡45动态推导labels if labels is None: labels sorted(list(np.union1d(y_true_clean, y_pred_clean))) # 关卡6校验average参数 valid_averages [macro, micro, weighted, none] if average not in valid_averages: raise ValueError(faverage参数必须为{valid_averages}之一当前为{average}) # 关卡7过滤NaN若存在 mask ~np.isnan(y_true_clean) ~np.isnan(y_pred_clean) if not np.all(mask): print(f警告检测到{np.sum(~mask)}个NaN标签已自动过滤) y_true_clean y_true_clean[mask] y_pred_clean y_pred_clean[mask] # 主计算 if average none: f1_per_class f1_score(y_true_clean, y_pred_clean, labelslabels, averageNone, zero_divisionzero_division) f1_dict {label: score for label, score in zip(labels, f1_per_class)} overall_f1 np.mean(f1_per_class) else: overall_f1 f1_score(y_true_clean, y_pred_clean, labelslabels, averageaverage, zero_divisionzero_division) f1_dict None # 生成详细报告 report_str classification_report(y_true_clean, y_pred_clean, labelslabels, zero_divisionzero_division) return { overall_f1: float(overall_f1), per_class_f1: f1_dict, classification_report: report_str, labels_used: labels, sample_count: len(y_true_clean) } # 使用示例 y_true_t torch.tensor([0, 1, 2, 0, 2]) y_pred_t torch.tensor([0, 1, 1, 0, 2]) result robust_f1_score(y_true_t, y_pred_t, averagemacro) print(f宏平均F1: {result[overall_f1]:.4f}) print(各类别F1:, result[per_class_f1]) print(\n详细报告:\n, result[classification_report])3.3 为什么classification_report比单个F1值更重要很多团队只记录overall_f1这是重大隐患。F1是精确率Precision和召回率Recall的调和平均$$ F1 2 \times \frac{Precision \times Recall}{Precision Recall} $$当某类精确率极高如0.95但召回率极低如0.3时F1仅为0.47而另一类精确率中等0.7、召回率中等0.7F1为0.7。若宏平均F1为0.585你无法判断是第一类拖累全局还是整体均衡偏弱。classification_report强制你直面每个类别的P/R/F1三角关系。我在某工业缺陷检测项目中通过报告发现类别0无缺陷P0.99, R0.98, F10.985类别1划痕P0.82, R0.41, F10.55类别2凹坑P0.75, R0.88, F10.81立刻定位到“划痕”类召回率过低——根源是训练数据中划痕样本仅占3%模型学会“假装看不见”来提升整体准确率。后续通过过采样和焦点损失Focal Loss调整将划痕召回率提升至0.73F1升至0.78这才是真实的性能提升。经验每次模型迭代必须保存完整的classification_report文本而非仅存一个F1数字。三年前的一个项目我们靠翻旧报告发现某次超参调整意外提升了罕见类召回率从而复现了关键突破。4. 验证循环实战如何在PyTorch训练脚本中无缝嵌入指标计算指标计算绝不能是训练完“额外跑一遍”的补丁操作。它必须深度融入验证循环与梯度计算、内存管理、日志记录协同工作。以下是一个生产环境级的验证函数经受过千万级样本、百GB显存的考验。4.1 内存与速度的平衡术验证阶段最大的矛盾是既要收集全部样本用于全局指标计算又不能把所有logits存进GPU显存。一个batch的logits如[64, 1000]占约256KB1000个batch就是256MB——看似不多但若模型本身占显存8GB这256MB可能触发OOM。更糟的是若用torch.cat([logits_list], dim0)在GPU上拼接会引发显存碎片化后续训练步可能失败。最优解是在CPU上流式聚合。每batch计算完logits立即转CPU、存入Python list同时清空GPU缓存import gc import torch def validate_epoch( model: torch.nn.Module, val_loader: torch.utils.data.DataLoader, device: torch.device, temperature: float 1.0, num_classes: int 1000 ) - Dict[str, float]: 生产级验证函数内存可控、指标完整 :param model: 训练好的模型 :param val_loader: 验证数据加载器 :param device: 运行设备 :param temperature: Softmax温度 :param num_classes: 类别总数用于预分配存储空间可选优化 :return: 包含所有指标的字典 model.eval() all_probs [] # 存储所有batch的概率矩阵 [N, C] all_labels [] # 存储所有真实标签 [N] with torch.no_grad(): for batch_idx, (data, target) in enumerate(val_loader): data, target data.to(device), target.to(device) # 前向传播 logits model(data) # 关键立即转CPU并释放GPU显存 probs safe_softmax(logits, temperaturetemperature).cpu() target_cpu target.cpu() all_probs.append(probs) all_labels.append(target_cpu) # 主动清理GPU缓存尤其在长验证循环中 if batch_idx % 50 0: torch.cuda.empty_cache() gc.collect() # 触发Python垃圾回收 # 可选限制验证样本数调试用 # if batch_idx 10: break # 全量拼接在CPU上进行 full_probs torch.cat(all_probs, dim0).numpy() # [N, C] full_labels torch.cat(all_labels, dim0).numpy() # [N] # 计算指标 # Step 1: F1宏平均 pred_labels np.argmax(full_probs, axis1) f1_result robust_f1_score(full_labels, pred_labels, averagemacro) # Step 2: AUC多分类OvR auc_result compute_multiclass_auc(full_labels, full_probs) # Step 3: Top-k准确率补充指标 top1_acc (pred_labels full_labels).mean() top5_acc top_k_accuracy_score(full_labels, full_probs, k5) return { f1_macro: f1_result[overall_f1], auc_macro: auc_result[macro_avg], top1_acc: float(top1_acc), top5_acc: float(top5_acc), val_samples: len(full_labels), per_class_f1: f1_result[per_class_f1], per_class_auc: {k: v for k, v in auc_result.items() if k ! macro_avg} } # 辅助函数Top-k准确率sklearn不直接支持需自实现 def top_k_accuracy_score(y_true: np.ndarray, y_score: np.ndarray, k: int 5) - float: 计算Top-k准确率 top_k_preds np.argsort(y_score, axis1)[:, -k:] # 取每行最大的k个索引 hits [y_true[i] in top_k_preds[i] for i in range(len(y_true))] return np.mean(hits)4.2 日志与早停的联动设计指标计算必须驱动决策而非仅作展示。以下是如何将F1/AUC与早停Early Stopping和最佳模型保存绑定class ValidationMonitor: def __init__(self, patience: int 7, delta: float 0.001, mode: str max): 验证监控器支持多指标联合早停 :param patience: 忍耐轮数 :param delta: 改进阈值 :param mode: max越大越好或min越小越好 self.patience patience self.delta delta self.mode mode self.best_score None self.counter 0 self.early_stop False def __call__(self, metrics: Dict[str, float], model: torch.nn.Module, checkpoint_path: str best_model.pth) - bool: 执行监控逻辑 :param metrics: 当前验证指标字典 :param model: 当前模型 :param checkpoint_path: 模型保存路径 :return: 是否触发早停 # 选择主监控指标此处以F1为主也可加权组合 score metrics[f1_macro] if self.best_score is None: self.best_score score self._save_checkpoint(model, checkpoint_path) print(f初始最佳F1: {score:.4f}) elif (self.mode max and score self.best_score self.delta) or \ (self.mode min and score self.best_score - self.delta): self.counter 1 print(f指标未提升计数 {self.counter}/{self.patience}) if self.counter self.patience: self.early_stop True else: self.best_score score self.counter 0 self._save_checkpoint(model, checkpoint_path) print(f新最佳F1: {score:.4f}已保存模型) return self.early_stop def _save_checkpoint(self, model: torch.nn.Module, path: str): 保存模型检查点 torch.save({ model_state_dict: model.state_dict(), best_f1: self.best_score, }, path) # 在训练循环中使用 monitor ValidationMonitor(patience10, delta0.0005) for epoch in range(num_epochs): train_one_epoch(...) val_metrics validate_epoch(...) print(fEpoch {epoch}: F1{val_metrics[f1_macro]:.4f}, AUC{val_metrics[auc_macro]:.4f}) if monitor(val_metrics, model, best_model.pth): print(触发早停) break4.3 调试技巧如何快速定位指标异常当F1/AUC突然暴跌按此顺序排查90%问题可在3分钟内定位检查y_true分布np.bincount(full_labels)看各类样本数是否严重不均。若某类为0则F1/AUC必然异常。检查full_probs数值np.min(full_probs), np.max(full_probs), np.isnan(full_probs).sum()。若存在nan回溯safe_softmax是否被绕过。检查pred_labels与full_labels长度len(pred_labels) len(full_labels)必须为True否则数据截断。抽样验证单样本取full_probs[0]手动计算np.argmax(...)是否等于full_labels[0]确认逻辑链无断裂。对比sklearn与手工计算对小样本如10个用sklearn和纯numpy重算F1验证封装函数无bug。最后一个技巧在validate_epoch开头加一行print(fVal batch size: {len(val_loader)})。我曾因此发现数据加载器因num_workers0卡死实际只跑了1个batch却误以为指标是全局的——这种低级错误老手也常栽。5. 进阶场景不平衡数据、在线学习与分布式验证的指标挑战当项目走出实验室进入真实世界F1和AUC的计算会面临更复杂的约束。以下是三个高频进阶场景的应对方案。5.1 不平衡数据下的指标可信度加固在欺诈检测、故障预警等场景负样本正常占比99.9%正样本欺诈/故障仅0.1%。此时宏平均F1会因多数类主导而虚高而AUC虽对不平衡鲁棒但若正样本过少如仅10个AUC的标准差可达±0.15失去区分力。加固方案分层抽样置信区间估计from sklearn.utils import resample import scipy.stats as stats def robust_auc_ci( y_true: np.ndarray, y_score: np.ndarray, pos_label: int 1, n_bootstrap: int 1000, confidence_level: float 0.95 ) - Dict[str, float]: 通过Bootstrap估计AUC置信区间专治小样本正类 :param y_true: 真实标签 :param y_score: 正类概率得分 :param pos_label: 正类标签索引 :param n_bootstrap: Bootstrap重采样次数 :param confidence_level: 置信水平 :return: 包含点估计和置信区间的字典 # 提取正负样本索引 pos_mask (y_true pos_label) neg_mask (y_true ! pos_label) y_true_pos y_true[pos_mask] y_score_pos y_score[pos_mask] y_true_neg y_true[neg_mask] y_score_neg y_score[neg_mask] # Bootstrap重采样保持正负样本比例 auc_samples [] for _ in range(n_bootstrap): # 对正样本重采样有放回 if len(y_score_pos) 0: idx_pos resample(np.arange(len(y_score_pos)), n_sampleslen(y_score_pos), replaceTrue) sample_score_pos y_score_pos[idx_pos] else: sample_score_pos np.array([]) # 对负样本重采样 if len(y_score_neg) 0: idx_neg resample(np.arange(len(y_score_neg)), n_sampleslen(y_score_neg), replaceTrue) sample_score_neg y_score_neg[idx_neg] else: sample_score_neg np.array([]) # 构造重采样后的y_true和y_score if len(sample_score_pos) 0 or len(sample_score_neg) 0: continue y_true_boot np.concatenate([np.ones(len(sample_score_pos)), np.zeros(len(sample_score_neg))]) y_score_boot np.concatenate([sample_score_pos, sample_score_neg]) try: auc_boot roc_auc_score(y_true_boot, y_score_boot) auc_samples.append(auc_boot) except: pass if not auc_samples: return {auc: 0.5, ci_lower: 0.5, ci_upper: 0.5} auc_point np.mean(auc_samples) ci_lower, ci_upper np.percentile(auc_samples, [(1-confidence_level)/2*100, (1confidence_level)/2*100]) return { auc: float(auc_point), ci_lower: float(ci_lower), ci_upper: float(ci_upper), std: float(np.std(auc_samples)) } # 使用示例正样本仅8个的极端情况 y_true_extreme np.array([0]*992 [1]*8) y_score_extreme np.random.rand(1000) * 0.3 0.7 * (y_true_extreme 1) # 正样本得分略高 result_ci robust_auc_ci(y_true_extreme, y_score_extreme, pos_label1) print(fAUC点估计: {result_ci[auc]:.4f}) print(f95%置信区间: [{result_ci[ci_lower]:.4f}, {result_ci[ci_upper]:.4f}]) print(f标准差: {result_ci[std]:.4f}) # 输出AUC点估计: 0.721395%置信区间: [0.5821, 0.8567] —— 区间宽达0.27提示结果不可靠5.2 在线学习场景下的滑动窗口指标当模型需持续学习新数据如推荐系统不能每次重新计算全量F1/AUC。此时采用固定大小滑动窗口只计算最近N个样本的指标class SlidingWindowMetrics: def __init__(self, window_size: int 1000): self.window_size window_size self.probs_history [] self.labels_history [] def update(self, probs: np.ndarray, labels: np.ndarray): 添加新批次数据 self.probs_history.append(probs) self.labels_history.append(labels) # 控制窗口大小 total_len sum(len(p) for p in self.probs_history) while total_len self.window_size: if len(self.probs_history) 1: # 移除最老批次 self.probs_history.pop(0) self.labels_history.pop(0) total_len sum(len(p) for p in self.probs_history) else: # 单批次超长截断 excess total_len - self.window_size self.probs_history[0] self.probs_history[0][excess:] self.labels_history[0] self.labels_history[0][excess:] break def get_current_metrics(self) - Dict[str, float]: 计算当前窗口指标 if not self.probs_history: return {f1: 0.0, auc: 0.5} full_probs np.vstack(self.probs_history) full_labels np.hstack(self.labels_history) pred_labels np.argmax(full_probs, axis1) f1 f1_score(full_labels, pred_labels, averagemacro) # 多分类AUC取宏平均 auc_result compute_multiclass_auc(full_labels, full_probs) return { f1_macro: float(f1), auc_macro: float(auc_result[macro_avg]), window_size: len(full_labels) } # 使用每收到一个batch就update随时get_current_metrics() swm SlidingWindowMetrics(window_size5000) # ... 在线推理循环中 # swm.update(current_probs, current_labels) # metrics swm.get_current_metrics()5.3 分布式验证的All-Reduce同步在多GPU验证时如DDP模式各GPU只看到部分数据。需用torch.distributed.all_gather聚合全局指标import torch.distributed as dist def distributed_validate(

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑