Entraîner un réseau de neurones profond est essentiellement une tâche de compression. Nous voulons représenter la distribution de nos données d’entraînement comme une fonction paramétrée par un ensemble de matrices. Plus la distribution est complexe, plus nous avons besoin de paramètres. La raison d’approximer la distribution entière est de pouvoir propager n’importe quel point valide lors de l’inférence avec le même modèle, et les mêmes poids. Mais si notre modèle était entraîné à la volée, pendant l’inférence ? Alors, lors de la propagation de , nous n’aurions besoin de modéliser que la distribution locale autour de . Puisque la région locale devrait avoir une dimensionnalité plus faible que l’ensemble d’entraînement complet, un modèle bien plus simple suffira !
C’est l’idée derrière l’approximation locale ou la régression locale. Considérons une simple tâche de régression.
Tâche
On nous donne échantillons des données suivantes :
où
Code de tracé
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# Générer les données
np.random.seed(42)
n_points = 100
X = np.random.uniform(0, 1, n_points)
epsilon = np.random.normal(0, 1 / 3, n_points)
Y = np.sin(4 * X) + epsilon
# Fonction vraie
x_true = np.linspace(0, 1, 500)
y_true = np.sin(4 * x_true)
# Créer le tracé
fig = go.Figure()
# Ajouter les points de dispersion pour les données bruitées
fig.add_trace(
go.Scatter(
x=X,
y=Y,
mode="markers",
name="Données bruitées",
marker=dict(color="gray"),
)
)
# Ajouter la fonction vraie
fig.add_trace(
go.Scatter(
x=x_true,
y=y_true,
mode="lines",
name="Fonction vraie",
line=dict(color="red"),
)
)
# Mettre à jour la mise en page commune aux thèmes
fig.update_layout(
title="Données",
xaxis_title="X",
yaxis_title="Y",
)
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
filename = output_dir / f"local_approximation_data_{theme['name']}.html"
themed_fig.write_html(filename)
print(f"Tracé enregistré sous {filename}")
# Afficher le tracé
fig.show()
On note l’ensemble de données qui est constitué des échantillons .
Notre tâche est d’ajuster une courbe raisonnable aux données, qui corresponde approximativement à la fonction vraie. Notons cette courbe .
K Plus Proches Voisins
Étant donné un certain , une approche consiste à prendre les valeurs les plus proches de , et à moyenner leurs valeurs pour obtenir l’estimation. C’est-à-dire,
où désigne les points les plus proches de .
Code du graphique
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# Générer les données
np.random.seed(42)
n_points = 100
X = np.random.uniform(0, 1, n_points)
epsilon = np.random.normal(0, 1 / 3, n_points)
Y = np.sin(4 * X) + epsilon
# Fonction réelle
x_true = np.linspace(0, 1, 500)
y_true = np.sin(4 * x_true)
# k-NN pour une plage de k
x_curve = np.arange(0, 1, 0.01)
k_range = range(1, 21)
y_curves_knn = {}
for k in k_range:
y_curve = []
for x in x_curve:
distances = np.square(X - x)
nearest_indices = np.argsort(distances)[:k]
y_curve.append(np.mean(Y[nearest_indices]))
y_curves_knn[k] = y_curve
# Créer la figure Plotly
fig = go.Figure()
# Ajouter les traces statiques
fig.add_trace(
go.Scatter(x=X, y=Y, mode="markers", name="Données bruitées", marker=dict(color="gray"))
)
fig.add_trace(
go.Scatter(
x=x_true, y=y_true, mode="lines", name="Fonction réelle", line=dict(color="red")
)
)
# Ajouter la première courbe k-NN (k=13, la position par défaut du curseur)
initial_k = 13
fig.add_trace(
go.Scatter(
x=x_curve,
y=y_curves_knn[initial_k],
mode="lines",
name="Courbe k-NN",
line=dict(color="yellow"),
)
)
# Définir les étapes du curseur
steps = []
for k in k_range:
step = dict(
method="update",
args=[
{"y": [Y, y_true, y_curves_knn[k]]}, # Mettre à jour les données y pour les traces
{
"title": f"Courbe k-NN interactive avec curseur pour k = {k}"
}, # Mettre à jour le titre dynamiquement
],
label=f"{k}",
)
steps.append(step)
# Ajouter le curseur à la mise en page
sliders = [
dict(
active=initial_k - 1,
currentvalue={"prefix": "k = "},
pad={"t": 50},
steps=steps,
)
]
fig.update_layout(
sliders=sliders,
title=f"Courbe k-NN interactive avec curseur pour k = {initial_k}",
xaxis_title="X",
yaxis_title="Y",
)
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
html_path = output_dir / f"knn_slider_{theme['name']}.html"
themed_fig.write_html(html_path)
print(f"Graphique interactif enregistré sous {html_path}")
# Afficher le graphique
fig.show()
Vous pouvez voir en utilisant le curseur qu’un plus grand donne une courbe plus lisse, mais qu’un faible intègre une partie du bruit. Aux extrêmes, suit exactement les données d’entraînement et donne une moyenne globale plate.
Régression par noyau de Nadaraya–Watson
Au lieu de limiter votre sous-ensemble de données à points, vous pourriez plutôt considérer tous les points de l’ensemble, mais pondérer la contribution de chaque point en fonction de sa proximité à . Considérez le modèle
où est un noyau, que nous utiliserons comme une mesure de proximité.
Cette fonction est paramétrée par , appelée la largeur de bande, qui contrôle la plage des valeurs de dans les données qui jouent un rôle dans la sortie de . Cela devient clair si nous traçons ces fonctions.
Fonctions Noyau
Ce qui est tracé ci-dessous est
où assure que s’intègre à sur son support.
Code de tracé
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
from scipy.integrate import quad
# Définir les fonctions noyau
def epanechnikov_kernel(u):
return np.maximum(0, 0.75 * (1 - u**2))
def tricube_kernel(u):
return np.maximum(0, (1 - np.abs(u) ** 3) ** 3)
def gaussian_kernel(u):
return np.exp(-0.5 * u**2) / np.sqrt(2 * np.pi)
def renormalized_kernel(kernel_func, u_range, bandwidth):
def kernel_with_lambda(u):
scaled_u = u / bandwidth
normalization_factor, _ = quad(lambda v: kernel_func(v / bandwidth), *u_range)
return kernel_func(scaled_u) / normalization_factor
return kernel_with_lambda
# Générateur de tracé pour les fonctions noyau
def generate_kernel_plot(
kernel_name, kernel_func, x_range, u_range, lambda_values, y_range
):
fig = go.Figure()
# Lambda initial
initial_lambda = lambda_values[len(lambda_values) // 2]
# Générer la courbe initiale du noyau
x = np.linspace(*x_range, 500)
kernel_with_lambda = renormalized_kernel(kernel_func, u_range, initial_lambda)
y = kernel_with_lambda(x)
fig.add_trace(
go.Scatter(
x=x,
y=y,
mode="lines",
name=f"Noyau {kernel_name} (λ={initial_lambda:.2f})",
line=dict(color="green"),
)
)
# Créer les trames pour le curseur
frames = []
for bandwidth in lambda_values:
kernel_with_lambda = renormalized_kernel(kernel_func, u_range, bandwidth)
y = kernel_with_lambda(x)
frames.append(
go.Frame(
data=[
go.Scatter(
x=x,
y=y,
mode="lines",
name=f"Noyau {kernel_name} (λ={bandwidth:.2f})",
line=dict(color="green"),
)
],
name=f"{bandwidth:.2f}",
)
)
# Ajouter les trames à la figure
fig.frames = frames
# Ajouter un curseur
sliders = [
{
"active": len(lambda_values) // 2,
"currentvalue": {"prefix": "Largeur de bande λ : "},
"steps": [
{
"args": [
[f"{bandwidth:.2f}"],
{"frame": {"duration": 0, "redraw": True}, "mode": "immediate"},
],
"label": f"{bandwidth:.2f}",
"method": "animate",
}
for bandwidth in lambda_values
],
}
]
# Mettre à jour la mise en page
fig.update_layout(
title=f"Fonction Noyau {kernel_name}",
xaxis_title="u",
yaxis_title="K(u)",
yaxis_range=y_range,
sliders=sliders,
autosize=True,
updatemenus=[
{
"buttons": [
# {
# "args": [
# None,
# {
# "frame": {"duration": 500, "redraw": True},
# "fromcurrent": True,
# },
# ],
# "label": "Lire",
# "method": "animate",
# },
# {
# "args": [
# [None],
# {
# "frame": {"duration": 0, "redraw": True},
# "mode": "immediate",
# },
# ],
# "label": "Pause",
# "method": "animate",
# },
],
"direction": "left",
"pad": {"r": 10, "t": 87},
"showactive": False,
"type": "buttons",
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top",
}
],
)
return fig
# Fonctions noyau
kernels = {
"Epanechnikov": epanechnikov_kernel,
"Tricube": tricube_kernel,
"Gaussian": gaussian_kernel,
}
# Paramètres
x_range_plot = (-3, 3) # Plage des valeurs u pour le tracé
u_range_integration = (-3, 3) # Plage pour la normalisation
lambda_values = np.linspace(0.01, 2, 20) # Valeurs linéaires de λ de 0.01 à 2
y_range_plot = (0, 1.5) # Plage ajustée pour les fonctions normalisées
# Générer et afficher les tracés pour chaque noyau
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
for kernel_name, kernel_func in kernels.items():
fig = generate_kernel_plot(
kernel_name,
kernel_func,
x_range_plot,
u_range_integration,
lambda_values,
y_range_plot,
)
# Sauvegarder les figures thématisées en fichiers HTML
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
filename = (
output_dir
/ f"{kernel_name}_dynamic_normalization_kernel_function_{theme['name']}.html"
)
themed_fig.write_html(filename, auto_play=False)
print(f"Tracé du noyau {kernel_name} sauvegardé dans {filename}")
# Afficher la figure
fig.show()
Résultats
Nous traçons maintenant les résultats pour chacune des fonctions noyau. Chaque graphique comporte un curseur , qui contrôle la sortie en direct.
Code de tracé
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# Définir les fonctions noyau
def epanechnikov_kernel(u):
return np.maximum(0, 0.75 * (1 - u**2))
def tricube_kernel(u):
return np.maximum(0, (1 - np.abs(u) ** 3) ** 3)
def gaussian_kernel(u):
return np.exp(-0.5 * u**2) / np.sqrt(2 * np.pi)
# Fonction de régression par noyau
def kernel_regression(X, Y, x_curve, kernel_func, bandwidth):
y_curve = []
for x in x_curve:
distances = np.abs(X - x) / bandwidth
weights = kernel_func(distances)
weighted_average = (
np.sum(weights * Y) / np.sum(weights) if np.sum(weights) > 0 else 0
)
y_curve.append(weighted_average)
return y_curve
# Générer les données
np.random.seed(42)
n_points = 100
X = np.random.uniform(0, 1, n_points)
epsilon = np.random.normal(0, 1 / 3, n_points)
Y = np.sin(4 * X) + epsilon
# Courbe réelle
x_true = np.linspace(0, 1, 500)
y_true = np.sin(4 * x_true)
# Points pour l'estimation par noyau
x_curve = x_true
# Fonctions noyau
kernels = {
"Epanechnikov": epanechnikov_kernel,
"Tricube": tricube_kernel,
"Gaussian": gaussian_kernel,
}
# Plage de largeurs de bande pour le curseur en échelle logarithmique
lambda_values = np.logspace(-2, 0, 20) # De 0.01 à 1
# Générer des graphiques séparés pour chaque noyau
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
# Générer des graphiques séparés pour chaque noyau
for kernel_name, kernel_func in kernels.items():
fig = go.Figure()
# Ajouter les points de dispersion pour les données bruitées
fig.add_trace(
go.Scatter(
x=X, y=Y, mode="markers", name="Données bruitées", marker=dict(color="gray")
)
)
# Ajouter la fonction réelle
fig.add_trace(
go.Scatter(
x=x_true,
y=y_true,
mode="lines",
name="Fonction réelle",
line=dict(color="red"),
)
)
# Ajouter la courbe initiale du noyau
initial_bandwidth = lambda_values[0]
y_curve = kernel_regression(X, Y, x_curve, kernel_func, initial_bandwidth)
fig.add_trace(
go.Scatter(
x=x_curve,
y=y_curve,
mode="lines",
name=f"Nadaraya-Watson ({kernel_name})",
line=dict(color="green"),
)
)
# Créer les trames pour le curseur
frames = []
for bandwidth in lambda_values:
y_curve = kernel_regression(X, Y, x_curve, kernel_func, bandwidth)
frames.append(
go.Frame(
data=[
go.Scatter(
x=X,
y=Y,
mode="markers",
name="Données bruitées",
marker=dict(color="gray"),
),
go.Scatter(
x=x_true,
y=y_true,
mode="lines",
name="Fonction réelle",
line=dict(color="red"),
),
go.Scatter(
x=x_curve,
y=y_curve,
mode="lines",
name=f"Nadaraya-Watson ({kernel_name})",
line=dict(color="green"),
),
],
name=f"{bandwidth:.2f}",
)
)
# Ajouter les trames à la figure
fig.frames = frames
# Ajouter le curseur
sliders = [
{
"active": 0,
"currentvalue": {"prefix": "Largeur de bande λ : "},
"steps": [
{
"args": [
[f"{bandwidth:.2f}"],
{"frame": {"duration": 0, "redraw": True}, "mode": "immediate"},
],
"label": f"{bandwidth:.2f}",
"method": "animate",
}
for bandwidth in lambda_values
],
}
]
# Mettre à jour la mise en page
fig.update_layout(
autosize=True,
title=f"Régression par noyau de Nadaraya-Watson (Noyau {kernel_name})",
xaxis_title="X",
yaxis_title="Y",
sliders=sliders,
updatemenus=[
{
"buttons": [
{
"args": [
None,
{
"frame": {"duration": 500, "redraw": True},
"fromcurrent": True,
},
],
"label": "Lecture",
"method": "animate",
},
{
"args": [
[None],
{
"frame": {"duration": 0, "redraw": True},
"mode": "immediate",
},
],
"label": "Pause",
"method": "animate",
},
],
"direction": "left",
"pad": {"r": 10, "t": 87},
"showactive": False,
"type": "buttons",
"x": 0.1,
"xanchor": "right",
"y": 0,
"yanchor": "top",
}
],
)
# Sauvegarder la figure dans un fichier HTML par thème
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
filename = output_dir / f"{kernel_name}_kernel_regression_{theme['name']}.html"
themed_fig.write_html(filename, auto_play=False)
print(f"Graphique du noyau {kernel_name} sauvegardé sous {filename}")
# Afficher la figure
fig.show()
Nous voyons qu’une simple moyenne pondérée des données permet de modéliser une sinusoïde assez bien.
Régression Linéaire Locale
Dans la régression à noyau de Nadaraya-Watson, nous prenons une moyenne pondérée dans un voisinage défini par la fonction noyau . Un problème potentiel avec cette approche est l’interpolation lisse au sein des voisinages locaux, puisque nous ne supposons pas réellement que la région suit un modèle particulier.
Et si nous supposions que chaque région est localement linéaire ? Nous pourrions alors résoudre l’ajustement des moindres carrés et interpoler librement !
Région : $k$-NN
Définissons notre région locale comme les plus proches voisins de notre entrée. Soit et les valeurs correspondantes. Les coefficients de l’ajustement des moindres carrés sont
Code de tracé
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# Générer les données
np.random.seed(42)
n_points = 100
X = np.random.uniform(0, 1, n_points)
epsilon = np.random.normal(0, 1 / 3, n_points)
Y = np.sin(4 * X) + epsilon
# Fonction réelle
x_true = np.linspace(0, 1, 500)
y_true = np.sin(4 * x_true)
# Régression Linéaire Locale par k-NN
def knn_linear_regression(X, Y, x_curve, k_range):
y_curves = {}
for k in k_range:
y_curve = []
for x in x_curve:
# Trouver les k plus proches voisins
distances = np.abs(X - x)
nearest_indices = np.argsort(distances)[:k]
# Sélectionner les k plus proches voisins
X_knn = X[nearest_indices]
Y_knn = Y[nearest_indices]
# Créer la matrice de conception pour les k plus proches voisins
X_design = np.vstack((np.ones_like(X_knn), X_knn)).T
# Résoudre pour beta par les moindres carrés ordinaires
beta = np.linalg.pinv(X_design.T @ X_design) @ X_design.T @ Y_knn
# Prédire la valeur y
y_curve.append(beta[0] + beta[1] * x)
y_curves[k] = y_curve
return y_curves
# Variables communes
x_curve = np.arange(0, 1, 0.01)
k_range = range(1, 21) # Valeurs de k de 1 à 20
initial_k = 10 # Valeur par défaut de k
# Calculer la RLL par k-NN
y_curves_knn = knn_linear_regression(X, Y, x_curve, k_range)
# Créer la figure Plotly
fig = go.Figure()
# Ajouter les traces statiques
fig.add_trace(
go.Scatter(x=X, y=Y, mode="markers", name="Données bruitées", marker=dict(color="gray"))
)
fig.add_trace(
go.Scatter(
x=x_true, y=y_true, mode="lines", name="Fonction réelle", line=dict(color="red")
)
)
# Ajouter la première courbe k-NN (k=initial_k)
fig.add_trace(
go.Scatter(
x=x_curve,
y=y_curves_knn[initial_k],
mode="lines",
name="Courbe k-NN",
line=dict(color="yellow"),
)
)
# Définir les étapes du curseur
steps = []
for k in k_range:
step = dict(
method="update",
args=[
{"y": [Y, y_true, y_curves_knn[k]]}, # Mettre à jour les données y pour les traces
{
"title": f"Courbe de Régression Linéaire Locale par k-NN avec k = {k}"
}, # Mettre à jour le titre dynamiquement
],
label=f"{k}",
)
steps.append(step)
# Ajouter le curseur à la mise en page
sliders = [
dict(
active=k_range.index(initial_k), # Utiliser l'index de initial_k
currentvalue={"prefix": "k = "},
pad={"t": 50},
steps=steps,
)
]
fig.update_layout(
autosize=True,
sliders=sliders,
title=f"Courbe de Régression Linéaire Locale par k-NN avec k = {initial_k}",
xaxis_title="X",
yaxis_title="Y",
)
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
html_path = output_dir / f"knn_slider_llr_{theme['name']}.html"
themed_fig.write_html(html_path)
print(f"Graphique interactif k-NN enregistré sous {html_path}")
# Afficher le graphique
fig.show()
On constate que la sortie peut être assez irrégulière pour de petites valeurs de .
Région : Fonction Noyau
Peut-être pouvons-nous réutiliser certaines idées du noyau de Nadaraya-Watson. Nous aimerions prendre en compte tous les points de l’ensemble d’apprentissage à des degrés divers, avec des poids plus élevés à l’intérieur de la région locale et des poids plus faibles à l’extérieur.
Pour cela, nous pouvons utiliser un objectif des moindres carrés pondérés, avec les poids . Cela a pour solution
Tracé des résultats pour diverses fonctions noyau :
Code de tracé
from pathlib import Path
import numpy as np
import plotly.graph_objects as go
# Générer les données
np.random.seed(42)
n_points = 100
X = np.random.uniform(0, 1, n_points)
epsilon = np.random.normal(0, 1 / 3, n_points)
Y = np.sin(4 * X) + epsilon
# Fonction vraie
x_true = np.linspace(0, 1, 500)
y_true = np.sin(4 * x_true)
# Noyaux
def gaussian_kernel(u):
return np.exp(-0.5 * u**2)
def epanechnikov_kernel(u):
return np.maximum(0, 1 - u**2)
def tricube_kernel(u):
return np.maximum(0, (1 - np.abs(u) ** 3) ** 3)
# Régression linéaire locale pour un noyau spécifique
def local_linear_regression(X, Y, x_curve, bandwidths, kernel):
y_curves = {}
for λ in bandwidths:
λ_rounded = round(λ, 2)
y_curve = []
for x in x_curve:
# Calculer les poids en utilisant le noyau spécifié
distances = (X - x) / λ
weights = kernel(distances)
W = np.diag(weights)
# Créer la matrice de conception
X_design = np.vstack((np.ones_like(X), X)).T
# Résoudre pour beta en utilisant les moindres carrés pondérés
beta = np.linalg.pinv(X_design.T @ W @ X_design) @ X_design.T @ W @ Y
# Prédire la valeur y
y_curve.append(beta[0] + beta[1] * x)
y_curves[λ_rounded] = y_curve
return y_curves
# Variables communes
x_curve = np.arange(0, 1, 0.01)
bandwidths = np.linspace(0.05, 0.5, 20)
initial_λ = bandwidths[len(bandwidths) // 2]
# Générer les tracés pour chaque noyau
kernels = {
"Noyau Gaussien": gaussian_kernel,
"Noyau d'Epanechnikov": epanechnikov_kernel,
"Noyau Tricube": tricube_kernel,
}
plots = []
for kernel_name, kernel_func in kernels.items():
# Calculer la RLL avec le noyau spécifié
y_curves = local_linear_regression(X, Y, x_curve, bandwidths, kernel_func)
# Créer la figure Plotly
fig = go.Figure()
# Ajouter les traces statiques
fig.add_trace(
go.Scatter(
x=X, y=Y, mode="markers", name="Données Bruitées", marker=dict(color="gray")
)
)
fig.add_trace(
go.Scatter(
x=x_true,
y=y_true,
mode="lines",
name="Fonction Vraie",
line=dict(color="red"),
)
)
# Ajouter la première courbe RLL (en utilisant la valeur médiane des largeurs de bande)
fig.add_trace(
go.Scatter(
x=x_curve,
y=y_curves[round(initial_λ, 2)],
mode="lines",
name=f"Courbe {kernel_name}",
line=dict(color="yellow"),
)
)
# Définir les étapes du curseur
steps = []
for λ in bandwidths:
λ_rounded = round(λ, 2)
step = dict(
method="update",
args=[
{"y": [Y, y_true, y_curves[λ_rounded]]}, # Mettre à jour les données y pour les traces
{
"title": f"RLL : {kernel_name} avec Largeur de Bande λ = {λ_rounded}"
}, # Mettre à jour le titre dynamiquement
],
label=f"{λ_rounded}",
)
steps.append(step)
# Ajouter le curseur à la mise en page
sliders = [
dict(
active=len(bandwidths) // 2, # Utiliser l'index de la largeur de bande médiane
currentvalue={"prefix": "λ = "},
pad={"t": 50},
steps=steps,
)
]
fig.update_layout(
autosize=True,
sliders=sliders,
title=f"RLL : {kernel_name} avec Largeur de Bande λ = {round(initial_λ, 2)}",
xaxis_title="X",
yaxis_title="Y",
)
plots.append(fig)
# Afficher et sauvegarder les tracés avec des arrière-plans thématiques
themes = [
{
"name": "light",
"template": "plotly_white",
"font_color": "#141413",
"background": "#f0efea",
"axis_color": "#141413",
"gridcolor": "rgba(20, 20, 19, 0.2)",
},
{
"name": "dark",
"template": "plotly_dark",
"font_color": "#f0efea",
"background": "#141413",
"axis_color": "#f0efea",
"gridcolor": "rgba(240, 239, 234, 0.2)",
},
]
output_dir = Path(__file__).resolve().parents[3] / "static"
output_dir.mkdir(parents=True, exist_ok=True)
for kernel_name, fig in zip(kernels.keys(), plots):
fig.show()
for theme in themes:
themed_fig = go.Figure(fig)
themed_fig.update_layout(
template=theme["template"],
font=dict(color=theme["font_color"]),
paper_bgcolor=theme["background"],
plot_bgcolor=theme["background"],
)
themed_fig.update_xaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
themed_fig.update_yaxes(
showline=True,
linecolor=theme["axis_color"],
tickcolor=theme["axis_color"],
tickfont=dict(color=theme["axis_color"]),
title_font=dict(color=theme["axis_color"]),
gridcolor=theme["gridcolor"],
zeroline=False,
)
filename = (
output_dir
/ f"llr_{kernel_name.lower().replace(' ', '_')}_{theme['name']}.html"
)
themed_fig.write_html(filename)
print(f"Tracé interactif pour {kernel_name} sauvegardé sous {filename}")
Je trouve que les résultats semblent bien plus lisses !
Références
- The Elements of Statistical Learning - Hastie, Tibshirani, et Friedman (2009). Un guide complet sur l’exploration de données, l’inférence et la prédiction. En savoir plus.