H2O Python中因子列relevel与relevel_by_frequency的GLM系数差异问题
H2O中因子参考水平设置后GLM系数差异问题
根据H2O官方文档,relevel('most_frequency_category')与relevel_by_frequency()设置因子列参考水平的效果应当一致,但实际使用这两种方法训练GLM模型后,系数估计值却出现了明显差异。以下是用sklearn公开数据集复现该现象的代码及输出,明明两个模型的基准水平应该相同,为什么系数会不一样?
复现代码
import pandas as pd from sklearn.datasets import fetch_openml import h2o from h2o.estimators.glm import H2OGeneralizedLinearEstimator h2o.init(max_mem_size=8) def load_mtpl2(n_samples=100000): """ 加载法国机动车第三方责任险索赔数据集 完整数据集包含678013条样本,可通过n_samples指定加载数量以加快运行速度 """ # 加载频率数据集(openml ID:41214) df_freq = fetch_openml(data_id=41214, as_frame=True)["data"] df_freq["IDpol"] = df_freq["IDpol"].astype(int) df_freq.set_index("IDpol", inplace=True) # 加载赔付金额数据集(openml ID:41215) df_sev = fetch_openml(data_id=41215, as_frame=True)["data"] # 按保单ID汇总赔付金额 df_sev = df_sev.groupby("IDpol").sum() # 合并数据集,填充缺失赔付金额 df = df_freq.join(df_sev, how="left") df["ClaimAmount"].fillna(0, inplace=True) # 去除字符串字段的引号 for column_name in df.columns[df.dtypes.values == object]: df[column_name] = df[column_name].str.strip("'") return df.iloc[:n_samples] df = load_mtpl2() # 修正赔付次数与金额不匹配的记录 df.loc[(df["ClaimAmount"] == 0) & (df["ClaimNb"] >= 1), "ClaimNb"] = 0 # 截断暴露时长和赔付金额的极值 df["Exposure"] = df["Exposure"].clip(upper=1) df["ClaimAmount"] = df["ClaimAmount"].clip(upper=100000) # 计算纯保费(赔付金额/暴露时长) df["PurePremium"] = df["ClaimAmount"] / df["Exposure"] # 数据集1:用relevel_by_frequency()设置参考水平 X_freq = h2o.H2OFrame(df) X_freq["VehBrand"] = X_freq["VehBrand"].asfactor() X_freq["VehBrand"] = X_freq["VehBrand"].relevel_by_frequency() # 数据集2:手动用relevel()设置最频繁类别B1为参考水平 X_relevel = h2o.H2OFrame(df) X_relevel["VehBrand"] = X_relevel["VehBrand"].asfactor() X_relevel["VehBrand"] = X_relevel["VehBrand"].relevel("B1") # 模型参数设置 response_col = "PurePremium" weight_col = "Exposure" predictors = "VehBrand" glm_freq = H2OGeneralizedLinearEstimator(family="tweedie", solver='IRLSM', tweedie_variance_power=1.5, tweedie_link_power=0, lambda_=0, compute_p_values=True, remove_collinear_columns=True, seed=1) glm_relevel = H2OGeneralizedLinearEstimator(family="tweedie", solver='IRLSM', tweedie_variance_power=1.5, tweedie_link_power=0, lambda_=0, compute_p_values=True, remove_collinear_columns=True, seed=1) # 训练模型 glm_freq.train(x=predictors, y=response_col, training_frame=X_freq, weights_column=weight_col) glm_relevel.train(x=predictors, y=response_col, training_frame=X_relevel, weights_column=weight_col) # 输出系数表 print('使用relevel_by_frequency()设置参考水平的GLM系数') print(glm_freq._model_json['output']['coefficients_table']) print('\n') print('手动用relevel()设置参考水平的GLM系数') print(glm_relevel._model_json['output']['coefficients_table'])
输出结果
使用relevel_by_frequency()设置参考水平的GLM系数 Coefficients: glm coefficients names coefficients std_error z_value p_value standardized_coefficients ------------ -------------- ----------- ---------- ----------- --------------------------- Intercept 5.40413 1.24082 4.35531 1.33012e-05 5.40413 VehBrand.B2 -0.398721 1.2599 -0.316472 0.751645 -0.398721 VehBrand.B12 -0.061573 1.46541 -0.0420176 0.966485 -0.061573 VehBrand.B3 -0.393908 1.30712 -0.301356 0.763144 -0.393908 VehBrand.B5 -0.282484 1.31929 -0.214118 0.830455 -0.282484 VehBrand.B6 -0.387747 1.25943 -0.307876 0.758177 -0.387747 VehBrand.B4 0.391771 1.45615 0.269047 0.787894 0.391771 VehBrand.B10 -0.0542706 1.35049 -0.040186 0.967945 -0.0542706 VehBrand.B13 -0.306381 1.4628 -0.209449 0.834098 -0.306381 VehBrand.B11 -0.435297 1.29155 -0.337035 0.736091 -0.435297 VehBrand.B14 -0.304243 1.34781 -0.225732 0.821411 -0.304243 手动用relevel()设置参考水平的GLM系数 Coefficients: glm coefficients names coefficients std_error z_value p_value standardized_coefficients ------------ -------------- ----------- ---------- ---------- --------------------------- Intercept 5.01639 0.215713 23.2549 2.635e-119 5.01639 VehBrand.B10 0.081366 0.804165 0.101181 0.919407 0.081366 VehBrand.B11 0.779518 0.792003 0.984237 0.325001 0.779518 VehBrand.B12 -0.0475497 0.41834 -0.113663 0.909505 -0.0475497 VehBrand.B13 0.326174 0.80891 0.403227 0.686782 0.326174 VehBrand.B14 0.387747 1.25943 0.307876 0.758177 0.387747 VehBrand.B2 -0.010974 0.306996 -0.0357465 0.971485 -0.010974 VehBrand.B3 -0.00616108 0.464188 -0.0132728 0.98941 -0.00616108 VehBrand.B4 0.333477 0.575082 0.579877 0.561999 0.333477 VehBrand.B5 0.105263 0.497431 0.211613 0.832409 0.105263 VehBrand.B6 0.0835042 0.568769 0.146816 0.883278 0.0835042
问题原因与解决方法
核心原因:参考水平实际不匹配
从输出的系数表可以明显看出,两个模型的实际参考水平并不一致:
- 第一个模型的截距(5.40413)是参考类别B1的加权纯保费均值;
- 第二个模型的截距(5.01639)恰好等于第一个模型中B6类别的纯保费均值(5.40413 + (-0.387747) ≈ 5.01638),说明第二个模型的参考水平实际是B6,而非你指定的B1。
这意味着relevel("B1")没有正确生效,导致两个模型的基准类别不同,最终系数出现显著差异。
为什么relevel("B1")失效?
可能的原因包括:
- 类别名称匹配问题:虽然你在数据加载时移除了字符串引号,但转为H2OFrame后,因子类别可能仍存在格式差异(如隐藏空格、大小写不一致);
- H2O版本bug:旧版本H2O的
relevel()方法存在兼容性问题,无法正确识别字符串类型的参考水平; - 因子水平未刷新:将Pandas字符串列转为H2O因子列时,默认按字典序排列水平,
relevel()调用后未正确刷新水平顺序。
验证与修复步骤
- 检查因子水平顺序:在
relevel()前后打印因子水平列表,确认B1是否被设为第一个水平(H2O默认将第一个水平作为参考类别):
# 查看初始因子水平 print("初始因子水平:", X_relevel["VehBrand"].levels()) # 执行relevel后再次查看 X_relevel["VehBrand"] = X_relevel["VehBrand"].relevel("B1") print("relevel后因子水平:", X_relevel["VehBrand"].levels())
- 通过索引设置参考水平:如果按名称设置失效,可通过索引指定(先找到B1在因子水平中的位置):
# 获取因子水平列表 levels = X_relevel["VehBrand"].levels()[0] # 找到B1的索引 b1_index = levels.index("B1") # 按索引设置参考水平 X_relevel["VehBrand"] = X_relevel["VehBrand"].relevel(b1_index)
- 确认
relevel_by_frequency()的效果:同样打印X_freq["VehBrand"].levels(),确认最频繁类别是否被设为第一个水平。
额外注意事项
relevel_by_frequency()是基于样本行计数确定最频繁类别,而你的模型使用了Exposure作为权重。如果某个类别的行频率最高,但加权后的暴露量不是最大的,模型计算的参考类别均值会和行频率均值不同,但这不会导致系数的数量级差异——你当前的问题本质还是参考类别不匹配导致的。
内容的提问来源于stack exchange,提问作者Stuart King
相关产品推荐
相关产品推荐

