Application
Appliquer pas à pas les concepts étudiés à un projet de data science
Une application fil rouge pour illustrer l’intérêt d’appliquer graduellement les bonnes pratiques dans une optique de mise en production d’une application de data science.
Dérouler les slides ci-dessous ou cliquer ici pour afficher les slides en plein écran.
L’objectif de cette mise en application est d’illustrer les différentes étapes qui séparent la phase de développement d’un projet de celle de la mise en production. Elle permettra de mettre en pratique les différents concepts présentés tout au long du cours.
Celle-ci est un tutoriel pas à pas pour avoir un projet reproductible et disponible sous plusieurs livrables. Toutes les étapes ne sont pas indispensables à tous les projets de data science.
Nous nous plaçons dans une situation initiale correspondant à la fin de la phase de développement d’un projet de data science. On a un notebook un peu monolithique, qui réalise les étapes classiques d’un pipeline de machine learning :
- Import de données ;
- Statistiques descriptives et visualisations ;
- Feature engineering ;
- Entraînement d’un modèle ;
- Evaluation du modèle
L’objectif est d’améliorer le projet de manière incrémentale jusqu’à pouvoir le mettre en production, en le valorisant sous une forme adaptée.
Il est important de bien lire les consignes et d’y aller progressivement. Certaines étapes peuvent être rapides, d’autres plus fastidieuses ; certaines être assez guidées, d’autres vous laisser plus de liberté. Si vous n’effectuez pas une étape, vous risquez de ne pas pouvoir passer à l’étape suivante qui en dépend.
Bien que l’exercice soit applicable sur toute configuration bien faite, nous recommandons de privilégier l’utilisation du SSP Cloud, où tous les outils nécessaires sont pré-installés et pré-configurés.
Illustration de notre point de départ

Illustration de l’horizon vers lequel on se dirige

Partie 0 : initialisation du projet
Etape 1 : forker le dépôt d’exemple et créer une branche de travail
Ouvrir un service
VSCode
sur le SSP Cloud. Vous pouvez aller dans la pageMy Services
et cliquer surNew service
. Sinon, vous pouvez lancer le service en cliquant directement ici.Générer un jeton d’accès (token) sur
GitHub
afin de permettre l’authentification en ligne de commande à votre compte. La procédure est décrite ici. Garder le jeton généré de côté.Forker le dépôt
Github
: https://github.com/ensae-reproductibilite/application-correctionClôner votre dépôt
Github
en utilisant le terminal depuisVisual Studio
(Terminal > New Terminal
) :
$ git clone https://<TOKEN>@github.com/<USERNAME>/ensae-reproductibilite-application-correction.git
où <TOKEN>
et <USERNAME>
sont à remplacer, respectivement, par le jeton que vous avez généré précédemment et votre nom d’utilisateur.
- Se placer avec le terminal dans le dossier en question :
$ cd ensae-reproductibilite-application-correction
Partie 1 : qualité du script
Cette première partie vise à rendre le projet conforme aux bonnes pratiques présentées dans le cours.
Elle fait intervenir les notions suivantes :
- Utilisation du terminal (voir Linux 101) ;
- Qualité du code (voir Qualité du code) ;
- Architecture de projets (voir Architecture des projets) ;
- Contrôle de version avec
Git
(voir RappelsGit
) ; - Travail collaboratif avec
Git
etGitHub
(voir RappelsGit
).
Le plan de la partie est le suivant :
Nous allons partir de ce Notebook Jupyter
, que vous pouvez prévisualiser voire tester en cliquant sur l’un des liens suivants:
Etape 1 : s’assurer que le script s’exécute correctement
On va partir du fichier notebook.py
qui reprend le contenu du notebook1 mais dans un script classique.
La première étape est simple, mais souvent oubliée : vérifier que le code fonctionne correctement.
- Ouvrir dans
VSCode
le scripttitanic.py
; - Exécuter le script ligne à ligne pour détecter les erreurs ;
- Corriger les deux erreurs qui empêchent la bonne exécution ;
- Vérifier le fonctionnement du script en utilisant la ligne de commande
python titanic.py
Il est maintenant temps de commit les changements effectués avec Git
2 :
$ git add titanic.py
$ git commit -m "Corrige l'erreur qui empêchait l'exécution"
$ git push
Etape 2: utiliser un linter puis un formatter
On va maintenant améliorer la qualité de notre code en appliquant les standards communautaires. Pour cela, on va utiliser le linter classique PyLint
.
N’hésitez pas à taper un code d’erreur sur un moteur de recherche pour obtenir plus d’informations si jamais le message n’est pas clair !
Pour appliquer le linter à un script .py
, la syntaxe à entrer dans le terminal est la suivante :
$ pylint mon_script.py
Le linter renvoie alors une série d’irrégularités, en précisant à chaque fois la ligne de l’erreur et le message d’erreur associé (ex : mauvaise identation). Il renvoie finalement une note sur 10, qui estime la qualité du code à l’aune des standards communautaires évoqués dans la partie Qualité du code.
- Diagnostiquer et évaluer la qualité de
titanic.py
avecPyLint
. Regarder la note obtenue. - Utiliser
black titanic.py --diff --color
pour observer les changements de forme que va induire l’utilisation du formatterBlack
- Appliquer le formatter
Black
- Réutiliser
PyLint
pour diagnostiquer l’amélioration de la qualité du script et le travail qui reste à faire. - Comme la majorité du travail restant est à consacrer aux imports:
- Délimiter des parties dans votre code pour rendre sa structure plus lisible
Le code est maintenant lisible, il obtient à ce stade une note formelle proche de 10. Mais il n’est pas encore totalement intelligible ou fiable. Il y a notamment beaucoup de redondance de code auxquelles nous allons nous attaquer par la suite. Néanmoins, avant cela, occupons-nous de mieux gérer certains paramètres du script: jetons d’API et chemin des fichiers.
Etape 3: gestion des paramètres
L’exécution du code et les résultats obtenus dépendent de certains paramètres. L’étude de résultats alternatifs, en jouant sur des variantes des paramètres, est à ce stade compliquée car il est nécessaire de parcourir le code pour trouver ces paramètres. De plus, certains paramètres personnels comme des jetons d’API ou des mots de passe n’ont pas vocation à être présents dans le code.
Il est plus judicieux de considérer ces paramètres comme des variables d’entrée du script. Cela peut être fait de deux manières:
- Avec des arguments optionnels appelés depuis la ligne de commande. Cela peut être pratique pour mettre en oeuvre des tests automatisés3 mais n’est pas forcément pertinent pour toutes les variables. Nous allons montrer cet usage avec le nombre d’arbres de notre random forest ;
- En utilisant un fichier de configuration dont les valeurs sont importées dans le script principal. Nous allons le mettre en oeuvre pour deux types de fichiers: les éléments de configuration à partager et ceux à conserver pour soi mais pouvant servir.
- En s’inspirant de cette réponse, créer une variable
n_trees
qui peut éventuellement être paramétrée en ligne de commande et dont la valeur par défaut est 20. - Tester cette paramétrisation en ligne de commande avec la valeur par défaut puis 2, 10 et 50 arbres
- Repérer le jeton d’API dans le code. Retirer le jeton d’API du code et créer à la racine du projet un fichier YAML nommé
secrets.yaml
où vous écrivez ce secret sous la formekey: value
- Pour éviter d’avoir à le faire plus tard, créer une fonction
import_yaml_config
qui prend en argument le chemin d’un fichierYAML
et renvoie le contenu de celui-ci en output. Vous pouvez suivre le conseil du chapitre sur la Qualité du code en adoptant le type hinting. - Créer la variable
API_TOKEN
ayant la valeur stockée danssecrets.yaml
4. - Tester en ligne de commande que l’exécution du fichier est toujours sans erreur
- Refaire un diagnostic avec
PyLint
et corriger les éventuels messages. - Créer un fichier
config.yaml
stockant trois informations: le chemin des données d’entraînement, des données de test et la répartition train/test utilisée dans le code. Créer les variables correspondantes dans le code après avoir utiliséimport_yaml_config
- Créer un fichier
.gitignore
. Ajouter dans ce fichiersecrets.yaml
car il ne faut pas committer ce fichier. - Créer un fichier
README.md
où vous indiquez qu’il faut créer un fichiersecrets.yaml
pour pouvoir utiliser l’API.
Indice si vous ne trouvez pas comment lire un fichier YAML
Si le fichier s’appelle toto.yaml
, vous pouvez l’importer de cette manière:
with open("toto.yaml", "r", encoding="utf-8") as stream:
= yaml.safe_load(stream) dict_config
Etape 4 : Adopter la programmation fonctionnelle
Nous allons mettre en fonctions les parties importantes de l’analyse, et les mettre dans un module afin de pouvoir les importer directement depuis le notebook.
Cet exercice étant chronophage, il n’est pas obligatoire de le réaliser en entier. L’important est de comprendre la démarche et d’adopter fréquemment une approche fonctionnelle5. Pour obtenir une chaine entièrement fonctionnalisée, vous pouvez reprendre le checkpoint.
- Créer une fonction qui importe les données d’entraînement (
train.csv
) et de test (test.csv
) et renvoie desDataFrames
Pandas
; - En fonction du temps disponible, créer plusieurs fonctions pour réaliser les étapes de feature engineering:
- La création de la variable “Title” peut être automatisée en vertu du principe “do not repeat yourself”6.
- Regrouper ensemble les
fillna
et essayer de créer une fonction généralisant l’opération. - Les label encoders peuvent être transformés en deux fonctions: une première pour encoder une colonne puis une seconde qui utilise la première de manière répétée pour encoder plusieurs colonnes. Remarquez les erreurs de copier-coller que cela corrige
- Finaliser les dernières transformations avec des fonctions
- Créer une fonction qui réalise le split train/test de validation en fonction d’un paramètre représentant la proportion de l’échantillon de test.
- Créer une fonction qui entraîne et évalue un classifieur
RandomForest
, et qui prend en paramètre le nombre d’arbres (n_estimators
). La fonction doit imprimer à la fin la performance obtenue et la matrice de confusion. - Déplacer toutes les fonctions ensemble, en début de script.
Le fait d’appliquer des fonctions a déjà amélioré la fiabilité du processus en réduisant le nombre d’erreurs de copier-coller. Néanmoins, pour vraiment fiabiliser le processus, il faudrait utiliser un pipeline de transformations de données.
Ceci n’est pas encore au programme du cours mais le sera dans une prochaine version.
git checkout appli4
ou
Les autres fichiers inchangés:
Partie 2 : adoption d’une structure modulaire
Dans la partie précédente, on a appliqué de manière incrémentale de nombreuses bonnes pratiques vues tout au long du cours. Ce faisant, on s’est déjà considérablement rapprochés d’un possible partage du code : celui-ci est lisible et intelligible. Le code est proprement versionné sur un dépôt GitHub
.
Illustration de l’état actuel du projet

