Forecasting des séries temporelles avec la librairie fbprophet

En 2017, les équipes de recherche de Facebook publiaient ce papier qui introduira la librairie fbprophet, disponible en R et en Python (pas de jaloux !). Cet outil peut être rangé dans la catégorie des modèles additifs généraux car il décompose une série temporelle de la sorte :

y(t) = g(t) + s(t) + h(t) + e(t)

avec respectivement :

  • g(t) : la tendance (linéaire ou logistique)
  • s(s) : une ou plusieurs composantes saisonnières (annuelle, hebdomadaire ou quotidienne)
  • h(t) : l’effet des vacances ou de jours spécifiques qui pourront être paramétrés
  • e(t) : l’erreur, bruit aléatoire qui mesure l’écart entre le modèle et les données réelles

Si l’on retient le terme “additif”, il est bien sûr possible de modifier le modèle pour le rendre “multiplicatif” (observez les crêtes de votre série temporelles, si celles-ci forment un cône, le modèle est sans doute multiplicatif).

La grande force de ce type de modèle tient dans sa capacité à être interprété ainsi que dans la clarté des représentations graphiques de la décomposition. Il sera particulièrement adapté à des phénomènes comme… la fréquentation d’un réseau social mais aussi des mesures économiques fortement soumises à des saisonnalités et aux périodes de vacances d’un pays. Il est si simple à mettre en œuvre qu’il gagnera à être comparé à des modèles plus classiques comme SARIMA (nous en reparlerons en toute fin d’article).

Installation de la librairie

Pour ne pas “polluer” notre installation locale avec de nouveaux packages et leurs dépendances, nous créons au préalable un environnement virtuel, à l’aide du package pyenv pour Windows (voir ce GitHub). Ne pas oublier d’ajouter les variables d’environnement et de redémarrer votre terminal ou IDE pour terminer l’installation.

pyenv install 3.8.10

pyenv shell 3.8.10

Nous pouvons alors installer les librairies suivantes :

pip install pystan==2.19.1.1

pip install fbprophet

Pystan est une librairie pour l’inférence bayésienne. Si cette installation ne fonctionne pas (les dépendances sont parfois capricieuses…), vous pouvez utiliser un prompt Anaconda et tenter la commande suivante :

conda install -c conda-forge fbprophet

Vérifiez enfin la bonne installation en choisissant l’interpréteur voulu (ici, conda) dans Visual Studio Code.

Lancez ensuite Python dans le terminal et testez un import de la librairie.

Mise en pratique

Déroulons maintenant un exemple simple, de bout en bout, en réalisant quelques tentatives d’optimisation du modèle. RTE France met à disposition les données énergétiques “eco2mix” dont la profondeur d’historique et le niveau de détail sera intéressant pour évaluer notre outil. J’ai pris connaissance de ce jeu de données dans cet excellent article du blog de Publicis Sapient.

Une seule contrainte dans la façon dont les données doivent être soumises à Prophet : les colonnes de temps et de mesure quantitative doivent être respectivement nommées “ds” et “y”.

Une granularité plus fine que le jour peut être utilisée dans le champ datetime. C’est alors qu’il sera pertinent d’activer le daily effect qui sera visualisable graphiquement.

La régularité est un élément fondamental pour la modélisation d’une série temporelle, que viennent perturber les années bissextiles. Nous pouvons décider de supprimer les 29 février, par exemple avec le code ci-dessous :

df= df.loc[~(df['ds'].dt.month.eq(2) & df['ds'].dt.day.eq(29))]

Attention, nous dégradons alors inévitablement la régularité des semaines…

Création du modèle et évaluation

Lançons tout d’abord un modèle simple, sur l’intégralité de l’historique. Dans l’extrait de code ci-dessous, df représente un pandas dataframe regroupant les deux colonnes nommées ds et y.

De nombreux paramètres sont définis par défaut : additivité, pas de saisonnalité journalière, détection automatique des change points (changement de tendance), etc.

m = Prophet(daily_seasonality=False)
m.add_country_holidays(country_name='FR')
m.fit(df)

Ici, nous ajoutons au modèle les jours fériés français qui deviendront autant d’indicateurs dans notre modèle additif.

m.train_holiday_names

Comme l’indique cette discussion, il ne semble pas possible d’ajouter plusieurs pays à l’aide de cette méthode.

Nous pouvons également ajouter nos propres dates si nous considérons que des événements (répétitifs sur la saisonnalité attendue) ont un impact sur le phénomène observé. Nous pourrions par exemple identifier les journées les plus froides de l’année qui engendrent certainement une hausse de consommation électrique. Mais serons-nous en capacité de les prédire par la suite ? Mieux vaut se limiter à des événements connus à l’avance tels que des compétitions sportives (lors de la finale de la Coupe du Monde, tout le monde allume la télé !).

Les séries temporelles seraient si simples si toutes les tendances étaient linéaires ! Mais dans la vraie vie (et encore plus en période de pandémie…), les tendances fluctuent et il est indispensable que notre modèle les comprenne. Prophet identifie automatiquement les “change points” et on les visualise ainsi, en rouge, à l’aide du code ci-dessous.

from prophet.plot import add_changepoints_to_plot

fig = m.plot(forecast)
a = add_changepoints_to_plot(fig.gca(), m, forecast)

Nous pouvons aussi les définir arbitrairement. Pourquoi pas en donnant les dates des changements de saison (été et hiver) ?

m = Prophet(daily_seasonality=False,
changepoints=['2013-12-21', '2014-06-21', '2014-12-21', '2015-06-21', '2015-12-21', '2016-06-21', '2016-12-21', '2017-06-21'],
changepoint_prior_scale=1)

Le paramètre changepoint_prior_scale indique à quel point le modèle doit respecter notre liste (ou sa détection automatique). C’est une valeur entre 0 et 1. Avec 1, nos change points sont bien retenus, comme l’atteste ce graphique.

Forecast

Pour calculer une prévision, nous allons avoir besoin d’un nouveau dataframe contenant une colonne de type datetime, toujours nommée “ds”, que nous soumettrons à la méthode .predict(), appliquée au modèle.

future = pd.date_range(start="2020-01-01",end="2021-12-31")
future = pd.DataFrame(future, columns=['ds'])

Tout comme pour la méthode .fit(), la syntaxe est ainsi tout à fait similaire à celle du package scikit-learn.

forecast = m.predict(future)

Une autre méthode pour créer la plage de forecast consiste à utiliser la fonction ci-dessous.

future = m.make_future_dataframe(periods=365)

Cette fonction génère automatiquement une plage de dates couvrant l’historique complet auquel s’ajoute la période définie en paramètre. Ceci a pour avantage de nous permettre de comparer les prévisions avec les données réelles qui ont été utilisées pour le modèle, ce que nous allons observer dans les sorties graphiques.

Sorties graphiques

Pour afficher la superposition des historiques et des prévisions, un simple plot suffit !

m.plot(forecast)

Nous pouvons ensuite obtenir les différents graphiques de décomposition.

m.plot_components(forecast)

D’autres graphiques interactifs sont disponibles conjointement avec la librairie plotly.

Cross validation

Nous allons maintenant évaluer la qualité de la prévision à l’aide des métriques d’évaluation traditionnelles que sont MSE, RMSE, MAE, MAPE, etc. et d’une méthode de validation croisée. Ici encore, tout est intégré dans une fonction ! Analysons le code ci-dessous.

df_cv = cross_validation(m, initial='730 days', period='365 days', horizon='365 days')
df_p = performance_metrics(df_cv, rolling_window=1)

