k Plus Proches Voisins Régression : Guide Complet
Résumé — La k plus proches voisins régression est une méthode d’apprentissage supervisé qui prédit une valeur numérique en faisant la moyenne des cibles des k observations les plus proches du point à prédire. Contrairement à la k-NN classification, où les voisins votent par majorité pour une classe, en régression on calcule une moyenne (éventuellement pondérée par la distance) des valeurs cibles. Cette approche simple mais puissante permet de modéliser des relations complexes sans supposer de forme fonctionnelle prédéfinie.
Principe mathématique
L’idée centrale de la k plus proches voisins régression est remarquablement directe. Étant donné un nouvel exemple x et un ensemble d’apprentissage D = {(x₁, y₁), …, (xₙ, yₙ)}, où chaque yᵢ est une valeur continue, on identifie les k observations de D les plus proches de x selon une métrique de distance (typiquement la distance euclidienne).
Prédictions uniformes
La prédiction ŷ(x) est la moyenne arithmétique des cibles des k plus proches voisins :
ŷ(x) = (1/k) × Σ yᵢ
où la somme parcourt les indices i correspondant aux k voisins les plus proches de x.
Prédictions pondérées
Pour donner plus d’influence aux voisins les plus proches, on utilise souvent une pondération inverse de la distance :
ŷ(x) = Σ (wᵢ × yᵢ) / Σ wᵢ
où wᵢ = 1 / d(x, xᵢ) et d(·, ·) est la distance choisie. Un voisin très proche aura un poids très élevé, tandis qu’un voisin éloigné aura un poids faible. Cette variante est particulièrement utile lorsque les données locales présentent des variations brutales.
Métriques de distance
Le choix de la distance est crucial car il détermine quels points sont considérés comme « similaires » :
- Distance euclidienne (L2) : d(x, x′) = √(Σ (xⱼ − x′ⱼ)²) — la plus courante, correspond à l’intuition géométrique naturelle.
- Distance de Manhattan (L1) : d(x, x′) = Σ |xⱼ − x′ⱼ| — plus robuste aux valeurs aberrantes, utile en haute dimension.
- Distance de Minkowski (Lp) : d(x, x′) = (Σ |xⱼ − x′ⱼ|ᵖ)^(1/p) — généralisation paramétrique ; p=2 redonne l’euclidienne, p=1 la Manhattan.
- Distance de Mahalanobis : prend en compte les corrélations entre les variables, mais nécessite l’inversion de la matrice de covariance.
Il est fondamental de normaliser ou standardiser les variables avant d’appliquer k-NN. En effet, une variable avec une échelle dix fois plus grande dominera le calcul de distance au détriment des autres.
Intuition
Imaginez que vous souhaitiez estimer le prix d’un appartement dans un quartier que vous connaissez peu. Vous n’avez pas de modèle économique sophistiqué, mais vous connaissez les prix de vente récents dans la zone. L’approche k plus proches voisins régression consiste à repérer les k appartements les plus similaires au vôtre (même surface, même nombre de chambres, même quartier) et à faire la moyenne de leurs prix de vente. Si deux appartements sont quasiment identiques au vôtre et un troisième un peu différent, les deux premiers « comptent » davantage dans votre estimation — c’est l’idée de la pondération par distance.
Ce qui distingue la k plus proches voisins régression des méthodes paramétriques comme la régression linéaire, c’est qu’elle ne suppose aucune forme fonctionnelle a priori. La relation entre les caractéristiques et la cible émerge directement des données. Si la vraie relation est en forme de vague sinusoïdale, le k-NN la découvrira de lui-même, à condition que k soit bien choisi et que l’on dispose d’assez de données d’entraînement.
Cette propriété fait du k-NN un apprentissage non paramétrique et paresseux (lazy learning) : aucune étape d’apprentissage n’est effectuée à l’avance. Les données d’entraînement sont simplement mémorisées, et tout le « travail » est reporté au moment de la prédiction.
Voir aussi
- Trouvez le Plus Petit Multiple en Python : Guide Complet et Astuces d’Optimisation
- Implémentez l’algorithme de hachage SHA-1 en Python : Guide étape par étape
Implémentation Python
Installation des dépendances
pip install scikit-learn numpy matplotlib
Exemple 1 : Données sinusoïdales bruitées
Commençons par un exemple visuel avec des données suivant une courbe sinusoïdale bruitée :
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
# Génération de données sinusoïdales bruitées
np.random.seed(42)
X = np.sort(5 * np.random.rand(200, 1), axis=0)
y = np.sin(X).ravel() + np.random.normal(0, 0.15, X.shape[0])
# Séparation entraînement / test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# k-NN avec k=5
knn = KNeighborsRegressor(n_neighbors=5, weights="uniform")
knn.fit(X_train, y_train)
# Prédictions
y_pred = knn.predict(X_test)
print(f"R² sur le test : {r2_score(y_test, y_pred):.4f}")
print(f"RMSE sur le test : {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}")
# Visualisation
X_plot = np.linspace(0, 5, 500).reshape(-1, 1)
y_true_plot = np.sin(X_plot)
y_pred_plot = knn.predict(X_plot)
plt.figure(figsize=(10, 6))
plt.scatter(X_train, y_train, c="steelblue", alpha=0.5, s=20, label="Entraînement")
plt.scatter(X_test, y_test, c="orange", alpha=0.5, s=20, label="Test")
plt.plot(X_plot, y_true_plot, c="red", linewidth=2, label="Vraie fonction (sin)")
plt.plot(X_plot, y_pred_plot, c="green", linewidth=2, label="Prédiction k-NN (k=5)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("k Plus Proches Voisins Régression sur données sinusoïdales")
plt.legend()
plt.tight_layout()
plt.show()
Le modèle k-NN capture la forme sinusoïdale sans aucune connaissance préalable de la fonction sinus — il se contente de moyenniser les voisins locaux.
Exemple 2 : Comparaison avec la régression linéaire
La force du k-NN régression apparaît clairement quand on le compare à un modèle linéaire sur des données non linéaires :
from sklearn.linear_model import LinearRegression
# Même problème
X_lin = np.sort(5 * np.random.rand(200, 1), axis=0)
y_lin = np.sin(X_lin).ravel() + np.random.normal(0, 0.1, X_lin.shape[0])
# k-NN
knn_comp = KNeighborsRegressor(n_neighbors=5, weights="distance")
knn_comp.fit(X_lin, y_lin)
# Régression linéaire
lr = LinearRegression()
lr.fit(X_lin, y_lin)
X_plot = np.linspace(0, 5, 500).reshape(-1, 1)
plt.figure(figsize=(10, 6))
plt.scatter(X_lin, y_lin, c="lightgray", s=15, label="Données")
plt.plot(X_plot, np.sin(X_plot), c="red", linewidth=2, label="Vraie courbe")
plt.plot(X_plot, lr.predict(X_plot), c="blue", linewidth=2, label="Régression linéaire")
plt.plot(X_plot, knn_comp.predict(X_plot), c="green", linewidth=2, label="k-NN (k=5, distance)")
plt.xlabel("x")
plt.ylabel("y")
plt.title("k-NN vs Régression linéaire sur données non linéaires")
plt.legend()
plt.tight_layout()
plt.show()
La régression linéaire ne peut tracer qu’une droite : elle échoue complètement à capturer la sinusoïde. Le k-NN, lui, épouse la courbe grâce à son caractère non paramétrique.
Exemple 3 : Impact du choix de k — sous-apprentissage et sur-apprentissage
Le paramètre k est le levier principal du compromis biais-variance :
k_values = [2, 5, 15, 50]
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.ravel()
for i, k in enumerate(k_values):
knn_k = KNeighborsRegressor(n_neighbors=k, weights="distance")
knn_k.fit(X, y)
y_plot = knn_k.predict(X_plot)
axes[i].scatter(X, y, c="lightgray", s=10, label="Données")
axes[i].plot(X_plot, np.sin(X_plot), c="red", linewidth=2, label="Vraie courbe", linestyle="--")
axes[i].plot(X_plot, y_plot, c="navy", linewidth=2, label=f"k={k}")
axes[i].set_title(f"k = {k}")
axes[i].legend()
axes[i].set_xlabel("x")
axes[i].set_ylabel("y")
fig.suptitle("Impact de k sur la qualité d’ajustement — k plus proches voisins régression")
plt.tight_layout()
plt.show()
- k trop petit (k=2) : la courbe suit chaque fluctuation du bruit — c’est le sur-apprentissage. Le modèle mémorise au lieu de généraliser.
- k optimal (k=5 à k=15) : la prédiction épouse la sinusoïde tout en lissant le bruit — c’est la zone idéale.
- k trop grand (k=50) : la courbe s’aplatit et tend vers la moyenne globale — c’est le sous-apprentissage. On perd toute la structure locale.
Pour automatiser le choix de k, on utilise typiquement la validation croisée :
from sklearn.model_selection import cross_val_score
k_range = range(1, 51)
cv_scores = []
for k in k_range:
knn_cv = KNeighborsRegressor(n_neighbors=k, weights="distance")
scores = cross_val_score(knn_cv, X, y, cv=5, scoring="neg_mean_squared_error")
cv_scores.append(-scores.mean())
best_k = int(k_range[np.argmin(cv_scores)])
print(f"Meilleur k selon la validation croisée : {best_k}")
plt.figure(figsize=(8, 5))
plt.plot(list(k_range), cv_scores, marker="o")
plt.axvline(best_k, c="red", linestyle="--", label=f"Meilleur k={best_k}")
plt.xlabel("k (nombre de voisins)")
plt.ylabel("RMSE (validation croisée)")
plt.title("Choix optimal de k par validation croisée")
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Exemple 4 : Données synthétiques multi-dimensionnelles
La k plus proches voisins régression fonctionne aussi avec plusieurs variables explicatives :
from sklearn.datasets import make_regression
from sklearn.preprocessing import StandardScaler
# Création d’un jeu de données multi-dimensionnel
X_multi, y_multi = make_regression(
n_samples=1000, n_features=10, n_informative=5,
noise=25, random_state=42
)
# Standardisation — indispensable pour k-NN
scaler = StandardScaler()
X_multi_scaled = scaler.fit_transform(X_multi)
X_tr, X_te, y_tr, y_te = train_test_split(
X_multi_scaled, y_multi, test_size=0.25, random_state=42
)
# Comparaison pondération uniforme vs distance
knn_uniform = KNeighborsRegressor(n_neighbors=7, weights="uniform")
knn_distance = KNeighborsRegressor(n_neighbors=7, weights="distance")
knn_uniform.fit(X_tr, y_tr)
knn_distance.fit(X_tr, y_tr)
r2_u = r2_score(y_te, knn_uniform.predict(X_te))
r2_d = r2_score(y_te, knn_distance.predict(X_te))
print(f"R² uniform : {r2_u:.4f}")
print(f"R² distance : {r2_d:.4f}")
La pondération par distance offre souvent un léger gain de qualité prédictive, car elle accorde plus de confiance aux voisins les plus similaires.

