Python 在自定义ML管道中的阶段之间传递变量
我想从数据帧中删除一些列,然后应用ML算法。我通过建造两条单独的管道来实现这一点。我的问题是如何将这两条管道合并为一条管道Python 在自定义ML管道中的阶段之间传递变量,python,apache-spark,pyspark,Python,Apache Spark,Pyspark,我想从数据帧中删除一些列,然后应用ML算法。我通过建造两条单独的管道来实现这一点。我的问题是如何将这两条管道合并为一条管道 ####################### from typing import Iterable import pandas as pd import pyspark.sql.functions as F from pyspark.ml import Pipeline, Transformer from pyspark.sql import DataFrame fro
#######################
from typing import Iterable
import pandas as pd
import pyspark.sql.functions as F
from pyspark.ml import Pipeline, Transformer
from pyspark.sql import DataFrame
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml.feature import VectorAssembler
#######################
#Custom Class
#######################
class ColumnDropper_test(Transformer):
def __init__(self, banned_list: Iterable[str]):
super().__init__()
self.banned_list = banned_list
def _transform(self, df: DataFrame) -> DataFrame:
df = df.drop(
*[x for x in df.columns if any(y in x for y in self.banned_list)])
return df
#######################
#Sample Data
#######################
data = pd.DataFrame({
'ball_column': [0, 1, 2, 3],
'keep_column': [7, 8, 9, 10],
'hall_column': [14, 15, 16, 17],
'banned_me': [14, 15, 16, 17],
'target': [21, 31, 41, 51]
})
df = spark.createDataFrame(data)
#######################
# First Pipeline
#######################
column_dropper = ColumnDropper_test(banned_list=['banned_me'])
model = Pipeline(stages=[column_dropper]).fit(df).transform(df)
#######################
#Second Pipeline(Question: Add the block of code below to the above pipeline)
#########################
ready = [col for col in model.columns if col != 'target']
assembler = VectorAssembler(inputCols=ready, outputCol='features')
dtc = DecisionTreeClassifier(featuresCol='features', labelCol='target')
model_2 = Pipeline(stages=[assembler,dtc])
train_data, test_data = model.randomSplit([0.5,0.5])
fit_model = model_2.fit(train_data)
results = fit_model.transform(test_data)
results.select('features','Prediction').show()
我发现的挑战在于上述代码中的变量ready
。由于调用column\u dropper
后,model.columns
将不同(列数较少),使用(df.columns)将其添加到同一管道将导致以下错误,因为原始数据已删除禁用的列
#Combining both Pipelines failed attempt
model = Pipeline(stages=[column_dropper,assembler,dtc]).fit(df).transform(df)
调用o188.transform时出错:
java.lang.IllegalArgumentException:字段“禁止”不存在。
可用字段:ball\u列、keep\u列、hall\u列、target
我最初的建议是创建一个新类,该类继承了ColumnDropper\u test
class的新变量df.columns
。我们如何让管道中的汇编阶段从列的dropper
阶段查看新的df
,而不是查看原始的df
?您必须创建一个继承向量汇编程序的自定义类来自动设置输入:
from pyspark import keyword_only
class CustomVecssembler(VectorAssembler):
@keyword_only
def __init__(self, outputCol='features'):
super(CustomVecssembler, self).__init__()
self.transformer = VectorAssembler(outputCol=outputCol)
if spark.version.startswith('2.1'):
kwargs = self.__init__._input_kwargs
else:
kwargs = self._input_kwargs
self.setParams(**kwargs)
@keyword_only
def setParams(self, outputCol='features'):
if spark.version.startswith('2.1'):
kwargs = self.__init__._input_kwargs
else:
kwargs = self._input_kwargs
return self._set(**kwargs)
def _transform(self, df):
ready = [col for col in df.columns if col != 'target']
self.setInputCols(ready)
self.transformer.setInputCols(ready)
df = self.transformer.transform(df)
return df
正在验证它是否有效:
# prep dataset
data = pd.DataFrame({
'ball_column': [0, 1, 2, 3],
'keep_column': [7, 8, 9, 10],
'hall_column': [14, 15, 16, 17],
'banned_me': [14, 15, 16, 17],
'target': [21, 31, 41, 51]
})
df = spark.createDataFrame(data)
# ORIGINAL IMPLEMENTATION
column_dropper = ColumnDropper_test(banned_list=['banned_me'])
model = Pipeline(stages=[column_dropper]).fit(df).transform(df)
ready = [col for col in model.columns if col != 'target']
assembler = VectorAssembler(inputCols=ready, outputCol='features')
dtc = DecisionTreeClassifier(featuresCol='features', labelCol='target')
model_2 = Pipeline(stages=[assembler, dtc])
train_data, test_data = model.randomSplit([0.5, 0.5])
fit_model = model_2.fit(train_data)
results = fit_model.transform(test_data)
results.select('features','Prediction').show()
# +--------------+----------+
# | features|Prediction|
# +--------------+----------+
# |[1.0,15.0,8.0]| 51.0|
# |[2.0,16.0,9.0]| 51.0|
# +--------------+----------+
# USING CUSTOM VEC ASSEMBLER
new_assembler = CustomVecssembler(outputCol='features')
new_pipeline = Pipeline(stages=[column_dropper, new_assembler, dtc]).fit(train_data)
new_results = new_pipeline.transform(test_data)
new_results.select('features', 'Prediction').show()
# +--------------+----------+
# | features|Prediction|
# +--------------+----------+
# |[1.0,15.0,8.0]| 51.0|
# |[2.0,16.0,9.0]| 51.0|
# +--------------+----------+
真是个有趣的问题。我提出的一个丑陋的解决方案是嵌套转换器
,即将汇编程序放在ColumnDropper
类中。但必须有更好的解决办法。但是,我偶然发现,定制的Transformer
没有getOutputCols
方法。因此,对于一个好的解决方案,我们必须找到一种方法来实现它(我认为)。我还没有找到如何做到这一点,好奇地想看看其他人的解决方法。@Florian不客气!总是有新的东西可以学习,所以@做得漂亮!非常感谢。另一方面,您认为在同一示例中,随着类数量的增加,为多个操作检查点/持久化/缓存df
是一个好主意吗?例如,如果我们想包含一个类,用于更改列名、选择特定类型的列和其他预处理步骤。@没问题!对于您的问题,我并不认为有必要保留df
,因为管道中的后续转换步骤不会真正利用缓存的df
——它们使用上一步转换的数据帧。我能看到持久化df
值的唯一情况是当您有多个管道要应用df
时。@Matthew不幸的是,这是用python编写自定义转换器的问题之一,因为转换器最初是用Java编写的。我还没有找到一个干净的解决方案,转换器可以从Java类访问属性/方法,但如果我找到了解决方案,我会让您知道。@Matthew我对该类添加了一些其他编辑。显然,在尝试转换时,我得到了一个“function”对象没有属性“\u input\u kwargs”
错误,并且我还添加了python 2.7与超级(父,自)初始化器的向后兼容性。有趣的是,该解决方案现在非常粗糙,因为它不仅继承自VectorAssembler
,而且还包装了该类的一个实例。