Постановка задачи машинного обучения

⏱ 30 мин · 🧩 заданий: 6

Три составляющие

Любая задача обучения с учителем формулируется одинаково — от линейной регрессии до ChatGPT.

1. Данные. Обучающая выборка из N пар «объект — ответ»:

D = \{(x_1, y_1), (x_2, y_2), \dots, (x_N, y_N)\}

x_i — вектор признаков (площадь и этаж; 64 пикселя; токены текста), y_i — правильный ответ (цена; цифра; следующее слово).

2. Модель. Семейство функций f_\theta(x) с параметрами \theta. Выбирая архитектуру (прямая, нейросеть из 3 слоёв, трансформер), мы выбираем семейство; конкретная функция определяется значениями параметров.

3. Функция потерь \ell(\hat y, y) — штраф за предсказание \hat y при правильном ответе y. Для регрессии — квадрат ошибки, для классификации — кросс-энтропия (мы вывели обе из правдоподобия).

Обучение — поиск параметров, минимизирующих среднюю потерю на выборке, эмпирический риск:

\theta^* = \arg\min_\theta \; \frac{1}{N}\sum_{i=1}^{N} \ell\big(f_\theta(x_i),\, y_i\big)

Так машинное обучение сводится к оптимизации: найти минимум функции L(\theta), где \theta — миллионы чисел. Главный инструмент для этого — градиентный спуск (следующий урок).

import numpy as np

# данные
X = np.array([32, 38, 45, 50, 56, 63, 70, 78, 85, 95], dtype=float)
y = np.array([6.8, 7.9, 9.1, 10.4, 11.0, 12.9, 13.8, 15.6, 16.5, 19.2])

# модель: семейство прямых с параметрами θ = (w, b)
def model(x, theta):
    w, b = theta
    return w * x + b

# функция потерь и эмпирический риск
loss = lambda pred, target: (pred - target) ** 2
risk = lambda theta: loss(model(X, theta), y).mean()

for theta in [(0.1, 5), (0.2, 0.5), (0.19, 0.3)]:
    print(f"θ = {theta}: риск = {risk(theta):.4f}")

Главное: качество на новых данных

Минимизировать ошибку на обучающих данных легко: можно просто запомнить все ответы. Модель «если вход совпадает с одним из обучающих — верни его ответ» имеет нулевую ошибку на выборке и бесполезна на новых объектах.

Нам нужно обобщение: хорошая работа на данных, которых модель не видела. Поэтому данные делят на части:

Выборка Доля Для чего
Обучающая (train) ~60–80% подбор параметров градиентным спуском
Валидационная (val) ~10–20% выбор гиперпараметров, архитектуры, момента остановки
Тестовая (test) ~10–20% одна финальная честная оценка

Не подглядывайте в тест

Каждый раз, когда вы смотрите на тестовую ошибку и что-то меняете, тест превращается в ещё одну валидацию, и итоговая оценка становится завышенной. Тестовая выборка используется один раз — в самом конце.

import numpy as np
import vis

rng = np.random.default_rng(0)
x = np.sort(rng.uniform(-3, 3, 30))
y = np.sin(x) + rng.normal(0, 0.25, 30)
idx = rng.permutation(30)
tr, te = idx[:20], idx[20:]

grid = np.linspace(-3, 3, 200)
curves = {}
for deg in (1, 3, 15):
    coef = np.polyfit(x[tr], y[tr], deg)
    err_tr = ((np.polyval(coef, x[tr]) - y[tr]) ** 2).mean()
    err_te = ((np.polyval(coef, x[te]) - y[te]) ** 2).mean()
    print(f"многочлен степени {deg:2}: ошибка train = {err_tr:.3f}, test = {err_te:.3f}")
    curves[f"степень {deg}"] = np.clip(np.polyval(coef, grid), -2, 2)
vis.line(grid, curves, title="Простая, подходящая и слишком сложная модель", xlabel="x")
vis.scatter(x[tr], y[tr], title="Обучающие точки")
  • Степень 1 — недообучение: модель слишком простая, плохо и на обучении, и на тесте.
  • Степень 3 — то, что нужно.
  • Степень 15 — переобучение: идеально проходит через обучающие точки, но между ними выделывает дикие петли, и ошибка на тесте огромна.

Борьбе с переобучением посвящён целый урок в модуле о нейросетях.

Параметры и гиперпараметры

  • Параметры \theta подбирает алгоритм обучения: веса и сдвиги.
  • Гиперпараметры выбирает человек (или перебор по валидации): архитектура, число слоёв и нейронов, шаг обучения, размер батча, число эпох, сила регуляризации.

