Faire tourner un réseau de neurones sur des données chiffrées ?

Faire tourner un réseau de neurones sans jamais voir les données

De « c'est quoi chiffrer » à « faire un ResNet sur des données chiffrées ». Dix concepts, dix animations.

Coucou ! Envie de voir comment on peut utiliser des réseaux de neurones de manière encryptée ? Aujourd'hui on va voir ça ensemble !
1

Le problème de départ

Les meilleurs modèles d'IA vivent dans le cloud (le MLaaS). Pour en profiter, il faut leur envoyer ses données. Mais certaines, on aimerait les garder pour soi.

Soapiane serre contre lui un dossier top secret qu'il ne veut pas livrer

On envoie les données… en clair

Ce modèle dans le cloud est génial… mais pour l'utiliser, je dois lui envoyer mes données.

💻 Client (Soapiane)
serveur cloud
Serveur : lit tout en clair
dossier en clair (top secret)
x
👉 Glisse pour voir

Le risque, en chiffres

0 %

des fuites de données impliquent des données stockées dans le cloud.

0 j

durée moyenne d'exposition des données avant l'endiguement de la fuite.

0,0 M$

coût moyen mondial d'une violation de données (en dollars américains), en baisse de 9 % sur un an grâce à une identification et un confinement plus rapides.

Source : IBM, Cost of a Data Breach 2025.

Il faut donc calculer sans jamais exposer les données. C'est l'enjeu du PPML (Privacy-Preserving ML).

On calcule sur du chiffré

Mes données médicales… en clair, chez moi. Pas question de les livrer telles quelles.

💻 Client (Soapiane)
Enc()
couche
serveur cloud
Serveur : voit seulement du chiffré
dossier en clair (top secret)
x
👉 Glisse pour voir
2

L'idée magique : l'homomorphisme

Homomorphisme = une opération sur les chiffrés correspond à la même opération sur les clairs. Faire le calcul « par le bas » (chiffrer, combiner, déchiffrer) donne exactement le même résultat que « par le haut », le diagramme commute.

Dec(Enc(a) ⊕ Enc(b)) = a + b

Deux nombres bien à moi : a = 5 et b = 7.

🔓 En clair · côté client
🔒 Chiffré · côté serveur
Enc()
Dec()
+
⊕
a = 5b = 7
a + b = 12
⟨3F1⟩⟨A9C⟩
⟨7E4⟩
👉 Glisse pour voir
3

Le bruit, héros et méchant

La sécurité repose sur un problème dur, RLWE (réseaux euclidiens) : on cache le message dans une équation, puis on ajoute un petit bruit aléatoire. Retrouver le message revient à résoudre un problème réputé NP-difficile, infaisable sans la clé, même pour un ordinateur quantique.

