MLOps DPE Chapitre 3: du notebook au script

Dans ce chapitre, vous allez transformer le notebook du chapitre précédent en une repository structurée de script python et inclure les premiers fichiers utiles à sa compréhension et à son utilisation que sont le README et un .gitignore. Vous publierez ensuite votre programme sur github après avoir créé une clé ssh d'authentification.

Comme nous l’avons vu au chapitre précédent, le notebook Jupyter n’est pas adapté à un environnement de production. Il nous faut transformer ce notebook en une collection de scripts python plus structurée. Le but est d’avoir un code qui soit versionné, sans bug, facile à faire évoluer et compréhensible par d’autres personnes que vous. Pour cela nous allons suivre certains principes qui permettent d’améliorer la qualité du code.

Comment améliorer son code ?

Écrire un code de qualité, c’est vraiment pas compliqué ! Il suffit simplement d’appliquer les principes suivants : KISS, YAGNI, DRY, GRASP et SOLID ! En voilà des acronymes sympa !

Bon, évidemment si écrire du code lisible et efficace était si facile nous n’aurions pas besoin de tous ces concepts. Mais à quoi reconnait-on du bon code ? Simplement, à du code compréhensible, facilement modifiable, maintenable et bien sur sans bug.

Pour ma part j’ai retenu les quelques idées simples que voici :

SRP ou le principe de responsabilité unique :

Chaque élément de code doit être dédié à une seule chose, un but unique. Cela s’applique à tous les niveaux : classes, fonctions, script, ligne, cellule de notebook, etc. On évite ainsi les fonctions qui font des centaines de lignes, les classes trop abstraites ou polymorphes et les lignes de code trop futées et inextricablement illisibles.

Illustrations :

En d’autres termes, le principe de responsabilité unique (SRP) signifie qu’une classe, fonction ou ligne de code ne doit avoir qu’une seule tâche, afin d’avoir un code clair, modulaire et facile à maintenir.

DRY ou “Don’t Repeat Yourself”

En français : “éviter les répétitions”.

Chaque fonctionnalité ou bloc de code ne doit être écrit qu’une seule fois. Éviter de dupliquer le code permet de centraliser les modifications. Cela réduit le risque d’en oublier une partie et rends le code plus lisible et maintenable.

YAGNI ou “You ain’t gonna need it”

En français on peut traduire YAGNI par “laisse tomber, t’en auras pas besoin”.

YAGNI ça sonne bien. YAGNI c’est cool. YAGNI !

YAGNI stipule de n’écrire que du code réellement nécessaire sans anticiper sur les fonctionnalités ou les optimisations qui pourrais survenir et dont nous imaginons l’utilité à tort ou à raison. En réalité, ce bout de code prévisionnel ne sera probablement jamais utilisé, car le design du code, le besoin du produit ou le contexte du projet changera avant. Ajouter du code qui sera potentiellement utile, rajoute du bruit et de la complexité.

KISS ou “keep it simple stupid !”

Le principe de base de tout bon code : fais simple !

On rencontre souvent du code inutilement savant, du code pédant. Une ratatouille de classes, une bouillabaisse de fonctions, une paella d’astuces pour ce qui pourrait être écrit en quelques lignes de code. Faite simple ! Privilégiez la lisibilité sur la complexité et n’utilisez les abstractions orienté objet que si celle ci apportent vraiment quelque chose.

A ces principes de base pour guider votre écriture, je rajouterai 2 points

En fait, l’idée principale derrière tous ces beaux principes, est que vous ne codez plus pour vous, mais bien pour celle ou celui qui viendra après vous et qui devra comprendre ce que vous faites. Non seulement ce que le code fait, mais aussi sa logique, ses patterns, etc. Facilitez la vie de votre alter ego en écrivant un code simple et propre.

Vous allez m’objecter que vous serez le seul à travailler sur le projet MLOps-DPE en question. Certes ! Mais comme l’a écrit DHH, créateur de Ruby on Rails, (je paraphrase) : as soon as it is written your code becomes legacy code. Votre code devient hérité dès qu’il est écrit même lorsque vous en êtes l’héritier. Celui ou celle qui devra se plonger dans vos écritures dans quelques mois, c’est vous. Alors autant faciliter le travail à votre futur self et tout bien organiser dès maintenant.

Mais qui a écrit ce code

Pour conclure ce paragraphe, il me semble que la difficulté d’écrire du bon code est cette recherche constante d’un juste équilibre entre concision et clarté. D’une part, la lisibilité du code prône sur sa densité ; une ligne ou une fonction par opération. D’autre part, moins il y a de code moins il y a de bug ; pas besoin d’une soupe de classes pour ouvrir un fichier.

A vous : Avant de regarder le code du chapitre (branche chapitre 3 du github), prenez votre notebook et transformez-le en code structuré avec un fichier contenant une classe pour la transformation des données et un autre pour entraîner le modèle.

