web-dev-qa-db-ja.com

タスクからJava / Scala関数を呼び出す

バックグラウンド

ここでの私の最初の質問は、なぜDecisionTreeModel.predict内部のmap関数は例外を発生しますか?および (元のラベル、予測ラベル)のタプルの生成方法Spark MLlib?

Scala API 推奨される方法RDD[LabeledPoint]DecisionTreeModelを使用すると、単にRDDにマッピングされます。

val labelAndPreds = testData.map { point =>
  val prediction = model.predict(point.features)
  (point.label, prediction)
}

残念ながら、PySparkでの同様のアプローチはあまりうまく機能しません。

labelsAndPredictions = testData.map(
    lambda lp: (lp.label, model.predict(lp.features))
labelsAndPredictions.first()

例外:ブロードキャスト変数、アクション、またはトランスフォーメーションからSparkContextを参照しようとしているようです。 SparkContextはドライバーでのみ使用でき、ワーカーで実行するコードでは使用できません。詳細については、 SPARK-506 を参照してください。

その代わりに 公式ドキュメント は次のようなものを推奨しています:

predictions = model.predict(testData.map(lambda x: x.features))
labelsAndPredictions = testData.map(lambda lp: lp.label).Zip(predictions)

ここで何が起こっているのでしょうか?ここにはブロードキャスト変数はなく、 Scala API は次のようにpredictを定義します。

/**
 * Predict values for a single data point using the model trained.
 *
 * @param features array representing a single data point
 * @return Double prediction from the trained model
 */
def predict(features: Vector): Double = {
  topNode.predict(features)
}

/**
 * Predict values for the given data set using the model trained.
 *
 * @param features RDD representing data points to be predicted
 * @return RDD of predictions for each of the given data points
 */
def predict(features: RDD[Vector]): RDD[Double] = {
  features.map(x => predict(x))
}

そのため、予測はローカル操作のように見えるため、少なくとも一見するとアクションまたは変換からの呼び出しは問題になりません。

説明

掘り下げた後、問題の原因は JavaModelWrapper.callDecisionTreeModel.predict から呼び出されるメソッド。それ accessSparkContextこれは、Java function:

callJavaFunc(self._sc, getattr(self._Java_model, name), *a)

質問

の場合には DecisionTreeModel.predict推奨される回避策があり、必要なコードはすべてScala APIの一部ですが、一般にこのような問題を処理するためのエレガントな方法はありますか?

私が今考えることができる解決策だけがかなり重いです:

  • Sparkクラスを暗黙的な変換で拡張するか、何らかのラッパーを追加することにより、すべてをJVMにプッシュダウンする
  • py4jゲートウェイを直接使用する
36
zero323

デフォルトのPy4Jゲートウェイを使用した通信は、単に不可能です。 PySpark Internalsドキュメント[1]から次の図を見る必要がある理由を理解するために:

enter image description here

Py4Jゲートウェイはドライバーで実行されるため、ソケットを介してJVMワーカーと通信するPythonインタープリターにアクセスできません(たとえば、 PythonRDD /- rdd.py )。

理論的には、ワーカーごとに個別のPy4Jゲートウェイを作成することは可能ですが、実際には有用ではないでしょう。信頼性などの問題を無視Py4Jは、データ集約型のタスクを実行するように設計されていません。

回避策はありますか?

  1. Spark SQL Data Sources API を使用してJVMコードをラップします。

    長所:サポートされており、高レベルであり、内部PySpark APIへのアクセスを必要としません

    短所:比較的詳細で、あまり文書化されておらず、主に入力データに限定されている

  2. Scala UDFsを使用したDataFramesでの操作。

    長所:簡単に実装できます( Spark:マッピング方法Python with ScalaまたはJavaユーザー定義関数? )、PythonとScalaデータが既にDataFrameに保存されている場合、Py4Jへの最小限のアクセス

    短所:Py4Jゲートウェイと内部メソッドへのアクセスが必要、Spark SQL、デバッグが困難、サポートされていません

  3. 高レベルScalaインターフェースをMLlibで行われるのと同様の方法で作成します。

    長所:任意の複雑なコードを実行する柔軟な機能。 RDDに直接ドン(例: MLlibモデルラッパー を参照)またはDataFramesを使用してドン( Scalaの使用方法を参照 Pyspark内のクラス)==)。後者のソリューションは、すべてのser-de詳細が既存のAPIによってすでに処理されているため、はるかに使いやすいようです。

    Cons:UDFがPy4Jおよび内部APIへのアクセスを必要とするのと同じ、低レベル、必要なデータ変換、サポートされていません

    基本的な例は ScalaでPySpark RDDを変換する にあります。

  4. 外部ワークフロー管理ツールを使用して、PythonとScala/JavaジョブとDFSへのデータの受け渡し。

    長所:実装が簡単で、コード自体への最小限の変更

    短所:データの読み取り/書き込みのコスト( Alluxio ?)

  5. 共有SQLContext(たとえば Apache Zeppelin または Livy を参照)を使用して、登録済みの一時テーブルを使用してゲスト言語間でデータを渡します。

    長所:インタラクティブな分析に最適

    短所:バッチジョブにはそれほど多くない(Zeppelin)、または追加のオーケストレーションが必要な場合がある(Livy)


  1. ジョシュア・ローゼン。 (2014年8月4日) PySpark Internalshttps://cwiki.Apache.org/confluence/display/SPARK/PySpark+Internals から取得
45
zero323