|
|
Не буду гадать, но поставил бы сотню баксов, что и перцептрон Розенблатта также если обучить на данных с шумом https://habr.com/ru/articles/958498Внимательно читаем ту же самую статью, и перечисляем сотку ... реквизиты куда высылать :) Цитата: Важно отметить, что была замерена точность прогноза во время обучения после каждой итерации. И именно, эти характеристики мы видим на рис. 7. Вначале обучения, пока внутренняя модель нейросети о задаче MNIST только формируется мы видим, как много на кривой “выбросов”, чем дальше обучение подходит к концу точность прогноза стабилизируется. Эта важная характеристика перцептрона TL&NL указывает на то, что он в отличии от MLP+backprop не может переобучится (overfiting). Более того, мы наблюдаем, что при обучении до нуля стабильность прогнозирования только увеличивается (линия выпрямляется).
Причем здесь "статья", статья - как пост на форуме, это в древнем Египте то что было написано на папирусе считалось истиной, сейчас текст ничего не значит, только код или формула, запустил, проверил, вопросов нет. И вы, вероятно, упускаете одну из ключевых идей машинного обучения: цель модели — не идеально воспроизвести обучающие данные, а научиться обобщать. В реальных данных всегда присутствует шум, ошибки измерений, опечатки в разметке, естественная вариативность процесса или просто случайные флуктуации. Если алгоритм достигает 100 % точности на обучающей выборке, это почти наверняка означает, что он не просто выучил полезную закономерность, а подстроился под шум — то есть переобучился. А поскольку шум в тестовой выборке другой (шум априори случаен!), модель, "запомнившая" обучающий шум, будет работать на тесте хуже, чем более простая и устойчивая модель, то есть не заучившая 100%но лёрн. Это классический пример компромисса смещение–дисперсия. Именно поэтому в ML так важны механизмы контроля сложности модели: В итеративных алгоритмах (например, нейросетях) — это ранняя остановка, на ряду с потбором сложности(слои, нейроны). В kNN — параметр k: маленькое k делает модель чувствительной к выбросам и шуму(но зато 100% акураси на тесте), большое k сглаживает решения, но может привести к недообучению. В деревьях ограничения на глубину дерева, минимальное число объектов в листе, регуляризация и т.п. Даже такие методы, как моментум или адам, помимо ускорения сходимости, косвенно влияют на обобщающую способность: они сглаживают траекторию градиентного спуска и "пролетая" мимо острых, переобученных минимумов в пользу более плоских и устойчивых. Чем выше уровень шума в данных, тем проще должна быть модель, то есть не запоминать обучающую выборку. Это не просто эвристика, а следствие теории обобщения: сложные модели имеют высокую емкость и склонны подстраиваться под случайные флуктуации, если им не мешать. Поэтому подбор гиперпараметров, контролирующих сложность, — это не "настройка ради настройки", а основной способ борьбы с переобучением и адаптации к качеству данных. Если вы видите 100 % акураси на трене и гораздо меньше на тесте — это не "почти идеально", это красный флаг переобучения, и модель в таком виде, скорее всего, бесполезна на практике.
<div class="codetitle"><b>Код:</b></div><div class="codecontent">import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# ----------------------------
# 1. Генерация данных: два класса с нелинейной границей
# ----------------------------
np.random.seed(42)
n_samples = 400
# Генерируем точки в круге радиуса 1.5
r = np.sqrt(np.random.uniform(0, 1.5**2, n_samples))
theta = np.random.uniform(0, 2 * np.pi, n_samples)
x = r * np.cos(theta)
y = r * np.sin(theta)
# Истинная граница: круг радиуса 1 → класс 1 внутри, 0 снаружи
labels = (r < 1).astype(int)
# Добавляем 20% шума в метки
noise_idx = np.random.choice(n_samples, size=int(0.2 * n_samples), replace=False)
labels[noise_idx] = 1 - labels[noise_idx]
X = np.column_stack((x, y))
# Разделение
X_train, X_test, y_train, y_test = train_test_split(
X, labels, test_size=0.5, random_state=42, stratify=labels
)
# ----------------------------
# 2. Обучение моделей с разной сложностью (k от 1 до 30)
# ----------------------------
k_values = np.arange(1, 50)
train_acc = []
test_acc = []
models = {}
for k in k_values:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(X_train, y_train)
models[k] = model
train_acc.append(accuracy_score(y_train, model.predict(X_train)))
test_acc.append(accuracy_score(y_test, model.predict(X_test)))
best_k = k_values[np.argmax(test_acc)]
print(f"Оптимальное k: {best_k}, точность на тесте: {max(test_acc):.3f}")
# ----------------------------
# 3. Визуализация
# ----------------------------
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# --- График 1: 2D данные + фоновая заливка ---
ax = axes[0]
# Сетка для предсказаний
xx, yy = np.meshgrid(np.linspace(-1.6, 1.6, 300), np.linspace(-1.6, 1.6, 300))
grid = np.c_[xx.ravel(), yy.ravel()]
# Выбираем три значения k: переобучение (k=1), оптимум, недообучение (k=30)
k_over = 1
k_opt = best_k
k_under = min(30, max(k_values))
# Цвета и стили
cases = [
(k_over, 'Переобучение (k=1)', 'red'),
(k_opt, f'Оптимум (k={k_opt})', 'green'),
(k_under, f'Недообучение (k={k_under})', 'blue')
]
# Фон: заливка по предсказаниям модели
for k, title, color in cases:
Z = models[k].predict(grid).reshape(xx.shape)
# Используем contourf для заливки фона
ax.contourf(xx, yy, Z, levels=[-0.5, 0.5, 1.5], colors=['#ffcccc', '#cce5ff'], alpha=0.3)
# Теперь рисуем контуры границы (уровень 0.5) поверх фона
for k, title, color in cases:
Z_proba = models[k].predict_proba(grid)[:, 1].reshape(xx.shape)
ax.contour(xx, yy, Z_proba, levels=[0.5], colors=color, linewidths=2, label=title)
# Точки данных
ax.scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1],
c='lightcoral', edgecolor='k', s=25, alpha=0.7, label='Train: класс 0')
ax.scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1],
c='lightblue', edgecolor='k', s=25, alpha=0.7, label='Train: класс 1')
ax.scatter(X_test[y_test == 0, 0], X_test[y_test == 0, 1],
c='red', marker='x', s=20, alpha=0.7, label='Test: класс 0')
ax.scatter(X_test[y_test == 1, 0], X_test[y_test == 1, 1],
c='blue', marker='x', s=20, alpha=0.7, label='Test: класс 1')
# Истинная граница
circle = plt.Circle((0, 0), 1, color='black', fill=False, linestyle=':', linewidth=1.5, label='Истинная граница')
ax.add_patch(circle)
ax.set_xlim(-1.6, 1.6)
ax.set_ylim(-1.6, 1.6)
ax.set_aspect('equal')
ax.set_title('Фоновая заливка и границы решений при разных k')
ax.legend(loc='upper right', fontsize=8)
# --- График 2: кривые точности ---
ax = axes[1]
ax.plot(k_values, train_acc, 'o-', color='blue', label='Train accuracy')
ax.plot(k_values, test_acc, 'o-', color='red', label='Test accuracy')
ax.axvline(best_k, color='gray', linestyle='--', label=f'Лучшее k = {best_k}')
ax.set_xlabel('k (число соседей)')
ax.set_ylabel('Accuracy')
ax.set_title('Зависимость качества от сложности модели (k-NN)')
ax.legend()
ax.grid(True, linestyle=':', alpha=0.7)
ax.set_xticks(k_values[::2]) # чтобы не перегружать ось
plt.tight_layout()
plt.show()</div>
 (Оффтоп)
Вам бы неплохо вспомнить лекции Константина Вячеславовича, их каждый ML-щик, хотя бы раз в год должен обязательно пересматривать, они священное писание. Лучшие из нас помнят наизусть их.
|
|