Quelle structure pour le projet ?

Commençons donc par la structure du projet. Comment organiser les fichiers ?

Il n’y a pas de recette surprise ni de standard universel. L’important est de pouvoir séparer les éléments logiquement de façon à pouvoir vous y retrouver rapidement et à pouvoir facilement importer les classes entre les fichiers.

Un autre principe est de séparer les rôles de chaque fichier. Quelqu’un doit pouvoir voir l’arborescence de votre projet et comprendre d’un coup d’œil ou sont les choses.

Importer les classes de fichiers en fichier n'est pas toujours si facile en python surtout si les fichiers sont dans des répertoires différents. Cela peut rapidement devenir un casse-tête. Voir ce fil sur Stackoverflow : Importing files from differemt folder.

Voici donc un exemple d’architecture de code avec 2 répertoires:

et il en viendra d’autres au fil du projet : test, shell, sql etc

On exécutera les scripts en ligne de commande avec

> python src/features.py
> python src/train.py

Chaque script contient une classe et une fonction main() pour exécuter le processus voulu une fois la classe instanciée.

Il n’y a rien de spécial dans ces classes qui ne soit pas évident en regardant le code disponible dans la repo github. L’important est que nous ayons bien séparé les rôles de chaque classe et chaque script. Un pour les données et un pour le modèle.

Il faut aussi pouvoir lire les données brutes, les écrire une fois numérisées et charger cette dernière version pour entraîner le modèle. On touche là du doigt le besoin d’attribuer une version à chaque version du dataset. Celui initial et brute et des versions en fonction des expériences, des essais que l’on fait. De même les paramètres du modèle voir le choix modèle lui-même vont varier au fur et à mesure des expériences.

Pour éviter le syndrome du fichier nommé final prématurément, il faudra donner une version spécifique à ces éléments au fil des expériences et modifications.

final final final version

Un exemple du code complet et structuré se trouve sur la branche chapitre 3 du github.

Git, Gitlab ou Github ?

Le protocole GIT transforme un programme en outil de travail collaboratif. C’est le logiciel de version le plus utilisé depuis 2010. Il a été développé en quelques semaines en 2005 par Linus Torvald lui-même.

Git permet à des développeurs de copier la version officielle du code, puis de travailler en parallèle sur leurs évolutions respectives et enfin de fusionner leurs modifications dans cette même version centralisée de départ. On parle de branches pour chaque version du code et de merge pour la fusion de deux branches.

Quand on travaille à plusieurs sur une même base de code (codebase) avec git, le principe est de créer une branche par fonctionnalité et de régulièrement mettre à jour cette branche de façon à intégrer les modifications des autres développeurs. On parle de rebase pour mettre à jour sa branche par rapport à la branche principale.

Github ou GitLab ? J'ai demandé à notre ami chatGPT ce qu'il en pensait. Résume en quelques lignes les avantages respectifs de gitlab et de github. En résumé, GitLab est plus orienté opensource et offre un niveau de contrôle d'accès plus granulaire, tandis que github est plus répandu et facile d'utilisation. Dans le cadre du projet MLOps-DPE cela ne changera rien, vous pouvez utiliser l'un ou l'autre.

Comment évolue le code ?

Au point de vue projet, on considère 3 stades dans la composition du code en fonction de sa maturité :

Le code vit donc sur 2 ou 3 branches : dev, staging et production.

Il est bien plus facile de fusionner un petit bout de code qu'une modification importante qui impacte plusieurs fichiers. Et fusionner régulièrement sans attendre que la branche principale ait beaucoup évolué évite de toujours devoir rattraper le code des autres. En règle générale, faire des commits petit, concis, cohérent et explicite, facilite la vie.

Ouvrir un compte github

A vous : Pour mettre votre code sur github, il vous faut ouvrir un compte github (ou gitlab, aucune importance à ce stade) qui est gratuit
  • - Allez sur github.com/new
  • Renseignez le nom de votre repo et une description
  • Sélectionnez le mode "Private"
  • Ne rajoutez pas les autres fichiers (readme, .gitignore), nous nous en chargerons

Nouvelle repo github

Github montre 2 façon d’initialiser votre nouvelle repo avec du code, la longue et la courte.

Comme vous avez déjà du code en local, la version courte suffit.

Voici les commandes à effectuer en ligne de commande

main ou master ? main semble être devenue le nouveau nom par défaut de la branche principale d'une repo git. Franchement cela ne change rien, ce n'est qu'une convention et vous pourriez nommer votre branche principale, maître ou gargamel, si vous préférez. Mais pour simplifier les choses on utilisera le nom main dans la suite du projet.

Mais avant de sauter sur ces lignes, il vous faut montrer patte blanche (vous authentifier) à github en créant une clé SSH.

Récapitulatif SSH

