PySpark中的自定义评估器

时间:2018-07-18 14:15:53

标签: pyspark cross-validation metrics

我想使用排名指标(MAP @ k)优化PySpark管道的超级参数。我已经在文档中看到了如何使用Evaluation(Scala)中定义的指标,但是由于尚未实现MAP @ k,因此我需要定义一个自定义的评估程序类。所以我需要做类似的事情:

model = Pipeline(stages=[indexer, assembler, scaler, lg])
paramGrid_lg = ParamGridBuilder() \
    .addGrid(lg.regParam, [0.001, 0.1]) \
    .addGrid(lg.elasticNetParam, [0, 1]) \
    .build()

crossval_lg = CrossValidator(estimator=model,
                      estimatorParamMaps=paramGrid_lg,
                      evaluator=MAPkEvaluator(), 
                      numFolds=2)

其中MAPkEvaluator()是我的自定义评估程序。我看过similar question,但没有看到答案。

是否有任何可用的示例或文档?有谁知道是否可以在PySpark中实现它?我应该实现什么方法?

3 个答案:

答案 0 :(得分:3)

@jarandaf在第一条评论中回答了该问题,但为清楚起见,我编写了如何使用随机指标实现基本示例:

import random
from pyspark.ml.evaluation import Evaluator

class RandomEvaluator(Evaluator):

    def __init__(self, predictionCol="prediction", labelCol="label"):
        self.predictionCol = predictionCol
        self.labelCol = labelCol

    def _evaluate(self, dataset):
        """
        Returns a random number. 
        Implement here the true metric
        """
        return random.randint(0,1)

    def isLargerBetter(self):
        return True

现在以下代码应该可以工作了:

from pyspark.ml.tuning import CrossValidator, ParamGridBuilder

paramGrid_lg = ParamGridBuilder() \
    .addGrid(lg.regParam, [0.01, 0.1]) \
    .addGrid(lg.elasticNetParam, [0, 1]) \
    .build()

crossval_lg = CrossValidator(estimator=model,
                      estimatorParamMaps=paramGrid_lg,
                      evaluator= RandomEvaluator(), 
                      numFolds=2)

cvModel = crossval_lg.fit(train_val_data_)

答案 1 :(得分:1)

@Amanda很好地回答了这个问题,但让我也向您展示一些避免的事情。如果您检查Evaluator()类的帮助:

help(Evaluator())

您将看到在其中定义的方法:

isLargerBetter(self)
 |      Indicates whether the metric returned by :py:meth:`evaluate` should be maximized
 |      (True, default) or minimized (False).
 |      A given evaluator may support multiple metrics which may be maximized or minimized.
 |      
 |      .. versionadded:: 1.5.0

现在,如果您需要最小化指标,则需要将此方法设置为:

def isLargerBetter(self):
        return False

当前方法的默认值为True

答案 2 :(得分:0)

为@Amanda 的明确答案添加一个实际示例,以下代码可用于创建自定义 Evaulator,它在二进制分类任务中计算 F1-score。它可能没有优化(我实际上不知道是否有更有效的方法来实现该指标),但它完成了工作。

import pyspark.sql.functions as F
from pyspark.ml.evaluation import Evaluator

class MyEvaluator(Evaluator):

    def __init__(self, predictionCol='prediction', labelCol='label'):
        self.predictionCol = predictionCol
        self.labelCol = labelCol

    def _evaluate(self, dataset):
        tp = dataset.filter((F.col(self.labelCol) == 1) & (F.col(self.predictionCol) == 1)).count()
        fp = dataset.filter((F.col(self.labelCol) == 0) & (F.col(self.predictionCol) == 1)).count()
        fn = dataset.filter((F.col(self.labelCol) == 1) & (F.col(self.predictionCol) == 0)).count()
        f1 = (2 * tp) / (2 * tp + fp + fn)
        return f1

    def isLargerBetter(self):
        return True