我的目标是构建一个多类分类器。
我已经建立了一个用于特征提取的管道,它包括作为第一步的 StringIndexer 转换器,用于将每个类名映射到一个标签,这个标签将用于分类器训练步骤。
管道适合训练集。
为了提取相同的特征向量,必须通过拟合管道处理测试集。
知道我的测试集文件具有与训练集相同的结构。这里可能的场景是在测试集中遇到一个看不见的类名,在这种情况下,StringIndexer 将无法找到标签,并且会引发异常。
这种情况有解决方案吗?或者我们怎样才能避免这种情况发生?
我的目标是构建一个多类分类器。
我已经建立了一个用于特征提取的管道,它包括作为第一步的 StringIndexer 转换器,用于将每个类名映射到一个标签,这个标签将用于分类器训练步骤。
管道适合训练集。
为了提取相同的特征向量,必须通过拟合管道处理测试集。
知道我的测试集文件具有与训练集相同的结构。这里可能的场景是在测试集中遇到一个看不见的类名,在这种情况下,StringIndexer 将无法找到标签,并且会引发异常。
这种情况有解决方案吗?或者我们怎样才能避免这种情况发生?
使用 Spark 2.2(2017 年 7 月发布),您可以.setHandleInvalid("keep")
在创建索引器时使用该选项。使用此选项,索引器会在看到新标签时添加新索引。
val categoryIndexerModel = new StringIndexer()
.setInputCol("category")
.setOutputCol("indexedCategory")
.setHandleInvalid("keep") // options are "keep", "error" or "skip"
从文档中:当您在一个数据集上安装一个 StringIndexer 然后使用它来转换另一个数据集时,关于 StringIndexer 如何处理看不见的标签,有三种策略:
有关 StringIndexer 的输出如何查找不同选项的示例,请参阅链接文档。
在 Spark 1.6 中有一种解决方法。
这是jira: https ://issues.apache.org/jira/browse/SPARK-8764
这是一个例子:
val categoryIndexerModel = new StringIndexer()
.setInputCol("category")
.setOutputCol("indexedCategory")
.setHandleInvalid("skip") // new method. values are "error" or "skip"
我开始使用它,但最终回到了 KrisP 关于将这个特定的 Estimator 拟合到完整数据集的第二个要点。
当您转换 IndexToString 时,您将在稍后的管道中需要它。
这是修改后的示例:
val categoryIndexerModel = new StringIndexer()
.setInputCol("category")
.setOutputCol("indexedCategory")
.fit(itemsDF) // Fit the Estimator and create a Model (Transformer)
... do some kind of classification ...
val categoryReverseIndexer = new IndexToString()
.setInputCol(classifier.getPredictionCol)
.setOutputCol("predictedCategory")
.setLabels(categoryIndexerModel.labels) // Use the labels from the Model
恐怕没有好办法。任何一个
StringIndexer
StringIndexer
训练和测试数据框的联合,因此您可以放心所有标签都在那里以下是执行上述操作的一些示例代码:
// get training labels from original train dataframe
val trainlabels = traindf.select(colname).distinct.map(_.getString(0)).collect //Array[String]
// or get labels from a trained StringIndexer model
val trainlabels = simodel.labels
// define an UDF on your dataframe that will be used for filtering
val filterudf = udf { label:String => trainlabels.contains(label)}
// filter out the bad examples
val filteredTestdf = testdf.filter( filterudf(testdf(colname)))
// transform unknown value to some value, say "a"
val mapudf = udf { label:String => if (trainlabels.contains(label)) label else "a"}
// add a new column to testdf:
val transformedTestdf = testdf.withColumn( "newcol", mapudf(testdf(colname)))
就我而言,我在一个大型数据集上运行 spark ALS,并且数据在所有分区中都不可用,所以我必须适当地缓存()数据,它就像一个魅力
对我来说,通过设置参数( https://issues.apache.org/jira/browse/SPARK-8764 )完全忽略行并不是解决问题的真正可行方法。
我最终创建了自己的 CustomStringIndexer 转换器,它将为训练时未遇到的所有新字符串分配一个新值。您也可以通过更改 spark 功能代码的相关部分来做到这一点(只需删除 if 条件显式检查并使其返回数组的长度)并重新编译 jar。
这不是一个简单的修复,但它肯定是一个修复。
我记得在 JIRA 中也看到了一个错误来合并它:https ://issues.apache.org/jira/browse/SPARK-17498
不过,它将与 Spark 2.2 一起发布。我猜只需要等待:S