ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

H2O CoxPH 的 start_column 参数详解:在 H2O-3 生存分析中定义左截断起始时间

H2O CoxPH 的 start_column 参数详解:在 H2O-3 生存分析中定义左截断起始时间
  • 机器学习
  • 深度学习
  • AutoML
  • 大数据
  • 后端

【免费下载链接】h2o-3

H2O is an Open Source, Distributed, Fast & Scalable Machine Learning Platform: Deep Learning, Gradient Boosting (GBM) & XGBoost, Random Forest, Generalized Linear Modeling (GLM with Elastic Net), K-Means, PCA, Generalized Additive Models (GAM), RuleFit, Support Vector Machine (SVM), Stacked Ensembles, Automatic Machine Learning (AutoML), etc.

项目地址:https://gitcode.com/gh_mirrors/h2/h2o-3
点击查看免费下载

本文以 H2O-3 开源仓库中h2o-docs/src/product/data-science/algo-params/start_column.rst文档为骨架,系统讲解 Cox 比例风险模型(CoxPH)的start_column参数:它的语义、默认行为、与stop_column的配套约束、R/Python 两种客户端的完整调用方式,以及底层源码中的校验与时间离散化实现。读完本文,你将能够在 H2O-3 中正确配置左截断(left-truncated)生存数据,并理解Surv(start, stop, event)公式是如何从参数推导出来的。

参数概览:一个位于“源数据”中的整数时间列

start_column是 H2O CoxPH 模型专属的参数,用于指定源数据集中表示起始时间(start time)的整数列名。它有以下基本属性:

  • 可用算法:仅 CoxPH(Cox Proportional Hazards)
  • 是否超参数:否(不参与网格搜索调参)
  • 数据类型:字符串(列名字符串),在 Python 客户端中接受None或str
  • 默认值:None(未指定时模型退化为仅使用stop_column的经典 CoxPH 形式)

在 h2o-algos/src/main/java/hex/coxph/CoxPHModel.java 中,参数被定义为模型参数类的两个成员字段:

public String _start_column; public String _stop_column;

start_column与stop_column在模型中作为一对“时间边界”配合使用。若提供了start_column,则要求每一行中start_column的值必须严格小于stop_column的值(即start < stop)。这一约束在文档、初始化校验和底层离散化任务中都有明确体现,下文将逐一展开。

适用场景:为什么生存分析需要起始时间

经典的 Cox 模型只需要一个“停止时间”列(随访结束时间)。但在实际队列研究中,个体往往并非从时间零点开始进入观察——例如受试者可能在研究进行到一半时才被招募,或者个体在暴露风险之前的某段时间不应计入风险集(risk set)。这类数据在流行病学中被称为**左截断(left truncation)**或计数过程(counting process)形式的数据。

此时数据中每个观测具有形如(start, stop, event)的三元组结构:

  • start:个体进入观察的时间点;
  • stop:个体退出观察的时间点(事件发生或删失);
  • event:事件发生标志(0/1)。

start_column正是用来告诉 H2O 哪个列扮演start的角色。当提供了起始时间后,模型公式会自动推导为Surv(start, stop, event) ~ ...,底层实现会将该个体在start之前的时间段从风险集中排除,从而得到正确的偏似然估计。

校验规则:从源码看 start_column 的合法性检查

start_column的校验发生在 h2o-algos/src/main/java/hex/coxph/CoxPH.java 的init(boolean expensive)方法中。源码明确划分了三级检查:

第一级:列必须存在于训练集中

if (_parms._start_column != null) { Vec startVec = _parms.startVec(); if (startVec == null) { error("start_column", "start_column " + _parms._start_column + " not found in the training frame"); }

若指定的列名在训练 Frame 中不存在,模型会直接报错start_column ... not found in the training frame。

第二级:列类型必须是数值型

} else if (!startVec.isNumeric()) { error("start_column", "start time must be undefined or of type numeric"); }

start_column必须是整数/数值列。类别(factor)列不被接受,错误信息为start time must be undefined or of type numeric。注意这里“undefined”特指start_column = NULL(不提供)是允许的。

第三级:整体上 start 必须小于 stop

if (_parms.startVec() != null && _parms.stopVec() != null) { if (_parms.startVec().min() >= _parms.stopVec().max()) error("start_column", "start times must be strictly less than stop times"); }

当两个时间列都存在时,H2O 会检查start列的全局最小值是否大于等于stop列的全局最大值——如果min(start) >= max(stop),则说明数据整体违背了“start 必须严格小于 stop”的原则,直接拒绝训练。

除了这三项初始化检查,每一行的约束在数据预处理阶段还会被再次强制校验:DiscretizeTimeTask(位于同一文件的 CoxPH.java)在逐行离散化时间时执行

