J'ai un modèle weka stocké dans S3 qui est d'une taille d'environ 400 Mo. Maintenant, j'ai un ensemble d'enregistrement sur lequel je veux exécuter le modèle et effectuer des prédictions.
Pour effectuer des prédictions, ce que j'ai essayé c'est,
Téléchargez et chargez le modèle sur le pilote en tant qu'objet statique, diffusez-le à tous les exécuteurs. Effectuez une opération de carte sur la prédiction RDD. ----> Ne fonctionne pas, comme dans Weka pour effectuer la prédiction, l'objet modèle doit être modifié et la diffusion nécessite une copie en lecture seule.
Téléchargez et chargez le modèle sur le pilote en tant qu'objet statique et envoyez-le à l'exécuteur lors de chaque opération de mappage. -----> Fonctionne (pas efficace, comme dans chaque opération de carte, je passe un objet de 400 Mo)
Téléchargez le modèle sur le pilote et chargez-le sur chaque exécuteur et mettez-le en cache. (Je ne sais pas comment faire ça)
Quelqu'un a-t-il une idée de comment puis-je charger le modèle sur chaque exécuteur une fois et le mettre en cache afin que pour les autres enregistrements, je ne le charge pas à nouveau?
Vous avez deux options:
object WekaModel {
lazy val data = {
// initialize data here. This will only happen once per JVM process
}
}
Ensuite, vous pouvez utiliser la valeur paresseuse dans votre fonction map
. Le lazy val
garantit que chaque JVM de travail initialise sa propre instance des données. Aucune sérialisation ou diffusion ne sera effectuée pour data
.
elementsRDD.map { element =>
// use WekaModel.data here
}
Avantages
Inconvénients
mapPartition
(ou foreachPartition
) sur le RDD au lieu de simplement map
.Cela vous permet d'initialiser tout ce dont vous avez besoin pour la partition entière.
elementsRDD.mapPartition { elements =>
val model = new WekaModel()
elements.map { element =>
// use model and element. there is a single instance of model per partition.
}
}
Avantages:
Inconvénients
Voici ce qui a fonctionné pour moi encore mieux que l'initialiseur paresseux. J'ai créé un pointeur de niveau objet initialisé à null et j'ai laissé chaque exécuteur l'initialiser. Dans le bloc d'initialisation, vous pouvez avoir du code à exécution unique. Notez que chaque lot de traitement réinitialisera les variables locales mais pas celles au niveau de l'objet.
object Thing1 {
var bigObject : BigObject = null
def main(args: Array[String]) : Unit = {
val sc = <spark/scala magic here>
sc.textFile(infile).map(line => {
if (bigObject == null) {
// this takes a minute but runs just once
bigObject = new BigObject(parameters)
}
bigObject.transform(line)
})
}
}
Cette approche crée exactement un gros objet par exécuteur, plutôt que le seul gros objet par partition d'autres approches.
Si vous placez var bigObject: BigObject = null dans l'espace de noms de la fonction principale, il se comporte différemment. Dans ce cas, il exécute le constructeur bigObject au début de chaque partition (ie. Batch). Si vous avez une fuite de mémoire, cela tuera éventuellement l'exécuteur testamentaire. La collecte des ordures devrait également faire plus de travail.
Voici ce que nous faisons habituellement
définir un client singleton qui fait ce genre de choses pour s'assurer qu'un seul client est présent dans chaque exécuteur
avoir une méthode getorcreate pour créer ou récupérer les informations du client, nous vous proposons généralement une plate-forme de service commune que vous souhaitez servir pour plusieurs modèles différents, puis nous pouvons utiliser comme concurrentmap pour garantir threadsafe et computeifabsent
la méthode getorcreate sera appelée à l'intérieur du niveau RDD comme transform ou foreachpartition, alors assurez-vous que init se produit au niveau de l'exécuteur