3. 分类问题基础:二分类与多分类,混淆矩阵

3.1 二分类 vs 多分类——问题的本质

做RNA-seq模型评估,先得搞清楚你面对的是什么类型的分类问题。其实就两种:二分类和多分类。

二分类,说白了就是“是”还是“不是”。比如判断一个基因是否差异表达、一个样本是否患病。输出只有两个标签,通常记作正类(Positive)和负类(Negative)。我刚开始接触RNA-seq数据分析时,觉得二分类太简单了,不就是0和1吗?但后来发现,真正麻烦的往往就是这种问题——类别极度不平衡。

多分类呢,是三个或更多互斥的类别。比如把肿瘤样本分成亚型A、B、C、D。每个样本只能属于一个亚型,标签之间没有顺序关系。你想想看,如果类别之间有层次关系(比如组织类型→细胞类型),那就是层级分类,不在我们今天讨论范围。多分类和二分类的核心区别在于——评估维度从“2×2”变成了“N×N”。

我个人习惯:拿到一个新问题时,先用二分类思维把最核心的判别任务拎出来。如果本质上是多分类,再扩展。这样不容易被多个类别的混淆矩阵搞晕。

3.2 混淆矩阵:模型预测的“两两对照表”

不管二分类还是多分类,评估模型的第一步都是构建混淆矩阵。它把真实标签和预测标签做交叉统计,形成一个方阵。

先看二分类的混淆矩阵长什么样:

真实 \ 预测 预测为正类 预测为负类
真实为正类 TP(真正例) FN(假负例)
真实为负类 FP(假正例) TN(真负例)

TP、FN、FP、TN这四个数字,就是我评估模型的基础原材料。所有后续指标——准确率、精确率、召回率、F1、特异度——全是从它们算出来的。

举个例子,假设我们训练了一个分类器预测基因是否在某种疾病中过表达。测试集有100个样本,其中40个真实过表达(正类),60个真实未过表达(负类)。模型预测出35个正类,其中30个猜对(TP=30),5个是误报(FP=5);剩下的65个预测为负类,其中55个真实负类猜对(TN=55),10个是漏报(FN=10)。你就把这四个数填进矩阵,开始了你的评估之旅。

我的习惯:拿到混淆矩阵后,第一件事不是算准确率,而是看FP和FN分布。如果FP很高,说明模型“过度敏感”,总是乱报阳性;如果FN很高,说明模型“过于保守”,漏了很多真阳性。这个直觉比单一指标更有用。

多分类的混淆矩阵是二分类的扩展:真实类别有K个,预测类别也有K个,矩阵变成K×K。对角线上的值是分类正确的个数,非对角线上的值是误分类到其他类的个数。比如三分类的矩阵:

真实 \ 预测 A B C
A 正确A A→B A→C
B B→A 正确B B→C
C C→A C→B 正确C

分析多分类矩阵时,我习惯先看每一行的非对角线元素——那些是漏分的样本集中在哪里?再看每一列的非对角线元素——哪些类别容易被误判进来?这个信息能直接指导特征工程。

3.3 从混淆矩阵导出的核心指标

二分类的常用指标,我列个表格对比:

指标 公式 含义 什么时候关注
准确率 (Accuracy) (TP+TN)/(TP+TN+FP+FN) 所有样本中预测正确的比例 类别平衡时
精确率 (Precision) TP/(TP+FP) 预测为正类中有多少是真实正类 FP代价高(如误诊)
召回率 (Recall) TP/(TP+FN) 真实正类中有多少被找出来了 FN代价高(如漏诊)
特异度 (Specificity) TN/(TN+FP) 真实负类中有多少被正确排除 需要优先保证负类识别
F1分数 2 × (P×R)/(P+R) 精确率与召回率的调和平均 需要平衡时

我用Python构建混淆矩阵和计算指标,代码一般长这样:

from sklearn.metrics import confusion_matrix, classification_report

y_true = [1, 0, 1, 1, 0, 0, 1, 0]
y_pred = [1, 0, 1, 0, 0, 1, 1, 0]

cm = confusion_matrix(y_true, y_pred)
print("Confusion Matrix:\n", cm)

report = classification_report(y_true, y_pred, target_names=['Negative', 'Positive'])
print(report)