Nous retrouvons le modèle préalablement entraîné (m). Un seul paramètre est obligatoire, celui de l’horizon de prévision. Mais nous pouvons également préciser la période initiale d’entrainement (par défaut, 3 horizons, et en effet avec ces méthodes dites “à court termes”, ne vous aventurez pas à prédire au delà d’un tiers de votre historique !). Enfin, le paramètre period indique la taille des “découpes” faites dans le jeu de données pour établir de nouvelles prévisions (par défaut, un demi horizon). Voici un exemple de sortie de la commande lancée sur un cluster Databricks.

A partir d’un entrainement sur les années 2013 et 2014, la validation croisée a effectué 4 prévisions sur les années 2015 à 2018.


Nous obtenons alors les métriques d’évaluation, en moyenne sur le nombre de “folds“.

L’argument rolling_window indique le pourcentage de données considérées dans la prévision (1 équivaut à 100%).

Nous pouvons enfin rechercher les meilleurs hyperparamètres pour notre modèle.

Utiliser une approche par hyperparameter tuning implique de lancer un nombre important d’entrainements et d’évaluations. C’est ici que nous tirerons profit d’un cluster de machines, en précisant le paramètre parallel=’processes’ dans la fonction cross_validation(). L’exemple de code donné sur le site officiel sera simple à adapter.

Et maintenant, Kats !

La R&D de Facebook ne s’est pas arrêtée là ! En 2021, une nouvelle librairie est mise à disposition : Kats. Celle-ci a pour vocation de simplifier les tâches des Data Scientists autour de l’analyse et de la modélisation des séries temporelles.

Ici, pas de nommage particulier des colonnes, mais nous transformerons le dataframe en objet spécifique à Kats : TimeSeriesData().

Une première fonctionnalité consiste à déterminer automatiquement 65 features de la série temporelle, c’est-à-dire des caractéristiques de cette série (moyenne, variance, entropie, etc.) qui pourront être par la suite intégrées à des modèles de Machine Learning ou à une approche par régression de la modélisation de la série temporelle.

De nombreuses fonctionnalités s’orientent autour de la détection : seasonalities, outlier, change point, and slow trend changes

Enfin, Kats intègre un grand nombre de modèles (ARIMA, HW, stlf… et l’inévitable Prophet !). Bref, c’est le couteau suisse rêvé de tout.e Data Scientist qui s’attaque à un sujet de séries temporelles.

Repos Databricks & GitHub actions, better together !

Ou en bon français, comment faire coopérer la nouvelle (mars 2021) fonctionnalité Repos de Databricks avec une logique d’intégration continue développée dans GitHub ?

Revenons tout d’abord sur le point à l’origine de la nécessité d’un tel mécanisme (une présentation plus détaillée des Repos Databricks a été faite et mise à jour sur ce blog). Dans une architecture dite “moderne” sur Azure, nous utilisons Azure Data Factory pour piloter les traitements de données et lancer conjointement des notebooks Databricks. Cela nécessite de donner le chemin du notebook sur l’espace de travail déclaré en tant que service lié.

Nous disposons de trois entrées pour définir le “notebook path“.

Si nous choisissons “Repos”, nous allons donner un chemin contenant le nom du développeur !

Les développements livrés en production doivent bien sûr être totalement indépendants d’une telle information. Un contournement consisterait à utiliser un sous-répertoire Shared dans la partie Repos mais cela reviendrait à perdre le rattachement du développement à son auteur.

Nous allons donc mettre en place un mécanisme permettant aux développeurs d’utiliser leur propre répertoire puis de versionner les développements dans un espace tiers, par exemple un repository GitHub (une démarche exploitant les repositories Azure DevOps serait tout à fait similaire).

Voici le processus suivi lors d’un développement :

  • le développeur A crée une nouvelle branche sur laquelle il réalise ses développements
  • il “commit & push” ensuite son travail
  • une Pull Request (PR) peut alors être soumise dans le but de fusionner le travail avec la branche principale (master ou main)
  • le développeur B vient alors valider la PR et le merge se lance sur la branche principale

C’est alors que se déclenche notre processus d’intégration continue (CI) qui réalise une copie des notebooks de la branche principale dans le répertoire /Shared de l’espace de travail Databricks.

Nous nous assurons ainsi d’avoir toujours la dernière “bonne” version des notebooks, sur un chemin qui sera identique entre les environnements. En effet, dans un second temps, un processus de déploiement continu (CD) viendra copier ces notebooks sur les autre environnements (qualification, production).

Comment réaliser une GitHub Action d’intégration continue

Nous nous plaçons dans le repository servant à versionner les notebooks. Le menu Actions est accessible dans la barre supérieure.

Puis, nous cliquons sur le lien “set up a workflow yourself“.

Un modèle de pipeline YAML est alors disponible et nous allons l’adapter.

Détaillons maintenant le code final, étape par étape.

# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
  # Triggers the workflow on push or pull request events but only for the master branch
  pull_request:
    branches: [ master ]
    paths: ['**.py']

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    env:
      DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }}
      DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }}

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - name: Checkout
        uses: actions/checkout@v2
      
      - name: Setup Python
        uses: actions/setup-python@v2.2.2
        with:
          python-version: '3.8.10'
          architecture: 'x64'
      
      # Install pip
      - name: Install pip
        run: |
          python -m pip install --upgrade pip

      # Install databricks CLI with pip
      - name: Install databricks CLI
        run: python -m pip install --upgrade databricks-cli

      # Runs
      - name: Run a multi-line script
        run: |
          echo Test python version
          python --version
          echo Databricks CLI version
          databricks --version
          
      # Import notebook to Shared
      - name: Import local github directory to Shared
        run: databricks workspace import_dir --overwrite . /Shared
          
      # List Shared content
      - name: List workspace with databricks CLI
        run: databricks workspace list --absolute --long --id /Shared

Le principe est d’utiliser une machine virtuelle munie d’un système d’exploitation Ubuntu (‘latest’ pour obtenir la dernière version disponible), d’y installer un environnement Python dans la version souhaitée (ici, 3.8.10) puis le CLI de Databricks.

Pour connecter ce dernier à notre espace de travail, nous définissons au préalable deux variables d’environnement :

  • DATABRICKS_HOST qui contient l’URL de notre espace de travail
  • DATABRICKS_TOKEN qui contient un jeton d’authentification

Pour ne pas faire figurer ces valeurs dans le fichier YAML (qui est lui-même versionné dans le repository), nous définirons au préalable deux secrets à l’aide du menu Settings.

Nous choisissons ici une portée des secrets au niveau repository.

L’instruction uses: actions/checkout@v2 garantit la disponibilité des fichiers archivés dans le repository (ceux-ci sont copiés sur la machine virtuelle) et le point (.) indique simplement ce chemin dans l’instruction du CLI import_dir.

Ainsi, dès la fusion d’une Pull Request avec la branche principale, le code se lance automatiquement et nous pouvons vérifier sa bonne exécution dans le menu “View runs”.

Une telle approche sera bien sûr complétée idéalement par une stratégie de tests portant sur le contenu des notebooks ou mieux encore, sur du code packagé qui y sera utilisé (voir cet article).

Utiliser les services cognitifs Azure au travers des images Docker

Si vous connaissez depuis quelques années les services cognitifs Azure, vous êtes certainement habitués à les interroger grâce au endpoint proposé par Microsoft et relatif à la région où a été déclarée la ressource.

Exemple d’une ressource de Computer Vision dans la région East US 2

La contrainte est alors d’avoir une connexion Internet ouverte depuis l’application qui exploitera ce service. Les scénarios “full cloud” sont ici tout-à-fait appropriés. Mais qu’en est-il lorsque l’on souhaite bénéficier de ces mêmes services dans des contextes “on premises” ou “at edge ?

