web-dev-qa-db-ja.com

Spark、ML、StringIndexer:見えないラベルの処理

私の目標は、マルチカルス分類器を構築することです。

特徴抽出用のパイプラインを構築しました。最初のステップとして、各クラス名をラベルにマップするStringIndexerトランスフォーマーが含まれています。このラベルは、分類子のトレーニングステップで使用されます。

パイプラインはトレーニングセットに適合しています。

同じ特徴ベクトルを抽出するために、テストセットはフィッティングされたパイプラインによって処理される必要があります。

テストセットファイルの構造がトレーニングセットと同じであることを知っています。ここで考えられるシナリオは、テストセットで目に見えないクラス名に直面することです。その場合、StringIndexerはラベルを見つけることができず、例外が発生します。

この場合の解決策はありますか?またはどうすればそれを回避できますか?

16
Rami

Spark 2.2(7-2017リリース))を使用すると、インデクサーを作成するときに.setHandleInvalid("keep")オプションを使用できます。このオプションを使用すると、インデクサーは新しいインデクサーを検出したときに新しいインデックスを追加しますラベル。以前のバージョンでは、"skip"オプション。インデクサーは新しいラベルの行を無視(削除)します。

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

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"

私はこれを使い始めましたが、結局、この特定のEstimatorを完全なデータセットに適合させることに関するKrisPの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
15
Chris Fregly

それを行うための良い方法はありません、私は恐れています。どちらか

  • StringIndexerを適用する前に、不明なラベルを持つテスト例を除外します
  • またはStringIndexerをtrainデータフレームとtestデータフレームの和集合に合わせると、すべてのラベルが存在することが保証されます
  • または、未知のラベルを持つテスト例ケースを既知のラベルに変換します

上記の操作を実行するためのサンプルコードを以下に示します。

// 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)))
11
KrisP

私の場合、私は大きなデータセットでspark ALSを実行していて、すべてのパーティションでデータが利用できなかったので、データを適切にcache()する必要があり、それは魅力のように機能しました

2
Suresh Gorakala

私にとって、引数( 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