Modèles de substitution avec Julia et JupyterLab dans la plateforme Serverless Azure Container Apps…
Tags: azure, serverless, docker, kubernetes
Published: 2022-05-28 23:30:16

---
title: Modèles de substitution avec Julia et JupyterLab dans la plateforme Serverless Azure Container Apps…
published: true
date: 2022-05-28 22:23:45 UTC
tags: azure,serverless,docker,kubernetes
canonical_url: https://deep75.medium.com/mod%C3%A8les-de-substitution-avec-julia-et-jupyterlab-dans-la-plateforme-serverless-azure-container-apps-384dfdb6a79c
---

![](https://cdn-images-1.medium.com/max/900/0*4mEsDKuf7qwCW9A2.png)

L’année dernière, à Ignite, Microsoft a annoncé une offre cloud native avec Azure Container Apps, permettant aux développeurs de créer des architectures de microservices à l’aide de conteneurs en avant-première en mode Serverless. La société a maintenant annoncé la disponibilité générale du service lors de la conférence annuelle Build.

Azure Container Apps repose sur une technologie open source avec des projets CNCF tels que Kubernetes Event-Driven Autoscaling (KEDA) , Distributed Application Runtime (Dapr) et Envoy s’exécutant sur Azure Kubernetes Service (AKS).

[Conteneurs dans Azure Container Apps](https://docs.microsoft.com/fr-fr/azure/container-apps/containers)

La version générale d’Azure Container Apps inclut désormais de nouvelles fonctionnalités telles que la possibilité pour les développeurs de sécuriser leurs applications en les déployant sur un réseau virtuel à l’aide d’ identités managées pour accéder à d’autres ressources protégées Azure Active Directory (Azure AD).

De plus, ils peuvent configurer l’authentification et l’autorisation intégrées pour leurs applications externes. De plus, des domaines et des certificats personnalisés peuvent être utilisés pour personnaliser les applications. En outre, diffusion en temps réel des journaux pour les messages de journal stdout et stderr, connexion aux consoles du conteneur , examen des métriques et définition des alertes peuvent tous être utilisés pour surveiller la santé et les performances des applications.

[Azure Container Apps overview](https://docs.microsoft.com/en-us/azure/container-apps/overview)

Azure Container Apps peut être utilisé dans divers contextes, des points de terminaison d’API publics aux microservices. Le service permet l’exécution de code d’application encapsulé dans n’importe quel conteneur, quel que soit l’environnement d’exécution ou le modèle de programmation. De plus, le code peut être développé dans par exemple avec Visual Studio Code, le portail Azure et Azure CLI pour gérer les ressources Azure Container Apps.

![](https://cdn-images-1.medium.com/max/1000/0*NbN-oV0DvBb_qQZ_.png)

![](https://cdn-images-1.medium.com/max/600/0*WOdaCrpAslpAAxhK.jpg)

Je vais ici faire un test via le portail Azure car la partie CLI est très bien documentée ici :

[Quickstart: Deploy your first container app](https://docs.microsoft.com/en-us/azure/container-apps/get-started?tabs=bash)

puisque Azure Container Apps est disponible depuis le marketplace :

![](https://cdn-images-1.medium.com/max/1024/1*OomxAKzLr3586WpF2PYhSQ.png)

[Applications Azure Container | Microsoft Azure](https://azure.microsoft.com/fr-fr/services/container-apps/?ocid=AID3042118)

Je définis le nom du Resource Group ainsi que celui du conteneur depuis le portail :

![](https://cdn-images-1.medium.com/max/1024/1*JvIqr0fwvJbL1A2Vq3vNEQ.png)

Les valeurs par défaut de l’environnement notamment pour la partie réseau et monitoring sont laissées ici en l’état :

![](https://cdn-images-1.medium.com/max/1024/1*JOB9rFRuenfBJlh2jl4Txg.png)

![](https://cdn-images-1.medium.com/max/1024/1*Ac0c4_K0KgdFf1OC5QuCtA.png)

![](https://cdn-images-1.medium.com/max/1024/1*EA9iXnRGVEozrs2mcshC3A.png)

Je réutilise une image officielle docker de JupyterLab dans sa version Datascience (qui comprend Python, Julia et R) via les caractéristiques du conteneur à renseigner dans le portail :

[Docker Hub](https://hub.docker.com/r/jupyter/datascience-notebook)

![](https://cdn-images-1.medium.com/max/1024/1*ZkZLvdtjPBDGpsZ2OBOp8Q.png)

Avec ici 2 vCPU et 4 Go de mémoire vive. Voir à ce propos les caractéristiques de la tarification d’Azure Container Apps avec 180,000 vCPU-secondes et 360,000 GiB-secondes gratuites par mois :

[Azure Container Apps - Pricing | Microsoft Azure](https://azure.microsoft.com/en-us/pricing/details/container-apps/)

![](https://cdn-images-1.medium.com/max/1024/1*wdI80SvUI3CcoC9CnmT1kg.png)

Azure Container Apps expose chaque application par le biais d’un nom de domaine si Ingress est activé. Les points de terminaison Ingress peuvent être exposés publiquement au monde entier ou en interne et uniquement disponibles pour les autres applications de conteneur dans le même environnement. J’active cette option dans le portail avec le port TCP 8888 comme port cible pour le conteneur JupyterLab :

[Connect applications in Azure Container Apps](https://docs.microsoft.com/en-us/azure/container-apps/connect-apps?tabs=bash)

![](https://cdn-images-1.medium.com/max/1024/1*7snG9cHrq8jPFs3jkMa4vw.png)

L’application avec JupyterLab est prête à être lancée :

![](https://cdn-images-1.medium.com/max/1024/1*aOKbxjPGuK6As4_8aFKmGg.png)

Déploiement effectué avec succès ici :

![](https://cdn-images-1.medium.com/max/1024/1*2H8pEejA-fjeJKqD8AFHXw.png)

![](https://cdn-images-1.medium.com/max/1024/1*uljZytEEzyPagcKr1eJLXQ.png)

Par défaut, je dispose d’un min/max dans le Pod dans AKS avec 0 à 10 réplicas du conteneur :

![](https://cdn-images-1.medium.com/max/1024/1*fvdcNlV9zp-e_u4l0O1h6Q.png)

Je passe à un seul conteneur ici :

![](https://cdn-images-1.medium.com/max/1024/1*zmL3bgMgRUCZyU3sGEJzDA.png)

![](https://cdn-images-1.medium.com/max/1024/1*6vRRXlO54AoywyXlomr5kA.png)

Ce qui entraîne la création d’une révision :

![](https://cdn-images-1.medium.com/max/1024/1*xI0nylHOeHyf6vDD3Vej7A.png)

avec cette nouvelle valeur :

![](https://cdn-images-1.medium.com/max/1024/1*cC8Z_wpIuryH01HhZhbOOA.png)

Comme il y a un seul conteneur en exécution ici, je récupère le token nécessaire à la connexion à JupyterLab dans la partie Log Stream du portail :

![](https://cdn-images-1.medium.com/max/1024/1*C6nrZlBt-nJwa1AFyaB9Zg.png)

En effet, l’ingress a été fournie dans le portail :

![](https://cdn-images-1.medium.com/max/900/1*Yccbwv0mkIy-nuWb-7pH_A.png)

toujours sous cette forme :

![](https://cdn-images-1.medium.com/max/900/0*IeqW887qgbvGHTA9.png)

Connexion à JupyterLab :

![](https://cdn-images-1.medium.com/max/1024/1*7xUptynR6fK4vpJYQLPHtQ.png)

![](https://cdn-images-1.medium.com/max/1024/1*CWuBsZjGxvRV2u1araFsHQ.png)

![](https://cdn-images-1.medium.com/max/750/0*6XA9bLH4TUlp8mz6.jpg)

Je dispose ici de la dernière version de Julia 1.7.3, ce qui me sera utile pour tester les modèles de substitution via Surrogates.jl.

[GitHub - SciML/Surrogates.jl: Surrogate modeling and optimization for scientific machine learning (SciML)](https://github.com/SciML/Surrogates.jl)

Comme l’indique ce dépôt sur GitHub, un modèle de substitution est une méthode d’approximation qui imite le comportement d’une simulation coûteuse en termes de calcul. En termes plus mathématiques : supposons que nous essayons d’optimiser une fonction **_f(p)_**, mais que chaque calcul de **_f_** est très coûteux. Il se peut que nous devions résoudre une **_EDP_** (équation aux dérivées partielles) pour chaque point ou utiliser des machines d’algèbre linéaire numérique avancées, ce qui est généralement coûteux. L’idée est alors de développer un modèle de substitution **_g_** qui se rapproche de **_f_** en s’entraînant sur des données précédentes collectées à partir d’évaluations de **_f_**. La construction d’un modèle de substitution peut être considérée comme un processus en trois étapes :

- Sélection de l’échantillon
- Construction du modèle de substitution
- Optimisation du modèle de substitution

[Surrogate model - Wikipedia](https://en.wikipedia.org/wiki/Surrogate_model)

![](https://cdn-images-1.medium.com/max/850/1*04rhvARVOx5Mmwj8ne8emw.png)
_https://www.researchgate.net/publication/338658899_Surrogate-assisted_global_sensitivity_analysis_an_overview_

Je peux créer un nouveau kernel prenant en compte le nombre de threads présents, ici au nombre de 4 :

![](https://cdn-images-1.medium.com/max/1024/1*vxPXOAXEg3HV41f2DPtOpg.png)

```julia
using IJulia
installkernel("Julia-4-threads", env=Dict("JULIA_NUM_THREADS"=>"4"))
```

![](https://cdn-images-1.medium.com/max/1024/1*GOeo5CWkDLB4A7IIEls-xg.png)

Un nouveau Kernel avec les 4 threads pour Julia est alors disponible :

![](https://cdn-images-1.medium.com/max/1024/1*fGMHyfledgmB5cBjp9XYEQ.png)

J’utilise ici bottom en lieu et place d’htop pour surveiller la consommation de ressources dans JupyterLab :

[GitHub - ClementTsang/bottom: Yet another cross-platform graphical process/system monitor.](https://github.com/ClementTsang/bottom)

![](https://cdn-images-1.medium.com/max/1024/0*oaMkh5bzZyI5-mfC.gif)

![](https://cdn-images-1.medium.com/max/1024/1*SZ81sfhsjlC6Si3A5wYMuA.png)

avec le thème Jupyter Dark pour une meilleure visualisation dans JupyterLab:

![](https://cdn-images-1.medium.com/max/1024/1*TntT2h91k3LHpp67mFjzfw.png)

Installation du paquet Surrogates.jl et Plots.jl ensuite :

```julia
using Pkg
Pkg.add(["Surrogates", "Plots"])
```

![](https://cdn-images-1.medium.com/max/1024/1*MG1Y0YxY_wA2FuNfZnPzZw.png)

![](https://cdn-images-1.medium.com/max/1024/1*vds5O2WrEiSQRN46LCEprg.png)

Application ici à la fonction de Branin qui est couramment utilisée comme fonction de test pour la métamodélisation dans les expériences informatiques, notamment dans le contexte de l’optimisation :

![](https://cdn-images-1.medium.com/max/1024/1*TIgbLboRIwLaucVEDLAU-A.png)

avec

![](https://cdn-images-1.medium.com/max/752/1*GMdHUMh8cXu7WIdG3k_AzA.png)

J’importe les paquets ici :

```julia
using Surrogates
using Plots
default()
```

et je définis la fonction :

```julia
function branin(x)
      x1 = x[1]
      x2 = x[2]
      b = 5.1 / (4*pi^2);
      c = 5/pi;
      r = 6;
      a = 1;
      s = 10;
      t = 1 / (8*pi);
      term1 = a * (x2 - b*x1^2 + c*x1 - r)^2;
      term2 = s*(1-t)*cos(x1);
      y = term1 + term2 + s;
end
```

avec la réprésentation graphique de cette fonction :

```julia
n_samples = 80
lower_bound = [-5, 0]
upper_bound = [10,15]
xys = sample(n_samples, lower_bound, upper_bound, SobolSample())
zs = branin.(xys);
x, y = -5:10, 0:15 # hide
p1 = surface(x, y, (x1,x2) -> branin((x1,x2)))
xs = [xy[1] for xy in xys] 
ys = [xy[2] for xy in xys] 
scatter!(xs, ys, zs) 
p2 = contour(x, y, (x1,x2) -> branin((x1,x2))) 
scatter!(xs, ys)
plot(p1, p2, title="Fonction vraie")
```

![](https://cdn-images-1.medium.com/max/1024/1*dwZqRIhxxZRc17NVkdq2QQ.png)

ce qui donne ce graphe :

![](https://cdn-images-1.medium.com/max/1024/1*oFy_Lc9ZuhCNPq620NsVrA.png)

Il est maintenant temps d’ajuster différents substituts et de les tracer. Application à la substitution par krigeage.

> Le krigeage ou régression par processus gaussien est une méthode d’interpolation pour laquelle les valeurs interpolées sont modélisées par un processus gaussien.

- [Kriging · Surrogates.jl](https://surrogates.sciml.ai/stable/kriging/)
- [Utilisation d'un méta-modèle de krigeage en optimisation](https://hal-emse.ccsd.cnrs.fr/emse-00691561)

```julia
kriging_surrogate = Kriging(xys, zs, lower_bound, upper_bound, p=[1.9, 1.9])
```

et de sa représentation graphique :

```julia
p1 = surface(x, y, (x, y) -> kriging_surrogate([x y]))
scatter!(xs, ys, zs, marker_z=zs)
p2 = contour(x, y, (x, y) -> kriging_surrogate([x y]))
scatter!(xs, ys, marker_z=zs)
plot(p1, p2, title="Substitution par Krigreage")
```

![](https://cdn-images-1.medium.com/max/1024/1*LUmDtDLESKJk8nwpHskG1w.png)

![](https://cdn-images-1.medium.com/max/1024/1*0ChwxJTFTbH1JysZHi7t_Q.png)

Utilisation ensuite du “modèle de substitution à distance inverse”.

Il s’agit d’une méthode d’interpolation. Dans cette méthode, les points inconnus sont calculés avec une moyenne pondérée des points d’échantillonnage.

> Ce modèle utilise la distance inverse entre les points inconnus et les points d’apprentissage pour prédire le point inconnu. Nous n’avons pas besoin d’ajuster ce modèle car la réponse d’un point inconnu **_x_** est calculée par rapport à la distance entre **_x_** et les points d’apprentissage.

[InverseDistance · Surrogates.jl](https://surrogates.sciml.ai/stable/InverseDistance/)

```julia
InverseDistance = InverseDistanceSurrogate(xys, zs, lower_bound, upper_bound)
```

et encore une fois sa représentation graphique :

```julia
p1 = surface(x, y, (x, y) -> InverseDistance([x y])) 
scatter!(xs, ys, zs, marker_z=zs) 
p2 = contour(x, y, (x, y) -> InverseDistance([x y])) 
scatter!(xs, ys, marker_z=zs)
plot(p1, p2, title="substitution distance inverse")
```

![](https://cdn-images-1.medium.com/max/1024/1*rgeYe3k9ClDq39RPdYELCg.png)

![](https://cdn-images-1.medium.com/max/1024/1*RoeLo6Py9poEJStN4bH3HA.png)

Utilisation enfin de la substitution de Lobachevsky. La fonction splines Lobachevsky est une fonction utilisée pour l’interpolation dispersée univariée et multivariée. Introduit par Lobachevsky en 1842 pour étudier les erreurs dans les mesures astronomiques :

[Lobachevsky · Surrogates.jl](https://surrogates.sciml.ai/stable/lobachevsky/)

```julia
Lobachevsky = LobachevskySurrogate(xys, zs, lower_bound, upper_bound, alpha = [2.8,2.8], n=8)
```

accompagnée de sa représentation graphique :

```julia
p1 = surface(x, y, (x, y) -> Lobachevsky([x y]))
scatter!(xs, ys, zs, marker_z=zs)
p2 = contour(x, y, (x, y) -> Lobachevsky([x y]))
scatter!(xs, ys, marker_z=zs)
plot(p1, p2, title="Substitution Lobachevsky")
```

![](https://cdn-images-1.medium.com/max/1024/1*O-LC5umApOBZ5rqnou3x3A.png)

![](https://cdn-images-1.medium.com/max/1024/1*SY-sgo7ACQftEs7fdxLU6Q.png)

Il est possible de définir un réseau de neurones comme modèle de substitution, en utilisant Flux.jl :

- [NeuralSurrogate · Surrogates.jl](https://surrogates.sciml.ai/stable/neural/)
- [Home · Flux](https://fluxml.ai/Flux.jl/stable/)

En effet, il est possible d’appeler des méthodes d’optimisation via les réseaux neuronaux. Surrogates.jl se retrouve dans JuliaSim, une plateforme de simulation de nouvelle génération basée sur le cloud, combinant les dernières techniques de SciML avec la modélisation et la simulation de jumeaux numériques basés sur des équations :

- [JuliaSim - Julia Computing](https://juliacomputing.com/products/juliasim/)
- [Composing Modeling and Simulation with Machine Learning in Julia](https://arxiv.org/abs/2105.05946)

comme le décrit cet article …

[Generating surrogates](https://help.juliahub.com/JuliaSim/Tutorials/Surrogates/)

![](https://cdn-images-1.medium.com/max/591/0*6XstQuoIxlK_ZkP5.jpg)

Pour finir, je peux reprendre l’utilisation de ParallelStencil.jl comme illustré dans l’article précédent :

- [Parallélisation distribuée presque triviale d’applications GPU et CPU basées sur des Stencils avec…](https://dev.to/deep75/parallelisation-distribuee-presque-triviale-dapplications-gpu-et-cpu-basees-sur-des-stencils-avec-25d0)
- [GitHub - omlins/ParallelStencil.jl: Package for writing high-level code for parallel high-performance stencil computations that can be deployed on both GPUs and CPUs](https://github.com/omlins/ParallelStencil.jl)

en installant les paquets nécessaires :

```julia
Pkg.add(["Printf", "Statistics", "ParallelStencil"])
```

avec l’exemple des ondes acoustiques en 2-D en utilisant la formulation de vitesse-pression fractionnée :

![](https://cdn-images-1.medium.com/max/1024/1*qnnO-C0XIMTXeMOpm6SlpA.png)
_https://pt.slideshare.net/AmrMousa12/2-dimensional-wave-equation-analytical-and-numerical-solution_

et ce code en Julia :

{% gist https://gist.github.com/deep75/5679b3721a4ba3039610861f6f05026a %}

![](https://cdn-images-1.medium.com/max/1024/1*OkMqZNBZRNihvB3DDKj6QA.png)

![](https://cdn-images-1.medium.com/max/1024/1*JZOeldjtd3CtcpXwOL_2Pw.png)

![](https://cdn-images-1.medium.com/max/600/1*ijNvJBOjKkFzIopRhRAhjw.gif)

Avec un suivi de métriques de consommation des ressources depuis le portail :

![](https://cdn-images-1.medium.com/max/1024/1*chjtQ72nm9QWttrHvoLUbA.png)

ou depuis le conteneur via bottom :

![](https://cdn-images-1.medium.com/max/1024/1*vPeItUX8ZCmTix3-PpDOlA.png)

La suppression de JupyterLab se fait simplement par la suppression du Ressource Group crée dans le cadre d’Azure Container Apps, via le portail ou Azure CLI :

```bash
az group delete --resource-group RG-JUPYTER --yes
```

Pour conclure, on peut aller beaucoup plus loin dans Azure Container Apps avec Dapr (Distributed Application Runtime), un runtime qui permet de créer des microservices résilients, sans état et avec état, qui peut être mis en oeuvre dans ce cas …

[Tutorial: Deploy a Dapr application to Azure Container Apps using the Azure CLI](https://docs.microsoft.com/en-us/azure/container-apps/microservices-dapr?tabs=bash)

![](https://cdn-images-1.medium.com/max/785/0*HlbzoTu5ReF2XWSL.png)

Avec Azure Container Apps, vous obtenez une version entièrement gérée des API Dapr lors de la création de microservices.

> Lorsque vous utilisez Dapr dans Azure Container Apps, vous pouvez permettre aux side-cars de s’exécuter à côté de vos microservices qui fournissent un riche ensemble de fonctionnalités.

Les API Dapr disponibles incluent les appels de service à service, Pub/Sub, les liaisons d’événements, les magasins d’état et les acteurs.

![](https://cdn-images-1.medium.com/max/604/0*evEz0HmtEd7Im7t2.jpg)

À suivre !