22

我的目标是构建一个多类分类器。

我已经建立了一个用于特征提取的管道,它包括作为第一步的 StringIndexer 转换器,用于将每个类名映射到一个标签,这个标签将用于分类器训练步骤。

管道适合训练集。

为了提取相同的特征向量,必须通过拟合管道处理测试集。

知道我的测试集文件具有与训练集相同的结构。这里可能的场景是在测试集中遇到一个看不见的类名,在这种情况下,StringIndexer 将无法找到标签,并且会引发异常。

这种情况有解决方案吗?或者我们怎样才能避免这种情况发生?

4

5 回答 5

26

使用 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 如何处理看不见的标签,有三种策略:

  • 'error':抛出异常(这是默认值)
  • 'skip':完全跳过包含看不见标签的行(删除输出中的行!)
  • 'keep':将看不见的标签放在一个特殊的附加桶中,索引为 numLabels

有关 StringIndexer 的输出如何查找不同选项的示例,请参阅链接文档。

于 2017-05-11T14:03:55.447 回答
15

在 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
于 2016-01-08T21:56:43.500 回答
11

恐怕没有好办法。任何一个

  • 在应用之前过滤掉带有未知标签的测试示例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)))
于 2016-01-08T21:20:16.717 回答
2

就我而言,我在一个大型数据集上运行 spark ALS,并且数据在所有分区中都不可用,所以我必须适当地缓存()数据,它就像一个魅力

于 2017-03-14T17:37:30.797 回答
2

对我来说,通过设置参数( 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

于 2017-03-19T09:17:13.507 回答