web-dev-qa-db-fra.com

Appel de la fonction Java / Scala à partir d'une tâche

Contexte

Ma question initiale ici était Pourquoi utiliser DecisionTreeModel.predict la fonction de carte interne déclenche une exception? et est liée à Comment générer des tuples de (étiquette d'origine, étiquette prédite) sur Spark avec MLlib?

Lorsque nous utilisons Scala API ne méthode recommandée pour obtenir des prédictions pour RDD[LabeledPoint] utiliser DecisionTreeModel, c'est simplement mapper sur RDD:

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

Malheureusement, une approche similaire dans PySpark ne fonctionne pas si bien:

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

Exception: il semble que vous tentiez de référencer SparkContext à partir d'une variable de diffusion, d'une action ou d'une transforamtion. SparkContext ne peut être utilisé que sur le pilote, pas dans le code qu'il exécute sur les travailleurs. Pour plus d'informations, consultez SPARK-506 .

Au lieu de cela documentation officielle recommande quelque chose comme ceci:

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

Que se passe-t-il? Il n'y a pas de variable de diffusion ici et API Scala définit predict comme suit:

/**
 * 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))
}

donc au moins au premier coup d'œil, l'appel de l'action ou de la transformation n'est pas un problème puisque la prédiction semble être une opération locale.

Explication

Après avoir creusé, j'ai compris que la source du problème était un JavaModelWrapper.call méthode invoquée depuis DecisionTreeModel.predict . Il accèsSparkContext qui est nécessaire pour appeler Java:

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

Question

En cas de DecisionTreeModel.predict il existe une solution de contournement recommandée et tout le code requis fait déjà partie de l'API Scala mais existe-t-il une manière élégante de gérer un problème comme celui-ci en général?

Seules les solutions auxquelles je peux penser en ce moment sont plutôt lourdes:

  • tout pousser vers JVM soit en étendant les classes Spark via les conversions implicites ou en ajoutant une sorte de wrappers
  • en utilisant la passerelle Py4j directement
36
zero323

La communication utilisant la passerelle Py4J par défaut n'est tout simplement pas possible. Pour comprendre pourquoi nous devons jeter un œil au diagramme suivant du document PySpark Internals [1]:

enter image description here

Étant donné que la passerelle Py4J fonctionne sur le pilote, elle n'est pas accessible aux interprètes Python qui communiquent avec les travailleurs JVM via des sockets (voir par exemple PythonRDD /- rdd.py ).

Théoriquement, il pourrait être possible de créer une passerelle Py4J distincte pour chaque travailleur, mais en pratique, il est peu probable qu'elle soit utile. Ignorer des problèmes tels que la fiabilité Py4J n'est tout simplement pas conçu pour effectuer des tâches gourmandes en données.

Existe-t-il des solutions de contournement?

  1. Utilisation de Spark SQL Data Sources API pour encapsuler le code JVM.

    Avantages : pris en charge, haut niveau, ne nécessite pas d'accès à l'API PySpark interne

    Inconvénients : Relativement verbeux et pas très bien documenté, limité principalement aux données d'entrée

  2. Fonctionnant sur des DataFrames en utilisant Scala UDFs.

    Avantages : Facile à implémenter (voir Spark: Comment mapper Python avec Scala ou Java Fonctions définies par l'utilisateur? ), aucune conversion de données entre Python et Scala si les données sont déjà stockées dans un DataFrame, accès minimal à Py4J

    Inconvénients : Nécessite un accès à la passerelle Py4J et aux méthodes internes, limité à Spark SQL, difficile à déboguer, non pris en charge

  3. Création de haut niveau Scala interface de la même manière que cela se fait dans MLlib.

    Avantages : Flexible, possibilité d'exécuter du code complexe arbitraire. Il peut être fait soit directement sur RDD (voir par exemple wrappers de modèle MLlib ) ou avec DataFrames (voir Comment utiliser un Scala = classe à l'intérieur de Pyspark ). Cette dernière solution semble être beaucoup plus conviviale puisque tous les détails du ser-de sont déjà gérés par l'API existante.

    Inconvénients : Bas niveau, conversion de données requise, identique aux UDF nécessite l'accès à Py4J et à l'API interne, non pris en charge

    Quelques exemples de base peuvent être trouvés dans Transformer PySpark RDD avec Scala

  4. Utilisation d'un outil de gestion de workflow externe pour basculer entre Python et Scala/Java jobs et transmission de données à un DFS).

    Avantages : Facile à implémenter, modifications minimales du code lui-même

    Inconvénients : Coût de lecture/écriture des données ( Alluxio ?)

  5. Utilisation de SQLContext partagé (voir par exemple Apache Zeppelin ou Livy ) pour transmettre des données entre les langues invitées à l'aide de tables temporaires enregistrées.

    Avantages : bien adapté à l'analyse interactive

    Inconvénients : Pas tellement pour les travaux par lots (Zeppelin) ou peut nécessiter une orchestration supplémentaire (Livy)


  1. Joshua Rosen. (2014, 04 août) PySpark Internals . Récupéré de https://cwiki.Apache.org/confluence/display/SPARK/PySpark+Internals
45
zero323