final double startTime = cs[0].atd(i); if (startTime >= stopTime) throw new IllegalArgumentException("start times must be strictly less than stop times");

也就是说,即使初始化时的全局检查通过,任何一行的start >= stop都会在运行期抛出一个IllegalArgumentException,异常信息同样是start times must be strictly less than stop times。

从模型构建的角度,start_column和stop_column还参与“非特征列”的计数:在 CoxPH.java 中,

int nonFeatureColCount = (_parms._start_column!=null?1:0) + (_parms._stop_column!=null?1:0); if (_train.numCols() < (2 + nonFeatureColCount)) error("_train", "Training data must have at least 2 features (incl. response).");

时间列不会被当作预测特征参与回归,这与event响应列一样,属于从特征集合中剔除的特殊列。

公式推导:Surv(start, stop, event)是如何生成的

在 CoxPHModel.java 的toFormula(Frame f)方法中,模型会根据是否提供start_column生成 R 风格的生存公式:

sb.append("Surv("); if (_start_column != null) { sb.append(_start_column).append(", "); } sb.append(_stop_column).append(", ").append(_response_column); sb.append(") ~ ");
  • 指定了start_column = "start"时:生成Surv(start, stop, event) ~ age;
  • 未指定start_column(NULL)时:公式退化为Surv(stop, event) ~ age。

这正是 R 中survival包Surv()函数的左截断语法Surv(start, stop, event),H2O 在客户端层面对标了该语义。测试用例 h2o-algos/src/test/java/hex/coxph/CoxPHTest.java 和 CoxPHTest.java 分别断言了两种形态:

assertEquals("Surv(start, stop, event) ~ age", parms.toFormula(fr)); // 有 start_column assertEquals("Surv(stop, event) ~ age", parms.toFormula(fr)); // 无 start_column

另外,responseCols()(CoxPHModel.java)会把start_column与stop_column、response_column一起视为响应相关列,而startVec()(CoxPHModel.java)则直接通过train().vec(_start_column)取出对应向量供上层使用。

底层实现:起始时间如何进入偏似然计算

start_column并非简单地“多一列特征”,它在 CoxPH 的核心计算中参与两个关键环节。

时间离散化(DiscretizeTimeTask)。H2O 的 CoxPH 将连续时间映射到排序后的离散时间点上。在 CoxPH.java 中,对每一行:

final double stopTime = cs[cs.length - 1].atd(i); final int t2 = Arrays.binarySearch(_time, stopTime); ... final double startTime = cs[0].atd(i); if (startTime >= stopTime) throw new IllegalArgumentException("start times must be strictly less than stop times"); final int t1c = Arrays.binarySearch(_time, startTime); final int t1 = t1c >= 0 ? t1c + 1 : -t1c - 1;

停止时间t2直接映射到它所在的时间点;起始时间t1则被映射为严格大于 start 的下一个时间点(t1c + 1)。从源码结构可以推断,这一设计正是左截断语义的体现:个体从start之后才进入风险集,start之前的风险时间段被排除在外。无start_column时,DiscretizeTimeTask只处理停止时间列(assert cs.length == (_has_start_column ? 2 : 1))。

风险集计数。在 CoxPH.java 的calcCounts中:

if (p._start_column == null) for (int t = o._n_risk.length - 2; t >= 0; --t) o._n_risk[t] += o._n_risk[t + 1];

当没有起始时间列时,风险集从后往前累加(所有生存到 t 时刻的个体始终在风险集中);而当提供了start_column后,这一累加被跳过——因为每个个体只在(start, stop]区间内属于风险集,必须按各自的时间区间独立计算。这正是“带左截断的 Cox 回归”与普通 Cox 回归在实现上的分水岭。

完整实战示例:heart 数据集的左截断 Cox 回归

原文档(start_column.rst)以公开的 heart 数据集为例给出了 R 与 Python 两种客户端的完整示例。该数据集包含start、stop、event等列,是演示计数过程(counting process)Cox 回归的经典用例;仓库测试 CoxPHTest.java 也使用了同一数据集。

R 客户端示例

library(h2o) h2o.init() # import the heart dataset heart <- h2o.importFile("http://s3.amazonaws.com/h2o-public-test-data/smalldata/coxph_test/heart.csv") # set the predictor name and response column x <- "age" y <- "event" # set the start and stop columns start <- "start" stop <- "stop" # train your model heart_coxph <- h2o.coxph(x = x, event_column = y, start_column = start, stop_column = stop, training_frame = heart) # view the model details heart_coxph Model Details: ============== H2OCoxPHModel: coxph Model ID: CoxPH_model_R_1527700369755_2 Call: "Surv(start, stop, event) ~ age" coef exp(coef) se(coef) z p age 0.0307 1.0312 0.0143 2.15 0.031 Likelihood ratio test = 5.17 on 1 df, p = 0.023 n = 172, number of events = 75

