
Garder les probabilités honnêtes : l’ajustement jacobien
Introduction
la gêne des clients due aux temps d’attente. Les appels arrivent de manière aléatoire, le temps d’attente X suit donc une distribution exponentielle : la plupart des attentes sont courtes, quelques-unes sont extrêmement longues.
Maintenant, je dirais que la gêne n’est pas linéaire : une attente de 10 minutes est deux fois plus pénible qu’une attente de 5 minutes. Vous décidez donc de modéliser les « unités de gêne » comme \(Y = X²\).
Simple, non ? Prenez simplement le pdf de X, remplacez x par \(\sqrt{y}\), et vous avez terminé.
Vous le tracez. Cela semble raisonnable – un pic proche de zéro, une longue traîne.
Mais et si vous calculiez réellement le CDF ? Vous vous en attendriez à 1, n’est-ce pas ?
Le résultat ? 2.
Court extrait numpy pour le confirmer
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import expon
# CDF of Exponential(1): F(x) = 1 - exp(-x) for x >= 0
def cdf_exp(x):
return 1 - np.exp(-x)
# Wrong (naive) pdf for Y = X²: just substitute x = sqrt(y)
def wrong_pdf(y):
return np.exp(-np.sqrt(y)) # This integrates to 2!
# Quick numerical check of integral
from scipy.integrate import quad
integral, err = quad(wrong_pdf, 0, np.inf)
print(f"Numerical integral ≈ {integral:.3f} (should be 1, but it's 2)")
# prints 2
Votre nouvelle distribution prétend que chaque résultat possible est deux fois plus probable qu’il devrait l’être.
C’est impossible… mais c’est arrivé parce que vous avez manqué un petit ajustement.
Cet « ajustement » est le jacobien, un facteur d’échelle qui compense la façon dont la transformation étire ou comprime l’axe en différents points. Sautez-le et vos probabilités mentent. Incluez-le, et tout s’additionne à nouveau parfaitement.
Dans cet article, nous allons construire l’intuition, dériver les mathématiques étape par étape, la voir apparaître naturellement dans l’égalisation de l’histogramme, visualiser l’étirement/rétrécissement de manière empirique et le prouver avec des simulations.
L’intuition
Pour comprendre pourquoi l’ajustement jacobien est nécessaire, utilisons une analogie tangible : considérons une distribution de probabilité comme une quantité fixe de sable (exactement 1 livre) répartie le long d’une droite numérique, où la hauteur du tas de sable en chaque point représente la densité de probabilité. Le total du sable totalise toujours 1, ce qui représente une probabilité de 100 %.
Désormais, lorsque vous transformez la variable aléatoire (par exemple, de X à Y = X²), c’est comme saisir cette droite numérique (une feuille de caoutchouc flexible) et la déformer en fonction de la transformation. Vous n’ajoutez ni n’enlevez du sable ; vous étirez ou comprimez simplement différentes parties de la feuille.
Dans les régions où la transformation comprime la feuille (un long tronçon de la ligne d’origine est écrasé en un segment plus court sur le nouvel axe Y), la même quantité de sable occupe désormais moins d’espace horizontal. Pour conserver la totalité du sable, le tas doit devenir plus haut : la densité augmente. Par exemple, près de Y = 0 dans la transformation quadratique, de nombreuses petites valeurs X (de 0 à 1) sont entassées dans un minuscule intervalle Y (0 à 1), de sorte que la densité augmente considérablement.
À l’inverse, dans les régions où la transformation étire la feuille (un court segment de la ligne d’origine est tiré vers un segment plus long sur l’axe Y), le sable s’étale sur plus d’espace, rendant le tas plus court et plus plat : la densité diminue. Pour un grand X (disons de 10 à 11), Y s’étend de 100 à 121, un intervalle beaucoup plus large, de sorte que la densité s’amincit.
Le point clé : le sable total reste exactement 1 lb, quelle que soit la façon dont vous déformez la feuille. Sans tenir compte de cet étirement et de ce rétrécissement locaux, votre nouvelle densité serait incohérente, comme si vous prétendiez avoir 2 livres de sable après la chaîne. Le jacobien est le facteur mathématique qui ajuste automatiquement la hauteur partout pour préserver le montant total.
Les mathématiques
Formalisons l’intuition avec l’exemple de \( Y = g(X) = X^2 \), où \( X \) a pdf \( f_X(x) = e^{-x} \) pour \( x \geq 0 \) (Exponentiel de taux 1).
Considérons un petit intervalle autour de \( x \) de largeur \( \Delta x \).
La probabilité dans cet intervalle est d’environ \( f_X(x) \Delta x \).
Après transformation, cela correspond à un intervalle autour de \( y = x^2 \) de largeur
\( \Delta y \approx \left| g'(x) \right| \Delta x = |2x| \Delta x \).
Pour conserver la probabilité :
$$ f_Y(y) \Delta y \approx f_X(x) \Delta x, $$
donc
$$ f_Y(y) \approx \frac{f_X(x)}{\left| g'(x) \right|} $$
Dans la limite de \( \Delta x \to 0 \), cela devient exact :
$$ f_Y(y) = f_X(x) \gauche| \frac{dx}{dy} \right|, $$
où \( x = \sqrt{y} \) (l’inverse) et
\( \frac{dx}{dy} = \frac{1}{2\sqrt{y}} \).
Branchement :
$$ f_Y(y) = e^{-\sqrt{y}} \cdot \frac{1}{2\sqrt{y}} \quad \text{for } y > 0. $$
Sans le terme jacobien \( \frac{1}{2\sqrt{y}} \), le naïf
\( f_Y(y) = e^{-\sqrt{y}} \)
s’intègre à 2 :
Soit \( u = \sqrt{y} \), \( y = u^2 \), \( dy = 2u \, du \) :
$$ \int_0^\infty e^{-\sqrt{y}} \, dy $$
$$ = \int_0^\infty e^{-u}\cdot 2u \, du $$
$$= 2 \int_0^\infty ue^{-u} \, du $$
$$ = 2 \Gamma(2) = 2 \cdot 1 = 2. $$
L’ajustement jacobien garantit $$ \int_0^\infty f_Y(y) \, dy = 1. $$
Une note sur \(\Gamma\)
\(\Gamma\) est la représentation factorielle des nombres réels. $$\Gamma(n) = (n-1) ! \quad \text{pour les entiers positifs } n$$
Ce facteur d’échelle \( \left| \frac{dx}{dy} \right| \) est précisément ce qui compense l’étirement et le rétrécissement locaux de l’axe.
La forme générale
Soit Y = g(X), où g est une fonction différentiable strictement monotone (croissante ou décroissante), et X a pdf \( f_X(x) \).
Nous voulons la pdf \( f_Y(y) \) de Y.
Considérons un petit intervalle autour de x de largeur \( \Delta x \).
La probabilité dans cet intervalle est d’environ \( f_X(x) \Delta x \).
Après transformation y = g(x), cet intervalle correspond à un intervalle autour de y de largeur
\( \Delta y \approx \left| g'(x) \right| \Delta x \).
Pour revenir à l’équation que nous avons développée précédemment :
$$ f_Y(y) = f_X(x) \gauche| \frac{dx}{dy} \right|, $$
où nous utilisons la relation inverse \(x = h(y) = g^{-1}(y) \), et
\( \frac{dx}{dy} = h'(y) = \frac{1}{g'(x)} \).
La formule générale est donc
$$ f_Y(y) = f_X(h(y)) \gauche| h'(y) \right|. $$
Preuve empirique
Simuler l’étirement et le rétrécissement
La meilleure façon de « ressentir » l’étirement et le rétrécissement est de zoomer sur deux régions séparément : proche de zéro (là où la compression se produit) et plus éloignée (là où l’étirement domine).
Nous allons générer quatre tracés :
1. Histogramme X original, zoomé sur de petites valeurs (X < 1), avec de petits intervalles égaux de largeur 0,1 — pour montrer la source de compression.
2. Histogramme Y = X² correspondant, zoomé près de zéro – montrant comment ces minuscules intervalles X deviennent encore plus petits sur Y (rétrécissement).
3. Histogramme X original pour des valeurs plus grandes (X > 1), avec des intervalles égaux de largeur 1 — pour montrer la source de l’étirement.
4. Histogramme Y correspondant pour les grandes valeurs — montrant comment ces intervalles X explosent en d’énormes intervalles Y (étirement).
Code
import numpy as np
import matplotlib.pyplot as plt
# Generate large sample for clear visuals
n = 50000
x = np.random.exponential(scale=1, size=n)
y = x**2
fig = plt.figure(figsize=(16, 10))
def plot_histogram(ax, data, bins, density, color, alpha, title, xlabel, ylabel):
ax.hist(data, bins=bins, density=density, color=color, alpha=alpha)
ax.set_title(title)
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
# Plot 1: X small values (compression source)
ax1 = fig.add_subplot(2, 2, 1)
plot_histogram(ax1, x[x < 1], bins=50, density=True, color='skyblue', alpha=0.7,
title='Original X (zoomed X < 1)', xlabel='X', ylabel='Density')
# Equal-width intervals of 0.1 on small X
small_x_lines = np.arange(0, 1.01, 0.1)
for line in small_x_lines:
ax1.axvline(line, color='red', linestyle='--', alpha=0.8)
# Plot 2: Y near zero (showing shrink/compression)
ax2 = fig.add_subplot(2, 2, 2)
plot_histogram(ax2, y[y < 1], bins=100, density=True, color='lightcoral', alpha=0.7,
title='Y = X² near zero (compression visible)', xlabel='Y', ylabel='Density')
# Mapped small intervals on Y (very narrow!)
small_y_lines = small_x_lines**2
for line in small_y_lines:
ax2.axvline(line, color='red', linestyle='--', alpha=0.8)
# Plot 3: X larger values (stretching source)
ax3 = fig.add_subplot(2, 2, 3)
plot_histogram(ax3, x[(x > 1) & (x < 12)], bins=50, density=True, color='skyblue', alpha=0.7,
title='Original X (X > 1)', xlabel='X', ylabel='Density')
# Equal-width intervals of 1 on larger X
large_x_starts = [1, 3, 5, 7, 9, 11]
large_x_lines = large_x_starts + [s + 1 for s in large_x_starts]
for line in large_x_lines:
if line < 12:
ax3.axvline(line, color='red', linestyle='--', alpha=0.8)
# Plot 4: Y large values (showing stretch)
ax4 = fig.add_subplot(2, 2, 4)
plot_histogram(ax4, y[(y > 1) & (y < 150)], bins=80, density=True, color='lightgreen', alpha=0.7,
title='Y = X² large values (stretching visible)', xlabel='Y', ylabel='Density')
# Mapped large intervals on Y (huge gaps!)
large_y_lines = np.array(large_x_lines)**2
for line in large_y_lines:
if line < 150:
ax4.axvline(line, color='red', linestyle='--', alpha=0.8)
# Update annotation styles with modified font style
fig.text(0.5, 0.75, "X-axis shrinking → Density increases\nY-axis higher near zero",
ha='center', va='center', fontsize=12, color='black',
fontstyle='italic', bbox=dict(facecolor='#f7f7f7', edgecolor='none', alpha=0.9))
fig.text(0.5, 0.25, "X-axis stretching → Density decreases\nY-axis lower for large values",
ha='center', va='center', fontsize=12, color='black',
fontstyle='italic', bbox=dict(facecolor='#f7f7f7', edgecolor='none', alpha=0.9))
fig.set_dpi(250)
plt.tight_layout()
plt.show()

Simulation de l’ajustement jacobien
Pour voir l’ajustement jacobien en action, simulons les données de la distribution exponentielle (1) pour X, calculons Y = X² et traçons l’histogramme empirique de Y par rapport à la pdf théorique pour augmenter la taille des échantillons n. À mesure que n grandit, l’histogramme doit converger vers le pdf ajusté correct, et non vers le naïf.
Code
import numpy as np
import matplotlib.pyplot as plt
def correct_pdf(y):
return np.exp(-np.sqrt(y)) / (2 * np.sqrt(y))
def naive_pdf(y):
return np.exp(-np.sqrt(y))
# Sample sizes to test
sample_sizes = [100, 1_000, 10_000]
fig, axs = plt.subplots(1, len(sample_sizes), figsize=(15, 5))
y_vals = np.linspace(0.01, 50, 1000) # Range for plotting theoretical pdfs
for i, n in enumerate(sample_sizes):
# Sample X ~ Exp(1)
x = np.random.exponential(scale=1, size=n)
y = x**2
# Plot histogram (normalized to density)
axs[i].hist(y, bins=50, range=(0, 50), density=True, alpha=0.6, color='skyblue', label='Empirical Histogram')
# Plot theoretical pdfs
axs[i].plot(y_vals, correct_pdf(y_vals), 'g-', label='Correct PDF (with Jacobian)')
axs[i].plot(y_vals, naive_pdf(y_vals), 'r--', label='Naive PDF (no Jacobian)')
axs[i].set_title(f'n = {n}')
axs[i].set_xlabel('Y = X²')
axs[i].set_ylabel('Density')
axs[i].legend()
axs[i].set_ylim(0, 0.5) # For consistent viewing
axs[i].grid(True) # Add grid to each subplot
# Set the figure DPI to 250 for higher resolution
fig.set_dpi(250)
plt.grid()
plt.tight_layout()
plt.show()
Et le résultat est à la hauteur de nos attentes.

Égalisation d’histogramme : une application réelle

Un exemple classique où l’ajustement jacobien apparaît naturellement est l’égalisation d’histogramme dans le traitement d’image.
Nous traitons les intensités de pixels X (typiquement en \([0, 255]\)) comme échantillons d’une certaine distribution avec pdf empirique basé sur l’histogramme de l’image.
Le but est de les transformer vers de nouvelles intensités Y pour que Y soit approximativement uniforme sur \([0, 255]\) — cela répartit les valeurs et améliore le contraste.
La transformation utilisée est exactement la fonction de distribution cumulative (CDF) mise à l’échelle de X :
$$ Y = 255 \cdot F_X(X) $$
où \( F_X(x) = \int_{-\infty}^x f_X
Pourquoi est-ce que ça marche ? Il s’agit d’une application directe de la transformation intégrale de probabilité (PIT) :
Si \( Y = F_X(X) \) et X est continu, alors Y ~ Uniforme\([0,1]\).
La mise à l’échelle par 255 donne Uniform\([0,255]\).
Voyez maintenant le jacobien à l’œuvre :
Soit \( g(x) = L \cdot F_X(x) \) (\( L = 255 \)).
La dérivée \( g'(x) = L \cdot f_X(x) \) (puisque la dérivée du CDF est le pdf).
Appliquez la formule de changement de variables :
$$ f_Y(y) = f_X(x) / |g'(x)| = f_X(x) / (L f_X(x)) = 1/L $$
Le \( f_X(x) \) s’annule parfaitement, laissant une densité constante (uniforme).
Le facteur jacobien \( 1 / |g'(x)| \) aplatit automatiquement la distribution en compensant les régions où la densité d’origine était élevée ou faible.
Dans les images discrètes, l’arrondi rend le résultat approximatif, mais le principe est le même.
Pour une analyse plus approfondie de l’égalisation d’histogramme avec des exemples, consultez mon article précédent : ici.
En conclusion
L’ajustement jacobien est l’un de ces éléments mathématiques discrets qui semblent inutiles, jusqu’à ce que vous l’ignoriez et que tout à coup, vos probabilités ne totalisent plus 1. Qu’il s’agisse de mettre au carré les temps d’attente, de modéliser l’énergie issue de la vitesse ou d’aplatir les histogrammes d’images, la transformation modifie non seulement les valeurs, mais aussi la manière dont la probabilité est répartie entre elles. Le facteur \( \left| \frac{dx}{dy} \right| \) (ou son cousin multivarié, le déterminant) est la compensation précise qui maintient la probabilité totale conservée tout en tenant compte de l’étirement et de la compression locaux.
La prochaine fois que vous transformerez une variable aléatoire, n’oubliez pas le sable sur la feuille de caoutchouc : déformez l’axe autant que vous le souhaitez, mais le sable total doit rester le même. L’ajustement jacobien est la règle qui permet que cela se produise.
Code
Références et lectures complémentaires
Quelques éléments que j’ai trouvés utiles.



