PySpark:CrossValidator.avgMetrics与从collectSubModels计算的平均值不一致



我已经设置了一个CrossValidator对象,该对象与线性回归管道和可供选择的超参数网格相结合。更具体地说,我对两个超参数组合产生的9个不同设置进行了5倍的交叉验证(每个设置有3个值(,并通过将collectSubModels标志设置为True:来跟踪所有产生的45个模型

...
lr = LinearRegression(featuresCol="features", labelCol="label")
pipeline = Pipeline(stages=indexers + [encoder] + [assembler] + [lr])
param_grid = ParamGridBuilder()
.addGrid(lr.regParam, [0.0, 0.05, 0.1]) 
.addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0])
.build()
cross_val = CrossValidator(estimator=pipeline, 
estimatorParamMaps=param_grid,
evaluator=RegressionEvaluator(metricName="rmse"),
numFolds=5,
collectSubModels=True
)
# Run cross-validation, and choose the best set of parameters
cv_model = cross_val.fit(train)
return cv_model

一切似乎都很顺利,除了当我打印出每个模型(即每个折叠的9个模型(的性能(即RMSE(,并尝试"手动"计算每个折叠的平均值时,得到的9个平均值与我使用CrossValidator的内部avgMetrics属性时得到的值根本不匹配。举个例子,以下是我使用两个超参数的第一个组合(即均设置为0(获得的5个RMSE值:

*************** Fold #1 ***************
--- Model #1 out of 9 ---
Parameters: lambda=[0.000]; alpha=[0.000] 
RMSE: 149354.656
*************** Fold #2 ***************
--- Model #1 out of 9 ---
Parameters: lambda=[0.000]; alpha=[0.000] 
RMSE: 146038.521
*************** Fold #3 ***************
--- Model #1 out of 9 ---
Parameters: lambda=[0.000]; alpha=[0.000] 
RMSE: 148739.919
*************** Fold #4 ***************
--- Model #1 out of 9 ---
Parameters: lambda=[0.000]; alpha=[0.000] 
RMSE: 146816.473
*************** Fold #5 ***************
--- Model #1 out of 9 ---
Parameters: lambda=[0.000]; alpha=[0.000] 
RMSE: 149868.621

正如您所看到的,RMSE的所有值都低于150000。我的期望是,如果我取上面这些值的平均值,我就会得到avgMetrics列表的第一个元素(实际上,它应该包含在折叠中计算的每个超参数组合的交叉验证平均值(。相反,如果我运行cv_model.avgMetrics,这就是我得到的:

[150091.7372030353, 150091.7372030353, 150091.7372030353, 150091.7345116686, 150093.66131828527, 150090.52769066638, 150091.7338301999, 150090.52716106002, 150091.59829053417]

有9个元素如预期,但没有一个看起来正确!事实上,尽管我的45款车型(不仅仅是上面列出的5款(都没有达到15万以上的数字,但它们都超过了15万。

看起来avgMetrics的填充方式是错误的。我知道2016年有一个问题,这个值错误地包含了交叉验证指标的总和,而不是平均值,但显然这个问题已经解决了。

我还试图检查CrossValidator对象的_fit方法的当前实现,尽管我没有在这方面花太多时间,但显然一切都很好:

for i in range(nFolds):
validateLB = i * h
validateUB = (i + 1) * h
condition = (df[randCol] >= validateLB) & (df[randCol] < validateUB)
validation = df.filter(condition).cache()
train = df.filter(~condition).cache()
tasks = _parallelFitTasks(est, train, eva, validation, epm, collectSubModelsParam)
for j, metric, subModel in pool.imap_unordered(lambda f: f(), tasks):
metrics[j] += (metric / nFolds)
if collectSubModelsParam:
subModels[i][j] = subModel

其他人也经历过同样的问题吗?

编辑:我盲目地认为问题(如果有的话(在avgMetrics属性上;然而,可能这些平均值实际上是正确的,而我在上面通过调用每个子模型上的.summary.rootMeanSquaredError打印出的单个度量是错误计算的。无论哪种方式,两者之间都存在明显的矛盾。

我已经将这个问题直接发布到Apache Spark github上,并被告知我做错了什么。

我会把答案贴在这里,以防有人有同样的问题。

基本上,我认为我是在打印每个(k(交叉验证运行的保留(即验证(部分所测量的单个RMSE。事实上,我打印出了在训练集部分(每个折叠(上计算的RMSE。

然而,显然,没有简单的方法来回忆我试图得到的东西,因为这些信息似乎无论如何都不会被存储。好消息是,交叉验证平均值是有意义的。

希望这能有所帮助。

最新更新