Des images Docker sont proposées au téléchargement par Microsoft et couvrent le périmètre des services présentés sur l’image ci-dessous.

C’est le site hub.docker.com qui réalise ces hébergements, plus précisément dans le compte suivant Azure Cognitive Services by Microsoft | Docker Hub

Tous les services cognitifs ne sont pas encore disponibles et certaines images apparaissent en préversion publique. Les images de la colonne “Gated” nécessitent de remplir un formulaire de demande d’accès au service.

La documentation complète officielle est accessible sur ce lien.

Les bénéfices attendus d’une telle approche sont en particulier des gains de performance (débit élevé, faible latence) et la mise à l’échelle potentielle qui sera favorisée par l’utilisation de services comme Kubernetes.

Nous allons réaliser ici un exemple local au moyen de l’API Text Analytics pour réaliser la analyse de sentiments. Il s’agira dans cet article de tester la documentation présentée ici.

Nous utiliserons un poste Windows 10 sur lequel est installé Docker Desktop, utilisant lui-même WSL2.

Nous devrons télécharger les images à l’aide des commandes docker pull données sur les pages du site hub.docker.com.

docker pull mcr.microsoft.com/azure-cognitive-services/textanalytics/sentiment

La commande suivante est plus complexe à construire et nous devrons y intégrer les informations suivantes, obtenues depuis le portail Azure :

  • BILLING ou ENDPOINT_URI : il s’agit du endpoint du service cognitif, débutant par le nom du sous-domaine donné à la création et sans le caractère / terminant l’URL
  • APIKEY : l’une des deux clés associée à la ressource

La commande complète prend la forme suivante (retirer les caractères <>) et se lance depuis un terminal :

docker run --rm -it -p 5000:5000 --memory 4g --cpus 1 mcr.microsoft.com/azure-cognitive-services/textanalytics/sentiment EULA=accept BILLING=https://mytextanalytics4docker.cognitiveservices.azure.com/ APIKEY=<**********>

Docker Desktop indique alors que l’image est en cours d’exécution.

Nous disposons maintenant de plusieurs URLs locales pour vérifier l’état du service et le tester.

http://localhost:5000/

http://localhost:5000/ready

{"service":"sentimentv3","ready":"ready"}

http://localhost:5000/status

{"service":"sentimentv3","apiStatus":"Valid","apiStatusMessage":"Api is valid."}

http://localhost:5000/swagger/index.html

Ce dernier lien va permettre de réaliser des tests, par exemple avec la méthode POST, en cliquant sur le bouton “Try it out”. Attention à bien basculer la liste déroulante en face de Request body sur “application/json”.

Nous visualisons le résultat suivant :

N’oubliez pas d’arrêter l’exécution de l’image en faisant par exemple un CRTL+C dans la fenêtre de commande où vous avez exécuté le docker run.

Forecasting par automated ML (UI)

Le portail Azure Machine Learning propose une interface graphique (UI) pour réaliser de premières modèles d’apprentissage dans trois cas de figure :

  • régression
  • classification
  • forecasting

Nous allons nous intéresser ici au forecasting, c’est-à-dire à la prévision sur des données issues d’une série temporelle (ou dite encore série chronologique).

Une série temporelle est un jeu de données composé tout simplement de deux colonnes : une mesure numérique et une variable de temps indiquant le moment de cette mesure. Il est indispensable que les intervalles de temps soient réguliers. Par exemple, nous pouvons disposer d’une mesure par jour, par semaine ou bien par mois, ou encore à des granularités plus fines comme l’heure, la minute, voire la seconde.

S’il existent des valeurs manquantes, il sera nécessaire de les “imputer” avant d’utiliser le jeu de données pour l’apprentissage automatisé.

Depuis le menu latéral, nous lançons une nouvelle exécution (“run“) d’automated ML.

Nous sélectionnons ensuite le jeu de données (“dataset“) répondant à la définition donnée ci-dessous d’une série temporelle.

Pour un premier exemple d’utilisation, nous choisirons le jeu de données du nombre de filles nées par jour en Californie en 1959, disponible sur ce lien.

Il faut ensuite donner un nom pour l’expérience (la trace de l’exécution dans le portail) et choisir une ressource de calcul de type compute cluster.

C’est à l’écran suivant que l’on choisira explicitement une tâche de type “time series forecasting“.

Avant de lancer l’exécution (bouton Finish), nous pouvons réaliser quelques paramétrages (attention, aucun retour arrière ne sera ensuite possible !).

Si les données ne sont pas agrégées pour ne disposer que d’une seule ligne par échelon de temps, il sera nécessaire de lister les “time series identifiers“, c’est-à-dire les colonnes qui permettraient de traiter plusieurs séries temporelles au travers de la même expérience. <Cela se traduira par la présence d’autres paramètres en entrée du service web prédictif.>

Deux options sont par défaut positionnées sur “autodetect” :

  • Frequency : il s’agit de la granularité temporelle du jeu de données. Les valeurs possibles sont présentées ci-dessous. Utilisez explicitement ce paramètre si un doute est possible ou qu’une agrégation est nécessaire (sum, min, max, mean).
  • Forecast horizon : il s’agit du nombre de périodes (dans l’unité du paramètre Frequency) qui seront prédites. La documentation officielle ne mentionne pas la règle appliquée dans le cas de l’auto-détection. Je vous recommande de le spécifier explicitement, en étant “raisonnable”, c’est-à-dire en ne dépassant pas le tiers de votre historique (ex.: pour 3 années d’entrainement, se limiter à une année de prévision).

Nous pouvons enfin définir des éléments de configuration additionnelle (cliquer sur “View additional configuration settings”).

Pour bloquer des algorithmes, il suffit de cliquer dans la liste déroulante.

Je vous conseille de conserver les méthodes dites “naïves” qui donneront un premier seuil pour les métriques d’évaluation. Vous pouvez également choisir de ne pas utiliser les méthodes non linéaires comme les RandomForest ou KNN. Les méthodes traditionnelles du domaine des séries temporelles sont présentes (autoARIMA, exponential smoothing) ainsi que la librairie issue de Facebook : Prophet.

Les méthodes d’apprentissage profond seront également évaluées en cochant la case “enable Deep Learning”. Ces méthodes ont montré leur efficacité mais rappelons qu’elles sont coûteuses en temps d’entrainement puis d’inférence et sur des problématiques relativement simples, elles ne donneront sans doute pas un gain significatif de performance.

Les trois métriques de comparaison des modèles sont :

  • normalized RMSE
  • R2
  • normalized MAE

Nous retrouverons cette métrique comme critère d’arrêt de l’expérience. Il faut alors renseigner un seuil entre 0 et 1 (les métriques sont normalisées et le R2 est par définition compris entre 0 et 1).

Il est important de ne pas se limiter à un seul indicateur d’évaluation, tous seront accessibles quand les différents modèles seront entrainés.

Je vous recommande aussi de borner le temps d’entrainement, afin de limiter les coûts mais également parce que vous obtiendrez sans doute assez rapidement une idée des modèles adaptés à vos données. Il sera ensuite plus efficace de bloquer les algorithmes les moins efficaces.

Si les deux critères sont utilisés, c’est le premier atteint qui arrêtera l’expérience.

Trois paramètres additionnels de configuration sont ensuite disponibles.

Ils correspondent respectivement à :

  • target lags : il s’agit ici d’exploiter des valeurs précédentes pour prédire les nouvelles valeurs. Par exemple, un lag de 1 permettra d’utiliser la valeur à t pour prédire t+1. Je vous recommande cet article pour approfondir ce sujet.
  • rolling window size : permet de ne tenir compte que d’une partie des données d’apprentissage. Par défaut, l’intégralité du jeu de données est considéré. Plus de détails sur cette méthode ici.
  • season and trend : active la méthode de décomposition STL (“Seasonal and Trend decomposition using Loess”), permettant d’isoler saisonnalité, tendance et bruit.

