(pyspark.ml) Tuning ALS model with CrossValidator ERROR! : Requirement failed: Nothing has been added to this summarizer

Viewed 153

I'm trying to tune the parameters of an ALS matrix factorization model.

So I'm using pyspark.ml.tuning.CrossValidator to run through a parameter grid and select the best model.

But When tuning with CrossValidator, I always get ERROR..

The error is as shown in the photo below, and referring to the answer in the following stackoverflow article, it seems that an error occurs when inferring users who are not in the train data from the valid data in the ALS model.

Also, referring to the following article, it is assumed that CrossValidator causes an error and stops the main thread when a fitting error occurs when it is running on multiple nodes in parallel, but I don't know how to solve it.

When I apply GridSearch with a for statement directly, there is no error, so I don't know why the error below only occurs in the CrossValidator.

Also, the ALS hyperparameter coldStartStrategy = "drop" seems to prevent errors in data that are unseen in the train data. I wonder if this part applies only to items.

from pyspark.ml.recommendation import ALS
als = ALS(
      userCol            = "user"  # user
    , itemCol            = "item"  # item
    , ratingCol          = 'read'  # interaction log (read : 0 or 1)
    , coldStartStrategy  = "drop"  # 
    , implicitPrefs      = True    # implicit feedback
    , nonnegative        = True    # Non-Negative Factorization
)

from pyspark.ml.tuning import TrainValidationSplit, CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.evaluation import RegressionEvaluator

ParamMaps = (ParamGridBuilder()
             .addGrid(als.rank,     [20, 40, 80, 120])
             .addGrid(als.maxIter,  [10, 30, 50])
             .addGrid(als.regParam, [0.001, 0.01, 0.05, 0.1])
            ).build()

evaluator = RegressionEvaluator(  metricName    = "rmse"
                                , labelCol      = "read"
                                , predictionCol = "prediction")

cv = CrossValidator(  estimator          = als
                    , estimatorParamMaps = ParamMaps
                    , evaluator          = evaluator
                    , parallelism        = 5
                    , numFolds           = 5
                    , seed               = 42
                   )

cvmodel = cv.fit(train)

Here is an Error

enter image description here

MLlib will automatically track trials in MLflow. After your tuning fit() call has completed, view the MLflow UI to see logged runs.
/databricks/spark/python/pyspark/ml/util.py:90: UserWarning: CrossValidator_3986dadc628f fit call failed but some spark jobs may still running for unfinished trials. To address this issue, you should enable pyspark pinned thread mode.
  .format(uid))