Типичный цикл работы: выбрать гиперпараметры → обучить на train → оценить на val → поменять гиперпараметры → … → лучшую модель один раз оценить на test.

Подготовка признаков

Признаки бывают в очень разных масштабах: площадь — десятки, цена в рублях — миллионы, доля — от 0 до 1. Градиентный спуск плохо работает, когда масштабы сильно различаются (поверхность потерь становится вытянутым оврагом — это мы видели на функции Розенброка). Поэтому признаки стандартизируют: вычитают среднее и делят на стандартное отклонение.

Утечка данных

Среднее и отклонение считают только по обучающей выборке, а затем применяют те же числа к валидации и тесту. Если считать их по всем данным, информация из теста «просочится» в обучение.

Базовое решение (baseline)

Прежде чем радоваться результату, сравните его с тупейшим решением:

  • регрессия — всегда предсказывать среднее значение y по обучающей выборке;
  • классификация — всегда предсказывать самый частый класс;
  • случайное угадывание.

Модель, которая не обгоняет baseline, ничему не научилась. Для цифр случайное угадывание даёт 10%, а кросс-энтропия случайной модели — \ln 10 \approx 2.30.

import numpy as np
import ml_data

X, y = ml_data.load_digits()
values, counts = np.unique(y, return_counts=True)
print("самый частый класс:", values[counts.argmax()], f"— baseline-точность {counts.max() / len(y):.1%}")
print(f"кросс-энтропия равномерного угадывания: ln 10 = {np.log(10):.3f}")

Итог

  • Задача обучения: данные D, семейство моделей f_\theta, функция потерь \ell; обучение — минимизация эмпирического риска \frac1N\sum \ell(f_\theta(x_i), y_i).
  • Цель — обобщение на новые данные: train для параметров, val для гиперпараметров, test — один раз в конце.
  • Недообучение — модель слишком проста; переобучение — запоминает выборку.
  • Признаки стандартизируют по статистикам обучающей выборки; результат сравнивают с baseline.

Практика решено 0 из 6

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Заполнить таблицу

Что это — параметр модели (подбирается обучением) или гиперпараметр (выбирает человек)?

параметргиперпараметр
Веса линейного слоя
Шаг обучения (learning rate)
Число слоёв нейросети
Сдвиг (bias) нейрона
Размер батча
Число эпох обучения
Отметьте ячейку — «+», пустая — «−».
Задание 2 Один вариант ответа

Модель показала ошибку 0.1% на обучающей выборке и 35% на тестовой. Что произошло?

Задание 3 Один вариант ответа

Для чего нужна валидационная выборка, если уже есть тестовая?

Задание 4 Один вариант ответа

В задаче классификации 95% писем — не спам. Модель говорит «не спам» всегда. Какая у неё точность (accuracy)?

Задание 5 Программа на Python

Разбиение данных. Напишите split(X, y, val=0.2, test=0.2, seed=0): перемешайте индексы (np.random.default_rng(seed).permutation(len(X))) и разбейте данные на три части: первые round(test·N) перемешанных индексов — тест, следующие round(val·N) — валидация, остальное — обучение. Верните (X_train, y_train, X_val, y_val, X_test, y_test).

Какие тесты проверяют код (1)
X = np.arange(100).reshape(50, 2); y = np.arange(50)
Xtr, ytr, Xva, yva, Xte, yte = split(X, y)
assert (len(Xtr), len(Xva), len(Xte)) == (30, 10, 10), f"Размеры {len(Xtr), len(Xva), len(Xte)}, ожидалось (30, 10, 10)"
assert sorted(np.concatenate([ytr, yva, yte]).tolist()) == list(range(50)), "Каждый объект ровно в одной части"
assert np.array_equal(Xtr[:, 0] // 2, ytr), "Признаки и ответы должны остаться в парах"
assert not np.array_equal(ytr, np.arange(30)), "Данные нужно перемешать"

Задание 6 Программа на Python

Нормализация без утечки. Напишите класс Standardizer с методами fit(X) (запоминает среднее и стандартное отклонение каждого столбца в атрибутах mean и std) и transform(X) (возвращает (X - mean) / std). Статистики считаются только на обучающих данных и потом применяются к любым другим.

Какие тесты проверяют код (1)
tr = np.array([[1.0, 100], [3, 300]])
s = Standardizer().fit(tr)
assert np.allclose(s.mean, [2, 200]) and np.allclose(s.std, [1, 100])
assert np.allclose(s.transform(tr), [[-1, -1], [1, 1]])
assert np.allclose(s.transform(np.array([[5.0, 200]])), [[3, 0]]), "Новые данные — со статистиками обучающей выборки"

Войдите, чтобы отмечать пройденные уроки.