Une dernière case permet de sélectionner un (et un seul) pays pour tenir compte des vacances dans les calculs de saisonnalité, ce que fait par exemple le modèle Prophet.

La seule méthode de validation du modèle est la méthode ce validation croisée “k-fold”, avec k = 5 par défaut.

Enfin, les algorithmes se paralléliseront en fonction du nombre de nœuds du compute cluster défini préalablement.

Nous pouvons maintenant analyser les sorties produites par l’expérience d’automated ML.

Le run principal est constitué de children runs, correspondant chacun à un algorithme, précédé éventuellement d’une préparation de données (PCA, MinMaxScaler, etc.) , et avec un choix d’ hyperparamètres. L’onglet Models présente ces différentes exécutions, triées selon la valeur décroissante de la métrique principale.

Notons également l’onglet Data guardails (garde-fous) qui nous avertit sur d’éventuels problèmes de constitution de notre jeu de données initial (historique trop court, valeurs manquantes, etc.).

Il est très vraisemblable que le modèle “VotingEnsemble” soit le meilleur. C’est en effet un “méta-modèle” qui utilise plusieurs modèles simples et les fait voter pour obtenir les meilleures prévisions. C’est également un modèle plus lourd à exposer.

Il est tout à fait possible de choisir un autre modèle pour l’exposer.

Pour un test simple, nous choisissons une ressource de type Azure Container Instance (ACI) et n’enclenchons pas l’authentification (un jeton serait alors demandé lors de l’appel au service web).

Dans les paramètres avancés, nous pouvons réduire le CPU et la RAM utilisés (et donc la facture associée… mais aussi les performances !).

Un “virtual” CPU peut être défini par un nombre décimal.

Un clic sur “Deploy” lance le déploiement et nous pouvons basculer sur la page dédiée aux endpoints.

La ressource ACI se retrouvera également dans le portail Azure, dans le même groupe de ressources que le service Azure Machine Learning.

Pour consommer le modèle à partir du service web, nous devons utiliser des dates postérieures à la dernière date ayant servi à l’entrainement. Ceci peut se faire au travers de l’interface de test ou à l’aide des exemples de code donnés dans les langages C#, Python et R.

Nous testons ici la journée du 1er janvier de l’année suivante (1960).

En conclusion, nous avons ici un outil dédié aux “citizen data scientists” puisqu’il n’est jamais nécessaire d’écrire de code mais une (très) bonne connaissance de la théorie des différentes méthodes algorithmes et de l’interprétation des métriques d’évaluation sera indispensable. Nous obtenons très rapidement un service web prédictif efficient qui pourra être exploité par d’autres applications. N’oublions pas enfin que pour “vivre en production”, une telle approche ne sera pas suffisante. Nous ne maîtrisons pas ici en particulier le réentrainement du modèle ni l’archivage de ces versions. Une approche par le code, exploitant les librairies du SDK azureml, sera alors une démarche plus pérenne et respectant les bonnes pratiques du MLOps. Prochain article à venir !

Réviser la certification AI-102

Obtenir le titre “Azure AI Engineer Associate” ne demande que de passer une seule certification Microsoft, celle nommé AI-102 et dont le descriptif est disponible ici. Elle a remplacé la certification AI-100 en 2021 et s’oriente vers le choix et l’utilisation des services cognitifs Azure, alors que la précédente version pouvait également aborder des thèmes comme l’implémentation et le monitoring.

Je vous conseille toutefois d’associer à cette certification la AI-900 “Microsoft Azure AI Fundamentals” qui, comme toutes les “900”, se veut plus générique et moins technique (certains diront plus faciles à obtenir). Elle aborde en particulier les grands principes pour une utilisation responsable de l’Intelligence Artificielle :

  • fairness (équité)
  • reliability (fiabilité)
  • privacy (vie privée)
  • inclusiveness (inclusivité)
  • transparency (transparence)
  • accountability (responsabilité)

Pour préparer l’AI-900, utilisez les parcours d’apprentissage de Microsoft Learn, comme celui-ci.

Observons maintenant en détail les cinq compétences mesurées qui seront autant de chapitres dans la liste des éléments à réviser :

  • Planifier et gérer une solution Azure Cognitive Services
  • Mettre en œuvre des solutions de vision par ordinateur
  • Mettre en œuvre des solutions de traitement du langage naturel
  • Mettre en œuvre des solutions d’exploration des connaissances
  • Mettre en œuvre des solutions de AI conversationnelle

Le détail est fourni dans ce document PDF, en anglais, et il faut en surveiller les mises à jour.

La documentation Microsoft sera bien sûr l’un de vos principaux alliés. Préférez une lecture en anglais pour vous familiariser avec la terminologie utilisée dans les questions de l’examen.

Nous travaillerons autour des quatre familles principales de services cognitifs :

  • vision
  • langage
  • speech
  • décision

Plusieurs questions porteront vraisemblablement sur le choix du bon service pour répondre à des scénarios précis. Ces questions ne devraient pas vous poser de difficulté une fois que vous aurez en tête les grandes fonctionnalités de chacun des services listés sur l’image ci-dessus.

Les services de la catégorie Décision ne seront pas présentés dans les quatre autres chapitres.

Mais attention, le contenu détaillé agrandit un peu le périmètre des services de base et se réorganise de la sorte :

  • Computer Vision
  • Natural Language Processing
  • Knowledge Mining
  • Conversational AI

Nous allons donc avoir à faire à quelques services que Microsoft désigne maintenant par le terme “Azure Applied AI Services” (voir ce lien) et en particulier aux services Azure Bot et Cognitive Search.

Plan and Manage an Azure Cognitive Services Solution

Nous allons nous concentrer ici sur trois points du programme :

  • la création d’une ressource
  • les aspects de sécurité
  • l’utilisation de conteneurs

Implement Computer Vision Solutions

Les services cognitifs concernés par ce chapitre sont :

  • Computer Vision
  • Custom Vision
  • Face

Implement Natural Language Processing Solutions

Les services cognitifs concernés par ce chapitre sont :

  • Text Analytics
  • Speech to Text & Text to Speech
  • Translate
  • Language Understanding Service (LUIS)Computer Vision

Implement Knowledge Mining Solutions

L’unique service cognitif concerné par ce chapitre est Azure Cognitive Search.

A priori, et selon le programme détaillé, vous ne devriez pas rencontrer de questions sur l’API Bing Search (présentée ici).

Implement Conversational AI Solutions

Les services cognitifs concernés par ce chapitre sont :

  • QnA Maker
  • Bot Framework

Ne pas oublier le service Dispatch pour la gestion du multi-langue.

En conclusion

Nous espérons vous avoir fourni ici les premières bases pour guider vos révisions. N’oubliez pas que la pratique est indispensable (profitez des free tiers souvent disponibles qui n’affecteront pas votre crédit Azure) et méfiez-vous des bases de questions (et encore plus des réponses !) que l’on peut trouver sur Internet.

Décorateurs, Swagger et Pandas… pour Azure Machine Learning

Derrière ces trois notions, se cache une succession d’étapes nécessaires pour obtenir un service prédictif de qualité et apte à gérer des données qui ne sont pas uniquement numériques.
Prenons l’exemple du classique jeu de données German Credit disponible sur ce lien, sur lequel nous nous baserons pour entrainer un modèle d’apprentissage supervisé, dans une tâche de classification binaire (risque ou absence de risque sur le non remboursement d’un emprunt bancaire). Pour le déploiement d’un service web prédictif, nous allons utiliser le service Azure Machine Learning avec lequel nous interagirons au travers du SDK Python azureml-core, souvent évoqué sur ce blog (ici et ).

