PySpark中CrossValidator.avgMetrics与手动计算均值不一致问题
嘿,我完全懂你这种困惑——明明代码逻辑看起来没问题,手动算的结果和框架返回的指标却对不上,太闹心了!结合你贴的代码和场景,我来帮你拆解下最可能的原因,以及怎么验证:
第一个最容易踩的坑:子模型的RMSE是训练集的,而avgMetrics是验证集的!
你手动调用subModel.summary.rootMeanSquaredError拿到的指标,是模型在它自己的训练集(也就是CrossValidator里拆分出的那4/5数据)上的表现,但CrossValidator的avgMetrics是用剩下1/5的验证集计算出来的RMSE均值!
看你贴的CrossValidator源码片段,里面明确把数据拆成了train和validation:模型在train上训练,然后用validation集评估得到单折的metric,再除以5累加到最终的metrics[j]里——这才是avgMetrics的来源。而子模型的summary默认是基于训练数据的评估结果,两者的评估数据集完全不同,数值自然会有差异!
怎么验证这个猜想?
你可以手动对齐CrossValidator的数据集拆分逻辑,用子模型对对应折的验证集做预测,再用同一个RegressionEvaluator计算RMSE:
# 先给训练集加随机列,和CrossValidator用的拆分逻辑对齐(用固定seed保证可复现) from pyspark.sql.functions import rand train_with_rand = train.withColumn("randCol", rand(seed=42)) nFolds = 5 h = 1.0 / nFolds # 取第一组超参的第一个子模型(对应第0折) sub_model = cv_model.subModels[0][0] # 获取第0折的验证集 validateLB = 0 * h validateUB = (0 + 1) * h validation_set = train_with_rand.filter( (train_with_rand["randCol"] >= validateLB) & (train_with_rand["randCol"] < validateUB) ) # 用子模型预测验证集,再计算RMSE predictions = sub_model.transform(validation_set) evaluator = RegressionEvaluator(metricName="rmse") manual_validation_rmse = evaluator.evaluate(predictions) print("子模型在验证集上的RMSE:", manual_validation_rmse) print("avgMetrics单折对应值(平均前):", cv_model.avgMetrics[0] * nFolds)
如果这两个数值接近,那就能实锤是数据集差异的问题了。
其他可能的小概率原因
1. Spark版本遗留bug
你提到的2016年总和/均值混淆的bug,虽然官方宣称修复,但如果你的Spark版本是2.0.x及更早的老版本,还是有可能存在残留问题。可以先检查下你的Spark版本:
print(spark.version)
如果版本太旧,建议升级到2.4+或者3.x的稳定版试试。
2. 子模型参数和ParamGrid不匹配?
虽然概率很低,但可以验证一下子模型的超参数是否和你定义的ParamGrid一致:
# 取第一组超参的子模型 sub_model_lr = sub_model.stages[-1] print("子模型regParam:", sub_model_lr.getRegParam()) print("子模型elasticNetParam:", sub_model_lr.getElasticNetParam())
确认是不是和你定义的[0.0, 0.0]一致,排除参数映射出错的可能。
内容的提问来源于stack exchange,提问作者Gabriele Tolomei

