
1. 先把准确度这件事掰开accuracy_score 到底在算什么很多人第一次接触 sklearn 的accuracy_score场景都差不多模型fit完了predict出来一列结果然后把真实标签和预测标签往里一塞打印出一个 0.9 几的数字心里踏实了报告也写完了。我自己早年也这么干过直到有一次在一个严重偏斜的业务数据集上把模型换成一个永远预测多数类的空壳分类器准确度依然有 0.96才发现这个指标远比表面上看起来要滑头。准确度Accuracy在 sklearn 里的正式实现就是sklearn.metrics.accuracy_score它回答的问题极其朴素——在所有样本里预测标签和真实标签完全一样的比例是多少。但就是这个朴素到几乎没有门槛的指标在实际项目里翻车的次数比大多数复杂指标都多。这篇内容我打算把accuracy_score从函数签名、内部计算逻辑、参数语义一直到多分类、样本加权、交叉验证、类别不平衡下的失真全部拆一遍。适合两类人看刚学 sklearn、只会写accuracy_score(y_true, y_pred)的新手以及用了两三年但一直把它当万能打分器、没细究过适用边界的老手。文中所有代码都可以直接跑数据全部用make_classification现场生成不需要额外下载。我会尽量把每个参数的为什么讲透而不是只丢一段 API 文档翻译。1.1 一行代码背后的四格表与除法accuracy_score的实现没有黑魔法。二分类场景下它先根据y_true和y_pred构造出混淆矩阵的四个格子真正例 TP、假正例 FP、假反例 FN、真反例 TN然后算一个除法——$$\text{Accuracy} \frac{TP TN}{TP TN FP FN}$$分母就是样本总数分子是预测对的样本数。多分类场景只是一个推广把混淆矩阵的主对角线元素加起来除以矩阵所有元素之和。所以准确度的本质是对角线占比它把所有类别的错误一视同仁地压成一个数字不区分你是把 A 类错判成 B 类还是把 A 类错判成 C 类。这一点非常关键后面讲不平衡数据时所有的坑都从这里长出来。我用一个小例子把数字摆出来。假设二分类测试集 100 条混淆矩阵是 TP40、FP10、FN5、TN45。准确度 (4045)/100 0.85。同一个矩阵下精确率 40/(4010) 0.80召回率 40/(405) ≈ 0.889。三个数字各不相同但只有准确度会让人产生整体还不错的错觉因为它被 45 个轻松判对的负样本抬高了。你在写评估报告时如果只报 0.85读者根本不知道你的模型在正例上其实漏了 11%。顺带说一个能帮你建立直觉的等价写法在不考虑样本权重的前提下accuracy_score(y_true, y_pred)的结果和np.mean(y_true y_pred)完全一致。我第一次意识到这一点之后就再也不把这个函数当成需要敬畏的黑盒了——它就是一个逐元素比较再求均值。sklearn 额外做的事情只有三件校验两个输入长度是否一致、处理sample_weight、以及把结果按normalize参数决定返回比例还是计数。1.2 normalize 和 sample_weight两个参数两种语义函数签名长这样1.x 版本accuracy_score(y_true, y_pred, *, normalizeTrue, sample_weightNone)注意那个星号它意味着normalize和sample_weight只能按关键字传。这是个历史遗留的兼容性坑早期版本允许位置传参accuracy_score(y, p, False)这种写法在老代码里很常见升级到 0.24 之后的版本会直接抛TypeError。我维护过一个跑了好几年的评估脚本就是被这个改动干掉的报错信息还挺含糊排查了半天。normalizeTrue返回正确率0 到 1 的浮点数normalizeFalse返回预测正确的样本条数整数。后者在需要报错了多少条的场景里比比例更有用比如给业务方解释这批 1 万条数据里模型判错了 137 条比说准确率 98.63%更直观。我一般两个都打日志里一行比例一行绝对数事后追查问题方便很多。sample_weight是个被严重低估的参数。传入权重数组后计算变成加权正确率$$\text{Accuracy}_{w} \frac{\sum_i w_i \cdot \mathbb{1}[y_i \hat{y}_i]}{\sum_i w_i}$$在normalizeFalse时返回的是加权后的分子浮点数不再是整数。它最实用的场景是样本重要性不同——比如同一批数据里近三个月的样本权重给 1.0半年前的给 0.3你就能得到一个偏向近期表现的准确度而不用真的去采样或删数据。另一个用法是做分层评估加权如果你把测试集按业务线拆成几段分别评估想合成一个总体数字直接对各段准确度做算术平均是错的各段样本量不同正确做法是把各段样本数当权重再算一次。import numpy as np from sklearn.metrics import accuracy_score y_true np.array([1, 0, 1, 1, 0, 0, 1, 0]) y_pred np.array([1, 0, 0, 1, 0, 1, 1, 0]) w np.array([1.0, 1.0, 3.0, 1.0, 1.0, 3.0, 1.0, 1.0]) print(accuracy_score(y_true, y_pred)) # 0.75 print(accuracy_score(y_true, y_pred, normalizeFalse)) # 6 print(accuracy_score(y_true, y_pred, sample_weightw)) # 0.6666...上面这个例子里两条判错的样本恰好都被赋了 3 倍权重准确度从 0.75 掉到 0.667。这个数字变化本身就是信息如果高权重样本恰好是模型容易错的那些说明模型在关键样本上表现不行不能靠总体准确度蒙混过去。1.3 为什么它是最容易看起来对的指标准确度有一个其他指标都不具备的特点它的取值永远在 0 到 1 之间永远看起来像个分数永远能用百分比讲给非技术同事听。这种人畜无害的外表导致它被过度使用。我见过太多评估报告通篇只有一行准确度没有混淆矩阵没有分类报告没有基线对比。这样的报告在类别均衡、代价对称的场景下勉强能看一旦碰上信用卡欺诈、设备故障预警、罕见病筛查这类正例占比不到 5% 的问题准确度基本等于噪声。判断能不能用准确度我给一个很粗暴但好用的经验法则先看多数类的占比。如果全猜多数类这个基线策略的准确度已经超过 0.85而你的模型只比它高两三个点那这个准确度数字就别拿出来说事了先去看召回率和混淆矩阵。这条法则帮我省掉过至少三次在评审会上被追问的尴尬。还有一个隐蔽的问题准确度对错误类型完全不敏感。把高风险客户判成低风险和把低风险判成高风险在accuracy_score看来都是错了一条权重一模一样。但业务上的代价可能差几十倍。这种时候要么用sample_weight把代价编码进去要么干脆换指标别硬套。2. 数值是怎么来的手算一遍比读十遍文档管用我一直建议身边的人学任何评估指标都先手算一遍再用 sklearn 验证。手算的过程会逼你面对每一个中间量而调包只会给你一个结果。这一节我把二分类、多分类、top-k 三种情况都走一遍顺便把几个容易搞混的边界说清楚。2.1 二分类场景的完整推导过程假设我们有一批 200 条测试样本模型预测完之后统计出TP72、FP18、FN8、TN102。先验证分母72188102200没问题。分子 72102 174。准确度 174/200 0.87。现在把同一组数字换成其他几个指标你会看到准确度有多宽容指标计算式结果准确度 Accuracy(72102)/2000.870精确率 Precision72/(7218)0.800召回率 Recall72/(728)0.900特异度 Specificity102/(10218)0.850F12×0.8×0.9/(0.80.9)0.847准确度 0.87 落在精确率和召回率之间看起来是个综合数字。但它综合的方式很粗暴它给 TP 和 TN 同等权重而 TN 有 102 个TP 只有 72 个所以这个数字实际上被负样本主导了。如果业务关心的是漏掉了多少正例那 8 个 FN 才是重点准确度完全体现不出来。用代码复现这个矩阵可以这样写import numpy as np from sklearn.metrics import accuracy_score, confusion_matrix # 按上面的四格表构造标签 y_true np.array([1]*80 [0]*120) y_pred np.array([1]*72 [0]*8 [1]*18 [0]*102) print(confusion_matrix(y_true, y_pred)) # [[102 18] # [ 8 72]] print(accuracy_score(y_true, y_pred)) # 0.87注意confusion_matrix默认的行是真实类别、列是预测类别顺序按标签排序0 在前、1 在后。所以左上角是 TN右下角是 TP。这个顺序经常有人记反尤其是标签是字符串的时候排序规则变成字典序no会排在yes前面矩阵看起来就反了。遇到这种情况显式传labels[yes, no]把顺序钉死别靠默认值猜。2.2 多分类子集准确率与 top-k 的区别多分类下accuracy_score要求预测标签和真实标签完全相等才计为正确。这被称为子集准确率subset accuracy或者严格匹配率。举个三分类的例子混淆矩阵如下[[50, 3, 2], [ 4, 40, 6], [ 1, 5, 44]]行和分别是 55、50、50总计 155对角线 504044 134准确度 134/155 ≈ 0.8645。每一类的召回率分别是 50/55≈0.909、40/500.8、44/500.88。可以看到第二类的表现明显更差但总体准确度 0.865 把它藏起来了。这就是为什么我评估多分类模型时一定同时看classification_report光一个总准确度根本定位不到问题类别。另一个容易混淆的概念是 top-k 准确率。有些模型比如图像分类、推荐排序输出的不只是一个标签而是一个按概率排序的候选列表。业务上正确答案排在前 3 名里就算命中这时候不能用accuracy_score要用top_k_accuracy_scorefrom sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, top_k_accuracy_score X, y make_classification( n_samples6000, n_features30, n_informative12, n_classes10, n_clusters_per_class1, random_state0) X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.3, random_state0, stratifyy) clf LogisticRegression(max_iter3000, multi_classmultinomial) clf.fit(X_tr, y_tr) y_pred clf.predict(X_te) y_prob clf.predict_proba(X_te) print(top-1:, accuracy_score(y_te, y_pred)) # 约 0.7x print(top-3:, top_k_accuracy_score(y_te, y_prob, k3, labelsclf.classes_))top_k_accuracy_score的第二个参数要的是概率矩阵或者 decision function 的形状n_samples × n_classes不是硬标签。labels参数强烈建议显式传虽然新版本能从列数推断但一旦你的概率矩阵列顺序和classes_不一致比如做了列筛选或重排推断出来的结果会静默错位准确度虚高而没有任何警告。这种 bug 极难发现我吃过一次亏之后所有涉及概率矩阵的评估代码里都强制写上labels。2.3 概率输出不能直接喂给 accuracy_score这是新手最高频的错误之一。模型predict_proba出来的是 0 到 1 的连续值直接传进accuracy_score会得到ValueError: Classification metrics cant handle a mix of binary and continuous targets报错信息已经写得很明白了分类指标处理不了连续目标。但很多人看到报错后的第一反应是那我把概率四舍五入一下于是写了np.round(y_prob)结果也不对——多分类场景下概率向量会被舍入成一堆 0 和 1形状都变了。正确姿势是两步先决定阈值再二值化。二分类用predict_proba(...)[:, 1] 阈值多分类用argmaxproba clf.predict_proba(X_te) y_pred_05 clf.classes_[proba.argmax(axis1)] # 等价于 clf.predict二分类手动调阈值时注意一个细节clf.predict()内部用的阈值固定是 0.5而且是在正类概率上判断。如果你的正类在classes_里的位置是 0 而不是 1predict_proba的列顺序就会跟着变。稳妥的写法是按classes_找索引pos_idx list(clf.classes_).index(1) proba_pos clf.predict_proba(X_te)[:, pos_idx] for thr in (0.3, 0.4, 0.5, 0.6): pred (proba_pos thr).astype(int) print(thr, accuracy_score(y_te, pred))这段循环跑下来你经常会看到准确度在 0.4 到 0.5 之间最高。很多人会本能地选那个让准确度最大的阈值然后就把它固化进代码。我的建议是如果业务代价并非对称阈值绝不应该由准确度决定而应该由精确率和召回率的可接受区间决定。准确度选阈值的做法本质上是在最大化总体判对率这跟少漏掉一个正例的业务目标可能完全相反。3. 三个能直接复现的实操案例光讲理论没意思这一节给三个完整案例从最基础的流水线到不平衡数据的失真现场代码都能独立运行。我会把每一步的意图和输出解释清楚你抄过去改改数据就能用。3.1 案例一最干净的二分类流水线先用一个类别相对均衡的数据集把标准流程跑通建立正确的心智模型。import numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import (accuracy_score, confusion_matrix, classification_report, balanced_accuracy_score) X, y make_classification( n_samples5000, n_features25, n_informative10, n_redundant5, weights[0.6, 0.4], flip_y0.02, class_sep1.0, random_state42) X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.25, random_state42, stratifyy) clf RandomForestClassifier( n_estimators300, max_depthNone, min_samples_leaf2, n_jobs-1, random_state42) clf.fit(X_tr, y_tr) y_pred clf.predict(X_te) print(acc :, round(accuracy_score(y_te, y_pred), 4)) print(acc_cnt :, accuracy_score(y_te, y_pred, normalizeFalse)) print(bacc :, round(balanced_accuracy_score(y_te, y_pred), 4)) print(confusion_matrix(y_te, y_pred)) print(classification_report(y_te, y_pred, digits4))几个设计点值得说。train_test_split里加stratifyy是为了保持训练集和测试集的正负比例一致不加的话小数据集上可能出现测试集正例占比偏几个百分点的情况准确度会飘。make_classification里flip_y0.02是故意注入 2% 的标签噪声模拟真实数据的脏乱不加这个参数的话随机森林的准确度容易直接冲到 0.99失去参考价值。RandomForestClassifier的min_samples_leaf2是我个人习惯的起点比默认的 1 稍微稳一点尤其在噪声标签存在时叶子节点样本太少会去拟合噪声。这个参数不是必须的但在多跑几次不同随机种子之后你会发现它的方差确实小一些。交叉验证部分单独写一段cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(clf, X, y, cvcv, scoringaccuracy, n_jobs-1) print(scores.mean(), scores.std())StratifiedKFold比默认的KFold更合适分类问题它保证每一折的类别比例和全量一致。shuffleTrue必须配合random_state用否则每次跑出来的折都不一样没法复现。scores.std()这个数别丢它反映模型在不同数据切片上的稳定性标准差异常大的时候说明模型对数据分布很敏感这时候光看均值会过于乐观。还有一个很多人不知道的等价关系clf.score(X_te, y_te)在分类器上返回的就是准确度和accuracy_score(y_te, clf.predict(X_te))完全一样。这个score方法在所有 sklearn 分类器上都存在但要注意回归器的score返回的是 R²别搞混。我在写快速试验脚本时常用clf.score省一行代码但在正式评估代码里一定写全accuracy_score因为显式调用能让读代码的人一眼看出用的是哪个指标。3.2 案例二交叉验证下同时盯住多个指标只跑一个准确度等于只测了一个维度。我现在的习惯是cross_validate一次跑多个指标把训练集得分也带出来这样一眼能看出过拟合程度。from sklearn.model_selection import cross_validate scoring [accuracy, balanced_accuracy, f1_macro, roc_auc] res cross_validate( clf, X, y, cvcv, scoringscoring, return_train_scoreTrue, n_jobs-1) for k in scoring: tr res[ftrain_{k}].mean() te res[ftest_{k}].mean() print(f{k:18}: train{tr:.4f} test{te:.4f} gap{tr-te:.4f})输出的gap那一列是我最看重的。准确度的训练集和验证集差距在 0.02 以内基本可以认为没有明显过拟合差距到 0.1 以上就得回头看树的深度、叶子样本数这些正则参数了。这里有个反直觉的点准确度的 gap 可能很小但roc_auc的 gap 很大。原因是指标准确度只看 0.5 阈值附近的表现而 AUC 看的是整体排序能力模型在排序上学到的过拟合细节不一定体现在硬分类结果上。所以多个指标一起看比单看一个准确度信息量大得多。scoring列表里的字符串必须是 sklearn 认可的 scorer 名。常用的几个accuracy、balanced_accuracy、f1、f1_macro、f1_weighted、precision、recall、roc_auc。注意roc_auc在多分类默认是 one-vs-rest 的宏平均二分类直接用正类概率。如果你传了一个拼错的名字cross_validate会抛ValueError: xxx is not a valid scoring value还会把可用列表打出来照着抄就行。3.3 案例三不平衡数据下准确度是怎么骗人的这个案例我建议每个做分类的人都亲手跑一遍。构造一个 98:2 的极端不平衡数据集然后分别用永远猜多数类的空模型和一个正经的逻辑回归去跑对比准确度。from sklearn.datasets import make_classification from sklearn.dummy import DummyClassifier from sklearn.linear_model import LogisticRegression from sklearn.metrics import (accuracy_score, balanced_accuracy_score, confusion_matrix, classification_report, precision_score, recall_score) X, y make_classification( n_samples20000, n_features20, n_informative6, n_redundant4, weights[0.98, 0.02], flip_y0.01, class_sep0.9, random_state7) X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.25, random_state7, stratifyy) dummy DummyClassifier(strategymost_frequent).fit(X_tr, y_tr) print(空模型 acc:, round(accuracy_score(y_te, dummy.predict(X_te)), 4)) lr LogisticRegression(max_iter2000, class_weightbalanced) lr.fit(X_tr, y_tr) pred lr.predict(X_te) print(LR acc :, round(accuracy_score(y_te, pred), 4)) print(LR bacc :, round(balanced_accuracy_score(y_te, pred), 4)) print(LR prec :, round(precision_score(y_te, pred), 4)) print(LR rec :, round(recall_score(y_te, pred), 4)) print(confusion_matrix(y_te, pred))跑出来的典型结果是这样空模型准确度大约 0.97 到 0.98逻辑回归加class_weightbalanced之后准确度掉到 0.90 左右看起来变差了。但平衡准确度从 0.5 跳到 0.85 上下正例召回率从 0 跳到 0.8 以上。这时候如果你只看准确度会把一个真正有用的模型毙掉留下一个什么都不做的空壳。class_weightbalanced的作用是按类别频率反比给权重公式是n_samples / (n_classes * np.bincount(y))。它对逻辑回归这种线性模型效果直接因为它改变了损失函数里每个样本的贡献。但要注意加权之后的predict输出概率不再校准输出的概率值不能直接当真实概率用只能用来排序或按阈值切。这一点在需要输出风险分值的业务里很要命得配合CalibratedClassifierCV重新校准。这一段我想强调的核心就一句准确度在不平衡数据上几乎不携带信息。它衡量的是你有没有学会猜多数类而这件事连DummyClassifier都能做到。看到 0.98 的准确度先别高兴先算一下多数类占比两者差不到 5 个点就基本没意义。4. 常见问题与排查实录这一节全部来自实际调试现场不是文档摘抄。每一条我都尽量给出触发条件和判断方法。4.1 报错与警告速查表现象常见原因处理方式Classification metrics cant handle a mix of binary and continuous targets把predict_proba的概率直接传给了accuracy_score先阈值化或argmax再传入Found input variables with inconsistent numbers of samplesy_true和y_pred长度不一致常见于筛选数据后忘了同步检查两者shape[0]或统一reset_index(dropTrue)TypeError: accuracy_score() takes 2 positional arguments老代码用位置参数传normalize改成normalizeFalse关键字写法准确度恒为 0 或极低但模型明明能训练标签类型不一致如y_true是字符串、y_pred是整数统一用LabelEncoder或在预测后映射回原标签准确度比交叉验证高出很多评估时用了训练集或做了特征筛选后再划分严格先划分再拟合任何预处理top_k_accuracy_score结果明显偏高labels缺失或与概率矩阵列顺序不一致显式传labelsclf.classes_多标签任务准确度极低accuracy_score在多标签下是子集准确率要求整行完全匹配改用hamming_loss或f1_score(averagesamples)表格里第一条和第四条是我遇到最多的。第四条尤其阴险sklearn 在某些版本里不会报错而是把所有样本都判为不匹配直接返回 0.0。你看到 0 的时候第一反应通常是模型坏了其实是标签没对齐。快速验证方法打印set(y_true)和set(y_pred)看两个集合有没有交集。没交集就是类型或编码问题跟模型无关。4.2 数值对不上时的排查顺序我给自己定了一套固定的排查顺序避免东一榔头西一棒子第一步确认样本量。len(y_true) len(y_pred)且等于你预期的测试集大小。差一条都会让结果偏移。第二步确认标签空间。两个数组的unique值集合是否一致。如果不一致看是类型问题还是真的有类别缺失。第三步手算一遍。np.mean(np.asarray(y_true) np.asarray(y_pred))和accuracy_score的结果比。如果两者不一致说明中间有sample_weight或者数据类型隐式转换在捣鬼。第四步检查索引对齐。这是 pandas 用户专属的坑如果你对 DataFrame 做了sort_values或者dropna之后没有reset_index索引会变得稀疏。sklearn 内部会把输入转成 numpy 数组不按索引对齐只按位置对齐。结果就是你以为在比较同一批样本实际上张冠李戴。这种错误不会报错只会给你一个偏低但看起来合理的数字极难发现。提醒所有传给 sklearn 指标的标签最好在传入前统一.values或np.asarray()把 pandas 的索引语义彻底切断。多写这一句能省掉后面几小时的排查。4.3 我在实际项目里踩过的坑第一个坑是随机种子。同一份代码跑两次准确度差 0.03我一度以为是数据读错了查了半天发现是train_test_split没设random_state。小数据集上这个波动很正常因为测试集本身只有几百条划分一变结果就变。后来我养成了固定种子的习惯同时在报告里写清楚单次划分结果波动范围约 ±0.02不让别人把小数点后两位当真。第二个坑是多次评估取最优。有些人会跑十几个随机种子挑准确度最高的那次写进报告。这在技术上不算错但在汇报上是自欺欺人。正确做法是固定划分跑一次或者用交叉验证报均值和标准差把不确定性一起呈现出来。第三个坑是预处理泄漏。有一次我先对全量数据做了标准化再划分训练测试集准确度虚高了好几个点。原因是标准化时用到了测试集的均值和方差等于提前泄露了信息。正确的顺序永远是划分 → 在训练集上fit预处理器 → 用同一个预处理器transform测试集。用Pipeline能强制你遵守这个顺序我现在的项目里几乎没有裸着调用StandardScaler的地方。第四个坑比较隐蔽多分类里某个类别在测试集中一条样本都没有。accuracy_score照常计算不会报错但这个类别的信息实际上完全没被评估到。这种情况在小样本多分类里很常见解决办法是用train_test_split(stratifyy)保证每类都进测试集或者干脆用分层 K 折。如果某个类别样本数少于折数StratifiedKFold会直接报错提醒你这反而是好事——早报错早处理。5. 什么时候该用 accuracy什么时候必须换指标这一节是全篇的落脚点。我不打算给一个永远用 XXX的结论因为指标选型从来都是看场景的。5.1 一张判断表解决大部分选型问题数据情况关注点推荐主指标accuracy 的角色类别均衡、错误代价对称总体判对率accuracy主指标够用类别不均衡多数类 80%少数类识别balanced_accuracy / recall / f1仅作参考不单独汇报假正例代价高误报骚扰用户精确率precision / f1辅助假反例代价高漏检召回率recall / PR 曲线辅助多分类且各类同等重要类间均衡macro f1 / balanced_accuracy辅助多分类且允许候选列表前 k 命中top_k_accuracy不用多标签逐标签表现hamming_loss / samples f1不用子集准确率过于严格需要概率校准概率可靠性brier_score / log_loss不用这张表的用法是先看第一列定位自己的场景再看第三列确定主指标最后用第四列决定 accuracy 放在什么位置。绝大多数情况下 accuracy 不是不能用而是不能只用。5.2 把准确度用对的几个工程习惯第一个习惯永远同时报基线。训练任何分类模型之前先跑一遍DummyClassifier把它的准确度记下来。你的模型准确度减去这个基线才是模型真正学到的增量。这个数字在 0.03 以内的时候我基本会怀疑模型没学到东西或者特征里根本没有可用信号。第二个习惯准确度和混淆矩阵绑在一起出现。单独一个accuracy_score的输出信息量太低配上confusion_matrix才能定位到哪一类被错判成了哪一类。我在代码里封装了一个评估函数固定打印准确度、平衡准确度、混淆矩阵、分类报告四件套任何模型都走这个函数避免临时起意漏掉关键信息。第三个习惯把阈值和指标放在一起讨论。前面提过predict()的 0.5 阈值是一个默认约定不是最优选择。如果你的业务能接受多报一些、少漏一些那就应该主动把阈值调低然后报出对应阈值下的准确率、精确率、召回率。只报默认阈值下的准确度等于隐藏了一个重要假设。第四个习惯样本量小的时候别报小数点后四位。500 条测试集上一条样本的差异就是 0.2%报 0.8743 这种精度纯属自欺欺人。我一般报两位小数同时附上交叉验证的标准差让读者自己判断这个数字有多稳。注意balanced_accuracy_score在多分类下是各类召回率的算术平均对少数类更敏感。它和accuracy一起看能快速判断准确度高是因为真的学会了还是因为多数类太多。这两个数字差距超过 0.1 的时候就该警惕类别不平衡了。最后分享一个我自己常用的小检查把测试集按预测置信度排序取置信度最低的 5% 样本单独算一次准确度。如果这 5% 的准确度低到离谱比如 0.1说明模型在边界样本上基本靠猜整体准确度是被高置信度样本撑起来的。这个分层视角在很多评估报告里看不到但对判断模型能不能上线很有参考价值——毕竟线上出问题往往就出在那些模型拿不准的样本上。