Même si leur champ de compétences grandit de jour en jour, les Data Scientists et Data Engineers ne sont pas attendus sur le développement de l’application finale qui offrira par exemple une interface de saisie et intègrera la restitution des prévisions. Pour autant, il est nécessaire de “passer la main” à une équipe de développeurs en fournissant une documentation claire et précise pour des personnes qui n’ont pas à se pencher sur des notions de feature selection ou encore feature engineering. Cette documentation s’établit communément sous le format dit Swagger qui correspond à un fichier JSON listant les colonnes en entrée du modèle ainsi que le type de données associé (texte, nombres entiers ou décimaux, dates voire fichier binaire dans un cas non structuré).

Afin d’obtenir ce résultat, nous devons préciser deux éléments dans la fonction de scoring qui supporte l’inférence du modèle. Rappelons que cette fonction Python score.py est ensuite hébergée sur une image Docker, stockée dans une ressource Azure Container Registry et exposée à l’aide d’Azure Container Instance ou Azure Kubernetes Services.

Nous disposerons alors de deux URLs, dont la propriété dns_name_label aura été précisée dans la définition de l’appel à la fonction deploy_configuration() sur l’objet AciWebservice :

  • celle du point de terminaison:
http://german-credit-classification.westeurope.azurecontainer.io/score
  • celle de la documentation Swagger :
http://german-credit-classification.westeurope.azurecontainer.io/swagger.json

Le fichier score.py se compose de deux fonctions au nom réservé :

  • init() qui récupère le binaire du modèle (par exemple au format Pickle) et le désérialise en mémoire
  • run(input) : qui récupère les données en entrée et applique la fonction .predict sur le modèle
def init():
     global model
     global encoder
# The AZUREML_MODEL_DIR environment variable indicates
# a directory containing the model file you registered.
     model_filename = 'german_credit_log_model.pkl'
     model_path = os.path.join(os.environ['AZUREML_MODEL_DIR'], model_filename)
     with open(model_path, 'rb') as f:
          encoder, model = joblib.load(f)

Pour garantir la cohérence des données en entrée (nombre de colonnes et type des données) ainsi qu’en sortie, nous allons définir deux objets appelés décorateurs. Ces objets Python servent à modifier le comportement d’une fonction existante. Cette page GitHub documente ce que Microsoft appelle l’InferenceSchema.

A partir du SDK Python, nous allons commencer par importer les éléments nécessaires à la création des décorateurs.

from inference_schema.schema_decorators import input_schema, output_schema from inference_schema.parameter_types.standard_py_parameter_type import StandardPythonParameterType from inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType from inference_schema.parameter_types.pandas_parameter_type import PandasParameterType

Nous disposons de trois types pour ces décorateurs :

  • le type standard que nous utiliserons pour une valeur simple comme par exemple un paramètre en entrée ou une valeur en sortie
  • le type Numpy Array soit un tableau de nombres en entrée (les features sont alors uniquement numériques) ou un tableau de valeurs en sortie (une prévision numérique pour une régression mais pourquoi pas un tableau de probabilités associées à chaque classe pour une classification)
  • le type Pandas Dataframe qui permet de faire entrer un jeu de données de multiples types, ce qui est bien plus souvent le cas dans la vraie vie que dans les tutoriels visibles sur le Web !

Voici la définition des décorateurs en entrée et sortie pour une tâche de classification sur le jeu de données German Credit. Il faut veiller à donner un exemple respectant le type de données de chaque colonne. Pour obtenir facilement ces informations, je vous recommande de lancer la commande df.iloc[:,1] sur le dataframe.

input_sample = pd.DataFrame(data=[{
     "Status of existing checking account": "A12",
     "Duration in month": 48,
     "Credit history": "A32",
     "Purpose": "A43",
     "Credit amount": 5951,
     "Savings account/bonds": "A61",
     "Present employment since": "A73",
     "Installment rate in percentage of disposable income": 2,
     "Personal status and sex": "A92",
     "Other debtors / guarantors": "A101",
     "Present residence since": 2,
     "Property": "A121",
     "Age in years": 22,
     "Other installment plans": "A143",
     "Housing": "A152",
     "Number of existing credits at this bank": 1,
     "Job": "A173",
     "Number of people being liable to provide maintenance for": 1,
     "Telephone": "A191",
     "foreign worker": "A201",
 }])
 output_sample = np.array([0])

@input_schema('data', PandasParameterType(input_sample))
@output_schema(NumpyParameterType(output_sample))

La méthode .predict() de Scikit-Learn accepte en entrée soit un tableau Numpy soit un Pandas dataframe. Nous n’aurons donc pas à intervenir sur le script score.py, le décorateur fera le travail d’interprétation des données en entrée et en particulier associera le nom des colonnes pour un Pandas dataframe.

Jetons un oeil du côté de la syntaxe classique d’appel au service web prédictif, ici en Python mais cet appel peut se faire dans de multiples langages. La seule contrainte est de passer les données dans un format JSON.

def run(data):
    
    print(data.shape)
    
    df_text = data.select_dtypes(include='object')
    df_num = data.select_dtypes(include='int64')

    df_text_encoded = pd.DataFrame(encoder.transform(df_text).toarray())
    df_encoded = pd.concat([df_num, df_text_encoded], axis=1)
    
    # Use the model object loaded by init().
    result = model.predict(df_encoded)
    
    # You can return any JSON-serializable object.
    return result.tolist()

Explorons maintenant le détail du code de la fonction run(). Celle-ci attend un paramètre en entrée et il est important de respecter le nom associé au Dataframe dans sa définition faite au sein du décorateur d’entrée (la casse également, attention aux majuscules et minuscules !).
La première étape consiste à lire le JSON en entrée grâce à la fonction json.loads(). N’oubliez pas de faire un import json dans le début du script ainsi que de charger la librairie pandas dans l’environnement d’inférence.

Nous passons ensuite un traitement spécifique aux colonnes de type texte, qui était lui-même stocké dans le fichier pickle.

Il n’y a plus qu’à demander la prédiction à partir du modèle avec l’instruction model.predict(data) puis à restituer le résultat en convertissant le Numpy array en liste, objet dit “JSON-serializable”.

Modification de la méthode de prévision

Puisque nous travaillons sur une classification binaire, nous pourrions préférer appliquer au modèle la méthode predict_proba() et

Voici le nouveau code à positionner dans la fonction score.py.

pandas_sample_input = pd.DataFrame(data=[{
    "Status of existing checking account": "A12",
    "Duration in month": 48,
    "Credit history": "A32",
    "Purpose": "A43",
    "Credit amount": 5951,
    "Savings account/bonds": "A61",
    "Present employment since": "A73",
    "Installment rate in percentage of disposable income": 2,
    "Personal status and sex": "A92",
    "Other debtors / guarantors": "A101",
    "Present residence since": 2,
    "Property": "A121",
    "Age in years": 22,
    "Other installment plans": "A143",
    "Housing": "A152",
    "Number of existing credits at this bank": 1,
    "Job": "A173",
    "Number of people being liable to provide maintenance for": 1,
    "Telephone": "A191",
    "foreign worker": "A201",
}])
method_sample_input = "predict"
output_sample = np.array([0])

@input_schema('data', PandasParameterType(pandas_sample_input))
@input_schema('method', StandardPythonParameterType(method_sample_input))
@output_schema(NumpyParameterType(output_sample))

