Skip to content

[SPARK-27007][PYTHON]add rawPrediction to OneVsRest in PySpark #23910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions python/pyspark/ml/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from pyspark.ml.wrapper import JavaEstimator, JavaModel, JavaParams
from pyspark.ml.wrapper import JavaWrapper
from pyspark.ml.common import inherit_doc, _java2py, _py2java
from pyspark.ml.linalg import Vectors
from pyspark.sql import DataFrame
from pyspark.sql.functions import udf, when
from pyspark.sql.types import ArrayType, DoubleType
Expand Down Expand Up @@ -1705,7 +1706,8 @@ def weights(self):
return self._call_java("weights")


class OneVsRestParams(HasFeaturesCol, HasLabelCol, HasWeightCol, HasPredictionCol):
class OneVsRestParams(HasFeaturesCol, HasLabelCol, HasWeightCol, HasPredictionCol,
HasRawPredictionCol):
"""
Parameters for OneVsRest and OneVsRestModel.
"""
Expand Down Expand Up @@ -1746,6 +1748,8 @@ class OneVsRest(Estimator, OneVsRestParams, HasParallelism, JavaMLReadable, Java
>>> df = spark.read.format("libsvm").load(data_path)
>>> lr = LogisticRegression(regParam=0.01)
>>> ovr = OneVsRest(classifier=lr)
>>> ovr.getRawPredictionCol()
'rawPrediction'
>>> model = ovr.fit(df)
>>> model.models[0].coefficients
DenseVector([0.5..., -1.0..., 3.4..., 4.2...])
Expand All @@ -1769,16 +1773,18 @@ class OneVsRest(Estimator, OneVsRestParams, HasParallelism, JavaMLReadable, Java
>>> model2 = OneVsRestModel.load(model_path)
>>> model2.transform(test0).head().prediction
0.0
>>> model.transform(test2).columns
['features', 'rawPrediction', 'prediction']

.. versionadded:: 2.0.0
"""

@keyword_only
def __init__(self, featuresCol="features", labelCol="label", predictionCol="prediction",
classifier=None, weightCol=None, parallelism=1):
rawPredictionCol="rawPrediction", classifier=None, weightCol=None, parallelism=1):
"""
__init__(self, featuresCol="features", labelCol="label", predictionCol="prediction", \
classifier=None, weightCol=None, parallelism=1):
rawPredictionCol="rawPrediction", classifier=None, weightCol=None, parallelism=1):
"""
super(OneVsRest, self).__init__()
self._setDefault(parallelism=1)
Expand All @@ -1788,10 +1794,10 @@ def __init__(self, featuresCol="features", labelCol="label", predictionCol="pred
@keyword_only
@since("2.0.0")
def setParams(self, featuresCol="features", labelCol="label", predictionCol="prediction",
classifier=None, weightCol=None, parallelism=1):
rawPredictionCol="rawPrediction", classifier=None, weightCol=None, parallelism=1):
"""
setParams(self, featuresCol="features", labelCol="label", predictionCol="prediction", \
classifier=None, weightCol=None, parallelism=1):
rawPredictionCol="rawPrediction", classifier=None, weightCol=None, parallelism=1):
Sets params for OneVsRest.
"""
kwargs = self._input_kwargs
Expand All @@ -1802,8 +1808,6 @@ def _fit(self, dataset):
featuresCol = self.getFeaturesCol()
predictionCol = self.getPredictionCol()
classifier = self.getClassifier()
assert isinstance(classifier, HasRawPredictionCol),\
"Classifier %s doesn't extend from HasRawPredictionCol." % type(classifier)

numClasses = int(dataset.agg({labelCol: "max"}).head()["max("+labelCol+")"]) + 1

Expand Down Expand Up @@ -1872,10 +1876,12 @@ def _from_java(cls, java_stage):
featuresCol = java_stage.getFeaturesCol()
labelCol = java_stage.getLabelCol()
predictionCol = java_stage.getPredictionCol()
rawPredictionCol = java_stage.getRawPredictionCol()
classifier = JavaParams._from_java(java_stage.getClassifier())
parallelism = java_stage.getParallelism()
py_stage = cls(featuresCol=featuresCol, labelCol=labelCol, predictionCol=predictionCol,
classifier=classifier, parallelism=parallelism)
rawPredictionCol=rawPredictionCol, classifier=classifier,
parallelism=parallelism)
py_stage._resetUid(java_stage.uid())
return py_stage

Expand All @@ -1892,6 +1898,7 @@ def _to_java(self):
_java_obj.setFeaturesCol(self.getFeaturesCol())
_java_obj.setLabelCol(self.getLabelCol())
_java_obj.setPredictionCol(self.getPredictionCol())
_java_obj.setRawPredictionCol(self.getRawPredictionCol())
return _java_obj

def _make_java_param_pair(self, param, value):
Expand Down Expand Up @@ -1982,7 +1989,8 @@ def _transform(self, dataset):
# update the accumulator column with the result of prediction of models
aggregatedDataset = newDataset
for index, model in enumerate(self.models):
rawPredictionCol = model._call_java("getRawPredictionCol")
rawPredictionCol = self.getRawPredictionCol()

columns = origCols + [rawPredictionCol, accColName]

# add temporary column to store intermediate scores and update
Expand All @@ -2003,14 +2011,24 @@ def _transform(self, dataset):
if handlePersistence:
newDataset.unpersist()

# output the index of the classifier with highest confidence as prediction
labelUDF = udf(
lambda predictions: float(max(enumerate(predictions), key=operator.itemgetter(1))[0]),
DoubleType())

# output label and label metadata as prediction
return aggregatedDataset.withColumn(
self.getPredictionCol(), labelUDF(aggregatedDataset[accColName])).drop(accColName)
if self.getRawPredictionCol():
def func(predictions):
predArray = []
for x in predictions:
predArray.append(x)
return Vectors.dense(predArray)

rawPredictionUDF = udf(func)
aggregatedDataset = aggregatedDataset.withColumn(
self.getRawPredictionCol(), rawPredictionUDF(aggregatedDataset[accColName]))

if self.getPredictionCol():
# output the index of the classifier with highest confidence as prediction
labelUDF = udf(lambda predictions: float(max(enumerate(predictions),
key=operator.itemgetter(1))[0]), DoubleType())
aggregatedDataset = aggregatedDataset.withColumn(
self.getPredictionCol(), labelUDF(aggregatedDataset[accColName]))
return aggregatedDataset.drop(accColName)

@since("2.0.0")
def copy(self, extra=None):
Expand Down
2 changes: 1 addition & 1 deletion python/pyspark/ml/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def test_output_columns(self):
ovr = OneVsRest(classifier=lr, parallelism=1)
model = ovr.fit(df)
output = model.transform(df)
self.assertEqual(output.columns, ["label", "features", "prediction"])
self.assertEqual(output.columns, ["label", "features", "rawPrediction", "prediction"])

def test_parallelism_doesnt_change_output(self):
df = self.spark.createDataFrame([(0.0, Vectors.dense(1.0, 0.8)),
Expand Down