Néanmoins, la structure du projet n’est pas encore normalisée. De plus, l’adoption d’une structure plus modulaire facilitera la compréhension de la chaine de traitement.
Etape 1 : modularisation
Fini le temps de l’expérimentation : on va maintenant essayer de se passer complètement du notebook. Pour cela, on va utiliser un main
script, c’est à dire un script qui reproduit l’analyse en important et en exécutant les différentes fonctions dans l’ordre attendu.
- Déplacer les fonctions dans une série de fichiers dédiés:
import_data.py
: fonctions d’import de donnéesbuild_features.py
: fonctions regroupant les étapes de feature engineeringtrain_evaluate.py
: fonctions d’entrainement et d’évaluation du modèle
- Spécifier les dépendances (i.e. les packages à importer) dans les modules pour que ceux-ci puissent s’exécuter indépendamment ;
- Renommer
titanic.py
enmain.py
pour suivre la convention de nommage des projetsPython
; - Importer les fonctions nécessaires à partir des modules. ⚠️ Ne pas utiliser
from XXX import *
, ce n’est pas une bonne pratique ! - Vérifier que tout fonctionne bien en exécutant le script
main
à partir de la ligne de commande :
$ python main.py
On dispose maintenant d’une application Python
fonctionnelle. Néanmoins, le projet est certes plus fiable mais sa structuration laisse à désirer et il serait difficile de rentrer à nouveau dans le projet dans quelques temps.
Etat actuel du projet 🙈
├── README.md
├── train.csv
├── test.csv
├── .gitignore
├── config.yaml
├── secrets.yaml
├── import_data.py
├── build_features.py
├── train_evaluate.py
└──main.py
Comme cela est expliqué dans la partie Structure des projets, on va adopter une structure certes arbitraire mais qui va faciliter l’autodocumentation de notre projet.
De plus, une telle structure va faciliter des évolutions optionnelles comme la packagisation du projet. Passer d’une structure modulaire bien faite à un package est quasi-immédiat en Python
.
git checkout appli5
ou
Les autres fichiers inchangés:
Etape 2 : adopter une architecture standardisée de projet
On va maintenant modifier l’architecture de notre projet pour la rendre plus standardisée. Pour cela, on va s’inspirer des structures cookiecutter
qui génèrent des templates de projet.
On va s’inspirer de la structure du template datascience développé par la communauté.
L’idée de cookiecutter
est de proposer des templates que l’on utilise pour initialiser un projet, afin de bâtir à l’avance une structure évolutive. La syntaxe à utiliser dans ce cas est la suivante :
$ pip install cookiecutter
$ cookiecutter https://github.com/drivendata/cookiecutter-data-science
Ici, on a déjà un projet, on va donc faire les choses dans l’autre sens : on va s’inspirer de la structure proposée afin de réorganiser celle de notre projet selon les standards communautaires.
En s’inspirant du cookiecutter data science on va adopter la structure suivante:
ensae-reproductibilite-application
├── main.py
├── README.md
├── data
│ └── raw
│ ├── test.csv
│ └── train.csv
├── configuration
│ ├── secrets.yaml
│ └── config.yaml
├── notebooks
│ └── titanic.ipynb
└── src
├── data
│ └── import_data.py
├── features
│ └── build_features.py
└── models
└── train_evaluate.py
- (optionnel) Analyser et comprendre la structure de projet proposée par le template
- Modifier l’arborescence du projet selon le modèle
- Adapter les scripts et les fichiers de configuration à la nouvelle arborescence
- Ajouter le dossier pycache au
.gitignore
7 et le dossierdata
git checkout appli6
ou
Les autres fichiers sont inchangés, à l’exception de leur emplacement.
Etape 3: indiquer l’environnement minimal de reproductibilité
Le script main.py
nécessite un certain nombre de packages pour être fonctionnel. Chez vous les packages nécessaires sont bien sûr installés mais êtes-vous assuré que c’est le cas chez la personne qui testera votre code ?
Afin de favoriser la portabilité du projet, il est d’usage de “fixer l’environnement”, c’est-à-dire d’indiquer dans un fichier toutes les dépendances utilisées ainsi que leurs version. Nous proposons de créer un fichier requirements.txt
minimal, sur lequel nous reviendrons dans la partie consacrée aux environnements reproductibles.
Le fichier requirements.txt
est conventionnellement localisé à la racine du projet. Ici on ne va pas fixer les versions, on raffinera ce fichier plus tard.
requirements.txt
- Créer un fichier
requirements.txt
avec la liste des packages nécessaires - Ajouter une indication dans
README.md
sur l’installation des packages grâce au fichierrequirements.txt
Etape 3 : stocker les données de manière externe
Pour mettre en oeuvre cette étape, il peut être utile de comprendre un peu comme fonctionne le SSP Cloud. Vous devrez suivre la documentation du SSP Cloud pour la réaliser. Une aide-mémoire est également disponible dans le cours de 2e année de l’ENSAE Python pour la data science
Comme on l’a vu dans le cours (partie structure des projets), les données ne sont pas censées être versionnées sur un projet Git
.
L’idéal pour éviter cela tout en maintenant la reproductibilité est d’utiliser une solution de stockage externe. On va utiliser pour cela MinIO
, la solution de stockage de type S3
offerte par le SSP Cloud.
A partir de la ligne de commande, utiliser l’utilitaire MinIO pour copier les données data/raw/train.csv
et data/raw/test.csv
vers votre bucket personnel, respectivement dans les dossiers ensae-reproductibilite/data/raw/train.csv
et ensae-reproductibilite/data/raw/test.csv
.
Indice
Structure à adopter:
$ mc cp data/raw/train.csv s3/<BUCKET_PERSONNEL>/ensae-reproductibilite/data/raw/train.csv
$ mc cp data/raw/test.csv s3/<BUCKET_PERSONNEL>/ensae-reproductibilite/data/raw/test.csv
en modifiant l’emplacement de votre bucket personnel
- Pour se simplifier la vie, on va utiliser des URL de téléchargement des fichiers (comme si ceux-ci étaient sur n’importe quel espace de stockage) plutôt que d’utiliser une librairie
S3
compatible commeboto3
ous3fs
. Pour cela, en ligne de commande, faire:
mc anonymous set download s3/<BUCKET_PERSONNEL>/ensae-reproductibilite/data/raw/
en modifiant <BUCKET_PERSONNEL>
. Les URL de téléchargement seront de la forme https://minio.lab.sspcloud.fr/<BUCKET_PERSONNEL>/ensae-reproductibilite/data/raw/test.csv
et https://minio.lab.sspcloud.fr/<BUCKET_PERSONNEL>/ensae-reproductibilite/data/raw/train.csv
- Modifier
configuration.yaml
pour utiliser directement les URL dans l’import - Supprimer les fichiers
.csv
du dossierdata
de votre projet, on n’en a plus besoin vu qu’on les importe de l’extérieur - Vérifier le bon fonctionnement de votre application
Partie 2bis: packagisation de son projet (optionnel)
Cette série d’actions n’est pas forcément pertinente pour tous les projets. Elle fait un peu la transition entre la modularité et la portabilité.
Etape 1 : proposer des tests unitaires (optionnel)
Notre code comporte un certain nombre de fonctions génériques. On peut vouloir tester leur usage sur des données standardisées, différentes de celles du Titanic.
Même si la notion de tests unitaires prend plus de sens dans un package, nous pouvons proposer dans le projet des exemples d’utilisation de la fonction, ceci peut être pédagogique.
Nous allons utiliser unittest
pour effectuer des tests unitaires. Cette approche nécessite une maîtrise de la programmation orientée objet.
Dans le dossier src/data/
, créer un fichier test_create_variable_title.py
8.
En s’inspirant de l’exemple de base, créer une classe TestCreateVariableTitle
qui effectue les opérations suivantes:
Création d’une fonction
test_create_variable_title_default_variable_name
qui permet de comparer les objets suivants:- Création d’un
DataFrame
de test :
= pd.DataFrame({ df 'Name': ['Braund, Mr. Owen Harris', 'Cumings, Mrs. John Bradley (Florence Briggs Thayer)', 'Heikkinen, Miss. Laina', 'Futrelle, Mrs. Jacques Heath (Lily May Peel)', 'Allen, Mr. William Henry', 'Moran, Mr. James', 'McCarthy, Mr. Timothy J', 'Palsson, Master. Gosta Leonard', 'Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)', 'Nasser, Mrs. Nicholas (Adele Achem)'], 'Age': [22, 38, 26, 35, 35, 27, 54, 2, 27, 14], 'Survived': [0, 1, 1, 1, 0, 0, 0, 0, 1, 1] })
- Utilisation de la fonction
create_variable_title
sur ceDataFrame
- Comparaison au
DataFrame
attendu:
= pd.DataFrame({ expected_result 'Title': ['Mr.', 'Mrs.', 'Miss.', 'Mrs.', 'Mr.', 'Mr.', 'Mr.', 'Master.', 'Mrs.', 'Mrs.'], 'Age': [22, 38, 26, 35, 35, 27, 54, 2, 27, 14], 'Survived': [0, 1, 1, 1, 0, 0, 0, 0, 1, 1] })
- Création d’un
Effectuer le test unitaire en ligne de commande avec
unittest
. Corriger le test unitaire en cas d’erreur.Si le temps le permet, proposer des variantes pour tenir compte de paramètres (comme la variable
variable_name
) ou d’exceptions (comme la gestion du cas “Dona”)
Lorsqu’on effectue des tests unitaires, on cherche généralement à tester le plus de lignes possibles de son code. On parle de taux de couverture (coverage rate) pour désigner la statistique mesurant cela.
Cela peut s’effectuer de la manière suivante avec le package coverage
:
$ coverage run -m pytest test_create_variable_title.py
$ coverage report -m
Name Stmts Miss Cover Missing
-------------------------------------------------------------
import_data.py 15 6 60% 16-19, 31-34
test_create_variable_title.py 21 1 95% 54
-------------------------------------------------------------
TOTAL 36 7 81%
Le taux de couverture est souvent mis en avant par les gros projets comme indicateur de leur qualité. Il existe d’ailleurs des badges Github
dédiés.
Etape 2 : transformer son projet en package (optionnel)
Notre projet est modulaire, ce qui le rend assez simple à transformer en package, en s’inspirant du cookiecutter
adapté, issu de cet ouvrage.
Structure visée
ensae-reproductibilite-application
├── docs ┐
│ ├── main.py │
│ └── notebooks │ Package documentation and examples
│ ├── titanic.ipynb │
├── README.md ┘
├── pyproject.toml ┐
├── requirements.txt │
├── src │
│ └── titanicml │ Package source code, metadata,
│ ├── __init__.py │ and build instructions
│ ├── config.yaml │
│ ├── import_data.py │
│ ├── build_features.py │
│ └── train_evaluate.py ┘
└── tests ┐
└── test_create_variable_title.py ┘ Package tests
Rappel: structure actuelle
ensae-reproductibilite-application
├── notebooks
│ └── titanic.ipynb
├── configuration
│ └── config.yaml
├── main.py
├── README.md
├── requirements.txt
└── src
├── data
│ ├── import_data.py
│ └── test_create_variable_title.py
├── features
│ └── build_features.py
└── models
└── train_evaluate.py
- Déplacer les fichiers dans le dossier
src
pour respecter la nouvelle arborescence ; - Dans
src/titanicml
, créer un fichier vide__init__.py
9 ; - Déplacer le fichier de configuration dans le package (nécessaire à la reproductibilité) ;
- Créer le dossier
docs
et mettre les fichiers indiqués dedans - Modifier
src/titanicml/import_data.py
:- Ajouter la variable
config_file = os.path.join(os.path.dirname(__file__), "config.yaml")
. Cela permettra d’utiliser directement le fichier ; - Proposer un argument par défaut à la fonction
import_config_yaml
égal àconfig_file
- Ajouter la variable
- Créer un fichier
pyproject.toml
à partir du contenu de ce modèle depyproject
10 - Installer le package en local avec
pip install .
- Modifier le contenu de
docs/main.py
pour importer les fonctions de notre packagetitanicml
et tester en ligne de commande notre fichiermain.py
Pour créer la structure minimale d’un package, le plus simple est d’utiliser le cookiecutter
adapté, issu de cet ouvrage.
Comme on a déjà une structure très modulaire, on va plutôt recréer cette structure dans notre projet déjà existant. En fait, il ne manque qu’un fichier essentiel, le principal distinguant un projet classique d’un package : pyproject.toml
.
cookiecutter https://github.com/py-pkgs/py-pkgs-cookiecutter.git
Dérouler pour voir les choix possibles
author_name [Monty Python]: Daffy Duck
package_name [mypkg]: titanicml
package_short_description []: Impressive Titanic survival analysis
package_version [0.1.0]:
python_version [3.9]:
Select open_source_license:
1 - MIT
2 - Apache License 2.0
3 - GNU General Public License v3.0
4 - Creative Commons Attribution 4.0
5 - BSD 3-Clause
6 - Proprietary
7 - None
Choose from 1, 2, 3, 4, 5, 6 [1]:
Select include_github_actions:
1 - no
2 - ci
3 - ci+cd
Choose from 1, 2, 3 [1]:
git checkout appli10
Partie 3 : construction d’un projet portable et reproductible
Dans la partie précédente, on a appliqué de manière incrémentale de nombreuses bonnes pratiques vues dans les chapitres Qualité du code et Structure des projets tout au long du cours.
Ce faisant, on s’est déjà considérablement rapprochés d’une possible mise en production : le code est lisible, la structure du projet est normalisée et évolutive, et le code est proprement versionné sur un dépôt GitHub
.
Illustration de l’état actuel du projet