def run(data, method):
        
    df_text = data.select_dtypes(include='object')
    df_num = data.select_dtypes(include='int64')

    df_text_encoded = pd.DataFrame(encoder.transform(df_text).toarray())
    df_encoded = pd.concat([df_num, df_text_encoded], axis=1)
    
    print(method)
    # Use the model object loaded by init().
    result = model.predict(df_encoded) if method=="predict" else model.predict_proba(df_encoded)

    # You can return any JSON-serializable object.
    return result.tolist()

Nous exploitons ici deux paramètres en entrée de la fonction run(). Le second est du type StandardPythonParameterType, c’est-à-dire tout simplement une chaîne de texte ! Ensuite, nous jouons avec une condition pour appliquer soit predict(), soit predict_proba() qui renverra alors les probabilités appliquées à chacune des deux classes :

[[0.27303021717776266, 0.7269697828222373]]

En conclusion

Voilà un fonctionnement qui est satisfaisant autant pour les développeurs du modèle et du service que pour les personnes qui l’exploiteront ensuite. Attention toutefois à ne pas oublier de venir modifier ces décorateurs si jamais votre modèle n’utilise plus les mêmes données en entrée. Il est recommandé de rester à l’identique de la donnée de départ et si une étape de préparation (feature engineering, scaling…) est nécessaire, il faudra l’inclure dans un pipeline, ce que nous verrons dans un prochain article.

Le notebook complet sera disponible dans ce repository.

Découverte de l’autoML experiment sous Azure Databricks

En 2021, toute société proposant une plateforme autour de la data semble vouloir se doter d’un outil d’automated Machine Learning, sorte de “force brute” de la recherche du meilleur algorithme. Databricks ne déroge pas à la règle et propose depuis peu (mai 2021) un menu de création d’une expérience “AutoML”.

Il s’agit à ce jour d’une fonctionnalité en préversion et celle-ci est documentée sur ce lien : Databricks AutoML | Databricks on AWS

Le concept d’expérience au sein de Databricks se rattache historiquement à l’utilisation de MLFlow pour le stockage, versionning et déploiement de modèles d’apprentissage. L’approche proposée ici s’adresse directement aux “citizen data scientists” au travers d’une interface graphique.

Nous aurons bien sûr besoin d’un cluster pour exécuter le code puis nous pourrons choisir entre les deux problématiques supervisées que sont la classification et la régression. La prévision sur série temporelle (forecasting) sera disponible prochainement.

Ce cluster doit disposer d’un runtime 8.3 ML ou supérieur (à venir), incluant donc Spark 3 et des packages spécifiques pour l’apprentissage automatique.

On devra ensuite désigner le dataset à utiliser. Celui-ci doit exister sous forme de table sur un cluster de l’espace de travail (pas obligatoirement celui qui exécutera l’entrainement), cluster devant être démarré pour que les tables soient visibles… et accessibles !

La table choisie doit présenter des données entièrement préparées pour le processus d’apprentissage (nettoyage des valeurs aberrantes, feature selection, feature engineering, etc.) car de telles opérations ne seront pas possibles par la suite.

Nous désignons ensuite la “prediction target“, puisque nous travaillons dans une approche d’apprentissage supervisé.

Dans les options d’évaluation, nous pourrons modifier la métrique d’évaluation qui servira à comparer les différents modèles, ainsi que donner des conditions d’arrêt, soit sur le temps d’entrainement, soit sur le nombre maximum d’essais réalisés.

C’est parti, l’expérience se lance !

Pas de chance, échec dès le démarrage, mais nous allons chercher la cause.

Nous disposons pour cela d’un notebook contenant l’exécution du job.

Des valeurs vides viennent polluer notre variable cible (“target“). Une meilleure préparation de données aurait dû être réalisée. Heureusement, Databricks vient à nouveau à notre secours avec un second lien vers un notebook de “Data exploration”.

Il s’agit du package pandas_profiling qui est mis en oeuvre dans un notebook.

En affichant le détail, nous retrouvons bien les 284 valeurs manquantes mais nous pouvons aussi observer des valeurs extrêmes, potentiellement aberrantes.

Nous allons repartir d’un dataset plus simple et déjà nettoyé : “German Credit” (disponible par exemple ici), que nous pouvons uploader directement sur le FileStore depuis le menu Data.

Relançons maintenant une expérience d’autoML, cette fois-ci sur une tâche de classification (la variable binaire class est ici la cible).

Au bout du temps imparti ou du nombre d’itérations, nous obtenons une liste des algorithmes que nous pouvons trier selon les différentes métriques.

Une très bonne surprise est de trouver, associé à chaque exécution, le notebook correspondant !

C’est encore XGBoost qui a gagné (comme souvent sans feature engineering préalable).

Celui-ci suit une cheminement tout à fait classique, donné par le plan en Markdown.

Une preprocessing des données est appliqué, sous forme de pipeline Scikit Learn, pour les différents types de variables :

  • variables binaires : imputation des valeurs manquantes et recodage en 0/1
  • variables numériques : imputation par la moyenne
  • variables catégorielles : dichotomisation (one hot encoding)

Cette première étape du pipeline est suivi d’un standardisation, par exemple avec la méthode StandardScaler(), toujours issue du package Scikit Learn.

Nous pouvons visualiser ce pipeline graphiquement.

Le modèle est lui aussi explicitement codé et nous pouvons donc découvrir les valeurs spécifiques des hyperparamètres du modèle utilisés lors de l’exécution.

Enfin, une explication du modèle par la méthode SHAP, classant les features par importance, a été codée ainsi que les commandes MLFlow permettant d’enregistrer puis de charger un modèle (inférence).

En cliquant sur le lien de la colonne “Models”, nous retrouvons les artefacts liés au modèle : un binaire sérialisé au format Pickle, mais aussi quelques graphiques comme la courbe ROC ou la matrice de confusion.

Il ne restera plus qu’à enregistrer le modèle dans MLFlow register pour pouvoir ensuite l’exposer.

En conclusion, nous avons ici un outil d’apprentissage automatisé qui vient ajouter une fonctionnalité à “la plateforme unifiée de données” qu’est Databricks. Cet outil se destine, à mon sens, à des Data Scientists voulant gagner du temps sur le codage de modèles simples (issus de la bibliothèque de Scikit Learn), sur des données déjà contrôlées et nettoyées (a minima, le retrait des valeurs aberrantes).

Nous pourrons regretter l’absence d’alertes automatiques sur les colinéarité entre variables soumises au modèle mais le notebook d’exploration basé sur pandas_profiling nous permet d’obtenir ces informations. Ce type d’outils pousse facilement au sur-apprentissage et c’est une limite qu’il faudra bien garder en tête.

Le fait de proposer le code sous forme de notebooks est un très grand avantage sur d’autres plateformes d’automated ML (rien n’empêche d’améliorer ce code par soi-même !). En étant exigeants, nous pourrions attendre de Databricks que celui-ci soit écrit pour profiter de la puissance du calcul distribué sur les nœuds du cluster (pourquoi pas avec le package koalas ?) mais des évolutions viendront sûrement prochainement.

Créer une ressource Azure Machine Learning

Voici un article simple pour vous accompagner dans les premières étapes de création d’un espace de travail (workspace) Azure Machine Learning, et en particulier sur le choix des paramètres de ce service et de ceux qui l’accompagnent.

Depuis le portail Azure, nous commençons par créer un groupe de ressources dédié.

Une bonne (excellente !) pratique consiste à ajouter des tags lors de la création de chaque ressource Azure. Nous appliquerons également cette pratique lors de la création des éléments propres à l’univers Azure ML.

