Introduction
Les Processus Gaussiens sont de puissants modèles probabilistes qui représentent des distributions sur des fonctions.
Ils excellent en régression et en classification, surtout lorsque la quantification de l’incertitude et la performance avec peu de données sont importantes.
Leurs principaux inconvénients sont le coût computationnel et la dépendance à un bon choix de noyau (kernel).
Qu’est-ce qu’un Processus Gaussien ? (GP)
Un Processus Gaussien (GP) est un modèle probabiliste qui définit une distribution sur des fonctions.
Au lieu de supposer une forme fonctionnelle spécifique (comme un polynôme ou un réseau de neurones), un GP suppose que :
Tout ensemble fini de valeurs de la fonction suit une distribution gaussienne multivariée.
Un GP est entièrement défini par :
- une fonction de moyenne ( m(x) )
- une fonction de covariance ou kernel ( k(x, x') )
\begin{equation}
[
f(x) \sim \mathrm{GP}(m(x), k(x,x'))
]
\end{equation}
Le kernel encode la lissité, la similarité, la périodicité, le bruit, ainsi que d’autres hypothèses structurelles sur la fonction.
Régression par Processus Gaussien (GPR)
La GPR modélise une relation :
\begin{equation}
y = f(x) + \epsilon, \quad \epsilon \sim \mathrm{N}(0, \sigma^2)
\end{equation}
À partir des données d’entraînement ((X, y)), le GP infère une distribution a posteriori sur les fonctions.
Cela fournit :
- une moyenne prédictive (meilleure estimation)
- une variance prédictive (incertitude)
Ainsi, la GPR n’est pas seulement une méthode de prédiction : c’est un cadre complet de quantification de l’incertitude.
Pourquoi la GPR est efficace pour la régression ?
- Fonctionne bien avec des jeux de données petits ou moyens
- Fournit de l’incertitude (intervalles de confiance)
- Équilibre automatiquement biais et complexité via le kernel
Classification par Processus Gaussien (GPC)
Pour la classification, les sorties ne sont pas gaussiennes. Les GPs utilisent donc une fonction de lien (logistique ou probit) :
\begin{equation}
[
p(y=1 \mid f) = \sigma(f)
]
\end{equation}
Le GP définit toujours un prior sur ( f(x) ), mais la vraisemblance est non gaussienne.
L’inférence nécessite des approximations :
- Approximation de Laplace
- Propagation d’attente (Expectation Propagation)
- Inférence variationnelle
La GPC fournit des prédictions probabilistes, permettant d’évaluer la confiance dans la classification et des probabilités bien calibrées.
Avantages des Processus Gaussiens
-
Quantification d’incertitude intégrée
Les GPs retournent une distribution complète (moyenne + variance) et pas seulement une estimation ponctuelle. -
Excellente performance avec peu ou du bruit dans les données
Idéal lorsque les données sont coûteuses ou limitées. -
Grande flexibilité grâce aux kernels
Permet de modéliser : -
fonctions lisses
- motifs périodiques
- corrélations spatiales
-
structures complexes via somme/produit de kernels
-
Peu d’hyperparamètres
L’entraînement ne concerne généralement que les paramètres du kernel, souvent moins nombreux que dans les réseaux de neurones. -
Non-paramétrique
La complexité du modèle croît avec les données, sans avoir à spécifier une forme fonctionnelle fixe.
Limitations des Processus Gaussiens
- Mauvaise scalabilité avec de grandes données
Coût d’entraînement :
\begin{equation}
[
O(n^3) \quad \text{en temps}, \qquad O(n^2) \quad \text{en mémoire}
]
\end{equation}
Peu adaptés aux grandes bases de données sauf si l’on utilise des approximations (sparse GPs, points induits…).
-
Choix du kernel complexe
La performance dépend fortement du type de kernel et de son optimisation. -
La GPC nécessite des approximations
La classification est plus coûteuse computationnellement à cause des vraisemblances non gaussiennes. -
Vitesse de prédiction limitée
La prédiction dépend du nombre de points d’entraînement ; les grandes bases ralentissent l’inférence.
## Régression par Processus Gaussien (GPR)
### Bruit homoscédastique – Exemple 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt # Utiliser la double précision pour la stabilité numérique avec l'algèbre linéaire dtype = torch.double torch.manual_seed(0) np.random.seed(0) # 1) Génération de données synthétiques def generate_data(n_train=25, noise_std=0.2): X = np.linspace(-3, 3, n_train)[:, None] y = np.sin(X).ravel() + noise_std * np.random.randn(n_train) return X, y X_train_np, y_train_np = generate_data(n_train=25, noise_std=0.2) X_test_np = np.linspace(-5, 5, 400)[:, None] X_train = torch.tensor(X_train_np, dtype=dtype) y_train = torch.tensor(y_train_np[:, None], dtype=dtype) # vecteur colonne X_test = torch.tensor(X_test_np, dtype=dtype) # 2) Kernel RBF implémenté en PyTorch (paramétré en log-space) class RBFKernel(nn.Module): def __init__(self, init_lengthscale=1.0, init_variance=1.0): super().__init__() self.log_lengthscale = nn.Parameter(torch.tensor(np.log(init_lengthscale), dtype=dtype)) self.log_variance = nn.Parameter(torch.tensor(np.log(init_variance), dtype=dtype)) def forward(self, x1, x2): # Matrice des distances au carré x1_sq = (x1**2).sum(dim=1, keepdim=True) x2_sq = (x2**2).sum(dim=1, keepdim=True) sqdist = x1_sq + x2_sq.T - 2.0 * (x1 @ x2.T) lengthscale = torch.exp(self.log_lengthscale) variance = torch.exp(self.log_variance) K = variance * torch.exp(-0.5 * sqdist / (lengthscale**2)) return K, lengthscale, variance # 3) Classe Régression par Processus Gaussien (NLML et prédiction) class GaussianProcessRegression(nn.Module): def __init__(self, kernel: RBFKernel, noise_init=0.2): super().__init__() self.kernel = kernel self.log_noise = nn.Parameter(torch.tensor(np.log(noise_init), dtype=dtype)) def negative_log_marginal_likelihood(self, X, y, jitter=1e-6): K, _, _ = self.kernel(X, X) noise = torch.exp(self.log_noise) n = X.shape[0] Kt = K + (noise**2 + jitter) * torch.eye(n, dtype=dtype) # Décomposition de Cholesky L = torch.linalg.cholesky(Kt) # Résolution pour alpha = K^{-1} y via systèmes triangulaires v = torch.linalg.solve_triangular(L, y, upper=False) alpha = torch.linalg.solve_triangular(L.T, v, upper=True) data_fit = 0.5 * (y.T @ alpha) log_det = torch.sum(torch.log(torch.diag(L))) constant = 0.5 * n * torch.log(torch.tensor(2.0 * np.pi, dtype=dtype)) nlml = data_fit + log_det + constant return nlml.squeeze() def predict(self, X_train, y_train, X_test, jitter=1e-6): K_tt, _, _ = self.kernel(X_train, X_train) noise = torch.exp(self.log_noise) n = X_train.shape[0] Kt = K_tt + (noise**2 + jitter) * torch.eye(n, dtype=dtype) L = torch.linalg.cholesky(Kt) K_s, _, _ = self.kernel(X_train, X_test) K_ss, _, _ = self.kernel(X_test, X_test) # moyenne prédictive v = torch.linalg.solve_triangular(L, y_train, upper=False) alpha = torch.linalg.solve_triangular(L.T, v, upper=True) pred_mean = K_s.T @ alpha # covariance prédictive via complément de Schur w = torch.linalg.solve_triangular(L, K_s, upper=False) z = torch.linalg.solve_triangular(L.T, w, upper=True) pred_cov = K_ss - K_s.T @ z pred_var = torch.clamp(torch.diag(pred_cov), min=1e-12).unsqueeze(1) return pred_mean.detach(), pred_var.detach() # 4) Instanciation et entraînement kernel = RBFKernel(init_lengthscale=1.0, init_variance=1.0) gpr = GaussianProcessRegression(kernel=kernel, noise_init=0.2).double() optimizer = torch.optim.Adam(gpr.parameters(), lr=0.05) n_iters = 300 # réduire pour des exécutions rapides ; augmenter pour meilleure convergence loss_history = [] for i in range(n_iters): optimizer.zero_grad() nlml = gpr.negative_log_marginal_likelihood(X_train, y_train) nlml.backward() optimizer.step() loss_history.append(nlml.item()) if (i+1) % 50 == 0 or i==0: with torch.no_grad(): ls = torch.exp(gpr.kernel.log_lengthscale).item() var = torch.exp(gpr.kernel.log_variance).item() noise = torch.exp(gpr.log_noise).item() print(f"Itération {i+1}/{n_iters} - NLML: {nlml.item():.4f} lengthscale: {ls:.4f} variance: {var:.4f} noise: {noise:.4f}") # 5) Prédiction et visualisation pred_mean, pred_var = gpr.predict(X_train, y_train, X_test) pred_mean = pred_mean.numpy().ravel() pred_std = np.sqrt(pred_var.numpy().ravel()) plt.figure(figsize=(8,5)) plt.scatter(X_train_np.ravel(), y_train_np.ravel(), marker='o', label='Données d’entraînement') plt.plot(X_test_np.ravel(), pred_mean, label='Moyenne prédictive') upper = pred_mean + 1.96 * pred_std lower = pred_mean - 1.96 * pred_std plt.fill_between(X_test_np.ravel(), lower, upper, alpha=0.3) plt.title("Régression par Processus Gaussien (PyTorch)") plt.xlabel("x") plt.ylabel("y") plt.legend() plt.show() with torch.no_grad(): final_ls = torch.exp(gpr.kernel.log_lengthscale).item() final_var = torch.exp(gpr.kernel.log_variance).item() final_noise = torch.exp(gpr.log_noise).item() print(f"Hyperparamètres finaux:\n lengthscale = {final_ls:.4f}\n variance = {final_var:.4f}\n noise = {final_noise:.4f}") |
!
### Explication du code de Régression par Processus Gaussien
Ce code implémente la régression par processus gaussien (GPR) à partir de zéro en PyTorch, incluant :
- définition du kernel
- optimisation de la vraisemblance marginale
- prédiction
- visualisation
1. Imports et configuration
On importe :
- PyTorch pour la différentiation automatique et l’algèbre linéaire
- NumPy pour générer les données synthétiques
- Matplotlib pour tracer les graphiques
On utilise :
1 2 3 | dtype = torch.double torch.manual_seed(0) np.random.seed(0) |
La double précision est recommandée pour les Processus Gaussiens à cause de la décomposition de Cholesky et des inversions de matrices. Les graines (seed) assurent la reproductibilité des résultats.
2. Génération de données synthétiques
1 2 3 4 | def generate_data(n_train=25, noise_std=0.2): X = np.linspace(-3, 3, n_train)[:, None] y = np.sin(X).ravel() + noise_std * np.random.randn(n_train) return X, y |
- Les entrées (X) sont des points de –3 à 3.
- Les cibles (y) suivent une sinusoïde bruitée.
- Les données de test couvrent un intervalle plus large pour la visualisation.
Les tableaux sont convertis en tenseurs PyTorch en double précision.
3. Classe Kernel RBF
Le kernel RBF (Radial Basis Function) est une fonction de covariance classique :
\begin{equation}
[
k(x,x') = \sigma^2 \exp\left( -\frac{|x-x'|^2}{2\ell^2} \right)
]
\end{equation}
- Les paramètres
log_lengthscaleetlog_variancesont appris en log-space pour la stabilité numérique. - La méthode
forwardcalcule les distances au carré entre tous les points et construit la matrice du kernel (K).
4. Classe Régression par Processus Gaussien
Cette classe gère :
(a) Log-vraisemblance marginale négative (NLML)
\begin{equation}
[
\log p(y|X,\theta) = -\frac12 y^\top K^{-1} y - \frac12 \log |K| - \frac{n}{2}\log(2\pi)
]
\end{equation}
Implémentée avec :
- calcul de la matrice du kernel
- ajout de la variance du bruit
- décomposition de Cholesky
- résolution de systèmes triangulaires pour calculer (\alpha = K^{-1} y)
La NLML est utilisée comme fonction de perte pour l’entraînement.
(b) Fonction de prédiction
Pour de nouveaux points (X_*) :
\begin{equation}
\mu_* = K_{*}^{T} K^{-1} y
\end{equation}
\begin{equation}
\Sigma_{*} = K_{**} - K_{*}^{T} K^{-1} K_*
\end{equation}
Le code calcule :
- la moyenne prédictive (
pred_mean) - la variance prédictive (
pred_var)
Les tenseurs sont détachés pour ne pas suivre les gradients.
5. Instanciation et entraînement du modèle
1 2 3 | kernel = RBFKernel(...) gpr = GaussianProcessRegression(...) optimizer = torch.optim.Adam(gpr.parameters(), lr=0.05) |
Le modèle apprend :
- le lengthscale
- la variance
- la variance du bruit
en minimisant la NLML.
La boucle d’entraînement :
- calcule la NLML
- rétropropagation des gradients
- mise à jour des paramètres via Adam
- affichage de l’avancement tous les 50 itérations
6. Prédiction et visualisation
Après entraînement :
1 2 | pred_mean, pred_var = gpr.predict(...) pred_std = np.sqrt(pred_var...) |
Le graphique affiche :
- les points de données d’entraînement
- la moyenne a posteriori du GP (courbe lisse)
- l’intervalle de confiance 95 % (zone ombrée)
Comment changer le bruit d’observation
Le bruit d’observation est entièrement contrôlé par cette ligne à l’intérieur de la classe GaussianProcessRegression :
1 | self.log_noise = nn.Parameter(torch.tensor(np.log(noise_init), dtype=dtype)) |
et utilisé ici pendant l’entraînement :
1 2 | noise = torch.exp(self.log_noise) Kt = K + (noise**2 + jitter) * torch.eye(n, dtype=dtype) |
Cela signifie que le modèle suppose une seule valeur globale de bruit, qui est apprise pendant l’entraînement.
OPTION 1 — Changer la valeur initiale du bruit
Modifier :
1 | gpr = GaussianProcessRegression(kernel=kernel, noise_init=0.2) |
Par exemple :
1 | gpr = GaussianProcessRegression(kernel=kernel, noise_init=0.8) |
Mais rappelez-vous : le GP apprendra toujours un seul niveau global de bruit, quelle que soit la valeur initiale.
OPTION 2 — Geler le bruit et le fixer manuellement
Si vous voulez un niveau de bruit fixe (non appris), faites :
1 2 | gpr.log_noise.requires_grad_(False) gpr.log_noise.data = torch.log(torch.tensor(0.5, dtype=dtype)) |
Cela force le GP à utiliser :
\begin{equation}
[
\sigma_n = 0.5
]
\end{equation}
pour toutes les observations.
Bruit homoscédastique – Exemple 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import torch import numpy as np import matplotlib.pyplot as plt torch.set_default_dtype(torch.double) # 1) Données X = torch.linspace(-3, 3, 25).unsqueeze(1) y = torch.sin(X) + 0.2 * torch.randn_like(X) # incertitude des mesures (exemple) y_err = 0.5 * torch.ones_like(y) X_test = torch.linspace(-5, 5, 400).unsqueeze(1) # 2) Paramètres (log-space) log_length = torch.tensor(0.0, requires_grad=True) log_var = torch.tensor(0.0, requires_grad=True) log_noise = torch.tensor(-1.0, requires_grad=True) # 3) Kernel RBF def rbf(x1, x2, log_l, log_v): l = torch.exp(log_l) v = torch.exp(log_v) sqdist = (x1**2).sum(1, keepdim=True) + (x2**2).sum(1) - 2*x1@x2.T return v * torch.exp(-0.5 * sqdist / l**2) # 4) Log-vraisemblance marginale négative def nlml(): K = rbf(X, X, log_length, log_var) noise = torch.exp(log_noise) K = K + noise**2 * torch.eye(len(X)) L = torch.linalg.cholesky(K) alpha = torch.cholesky_solve(y, L) return ( 0.5 * (y.T @ alpha) + torch.sum(torch.log(torch.diag(L))) + 0.5 * len(X) * torch.log(torch.tensor(2*np.pi)) ) # 5) Entraînement des hyperparamètres opt = torch.optim.Adam([log_length, log_var, log_noise], lr=0.05) for i in range(300): opt.zero_grad() loss = nlml() loss.backward() opt.step() if i % 50 == 0: print(i, loss.item()) # 6) Distribution prédictive def predict(X_new): K = rbf(X, X, log_length, log_var) noise = torch.exp(log_noise) K = K + torch.diag(noise**2 + y_err.squeeze()**2) L = torch.linalg.cholesky(K) Ks = rbf(X, X_new, log_length, log_var) Kss = rbf(X_new, X_new, log_length, log_var) alpha = torch.cholesky_solve(y, L) mean = Ks.T @ alpha v = torch.linalg.solve(L, Ks) cov = Kss - v.T @ v std = torch.diag(cov).clamp(min=1e-9).sqrt() return mean.squeeze().detach(), std.detach() mu, std = predict(X_test) # 7) Visualisation plt.figure(figsize=(8,5)) # Données d'entraînement AVEC barres d'erreur plt.errorbar( X.squeeze().numpy(), y.squeeze().numpy(), yerr=y_err.squeeze().numpy(), fmt='o', color='black', ecolor='gray', elinewidth=1.5, capsize=3, label="Données d'entraînement ± erreur" ) # Moyenne prédictive plt.plot(X_test.numpy(), mu.numpy(), label="Moyenne prédictive") # Région prédictive ombrée plt.fill_between( X_test.squeeze().numpy(), (mu - 2*std).numpy(), (mu + 2*std).numpy(), alpha=0.3, label="GP ±2σ" ) plt.legend() plt.title("Régression par Processus Gaussien avec barres d'erreur") plt.show() |

Bruit hétéroscédastique – Exemple 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt # Utiliser la double précision pour la stabilité numérique dtype = torch.double torch.manual_seed(0) np.random.seed(0) # ------------------------------------------------------- # 1) Générer des données synthétiques + erreurs hétéroscédastiques # ------------------------------------------------------- def generate_data(n_train=25, noise_std=0.2): X = np.linspace(-3, 3, n_train)[:, None] y = np.sin(X).ravel() + noise_std * np.random.randn(n_train) return X, y X_train_np, y_train_np = generate_data(n_train=25, noise_std=0.2) X_test_np = np.linspace(-5, 5, 400)[:, None] # Exemple : incertitude hétéroscédastique # On augmente l'erreur vers les bords pour démonstration obs_error_std_np = 0.1 + 0.3 * np.abs(X_train_np.ravel()) / 3.0 X_train = torch.tensor(X_train_np, dtype=dtype) y_train = torch.tensor(y_train_np[:, None], dtype=dtype) obs_error_std = torch.tensor(obs_error_std_np[:, None], dtype=dtype) X_test = torch.tensor(X_test_np, dtype=dtype) # ------------------------------------------------------- # 2) Kernel RBF # ------------------------------------------------------- class RBFKernel(nn.Module): def __init__(self, init_lengthscale=1.0, init_variance=1.0): super().__init__() self.log_lengthscale = nn.Parameter(torch.tensor(np.log(init_lengthscale), dtype=dtype)) self.log_variance = nn.Parameter(torch.tensor(np.log(init_variance), dtype=dtype)) def forward(self, x1, x2): x1_sq = (x1**2).sum(dim=1, keepdim=True) x2_sq = (x2**2).sum(dim=1, keepdim=True) sqdist = x1_sq + x2_sq.T - 2.0 * (x1 @ x2.T) lengthscale = torch.exp(self.log_lengthscale) variance = torch.exp(self.log_variance) K = variance * torch.exp(-0.5 * sqdist / (lengthscale**2)) return K, lengthscale, variance # ------------------------------------------------------- # 3) Régression par Processus Gaussien avec erreurs par observation # ------------------------------------------------------- class GaussianProcessRegression(nn.Module): def __init__(self, kernel: RBFKernel): super().__init__() self.kernel = kernel def negative_log_marginal_likelihood(self, X, y, obs_error_std, jitter=1e-6): K, _, _ = self.kernel(X, X) n = X.shape[0] # --- NOUVEAU : utiliser les variances par observation --- noise_diag = obs_error_std.squeeze()**2 Kt = K + torch.diag(noise_diag + jitter) # Décomposition de Cholesky L = torch.linalg.cholesky(Kt) v = torch.linalg.solve_triangular(L, y, upper=False) alpha = torch.linalg.solve_triangular(L.T, v, upper=True) data_fit = 0.5 * (y.T @ alpha) log_det = torch.sum(torch.log(torch.diag(L))) constant = 0.5 * n * torch.log(torch.tensor(2.0 * np.pi, dtype=dtype)) nlml = data_fit + log_det + constant return nlml.squeeze() def predict(self, X_train, y_train, X_test, obs_error_std, jitter=1e-6): K_tt, _, _ = self.kernel(X_train, X_train) noise_diag = obs_error_std.squeeze()**2 Kt = K_tt + torch.diag(noise_diag + jitter) L = torch.linalg.cholesky(Kt) K_s, _, _ = self.kernel(X_train, X_test) K_ss, _, _ = self.kernel(X_test, X_test) v = torch.linalg.solve_triangular(L, y_train, upper=False) alpha = torch.linalg.solve_triangular(L.T, v, upper=True) pred_mean = K_s.T @ alpha w = torch.linalg.solve_triangular(L, K_s, upper=False) z = torch.linalg.solve_triangular(L.T, w, upper=True) pred_cov = K_ss - K_s.T @ z pred_var = torch.clamp(torch.diag(pred_cov), min=1e-12).unsqueeze(1) return pred_mean.detach(), pred_var.detach() # ------------------------------------------------------- # 4) Instanciation et entraînement # ------------------------------------------------------- kernel = RBFKernel(init_lengthscale=1.0, init_variance=1.0) gpr = GaussianProcessRegression(kernel=kernel).double() optimizer = torch.optim.Adam(gpr.parameters(), lr=0.05) n_iters = 300 loss_history = [] for i in range(n_iters): optimizer.zero_grad() nlml = gpr.negative_log_marginal_likelihood(X_train, y_train, obs_error_std) nlml.backward() optimizer.step() loss_history.append(nlml.item()) if (i+1) % 50 == 0: with torch.no_grad(): ls = torch.exp(gpr.kernel.log_lengthscale).item() var = torch.exp(gpr.kernel.log_variance).item() print(f"Iter {i+1}/{n_iters} | NLML={nlml:.4f} | ls={ls:.4f} | var={var:.4f}") # ------------------------------------------------------- # 5) Prédiction et visualisation # ------------------------------------------------------- pred_mean, pred_var = gpr.predict(X_train, y_train, X_test, obs_error_std) pred_mean = pred_mean.numpy().ravel() pred_std = np.sqrt(pred_var.numpy().ravel()) plt.figure(figsize=(10,5)) plt.scatter(X_train_np.ravel(), y_train_np.ravel(), c='k', label='Train', s=40) plt.errorbar( X_train_np.ravel(), y_train_np.ravel(), yerr=obs_error_std_np, fmt='none', ecolor='gray', alpha=0.5, label='Erreurs d’observation' ) plt.plot(X_test_np.ravel(), pred_mean, label="Moyenne") plt.fill_between( X_test_np.ravel(), pred_mean - 1.96*pred_std, pred_mean + 1.96*pred_std, alpha=0.3, label="IC 95%" ) plt.legend() plt.title("Régression par Processus Gaussien avec erreurs par observation") plt.show() |

Processus Gaussiens avec GPyTorch
Qu’est-ce que GPyTorch ?
GPyTorch est une bibliothèque moderne et scalable pour les processus gaussiens (GP), construite sur PyTorch.
Elle fournit une accélération GPU, la différentiation automatique, et des méthodes efficaces d’algèbre linéaire, permettant d’entraîner des GP sur des ensembles de données beaucoup plus grands que les implémentations classiques.
Elle a été développée chez Uber AI Labs et supporte :
- Les processus gaussiens exacts (pour petits/moyens ensembles de données)
- Les GP approximatifs / variationnels (pour grands ensembles de données)
- Les kernels additifs, les GP multitâches, les kernels de mélange spectral
- Une intégration naturelle avec les modèles PyTorch
Comment installer GPyTorch
Installation avec pip :
1 | pip install gpytorch |
Ou avec conda :
1 | conda install -c conda-forge gpytorch |
Vous devez avoir PyTorch installé au préalable :
1 | pip install torch |
Pourquoi GPyTorch est intéressant
Différentiation automatique
Les paramètres du kernel, le bruit et les paramètres du modèle sont optimisés automatiquement grâce à l’autograd de PyTorch.
Accélération GPU
Les décompositions de Cholesky, la résolution de matrices et les opérations sur les kernels s’exécutent sur GPU.
Kernels intégrés
RBF, Matern, Spectral Mixture, RQ, Polynômes, etc.
Support pour GP à grande échelle
Via les kernels SKI, les GP variationnels et l’interpolation de kernels structurée.
API propre et modulaire
Les modèles sont des sous-classes de gpytorch.models.ExactGP.
Support du bruit hétéroscédastique
Via likelihood(noise) ou des modèles de bruit personnalisés.
Implémentation complète de GPyTorch (avec bruit hétéroscédastique)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | import torch import gpytorch import numpy as np import matplotlib.pyplot as plt dtype = torch.double torch.manual_seed(0) np.random.seed(0) # ------------------------------------------------------- # 1) Génération des données # ------------------------------------------------------- def generate_data(n_train=25, noise_std=0.2): X = np.linspace(-3, 3, n_train)[:, None] y = np.sin(X).ravel() + noise_std * np.random.randn(n_train) return X, y X_train_np, y_train_np = generate_data() X_test_np = np.linspace(-5, 5, 400)[:, None] # Exemple de bruit hétéroscédastique obs_error_std_np = 0.1 + 0.3 * np.abs(X_train_np.ravel()) / 3.0 X_train = torch.tensor(X_train_np, dtype=dtype) y_train = torch.tensor(y_train_np, dtype=dtype) obs_noise = torch.tensor(obs_error_std_np**2, dtype=dtype) X_test = torch.tensor(X_test_np, dtype=dtype) # ------------------------------------------------------- # 2) Définition du modèle GP (Exact GP) # ------------------------------------------------------- class ExactGPModel(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels.ScaleKernel( gpytorch.kernels.RBFKernel() ) def forward(self, x): mean_x = self.mean_module(x) cov_x = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean_x, cov_x) # ------------------------------------------------------- # 3) Likelihood avec bruit hétéroscédastique # ------------------------------------------------------- likelihood = gpytorch.likelihoods.FixedNoiseGaussianLikelihood( noise=obs_noise, learn_additional_noise=False # empêche l'apprentissage d'un terme homoscédastique supplémentaire ) model = ExactGPModel(X_train, y_train, likelihood).double() model.train() likelihood.train() optimizer = torch.optim.Adam(model.parameters(), lr=0.1) mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model) # ------------------------------------------------------- # 4) Entraînement des hyperparamètres du GP # ------------------------------------------------------- training_iter = 300 for i in range(training_iter): optimizer.zero_grad() output = model(X_train) loss = -mll(output, y_train) loss.backward() if (i + 1) % 50 == 0: ls = model.covar_module.base_kernel.lengthscale.item() var = model.covar_module.outputscale.item() print(f"Iter {i+1}/{training_iter} | Loss={loss.item():.4f} | ls={ls:.3f} | var={var:.3f}") optimizer.step() # ------------------------------------------------------- # 5) Évaluation # ------------------------------------------------------- model.eval() likelihood.eval() with torch.no_grad(): preds = likelihood(model(X_test)) mean = preds.mean.numpy() lower, upper = preds.confidence_region() # ------------------------------------------------------- # 6) Visualisation # ------------------------------------------------------- plt.figure(figsize=(10, 5)) plt.scatter(X_train_np, y_train_np, c='k', s=40, label="Train") plt.errorbar( X_train_np, y_train_np, yerr=obs_error_std_np, fmt='none', ecolor='gray', alpha=0.6, label="Bruit observé" ) plt.plot(X_test_np, mean, label='Moyenne prédite') plt.fill_between( X_test_np.ravel(), lower.numpy(), upper.numpy(), alpha=0.3, label="Intervalle de confiance 95%" ) plt.title("GPyTorch — Régression GP avec bruit hétéroscédastique") plt.legend() plt.show() |

Un autre exemple
Nous revisitons maintenant l’exemple de l’article précédent où nous avions implémenté un modèle de GP manuellement sans PyTorch.
Cela permet de comparer cette approche avec une implémentation propre utilisant GPyTorch.
Exemple 1D
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import torch import gpytorch import matplotlib.pyplot as plt # 1) Données synthétiques X = torch.tensor([1., 3., 5., 6., 7., 8.]).unsqueeze(1) Y = X.squeeze() * torch.sin(X.squeeze()) # Nouveau : bruit différent par observation obs_noise_std = torch.tensor([0.2, 0.1, 0.5, 0.3, 0.15, 0.4]) obs_noise_var = obs_noise_std**2 # 2) Modèle GP avec FixedNoiseGaussianLikelihood class ExactGPModel(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels.ScaleKernel( gpytorch.kernels.RBFKernel() ) def forward(self, x): mean_x = self.mean_module(x) covar_x = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean_x, covar_x) likelihood = gpytorch.likelihoods.FixedNoiseGaussianLikelihood( noise=obs_noise_var ) model = ExactGPModel(X, Y, likelihood) # 3) Entraînement model.train() likelihood.train() optimizer = torch.optim.Adam(model.parameters(), lr=0.1) mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood, model) for i in range(120): optimizer.zero_grad() output = model(X) loss = -mll(output, Y) loss.backward() optimizer.step() # 4) Prédiction model.eval() likelihood.eval() Xtest = torch.linspace(0, 10, 200).unsqueeze(1) with torch.no_grad(), gpytorch.settings.fast_pred_var(): pred = likelihood(model(Xtest)) pred_mean = pred.mean.numpy() pred_std = pred.stddev.numpy() # 5) Visualisation plt.figure(figsize=(9,5)) plt.errorbar( X.squeeze().numpy(), Y.numpy(), yerr=obs_noise_std.numpy(), fmt='o', capsize=4, label="Données d'entraînement (bruit hétéroscédastique)" ) plt.plot(Xtest.numpy(), pred_mean, "r--", label="Moyenne GP") plt.fill_between( Xtest.squeeze().numpy(), pred_mean - 1.96*pred_std, pred_mean + 1.96*pred_std, alpha=0.3, label="Intervalle 95%" ) plt.xlabel("x") plt.ylabel("y") plt.title("Régression GP (avec bruit par point)") plt.legend() plt.grid(True) plt.show() |

Exemple 2D
Modèle de régression GP (kernel RBF 2D)
\begin{equation}
k(x, x') = \sigma_f^2 \exp\left(-\frac{(x_1-x_1')^2}{2\ell_1^2}-\frac{(x_2-x_2')^2}{2\ell_2^2}\right)
\end{equation}
- Régression 2D avec GP exact (RBF avec ARD)
- Classification binaire avec GP variationnel sparse/approx.
Updated Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | import torch import gpytorch import numpy as np import matplotlib.pyplot as plt from matplotlib import cm torch.manual_seed(0) # ----------------------------- # Data (same as our original example) # ----------------------------- X_np = np.array([ [-8.0,-8.0], [-6.0,-3.0], [-7.0,2.0], [-4.0,4.0], [ 2.0, 3.0], [ 5.0, 7.0], [ 1.0,-1.0], [ 3.0,-4.0], [ 7.0,-7.0] ], dtype=np.float32) # labels originally -1 / +1 -> convert to 0/1 for Bernoulli likelihood Y_class_np = np.array([-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0], dtype=np.float32) Y_class_01_np = (Y_class_np > 0).astype(np.float32) # 0 or 1 # We'll also use Y_reg as same numeric labels for regression (float) Y_reg_np = Y_class_np.copy() # Torch tensors (float) X = torch.from_numpy(X_np) Y_reg = torch.from_numpy(Y_reg_np) Y_class = torch.from_numpy(Y_class_01_np) # 0/1 # ----------------------------- # Regression: Exact GP (RBF with ARD) # ----------------------------- class ExactGPRegression(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() # RBF with ARD -> separate lengthscale per input dim self.covar_module = gpytorch.kernels.ScaleKernel( gpytorch.kernels.RBFKernel(ard_num_dims=train_x.shape[1]) ) def forward(self, x): mean = self.mean_module(x) cov = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean, cov) # Setup likelihood & model likelihood_reg = gpytorch.likelihoods.GaussianLikelihood() model_reg = ExactGPRegression(X, Y_reg, likelihood_reg) # Train regression model model_reg.train() likelihood_reg.train() optimizer = torch.optim.Adam(model_reg.parameters(), lr=0.1) mll = gpytorch.mlls.ExactMarginalLogLikelihood(likelihood_reg, model_reg) n_iter_reg = 200 for i in range(n_iter_reg): optimizer.zero_grad() output = model_reg(X) loss = -mll(output, Y_reg) loss.backward() optimizer.step() if (i+1) % 50 == 0: print(f"[Reg] Iter {i+1}/{n_iter_reg} NLML: {loss.item():.4f}") # ----------------------------- # Regression: predictions on grid # ----------------------------- model_reg.eval() likelihood_reg.eval() # grid settings grid_x1 = np.arange(-10.0, 10.0, 0.12, dtype=np.float32) grid_x2 = np.arange(-10.0, 10.0, 0.12, dtype=np.float32) X1g, X2g = np.meshgrid(grid_x1, grid_x2, indexing='xy') X_grid = torch.from_numpy(np.column_stack([X1g.ravel(), X2g.ravel()]).astype(np.float32)) with torch.no_grad(), gpytorch.settings.fast_pred_var(): pred_reg = likelihood_reg(model_reg(X_grid)) mean_grid = pred_reg.mean.reshape(X1g.shape).numpy() var_grid = pred_reg.variance.reshape(X1g.shape).numpy() # Plot regression mean plt.figure(figsize=(6,5)) plt.imshow(mean_grid, origin='lower', extent=[-10,10,-10,10], cmap=cm.jet) plt.colorbar(label='Predictive mean') plt.scatter(X_np[:,0], X_np[:,1], c=Y_reg_np, edgecolors='k', cmap=cm.coolwarm, s=60) plt.title("GP Regression — Predictive Mean") plt.xlabel("x1"); plt.ylabel("x2") plt.grid(True, linestyle='--', alpha=0.4) plt.show() # Plot regression variance plt.figure(figsize=(6,5)) plt.imshow(var_grid, origin='lower', extent=[-10,10,-10,10], cmap=cm.jet) plt.colorbar(label='Predictive variance') plt.scatter(X_np[:,0], X_np[:,1], c='white', edgecolors='k', s=60) plt.title("GP Regression — Predictive Variance") plt.xlabel("x1"); plt.ylabel("x2") plt.grid(True, linestyle='--', alpha=0.4) plt.show() # ----------------------------- # Classification: Variational GP (Sparse/Approximate) # ----------------------------- # Convert dataset for classifier: X (same), Y_class (0/1) # Use inducing points (we'll use training inputs as inducing for this tiny example) inducing_points = X.clone() class VariationalGPClassifier(gpytorch.models.ApproximateGP): def __init__(self, inducing_points): variational_distribution = gpytorch.variational.CholeskyVariationalDistribution(inducing_points.size(0)) variational_strategy = gpytorch.variational.VariationalStrategy( self, inducing_points, variational_distribution, learn_inducing_locations=True ) super().__init__(variational_strategy) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels.ScaleKernel( gpytorch.kernels.RBFKernel(ard_num_dims=inducing_points.shape[1]) ) def forward(self, x): mean = self.mean_module(x) cov = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean, cov) # Likelihood and model likelihood_clf = gpytorch.likelihoods.BernoulliLikelihood() model_clf = VariationalGPClassifier(inducing_points) # Put into training mode model_clf.train() likelihood_clf.train() # Optimizer and ELBO optimizer_clf = torch.optim.Adam(model_clf.parameters(), lr=0.05) num_data = X.shape[0] elbo = gpytorch.mlls.VariationalELBO(likelihood_clf, model_clf, num_data) # Training loop n_iter_clf = 500 for i in range(n_iter_clf): optimizer_clf.zero_grad() output = model_clf(X) loss = -elbo(output, Y_class) loss.backward() optimizer_clf.step() if (i+1) % 100 == 0: print(f"[Clf] Iter {i+1}/{n_iter_clf} ELBO loss: {loss.item():.4f}") # ----------------------------- # Classification: predictions on grid # ----------------------------- model_clf.eval() likelihood_clf.eval() with torch.no_grad(), gpytorch.settings.fast_pred_var(): preds_clf = likelihood_clf(model_clf(X_grid)) probs = preds_clf.mean.reshape(X1g.shape).numpy() # P(Y=1) # Plot classification probability map plt.figure(figsize=(6,5)) plt.imshow(probs, origin='lower', extent=[-10,10,-10,10], cmap=cm.jet, vmin=0, vmax=1) plt.colorbar(label='P(Y=1)') # plot training points (use marker styles like original) for i, y in enumerate(Y_class_np): if y == -1.0: plt.scatter(X_np[i,0], X_np[i,1], marker='o', color='#1f77b4', edgecolors='k', s=80) else: plt.scatter(X_np[i,0], X_np[i,1], marker='x', color='#ff7f0e', s=80, linewidths=2) plt.title("GP Classification — Predicted Probability P(Y=1)") plt.xlabel("x1"); plt.ylabel("x2") plt.grid(True, linestyle='--', alpha=0.4) plt.show() # Contour of decision surface plt.figure(figsize=(6,5)) CS = plt.contour(X1g, X2g, probs, levels=np.linspace(0,1,11), cmap=cm.jet) plt.clabel(CS, inline=True, fontsize=8) plt.title("GP Classification — Probability Contours") plt.xlabel("x1"); plt.ylabel("x2") plt.grid(True, linestyle='--', alpha=0.4) plt.scatter(X_np[:,0], X_np[:,1], c='k', s=30) plt.show() |


Références
| Liens | Site |
|---|---|
| PyTorch Documentation | Documentation officielle PyTorch |
| Cholesky PyTorch | Décomposition Cholesky |
| Optimisateurs PyTorch | Optimiseurs PyTorch |
| Scikit-Learn GP | Implémentation de référence des GP |
| GPyTorch | Bibliothèque officielle GPyTorch |
| GPML Textbook | Livre GPML (Rasmussen & Williams) |
| Wikipedia - Gaussian Process | Vue d’ensemble des processus gaussiens |
