我正在尝试在Spark 2.4.0中创建一个自定义转换器。保存它可以正常工作。但是,当我尝试加载它时,出现以下错误:
java.lang.NoSuchMethodException: TestTransformer.<init>(java.lang.String)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getConstructor(Class.java:1825)
at org.apache.spark.ml.util.DefaultParamsReader.load(ReadWrite.scala:496)
at org.apache.spark.ml.util.MLReadable$class.load(ReadWrite.scala:380)
at TestTransformer$.load(<console>:40)
... 31 elided
这向我表明它找不到我的转换器的构造函数,这对我来说真的没有意义。
MCVE:
import org.apache.spark.sql.{Dataset, DataFrame}
import org.apache.spark.sql.types.{StructType}
import org.apache.spark.ml.Transformer
import org.apache.spark.ml.param.ParamMap
import org.apache.spark.ml.util.{DefaultParamsReadable, DefaultParamsWritable, Identifiable}
class TestTransformer(override val uid: String) extends Transformer with DefaultParamsWritable{
def this() = this(Identifiable.randomUID("TestTransformer"))
override def transform(df: Dataset[_]): DataFrame = {
val columns = df.columns
df.select(columns.head, columns.tail: _*)
}
override def transformSchema(schema: StructType): StructType = {
schema
}
override def copy(extra: ParamMap): TestTransformer = defaultCopy[TestTransformer](extra)
}
object TestTransformer extends DefaultParamsReadable[TestTransformer]{
override def load(path: String): TestTransformer = super.load(path)
}
val transformer = new TestTransformer("test")
transformer.write.overwrite().save("test_transformer")
TestTransformer.load("test_transformer")
运行此命令(我正在使用Jupyter笔记本)会导致上述错误。我尝试过将其编译为.jar文件并运行,没有区别。
让我感到困惑的是,等效的PySpark代码可以正常工作:
from pyspark.sql import SparkSession, DataFrame
from pyspark.ml import Transformer
from pyspark.ml.util import DefaultParamsReadable, DefaultParamsWritable
class TestTransformer(Transformer, DefaultParamsWritable, DefaultParamsReadable):
def transform(self, df: DataFrame) -> DataFrame:
return df
TestTransformer().save('test_transformer')
TestTransformer.load('test_transformer')
如何制作可以保存和加载的自定义Spark转换器?
答案 0 :(得分:3)
我可以在“火花壳”中重现您的问题。
试图找到问题的根源,我研究了DefaultParamsReadable
和DefaultParamsReader
的源,发现它们利用了Java反射。
第495-496行
val instance =
cls.getConstructor(classOf[String]).newInstance(metadata.uid).asInstanceOf[Params]
我认为scala REPL和Java反射不是好朋友。
如果运行此代码段(在您的代码段之后):
new TestTransformer().getClass.getConstructors
您将获得以下输出:
res1: Array[java.lang.reflect.Constructor[_]] = Array(public TestTransformer($iw), public TestTransformer($iw,java.lang.String))
是真的! TestTransformer.<init>(java.lang.String)
不存在。
我找到了两种解决方法,
使用sbt编译代码并创建一个jar,然后使用:require
将其包含在spark-shell中,这对我有用(您提到您尝试过jar,但我不知道如何做)
使用:paste -raw
将代码粘贴在spark-shell中也可以正常工作。我想-raw
会阻止REPL对您的班级进行欺骗。
参见:https://docs.scala-lang.org/overviews/repl/overview.html
我不确定您如何适应Jupyter中的任何一种,但我希望此信息对您有用。
注意:我实际上在spark 2.4.1中使用了spark-shell