Le groupe de ressources est maintenant prêt. En cliquant sur le bouton “Create” puis en recherchant “Machine Learning”, nous trouvons le service recherché.

Même si nous ne choisissons ici qu’un seul service, il faut comprendre que celui-ci s’accompagnera de trois à quatre autres services Azure, comme détaillé sur ce schéma, issu de la documentation officielle de Microsoft.

https://docs.microsoft.com/fr-fr/azure/machine-learning/concept-azure-machine-learning-architecture

Décrivons rapidement ces services :

  • Storage Account : le compte de stockage, où seront écrits tous les fichiers nécessaires à l’utilisation d’Azure ML comme par exemple des jeux de données importés, des journaux d’exécution, des artefacts de modèles, etc.
  • Key Vault : le magasin de secrets qui permettra le stockage sécurisé des clés, jetons d’authentification, mots de passe, etc.
  • Application Insights : fonctionnalité d’Azure Monitor, ce service permettra le suivi des ressources en temps réel, comme par exemple la disponibilité et les temps de réponse d’un point de terminaison (endpoint)
  • Container Registry : cette ressource est facultative, elle ne deviendra nécessaire que lors du déploiement de modèles prédictifs sur un service web. Mais comme ce scénario correspond à la finalité de nombreux cas d’usage du Machine Learning, nous ne pourrons nous en passer, dans les faits. Ce sont des images Docker qui seront enregistrées dans cette ressource.

Des règles de nommage (malheureusement variables d’un service à l’autre) devront être respectées : nombre de caractères, autorisation ou non de l’usage des tirets haut ou bas, chiffres… et parfois unicité du nom dans l’ensemble du cloud Azure !

Pour un usage en entreprise, le plan de nommage ne doit pas être pris à la légère. Il ne serait pas incohérent d’y passer plusieurs jours de réflexion tant celui-ci sera ensuite immuable et jouera sur la capacité des utilisateurs à bien appréhender les ressources (distinguer les environnements de développement et de production, simplifier l’analyse de la facturation, etc.).

Idéalement, ou dans une approche d’Infrastructure as Code avec l’outil Terraform, nous aurions préalablement créé ces quatre services, dans le même groupe de ressources, afin de les associer dans cet écran de paramétrage.

Nous pouvons néanmoins les créer individuellement en cliquant sur le bouton “Create new“. Nous n’aurons toutefois pas la main sur l’ensemble des paramètres disponibles lors de la création individuelle de ces ressources.

Pour le compte de stockage, nous choisissons le niveau de redondance le plus faible, et donc le moins coûteux: LRS.

Ce choix s’entend dans une approche de test, mais pour un usage “en production”, il se poserait la question de comment restaurer le service Azure ML en cas de perte du compte de stockage associé. La documentation actuelle ne fait pas part de ce type de procédure.

Pour la création de la ressource Container Registry, nous choisissons le mode de licence Basic qui autorisera la stockage, sans surcoût, de 10Go d’images.

Voici donc nos ressources créées et correctement nommées.

Nous ne modifierons pas ici les paramètres réseaux par défaut. Ceux-ci autorisent la connexion issue de tous réseaux à notre service.

Deux options sont disponibles dans les paramètres avancés, nous les laisserons positionnées par défaut.

Terminons enfin par cette bonne pratique consistant à taguer notre nouveau service.

La ressource est maintenant créée et prête à l’usage !

Notez le bouton “download config.json“, ce fichier contiendra des informations de connexion à la ressource depuis un autre environnement que le portail Azure Machine Learning.

Nous retrouvons bien nos quatre ressources liées, listées sur la droite de la page.

Le bouton “Launch studio” permet d’ouvrir dans une nouvelle fenêtre le portail dédié à cet espace de travail Azure ML.

EDIT 20210616: la configuration du Key Vault n’est pas tout à fait complète ou tout du moins utilisable. En effet, même si vous êtes “owner” de la ressource, vous ne pourrez pas par défaut ajouter de secrets.

Il sera nécessaire d’ajouter une “access policy” pour l’utilisateur (à désigner dans la case “select principal”, à partir des utilisateurs déclarés dans Azure Active Directory).

Ne pas oublier d’enregistrer cette nouvelle policy avec le bouton “save” !

Il sera alors possible d’y renseigner des informations propres à la connexion, comme par exemple l’account key d’un compte de stockage ou le client secret d’un principal de service.

Ensuite, une approche par le code permettra de créer de nouveaux datastores.

Le code illustré ci-dessus est disponible dans ce notebook.

En résumé, toute cette installation mériterait d’être abordée en infra as code, par exemple avec l’outil Terraform !

Gestion des repos Git dans Azure Databricks

Au mois de mars 2021, une nouvelle fonctionnalité vient accompagner les espaces de travail Azure Databricks : les repos Git.

Pour l’activer, nous devons passer par la console d’administration, cliquer sur “Enable” puis rafraîchir la page.

Une nouvelle icône apparaît alors dans le menu latéral.

Jusqu’ici, le lien avec un gestionnaire de versions se faisait individuellement, par le biais de de la fenêtre “user settings“, comme expliqué dans cet article.

Chaque notebook devait alors être relié manuellement à un repository. Pour des actions de masse, il fallait employer les commandes en ligne (Databricks CLI).

Pour démarrer, nous cliquons sur l’icône Repos et le menu propose un bouton “Add Repo“.

Une boîte de dialogue s’ouvre alors.

Il faut comprendre ici qu’il s’agit de créer un repository local au sens de l’espace de travail (“Add Git remote later”) ou bien de cloner un reporemote“, c’est-à-dire relié à un des fournisseurs présents dans la liste. Ce dernier peut être vide si le projet débute.

Nous créons ici un premier notebook dans ce nouveau repository local.

Il faut alors s’habituer à une nouvelle façon de travailler. Les notebooks ne sont plus visibles depuis l’icône “Workspace”. Le lien “Git: Synced” que l’on trouvait en cliquant sur “Revision history” n’existe plus. Mais une icône Git apparaît maintenant en haut à gauche du notebook.

L’historique des modifications s’enregistre toujours bien automatiquement et des retours arrières (“Restore this version”) sont possibles.

Sans réaliser l’association avec un gestionnaire de versions tiers, nous ne disposons pas d’autres fonctionnalités.

Un clic sur l’icône Git nous propose de réaliser cette association, ici avec le service Azure DevOps.

Attention, on ne peut attacher qu’un dépôt qui ne contient pas de commit.

Pour un repository de type privé, il va être nécessaire de réaliser des manipulations de sécurité.

Il s’agit de fournir un Personal Access Token (PAT) généré depuis le gestionnaire de versions.

Une fois la configuration réalisée, la boîte de dialogues ci-dessous apparaît alors.

Nous retrouvons des notions familières aux utilisateurs de Git : branche (master ou main), pull, commit & push.

Il sera nécessaire (c’est même obligatoire) de saisir un résumé (summary) des modifications pour pouvoir cliquer sur le bouton “Commit & Push“.

Attention, vous pourriez rencontrer l’erreur suivante.

En effet, une liste de repositories autorisés doit être donné dans la console d’administration (seulement si cette option a été activée). Ce scénario s’inscrit dans le cadre de la licence Premium de Databricks où tous les utilisateurs ne sont pas obligatoirement administrateurs.

Notons que l’extension du fichier est .py mais il s’agit bien d’un notebook Databricks. Le contenu de celui-ci, en particulier les cellules Markdown, est arrangée pour “ressembler” à un script Python.

Attention, une commande comme display est propre à l’environnement des notebooks Databricks.

