Files
optimiz-rs/examples/notebooks/16_robust_drift.ipynb
T

276 lines
185 KiB
Plaintext
Raw Normal View History

{
"cells": [
{
"cell_type": "markdown",
"id": "0324628f",
"metadata": {},
"source": [
"# 16 — Robust drift estimation (Huber)\n",
"\n",
"Doc page: [robust_drift.rst](../../docs/source/algorithms/robust_drift.rst).\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "c4c76fee",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:06:05.154949Z",
"iopub.status.busy": "2026-05-12T14:06:05.154589Z",
"iopub.status.idle": "2026-05-12T14:06:05.740238Z",
"shell.execute_reply": "2026-05-12T14:06:05.738386Z"
}
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from optimizr import _core as opt\n",
"plt.rcParams['figure.figsize'] = (8.5, 4.5)\n",
"plt.rcParams['figure.dpi'] = 110\n",
"plt.rcParams['axes.grid'] = True\n",
"plt.rcParams['grid.alpha'] = 0.3\n"
]
},
{
"cell_type": "markdown",
"id": "1acf345b",
"metadata": {},
"source": [
"## Cellule 1 — Récupération du drift d'un OU sous bruit gaussien\n",
"\n",
"**Théorème.** Pour le processus d'OrnsteinUhlenbeck discret\n",
"$$x_{k+1} = x_k + (a + b x_k)\\,\\Delta t + \\sigma\\,\\varepsilon_k,$$\n",
"l'estimateur de Huber via IRLS converge vers $(a, b)$ pour bruit\n",
"symétrique.\n",
"\n",
"**Équation pivot (lien avec OU continu).** $dX_t = (a + b X_t) dt\n",
"+ \\sigma dW_t$ ; en posant $a = \\theta\\mu$, $b = -\\theta$, on\n",
"récupère le mean-reversion $dX_t = \\theta(\\mu - X_t)dt + \\sigma dW_t$.\n",
"\n",
"**Ce que la cellule vérifie.** $\\theta = 2$, $\\mu = 1$ donc\n",
"$a_\\text{vrai} = 2$, $b_\\text{vrai} = -2$. L'estimateur retrouve\n",
"ces valeurs sur $N = 1000$ observations.\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c43c131e",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:06:05.743817Z",
"iopub.status.busy": "2026-05-12T14:06:05.743466Z",
"iopub.status.idle": "2026-05-12T14:06:06.453430Z",
"shell.execute_reply": "2026-05-12T14:06:06.452371Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a vrai / estimé : 2.000 / 2.296\n",
"b vrai / estimé : -2.000 / -2.415\n",
"theta_hat = 2.415, mu_hat = 0.951\n",
"itérations IRLS : 9\n"
]
},
{
"data": {
"image/png": "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UQGlUokRI0bgt7/9Ldxud1r9tXfvXlx++eUYMmQIVCoVKioqcOqpp+L5558X53nxxRcTpvuJl9Mw1XN4prz99tuYO3cujEYjNBoNpk6dGtG+dPjrX/8qnofDhw/HAw88AIZhYuYT9vXTTz/Fww8/jNGjR0OlUuHxxx+Pu7+1tbWYO3cugMj3mtraWtx3330AgOHDh4vTKbcj0Rfoe58FCILIK06nE7Nnz8aMGTNw3333weFwQK/XAwD+/Oc/Y9asWbj55ptRXl6OgwcP4vnnn8e6devw/fffY+TIkUnXvX37dsybNw9arRbXXHMNhg0bhoMHD+Lvf/871q9fj2+//RZFRUUAAJvNhtmzZ2Pnzp04//zzcc0110ChUGDnzp145513cMstt+DCCy9EIBDAihUrcP3112P27NkAgMrKSnGbS5cuxSuvvILTTjsNK1asgNPpxPPPP4+zzz4bL7/8ckqHZ7I+sdvtOP3003Ho0CEsWbIEkydPhsViwXPPPYdTTjkFX3zxBU466aSk637zzTcBADfeeGPCeaZMmYJTTjkFX3/9NRoaGjB06NCUbc4EhmFw5plnorGxETfeeCPGjh0Lp9OJffv24X//+x9+//vfp1zH999/j8WLF+MXv/gFrrzySqxevRp//vOfIZPJsHPnTtjtdvzmN7+By+XCE088gfPOOw9HjhzJqUMg0+Nx9dVX4+WXX8YFF1yAn/3sZwCAd955Bz/5yU/w97//HTfccEPO2kYQBEEQ6XD48GEAQFlZmTgtk/sbwzA4++yzsXnzZlx88cWYN28empqasHTpUowePTrhds844wyUlZXht7/9LTiOQ1VVFRiGwTnnnIPPPvsMl19+OW644Qa43W68+uqrWLBgAd577z2cd955AILC25VXXonTTjsNy5Ytg16vx7Fjx/C///0P+/fvx/jx47v9vPGnP/0JDzzwACZOnIhly5aB53m8+uqruPzyy1FXVxezvMvlwuzZszFt2jQ88MADaG1txZNPPolFixahrq4u7WeQ//f//h9aW1tx3XXXoaamRlwum/bMnTsXU6dOxYMPPojGxkY8/fTTmDNnDr766itMnjw5rfaEk06fjhs3Dq+88gquuuqquOmUAGDNmjVYvHgxhgwZgltvvRWVlZXYsWMHVq5cia+++gobNmxI6ig0m82YP38+OI7DL3/5SwwfPhwWiwU7d+7EZ599hmuvvTbjfRNI9m6SCcuWLcP999+P+fPnY9myZdBoNFi7di2uu+46HDp0CI888kjKdfz2t7/Fo48+Kgq6Pp8P//znP/H+++8nXOauu+6C2+3GkiVLYDKZMGTIkLjz/eUvf8F3330X815jMBjw0ksv4d1338WTTz6J8vJyAMCkSZMy7gOC6HF4giAGHBs2bOAB8EuWLImYPnfuXB4Af88998Rdzul0xkzbtWsXr1Ao+JtuuilmXcOGDYuYNmXKFH748OG82WyOmP7NN9/wMpmMX758uTjt5ptv5gHwTzzxRMw2WZaN2ZcXXnghZr7169fzAPhFixbxDMOI09va2viKigq+uLiYdzgcSducrE9+9atf8QqFgt+8eXPEdIvFwtfU1PDz5s2LWSaasrIy3mAwpJzvlltu4QHwH3zwgTgNQEx7w1myZAkPgN+wYUPSde/YsYMHwD/yyCM
"text/plain": [
"<Figure size 1320x440 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"rng = np.random.default_rng(0)\n",
"theta_true, mu_true, sigma = 2.0, 1.0, 0.2\n",
"N = 1000\n",
"dt = 0.01\n",
"x = np.zeros(N)\n",
"x[0] = 0.5\n",
"for k in range(N - 1):\n",
" x[k + 1] = x[k] + theta_true * (mu_true - x[k]) * dt + \\\n",
" sigma * np.sqrt(dt) * rng.standard_normal()\n",
"\n",
"res = opt.robust_drift(x.tolist(), dt, huber_delta=1.345)\n",
"a_hat, b_hat = res['a'], res['b']\n",
"a_true, b_true = theta_true * mu_true, -theta_true\n",
"print(f\"a vrai / estimé : {a_true:.3f} / {a_hat:.3f}\")\n",
"print(f\"b vrai / estimé : {b_true:.3f} / {b_hat:.3f}\")\n",
"print(f\"theta_hat = {-b_hat:.3f}, mu_hat = {a_hat / -b_hat:.3f}\")\n",
"print(f\"itérations IRLS : {res['iterations']}\")\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n",
"ts = np.arange(N) * dt\n",
"axes[0].plot(ts, x, lw=0.8)\n",
"axes[0].axhline(mu_true, ls='--', color='gray', label=fr'$\\mu = {mu_true}$')\n",
"axes[0].set_xlabel('t'); axes[0].set_ylabel('x(t)')\n",
"axes[0].set_title(\"Trajectoire OU simulée\")\n",
"axes[0].legend()\n",
"dx = (x[1:] - x[:-1]) / dt\n",
"axes[1].scatter(x[:-1], dx, s=4, alpha=0.4, label='observations')\n",
"xs_lin = np.linspace(x.min(), x.max(), 50)\n",
"axes[1].plot(xs_lin, a_true + b_true * xs_lin, 'k--',\n",
" lw=1.5, label='vrai drift')\n",
"axes[1].plot(xs_lin, a_hat + b_hat * xs_lin, 'C3-',\n",
" lw=2, label='Huber estimé')\n",
"axes[1].set_xlabel('x'); axes[1].set_ylabel(r'$\\Delta x / \\Delta t$')\n",
"axes[1].set_title(\"Régression robuste sur le drift\")\n",
"axes[1].legend()\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "1fabf161",
"metadata": {},
"source": [
"**Résultat attendu.** $\\hat a \\approx 2.0$, $\\hat b \\approx -2.0$.\n",
"\n",
"**Lecture du graphique.** Gauche : trajectoire qui oscille autour de\n",
"$\\mu = 1$. Droite : nuage des incréments avec deux droites\n",
"superposées (vraie et estimée).\n",
"\n",
"**Conclusion.** Sur bruit gaussien, Huber se comporte comme OLS\n",
"(efficacité ~95 %).\n"
]
},
{
"cell_type": "markdown",
"id": "bdb6e2dc",
"metadata": {},
"source": [
"## Cellule 2 — Robustesse face à un bruit de Cauchy\n",
"\n",
"**Théorème (consistance de Huber sous queues lourdes).** Pour\n",
"$\\varepsilon_k \\sim \\text{Cauchy}$, OLS *diverge* (variance\n",
"infinie) tandis que Huber reste consistant.\n",
"\n",
"**Équation pivot.**\n",
"$$\\hat\\theta_\\text{Huber} \\xrightarrow{a.s.} \\theta_0,\n",
" \\qquad \\hat\\theta_\\text{OLS} \\nrightarrow \\theta_0\n",
" \\text{ (Cauchy)}.$$\n",
"\n",
"**Ce que la cellule vérifie.** On compare Huber vs OLS sur 30 jeux\n",
"synthétiques OU avec bruit de Cauchy injecté.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f01bc011",
"metadata": {
"execution": {
"iopub.execute_input": "2026-05-12T14:06:06.458599Z",
"iopub.status.busy": "2026-05-12T14:06:06.458229Z",
"iopub.status.idle": "2026-05-12T14:06:07.284751Z",
"shell.execute_reply": "2026-05-12T14:06:07.283487Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OLS : médiane = 1.278, max = 6.83\n",
"Huber : médiane = 0.023, max = 0.19\n"
]
},
{
"data": {
"image/png": "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UkBAgCIiIoo0m3zFihVq166d/Pz85OXlpcjISL3//vvXHYt9+/apR48eCggIsJ6r5j7vaWlpevzxxxUaGiovLy/9+OOPkv5MJs2YMUNNmza1jnmnTp20bdu2PH3kXhqb39IX+Z2/F9Tvhg0b1LFjR4WEhMjT01M1a9ZUt27d9P333xd6fICSxIwrwMlkZ2crIyNDOTk5OnnypGbOnKkLFy6oZ8+eeer99NNPcnFx0d13323TRsuWLbVkyRKdPXtWVatWLbDP3bt3a/ny5Ro+fLgGDBhwU7NgTp8+rbZt26p3796aPn26Dh8+rDlz5qhPnz7av3+/XFz+zJdPnTpVL7/8srp3764RI0bI3d1dJ0+e1IYNG/T7778rNDT0un3s2LFDp0+f1sMPP6waNWpct94TTzyhL774QsuXL1f37t2LfUyOcPHiRbVr106RkZGaPHmyEhMTNW/ePLVt21b/+c9/rMnFXF9++aVmz56tmJgYjRw50m5rRy1cuFBTpkxRamqq3nzzTWv5tZdZ/FVCQoJ27typ1q1bKyIiotB9zZ07VwEBARoxYoRCQ0OVmJioDz74QB06dNB3332nVq1aFfs4hg4dqk8++USRkZGKjY1VSEiIjh49qpUrV+q1116Tu7u7te769es1d+5cjRo1SkOHDtWWLVv07rvvymQyWS/r6NOnj6pXr673339fo0ePztPX6dOntW7dOvXv35/LVAHAiYwaNUozZszQmjVr9OKLL1633qJFizRo0CC1bt1aEyZMkI+Pj06fPq1vvvlGv/32m5o0aaKXXnpJVatW1apVq/Tmm29a3++bNm2ap63Y2FhlZWVpyJAhCg4OVq1atQqMs2vXrvLz89Mrr7yis2fP6p133tF9992nr776Svfdd1+xjtvDw0Pvvvuuxo8fb/0Mr1ev3g33Gzp0qBYuXKjWrVtr6tSpunDhgt5//3117dpVn3zyic3MtsKeA15PZmam7rnnHh05ckRDhgxRVFSUDhw4oJ49e+Yb688//6z27durcuXKGj58uMLCwnT48GG9/fbb2rJli3bu3GlNGPXv31/ffvutHn/8cd1xxx26evWqjh49WuhZ+RMmTNBrr72mDh06aMKECfLy8tLXX3+tkSNH6siRI3rjjTfy1E9MTFS7du3Ut29fTZs2TWfOnMmzvVOnTgoMDNQLL7wgi8Wi6tWrKycnR927d9d3332nRx55RDExMcrKytKnn36qjh076osvvrD5e6Co8ut327Zt6tmzp5o0aaLY2FgFBgbqzJkz2rFjh/7f//t/uueee26qT8AuDABO5dtvvzUkWX+qVKliPP/880Z2dnaeej4+PkZISEi+bcTGxhqSjL179xbYX24/69evt9k2YcIEQ5Jx/Phxm23t2rUzwsLC8pSFhYUZkozFixfnKZ82bZohyfj666+tZZGRkcatt95aYHz5mTNnjiHJmDlz5g3rpaamGpKM5s2bW8tyj2nBggX57nP8+HFDktGuXbtixWYYf47Nze4vyXjyySfzlO/evdtwcXHJ03ZuvJUqVTL2799v09aQIUOM673Vh4WF2cSZX1l+z/WNfPXVV4YkY8yYMYXexzAM48KFCzZlSUlJRmBgoNG9e/cC4zSM/43HhAkTrGXLli0zJBn333+/ze+RxWIxLBZLnn29vLy
"text/plain": [
"<Figure size 1210x440 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"rng = np.random.default_rng(123)\n",
"theta_true, mu_true = 2.0, 1.0\n",
"sigma_cauchy = 0.05\n",
"N = 1000; dt = 0.01\n",
"n_runs = 30\n",
"a_true, b_true = theta_true * mu_true, -theta_true\n",
"\n",
"ols_errs, huber_errs = [], []\n",
"for _ in range(n_runs):\n",
" x = np.zeros(N); x[0] = 0.5\n",
" for k in range(N - 1):\n",
" eps = rng.standard_cauchy() * sigma_cauchy\n",
" x[k + 1] = x[k] + theta_true * (mu_true - x[k]) * dt + eps * np.sqrt(dt)\n",
" Xmat = np.column_stack([np.ones(N - 1), x[:-1]])\n",
" yvec = (x[1:] - x[:-1]) / dt\n",
" th_ols, *_ = np.linalg.lstsq(Xmat, yvec, rcond=None)\n",
" r = opt.robust_drift(x.tolist(), dt, huber_delta=1.345)\n",
" th_h = np.array([r['a'], r['b']])\n",
" ols_errs.append(np.linalg.norm(th_ols - np.array([a_true, b_true])))\n",
" huber_errs.append(np.linalg.norm(th_h - np.array([a_true, b_true])))\n",
"\n",
"ols_errs = np.array(ols_errs); huber_errs = np.array(huber_errs)\n",
"print(f\"OLS : médiane = {np.median(ols_errs):.3f}, max = {ols_errs.max():.2f}\")\n",
"print(f\"Huber : médiane = {np.median(huber_errs):.3f}, max = {huber_errs.max():.2f}\")\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
"axes[0].boxplot([ols_errs, huber_errs], labels=['OLS', 'Huber'])\n",
"axes[0].set_ylabel(r'$\\|\\hat\\theta - \\theta_0\\|$')\n",
"axes[0].set_yscale('log')\n",
"axes[0].set_title(f\"{n_runs} runs OU + bruit Cauchy\")\n",
"axes[1].hist(np.clip(ols_errs, 0, 10), bins=20,\n",
" alpha=0.5, label='OLS', color='C3')\n",
"axes[1].hist(huber_errs, bins=20, alpha=0.7, label='Huber', color='C0')\n",
"axes[1].set_xlabel(r'$\\|\\hat\\theta - \\theta_0\\|$')\n",
"axes[1].set_ylabel('fréquence')\n",
"axes[1].set_title(\"Distribution des erreurs\")\n",
"axes[1].legend()\n",
"fig.tight_layout(); plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "276c9e2d",
"metadata": {},
"source": [
"**Résultat attendu.** Huber médiane bien inférieure à OLS médiane.\n",
"\n",
"**Lecture du graphique.** Gauche : boxplots log, Huber serré.\n",
"Droite : histogramme — OLS dispersé, Huber concentré près de $0$.\n",
"\n",
"**Conclusion.** L'estimateur robuste est indispensable dès que les\n",
"queues s'éloignent du gaussien (mesures expérimentales bruitées,\n",
"signaux de capteurs aberrants).\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "rhftlab",
"language": "python",
"name": "rhftlab"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}