在 R 客户端 h2o-r/h2o-package/R/coxph.R 的函数签名中,start_column = NULL是默认值,且当x缺省时,start_column与stop_column、event_column会被自动从预测变量集合中排除(见 coxph.R 的参数注释)。参数最终通过parms$start_column <- start_column写入请求参数(coxph.R)。

Python 客户端示例

import h2o from h2o.estimators.coxph import H2OCoxProportionalHazardsEstimator h2o.init() # import the heart dataset heart = h2o.import_file("http://s3.amazonaws.com/h2o-public-test-data/smalldata/coxph_test/heart.csv") # set the parameters heart_coxph = H2OCoxProportionalHazardsEstimator(start_column="start", stop_column="stop", ties="breslow") # train your model heart_coxph.train(x="age", y="event", training_frame=heart) # view the model details heart_coxph

在 Python 客户端 h2o-py/h2o/estimators/coxph.py 中,start_column是一个带类型校验的属性:

@property def start_column(self): """Start Time Column. Type: ``str``. """ return self._parms.get("start_column") @start_column.setter def start_column(self, start_column): assert_is_type(start_column, None, str) self._parms["start_column"] = start_column

assert_is_type(start_column, None, str)保证该参数只能是None或字符串列名,任何其他类型都会在客户端被立即拦截。注意 Python 示例中还使用了ties="breslow",这是并列事件的处理方法(可选"efron"/"breslow"),与start_column相互独立。

测试佐证:有/无起始时间的模型行为对比

仓库单元测试 h2o-algos/src/test/java/hex/coxph/CoxPHTest.java 对start_column的正反两种用法都有覆盖:

  • testCoxPHEfron1Var(CoxPHTest.java):设置parms._start_column = "start",同时通过_ignored_columns忽略id、year、surgery、transplant,得到Surv(start, stop, event) ~ age,并校验了系数0.0307077486571334、样本量n = 172、事件数total_event = 75、一致性指数concordance ≈ 0.5806等输出。
  • testCoxPHEfron1VarNoStart(CoxPHTest.java):parms._start_column = null,并把start列也加入_ignored_columns,公式变为Surv(stop, event) ~ age,对应系数变为0.0289468187293998、concordance ≈0.5671。

对比可见,同一份 heart 数据在“是否提供起始时间”两种设定下得到的系数与一致性指数明显不同,说明start_column确实实质性地改变了偏似然构造,而非仅改变公式字符串。此外,GridTest与GenericModelTest中也引用了start_column,用于验证其与网格搜索、通用模型的兼容性。

使用注意事项

综合文档与源码,使用start_column时请留意以下几点:

  1. 列名必须真实存在且为数值型:指定不存在的列会报start_column ... not found in the training frame;指定字符串/类别列会报start time must be undefined or of type numeric。
  2. 必须逐行满足start < stop:不仅初始化时会做全局范围检查(min(start) >= max(stop)直接报错),每行数据在时间离散化阶段也会强制校验,任何一行违规都会抛出IllegalArgumentException。预处理数据时应先清洗掉start >= stop的观测。
  3. start_column不是超参数:它描述数据的结构信息而非模型配置,不参与超参数搜索(文档明确标注Hyperparameter: no)。
  4. 与stop_column配套理解:stop_column是必填项;只有同时提供了start_column,模型才会进入左截断/计数过程的计算路径(stop_column 文档 中对两者约束的描述互为镜像)。
  5. 时间列不参与建模:start_column、stop_column与event_column一样被排除在特征集合之外,仅用于构造生存响应,不要把时间列误放进x预测变量中。

掌握了以上语义、校验与实现细节,你就可以在 H2O-3 中正确处理左截断生存数据,并理解start_column从客户端参数到Surv(start, stop, event)偏似然计算的全链路行为。

  • 机器学习
  • 深度学习
  • AutoML
  • 大数据
  • 后端

【免费下载链接】h2o-3

H2O is an Open Source, Distributed, Fast & Scalable Machine Learning Platform: Deep Learning, Gradient Boosting (GBM) & XGBoost, Random Forest, Generalized Linear Modeling (GLM with Elastic Net), K-Means, PCA, Generalized Additive Models (GAM), RuleFit, Support Vector Machine (SVM), Stacked Ensembles, Automatic Machine Learning (AutoML), etc.

项目地址:https://gitcode.com/gh_mirrors/h2/h2o-3
点击查看免费下载

相关推荐

上一篇:aioredis-py 2.0迁移指南:从旧版本升级到全新异步Redis客户端
下一篇:终极游戏按键可视化工具:让你的操作在直播中脱颖而出

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进