Spark、ML、StringIndexer:处理看不见的标签
Spark, ML, StringIndexer: handling unseen labels
我的目标是构建一个 multicalss classifier。
我已经构建了一个用于特征提取的管道,它包括作为第一步的 StringIndexer 转换器,用于将每个 class 名称映射到一个标签,这个标签将用于 classifier 训练步骤。
流水线与训练集相匹配。
为了提取相同的特征向量,测试集必须由拟合管道处理。
知道我的测试集文件和训练集的结构是一样的。这里可能出现的情况是在测试集中遇到一个没见过的class名字,在这种情况下StringIndexer会找不到标签,并抛出异常。
这种情况有解决办法吗?或者我们怎样才能避免这种情况发生?
恐怕没有好的方法。要么
- 在应用之前过滤掉带有未知标签的测试示例
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 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
就我而言,我是 运行 在大型数据集上使用 spark ALS 并且数据并非在所有分区都可用,因此我必须适当地缓存()数据并且它非常有效
对我来说,通过设置参数 (https://issues.apache.org/jira/browse/SPARK-8764) 完全忽略行并不是解决问题的真正可行方法。
我最终创建了自己的 CustomStringIndexer 转换器,它将为训练时未遇到的所有新字符串分配一个新值。您也可以通过更改 spark 功能代码的相关部分来做到这一点(只需删除 if 条件明确检查这个并使其 return 改为数组的长度)并重新编译 jar.
不是一个简单的修复,但确实是一个修复。
我记得在 JIRA 中看到一个错误也将其合并:https://issues.apache.org/jira/browse/SPARK-17498
虽然它被设置为与 Spark 2.2 一起发布。我猜只需要等待 :S
在 Spark 2.2(7-2017 年发布)中,您可以在创建索引器时使用 .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 的输出如何查找不同选项的示例,请参阅链接文档。
我的目标是构建一个 multicalss classifier。
我已经构建了一个用于特征提取的管道,它包括作为第一步的 StringIndexer 转换器,用于将每个 class 名称映射到一个标签,这个标签将用于 classifier 训练步骤。
流水线与训练集相匹配。
为了提取相同的特征向量,测试集必须由拟合管道处理。
知道我的测试集文件和训练集的结构是一样的。这里可能出现的情况是在测试集中遇到一个没见过的class名字,在这种情况下StringIndexer会找不到标签,并抛出异常。
这种情况有解决办法吗?或者我们怎样才能避免这种情况发生?
恐怕没有好的方法。要么
- 在应用之前过滤掉带有未知标签的测试示例
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 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
就我而言,我是 运行 在大型数据集上使用 spark ALS 并且数据并非在所有分区都可用,因此我必须适当地缓存()数据并且它非常有效
对我来说,通过设置参数 (https://issues.apache.org/jira/browse/SPARK-8764) 完全忽略行并不是解决问题的真正可行方法。
我最终创建了自己的 CustomStringIndexer 转换器,它将为训练时未遇到的所有新字符串分配一个新值。您也可以通过更改 spark 功能代码的相关部分来做到这一点(只需删除 if 条件明确检查这个并使其 return 改为数组的长度)并重新编译 jar.
不是一个简单的修复,但确实是一个修复。
我记得在 JIRA 中看到一个错误也将其合并:https://issues.apache.org/jira/browse/SPARK-17498
虽然它被设置为与 Spark 2.2 一起发布。我猜只需要等待 :S
在 Spark 2.2(7-2017 年发布)中,您可以在创建索引器时使用 .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 的输出如何查找不同选项的示例,请参阅链接文档。