为何h2o.r2()计算结果与手动计算的R²不一致?
H2O AutoML模型h2o.r2()与手动计算R²结果不一致的原因及解决方法
在使用H2O AutoML训练回归模型时,发现h2o.r2(model, train=T)返回的R²值与手动计算结果差异显著,但简单线性模型下两者结果一致。以下是复现代码:
library(tidyverse) # fit AML, get leader... fit_aml_H2O = function(X_df, Y_vec) { library(h2o); h2o.init() df = cbind(X_df, Y_vec) colnames(df)[[ncol(df)]]='Y' df = as.h2o(df) aml <- h2o.automl(x=colnames(X_df), y='Y', training_frame=df, nfolds=0, max_models = 15, max_runtime_secs=90) leader = h2o.get_best_model(aml) cat('R^2: ', h2o.r2(leader, train=T)) return(leader) } # manually compute R^2 (verified to work) R2 = function(Y_pred, Y_true) { MSE = mean((as.numeric(Y_true)-as.numeric(Y_pred))**2) R2 = 1-MSE/var(Y_true) return(R2) } data(iris) X_df = iris %>% select(-Petal.Length) Y = iris %>% select(Petal.Length) model = fit_aml_H2O(X_df, Y) X_df = as.h2o(X_df) Y_pred = h2o.predict(model, newdata=X_df)$predict (R2_train = R2(Y_pred[,1], Y[,1])) cat('R^2 (manually computed): ', R2_train, '\n') cat('R^2 (reported by H2O): ', h2o.r2(model,train=T), '\n') cat('difference between manual R^2 & H2O reported R^2: ', abs(R2_train-h2o.r2(model,train=T)), '\n') stopifnot(all.equal(h2o.r2(model,train=T), R2_train))
核心原因
出现差异的关键在于手动计算与H2O内部的R²公式不一致:
- H2O的R²计算公式为:
R² = 1 - (RSS / TSS),其中RSS是残差平方和(sum((Y_true - Y_pred)^2)),TSS是总平方和(sum((Y_true - mean(Y_true))^2)),基于总体方差逻辑计算。 - 你的手动代码中,
var(Y_true)是样本方差(即sum((Y_true - mean(Y_true))^2)/(n-1)),导致最终公式变为1 - (MSE) / (TSS/(n-1)),与H2O的计算逻辑出现偏差。
此外,AutoML中的复杂模型(如GBM、堆叠集成模型)训练时会自动处理特征,若手动预测时使用的数据集与模型训练集存在细微差异(比如R数据框转H2O时的类型处理),也可能放大差异,但核心矛盾是方差计算方式的不同。
解决方案
修正手动计算逻辑,与H2O的公式保持一致,同时确保使用模型训练时的原始训练集数据进行计算,避免数据不一致:
library(tidyverse) library(h2o) fit_aml_H2O = function(X_df, Y_vec) { h2o.init() df = cbind(X_df, Y_vec) colnames(df)[[ncol(df)]]='Y' df_h2o = as.h2o(df) aml <- h2o.automl(x=colnames(X_df), y='Y', training_frame=df_h2o, nfolds=0, max_models = 15, max_runtime_secs=90) leader = h2o.get_best_model(aml) cat('R^2 (H2O reported): ', h2o.r2(leader, train=T), '\n') return(list(model=leader, train_data=df_h2o)) # 返回训练集H2O对象 } # 与H2O逻辑一致的手动R²计算函数 R2_h2o_style = function(Y_pred, Y_true) { Y_pred_vec = as.numeric(Y_pred) Y_true_vec = as.numeric(Y_true) RSS = sum((Y_true_vec - Y_pred_vec)^2) TSS = sum((Y_true_vec - mean(Y_true_vec))^2) R2 = 1 - RSS/TSS return(R2) } data(iris) X_df = iris %>% select(-Petal.Length) Y = iris %>% select(Petal.Length) result = fit_aml_H2O(X_df, Y) model = result$model train_data_h2o = result$train_data # 使用模型训练时的原始H2O数据集预测,避免数据偏差 Y_pred = h2o.predict(model, newdata=train_data_h2o)$predict Y_true = train_data_h2o$Y (R2_train = R2_h2o_style(Y_pred, Y_true)) cat('R^2 (manually computed, H2O style): ', R2_train, '\n') cat('difference between manual R^2 & H2O reported R^2: ', abs(R2_train - h2o.r2(model, train=T)), '\n') stopifnot(all.equal(h2o.r2(model,train=T), R2_train, tolerance=1e-6))
修正说明
- 修改
fit_aml_H2O函数,返回训练集的H2O对象,确保手动计算使用与模型训练完全一致的数据。 - 重写
R2_h2o_style函数,采用H2O的1 - RSS/TSS公式,摒弃基于样本方差的计算逻辑。 - 直接使用训练集H2O对象进行预测,避免原R数据框转H2O时可能出现的类型或数据差异。
内容的提问来源于stack exchange,提问作者profPlum
相关产品推荐
相关产品推荐

