我的用例是一个常见的用例:二分类问题中标签不平衡,因此我们决定通过交叉验证使用f1-score来选择超参数。我们使用的是PySpark 2.3和pyspark.ml,我们创建了一个CrossValidator对象,但对于评估器,问题如下:
- BinaryClassificationEvaluator没有f1分数作为评估指标。
- MulticlassClassificationEvaluator有f1分数,但返回的结果不正确,我猜测它计算了每个类别的f1分数(在这种情况下只有两个类别),然后返回某种平均值,由于负类(y=0)占主导地位,它生成的高f1分数,但模型实际上很差(正类别的f1分数为0)。
- MulticlassClassificationEvaluator在最近的版本中添加了参数evaluator.metricLabel,我认为这允许指定使用哪个标签(在我的情况下,我会将其设置为1),但在Spark 2.3中不可用。
但问题是:我使用的是企业级Spark集群,没有计划升级当前版本(2.3),所以问题是:在受限于Spark 2.3的情况下,如何在二分类问题中使用f1分数作为CrossValidator评估器的评估指标?
回答:
你可以为此创建一个类。我在公司的Spark 2.4中也遇到了同样的问题,所以我尝试为二分类问题创建了一个f1分数评估器。我必须为新类指定.evaluate
和.isLargerBetter
方法。以下是我在这个数据集上尝试时的示例代码:
class F1BinaryEvaluator(): def __init__(self, predCol="prediction", labelCol="label", metricLabel=1.0): self.labelCol = labelCol self.predCol = predCol self.metricLabel = metricLabel def isLargerBetter(self): return True def evaluate(self, dataframe): tp = dataframe.filter(self.labelCol + ' = ' + str(self.metricLabel) + ' and ' + self.predCol + ' = ' + str(self.metricLabel)).count() fp = dataframe.filter(self.labelCol + ' != ' + str(self.metricLabel) + ' and ' + self.predCol + ' = ' + str(self.metricLabel)).count() fn = dataframe.filter(self.labelCol + ' = ' + str(self.metricLabel) + ' and ' + self.predCol + ' != ' + str(self.metricLabel)).count() return tp / (tp + (.5 * (fn +fp)))f1_evaluator = F1BinaryEvaluator()from pyspark.ml.tuning import ParamGridBuilder, CrossValidatorfrom pyspark.ml.classification import GBTClassifiergbt = GBTClassifier()paramGrid = (ParamGridBuilder() .addGrid(gbt.maxDepth, [3, 5, 7]) .addGrid(gbt.maxBins, [10, 30]) .addGrid(gbt.maxIter, [10, 15]) .build())cv = CrossValidator(estimator=gbt, estimatorParamMaps=paramGrid, evaluator=f1_evaluator, numFolds=5)cvModel = cv.fit(train)cv_pred = cvModel.bestModel.transform(test)
交叉验证过程运行没有问题,尽管我不知道性能如何。我还尝试将评估器与sklearn.metrics.f1_score
进行比较,结果值非常接近。
from sklearn.metrics import f1_scoreprint("自定义F1分数评估器 : ", f1_evaluator.evaluate(cv_pred))print("sklearn F1分数评估器 : ", f1_score(cv_pred.select('label').toPandas(), cv_pred.select('prediction').toPandas()))自定义F1分数评估器 : 0.9363636363636364sklearn F1分数评估器 : 0.9363636363636363