IllegalArgumentException: requirement failed: Nothing has been added to this summarizer.
---------------------------------------------------------------------------
IllegalArgumentException                  Traceback (most recent call last)
<command-3029966> in <module>
     10 if one of the trial tasks failed, the CrossValidator/TrainValidationSplit fit will raise error and break the main thread, but other backgroud threads running other trial tasks will continue to run, and trial tasks which are pending to run in thread queue will also continue to launch.
     11 '''
---> 12 cvmodel = cv.fit(train)

/databricks/spark/python/pyspark/ml/base.py in fit(self, dataset, params)
    127                 return self.copy(params)._fit(dataset)
    128             else:
--> 129                 return self._fit(dataset)
    130         else:
    131             raise ValueError("Params must be either a param map or a list/tuple of param maps, "

/databricks/spark/python/pyspark/ml/tuning.py in _fit(self, dataset)
    458                         subModels[i][j] = subModel
    459 
--> 460             _cancel_on_failure(dataset._sc, self.uid, sub_task_failed, calculate_metrics)
    461             validation.unpersist()
    462             train.unpersist()

/databricks/spark/python/pyspark/ml/util.py in _cancel_on_failure(sc, uid, sub_task_failed, f)
     89                           "issue, you should enable pyspark pinned thread mode."
     90                           .format(uid))
---> 91             raise e
     92 
     93     old_job_group = sc.getLocalProperty("spark.jobGroup.id")

/databricks/spark/python/pyspark/ml/util.py in _cancel_on_failure(sc, uid, sub_task_failed, f)
     83     if os.environ.get("PYSPARK_PIN_THREAD", "false").lower() != "true":
     84         try:
---> 85             return f()
     86         except Exception as e:
     87             warnings.warn("{} fit call failed but some spark jobs "

/databricks/spark/python/pyspark/ml/tuning.py in calculate_metrics()
    452                     return task()
    453 
--> 454                 for j, metric, subModel in pool.imap_unordered(run_task, tasks):
    455                     metrics[j] += (metric / nFolds)
    456                     metrics_all[i][j] = metric

/usr/lib/python3.7/multiprocessing/pool.py in next(self, timeout)
    746         if success:
    747             return value
--> 748         raise value
    749 
    750     __next__ = next                    # XXX

/usr/lib/python3.7/multiprocessing/pool.py in worker(inqueue, outqueue, initializer, initargs, maxtasks, wrap_exception)
    119         job, i, func, args, kwds = task
    120         try:
--> 121             result = (True, func(*args, **kwds))
    122         except Exception as e:
    123             if wrap_exception and func is not _helper_reraises_exception:

/databricks/spark/python/pyspark/ml/tuning.py in run_task(task)
    450                     if sub_task_failed[0]:
    451                         raise RuntimeError("Terminate this task because one of other task failed.")
--> 452                     return task()
    453 
    454                 for j, metric, subModel in pool.imap_unordered(run_task, tasks):

/databricks/spark/python/pyspark/ml/tuning.py in singleTask()
     57         #  `MetaAlgorithmReadWrite.getAllNestedStages`, make it return
     58         #  all nested stages and evaluators
---> 59         metric = eva.evaluate(model.transform(validation, epm[index]))
     60         return index, metric, model if collectSubModel else None
     61 

/databricks/spark/python/pyspark/ml/evaluation.py in evaluate(self, dataset, params)
     70                 return self.copy(params)._evaluate(dataset)
     71             else:
---> 72                 return self._evaluate(dataset)
     73         else:
     74             raise ValueError("Params must be a param map but got %s." % type(params))

/databricks/spark/python/pyspark/ml/evaluation.py in _evaluate(self, dataset)
    100         """
    101         self._transfer_params_to_java()
--> 102         return self._java_obj.evaluate(dataset._jdf)
    103 
    104     def isLargerBetter(self):

/databricks/spark/python/lib/py4j-0.10.9-src.zip/py4j/java_gateway.py in __call__(self, *args)
   1303         answer = self.gateway_client.send_command(command)
   1304         return_value = get_return_value(
-> 1305             answer, self.gateway_client, self.target_id, self.name)
   1306 
   1307         for temp_arg in temp_args:

/databricks/spark/python/pyspark/sql/utils.py in deco(*a, **kw)
    131                 # Hide where the exception came from that shows a non-Pythonic
    132                 # JVM exception message.
--> 133                 raise_from(converted)
    134             else:
    135                 raise

/databricks/spark/python/pyspark/sql/utils.py in raise_from(e)

IllegalArgumentException: requirement failed: Nothing has been added to this summarizer.
1 Answers

For some reason, the automatic cross-validation in PySpark generates that error...The option I recommend is to do manual cross-validation.

This would be the code (for implicit feedback data):

#Define hyperparameters (These values are optional)
ranks = [10, 20, 30, 40]
maxIters = [10, 20, 30, 40]
regParams = [.05, .1, .15]
alphas = [20, 40, 60, 80]
model_list = [] # List where the models will be saved

# For loop will automatically create and store ALS models
for r in ranks:
    for mi in maxIters:
        for rp in regParams:
            for a in alphas:
                model_list.append(ALS(userCol= "user_id_index", itemCol= "item_id_index", \
                                      ratingCol= "liked", rank = r, maxIter = mi, regParam = rp, alpha = a, \
                                      coldStartStrategy="drop", nonnegative = True, implicitPrefs = True))

# Print the model list, and the length of model_list
print (model_list, "Length of model_list: ", len(model_list))

# Validate number of models
len(model_list) == (len(ranks)*len(maxIters)*len(regParams)*len(alphas))

# Split the data into training and test sets
(training, test) = df.randomSplit([0.8, 0.2])

#Building 5 folds within the training set.
train1, train2, train3, train4, train5 = training.randomSplit([0.2, 0.2, 0.2, 0.2, 0.2], seed = 1)
fold1 = train2.union(train3).union(train4).union(train5)
fold2 = train3.union(train4).union(train5).union(train1)
fold3 = train4.union(train5).union(train1).union(train2)
fold4 = train5.union(train1).union(train2).union(train3)
fold5 = train1.union(train2).union(train3).union(train4)

foldlist = [(fold1, train1), (fold2, train2), (fold3, train3), (fold4, train4), (fold5, train5)]

# Empty list to fill with ROC-AUCs from each model
ROCAUC = []

# Loops through all models and all folds
for model in model_list:
    for ft_pair in foldlist:

        # Fits model to fold within training data
        fitted_model = model.fit(ft_pair[0])

        # Generates predictions using fitted_model on respective CV test data
        predictions = fitted_model.transform(ft_pair[1])
   
        predictions = spark.createDataFrame(predictions.select('liked', 'prediction').rdd, \
                                    schema=StructType([StructField('liked', DoubleType()), \
                                                       StructField('prediction', DoubleType())]))
        # Generates and prints a ROC-AUC metric CV test data
        
        r = evaluator.evaluate(predictions)
        print ("ROC-AUC: ", r)

    # Fits model to all of training data and generates preds for test data
    v_fitted_model = model.fit(training)
    v_predictions = v_fitted_model.transform(test)
    v_predictions = spark.createDataFrame(v_predictions.select('liked', 'prediction').rdd, \
                                    schema=StructType([StructField('liked', DoubleType()), \
                                                       StructField('prediction', DoubleType())]))
    
    v_r = evaluator.evaluate(v_predictions)

    # Adds validation ROC-AUC values to ROCAUC list
    ROCAUC.append(v_r)
    print ("ROC-AUC in Test set: ", v_r)
Related