Selon wikipédia : Avec SSH, l’authentification peut se faire sans l’utilisation de mot de passe ou de phrase secrète en utilisant la cryptographie asymétrique. La clé publique est distribuée sur les systèmes auxquels on souhaite se connecter. La clé privée, qu’on prendra le soin de protéger par un mot de passe, reste uniquement sur le poste à partir duquel on se connecte.

Une clé SSH est donc composée de 2 parties : une publique et une privée. Vous gardez la partie privée sur votre ordinateur et surtout vous ne la divulguez jamais.

La partie publique permet aux services externes de matcher avec votre clé privée. C’est cette partie publique que vous mettrez sur github. Mais il ne faut pas non plus la divulguer sinon quelqu’un d’autre peut se faire passer pour vous.

Pour créer une clef SSH sur mac, dans un terminal

ssh-keygen -t ed25519 -C "votre adresse email"

Il n’y a pas besoin de saisir de mot de passe. Si c’est votre première clé, acceptez les noms de fichiers par défaut. Le processus de création va créer 2 fichiers dans le répertoire ~/.ssh : id_rsa (la clé privée) et id_rsa.pub la clé publique.

Il vous faut ensuite activer la clé avec

eval "$(ssh-agent -s)"

Vous allez maintenant copier votre clé publique sur github

cd ~/.ssh

Copiez la clé manuellement avec vim id_rsa.pub ou directement dans le terminal avec pbcopy id_rsa.pub

Sur github allez en haut à droite dans votre photo / avatar et tout en bas du menu déroulant cliquez sur settings :

Settings pour ajouter une clé ssh

à gauche maintenant de l’écran, cliquez sur SSH and GPG keys :

ajouter une clé ssh

Puis cliquez sur new ssh key (pas new gpg key)

Donnez un nom à cette clé et dans la case principale Key copiez votre clé publique

copier une clé ssh

Maintenant pour tester que votre clé a bien été prise en compte, retournez sur votre répertoire local et exécutez

git push -u origin main

Bravo !

Quelques commandes git

Dans le projet MLOps-DPE, vous travaillez a priori seul. Vous n’aurez donc pas besoin de fusionner votre code avec celui d’autres devs. Si vous travaillez sur la branche main, les seules commandes sont les mêmes à chaque fois que vous modifiez votre code:

Github dans le cas d’un travail en solitaire sert plutôt de simple sauvegarde et ne remplit pas son potentiel de plateforme de travail collaboratif.

3 points :

Votre code est donc maintenant sur github. Prenez quelques moments pour vous familiariser avec ces commandes.

Vous trouverez un excellent résumé des principales commandes git sur cette page github-git-cheat-sheet.

Le Readme

Lorsque vous allez sur une librairie ou un programme public sur github, la première chose que vous voyez est le fichier README qui constitue la première page de la repo. Littéralement, readme veut dire lis moi. direct!

Le readme est le premier fichier que vos utilisateurs et vos développeurs contributeurs voient quand ils découvrent votre projet. C’est un guide, une description de votre travail qui indique quoi attendre du projet et comment le faire fonctionner.

Un bon readme contient au minimum les éléments suivants

Et aussi, si votre projet est conséquent et mure

En résumé, le readme explique comment prendre en main le projet.

Le readme est au format markdown. Il est écrit dans un fichier nommé readme.md situé à la racine du projet.

A vous, lisez les readme de certaines repo comme category encoders, scikit-learn, ou celui plus long de la librairie transformers de huggingface. Le contenu varie, mais le but est toujours de présenter et d’aider à la prise en main du module.

Le fichier .gitignore

Comme son nom l’indique, le fichier .gitignore liste les fichiers qui ne doivent pas se retrouver sur github.

Il y en a de 3 types

Vous êtes libres d’ajouter les fichiers que vous souhaitez.

Voici un exemple de .gitignore :

# Ignored Directories
data/
.ipynb_checkpoints/

# Ignored File Types
*.pdf
*.csv
*.xlsx
*.pkl
*.json
*.log
*.zip
*.tar.gz

# Ignored Python Files
__pycache__/
*.pyc
*.pyo
*.pyd

# Ignored Configuration Files
.env
.vscode/

# Ignored Miscellaneous Files
.DS_Store
Thumbs.db

Conclusion

Nous sommes passés d’un notebook Jupyter à un début d’organisation non seulement du code et des données, mais surtout de la structure de votre code qui permet à vous et à d’autres de travailler, utiliser voir contribuer à ce projet. Le point important à retenir au-delà des aspects techniques et que vous écrivez votre code pour quelqu’un d’autre (ou pour vous-mêmes dans quelques mois ce qui revient au même).

Organiser votre code va de pair avec organiser et à versionner les données et les modèles.

Dans le prochain chapitre nous allons plus loin dans cette organisation et stabilisation de votre code en travaillant sur le format, la qualité du code et l’environnement d’exécution.