拓冰建站拓冰建站
首页 / 资讯中心 / 正文

【RustyML入门】5.1. 回归指标

5.1. 回归指标回归指标把一组预测值和一组真值压缩成一个标量这个标量说明拟合的好坏。RustyML 把这些指标归在rustyml::metrics下。类别模块rustyml::metrics::regression把它们扁平重导出所以你可以写成rustyml::metrics::mean_squared_error来使用其中任意一个。你也可以通过 prelude 用use rustyml::prelude::*;来访问它们。本页覆盖 crate 提供的 7 个回归函数。它讲解每个函数背后的数学、每个函数对离群点的反应以及参数顺序如何改变结果。它还展示了在拟合一个 线性回归 模型之后如何把这些函数放在一起解读。这些函数名和(y_true, y_pred)的参数顺序都和 scikit-learn 保持一致。RustyML 在 2 个地方特意偏离 scikit-learn它用 panic 代替返回Result见 5.1.6 节而且它不提供adjusted_r2见 5.1.2 节。5.1.1. 7 个函数及其签名每个回归指标的形状都一样输入 2 个一维数组返回一个f64值。这些函数对 ndarray 的存储类型是泛型的。y_true和y_pred各自既可以是拥有所有权的数组Array1f64也可以是视图ArrayView1f64比如二维数组的一个.column(j)切片。两个数组都必须装f64值。没有f32的重载版本。// 其中 S1: DataElem f64, S2: DataElem f64pubfnmean_squared_errorS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnroot_mean_squared_errorS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnmean_absolute_errorS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnmedian_absolute_errorS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnmean_absolute_percentage_errorS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnr2_scoreS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;pubfnexplained_variance_scoreS1,S2(y_true:ArrayBaseS1,Ix1,y_pred:ArrayBaseS2,Ix1)-f64;参数顺序是(y_true, y_pred)真值在前预测在后。这与 scikit-learn 以及聚类指标的(labels_true, labels_pred)顺序一致。对 7 个函数中的 4 个MSE、RMSE、MAE、MedAE来说顺序无关紧要。它们都建立在abs(y_true - y_pred)或(y_true - y_pred)^2之上这两种形式都是对称的。MAPE 的分母只用到第一个参数所以 MAPE 也对顺序敏感见 5.1.5 节。对另外 2 个方差解释类的分数来说顺序的影响很大。r2_score和explained_variance_score用第一个参数的离散程度做归一化。参数写反不会报错只会悄悄返回一个不同的、错误的数值。这是搞坏回归评估最常见的方式。5.1.5 节会展示这个错误的代价。函数定义单位最优值典型范围mean_squared_errorMSE(y_true - y_pred)^2的均值目标值单位的平方0.0[0, infinity)root_mean_squared_errorRMSEsqrt(MSE)目标值单位0.0[0, infinity)mean_absolute_errorMAEabs(y_true - y_pred)的均值目标值单位0.0[0, infinity)median_absolute_errorMedAEabs(y_true - y_pred)的中位数目标值单位0.0[0, infinity)mean_absolute_percentage_errorMAPEabs(y_true - y_pred) / max(abs(y_true), eps)的均值分数乘以 100 得到百分比0.0[0, infinity)r2_scoreR^21 - SSE / SST无量纲1.0(-infinity, 1.0]explained_variance_scoreEVS1 - Var(resid) / Var(y_true)无量纲1.0(-infinity, 1.0]5.1.2. 每个指标衡量什么什么时候可以信它MSE对平方误差取平均。平方让每个残差都变正所以参数顺序不影响结果。平方还会让大残差比小残差权重更高。一个大小为 4 的误差贡献 16。四个大小为 1 的误差合计贡献 4。正是这种二次加权让多数优化器把 MSE 当作损失函数来最小化。它也让 MSE 对离群点极度敏感因为一个坏样本就能主宰整个数值。MSE 的单位是目标值单位的平方所以很难直接解读。类似误差是 0.02 平方美元这样的话没有清晰的含义。RMSE是sqrt(MSE)。开平方把数值带回目标值本身的单位于是你可以说典型误差大约是 0.15 美元。MSE 从不为负所以平方根总是有定义。RMSE 保留了 MSE 对大误差的二次强调所以它仍然对离群点敏感。当大错代价格外高、又想要真实单位下的答案时就报 RMSE。RMSE 从不小于 MAE只有当每个误差大小都相同时两者才相等。RMSE 和 MAE 之间差距大就说明有几个大残差在把 RMSE 撑高。MAE对绝对误差取平均。每个样本按它自身的误差、而不是误差的平方来贡献。这让 MAE 比 RMSE 对离群点更不敏感并且 MAE 保持在目标值的单位下。当每个单位的误差代价相同、又不想让少数极端点左右评分时就用 MAE。MedAE取绝对误差的中位数而不是均值。中位数完全不理会尾部的大小。哪怕一半样本错得离谱中位数也不会移动。这让 MedAE 成为这组指标里受离群点影响最小的一个。数据带重尾噪声、或已知有坏记录时就用 MedAE。MedAE 对它忽略的那条尾巴只字不提。当真正在乎的恰恰是那些大误差时就不要单独报 MedAE。按对离群点的敏感度从高到低排MSE 和 RMSE平方然后是 MAE线性然后是 MedAE基于排序、受影响最小。MAPE 不适用这个排序。MAPE 按真值的大小、而不是误差本身的大小来重新加权误差。MAPE是各样本相对误差的均值abs(y_true - y_pred) / max(abs(y_true), eps)其中eps取f64::EPSILON。结果是一个分数。乘以 100 就能把它说成百分比。MAPE 与量纲无关。当目标值横跨好几个数量级时这个性质很重要因为同一个绝对误差在不同尺度下意味着不同的事情。有两个行为值得留意。其一分母用的是abs(y_true)所以负的目标值可以正常处理。y_true的取值为零或接近零时会被下限截断到f64::EPSILON而不是引发除零。这个下限会让那个样本的项炸到大约10^13把整个均值拖上去。碰到万亿级的 MAPE就把它当成某个y_true值为零的信号而不是模型失败的信号。其二MAPE 不对称。低估的误差上限是 100%而高估没有上限。结果就是MAPE 会不动声色地奖励那些偏向低报的模型。R^2即决定系数。公式是1 - SSE / SST。SSE sum((y_pred - y_true)^2)是残差平方和。SST sum((y_true - mean(y_true))^2)是目标值围绕自身均值的总方差。R^2 说明模型解释了目标值方差的几成参照的基线是始终预测均值。取值 1.0 代表完美拟合。取值 0.0 意味着模型不比预测mean(y_true)更好。R^2 没有下界。只要SSE大于SST也就是模型比恒定均值这条基线还差R^2 就会变成负数。负的 R^2 是一个真实且有意义的信号。它通常有以下 3 个原因之一模型设定有误、模型是在一个和训练分布不同的数据上被评估、或者参数被写反了。由于SST只来自y_trueR^2 对参数并不对称见 5.1.5 节。当y_true恒定时这个比值没有定义。RustyML 沿用 scikit-learn 的做法精确拟合返回1.0否则返回0.0所以恒定的目标值不会产生NaN。RustyML 通过把各个值互相比较来判断恒定而不是拿SST去和某个阈值比。早期版本对未归一化的平方和用过一个绝对的1e-10阈值。那个阈值会给一个真正在变化、但整体跨度很小的目标比如[1e-6, 2e-6, 3e-6]其 SST 为2e-12报出虚假的1.0。而对SST 0.0做精确判断又会朝另一个方向出错因为计算均值并不能把每个常数都精确地还原回来。EVS即可解释方差分数把 R^2 里的残差平方和换成残差的方差。公式是1 - Var(y_true - y_pred) / Var(y_true)。在平方之前先减去残差均值意味着恒定的预测偏差不会拉低分数。假设一个模型总是恰好偏差 1。它的残差方差为零所以 EVS 等于 1.0即便预测系统性地偏了。R^2 则会正确地惩罚同样这个偏差。EVS - R^2这个差值衡量的是预测有多偏。设想一次无偏的拟合比如任何带截距、在自己的训练数据上用最小二乘拟合出来的模型。它的残差均值接近 0所以 EVS 接近 R^2。RustyML 不提供 adjusted-R^2 函数。普通 R^2 在你加入特征时永远不会下降所以它没法比较特征数量不同的模型。如果你需要这个调整项就自己算。公式是adj 1 - (1-r2)*(n-1)/(n-p-1)其中n是样本数p是预测变量数。5.1.3. 为模型选择挑一个指标至少同时报一个感知量纲的误差指标外加一个方差解释类的分数。单个数字盖住的东西太多。大误差代价格外高、又想要目标值单位下的答案时用 RMSE。误差与代价成线性关系、又不信任那几个极端点时用 MAE。数据已知有坏记录或带重尾、想要一个尾巴撼动不了的数字时用 MedAE。只有目标值严格为正且跨尺度可比时才用 MAPE只要目标值可能为零就不要用它。用 R^2 来无量纲地说明模型有多好。想专门把恒定偏差剔除时改用 EVS。做超参数搜索和跨模型比较时一开始就选定一个指标并固定下来。拿模型 A 的 RMSE 去比模型 B 的 MAE 没有意义。这些指标始终要在留出的划分上计算见 训练集与测试集划分。过拟合训练集会轻易把这一页里的每个指标都压到最低。5.1.4. 一个完整示例这个示例在 5 个带噪点上拟合一个 线性回归 模型在同一批输入上预测然后计算这页里的每个指标。这里LinearRegression用的是它默认的闭式求解器。这个求解器精确又瞬时没有学习率或迭代次数要调所以示例保持确定且快速。不管预测是怎么产生的指标的调用方式都不变。usendarray::array;userustyml::machine_learning::LinearRegression;userustyml::metrics::{explained_variance_score,mean_absolute_error,mean_absolute_percentage_error,mean_squared_error,median_absolute_error,r2_score,root_mean_squared_error,};fnmain(){// 5 个样本1 个特征。大致是 y 2x带一点测量噪声。letxarray![[1.0],[2.0],[3.0],[4.0],[5.0]];lety_truearray![2.1,3.9,6.2,7.8,10.1];// 闭式普通最小二乘精确、无超参数、瞬时完成。letmutmodelLinearRegression::new(true);model.fit(x,y_true).unwrap();lety_predmodel.predict(x).unwrap();// 真值在前预测在后。顺序对 R^2 和 EVS 有影响。println!(MSE {:.5},mean_squared_error(y_true,y_pred));println!(RMSE {:.5},root_mean_squared_error(y_true,y_pred));println!(MAE {:.5},mean_absolute_error(y_true,y_pred));println!(MedAE {:.5},median_absolute_error(y_true,y_pred));println!(MAPE {:.5},mean_absolute_percentage_error(y_true,y_pred));println!(R2 {:.5},r2_score(y_true,y_pred));println!(EVS {:.5},explained_variance_score(y_true,y_pred));// 拟合好的 LinearRegression 也能不调用 predict() 就直接给你 R^2println!(score {:.5},model.score(x,y_true).unwrap());}下面是程序打印出的数值已经四舍五入。闭式求解器让输出是确定性的所以同样的输入总是打印同样的数字。MSE ~ 0.021 (y 单位的平方) RMSE ~ 0.146 (y 单位) MAE ~ 0.136 (y 单位) MedAE ~ 0.130 (y 单位) MAPE ~ 0.026 (分数 - ~2.6%) R2 ~ 0.997 EVS ~ 0.997 score ~ 0.997把这些数放在一起读讲述的是一个连贯的故事。R^2 大约是 0.997说明拟合出的这条直线解释了y里几乎全部的方差。RMSE 大约是 0.15MAE 大约是 0.14两者都以y的单位表示而y的取值范围是 2 到 10。这 2 个值证实了典型的偏差大约是七分之一个单位相对于y的取值范围来说很小。RMSE 只比 MAE 高一点说明没有哪个残差在主导误差。MedAE 接近 MAE这是另一个信号说明误差大小均匀而不是尾部沉重。MAPE 大约是 2.6%用与量纲无关的方式表达了同样的精度。EVS 在这个精度下与 R^2 相等。带截距的最小二乘拟合产生的残差均值接近 0所以没有偏差留给 EVS 去宽恕。model.score(x, y_true)复现了r2_score(y_true, y_pred)因为LinearRegression内部按同样的定义计算 R^2。在拟合好的模型上为图方便就用score。给其他任何东西的预测打分时比如神经网络、KNN 回归器或者某个外部模型就用自由函数r2_score。5.1.5. 参数顺序的陷阱r2_score和explained_variance_score用第一个参数的方差做归一化。不小心调用r2_score(y_pred, y_true)不会报错。它会算出一个格式良好、但错误的数。下面的示例把同样的 2 个数组按两种顺序各打一次分。它还展示了一个真正糟糕的模型如何让 R^2 变负。usendarray::array;userustyml::metrics::r2_score;fnmain(){letaarray![1.0,2.0,4.0];letbarray![2.0,3.0,4.0];// 交换参数会改变 R^2因为 SST 只来自第一个数组。println!(r2_score(a, b) {:.4},r2_score(a,b));// ~ 0.5714 ( 4/7)println!(r2_score(b, a) {:.4},r2_score(b,a));// 0.0000// 预测方向与真值相反的模型比预测均值还差// 所以 R^2 会变负。这是真实信号不是错误状态。lety_truearray![1.0,2.0,3.0];lety_predarray![3.0,2.0,1.0];println!(negative R² {:.4},r2_score(y_true,y_pred));// -3.0000}对称指标MSE、RMSE、MAE、MedAE以及 MAPE 的分子不受这个问题影响因为它们只看得到y_true - y_pred。MAPE 是误差类指标里的例外。它的分母用到第一个参数所以mean_absolute_percentage_error同样对顺序敏感。有一个习惯能防住这一切。永远把真值放在前面并把变量命名为y_true和y_pred这样一旦写反在调用处就能一眼看出来。5.1.6. 输入校验、panic 与 NaN 行为第 2 章的模型 API 返回Result_, Error见 错误处理。metrics模块的做法不一样。它会在违反前置条件时panic而不是返回错误这与ndarray自身在维度不匹配时的做法一致。7 个函数都先跑同一套检查。长度必须相等并且输入不能为空。长度检查先于空值检查所以即便一侧为空报出来的也是长度不匹配。panic 的消息对齐 crate 的Error措辞dimension mismatch: expected 3, found 2 input is empty: y_true and y_pred你没法用?来传播一个 panic。在调用这些函数之前先在自己的代码里校验长度。如果需要从 panic 里恢复就用std::panic::catch_unwind把调用包起来。实践中你能掌控传进去的数组长度比如一个predict的输出和它对应的目标。只要长度本就匹配panic 就永远不会触发。这些函数处理非有限输入的方式是有意不一致的。当你的数据里可能混进NaN或inf时这一点很要紧。r2_score用的是普通求和。只要任一数组里有一个非有限值它就会沿着求和传播结果就是NaN。这会把脏数据大声暴露出来而不是藏起来通常这正是你想要的。explained_variance_score走的是相反的路子。它的方差辅助函数会悄悄跳过非有限样本只在有限的那部分上求平均。几个坏值过后剩下的样本仍会算出一个看起来正常的分数。这样做很方便但可能掩盖数据问题。如果丢样本会有影响就先把输入清洗干净。误差指标 MSE、RMSE 和 MAE 同样会让NaN穿过求和传播。MedAE 用total_cmp排序它会把NaN确定性地排好序而不是 panic。还有两个边界情形收尾。当y_true恒定时R^2 和 EVS 本会除以零。R^2 只在精确拟合时返回1.0否则返回0.0。EVS 只要残差方差为零就返回1.0哪怕存在恒定的偏移否则返回0.0。这两条路径都不会产生NaN。RustyML 直接从各个值本身读出恒定而不是靠方差上的某个容差。这个做法让一个跨度确实很小、但真正在变化的目标不会被误判成常数、拿到一个虚假的1.0。至于 MAPEy_true中为零的项不会导致除零。它会把分母下限截断到f64::EPSILON于是那个样本的项会炸开并把均值一起拖上去。碰到大得离谱的 MAPE就把它当成y_true里有个零的信号而不是对模型下的判决。另外 2 个任务大类的配套指标见 分类指标 和 聚类指标。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门