← Назад к вопросам
Как меняется bias и variance при увеличении k в k-NN модели?
1.8 Middle🔥 211 комментариев
#Машинное обучение
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Bias-Variance Tradeoff в k-NN: Полный анализ
k-Nearest Neighbors (k-NN) — это простой, но очень информативный алгоритм для понимания компромисса между смещением (bias) и дисперсией (variance).
1. Теоретические основы
Что такое Bias и Variance?
Ощибка модели = Bias² + Variance + Irreducible Error
Bias (смещение):
- Способность модели захватить истинную зависимость
- Ошибка от неправильных предположений в модели
- Высокий bias → недообучение (underfitting)
- Низкий bias → модель может приспособиться к сложным паттернам
Variance (дисперсия):
- Чувствительность модели к колебаниям в обучающих данных
- Насколько сильно меняются предсказания при изменении тренировочного набора
- Высокая variance → переобучение (overfitting)
- Низкая variance → стабильные предсказания
2. Как k влияет на bias в k-NN
При k=1:
from sklearn.neighbors import KNeighborsRegressor
import numpy as np
import matplotlib.pyplot as plt
# Создать простой dataset
np.random.seed(42)
X = np.linspace(0, 10, 50).reshape(-1, 1)
y_true = np.sin(X).ravel()
y_train = y_true + np.random.normal(0, 0.2, len(y_true)) # Добавить шум
# k=1
knn_1 = KNeighborsRegressor(n_neighbors=1)
knn_1.fit(X, y_train)
y_pred_1 = knn_1.predict(X)
print("k=1 (Очень низкий bias, очень высокая variance):")
print(f" MSE на тренировочных данных: {np.mean((y_pred_1 - y_train)**2):.6f}")
print(f" Модель практически интерполирует данные")
print(f" Каждая точка просто = ближайшему соседу = самой себе")
print(f" На новых данных может дать совсем другой результат")
Характеристика k=1:
- Модель запоминает каждый обучающий пример
- Каждая точка тренировочного набора идеально предсказывается
- На неизвестных данных может быть очень шумно
- Bias близок к 0 (модель может приспособиться к любому паттерну)
- Variance максимальна (любое изменение в данных сильно влияет)
При k=5, k=10, ..., k=n:
# Сравнение разных k
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
fig.suptitle("Влияние k на предсказания k-NN")
k_values = [1, 5, 15, 30]
for idx, k in enumerate(k_values):
ax = axes[idx // 2, idx % 2]
knn = KNeighborsRegressor(n_neighbors=k)
knn.fit(X, y_train)
y_pred = knn.predict(X)
ax.scatter(X, y_train, alpha=0.5, label="Training data")
ax.plot(X, y_true, "g-", label="True function", linewidth=2)
ax.plot(X, y_pred, "r--", label="k-NN prediction", linewidth=2)
ax.set_title(f"k={k}")
ax.set_ylim([-1.5, 1.5])
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Характеристика большого k:
- Модель усредняет по большему количеству соседей
- Предсказания становятся более гладкими
- Bias растёт (модель не может захватить сложные локальные паттерны)
- Variance падает (предсказания менее чувствительны к шуму)
3. Количественный анализ
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score
import numpy as np
# Создать dataset
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=15,
n_redundant=5,
random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
k_values = np.arange(1, 51, 2)
train_errors = []
test_errors = []
for k in k_values:
knn = KNeighborsClassifier(n_neighbors=k)
knn.fit(X_train, y_train)
train_acc = knn.score(X_train, y_train)
test_acc = knn.score(X_test, y_test)
train_errors.append(1 - train_acc) # Error = 1 - Accuracy
test_errors.append(1 - test_acc)
plt.figure(figsize=(12, 6))
plt.plot(k_values, train_errors, "o-", label="Train Error", linewidth=2)
plt.plot(k_values, test_errors, "s-", label="Test Error", linewidth=2)
plt.xlabel("k (Number of Neighbors)", fontsize=12)
plt.ylabel("Error Rate", fontsize=12)
plt.title("k-NN: Train vs Test Error при изменении k", fontsize=14)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.show()
optimal_k = k_values[np.argmin(test_errors)]
print(f"\nОптимальное k: {optimal_k}")
print(f"Min test error: {min(test_errors):.3f}")
4. Bias-Variance разложение в k-NN
from sklearn.neighbors import KNeighborsRegressor
import numpy as np
def estimate_bias_variance(X_train, y_train, X_test, y_test, k, n_bootstrap=100):
"""
Оценить bias и variance через bootstrap
"""
predictions = np.zeros((n_bootstrap, len(X_test)))
for i in range(n_bootstrap):
# Bootstrap sample
indices = np.random.choice(
len(X_train),
size=len(X_train),
replace=True
)
X_boot = X_train[indices]
y_boot = y_train[indices]
# Обучить модель
model = KNeighborsRegressor(n_neighbors=k)
model.fit(X_boot, y_boot)
predictions[i] = model.predict(X_test)
# Средние предсказания
mean_prediction = predictions.mean(axis=0)
# Bias: ошибка от среднего предсказания
bias = np.mean((mean_prediction - y_test) ** 2)
# Variance: ошибка от вариабельности предсказаний
variance = np.mean(predictions.var(axis=0))
# Total squared error
total_error = np.mean((predictions.mean(axis=0) - y_test) ** 2) + variance
return bias, variance, total_error
# Создать regression dataset
X = np.linspace(0, 10, 100).reshape(-1, 1)
y = np.sin(X).ravel() + np.random.normal(0, 0.2, len(X))
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
k_values = [1, 3, 5, 10, 20, 50]
biases = []
variances = []
total_errors = []
for k in k_values:
bias, variance, total_error = estimate_bias_variance(
X_train, y_train, X_test, y_test, k
)
biases.append(bias)
variances.append(variance)
total_errors.append(total_error)
print(f"\nk={k}:")
print(f" Bias²: {bias:.4f}")
print(f" Variance: {variance:.4f}")
print(f" Total Error: {total_error:.4f}")
# Визуализировать
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# Слева: Bias vs Variance trade-off
ax1.plot(k_values, biases, "o-", label="Bias²", linewidth=2, markersize=8)
ax1.plot(k_values, variances, "s-", label="Variance", linewidth=2, markersize=8)
ax1.plot(k_values, total_errors, "^-", label="Total Error", linewidth=2, markersize=8)
ax1.set_xlabel("k", fontsize=12)
ax1.set_ylabel("Error", fontsize=12)
ax1.set_title("Bias-Variance Tradeoff в k-NN", fontsize=14)
ax1.legend(fontsize=10)
ax1.grid(True, alpha=0.3)
# Справа: Визуальное объяснение
ax2.text(0.05, 0.95, "При k=1:", transform=ax2.transAxes, fontsize=11, weight="bold", va="top")
ax2.text(0.05, 0.88, "• Bias → минимум", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.82, "• Variance → максимум", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.75, "• Model: очень гибкая, переобучение", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.65, "При k=n (все соседи):", transform=ax2.transAxes, fontsize=11, weight="bold", va="top")
ax2.text(0.05, 0.58, "• Bias → максимум", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.52, "• Variance → минимум", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.45, "• Model: всегда средний класс, недообучение", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.35, "Оптимальный k:", transform=ax2.transAxes, fontsize=11, weight="bold", va="top")
ax2.text(0.05, 0.28, "• Баланс между bias и variance", transform=ax2.transAxes, fontsize=10, va="top")
ax2.text(0.05, 0.22, "• Обычно через кросс-валидацию", transform=ax2.transAxes, fontsize=10, va="top")
ax2.set_xlim(0, 1)
ax2.set_ylim(0, 1)
ax2.axis("off")
plt.tight_layout()
plt.show()
5. Практическое влияние k
Случай 1: k=1
Большой соседей: 1
Предсказание: класс единственного ближайшего соседа
Проблема:
- Очень чувствителен к выбросам
- Если сосед - выброс, дать неправильный класс
- На новых данных может быть совсем другой результат
Случай 2: k=sqrt(n)
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target
n_samples = len(X)
# Рекомендация: k = sqrt(n) для классификации
recommended_k = int(np.sqrt(n_samples))
print(f"Dataset size: {n_samples}")
print(f"Recommended k: sqrt({n_samples}) ≈ {recommended_k}")
model = KNeighborsClassifier(n_neighbors=recommended_k)
model.fit(X, y)
print(f"Training accuracy: {model.score(X, y):.3f}")
Случай 3: k=n (все соседи)
Большой соседей: все примеры
Предсказание: класс большинства (самый частый класс)
Проблема:
- Модель полностью игнорирует признаки
- Всегда предсказывает класс большинства
- Accuracy = % наиболее частого класса
- Никакой способности к обучению
6. Таблица: Как меняются метрики
┌────┬──────────┬──────────┬────────────┬─────────────┐
│ k │ Bias │ Variance │ Train Acc │ Test Acc │
├────┼──────────┼──────────┼────────────┼─────────────┤
│ 1 │ Очень │ Очень │ 100% или │ Может быть │
│ │ низкий │ высокая │ близко │ низкой │
├────┼──────────┼──────────┼────────────┼─────────────┤
│ 3 │ Низкий │ Высокая │ Высокая │ Хорошая │
├────┼──────────┼──────────┼────────────┼─────────────┤
│ 5 │ Средний │ Средняя │ Хорошая │ Хорошая │
├────┼──────────┼──────────┼────────────┼─────────────┤
│ 10 │ Выше │ Ниже │ Ниже │ Может быть │
│ │ среднего │ среднего │ │ хорошая │
├────┼──────────┼──────────┼────────────┼─────────────┤
│ n │ Очень │ Очень │ % большин. │ % большин. │
│ │ высокий │ низкая │ │ │
└────┴──────────┴──────────┴────────────┴─────────────┘
7. Выбор оптимального k
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
# Метод 1: Cross-validation
k_values = np.arange(1, 31)
mean_scores = []
std_scores = []
for k in k_values:
model = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
mean_scores.append(scores.mean())
std_scores.append(scores.std())
# Найти оптимальный k
optimal_idx = np.argmax(mean_scores)
optimal_k = k_values[optimal_idx]
plt.figure(figsize=(10, 5))
plt.errorbar(k_values, mean_scores, yerr=std_scores, marker="o")
plt.xlabel("k")
plt.ylabel("Cross-validated Accuracy")
plt.title(f"Выбор оптимального k (оптимум в k={optimal_k})")
plt.grid(True, alpha=0.3)
plt.axvline(x=optimal_k, color="red", linestyle="--", label=f"Optimal k={optimal_k}")
plt.legend()
plt.show()
print(f"\nОптимальное k: {optimal_k}")
print(f"Mean accuracy: {mean_scores[optimal_idx]:.3f}")
print(f"Std deviation: {std_scores[optimal_idx]:.3f}")
8. Итоговые выводы
При увеличении k в k-NN:
-
Bias РАСТЁТ:
- Модель усредняет по большему числу соседей
- Локальные паттерны теряются
- Становится сложнее захватить сложные зависимости
-
Variance ПАДАЕТ:
- Предсказания становятся стабильнее
- Меньше влияния шума в обучающих данных
- Более гладкие границы решения
-
Общая ошибка:
- При маленьких k: высокая variance доминирует (переобучение)
- При больших k: высокий bias доминирует (недообучение)
- Оптимум k - компромисс между ними
Практические рекомендации:
- Начни с k=sqrt(n) для классификации
- Используй кросс-валидацию для поиска оптимума
- При дисбалансе классов: чётное k помогает
- Для регрессии: часто k больше, чем для классификации