web-dev-qa-db-fra.com

Meilleures pratiques pour transformer les cahiers jupyter en scripts python

Le bloc-notes Jupyter (iPython) est à juste titre connu comme un bon outil pour prototyper le code et faire toutes sortes de choses d'apprentissage automatique de manière interactive. Mais quand je l'utilise, je rencontre inévitablement les éléments suivants:

  • le cahier devient rapidement trop complexe et désordonné pour être maintenu et amélioré en tant que cahier, et je dois en faire des scripts python;
  • en ce qui concerne le code de production (par exemple, qui doit être réexécuté tous les jours), le bloc-notes n'est pas le meilleur format.

Supposons que j'ai développé un pipeline complet d'apprentissage automatique dans jupyter qui comprend la récupération de données brutes à partir de diverses sources, le nettoyage des données, l'ingénierie des fonctionnalités et les modèles de formation après tout. Maintenant, quelle est la meilleure logique pour en faire des scripts avec un code efficace et lisible? Jusqu'à présent, je l'avais abordé de plusieurs manières:

  1. Convertissez simplement .ipynb en .py et, avec seulement de légères modifications, codez en dur tout le pipeline du bloc-notes en un seul script python.

    • '+': rapide
    • '-': sale, non flexible, peu pratique à entretenir
  2. Créez un script unique avec de nombreuses fonctions (environ, 1 fonction pour chaque une ou deux cellules), en essayant de comprendre les étapes du pipeline avec des fonctions distinctes, et nommez-les en conséquence. Spécifiez ensuite tous les paramètres et constantes globales via argparse.

    • '+': utilisation plus flexible; code plus lisible (si vous avez correctement transformé la logique du pipeline en fonctions)
    • '-': souvent, le pipeline n'est PAS divisible en morceaux complétés logiquement qui pourraient devenir des fonctions sans aucune bizarrerie dans le code. Toutes ces fonctions doivent généralement être appelées une seule fois dans le script plutôt que plusieurs fois à l'intérieur de boucles, de cartes, etc. fonction.
  3. La même chose que point (2), mais maintenant envelopper toutes les fonctions à l'intérieur de la classe. Maintenant, toutes les constantes globales, ainsi que les sorties de chaque méthode peuvent être stockées en tant qu'attributs de classe.

    • '+': vous n'avez pas besoin de passer de nombreux arguments à chaque méthode - toutes les sorties précédentes sont déjà stockées en tant qu'attributs
    • '-': la logique globale d'une tâche n'est toujours pas capturée - c'est un pipeline de données et d'apprentissage machine, pas seulement la classe. Le seul objectif de la classe est d'être créé, d'appeler toutes les méthodes séquentiellement une par une, puis d'être supprimé. De plus, les classes sont assez longues à implémenter.
  4. Convertir un cahier en module python avec plusieurs scripts. Je n'ai pas essayé cela, mais je soupçonne que c'est le plus long moyen de résoudre le problème.

Je suppose que ce paramètre général est très courant chez les scientifiques des données, mais étonnamment, je ne trouve aucun conseil utile.

Mes amis, partagez vos idées et votre expérience. Avez-vous déjà rencontré ce problème? Comment l'avez-vous abordé?

41
kurtosis

Économiseur de vie : pendant que vous écrivez vos cahiers, refactorisez progressivement votre code en fonctions, en écrivant quelques tests et docstrings assert minimes.

Après cela, la refactorisation du cahier au script est naturelle. Non seulement cela, mais cela vous facilite la vie lorsque vous écrivez de longs cahiers, même si vous n'avez pas l'intention de les transformer en autre chose.

Exemple de base du contenu d'une cellule avec des tests et des docstrings "minimaux":

def Zip_count(f):
    """Given Zip filename, returns number of files inside.

    str -> int"""
    from contextlib import closing
    with closing(zipfile.ZipFile(f)) as archive:
        num_files = len(archive.infolist())
    return num_files

Zip_filename = 'data/myfile.Zip'

# Make sure `myfile` always has three files
assert Zip_count(Zip_filename) == 3
# And total Zip size is under 2 MB
assert os.path.getsize(Zip_filename) / 1024**2 < 2

print(Zip_count(Zip_filename))

Une fois que vous l'avez exporté vers le nu .py fichiers, votre code ne sera probablement pas encore structuré en classes. Mais cela vaut la peine d'avoir refactorisé votre bloc-notes au point qu'il possède un ensemble de fonctions documentées, chacune avec un ensemble d'instructions assert simples qui peuvent facilement être déplacées dans tests.py pour tester avec pytest, unittest, ou quoi d'autre. Si cela a du sens, regrouper ces fonctions dans des méthodes pour vos classes est très simple après cela.

