Structuration des blocs-notes Jupyter pour des expériences d’apprentissage machine rapides et itératives

Une feuille de triche pour les praticiens de ML occupés qui ont besoin d’exécuter rapidement de nombreuses expériences de modélisation dans un espace de travail Jupyter bien rangé.
Contrairement au monde du logiciel, le terme «composant réutilisable» peut être difficile à appliquer dans le monde de la modélisation. Les expériences sont souvent ponctuelles et peu de codes ont été réutilisés. Si vous êtes un défenseur du code propre qui aime passer du temps à refactoriser chaque ligne de code pour suivre le principe «Ne vous répétez pas» (DRY), vous pourriez facilement passer trop de temps à le faire.
Cependant, je ne suggère pas d’aller à l’opposé du principe «Ne vous répétez pas». J’ai vu des répertoires de cahiers Jupyter très désordonnés et non organisés. Cependant, nous devons nous efforcer de comprendre quels composants devons-nous réutiliser. Dans cet article, je vais mettre en évidence les composants qui ont tendance à être réutilisés dans un projet Machine Learning, sur la base de mon expérience dans le prétraitement et la modélisation de données pendant 2 ans + à l’aide de notebooks Jupyter.
La principale raison pour laquelle nous utilisons Jupyter dans les projets de modélisation est que nous voulons aller vite. Nous voulons exécuter des expériences rapides, échouer rapidement et apprendre rapidement. Le traitement des données prend du temps et la formation en machine learning prend encore plus de temps. Contrairement au monde du logiciel où le «rechargement à chaud» est une chose, nous ne l’avons généralement pas dans le monde de la modélisation. La préparation d’une expérience et de l’expérience elle-même prend beaucoup de temps. Et pour être rapide, nous devons utiliser Jupyter, ce qui nous permet de tester l’exécution sur une petite partie de notre code plutôt que sur le script entier.
Il s’agit d’un processus itératif. Plus vite vous pourrez faire le tour de cette boucle, plus vite vous progresserez.
– Andrew Ng, désir d’apprentissage machine
Maintenant, sachant que nous ne devons pas écrire de scripts au début, mais plutôt utiliser des blocs-notes Jupyter, voyons comment structurer nos projets.
Voici un aperçu de ce que nous allons couvrir dans cet article:
Tout d’abord, avant de commencer à écrire des codes pour nos modèles de traitement et de données, nous devons avoir un ensemble de «petites données» comme données. L’intuition principale est d’avoir un très petit ensemble de données qui peuvent être traitées rapidement. Ce faisant, lorsque nous exécutons notre code pour la première fois, nous n’avons pas à attendre quelques heures avant de savoir qu’il existe un simple bug dans notre code.
Par exemple, si nous espérons former un modèle sur 10 millions d’images, essayez d’échantillonner seulement 50 images par classe pour écrire le code. Ou, si nous formons 100 millions de lignes de données de vente, nous pouvons essayer d’échantillonner 2 000 lignes de données de vente comme «petites données».
La taille des «petites données» dépend de la représentativité de l’échantillon et du temps de traitement. Pour chaque échantillon, essayez d’obtenir au moins 5 échantillons par classe. En termes de temps, une règle empirique est que le temps nécessaire pour que les «petites données» passent du traitement des données à la formation d’un modèle devrait s’exécuter en moins de 10 minutes.
Vous pouvez utiliser le code suivant au début de votre bloc-notes pour basculer SMALL_DATA_MODE
allumé et éteint.
SMALL_DATA_MODE = Trueif SMALL_DATA_MODE:
DATA_FILE = "path/to/smallData.csv"
else:
DATA_FILE = "path/to/originalData.csv"
Au fur et à mesure que vous exécutez de plus en plus d’expériences, il est probable que les anciens codes soient supprimés et remplacés par de nouveaux codes. Créer un nouveau cahier pour juste un petit changement de code est mauvais parce que nous n’en aurons peut-être même plus besoin à l’avenir, cela prendra de l’encombrement dans notre espace de travail.
L’utilisation de git nous aide à contrôler la version de nos ordinateurs portables tout en gardant notre lieu de travail propre. Si nécessaire, vous pouvez toujours revenir à une version plus ancienne en revenant aux validations git précédentes. De plus, nous n’avons pas à nous soucier de la perte de code si nous poussons régulièrement nos codes vers un référentiel distant. Vous pouvez simplement tout envoyer à la branche principale si vous travaillez seul sur ce projet ou pousser à différentes branches si vous faites partie d’une équipe.
Pour installer git,
Sous Windows, accédez à https://git-scm.com/download/win
Sous macOS, exécutez git --version
dans le terminal. Si vous n’avez pas installé git, il vous demandera l’installation.
Sous Linux Ubuntu, exécutez sudo apt install git-all
Après l’installation, exécutez ce qui suit dans votre répertoire de projet
git init
Également, spécifions les fichiers que nous ne voulons pas suivre avec git. Créez un nouveau fichier appelé .gitignore
et mettez les textes suivants dans ce fichier. Nous allons ignorer les points de contrôle Jupyter, le cache python et le répertoire de données.
.ipynb_checkpoints/
data/
__pycache__/
Pour valider, utilisez ce qui suit (cela devrait vous être familier si vous avez déjà rejoint des projets logiciels). Sinon, je recommande de consulter les tutoriels git.
git add .
git commit -m "Give a clear message here on what's changing compared to last time you commit"# remember to set a remote named `origin` for this:
git push origin master
Un projet d’apprentissage automatique comportera généralement plusieurs expériences utilisant les mêmes données et le même modèle. Au lieu de tout enfermer dans un répertoire, une bonne façon est de séparer le Les données, prétraitement, la modélisation, et sortie d’expérience (exp).
Voici ma structure de fichiers préférée:
Ici, je vais montrer ce que nous devons faire lorsque nous exécutons notre code pour la première fois dans des blocs-notes, nous devons démarrer notre bloc-notes avec les paramètres du bloc-notes.
# PARAMETER
#-------------# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
DATA_DIR = './data/temp/' #
RAW_FILE = f'/path/to/smallData.csv' # use "small data" here
PROCESSED_FILE = f'{DATA_DIR}processed.pkl' # always use pickle for fast I/O!
OTHER_PREPROCESS_PARAMETER = ... # e.g. batch size, sliding window size, etc
Le code ci-dessus définit les paramètres par défaut de notre ordinateur portable. Nous n’utiliserons qu’un répertoire temporaire TMP_DIR
(en dessous de data/
répertoire) pour stocker notre sortie. Il s’agit d’assurer une itération rapide. Lorsque nous écrivons du code, nous devons toujours utiliser des «petites données».
Vous pouvez continuer à coder la partie de prétraitement. Puisque vous utilisez des «petites données» pour cette phase de «développement», vous devriez être rapide! Lorsque vous avez terminé le prétraitement, n’oubliez pas de sortir un fichier Pickle à l’aide de pickle
bibliothèque:
import pickle
with open(PROCESSED_FILE, 'wb') as f:
pickle.dump(python_object, f)
Alternativement, en utilisant pandas
»Raccourci:
df.to_pickle(PROCESSED_FILE)
Nous utilisons Pickle au lieu du format CSV pour la persistance et la lecture et l’écriture rapides. Cette PROCESSED_FILE
sera lu par The Modeling Notebook dans la prochaine partie.
Commencez notre modèle de bloc-notes avec ceci:
# PARAMETER
#-------------# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
DATA_DIR = './data/temp/'
EXP_DIR = './exp/temp/'
PROCESSED_FILE = f'{DATA_DIR}processed.pkl'
MODEL_FILE = f'{EXP_DIR}model.pkl'
PREDICTION_FILE = f'{EXP_DIR}ypred.pkl'
OTHER_MODEL_PARAMETERS = ... # like N_ESTIMATOR, GAMMA, etc
Notez que le DATA_DIR
et PROCESSED_FILE
est identique à la sortie précédente du bloc-notes de prétraitement et y est connectée.
Dans ce cahier de modélisation, vous devez faire 3 choses (non représentées ici car elles seront différentes pour chaque modèle):
Le carnet de rapports est rapide, il suffit de lire exp/
annuaire. Son entrée est connectée à la sortie du Modeling Notebook via EXP_DIR
, MODEL_FILE
, et PREDICTION_FILE
.
# PARAMETER
#-------------# check if IS_MASTER exists, this variable will only exist if it's being called by MASTER notebook.
# if it does not exist, set it to False
try: IS_MASTER
except: IS_MASTER = False# The code below will only run if it's NOT being called from MASTER notebook
if IS_MASTER:
EXP_DIR = './exp/temp/'
MODEL_FILE = f'{EXP_DIR}model.pkl'
PREDICTION_FILE = f'{EXP_DIR}ypred.pkl'
Voici où vous prenez les étiquettes prévues et les marquez par rapport aux étiquettes réelles. Vous utiliseriez ici des métriques comme Precision, Recall ou ROC AUC. Vous devez également exécuter les codes des graphiques et des graphiques ici.
Enfin, le MASTER Notebook!
Le bloc-notes Master est le bloc-notes qui appelle tous les autres blocs-notes. Pendant que vous êtes dans ce bloc-notes, vous superviserez l’ensemble du pipeline de données (du prétraitement des données brutes à la modélisation et à la création de rapports).
Le bloc-notes principal est également l’endroit où vous appelez les autres blocs-notes de prétraitement et de modélisation (bien testés) pour exécuter les «mégadonnées» réelles. J’introduirai également une astuce de journalisation (car bon, si « big data » a une erreur, nous voulons savoir pourquoi).
Nous utiliserons également une commande magique Jupyter %run
ici aussi.
Tout d’abord, créez un fichier appelé print_n_log.py
et collez-y le code ci-dessous:
"""
Returns a modified print() method that returns TEE to both stdout and a file
"""
import loggingdef run(logger_name, log_file, stream_level='ERROR'):
stream_level = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
}[stream_level]# create logger with 'logger_name'
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
# create file handler which logs even debug messages
fh = logging.FileHandler(log_file)
fh.setLevel(logging.DEBUG)
# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(stream_level)
# create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# add the handlers to the logger
logger.addHandler(fh)
logger.addHandler(ch)
def modified_print(*args):
s = ' '.join([str(a) for a in args])
logger.info(s)
return modified_print
Le code ci-dessus créera une modification print()
méthode qui redirige la sortie (stdout) et l’erreur (stderr) vers la sortie de la cellule du bloc-notes principal ET un fichier journal.
Ensuite, importez ce module dans votre bloc-notes principal:
import print_n_log
Dans votre cellule suivante, essayons d’appeler le bloc-notes de prétraitement:
# Parameters for Preprocessing Notebook
#---------------------------------------------
FROM_MASTER = True # Remember this? We need to set this to True in MASTER Notebook so that it does not use the default parameters in processing notebook.
RAW_FILE = f'/path/to/smallData.csv' # use "small data" here
PROCESSED_FILE = f'{DATA_DIR}processed.pkl' # always use pickle for fast I/O!
OTHER_PREPROCESS_PARAMETER = ... # like batch size, sliding# Let's save the original print method in ori_print
#---------------------------------------------------
ori_print = print# Now we set the print method to be modified print
#--------------------------------------------------
print = print_n_log.run('preproc', './logs/preprocess.log', 'DEBUG')# Now, we run the Preprocessing Notebook using the %run magic
#-------------------------------------------------------------
%run 'c_preprocess.ipynb'# Finally, after running notebook, we set the print method back to the original print method.
#-----------------------------------------------------
print = ori_print
Notez que nous utilisons %run
magique pour exécuter le bloc-notes de prétraitement. Il s’agit d’une magie IPython qui nous permet d’exécuter d’autres fichiers python et blocs-notes Jupyter à partir de notre bloc-notes actuel. Nous utilisons cette commande pour exécuter tous les codes du bloc-notes de prétraitement.
En appelant print_n_log.run('preproc', './logs/preprocess.log', 'DEBUG')
, nous modifions le Python original intégré print()
pour rediriger la sortie vers l’écran et un fichier journal. 'preproc'
est juste un nom pour notre enregistreur, vous pouvez utiliser n’importe quel autre nom. Après avoir couru, vous pouvez aller à './logs/preproc.log'
pour voir la sortie consignée de l’exécution du bloc-notes de prétraitement. Le dernier paramètre 'DEBUG'
dit simplement « imprimer chaque sortie à l’écran ». Vous pouvez également utiliser 'ERROR'
si vous voulez seulement voir les erreurs sur l’écran (pas de sorties).
Et oui, c’est tout! Vous pouvez suivre le même modèle pour appeler le carnet de modélisation et le carnet de rapports. Cependant, juste une astuce, vous ne voudrez peut-être pas enregistrer la sortie de Reporting Notebook, vous pouvez donc utiliser la méthode print () d’origine pour celui-ci.
L’idée clé ici est de «modulariser» des pièces dans un bloc-notes Jupyter qui n’ont pas beaucoup d’enchevêtrement avec d’autres pièces, et en même temps, d’avoir une vue d’ensemble de l’ensemble du pipeline de données.
Contrairement à la philosophie logicielle de l’encapsulation, nous ne voulons pas réellement encapsuler notre code dans des fichiers python et ne plus les voir (bien que nous puissions le faire pour certaines méthodes utilitaires). Nous voulons conserver la plupart du code dans les cahiers, car lors de la conception d’une expérience, chaque partie doit être modifiable. Lorsqu’une pièce est modifiée, nous voulons également les rendre testables. En restant dans le cahier Jupyter, nous savons que notre code est testable lorsque nous l’exécutons avec des «petites données». En suivant ces directives, nous aurons un projet Jupyter organisé et modulaire, mais chaque partie est facilement modifiable et testable.
Pour être informé de mes publications, suivez-moi sur Medium, Twitter, ou Facebook.