相信很多人在MATLAB里第一次看到rocmetrics输出结果时,都会盯着Threshold那一列发呆:阈值怎么还有负的?我印象里的阈值不是应该固定在一个区间里吗?这个问题并不复杂,但确实容易让新手困惑很久。因为在绝大多数教程里,模型输出的预测结果都被描述成“概率”,概率的取值范围天然在0到1之间,于是大家默认“ROC曲线的阈值也必须在0到1之间”。甚至有人遇到负阈值之后,第一反应是代码写错了,跑去论坛发帖求助。
但实际上,这个默认想法并不准确。rocmetrics计算的是ROC曲线上的坐标点,而ROC曲线本质上描述的是:当分类器采用不同阈值时,真正例率和假正例率如何联动变化。这里的阈值,并不是指“概率临界值”,而是指“预测分数(score)的临界值”。预测分数未必是概率,它可以来自线性回归的原始输出、SVM的决策函数、判别分析的距离值等,这些分数的取值范围可能是整个实数轴。既然分数可以为负,以分数为基准的阈值当然也可以为负。
如果你手头有分布从负数延伸到正数的预测分数,却不知道为什么rocmetrics会给出负阈值,那么这篇文章就是为你写的。无论你是正在用MATLAB做二分类模型评估的学生,还是在工作中需要交付分类模型报表的工程师,搞懂这个细节都能帮你少走弯路。我会把原理、代码、踩坑记录放在一起讲,尽量让你看完就能直接上手。
1. ROC曲线与阈值的基础认知
1.1 阈值在二分类中扮演什么角色
先聊一个最朴素的问题:为什么分类器需要有阈值?
二分类模型处理的是“是/否”这类问题,比如判断一封邮件是不是垃圾邮件,判断一个客户会不会流失,判断一张影像是否显示异常。但在绝大多数情况下,模型并不会直接输出一个“是”或“否”的标签,而是输出一个衡量倾向性的分数——比如邮件属于垃圾邮件的概率是0.87,客户流失的倾向得分是-3.5,影像异常的置信度是0.62。我们拿到分数之后,需要一个规则来决定“多少分以上算1,多少分以下算0”。这个分界线,就是阈值。
可以用一个生活化的例子来理解。假设一位老师根据学生的综合表现打了一个“综合分”,分数本身能反映学生水平的高低排序,但最终“通过”和“不通过”的界限需要额外划定。分数线定得高,通过的人就少,但通过的人大概率是真优秀的;分数线定得低,通过的人就多,但难免混进一些不够格的。这个分数线就是阈值,分数线高低带来的通过率变化,就是灵敏度与误判率之间的权衡。
在机器学习的标准术语里,阈值的作用可以描述为:对于一个给定的预测分数s和阈值t,如果s ≥ t,则预测为正类,否则为负类。可以看到,阈值本质上是分数坐标轴上的一个参照点,这个坐标轴对应的是模型输出分数的分布空间。分数可以映射到哪个范围,阈值就可以出现在哪个范围,并没有谁规定它必须落在0到1之间。
1.2 rocmetrics在MATLAB中的地位与作用
明白了阈值是干嘛的,就可以来看rocmetrics了。它是MATLAB提供的专门用来评估二分类器性能的函数,核心工作是计算ROC曲线的坐标点、AUC、最优工作点等指标。在它出现之前,大家通常用perfcurve函数,现在新版MATLAB推荐改用rocmetrics。
它的基本用法很简单:
matlab复制% 假设labels是真实标签向量,scores是对应每个样本的预测分数
% 第三个参数指定把哪一类当成正类
rm = rocmetrics(labels, scores, 1);
输入的三样东西,正是前面反复提到的概念:labels是真实答案,scores是模型给出的分数,第三个参数指定把哪一类当成正类。输出的时候,函数会返回一个rocmetrics对象,里面自带表格,包含了各个候选阈值对应的TPR、FPR,以及AUC等汇总指标。我们可以直接从这个表格里看到完整的阈值序列,也能绘制出标准的ROC曲线图。
很多人第一次看到rocmetrics的Threshold列时,会下意识觉得“阈值应该在0到1之间”,但实际上这个函数并不会替你把分数“翻译”成概率,它只是基于你给的scores进行排序、求唯一值、再逐个计算指标。换句话说,阈值序列的取值范围,完全由scores的取值范围决定。这个细节,是理解负阈值的关键起点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么阈值会出现负值——数据角度的答案
2.1 分数(score)不一定是概率
这是最核心的一层认知。在机器学习的评估链路中,模型输出的分数有两种常见形态。
第一种是概率形式。逻辑回归的predict_proba、神经网络经过softmax层的输出,都属于这一类。概率的取值范围被限制在[0,1]之间,如果我们拿这种分数去计算ROC曲线,阈值自然也会落在[0,1]内。
第二种是决策值/原始分数形式。很多模型在输出概率之前,会先计算一个实数域的决策量。最典型的是线性SVM,它的决策函数输出是一个有正有负的实数值,符号决定了类别方向,绝对值代表距离决策面的远近。线性回归、线性判别分析(LDA)、偏最小二乘判别(PLS-DA)的原始输出也属于这一类,这些分数的范围取决于输入特征和模型权重,可能是[-100, 200]、[-5, 8],甚至更大范围。
举个例子,我们训练一个线性模型来预测“某笔交易是否异常”,输入的原始特征是额度、频率、历史平均偏差等。模型的输出是
score = β0 + β1x1 + β2x2 + ...
如果β0=-3,并且某些特征本身是负的,那么许多正常样本的score会是负数。这完全正常,并不意味着模型坏了。倒不如说,模型选择用“零”作为决策面的参照,负分数代表“不太像正类”。
当把这些score直接传给rocmetrics时,函数内部会收集所有score的唯一值,按从大到小排序,取每一个值作为候选阈值,逐一计算混淆矩阵和派生指标。因为scores里有负数,thresholds里自然也会出现负数。
2.2 rocmetrics如何确定阈值序列
具体到内部逻辑,rocmetrics生成阈值序列的方式并不玄乎。你可以理解成下面这几步:
- 接收真实标签labels和预测分数scores;
- 获取所有scores的唯一值,并按从大到小的顺序排列;
- 在每个唯一分数值处,模拟“如果以这个值作为阈值,哪些样本会被判为正类,哪些会被判为负类”;
- 基于每次划分的结果计算TPR、FPR等指标,形成ROC曲线上的一个点;
- 最后把这些点连起来,就是ROC曲线。曲线下的面积就是AUC。
正因如此,阈值的最大值就是scores的最大值,阈值的最小值就是scores的最小值。如果scores覆盖了负数区间,阈值就会覆盖负数区间。这就像我们用一把尺子量物体长度,物体最短的是-5厘米(比如参照点设在非零位置),那尺子上会出现的刻度也包括-5,这是件很自然的事。
用一个简单的例子来说明。假设有5个样本,真实标签和分数如下:
| 样本 | 真实标签 | 分数 |
|---|---|---|
| A | 0 | -3.0 |
| B | 0 | -1.5 |
| C | 1 | 0.2 |
| D | 1 | 0.2 |
| E | 1 | 2.5 |
对scores求唯一值并降序排列,候选阈值包括2.5、0.2、-1.5、-3.0。每个阈值对应的划分结果完全不同:
- 阈值为2.5时,只有E被预测为正类;
- 阈值为0.2时,C、D、E被预测为正类;
- 阈值为-1.5时,B、C、D、E都被预测为正类,B其实是负类,所以这里已经出现了误报;
- 阈值为-3.0时,所有样本都被预测为正类,A也被误报。
这个例子直观地展示了为什么负阈值也有意义:它对应的是“较宽松”的决策区段,在这个区段里,模型会放过更多样本进入正类阵营,从而影响TPR和FPR这两个关键指标。
还有一个细节值得注意。在实际建模时,ROC曲线最左端的点(0,0)对应的是“极端严苛”的阈值——让所有样本都不被判定为正类;最右端的点(1,1)对应的是“极度宽松”的阈值——让所有样本都被判定为正类。这两个极端点在实际计算中可能对应正无穷或负无穷,但在rocmetrics输出的Threshold列里,它们往往只体现为scores的最大值向下取整和最小值,因为超出scores范围后,再增大或减小阈值,划分结果已经不会变化了。这也是为什么你会看到阈值序列从负数一路延续到正数,中间每个值都对应一个真实的候选分界线。
2.3 阈值与模型性能指标的内在联系
理解负阈值的另一个维度,是看阈值变化时各个指标的变化趋势。
给定一个阈值t,预测结果会形成一个2x2混淆矩阵:
- TP(真正例):实际为正类,预测为正类;
- FP(假正例):实际为负类,预测为正类;
- FN(假负例):实际为正类,预测为负类;
- TN(真负例):实际为负类,预测为负类。
基于混淆矩阵,最重要的两个指标是:
- TPR(真正例率,也叫召回率/敏感性)= TP / (TP + FN);
- FPR(假正例率)= FP / (FP + TN),它等于1 - 特异性。
当阈值t很大时,只有分数很高的样本才会被判定为正类。这时候TPR比较低——因为很多真正的正类样本分数不够高,被漏掉了;FPR也很低——因为负类样本也很少能越过这么高的门槛。整体判断偏向“保守”。
当阈值t减小时,越来越多的样本会被判定为正类,TPR往上升,FPR也会跟着往上升。当阈值t变成负值时,意味着连分数为负但不算太离谱的样本也会被拉进正类阵营——这是一种相当“宽松”的判断策略。如果模型本身有区分度,那么在这个区域通常表现为TPR升高得更快、FPR升高得相对慢,ROC曲线在这一段仍会明显高于对角线。
用一个数值例子来感受一下。假设正类分数集中在5附近,负类分数集中在-3附近。如果阈值取10,正类和负类的分数都很难超过,TPR和FPR都接近0,对应ROC曲线的左下角;如果阈值取0,正类大部分通过,TPR很高,负类中有一部分也跟着越过0,FPR处于中等水平;如果阈值取-5,几乎所有样本都会被判定为正类,TPR接近1,FPR也接近1,对应ROC曲线的右上角。
换句话说,负阈值并不代表模型做错了什么,它只是“宽松策略”下对应的决策点。ROC曲线之所以要横跨整个分数范围,就是要把从极保守到极宽松的所有可能性都画出来,方便我们评估模型在不同业务偏好下的表现。
3. 从rocmetrics到阈值选择——实操环节的核心内容
3.1 读懂rocmetrics输出的每个字段
在MATLAB中运行完rocmetrics之后,返回的rm对象里可以直接访问几个关键属性:
- rm.Thresholds:阈值列,是一个数值向量,包含所有候选阈值。
- rm.ROC:一个表格,包含每个阈值下对应的TPR、FPR等坐标信息。
- rm.AUC:ROC曲线下的面积,衡量模型整体区分度。
运行代码之后,你可以用以下命令查看前几条阈值和指标:
matlab复制% 展示前10条ROC坐标
head(rm.ROC, 10)
这里特别提醒一下,在MATLAB较新版本中,plot(rm)可以直接画出ROC曲线,并在图上标注AUC。如果你只想要坐标数据,直接读取rm.ROC或rm.Thresholds即可。
在rm.ROC表格里,你会看到一行行记录,从最保守的决策点(阈值很大,几乎没有样本被判定为正类,TPR和FPR都接近0)逐步过渡到最宽松的决策点(阈值很小,几乎所有样本都被判定为正类,TPR和FPR都接近1)。字段一般包括Threshold、TPR、FNR、FPR、TNR等,具体列名视版本略有差异,但核心的TPR和FPR一定在。
3.2 实操:在MATLAB中快速跑通一个示例
与其空谈,不如直接跑个例子。我模拟一个二分类场景:负类样本的分数大致集中在负区间,正类样本的分数大致集中在零附近和正区间,两个分布有重叠,这样能得到一条比较有区分度的ROC曲线,而且阈值范围天然包含负值。
matlab复制rng(2024); % 固定随机种子,保证可复现
% 生成100个负类样本,分数在-10到3之间
neg = -10 + 13 * rand(100, 1);
% 生成100个正类样本,分数在-2到12之间,其中一些分数降到了负区间
pos = -2 + 14 * rand(100, 1);
scores = [neg; pos];
labels = [zeros(100, 1); ones(100, 1)];
% 调用rocmetrics,正类设置为1
rm = rocmetrics(labels, scores, 1);
% 查看阈值范围
fprintf('阈值范围:%.4f 到 %.4f\n', min(rm.Thresholds), max(rm.Thresholds));
% 查看头部记录
head(rm.ROC, 5)
在我的模拟数据里,scores的最小值大约是-9.97,最大值大约是11.85,因此Threshold列也会从约-9.9一路排到约11.8。你可以在输出窗口清楚地看到负数阈值的存在。这不是异常,只是数据分布的直接映射。
如果我把标签和分数换一下顺序,或者把正类指定成0而不是1,结果就会完全不同,这是新手最常见的坑之一。
运行之后,你会在rm.ROC中看到类似这样的结构(具体数值取决于随机种子):
| Threshold | TPR | FPR |
|---|---|---|
| 11.XXXX | 0.00 | 0.00 |
| 10.XXXX | 0.01 | 0.00 |
| ... | ... | ... |
| -9.XXXX | 1.00 | 1.00 |
整体趋势一定是从左上角区域一路走到右下角区域。负阈值集中在后半段,意味着越往这个方向走,模型判得越宽松,误报也随之增加。
3.3 如何选择最优阈值
知道了阈值是怎么回事,接下来要解决的实际问题是:在ROC曲线上选哪个点?也就是说,选哪个阈值做最终决策?
一般有三种常用策略。
第一种是约登指数(Youden's Index),定义是 TPR - FPR。它衡量的是“在不牺牲太多特异性的前提下提高敏感性”的平衡点。数值越接近1越好,通常我们会在候选阈值中找使约登指数最大的那一个。
matlab复制% 基于rm.ROC表格计算约登指数
thresholds = rm.Thresholds;
rocTable = rm.ROC;
youden = rocTable.TPR - rocTable.FPR;
[bestYouden, idx] = max(youden);
bestThreshold = thresholds(idx);
fprintf('约登指数最大为 %.3f,对应阈值 %.4f\n', bestYouden, bestThreshold);
第二种是“离左上角最近点”策略。把ROC平面想象成一个正方形,理想的点位于左上角(0,1)处,即FPR为0、TPR为1。对每个候选阈值,计算它到(0,1)的欧氏距离,取距离最小的阈值。
matlab复制distances = sqrt((rocTable.FPR - 0).^2 + (rocTable.TPR - 1).^2);
[~, idx2] = min(distances);
fprintf('距左上角最近阈值:
