|  | 
				
					| Не буду гадать, но поставил бы сотню баксов, что и перцептрон Розенблатта также если обучить на данных с шумом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 npimport 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-щик, хотя бы раз в год должен обязательно пересматривать, они священное писание. Лучшие из нас помнят наизусть их.
 |  |