
上个月帮课题组审一篇投稿模拟值和实测值的散点图画得挺漂亮R²标了0.91正文结论写“模型表现优秀”。我顺手在数据表里算了一下NSE只有0.63。审稿意见里我没有一票否决但让作者补了一段解释模拟结果在低流量段系统性偏低R²再高水文预测又有多少可信度这两个指标在论文里经常同时出现也经常被当成“双保险”来用。可它们衡量的是两件不同的事甚至在特定条件下会得出完全相反的结论。纳什效率系数Nash-Sutcliffe efficiencyNSE和可决系数coefficient of determinationR²公式结构几乎一样但背后的条件假设、对偏差的敏感度、适用范围差异大得惊人。这篇文章想把这层窗户纸捅破。刚接触水文模型或机器学习回归评价的朋友以及跑过模型但总觉得指标解释不透的工程师都可以往下看。1. 两个指标长得差不多肚子里算的却不是同一笔账1.1 NSE把“实测均值”当成裁判NSE是Nash和Sutcliffe在1970年提出来的当时就是给水文模型用的。它的公式是NSE 1 - Σ(Oi - Si)² / Σ(Oi - Ō)²其中Oi是第i个实测值Si是第i个模拟值Ō是实测序列的均值。分子是模拟值和实测值的误差平方和分母是实测序列偏离其均值的平方和。这个结构有个特别直观的解释分母代表“如果完全不知道实测值只拿平均值去预测”会产生的误差分子代表“用了模型之后”还剩下的误差。所以NSE0就是模型和“直接取平均”打了个平手NSE0说明模型比拍脑袋取均值强NSE最大为1但可以小于0说明模型预测比直接用实测均值预测还要差。注意分子里是(Oi - Si)不是某个回归拟合值和实测值的差。也就是说误差是相对于1:1线算的不是相对于回归线算的。这一步差异就是NSE和R²分道扬镳的起点。1.2 R²只看方向不看幅度R²诞生于线性回归最正经的定义是“回归模型解释掉的方差占总方差的比例”。对一元线性回归来说它也等于实测值和拟合值相关系数的平方。但在模型验证的语境里大家口中的R²通常指皮尔逊相关系数的平方R² [Σ(Oi - Ō)(Si - S̄)]² / [Σ(Oi - Ō)² · Σ(Si - S̄)²]它回答的问题是模拟值和实测值是不是同步涨落。同步涨落可以是同向的任意直线。斜率为0.5算完美斜率为1也完美斜率为2还是完美只要散点落在一根直线上R²就接近1。问题就在这里水文模型要的是模拟值和实测值落在1:1线附近而不是落在某条斜率0.5的直线附近。R²没有能力分辨这两种状态。它天生就不该承担这个任务但很多人把它硬拉过来当拟合优度用。1.3 先给一个关键结论NSE通常不会超过R²先给结论只要R²按皮尔逊相关系数的平方来算NSE通常小于等于R²。且不是略小于而是可能小到令人警觉。我这些年看过太多R²0.9、NSE0.6的组合看上去像模型在“某一项指标上不佳”其实这个差距本身就是模型结构或参数的重要诊断信息。下一节我从数学上把它拆开看看这个不等式从哪来。2. 数学拆解为什么R²漂亮的时候NSE会拖后腿2.1 三段式推导把差距看得明明白白先假设模拟值和实测值的均值相同也就是暂时不考虑系统偏差只看波动形态的影响。设实测序列O的方差为σO²模拟序列S的方差为σS²两个序列的相关系数为r。把NSE的分子展开除以样本量n之后会得到Σ(Si - Oi)² / n σS² σO² - 2rσSσO再除以σO²NSE可以写成NSE 1 - (σS²/σO² 1 - 2r·σS/σO) 2rk - k²其中k σS/σO也就是模拟值标准差相对实测标准差的倍数。而R²r²。于是NSE 2rk - k² r² - (k - r)² ≤ r² R²等号只在kr时成立也就是模拟序列的标准差倍率恰好等于相关系数同时均值没有偏差。一旦k偏离r哪怕一点点NSE就会低于R²。这个推导并不复杂但它把两个指标的差异性一次性讲清楚了R²只关心相关系数r而NSE还要看模拟值的波动幅度k是否与r匹配。2.2 一切偏差都要在NSE里加倍偿还上面是均值对齐后的情况。如果模拟值整体偏低或偏高分子里还会多出一项n(μS - μO)²这一项除以Σ(Oi - Ō)²之后是正的所以NSE还要再往下降。R²对均值偏差毫无感知因为相关系数看的是减去各自均值之后的共同波动。这就是为什么系统偏差会让“R²高、NSE低”这个组合频繁出现。用大白话说R²只关心两个人走路方向是否一致NSE还计较两个人一步跨多远、位置是不是越走越偏。方向一致但步子明显小了R²照样给高分NSE就会诚实地把分扣下来。2.3 两个极端例子直观感受差异我经常用两个极端例子给人演示这个差异。第一组数据是实测序列1到10模拟值取0.7倍的实测值。散点图完美落在一根直线上R²1无可挑剔。但NSE呢分母82.5分子是Σ(0.3Oi)²34.65NSE0.58。幅度打七折NSE直接从1掉到0.58。第二组数据模拟值等于实测值整体加3也就是把曲线平行抬高3个单位。R²依然是1NSE变成1 - 90/82.5 ≈ -0.09模型比直接用实测均值预测还差。这两个例子足以说明R²高不等于模型好NSE低也不代表模型是一团糟它只是在告诉你“方向和幅度不匹配”。真正要紧的是搞清楚不匹配的原因而不是盯着某个数字发呆。3. 你以为的R²是哪个R²工具之间差了一整个NSE3.1 Excel、Python、R 里藏着三个“R²”真正到动手计算时还会遇到一个更隐蔽的问题不同工具里的R²根本不是同一个公式。工具/函数实际计算内容对系统偏差的反应Excel 的 RSQ 函数cor(O, S)²经典R²不敏感Python 的 sklearn.metrics.r2_score1 - Σ(O-S)² / Σ(O-Ō)²敏感等价于NSER 语言 cor(O, S)^2cor(O, S)²经典R²不敏感Origin 中 R-Square (COD)取决于残差相对回归线还是1:1线可能不同手写线性回归决定系数1 - Σ(O-Ŝ)² / Σ(O-Ō)²Ŝ来自回归线基于回归线sklearn的r2_score之所以容易被误用是因为它的数学定义就是“残差平方和除以总平方和”。当第一个参数传观测值、第二个参数传模拟值时分子是Σ(Oi-Si)²分母是Σ(Oi-Ō)²写出来和NSE一模一样。所以你千万别以为调用r2_score就算出了统计学意义上的R²。它算出来的其实是简化版的NSE。3.2 同一份数据两个“R²”能差出0.2同一个数据集用Excel RSQ可能给出0.95用sklearn r2_score可能给出0.72。如果你在论文里写法是“R²采用模拟与实测相关系数的平方”实际却用了sklearn报告的数字就是NSE而不是R²。这就是为什么有些文章里R²和NSE两个值几乎一样不是两个指标互相印证而是同一个公式被算了两遍。反过来如果你真想算经典R²请用cor(obs, sim)**2或者Excel的RSQ或者手动把偏差平方和算清楚。3.3 审稿时判断“指标有没有算对”的小习惯我现在审稿有个固定动作先把R²和NSE放一起看差值。如果二者非常接近我会去方法部分查R²的定义如果R²比NSE大很多我会去看配对散点图找系统性偏差。这个方法帮我发现了不少工具层面的问题。有一次对方回复说“我们把sklearn的r2_score当成了R²实际应该用cor的平方”改过来之后模型结论并没有变但指标的解释方式和讨论深度完全不同了。所以说工具选错了数字本身不会报警只有你自己足够警觉才行。4. 实战选型不同任务该用哪一套指标4.1 水文模型率定NSE唱主角KGE做补充水文行业里NSE从1970年活到现在依然是使用频率最高的效率指标。大家常见的经验分级大致是NSE大于0.75算优秀0.65到0.75算良好0.5到0.65算可接受低于0.5就需要反思模型结构或率定策略。更严谨的做法是结合站点、时段、变量类型来看不同流域的基准值可能差别很大。但NSE不是万能的。它对高值敏感对低流量漠不关心还容易受离群点影响。所以现在很多水文研究里会搭配KGE也就是Kling-Gupta效率系数KGE 1 - sqrt((r-1)² (α-1)² (β-1)²)其中r是相关系数α是模拟与实测标准差之比β是均值之比。它把相关性、变率、偏差三条腿分开惩罚哪个环节出问题一目了然。如果你只用NSE做率定可能把误差藏在高值里加上KGE之后系统偏差会被单独拎出来。4.2 机器学习回归R²RMSEPBIAS的组合拳在机器学习回归任务里更常用的还是经典R²和RMSE。这里R²用来衡量模型解释了多少方差RMSE衡量绝对误差但这还不够。很多回归模型测试集上的R²很高一画残差图却发现预测值整体偏高。所以我建议加一个PBIAS百分比偏差PBIAS 100 × Σ(Si - Oi) / ΣOiPBIAS接近0说明总体无偏正数代表高估负数代表低估。R²、RMSE、PBIAS三个值放在一起基本能覆盖趋势、误差大小、系统性偏差三个维度。单独看R²真的容易被一两个离群点或者趋势掩盖问题。4.3 洪峰与小流量NSE的变形记如果你的研究对象是洪水过程NSE对洪峰附近的大误差特别敏感容易被一两场大洪水带偏如果是枯水期研究NSE又可能对低流量段的小误差视而不见。常见做法是对流量取对数后再算NSE把低流量的相对误差放大这样更公平。但要注意log空间的NSE和原始流量空间的NSE没有可比性论文里必须写清楚“NSE是基于对数变换后的流量计算的”。还有相对NSE、逐时NSE等变形思路都是调整误差在不同流量段的权重。4.4 我的多指标组合建议我的建议是不要把宝押在任何一个指标上。用NSE看总体过程吻合度用R²看趋势一致性用PBIAS看总体偏差再配合散点图和时间序列叠图。如果只是写一篇普通报告两个指标加一张图足矣但如果是发论文就按上面的组合把指标定义和数据变换都写清楚。审稿人最反感的不是数字不好看而是数字来自哪个公式都说不清。5. 完整计算演示一份数据把三个指标盘明白5.1 先用一组手算都能算的序列热个身先看一个不用写代码的例子。实测序列是1到10的等差数列模拟值取实测值的七折。前面算过R²1NSE0.58。如果你在论文里只放R²1读者会觉得模拟完美把NSE0.58放进去立刻就暴露了幅度被压缩的问题。再看模拟值整体加3的情况R²1NSE-0.09等于模型预测比直接用实测平均值预测还差。这就是因为全部误差都被系统偏差吃掉趋势相关却没受到任何惩罚。5.2 用真实感更强的随机序列跑一遍代码更贴近现实的场景我用一段Python代码演示代码不长可以直接跑。import numpy as np np.random.seed(2024) obs np.random.lognormal(mean4.0, sigma0.6, size120) sim 0.7 * obs 2.0 np.random.normal(0, 5.0, size120) obs_mean obs.mean() nse 1 - np.sum((obs - sim) ** 2) / np.sum((obs - obs_mean) ** 2) r2_corr np.corrcoef(obs, sim)[0, 1] ** 2 r2_sklearn_style 1 - np.sum((obs - sim) ** 2) / np.sum((obs - obs_mean) ** 2) pbias 100.0 * np.sum(sim - obs) / np.sum(obs) print(fNSE: {nse:.3f}) print(fR2 (corr^2): {r2_corr:.3f}) print(fsklearn r2_score: {r2_sklearn_style:.3f}) print(fPBIAS: {pbias:.1f}%)数据构造的逻辑是观测值服从对数正态分布模拟值取观测的0.7倍再加一个常数偏移和一点随机噪声。这样造出来的序列趋势与观测高度同步但整体明显偏低。运行结果里R²(corr²)会在0.95以上NSE在0.7上下PBIAS接近-30%。如果你复制代码运行会得到确定的结果可以把四个print的值抄下来。5.3 结果该怎么解读为什么R²接近0.97而NSE只有0.7因为模拟值的形状和观测基本同步相关系数高但它只有观测的七成幅度再加上常数偏移和噪声导致绝对误差变大。PBIAS接近-30%直接告诉你模型平均低估了约三成。这三个数字放在一起问题就不再是“模型好不好”而是“趋势对了量级错了需要修正增益系数”。如果只报告R²你根本不会去想这个问题。这就是多指标配合的价值。6. 踩坑实录这些年我在指标上闹过的笑话6.1 把同一个公式算了两次还当成了两个指标最典型的翻车现场。我见过一篇稿子方法部分写了R²按相关系数平方计算结果部分的R²和NSE都是0.91。一查数据作者用sklearn算R²pandas算NSE两个函数底层公式完全一致。审稿意见里我提醒了一句作者改完才发现其实论文里一直只有NSE一个指标。6.2 对log变换后的数据直接报NSE有个项目里模型原始NSE只有0.55取log后NSE变成0.83汇报时差点写“模型表现显著提升”。后来发现只是计算空间变了模型根本没改。NSE本身无量纲但它依赖误差平方和数据变换会改变误差的相对权重所以不同空间算出的NSE不能互相比较。论文里要报告就统一口径要么都原始量纲要么明确标注变换。6.3 拿NSE对比不同量纲的模拟有一次我用同一套模型模拟径流量和泥沙量径流NSE0.82泥沙NSE0.35于是判断泥沙模块很差。后来细想泥沙数据变异性极大误差平方和天然占优NSE更容易偏低但绝对误差不一定比径流大多少。指标可以作参考但跨变量比较必须谨慎最好回到标准化残差或泰勒图上。6.4 别让两个数字替你拍板图形才是裁判NSE和R²都是把整条时间序列压成一个数信息损失非常大。模型在洪水期低估、枯水期高估两个指标可能都不那么差但过程完全不对。我现在的地图是先画时间序列叠图和1:1散点图再看NSE、R²、PBIAS必要时上KGE。图形是诊断数字是裁判裁判也要分几个岗位。最后分享一个习惯。我拿到新的模拟结果会先把R²和NSE的差值算出来差值小于0.05说明模型基本无偏差值在0.1到0.2多半有幅度偏差差值超过0.3模型大概率存在系统性结构问题。这个经验值不绝对但能帮我快速判断该往哪个方向排查。做模型评价说到底不是比谁的R²高而是比谁更理解自己手里的数字。把指标的定义、计算空间、工具差异都交代清楚你的结果才经得起审稿人来回看。