Ce bruit qui nous protège a pourtant un revers : on enchaîne les opérations (chacune s'applique au résultat de la précédente), et à chaque étape le bruit s'accumule. Vas-y, empile les opérations et regarde la jauge :

Soapiane se bouche les oreilles, assourdi par le bruit
Fraîchement chiffré : presque aucun bruit, je suis serein 😌
La chaîne de calculs (chaque opération s'applique au résultat de la précédente) :
🔒 x→ ajoute des opérations…
longueur de la chaîne : 0 opération
Résultat déchiffré= 42
bruit accumulé8%
↑ seuil de rupture

Chaque opération s'ajoute à la chaîne et consomme du budget de bruit. La × en coûte bien plus que la +, d'où le nombre limité d'opérations avant le seuil.

🔑 Tout le design d'un calcul HE, c'est un budget de bruit à ne pas dépasser, et une × coûte bien plus cher qu'une +.

« Et quand ça déborde ? » Deux issues : dimensionner large, ou un « reset » du bruit. On y revient après un détour par les réseaux de neurones.

4

Quel schéma pour le machine learning ?

Avant de choisir, un petit rappel : de quoi est fait un réseau de neurones, et quelles opérations il faut savoir exécuter pour faire une inférence.

Rappel express

Un réseau, c'est un empilement de couches, et chaque neurone fait deux choses

w1w2w3x1x2x3Σfy
y = f(w₁x₁ + w₂x₂ + … + b)
1. Partie linéaire

La somme pondérée w·x + b : des × (poids × entrées) et des + (l'accumulation). C'est tout Conv et Dense/FC.

2. Activation

Une fonction non-linéaire f (ReLU, sigmoïde…) appliquée à chaque valeur.

L'inférence, c'est empiler ces couches et pousser l'entrée à travers (forward pass). Deux ingrédients seulement : des sommes pondérées (×, +) et des activations. Retiens bien ce partage, il décide de tout côté HE.

Or les poids et les activations sont des nombres réels

Il faut donc un schéma qui gère la virgule. Parmi la famille des schémas HE :

BFV / BGVEntiers exacts
Arithmétique exacteVotes, bases de données
CKKSRéels / complexes approchés
Virgule + packing massif✅ Machine Learning
TFHE / FHEWBits / booléens
Bootstrapping ultra-rapideNon-linéarités, logique

CKKS = « le flottant chiffré ». Il gère nativement les réels et tolère de petites erreurs (comme du flottant bruié), et un réseau tolère ces erreurs. Mariage parfait : c'est le langage du ML sur HE.

5

Le chiffré CKKS et ses 3 contraintes

En CKKS, un nombre est représenté comme ceci : pas un simple nombre, mais une paire de polynômes qui portent le message × une échelle Δ (plus un compteur ℓ dont on comprendra le rôle un peu plus loin). Déroule l'animation :

Un chiffré A propre : 2 composantes (c0, c1), échelle Δ, niveau ℓ = 4.

× multiplication
le chiffré gonfle (2→3)
relinéarisation
recompacte (3→2)
rescaling
−1 niveau
composantes du chiffré
c0
c1
échelle
Δ
niveau ℓ
4

Mais il manque un champ crucial à ce chiffré : le bruit. Zoomons dessus.

Clique sur « calculer » pour simuler une couche de calcul !
bruit accumulé78%
↑ plafond : on ne peut plus calculer
On évalue Dec() sous le chiffrement

Le chiffré bruité entre dans la machine, on rejoue son déchiffrement à l'aveugle, le cadenas 🔒 ne s'ouvre jamais, et il ressort rafraîchi, à bas bruit.

→ profondeur illimitée (Fully HE)
calculs (×)
0
bootstraps
0
temps total
⏱️ 0.00 s

L'intérêt : sans bootstrapping, la profondeur est plafonnée (leveled). Avec, elle est illimitée, mais un seul bootstrap coûte plus cher que 160 multiplications : c'est l'opération la plus lente de tout HE.

Et voilà d'où vient le niveau ℓ

Puisqu'on ne peut pas laisser le bruit franchir ce seuil, on ne peut enchaîner qu'un nombre limité de multiplications avant de devoir le remettre à zéro (bootstrapping). Ce compte à rebours d'opérations, fixé au départ, c'est le niveau ℓ.

Après chaque × chiffré, le rescaling remet l'échelle Δ et le bruit sous contrôle, mais consomme un cran de ℓ. À zéro, plus aucune × possible sans bootstrapper.

Donc, faire calculer ce chiffré impose 3 contraintes

1. Profondeur multiplicative. Cette longueur de chaîne que tu empilais tout à l'heure, on l'appelle la profondeur. Ce qui compte : les × en chaîne, pas leur nombre total. Chaque couche du réseau en consomme.

2. Les niveaux. Le budget ℓ qu'on vient de voir : chaque × chiffré en consomme un ; à zéro, on s'arrête ou on bootstrappe.

3. La maintenance. Relinéarisation + rescaling après chaque ×, exactement ce qu'on a vu sur la structure ci-dessus.

Je construis mon réseau couche par couche, chacune est un × en chaîne.
Profondeur multiplicative
0
couches / × en chaîne
Niveaux restants
6/6
budget (modulus chain)
Maintenance CKKS
prête
après chaque couche
Ton réseau (chaque maillon s'applique au résultat du précédent) :
entrée
→ ajoute une couche…
Capacité du modèle (l'intérêt d'aller plus profond)0%
Plus le réseau est profond, plus il est expressif, mais chaque couche est un × en chaîne qui coûte un niveau et déclenche la maintenance.
6

Le packing SIMD : un chiffré = tout un vecteur

Retour au partage de tout à l'heure : un neurone, c'est une somme pondérée w·x + b, puis une activation. Cette somme pondérée, c'est l'essentiel du calcul d'un réseau. Et sous chiffrement, elle coûte une fortune : une image, c'est des milliers de valeurs, donc des milliers de × et de +… sur des chiffrés CKKS énormes (un gros polynôme chacun). À raison d'un nombre par chiffré, HE serait inutilisable.

🐌 Sans packing
🔒3
🔒1
🔒4
🔒1
🔒5
🔒9

6 chiffrés énormes · 6 opérations pour 6 malheureux nombres. Multiplie par les milliers de valeurs d'une vraie couche : impraticable.

⚡ Avec packing
🔒 1 chiffré
3
1
4
1
5
9

1 chiffré · 1 opération traite toutes les valeurs en parallèle (SIMD). La somme pondérée redevient abordable.

Un seul chiffré contient tout le vecteur. Appuie sur une opération…
🔒 1 chiffré CKKS8 slots affichés · en vrai : N/2 ≈ 8192
3
1
4
1
5
9
2
6
slot 0slot 1slot 2slot 3slot 4slot 5slot 6slot 7
opérations lancées : 0valeurs traitées : 0 (= 0 × 8 slots, en parallèle)

Une même instruction s'applique aux 8 slots d'un coup (SIMD). Pour sommer entre slots (produit scalaire, convolution), il faut les faire tourner.

🔑 Le packing, c'est ce qui rend la somme pondérée praticable : une seule × par les poids (en clair) traite tout le vecteur d'un coup, puis les rotations replient les slots les uns sur les autres pour en faire la somme. C'est le rotate-and-sum.

Voilà tes données x, chiffrées. Et voilà w, les poids du neurone : eux sont en clair, le serveur les connaît.

x : tes données (chiffrées)vecteur
3
1
4
2
×une seule × SIMD, slot par slot → le vecteur w ⊙ x
w : les poids du neurone (en clair)vecteur
2
5
1
3
🔒 1 chiffré CKKScoût : 0 × · 0 ↻
?
?
?
?

Le produit scalaire w·x, c'est donc une multiplication puis une somme repliée par rotations. Et pour n valeurs, il ne faut que log₂(n) rotations : 8192 slots se replient en 13 tours. C'est ça, le rotate-and-sum, et c'est ce qui rend une couche linéaire bon marché sous chiffrement.

Voilà pourquoi, plus loin, les couches linéaires seront « bon marché » : toute une couche tient en une poignée d'opérations packées. Reste à savoir tout ce que HE sait faire, et surtout ce qu'il ne sait pas.

7

Ce qu'on peut… et ce qu'on ne peut PAS faire

En CKKS, HE ne dispose que d'une toute petite boîte à outils. La grosse carte défile toute seule, clique sur une opération pour voir son coût, ou pourquoi elle est impossible.

✅ Les seules opérations natives
🚫 Ce que HE ne sait PAS faire
ReLU

Activations

ReLU, sigmoïde, tanh, softmax, exp… ne sont pas des polynômes. HE ne fait que + et ×.

Comment on contourne : on les APPROXIME par des polynômes (juste après)

C'est le point de bascule : un réseau, ce n'est pas que des × et des +. Il y a des activations, et là, HE ne sait pas faire directement.

8

Le défi central : les non-linéarités

Le cœur du sujet. HE ne fait que + et × → donc seulement des polynômes. Or ReLU, sigmoïde, max-pooling ne sont pas des polynômes. La parade : les remplacer par un polynôme qui leur ressemble sur l'intervalle utile, joue avec le degré et le point de test :

Le polynôme, développé : que des additions et des multiplications :
p(x)=0.235+0.5×x+0.205×x²−0.00636×x⁴
+ additions · × multiplications → exactement ce que HE sait faire. Un plus haut degré ⇒ plus de × en chaîne (= plus de profondeur).
Plus je monte le degré, mieux la courbe colle à ReLU.
zone sûre [−4, 4]
ReLU (cible) polynôme degré 4
ReLU(x)
2.00
polynôme(x)
1.95
précision
89%
profondeur
≈ 2 niv.

Monte le degré : la précision grimpe… mais le coût en profondeur aussi (d ≈ 2ⁿ ⇒ n niveaux). C'est le compromis du domaine.

🔑 Rendre une couche non-linéaire « exécutable » = trouver le meilleur compromis entre précision de l'approximation et profondeur consommée, sans laisser les activations sortir de l'intervalle où le polynôme diverge.

9

Assembler un réseau chiffré

L'objectif du début : faire tourner un modèle sur tes données privées sans jamais les exposer. Bonne nouvelle : on n'entraîne pas sous HE. Seule l'inférence passe sous chiffrement.

1. Entraîner en clair

Le réseau est entraîné normalement, sur des données en clair. On en ressort des poids.

sans chiffrement
2. Adapter au HE

On rend le modèle « HE-compatible » : ReLU → polynômes, packing, budget de niveaux et bootstraps.

une fois, avant de déployer
3. Inférer sous chiffrement

Le client chiffre son entrée, le serveur fait le forward pass sur du chiffré, et renvoie un résultat que seul le client déchiffre.

à chaque requête

L'inférence chiffrée, déroulée

Le réseau est déjà entraîné, en clair. J'y envoie mon image, chiffrée : elle entre par la couche d'entrée 🔒.

entrée 🔒sortieσ polyσ polyσ poly🔄bootstrap
🔒 Entrée chiffrée
budget de niveaux
côté serveur : tout est chiffré 🔒
Soapiane très content

Merci d'avoir suivi cette aventure jusqu'au bout 🎉

J'espère que ce voyage au cœur du chiffrement homomorphe vous a plu. N'hésitez pas à partager vos opinions, questions ou retours, ça compte vraiment.

Et à très vite pour la prochaine aventure ! 🚀