A présent, nous avons une version du projet qui est largement partageable. Du moins en théorie, car la pratique est souvent plus compliquée : il y a fort à parier que si vous essayez d’exécuter votre projet sur un autre environnement (typiquement, votre ordinateur personnel), les choses ne se passent pas du tout comme attendu. Cela signifie qu’en l’état, le projet n’est pas portable : il n’est pas possible, sans modifications coûteuses, de l’exécuter dans un environnement différent de celui dans lequel il a été développé.
Dans cette seconde partie, nous allons voir comment normaliser l’environnement d’exécution afin de produire un projet portable. Autrement dit, nous n’allons plus nous contenter de modularité mais allons rechercher la portabilité. On sera alors tout proche de pouvoir mettre le projet en production. On progressera dans l’échelle de la reproductibilité de la manière suivante:
- :one: Environnements virtuels ;
- :two: Créer un script shell qui permet, depuis un environnement minimal, de construire l’application de A à Z ;
- :three: Images et conteneurs
Docker
.
Nous allons repartir de l’application 8, c’est-à-dire d’un projet modulaire mais qui n’est pas, à strictement parler, un package (objet des applications optionnelles suivantes 9 et 10).
Pour se replacer dans l’état du projet à ce niveau, il est possible d’utiliser le tag ad hoc.
git checkout appli8
Etape 1 : un environnement pour rendre le projet portable
Pour qu’un projet soit portable, il doit remplir deux conditions:
- Ne pas nécessiter de dépendance qui ne soient pas renseignées quelque part
- Ne pas proposer des dépendances inutiles, qui ne sont pas utilisées dans le cadre du projet.
L’approche la plus légère est l’environnement virtuel. Nous avons en fait implicitement déjà commencé à aller vers cette direction en créant un fichier requirements.txt
.
venv
Exécuter
pip freeze
en ligne de commande et observer la (très) longue liste de packageCréer l’environnement virtuel
titanic
en s’inspirant de la documentation officielle11Utiliser
ls
pour observer et comprendre le contenu du dossiertitanic/bin
installéActiver l’environnement et vérifier l’installation de
Python
maintenant utilisée par votre machineVérifier directement depuis la ligne de commande que
Python
exécute bien une commande avec:python -c "print('Hello')"
Faire la même chose mais avec
import pandas as pd
Installer les packages à partir du
requirements.txt
. Tester à nouveauimport pandas as pd
pour comprendre la différence.Exécuter
pip freeze
et comprendre la différence avec la situation précédente.Vérifier que le script
main.py
fonctionne bien. Sinon ajouter les packages manquants dans lerequirements.txt
et reprendre de manière itérative à partir de la question 7Ajouter le dossier
titanic/
au.gitignore
pour ne pas ajouter ce dossier àGit
git checkout appli11a
Les environnements conda
sont plus lourds à mettre en oeuvre que les environnements virtuels mais peuvent permettre un contrôle plus formel des dépendances.
conda
est à la fois un gestionnaire de packages (alternative à pip
) et d’environnements virtuels. L’inconvénient de l’utilisation de conda
pour gérer les environnements virtuels est que cet outil est assez lent car l’algorithme de vérification des conflits de version n’est pas extrêmement rapide.
Pour cette raison, nous allons utiliser mamba
, un utilitaire de gestion des environnements conda
qui est plus rapide.
conda
Exécuter
conda env export
en ligne de commande et observer la (très) longue liste de packageTester l’utilisation d’un package qu’on n’utilise pas dans notre chaine de production, par exemple
seaborn
:python -c "import seaborn as sns"
Créer un environnement
titanic
avecmamba create
en listant les packages que vous aviez mis dans lerequirements.txt
et en ajoutant l’option-c conda-forge
à la fin pour utiliser la conda forgeActiver l’environnement et vérifier l’installation de
Python
maintenant utilisée par votre machine(optionnel) Utiliser
ls
dans le dossier parent dePython
pour observer et comprendre le contenu de celui-ciVérifier que cette fois
seaborn
n’est pas installé dans l’environnement :python -c "import seaborn as sns"
Exécuter à nouveau
conda env export
et comprendre la différence avec la situation précédente12.Vérifier que le script
main.py
fonctionne bien. Sinon utilisermamba install
avec les packages manquants jusqu’à ce que la chaine de production fonctionneCréer le fichier
environment.yaml
à partir deconda env export
:conda env export > environment.yaml
Ajouter le dossier
titanic/
au.gitignore
pour ne pas ajouter ce dossier àGit
git checkout appli11b
Etape 2: construire l’environnement de notre application via un script shell
Les environnements virtuels permettent de mieux spécifier les dépendances de notre projet, mais ne permettent pas de garantir une portabilité optimale. Pour cela, il faut recourir à la technologie des conteneurs. L’idée est de construire une machine, en partant d’une base quasi-vierge, qui permette de construire étape par étape l’environnement nécessaire au bon fonctionnement de notre projet. C’est le principe des conteneurs Docker
.
Leur méthode de construction étant un peu difficile à prendre en main au début, nous allons passer par une étape intermédiaire afin de bien comprendre le processus de production.
- Nous allons d’abord créer un script
shell
, c’est à dire une suite de commandesLinux
permettant de construire l’environnement à partir d’une machine vierge ; - Nous transformerons celui-ci en
Dockerfile
dans un deuxième temps. C’est l’objet de l’étape suivante.
- Créer un service
ubuntu
sur le SSP Cloud - Ouvrir un terminal
- Cloner le dépôt
- Se placer dans le dossier du projet avec
cd
- Se placer au niveau du checkpoint 11a avec
git checkout appli11a
- Via l’explorateur de fichiers, créer le fichier
install.sh
à la racine du projet avec le contenu suivant:
Script à créer sous le nom install.sh
#!/bin/bash
# Install Python
apt-get -y update
apt-get install -y python3-pip python3-venv
# Create empty virtual environment
python3 -m venv titanic
source titanic/bin/activate
# Install project dependencies
pip install -r requirements.txt
- Changer les permissions sur le script pour le rendre exécutable
chmod +x install.sh
- Exécuter le script depuis la ligne de commande avec des droits de super-utilisateur (nécessaires pour installer des packages via
apt
)
sudo ./install.sh
- Vérifier que le script
main.py
fonctionne correctement dans l’environnement virtuel créé
source titanic/bin/activate
python3 main.py
git checkout appli12a
- Créer un service
ubuntu
sur le SSP Cloud en cliquant sur ce lien - Cloner le dépôt et se placer au niveau du checkpoint 11b avec
git checkout appli11b
- Se placer dans le dossier du projet avec
cd
- On va se placer en super-utilisateur dans la ligne de commande en tapant
sudo bash
- Créer le fichier
install.sh
avec le contenu suivant:
Script à créer sous le nom install.sh
apt-get -y update && apt-get -y install wget
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
bash Miniconda3-latest-Linux-x86_64.sh -b -p /miniconda && \
rm -f Miniconda3-latest-Linux-x86_64.sh
PATH="/miniconda/bin:${PATH}"
# Create environment
conda install mamba -c conda-forge
mamba create -n titanic pandas PyYAML scikit-learn -c conda-forge
mamba activate titanic
PATH="/miniconda/envs/titanic/bin:${PATH}"
python main.py
- Changer les permissions sur le fichier
chmod u+x ./install.sh
- Exécuter le script depuis la ligne de commande
git checkout appli12b
Etape 3: conteneuriser l’application avec Docker
Cette application nécessite l’accès à une version interactive de Docker
. Il n’y a pas beaucoup d’instances en ligne disponibles.
Nous proposons deux solutions:
- Installer
Docker
sur sa machine ; - Se rendre sur l’environnement bac à sable Play with Docker
Maintenant qu’on sait que ce script préparatoire fonctionne, on va le transformer en Dockerfile
(la syntaxe Docker
est légèrement différente de la syntaxe Linux
classique). Puis on va le tester dans un environnement bac à sable (pour ensuite pouvoir plus facilement automatiser la construction de l’image Docker
par la suite).
Docker
Se placer dans un environnement avec Docker
- Dans le terminal
Linux
, cloner votre dépôtGithub
- Créer via la ligne de commande un fichier texte vierge nommé
Dockerfile
(la majuscule au début du mot est importante)
Commande pour créer un Dockerfile
vierge depuis la ligne de commande
touch Dockerfile
- Ouvrir ce fichier via un éditeur de texte et copier le contenu suivant dedans:
Premier Dockerfile
FROM ubuntu:22.04
WORKDIR ${HOME}/titanic
# Install Python
RUN apt-get -y update && \
apt-get install -y python3-pip
# Install project dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
CMD ["python3", "main.py"]
Construire l’image
Le Dockerfile
est la recette de construction de l’image. La construction effective de l’image à partir de cette recette s’appelle l’étape de build
.
- Utiliser
docker build
pour créer une image avec le tagmy-python-app
docker build . -t my-python-app
- Vérifier les images dont vous disposez. Vous devriez avoir un résultat proche de celui-ci :
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
my-python-app latest c0dfa42d8520 6 minutes ago 836MB
ubuntu 25.04 825d55fb6340 6 days ago 77.8MB
Tester l’image: découverte du cache
L’étape de build
a fonctionné: une image a été construite.
Mais fait-elle effectivement ce que l’on attend d’elle ?
Pour le savoir, il faut passer à l’étape suivante, l’étape de run
.
$ docker run -it my-python-app
python3: can't open file '/~/titanic/main.py': [Errno 2] No such file or directory
Le message d’erreur est clair : Docker
ne sait pas où trouver le fichier main.py
. D’ailleurs, il ne connait pas non plus les autres fichiers de notre application qui sont nécessaires pour faire tourner le code: config.yaml
et le dossier src
.
- Avant l’étape
CMD
, copier les fichiers nécessaires sur l’image afin que l’application dispose de tous les éléments nécessaires pour être en mesure de fonctionner.
Nouveau Dockerfile
FROM ubuntu:22.04
WORKDIR ${HOME}/titanic
# Install Python
RUN apt-get -y update && \
apt-get install -y python3-pip
# Install project dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY main.py .
COPY src ./src
COPY configuration ./configuration
CMD ["python3", "main.py"]
Refaire tourner l’étape de
build
Refaire tourner l’étape de
run
. A ce stade, la matrice de confusion doit fonctionner 🎉. Vous avez créé votre première application reproductible !
Ici, le cache permet d’économiser beaucoup de temps. Par besoin de refaire tourner toutes les étapes, Docker
agit de manière intelligente en faisant tourner uniquement les étapes qui ont changé.
git checkout appli13
Partie 4 : automatisation avec l’intégration continue
Une image Docker
est un livrable qui n’est pas forcément intéressant pour tous les publics. Certains préféreront avoir un plat bien préparé qu’une recette. Nous allons donc proposer d’aller plus loin en proposant plusieurs types de livrables.
Cela va nous amener à découvrir les outils du CI/CD
(Continuous Integration / Continuous Delivery) qui sont au coeur de l’approche DevOps
.
Notre approche appliquée au machine learning va nous entraîner plutôt du côté du MLOps
qui devient une approche de plus en plus fréquente dans l’industrie de la data science.
Nous allons améliorer notre approche de trois manières:
- Automatisation de la création de l’image
Docker
et tests automatisés de la qualité du code ; - Production d’un site web automatisé permettant de documenter et valoriser le modèle de Machine Learning ;
- Mise à disposition du modèle entraîné par le biais d’une API pour ne pas le ré-entraîner à chaque fois et faciliter sa réutilisation ;
A chaque fois, nous allons d’abord tester en local notre travail puis essayer d’automatiser cela avec les outils de Github
.
On va ici utiliser l’intégration continue pour deux objectifs distincts:
- la mise à disposition de l’image
Docker
; - la mise en place de tests automatisés de la qualité du code sur le modèle de notre
linter
précédent
Nous allons utiliser Github Actions
pour cela.
Etape 1: mise en place de tests automatisés
Avant d’essayer de mettre en oeuvre la création de notre image Docker
de manière automatisée, nous allons présenter la logique de l’intégration continue en testant de manière automatisée notre script main.py
.
Pour cela, nous allons partir de la structure proposée dans l’action officielle. La documentation associée est ici
A partir de l’exemple présent dans la documentation officielle de Github
, on a déjà une base de départ qui peut être modifiée:
- Créer un fichier
.github/workflows/ci.yaml
avec le contenu de l’exemple de la documentation - Retirer la
strategy matrix
et ne tester qu’avec la version3.10
dePython
- Utiliser le fichier
requirements.txt
pour installer les dépendances. - Remplacer
russ
parpylint
pour vérifier la qualité du code. Ajouter l’argument--fail-under=6
pour renvoyer une erreur en cas de note trop basse13 - Plutôt que
pytest
, utiliserpython main.py
pour tester que la matrice de confusion s’affiche bien.
Fichier .github/workflows/ci.yaml
obtenu
name: Build, test and push
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
pip install -r requirements.txt - name: Lint
run: |
pylint src --fail-under=6 - name: Test workflow
run: |
python main.py
git checkout appli14
Maintenant, nous pouvons observer que l’onglet Actions
s’est enrichi. Chaque commit
va entraîner une action pour tester nos scripts.
Si la note est mauvaise, nous aurons une croix rouge (et nous recevrons un mail). On pourra ainsi détecter, en développant son projet, les moments où on dégrade la qualité du script afin de la rétablir immédiatemment.
Etape 2: Automatisation de la livraison de l’image Docker
Maintenant, nous allons automatiser la mise à disposition de notre image sur DockerHub
. Cela facilitera sa réutilisation mais aussi des valorisations ultérieures.
Là encore, nous allons utiliser une série d’actions pré-configurées.
Pour que Github
puisse s’authentifier auprès de DockerHub
, il va falloir d’abord interfacer les deux plateformes. Pour cela, nous allons utiliser un jeton (token) DockerHub
que nous allons mettre dans un espace sécurisé associé à votre dépôt Github
.
- Se rendre sur https://hub.docker.com/ et créer un compte.
- Créer un dépôt public
application-correction
- Aller dans les paramètres (https://hub.docker.com/settings/general) et cliquer, à gauche, sur
Security
- Créer un jeton personnel d’accès, ne fermez pas l’onglet en question, vous ne pouvez voir sa valeur qu’une fois.
- Dans votre dépôt
Github
, cliquer sur l’ongletSettings
et cliquer, à gauche, surActions
. Sur la page qui s’affiche, cliquer surNew repository secret
- Donner le nom
DOCKERHUB_TOKEN
à ce jeton et copier la valeur. Valider - Créer un deuxième secret nommé
DOCKERHUB_USERNAME
ayant comme valeur le nom d’utilisateur que vous avez créé surDockerhub
A ce stade, nous avons donné les moyens à Github
de s’authentifier avec notre identité sur Dockerhub
. Il nous reste à mettre en oeuvre l’action en s’inspirant de https://github.com/docker/build-push-action/#usage. On ne va modifier que trois éléments dans ce fichier. Effectuer les actions suivantes:
Docker
- En s’inspirant de ce template, ajouter un nouveau job
docker
dans le fichierci.yaml
qui va build et push l’image sur leDockerHub
- Définir le job
test
comme prérequis du jobdocker
en vous référant à cette documentation - Changer le tag à la fin pour mettre
<username>/application-correction:latest
oùusername
est le nom d’utilisateur surDockerHub
;
Fichier .github/workflows/ci.yaml
obtenu
name: Build, test and push
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pylint
pip install -r requirements.txt - name: Lint
run: |
pylint src --fail-under=6 - name: Test workflow
run: |
python main.py docker:
runs-on: ubuntu-latest
needs: test
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
push: true
tags: linogaliana/application-correction:latest
- Faire un
commit
et unpush
de ces fichiers
Comme on est fier de notre travail, on va afficher ça avec un badge sur le README
.
README
- Se rendre dans l’onglet
Actions
et cliquer sur un des scripts en train de tourner. - En haut à droite, cliquer sur
...
- Sélectionner
Create status badge
- Récupérer le code
Markdown
proposé - Copier dans le
README
depuisVSCode
- Faire de même pour l’autre workflow
Maintenant, il nous reste à tester notre application dans l’espace bac à sable ou en local, si Docker
est installé.
- Se rendre sur l’environnement bac à sable Play with Docker
- Récupérer l’image :
docker pull <username_dockerhub>/application-correction:latest
- Tester le bon fonctionnement de l’image
docker run -it <username_dockerhub>/application-correction:latest
:tada: La matrice de confusion doit s’afficher ! Vous avez grandement facilité la réutilisation de votre image.
git checkout appli15
Partie 5: mise en production d’une API servant un modèle de machine learning
Etape 1: création d’un pipeline scikit
Notre code respecte des bonnes pratiques formelles. Cependant, la mise en production nécessite d’être exigeant sur la mise en oeuvre opérationnelle de notre pipeline.
Quand on utilise scikit
, la bonne pratique est d’utiliser les pipelines qui sécurisent les étapes de feature engineering avant la mise en oeuvre d’un modèle (que ce soit pour l’entraînement ou le test sur un nouveau jeu de données).
On va donc devoir refactoriser notre application pour utiliser un pipeline scikit
. Les raisons sont expliquées ici. Cela aura aussi l’avantage de rendre les étapes plus lisibles.
Refactoriser le code de
random_forest_titanic
pour créer un vrai pipeline de preprocessing avant la modélisationSimplifier la fonction
split_train_test_titanic
en la réduisant au découpage train/testModifier
main.py
pour que ce soit à ce niveau qu’a lieu le découpage en train/test, l’entrainement et l’évaluation du modèle
git checkout appli16
Etape 2: développer une API en local
- Créer un nouveau service
SSPCloud
en paramétrant dans l’ongletNetworking
le port 5000 ; - Cloner le dépôt et se placer au niveau de l’application précédente (
git checkout appli16
) - Installer
fastAPI
etuvicorn
puis les ajouter aurequirements.txt
- Renommer le fichier
main.py
entrain.py
et insérer le contenu suivant dedans :
Fichier train.py
Récupérer le contenu sur cette page
- Créer le fichier
api.py
permettant d’initialiser l’API:
Fichier api.py
Récupérer le contenu sur cette page
- Exécuter
train.py
pour stocker en local le modèle entraîné - Ajouter
model.joblib
au.gitignore
- Déployer en local l’API avec la commande
uvicorn api:app --reload --host "0.0.0.0" --port 5000
- A partir du
README
du service, se rendre sur l’URL de déploiement, ajouter/docs/
à celui-ci et observer la documentation de l’API - Se servir de la documentation pour tester les requêtes
/predict
- Récupérer l’URL d’une des requêtes proposées. La tester dans le navigateur et depuis
Python
avecrequests
(requests.get(url).json()
)
git checkout appli17
Etape 3: déployer l’API
A ce stade, nous avons déployé l’API seulement localement, dans le cadre d’un service. Ce mode de déploiement est très pratique pour la phase de développement, afin de s’assurer que l’API fonctionne comme attendu. A présent, il est temps de passer à l’étape de déploiement, qui permettra à notre API d’être accessible via une URL sur le web, et donc aux utilisateurs potentiels de la requêter. Pour se faire, on va utiliser les possibilités offertes par Kubernetes
, sur lequel est basé le SSP Cloud.
Modifier le
Dockerfile
pour tenir compte des changements dans les noms de fichier effecutés dans l’application précédenteCréer un script
run.sh
à la racine du projet qui lance le scripttrain.py
puis déploie localement l’API
Fichier run.sh
#/bin/bash
python3 train.py
uvicorn api:app --reload --host "0.0.0.0" --port 5000
Donner au script
run.sh
des permissions d’exécution :chmod +x run.sh
Changer l’instruction
CMD
duDockerfile
pour exécuter le scriptrun.sh
au lancement du conteneurCommit et push les changements
Une fois le CI terminé, récupérer la nouvelle image dans l’environnement de test et vérifier que l’API se déploie correctement
git checkout appli18
Créer un dossier
deployment
à la racine du projet qui va contenir les fichiers de configuration nécessaires pour déployer sur un clusterKubernetes
En vous inspirant de la documentation, y ajouter un premier fichier
deployment.yaml
qui va spécifier la configuration du Pod à lancer sur le cluster
Fichier deployment/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: titanic-deployment
labels:
app: titanic
spec:
replicas: 1
selector:
matchLabels:
app: titanic
template:
metadata:
labels:
app: titanic
spec:
containers:
- name: titanic
image: linogaliana/application-correction:latest
ports:
- containerPort: 5000
- En vous inspirant de la documentation, y ajouter un second fichier
service.yaml
qui va créer une ressourceService
permettant de donner une identité fixe auPod
précédemment créé au sein du cluster
Fichier deployment/service.yaml
apiVersion: v1
kind: Service
metadata:
name: titanic-service
spec:
selector:
app: titanic
ports:
- protocol: TCP
port: 80
targetPort: 5000
- En vous inspirant de la documentation, y ajouter un troisième fichier
ingress.yaml
qui va créer une ressourceIngress
permettant d’exposer le service via une URL en dehors du cluster
Fichier deployment/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: titanic-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
tls:
- hosts:
- titanic.kub.sspcloud.fr
rules:
- host: titanic.kub.sspcloud.fr
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: titanic-service
port:
number: 80
Appliquer ces fichiers de configuration sur le cluster :
kubectl apply -f deployement/
Si tout a correctement fonctionné, vous devriez pouvoir accéder à l’API à l’URL spécifiée dans le fichier
deployment/ingress.yaml
git checkout appli19
Partie 6: un workflow complet de MLOps
Ce sera l’an prochain, désolé !
Partie 7: livrer un site web de manière automatisée
On va proposer un nouveau livrable pour parler à un public plus large. Pour cela, on va déployer un site web statique qui permet de visualiser rapidement les résultats du modèle.
On propose de créer un site web qui permet de comprendre, avec l’appui des valeurs de Shapley, les facteurs qui auraient pu nous mettre la puce à l’oreille sur les destins de Jake et de Rose.
Pour faire ce site web, on va utiliser Quarto
et déployer sur Github Pages
. Des étapes préliminaires sont réalisées en Python
puis l’affichage interactif sera contrôlé par du JavaScript
grâce à des blocs Observable
.
Dans un premier temps, on va créer un projet Quarto
au sein de notre dépôt:
- Installer
Quarto
dans votre environnement local (s’il n’est pas déjà disponible) ; - Dans le projet, utiliser la commande
quarto create-project
pour initialiser le projetQuarto
; - Supprimer le fichier automatiquement généré avec l’extension
.qmd
; - Récupérer le contenu du modèle de fichier
Quarto Markdown
cette page. Celui-ci permet de générer la page d’accueil de notre site. Enregistrer dans un fichier nomméindex.qmd
On teste ensuite la compilation en local du fichier:
- Modifier le fichier
train.py
à partir de cette page pour être en mesure de compiler le fichier - Exécuter le fichier
train.py
- En ligne de commande, faire
quarto preview
(ajouter les arguments--port 5000 --host 0.0.0.0
si vous passez par leSSPCloud
) - Observer le site web généré en local
Enfin, on va construire et déployer automatiquement ce site web grâce au combo Github Actions
et Github Pages
:
- Créer une branche
gh-pages
à partir du contenu de cette page - Créer un fichier
.github/workflows/website.yaml
avec le contenu de ce fichier
On doit dans cette application modifier le fichier train.py
pour enregistrer en local une duplication du modèle de machine learning et de l’ensemble d’entraînement car pour ces deux éléments on n’est pas allé au bout de la démarche MLOps d’enregistrement dans un model registry et un feature store.
Dans la prochaine version de ce cours, qui intègrera MLFlow
, on aura une démarche plus propre car on utilisera bien le modèle de production et le jeu d’entrainement associé.
git checkout appli20
Footnotes
L’export dans un script
.py
a été fait avecJupytext
. Comme cela n’est pas vraiment l’objet du cours, nous passons cette étape et fournissons directement le script. Mais n’oubliez pas que cette démarche, fréquente quand on a démarré sur un notebook et qu’on désire consolider en faisant la transition vers des scripts, nécessite d’être attentif pour ne pas risquer de faire une erreur.↩︎Essayez de commit vos changements à chaque étape de l’exercice, c’est une bonne habitude à prendre.↩︎
Nous le verrons lorsque nous mettrons en oeuvre l’intégration continue.↩︎
Ici, le jeton d’API n’est pas indispensable pour que le code fonctionne. Afin d’éviter une erreur non nécessaire lorsqu’on automatisera le processus, on peut créer une condition qui vérifie la présence ou non de ce fichier.
Cela peut être fait avec la fonction
os.path.exists
:if os.path.exists('secrets.yaml'): secrets = import_yaml_config("secrets.yaml")
La variable
secrets
n’existera que dans le cas où un fichiersecrets.yaml
existe. Le script reste donc reproductible même pour un utilisateur n’ayant pas le fichiersecrets.yaml
.↩︎Nous proposons ici d’adopter le principe de la programmation fonctionnelle. Pour encore fiabiliser un processus, il serait possible d’adopter le paradigme de la programmation orientée objet (POO). Celle-ci est plus rebutante et demande plus de temps au développeur. L’arbitrage coût-avantage est négatif pour notre exemple, nous proposons donc de nous en passer. Néanmoins, pour une mise en production réelle d’un modèle, il est recommandé de l’adopter. C’est d’ailleurs obligatoire avec des pipelines
scikit
.↩︎Au passage vous pouvez noter que mauvaises pratiques discutables, peuvent être corrigées, notamment l’utilisation excessive de
apply
là où il serait possible d’utiliser des méthodes embarquées parPandas
. Cela est plutôt de l’ordre du bon style de programmation que de la qualité formelle du script. Ce n’est donc pas obligatoire mais c’est mieux.↩︎Il est normal d’avoir des dossiers
__pycache__
qui traînent : ils se créent automatiquement à l’exécution d’un script enPython
.↩︎L’emplacement de ce fichier est amené à évoluer dans le cadre d’une packagisation. Dans un package, ces tests seront dans un dossier spécifique
/tests
carPython
sait gérer de manière plus formelle les imports de fonctions depuis des modules. Ici, on est dans une situation transitoire, raison pour laquelle les tests sont dans les mêmes dossiers que les fonctions.↩︎Le fichier
__init__.py
indique àPython
que le dossier est un package. Il permet de proposer certaines configurations lors de l’import du package. Il permet également de contrôler les objets exportés (c’est-à-dire mis à disposition de l’utilisateur) par le package par rapport aux objets internes au package. En le laissant vide, nous allons utiliser ce fichier pour importer l’ensemble des fonctions de nos sous-modules. Ce n’est pas la meilleure pratique mais un contrôle plus fin des objets exportés demanderait un investissement qui ne vaut, ici, pas le coût.↩︎Ce
pyproject.toml
est un modèle qui utilisesetuptools
pour build le package. C’est l’outil classique. Néanmoins, pour des usages plus raffinés, il peut être utile d’utiliserpoetry
qui propose des fonctionnalités plus complètes.↩︎Si vous désirez aussi contrôler la version de
Python
, ce qui peut être important dans une perspective de portabilité, vous pouvez ajouter une option, par exemple-p python3.9
.↩︎Pour comparer les deux listes, vous pouvez utiliser la fonctionnalité de split du terminal sur
VSCode
pour comparer les outputs deconda env export
en les mettant en face à face.↩︎Il existe une approche alternative pour faire des tests réguliers: les hooks
Git
. Il s’agit de règles qui doivent être satisfaites pour que le fichier puisse être committé. Cela assurera que chaquecommit
remplisse des critères de qualité afin d’éviter le problème de la procrastination.La documentation de pylint offre des explications supplémentaires.↩︎