C’est en février 2025, confortablement installé sur mon canapé à scroller sur LinkedIn que je tombe sur un post annonçant la sortie d’une toute nouvelle lib Python open-source : skore ! Bizarre : c’est une toute nouvelle lib, et pourtant elle me semble déjà étrangement familière…
L’orthographe si particulière du nom et le logo orange et bleu de ce nouveau venu n’auront trompé aucun data scientist : il y a effectivement un lien avec scikit-learn, d’où cette sensation de déjà-vu. L’auteur de cette annonce : Probabl, la nouvelle entité chapeautant désormais scikit-learn. Cette entreprise, chargée notamment de maintenir cette librairie, n’en est pas à sa première annonce puisque, quelques mois plus tôt, elle dévoilait déjà à la communauté le tout premier programme de certification sur cette incontournable librairie de machine learning.
Curieux de découvrir ce que ce nouvel outil avait à proposer, je me suis lancé à la conquête des quelques tutoriels que propose la documentation (qui, soit dit en passant, est aussi excellente que l’iconique documentation de scikit-learn). Je vous propose donc un condensé de mes découvertes sur ce nouvel outil, présenté comme le “scikit-learn side kick”.
Utilité de la libraire
En une phrase, skore est l’allié de tout data scientist voulant développer rapidement, en appliquant les bonnes pratiques du domaine de l’IA, et en évitant les pièges qui jonchent le chemin du développement de modèles de machine learning.
Pour cela, skore dispose des choses suivantes :
- Un mécanisme de persistance des résultats pour éviter des recalculs inutiles
- Un ensemble de classes permettant de diminuer le temps passé à évaluer la qualité d’un modèle
- Un système de warning pour éviter les erreurs méthodologiques
Je vous propose de découvrir maintenant un peu plus en détails ces 3 concepts qui composent la version 0.7 de skore publiée en février 2025.
1 - Pour commencer : un mécanisme de persistance
Premier concept : le skore.Project qui représente le projet sur lequel on travaille. C’est dans ce projet que seront stockées les informations de notre choix, sous la forme d’une paire clé/valeur. Pour commencer, il faut créer un projet :
import skore
my_project = skore.Project("my_project")
Cela crée un sous-dossier my_project.skore/ dans l’arborescence. On peut alors y sauvegarder un peu tout ce que l’on veut, par exemple un entier :
my_project.put("random_seed", 42)
Valeur que l’on pourra ensuite récupérer à partir de son nom de la façon suivante :
my_project.get("random_seed")
>>> 42
On peut sauvegarder dans le projet tout un tas de choses, comme :
- une chaîne de caractère,
- une liste,
- un dictionnaire,
- un DataFrame (pandas ou polars),
- un graphique (matplotlib, plotly, …)
- un modèle déjà entraîné,
- un pipeline de preprocessing,
- un skrub.TableReport (rapport d’EDA - Exploratory Data Analysis - de la librairie skrub)
- des objets customs,
- …
Après quelques objets sauvegardés, il est difficile de se souvenir quelle clé utiliser pour récupérer tel ou tel item, d’autant plus que l’arborescence du dossier my_project.skore/ ne reflète pas le contenu sauvegardé (cf image 1). Heureusement, la méthode .keys() nous permet de lister ce qui a été sauvegardé.
# On sauvegarde 2 autres items
my_project.put("dataset_folder", "data/export-2025-02-12")
my_project.put("accuracy_scores", [0.97, 0.89, 0.92])
# On affiche le contenu sauvegardé
my_project.keys()
>>> ['accuracy_scores', 'dataset_folder', 'random_seed']
Image 1 : Arborescence après avoir sauvegardé les 3 items
Point intéressant : on peut sauvegarder une nouvelle valeur pour une clé déjà existante sans se soucier de perdre la valeur précédente, car un historique est gardé :
# On override la valeur précédente
my_project.put("accuracy_scores", [0.50, 0.55, 0.61])
history = my_project.get("accuracy_scores", version="all", metadata=True)
history
>>> [
{
'value': [0.97, 0.89, 0.92],
'date': '2025-03-04T14:46:01.918631+00:00',
'note': None
},
{
'value': [0.5, 0.55, 0.61],
'date': '2025-03-04T14:59:48.347088+00:00',
'note': None
}
]
Vous aurez constaté, avec le retour précédent, la possibilité de joindre une information lors de l’enregistrement d’une valeur grâce à l’attribut “note” qui sert en quelque sorte de mémo.
2 - Une évaluation des modèles facilitée
Bon, c’est plutôt sympa, mais jusque-là, on ne peut pas vraiment parler de révolution. Fort heureusement, le reste est à mes yeux beaucoup plus intéressant, notamment l’ensemble de classes permettant d'évaluer facilement et rapidement la qualité d’un modèle.
La première de ces classes est skore.EstimatorReport. Voilà un exemple où cette classe est utilisée pour évaluer un modèle de régression logistique sur une tâche de classification binaire :
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from skore import EstimatorReport
# Création d'un dataset
X, y = make_classification(n_classes=2, n_samples=100_000, n_features=20, n_informative=4)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# Entrainement du modèle
clf = LogisticRegression()
clf.fit(X_train, y_train)
# Rapport d'évaluation de skore
report = EstimatorReport(clf, X_test=X_test, y_test=y_test)
En temps normal, pour évaluer la qualité du modèle, il faudrait calculer nous-mêmes les métriques utiles (par exemple ici le recall ou la precision) et tracer certains graphiques utiles comme celui de la fameuse courbe ROC. Spoiler : c’est un peu long et répétitif. Il est vrai que l’on peut se faire quelques snippets à réutiliser tout au long de nos expérimentations (vous avez d’ailleurs peut-être déjà les vôtres), mais c’est malgré tout un peu fastidieux.
Heureusement, skore nous simplifie la vie grâce à la classe EstimatorReport qui, lorsqu’on lui fournit le modèle à évaluer ainsi qu’un dataset d’évaluation, nous met alors à disposition tous les résultats d’évaluation pertinents vis-à-vis de notre tâche. Par exemple, avec la méthode .help() pour le code précédent, on peut voir que l’on a accès à l’ensemble des métriques suivantes :
report.metrics.help()
Image 2 : Aide affichée grâce à la méthode help()
S'il s'agissait plutôt d'une tâche de régression et non de classification, cette sortie aurait été différente ! Les métriques affichées auraient été plutôt le coefficient R² ou à la RMSE par exemple. Skore détermine automatiquement les métriques pertinentes selon notre tâche. Quelles que soient les métriques proposées par Skore, on peut les afficher et les utiliser très facilement :
report.metrics.accuracy()
>>> 0.8104
report.metrics.log_loss()
>>> 0.42589991944974115
report.metrics.precision()
>>> {0: 0.8127334740945266, 1: 0.8081349519154974}
report.metrics.recall()
>>> {0: 0.8043722874135991, 1: 0.8163720337633381}
report.metrics.roc_auc()
>>> 0.8867869385680727
Et si l’on souhaite plutôt une synthèse :
report.metrics.report_metrics()
Image 3 : DataFrame de synthèse affiché en sortie
On a également accès à des graphiques de qualité que l’on peut afficher en peu de lignes :
import matplotlib.pyplot as plt
display = report.metrics.roc()
display.plot()
plt.tight_layout()
Image 4 : Courbe ROC obtenue grâce à report.metrics.roc()
En cas de classification à plus de 2 classes, les métriques, les graphiques et la synthèse montrent les résultats pour chaque classe, ce qui est bien pratique ! Ma seule déception est de ne pas avoir accès à la matrice de confusion. Mais peut-être sera-t-elle ajoutée dans une prochaine version ?
Pour aller plus loin, skore propose également un mécanisme de cache, pour éviter des recalculs inutiles, ainsi que d’autres classes tout aussi intéressantes :
- ComparisonReport : pour comparer des EstimatorReport
- CrossValidationReport : comme un EstimatorReport mais pour une cross validation. On obtient grosso-modo la même chose que précédemment, mais avec le détail pour chaque split, comme montré ci-dessous :
from skore import CrossValidationReport
cv_report = CrossValidationReport(clf, X, y)
# Affichage de la precision (cf image 5)
cv_report.metrics.precision()
# Affichage de la courbe ROC (cf image 6)
display = cv_report.metrics.roc()
display.plot()
plt.tight_layout()
Image 5 : Dataframe présentant la précision pour chaque split de la cross validation
Image 6 : Courbe ROC pour chaque split de la cross validation
3 - Et pour finir, un système de warning pour éviter les pièges
Avec cette évaluation facilitée, à nous les dizaines d'entraînements et d’itérations pour améliorer les performances de nos modèles ! Encore faut-il ne pas faire n’importe quoi avec ses données. C’est ici que le système de warning proposé par skore intervient. Ce dernier nous avertit en cas d’erreur potentielle dans la méthodologie ou en cas de non-respect d’une bonne pratique de ML.
Dans la version 0.7 de skore explorée dans cet article, les seuls warnings existants sont en lien avec la bien connue méthode train_test_split() de sklearn qui permet de créer nos datasets d’entraînement et de test. Ceci étant dit, rien qu’avec cette méthode en apparence simple, il existe déjà un bon nombre de pièges à éviter (pour preuve, un des tutoriels skore nous apprend que la page de la méthode train_test_split() est la plus visitée de la documentation scikit-learn). C'est pourquoi skore propose un wrapper à la méthode train_test_split() qui est capable de lever des warnings lors de son utilisation.
Un premier exemple, avec un code très simple :
import numpy as np
import skore
# Création d'un dataset
X = np.arange(10000).reshape((5000, 2))
y = [0] * 2500 + [1] * 2500
# Split
X_train, X_test, y_train, y_test = skore.train_test_split(X=X, y=y, test_size=0.2)
Avez-vous une idée de ce que l’on peut améliorer dans ce petit bout de code ? Un indice : que se passe-t-il si on l’exécute plusieurs fois ? Si vous donnez votre langue au chat, voilà le warning affiché par skore à l’exécution du code précédent :
Et oui, pas bête. Voici un autre exemple où l’on manipule cette fois un dataset contenant des informations sur des employés (intitulé du poste, sexe, département, date d’embauche, …) :
import pandas as pd
from skrub.datasets import fetch_employee_salaries
# Chargement du dataset
dataset = fetch_employee_salaries()
X, y = dataset.X, dataset.y
# Conversion en date
X["date_first_hired"] = pd.to_datetime(X["date_first_hired"])
# Split (avec le wrapper de skore)
X_train, X_test, y_train, y_test = skore.train_test_split(
X=X, y=y, random_state=0, shuffle=True
)
Une petite intuition de l’erreur méthodologique présentée ici ? C’est un peu plus difficile que précédemment. Un indice, cela concerne les dates. Voici la réponse de skore :
Eh oui, un dataset contenant des dates est à manipuler avec précaution. En ce qui me concerne, un rappel sur le sujet n’était pas de trop, car je ne peux pas dire que cela m’avait vraiment sauté aux yeux.
D‘autres warnings (que je ne détaille pas ici) existent, notamment en rapport avec les datasets déséquilibrés. Je vous laisse les découvrir ici. Pour terminer, vous aurez peut-être remarqué dans les exemples précédents qu’avec le wrapper de la méthode train_test_split() que propose skore, il est possible d’utiliser des keyword arguments pour passer X et y :
X_train, X_test, y_train, y_test = skore.train_test_split(X=X, y=y)
On a bien utilisé “X=” et “y=” pour passer des paramètres à la méthode, ce qui n’est pas possible avec la méthode de sklearn :
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X=X, y=y)
>>> TypeError: got an unexpected keyword argument 'X'
La syntaxe sklearn autorisée nécessitant des positional arguments (il faut passer les paramètres dans le bon ordre, donc avoir bien lu la documentation) :
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y)
Ce n’est pas grand-chose, mais cela peut éviter un bug lors d’une inversion malencontreuse de X et y dans les paramètres, alors on prend !
Conclusion
Voilà donc pour ce tour d’horizon de cette nouvelle librairie qui vient compléter l’utilisation de scikit-learn en facilitant l’évaluation des modèles de ML.
À mes yeux, skore se destine nettement à une utilisation en phase expérimentale (lorsque l’on explore et teste des choses dans des notebooks de façon un peu artisanale), plutôt qu’une utilisation dans un workflow MLOPS où l’on cherche à industrialiser l’entrainement et l’évaluation de modèles. Il n’en reste pas moins que skore est un outil très intéressant qui s’adresse aussi bien aux débutants (qui tireront vivement parti du système de warning) qu’aux initiés (qui apprécieront les différentes classes permettant d’obtenir un rapport d’évaluation).
Cette librairie n’en est qu’à ses débuts (c’est d’ailleurs pour le moment assez difficile d’obtenir des résultats pertinents sur les moteurs de recherches en tapant “skore”), mais elle est très prometteuse. Elle possède pour le moment peu de features, mais celles-ci sont très intéressantes et de nouvelles versions sont régulièrement publiées (par exemple les features importance viennent d’être ajoutées dans la 0.8). De plus la documentation est de qualité, à l'image de scikit-learn.
Ce nouveau venu n’est pas sans rappeler skrub, une autre librairie de l’écosystème scikit-learn sortie fin 2023 qui permet, elle, de faciliter la phase de pré-traitement des données. Combinée avec skore, ces deux “scikit-learn side kick” sont sans aucun doute les alliés idéals pour faciliter le développement de modèle de ML. Ce binôme gagnant fait d’ailleurs l’objet d’un très bon tutoriel skore présentant un projet de data science mené de bout en bout avec skrub, scikit-learn et skore.
Si vous avez aimé l'article, repéré une coquille, ou simplement envie d'échanger à ce sujet, n'hésitez pas à me faire signe sur LinkedIn !