对于多分类,光看全局准确率远远不够。我们需要对每个类别分别计算精确率、召回率,然后做宏平均(macro-average)或加权平均(weighted-average)。宏平均就是把每个类别的指标简单平均,不考虑类别样本数,适合各类别同等重要时。加权平均按各真实类别的样本数加权,更能反映整体性能。

我曾经踩过一个坑:多分类评估时只看了全局准确率,觉得自己模型90%不错。后来一查混淆矩阵,发现模型把90%的样本都预测成最常见的类别A,其他类别几乎没识别。准确率被类不均衡“骗”了。从那以后,我坚持必须先看每个类别的召回率和混淆矩阵的可视化热图。

3.4 多分类场景下的混淆矩阵——扩展与聚合

多分类的原始混淆矩阵是K×K的方阵,但我们可以把它“降解”成K个二分类问题:对每个类别,把该类视为正类,其他所有类视为负类。然后计算每个类别的TP、FP、FN、TN,再算指标。这叫“一对多”策略。

比如对类别A:

  • 真正类TP_A = 对角线上A对应的值
  • 假正类FP_A = 列A中除去对角线的所有值之和(即其他类别被误判为A的个数)
  • 假负类FN_A = 行A中除去对角线的所有值之和(即A被误判为其他类别的个数)
  • 真负类TN_A = 所有非A行、非A列的元素之和

这样每个类别都能得到一套二分类指标。我个人习惯把这些指标画成雷达图,直观看出哪个类别是薄弱环节。

下面这张SVG图总结了本章的核心概念关系:从预测结果出发,构建混淆矩阵,再引出关键指标,并区分二分类与多分类的评估路径。

混淆矩阵:从预测到评估的核心结构 二分类混淆矩阵 (2×2) TP 真正例 FN 假负例 FP 假正例 TN 真负例 真实 真负 预测为正 预测为负 多分类混淆矩阵 (K×K) A→A A→B A→C B→A B→B B→C C→A C→B C→C 真实A 预测B 预测C 从矩阵到指标 二分类指标 · 准确率 = (TP+TN)/(TP+TN+FP+FN) · 精确率 = TP/(TP+FP) · 召回率 = TP/(TP+FN) · 特异度 = TN/(TN+FP) · F1 = 2×(P×R)/(P+R) 多分类指标策略 1. 一对多:将K类转化为K个二分类 2. 宏平均:各指标简单平均 3. 加权平均:按样本数加权 关键提醒 ⚠️ 不平衡数据别只相信准确率 ⚠️ 多分类必看每个类的召回率 ⚠️ 混淆矩阵是“原始证据” 先看混淆矩阵,再谈优化
核心认识:混淆矩阵是评估的“原始数据”。不管后面怎么画ROC曲线、做交叉验证,第一步永远是把真实标签和预测标签对好,填进矩阵。我每次做RNA-seq模型评估,都会先打印这个矩阵,再往下走。它能暴露的问题,比任何一个复合指标都直接。

3.5 常用API速查

Python的sklearn.metrics包基本覆盖了所有功能。下面是我常用的几个函数:

from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score, roc_auc_score

# 二分类示例
y_true = [0,1,0,1,1,0]
y_pred = [0,0,0,1,1,0]

cm = confusion_matrix(y_true, y_pred)
acc = accuracy_score(y_true, y_pred)
prec = precision_score(y_true, y_pred)
rec = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)

print(f"Confusion Matrix:\n{cm}")
print(f"Acc:{acc:.3f} Prec:{prec:.3f} Rec:{rec:.3f} F1:{f1:.3f}")

# 多分类示例(一对多自动处理)
from sklearn.metrics import classification_report
y_true_multi = [0,1,2,0,1,2]
y_pred_multi = [0,1,1,0,2,2]
report = classification_report(y_true_multi, y_pred_multi, target_names=['Class0','Class1','Class2'])
print(report)

用classification_report一次性输出每个类别的精确率、召回率、F1,以及宏平均和加权平均,非常方便。

我的小技巧:如果数据集很大,我会先用pandas的crosstab函数把混淆矩阵可视化出来,再加个颜色映射,一眼看出哪些类混淆严重。
pd.crosstab(y_true, y_pred, rownames=['True'], colnames=['Predicted'])

二分类与多分类、混淆矩阵与衍生指标,是整个模型评估的基石。你理解了它们,后续的ROC曲线、交叉验证等高级话题,说白了就是在这个基础上叠加更精细的分析维度。