Si tout se passe bien, tout ce que vous devez faire après cela est d'écrire votre if __name__ == '__main__': et ses "hooks": si vous écrivez un script à appeler par le terminal, vous voudrez gérer les arguments de la ligne de commande , si vous écrivez un module, vous ' Je veux penser à son API avec le __init__.py fichier , etc.

Tout dépend bien sûr du cas d'utilisation prévu: il y a une grande différence entre convertir un ordinateur portable en petit script et le transformer en un module ou un package à part entière.

Voici quelques idées pour un flux de travail bloc-notes vers script :

  1. Exportez le bloc-notes Jupyter vers le fichier Python (.py) via l'interface graphique).
  2. Supprimez les lignes "d'assistance" qui ne font pas le travail réel: print instructions, tracés, etc.
  3. Si besoin est, regroupez votre logique en classes. Le seul travail de refactorisation supplémentaire requis devrait être d'écrire vos docstrings et attributs de classe.
  4. Écrivez les entrées de votre script avec if __name__ == '__main__'.
  5. Séparez vos instructions assert pour chacune de vos fonctions/méthodes et étoffez une suite de tests minimale dans tests.py.
11
François Leblanc

Nous avons le même problème. Cependant, nous utilisons plusieurs cahiers pour prototyper les résultats qui devraient devenir aussi plusieurs scripts python après tout).

Notre approche consiste à mettre de côté le code, qui semble se répéter sur ces blocs-notes. Nous l'avons mis dans le module python, qui est importé par chaque ordinateur portable et également utilisé dans la production. Nous améliorons ce module de manière itérative en continu et ajoutons des tests de ce que nous trouvons pendant le prototypage.

Les carnets deviennent alors un peu comme les scripts de configuration (que nous copions simplement dans la fin des fichiers python) et plusieurs vérifications et validations de prototypage, dont nous n'avons pas besoin dans la production.

Surtout nous n'avons pas peur du refactoring :)

11
Radek

J'ai récemment créé un module ( NotebookScripter ) pour aider à résoudre ce problème. Il vous permet d'appeler un ordinateur portable jupyter via un appel de fonction. C'est aussi simple à utiliser que

from NotebookScripter import run_notebook
run_notebook("./path/to/Notebook.ipynb", some_param="Provided Exteranlly")

Les paramètres des mots clés peuvent être transmis à l'appel de fonction. Il est facile d'adapter un ordinateur portable pour être paramétrable en externe.

Dans une cellule .ipynb

from NotebookScripter import receive_parameter

some_param = receive_parameter(some_param="Return's this value by default when matching keyword not provided by external caller")

print("some_param={0} within the invocation".format(some_param))

run_notebook () prend en charge les fichiers .ipynb ou les fichiers .py - ce qui permet d'utiliser facilement les fichiers .py qui pourraient être générés par nbconvert d'ipython de vscode. Vous pouvez garder votre code organisé d'une manière qui a un sens pour une utilisation interactive, et également le réutiliser/le personnaliser en externe en cas de besoin.

2
Ben

Vous devez décomposer la logique en petites étapes, de cette façon, votre pipeline sera plus facile à entretenir. Puisque vous avez déjà une base de code fonctionnelle, vous voulez garder votre code en cours d'exécution, alors faites de petites modifications, testez et répétez.

J'irais comme ça:

  1. Ajoutez des tests à votre pipeline, pour les pipelines ML, c'est un peu difficile, mais si votre ordinateur portable forme un modèle, vous pouvez utiliser des mesures de performances pour tester si votre pipeline fonctionne toujours (votre test peut être de précision = 0,8, mais assurez-vous de définir une plage tolérable car le nombre n'est pas exactement le même pour chaque série)
  2. Divisez votre ordinateur portable en plusieurs plus petits, la sortie de l'un devrait être l'entrée de l'autre. Dès que vous créez un fractionnement, assurez-vous d'ajouter quelques tests pour chaque bloc-notes individuellement. Pour gérer cette exécution séquentielle, vous pouvez utiliser papermill pour exécuter vos cahiers ou un outil de gestion de workflow tel que ploomber qui s'intègre à papermill, est capable de résoudre des dépendances complexes et possède un crochet pour exécuter des tests lors de l'exécution du bloc-notes (Avertissement: je suis l'auteur du ploomber)
  3. Une fois que vous avez un pipeline composé de plusieurs cahiers qui réussit tous vos tests, vous pouvez décider si vous souhaitez continuer à utiliser le format ipynb ou non. Ma recommandation serait de ne garder que comme cahiers les tâches qui ont une sortie riche (comme les tableaux ou les tracés), le reste peut être refactorisé en fonctions Python, qui sont plus maintenables
1
Edu