You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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")失效?

可能的原因包括:

  1. 类别名称匹配问题:虽然你在数据加载时移除了字符串引号,但转为H2OFrame后,因子类别可能仍存在格式差异(如隐藏空格、大小写不一致);
  2. H2O版本bug:旧版本H2O的relevel()方法存在兼容性问题,无法正确识别字符串类型的参考水平;
  3. 因子水平未刷新:将Pandas字符串列转为H2O因子列时,默认按字典序排列水平,relevel()调用后未正确刷新水平顺序。

验证与修复步骤

  1. 检查因子水平顺序:在relevel()前后打印因子水平列表,确认B1是否被设为第一个水平(H2O默认将第一个水平作为参考类别):
# 查看初始因子水平
print("初始因子水平:", X_relevel["VehBrand"].levels())
# 执行relevel后再次查看
X_relevel["VehBrand"] = X_relevel["VehBrand"].relevel("B1")
print("relevel后因子水平:", X_relevel["VehBrand"].levels())
  1. 通过索引设置参考水平:如果按名称设置失效,可通过索引指定(先找到B1在因子水平中的位置):
# 获取因子水平列表
levels = X_relevel["VehBrand"].levels()[0]
# 找到B1的索引
b1_index = levels.index("B1")
# 按索引设置参考水平
X_relevel["VehBrand"] = X_relevel["VehBrand"].relevel(b1_index)
  1. 确认relevel_by_frequency()的效果:同样打印X_freq["VehBrand"].levels(),确认最频繁类别是否被设为第一个水平。

额外注意事项

relevel_by_frequency()是基于样本行计数确定最频繁类别,而你的模型使用了Exposure作为权重。如果某个类别的行频率最高,但加权后的暴露量不是最大的,模型计算的参考类别均值会和行频率均值不同,但这不会导致系数的数量级差异——你当前的问题本质还是参考类别不匹配导致的。

内容的提问来源于stack exchange,提问作者Stuart King

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 07:25:19