Si le repository est aussi utilisé pour conserver d’autres éléments que ceux propres à Databricks, nous verrons apparaître les dossiers du repo dans le menu de navigation de Databricks. Il pourrait être tentant de les supprimer depuis Databricks (ils sembleront vides puisque seules quelques extensions .py et .ipynb sont affichées) mais ne le faîtes surtout pas !

Une action “removed” serait alors être proposée au prochain commit. Il sera bien sûr possible de l’éviter en basculant sur “Discard changes“.

Ici, le repo est partagé avec des pipelines Azure Data Factory.

Le répertoire “vide” va alors réapparaître dans l’arborescence.

En conclusion, il serait préférable de dédier un repository à Databricks pour éviter les confusions.

EDIT 2021-08-23 :

La fonctionnalité originelle de liaison avec un repository Git est dorénavant une “legacy feature” et il est recommandé de ne plus l’utiliser.

En poussant les tests, on obtient un blocage pour commiter depuis Databricks lors du scénario suivant :

  • publication réalisée depuis un outil tiers dans le repository partagé (par exemple, création d’un nouveau pipeline dans Azure Data Factory)
  • modifications d’un notebook dans Azure Databricks
  • sauvegarde de ces modifications
  • tentative de commit & push depuis Databricks

Nous obtenons alors le message d’erreur ci-dessous.

Il est indispensable de réaliser une action Pull avant de modifier les notebooks et nous recommandons de le faire avant même de débuter vos travaux sous Databricks.

Un message d’alerte s’affiche alors.

Toutes les limitations liées à la fonctionnalité “Repos” dans Azure Databricks sont données sur ce lien officiel de la documentation Microsoft.

Associer Azure Databricks et Azure Machine Learning

Si les deux outils ont en partie des fonctionnalités qui se recouvrent, avec quelques différences (voir cet article), il est tout à fait possible de tirer profit du meilleur de chacun d’eux et de les associer dans une architecture data sur Azure.

Le scénario générique d’utilisation sera alors l’industrialisation de modèles d’apprentissage (machine learning) entrainés sur des données nécessitant la puissance de Spark (grande volumétrie voire streaming).

Nous allons détailler ici comment Databricks va interagir avec les services d’Azure Machine Learning au moyen du SDK azureml-core.

A l’inverse, il sera possible de lancer, depuis Azure ML, des traitements qui s’appuieront sur la ressource de calcul Databricks déclarée en tant que attached compute.

Le SDK azureml sous Databricks

Nous commencerons par installer le package azureml-sdk[databricks] au niveau du cluster interactif (d’autres approches d’installation sont possibles, en particulier pour les automated clusters). Le cluster doit être démarré pour pouvoir effectuer l’installation.

Nous pouvons vérifier la version installée en lançant le code suivant dans un notebook :

import azureml.core
print("Azure ML SDK Version: ", azureml.core.VERSION)

Un décalage peut exister avec la version du SDK non spécifique à Databricks. Des dépendances peuvent également être demandées pour certaines parties du SDK comme l’annonce l’avertissement reçu à l’exécution de la commande précendente.

Failure while loading azureml_run_type_providers. Failed to load entrypoint automl = azureml.train.automl.run:AutoMLRun._from_run_dto with exception (cryptography 3.1.1 (/databricks/python3/lib/python3.8/site-packages), Requirement.parse('cryptography<4.0.0,>=3.3.1; extra == "crypto"'), {'PyJWT'}).

L’étape suivante consistera à se connecter à l’espace de travail d’Azure Machine Learning depuis le script, ce qui se fait par le code ci-dessous :

from azureml.core import Workspace

ws = Workspace.from_config()

Plusieurs possibilités s’ouvrent à nous pour réaliser ce lien. La première consiste à établir explicitement ce lien dans le portail Azure, depuis la ressource Databricks.

Mais cette approche ne permet d’avoir qu’un seul espace lié, celui-ci n’étant plus modifiable au travers de l’interface Azure (mais peut-être en ligne de commandes ?). La configuration étant donnée par un fichier config.json, téléchargeable depuis la page du service Azure ML, nous pouvons placer ce fichier sur le DataBricks File System (DBFS), et donner son chemin (au format “File API format”) dans le code .

from azureml.core import Workspace

ws = Workspace.from_config(path='/dbfs/FileStore/azureml/config.json')

Une authentification “interactive” sera alors demandée : il faut saisir le code donné dans l’URL https://microsoft.com/devicelogin puis entrer ses login et mot de passe Azure.

Pour une authentification non interactive, nous utiliserons un principal de service.

Nous pouvons maintenant interagir avec les ressources du portail Azure Machine Learning et en particulier, exécuter notre code au sein d’une expérience.

from azureml.core import experiment
experiment = Experiment(workspace=ws, name="ExperimentFromDBXs")

Après l’entrainement d’un modèle, idéalement réalisé grâce à Spark, nous pouvons enregistrer celui-ci sur le portail.

L’instruction run.log() permet de conserver les métriques d’évaluation.

A noter que les widgets ne peuvent pas s’afficher dans les notebooks Databricks.

Databricks comme attached compute

Comme pour toute ressource de calcul, nous commençons par déclarer le cluster Databricks dans le portail Azure Machine Learning.

Un jeton d’accès (access token) est attendu pour réaliser l’authentification. Nous prendrons soin de le générer grâce à un principal de service pour éviter qu’il ne soit attaché à une personne (“Personal Access Token”).

La ressource est maintenant correctement déclarée.

Nous allons l’exploiter au travers du SDK Python azureml dans lequel nous disposons de deux objets liés à Databricks.

La documentation présente la classe qui permettrait de créer cette ressource au travers du code :

DatabricksCompute(workspace, name)

Nous trouvons aussi dans la documentation l’objet qui crée une étape de pipeline exécutée ensuite au sein d’une expérience :

DatabricksStep(name, inputs=None, outputs=None, existing_cluster_id=None, spark_version=None, node_type=None, instance_pool_id=None, num_workers=None, min_workers=None, max_workers=None, spark_env_variables=None, spark_conf=None, init_scripts=None, cluster_log_dbfs_path=None, notebook_path=None, notebook_params=None, python_script_path=None, python_script_params=None, main_class_name=None, jar_params=None, python_script_name=None, source_directory=None, hash_paths=None, run_name=None, timeout_seconds=None, runconfig=None, maven_libraries=None, pypi_libraries=None, egg_libraries=None, jar_libraries=None, rcran_libraries=None, compute_target=None, allow_reuse=True, version=None)

Il est possible de lancer un script Python qui sera exécuté sur le cluster Databricks mais l’usage le plus intéressant est sans doute l’exécution d’un notebook.

from azureml.core.compute import DatabricksCompute
from azureml.pipeline.steps import DatabricksStep

databricks_compute = DatabricksCompute(workspace=ws, name="my-dbx-cluster")

dbNbStep = DatabricksStep( name="DBNotebookInWS",
 num_workers=1,
 notebook_path=notebook_path,
 run_name='DB_Notebook_demo',
 compute_target=databricks_compute,
 allow_reuse=True
)

Nous pourrons alors suivre les logs du cluster dans l’expérience.

Je vous recommande ce notebook pour découvrir tous les usages de DatabricksStep, comme par exemple l’exécution d’un JAR.

En conclusion

La souplesse des services managés et la séparation stockage / calcul autorisent aujourd’hui à penser les architectures data avec les services qui sont les plus utiles au moment souhaité, quitte à avoir des redondances de fonctionnalités. Nous veillerons à préserver au maximum l’indépendance de certaines parties du code (a minima la préparation de données et l’entrainement) vis à vis des plateformes propriétaires. Il sera alors possible d’envisager des alternatives chez d’autres fournisseurs cloud ou bien encore dans le monde de l’Open Source.