【问题标题】:Spark, ML, StringIndexer: handling unseen labelsSpark、ML、StringIndexer:处理看不见的标签
【发布时间】:2016-04-13 09:37:11
【问题描述】:

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

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

管道拟合训练集。

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

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

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

【问题讨论】:

  • 请重新接受@queise 的回答。它比已经添加的解决方案要好得多。

标签: apache-spark apache-spark-ml


【解决方案1】:

恐怕没有好办法。要么

  • 在应用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)))

【讨论】:

  • 难道没有办法提供没有任何标签的测试数据,以便算法从头开始预测它。就我而言,我没有任何测试数据项的标签。请参阅:stackoverflow.com/questions/44127634/… 在我的情况下,我是否必须为项目关联随机标签?
  • @queise 使用 spark 2.2 的答案现在是最佳答案
【解决方案2】:

在火花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"

我开始使用它,但最终返回到克里斯普的第2个符号点,即将这个特定的估算器拟合到完整的数据集。

当您转换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

【讨论】:

  • 但是当您尝试将模型应用于新数据时会发生什么?您可能会发现某些列中的新值不在原始测试或培训数据中。我担心 setHandleInvalid("skip") 会导致整行被丢弃,当你真的只想忽略以前看不见的值,但仍然使用行中的其他值时。
  • 这将意味着新的看不见的数据在数据之前不属于与训练数据相同的统计分布。没关系,实际上可能在大多数应用程序中不能给它一个标签,因为它没有“看”就像模型之前的任何数据一样。 “看起来像”在统计意义上,显然正在尝试学习不记忆。 span>
  • 有一个新的.sethandleInvalid(“保留”)选项,带有Spark 2.2的选项。,它将在处理新数据时添加新索引。在我看来,此功能将非常有用,因为希望您之后应用的预测模型将利用所有其他变量输出有效的预测(当然,新索引的预测能力为零)。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      使用 Spark 2.2(2017 年 7 月发布),您可以在创建索引器时使用 .setHandleInvalid("keep") 选项。使用此选项,索引器会在看到新标签时添加新索引。

      val categoryIndexerModel = new StringIndexer()
        .setInputCol("category")
        .setOutputCol("indexedCategory")
        .setHandleInvalid("keep") // options are "keep", "error" or "skip"
      

      来自documentation:当您在一个数据集上安装一个 StringIndexer 然后使用它来转换另一个数据集时,StringIndexer 如何处理看不见的标签有三种策略:

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

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

      【讨论】:

      • 您能否提供更多关于keep error skip 选项之间的确切差异的详细信息。 Skip,删除数据点? Error,打断模型?和Keep,添加一个新列?这是否适用于两种方式,所以在测试中看不见的值,不在训练中,在训练中看不见的值不在测试中?
      猜你喜欢
      • 2016-08-03
      • 2019-07-14
      • 2016-07-06
      • 2017-06-27
      • 2019-02-21
      • 1970-01-01
      • 1970-01-01
      • 2021-09-24
      相关资源
      最近更新 更多