web-dev-qa-db-fra.com

Comment attribuer des numéros contigus uniques à des éléments dans un Spark RDD

J'ai un ensemble de données de (user, product, review), et souhaitons l'introduire dans l'algorithme ALS de mllib.

L'algorithme a besoin que les utilisateurs et les produits soient des nombres, tandis que les miens sont des noms d'utilisateur de chaîne et des SKU de chaîne.

À l'heure actuelle, j'obtiens les utilisateurs et les SKU distincts, puis je leur attribue des ID numériques en dehors de Spark.

Je me demandais s'il y avait une meilleure façon de procéder. La seule approche à laquelle j'ai pensé est d'écrire un RDD personnalisé qui énumère essentiellement de 1 à n, puis d'appeler Zip sur les deux RDD.

46
Dilum Ranatunga

À partir de Spark 1. il existe deux méthodes que vous pouvez utiliser pour résoudre ce problème facilement:

  • RDD.zipWithIndex est comme Seq.zipWithIndex, il ajoute des nombres contigus (Long). Cela doit d'abord compter les éléments dans chaque partition, donc votre entrée sera évaluée deux fois. Mettez en cache votre RDD d'entrée si vous souhaitez l'utiliser.
  • RDD.zipWithUniqueId vous donne également des identifiants Long uniques, mais ils ne sont pas garantis contigus. (Ils ne seront contigus que si chaque partition a le même nombre d'éléments.) L'avantage est que cela n'a pas besoin de connaître quoi que ce soit sur l'entrée, donc cela ne provoquera pas de double évaluation.
41
Daniel Darabos

Pour un exemple d'utilisation similaire, je viens de hacher les valeurs de chaîne. Voir http://blog.cloudera.com/blog/2014/03/why-Apache-spark-is-a-crossover-hit-for-data-scientists/

def nnHash(tag: String) = tag.hashCode & 0x7FFFFF
var tagHashes = postIDTags.map(_._2).distinct.map(tag =>(nnHash(tag),tag))

On dirait que vous faites déjà quelque chose comme ça, bien que le hachage puisse être plus facile à gérer.

Matei a suggéré ici une approche pour émuler zipWithIndex sur un RDD, ce qui revient à attribuer des ID au sein de chaque partition qui seront uniques au monde: https://groups.google.com/forum/# ! topic/spark-users/WxXvcn2gl1E

15
Sean Owen

Une autre option simple, si vous utilisez des DataFrames et que vous vous préoccupez uniquement de l'unicité, est d'utiliser la fonction MonotonicallyIncreasingID

import org.Apache.spark.sql.functions.monotonicallyIncreasingId 
val newDf = df.withColumn("uniqueIdColumn", monotonicallyIncreasingId)

Modifier: MonotonicallyIncreasingID a été déconseillé et supprimé depuis Spark 2. ; il est désormais connu sous le nom de monotonically_increasing_id.

8
radek1st

Les gens ont déjà recommandé monotonically_increasing_id () , et ont mentionné le problème qu'il crée des Longs, pas des Ints.

Cependant, d'après mon expérience (mise en garde - Spark 1.6) - si vous l'utilisez sur un seul exécuteur (répartition à 1 avant), aucun préfixe d'exécuteur n'est utilisé et le nombre peut être casté en toute sécurité en Int. Évidemment, vous devez avoir moins de lignes Integer.MAX_VALUE.

2
Eyal

monotonically_increasing_id () apparaît pour être la réponse, mais ne fonctionnera malheureusement pas pour ALS car il produit des nombres 64 bits et ALS attend les 32 bits (voir mon commentaire ci-dessous la réponse de radek1st pour les deets).

La solution que j'ai trouvée est d'utiliser zipWithIndex () , comme mentionné dans la réponse de Darabos. Voici comment l'implémenter:

Si vous disposez déjà d'un DataFrame à colonne unique avec vos utilisateurs distincts appelés userids, vous pouvez créer une table de recherche (LUT) comme suit:

# PySpark code
user_als_id_LUT = sqlContext.createDataFrame(userids.rdd.map(lambda x: x[0]).zipWithIndex(), StructType([StructField("userid", StringType(), True),StructField("user_als_id", IntegerType(), True)]))

Maintenant vous pouvez:

  • Utilisez cette LUT pour obtenir des ID entiers compatibles ALS à fournir à ALS
  • Utilisez cette LUT pour effectuer une recherche inversée lorsque vous devez revenir de l'ID ALS à l'ID d'origine

Faites de même pour les articles, évidemment.

2
xenocyon