Перейти к содержанию
С нуля
Программа курса
EN Открыть

Программа курса

Модуль 0. Фундамент до старта

Учиться в приложении: тьютор, задачи с кодом →

Это вход в курс «LLM с нуля» для тех, у кого пока нет подготовки. Если ты не прошёл первый тест-диагностику и попал сюда: это нормально: модуль для того и написан. Он занимает от 2 до 6 недель и идёт перед ядром курса (так здесь называются основные 24 недели). Сколько именно недель нужно тебе, решает не самооценка, а входная диагностика ниже: 60–90 минут, ручка и бумага, без AI и без поиска. Короткий тест в приложении: та же диагностика, только в сокращённой форме (подробнее ниже).

Нагрузка: 15–20 часов в неделю. Неделя Foundation (так называется этот модуль) устроена не как лекции. Это короткая теория плюс много работы руками: всё, что здесь изучается, в ядре используется с первой недели как само собой разумеющееся. Бояться этого не нужно: каждая неделя ниже объясняет свои термины с нуля и даёт пример на маленьких числах.


Для кого

Ядро курса с первой недели требует считать Якобианы (таблицы производных функции от многих переменных) и вручную делать backward (обратный проход: вычисление градиентов) через MLP (многослойную сеть из линейных слоёв и нелинейностей). Если половина этих слов пока незнакома: это нормально: именно для таких случаев модуль и написан. Ядро предполагает, что четыре вещи уже работают на автомате: Python без борьбы с языком, формы тензоров (размеры многомерных массивов чисел по каждой оси) без гадания, производные сложных функций, базовая вероятность. Кроме того, в ядре нет отдельного места под классический ML, а на ML coding интервью иногда просят реализовать логистическую регрессию или k-means.

Модуль нужен тем, кто:

  • пришёл из разработки и уверенно пишет код, но математику последний раз видел в институте;
  • пришёл из науки (физика, математика, биоинформатика) и знает математику, но пишет скрипты, а не пакеты с тестами;
  • учил ML по верхам через sklearn.fit() и никогда не реализовывал алгоритм сам.

Модуль не нужен тем, кто набрал ≥80% в диагностике. Проходить его «на всякий случай» невыгодно: пробелы, которые останутся, ядро вскроет само, и закрывать их точечно дешевле.


Как устроен модуль

Диагностика (60–90 мин)
   │
   ├─ ≥80%        → сразу неделя 1 ядра
   ├─ 50–80%      → только недели F, соответствующие слабым блокам
   └─ <50%        → все шесть недель F1–F6
   │
Выходной контроль → неделя 1 ядра
НеделяТемаЗакрывает блок диагностики
F1Python и инженерная гигиена1 (Python), 6 (системное мышление)
F2NumPy и формы тензоров2 (формы и NumPy)
F3PyTorch и autograd на практике3 (производные), 6 (системное мышление)
F4Линейная алгебра2 (формы и NumPy, вопросы 9–10)
F5Матанализ и вероятность3 (производные), 4 (вероятность)
F6Классический ML, минимально5 (базовый ML)

Почему порядок именно такой: F1–F3 дают инструмент, а F4–F6 дают содержание. Линейную алгебру удобнее учить, когда NumPy уже в руках и каждое утверждение можно проверить в две строки кода.


Входная диагностика

Правила. 30 вопросов, 6 блоков по 5. Отвечать письменно, кратко, с выводом там, где он нужен. Балл за вопрос: 1 за полный ответ, 0.5 за верный ответ без объяснения или за объяснение с мелкой ошибкой, 0 за неверный ответ или «не знаю». Не гадать: «не знаю» честнее и полезнее, потому что ведёт в нужную неделю.

Ключи ответов лежат после всех вопросов. Не открывать до конца.

Две формы одного теста. В приложении диагностика короче: 24 вопроса с вариантами ответов, по 4 на блок, около 10 минут. Здесь полная бумажная версия: 30 открытых вопросов, которые надо решить, а не узнать. Блоки, пороги (80% / 50%) и рекомендации траектории у обеих форм одинаковые. Короткую проходят для быстрой ориентировки, полную проходят, если результат оказался на границе порога.

Блок 1. Python

  1. Что напечатает код и почему?
    def add(x, acc=[]):
        acc.append(x)
        return acc
    add(1)
    print(add(2))
  2. Что вернёт sum(x * x for x in range(4))? Сколько памяти займёт то же выражение с range(10**9)? Изменится ли ответ, если заменить круглые скобки на квадратные?
  3. Напишите декоратор timed, который печатает время выполнения любой функции и возвращает её результат без изменений.
  4. Что напечатает код и почему?
    a = [[0] * 3] * 3
    a[0][0] = 1
    print(a)
  5. Скрипт работает 40 минут. Как узнать, какая именно функция тратит время? Назовите инструмент и команду.

Блок 2. Формы тензоров, NumPy, линейная алгебра

  1. A.shape == (32, 10), b.shape == (10,). Какая форма у A + b? Что произойдёт при A + b[:, None]?
  2. X.shape == (B, D), W.shape == (D, F). Какая форма у X @ W и сколько операций умножения-сложения выполняется?
  3. x = np.arange(12).reshape(3, 4), y = x.T. Копирует ли .T данные? Чему станет равно y[0, 0] после x[0, 0] = 100?
  4. Каков ранг матрицы u vᵀ для ненулевых векторов u ∈ ℝⁿ, v ∈ ℝᵐ?
  5. Матрица Q ортогональна. Чему равны ‖Qx‖ и Q⁻¹?

Блок 3. Производные

  1. Найдите производную σ(x) = 1 / (1 + e^{−x}) и выразите её через σ(x).
  2. Найдите производную f(x) = log(1 + eˣ).
  3. Найдите градиент f(x) = xᵀAx по вектору x. Как он упрощается для симметричной A?
  4. Найдите градиент L(w) = (wᵀx − y)² по w.
  5. L = sin(u), u = x². Найдите dL/dx.

Блок 4. Вероятность

  1. Честную монету бросают 3 раза. Какова вероятность хотя бы одного орла?
  2. Чему равны матожидание и дисперсия Binomial(n, p)?
  3. Болезнь у 1% людей. Тест даёт положительный результат у 99% больных и у 1% здоровых. Человек получил положительный результат. Какова вероятность, что он болен?
  4. X и Y независимы, Var(X) = Var(Y) = 1. Чему равна Var(X − Y)?
  5. Монета выпадает орлом с вероятностью p. Сколько бросков в среднем нужно до первого орла включительно?

Блок 5. Базовый ML

  1. Лосс на обучении падает, на валидации растёт. Что происходит и какие три действия можно предпринять?
  2. Положительный класс составляет 1% данных. Модель всегда предсказывает «отрицательный». Какая у неё accuracy? Почему это плохо и какую метрику смотреть вместо неё?
  3. Зачем нужен отдельный test-набор, если уже есть validation?
  4. Какую функцию потерь минимизирует логистическая регрессия и почему не MSE?
  5. Приведите два конкретных примера утечки данных (data leakage).

Блок 6. Системное мышление

  1. Модель на 1 млрд параметров. Сколько памяти занимают её веса в fp32? В bf16?
  2. Обучение идёт 10 часов, загрузка GPU держится на 20%. Где вы будете искать причину в первую очередь?
  3. Назовите четыре вещи, без которых эксперимент нельзя воспроизвести через месяц.
  4. Что делает git bisect и в какой ситуации он незаменим?
  5. В цикле обучения на каждом шаге вызывается loss.item() для печати. Почему это может заметно замедлить обучение на GPU?

Ключи ответов

Блок 1.

  1. [1, 2]. Значение по умолчанию вычисляется один раз, при определении функции, и список живёт между вызовами. Правильно: acc=None и внутри if acc is None: acc = [].
  2. 14 (0+1+4+9). Генератор ленивый: память O(1) при любом range. Со списком ответ тот же, но сначала материализуется список из 10⁹ элементов, а это десятки гигабайт.
  3. Ключевые элементы: functools.wraps(fn), обёртка с *args, **kwargs, time.perf_counter() до и после, return result. Без wraps теряются имя и docstring функции; без return декоратор ломает любую функцию с результатом.
  4. [[1, 0, 0], [1, 0, 0], [1, 0, 0]]. Умножение списка копирует ссылки, а не объекты: все три строки указывают на один и тот же список. Правильно: [[0] * 3 for _ in range(3)].
  5. python -m cProfile -s cumtime script.py (или line_profiler для построчного). Засчитывается любой профилировщик; не засчитывается «расставлю print со временем»: это измерение того, что уже подозреваешь, а не поиск.

Блок 2.

  1. (32, 10): b растягивается по первой оси. b[:, None] имеет форму (10, 1); сравнение справа: 1 и 10 совместимы, 10 и 32 нет → ошибка broadcasting.
  2. (B, F); B·D·F умножений-сложений, то есть 2BDF FLOPs. Это та же двойка, из которой в неделе 9 получится 2N на токен.
  3. Не копирует: .T возвращает view с переставленными strides. y[0, 0] == 100.
  4. Ранг 1: каждый столбец равен u, умноженному на число.
  5. ‖Qx‖ = ‖x‖ (ортогональные преобразования сохраняют длины), Q⁻¹ = Qᵀ.

Блок 3.

  1. σ'(x) = σ(x)(1 − σ(x)).
  2. f'(x) = eˣ / (1 + eˣ) = σ(x). Полезно запомнить: производная softplus и есть сигмоида.
  3. ∇f = (A + Aᵀ)x; для симметричной A получается 2Ax.
  4. ∇L = 2(wᵀx − y)x.
  5. dL/dx = 2x·cos(x²).

Блок 4.

  1. 1 − (1/2)³ = 7/8.
  2. E = np, Var = np(1 − p): это сумма n независимых Бернулли.
  3. 0.5. Положительных больных: 0.01 · 0.99 = 0.0099; положительных здоровых: 0.99 · 0.01 = 0.0099. Ответ «99%» означает классическую ошибку: базовую частоту проигнорировали.
  4. 2. Дисперсии независимых величин складываются даже при вычитании: Var(−Y) = Var(Y).
  5. 1/p: это матожидание геометрического распределения.

Блок 5.

  1. Переобучение. Действия: регуляризация (weight decay, dropout), ранняя остановка, больше данных или аугментации, уменьшить модель. Засчитываются любые три.
  2. 99%. Модель бесполезна: она не находит ни одного положительного. Смотреть recall, precision, F1, PR-AUC; accuracy при сильном дисбалансе ничего не говорит.
  3. По validation выбирают гиперпараметры и модель, поэтому оценка на нём оптимистично смещена. Test трогают один раз, в самом конце.
  4. Бинарную кросс-энтропию, то есть отрицательное лог-правдоподобие Бернулли. С сигмоидой MSE даёт невыпуклую задачу и почти нулевой градиент при уверенной ошибке (сигмоида насыщена). Засчитывается любой из двух аргументов с объяснением.
  5. Например: нормализация по всему датасету до разбиения на train/test; дубликаты или почти-дубликаты между train и test; признак, вычисленный по информации из будущего; один пользователь или пациент одновременно в train и test.

Блок 6.

  1. fp32: 4 байта × 10⁹ = 4 ГБ; bf16: 2 ГБ.
  2. Даталоадер (CPU не успевает готовить батчи), синхронизации CPU–GPU, слишком маленький батч. И главное: не гадать, а запустить профилировщик.
  3. Зафиксированные сиды; версии кода (commit hash) и зависимостей; конфиг со всеми гиперпараметрами; версия данных. Засчитываются любые четыре разумных.
  4. Двоичный поиск по истории коммитов: находит коммит, внёсший регрессию, за log₂(n) проверок. Незаменим, когда «раньше работало» и между «работало» и «сломалось» сотни коммитов.
  5. .item() копирует значение на CPU и заставляет CPU ждать, пока GPU закончит все поставленные в очередь ядра. Асинхронность пропадает: GPU простаивает, пока CPU готовит следующий шаг.

Как считать результат

Итог (из 30)ДоляЧто делать
≥ 24≥ 80%Сразу в неделю 1 ядра
15–23.550–80%Выборочно: каждый блок, где набрано ≤ 3 из 5, открывает свои недели F по таблице выше
< 15< 50%Весь модуль, F1–F6 по порядку

Пограничный случай: общий балл ≥ 24, но один блок ≤ 2 из 5. Пройти только соответствующую неделю F: одиночный провал говорит о конкретной дыре, а не об общем уровне.


Неделя F1. Python и инженерная гигиена

Цель недели: чтобы язык и инструменты перестали отнимать внимание. Если ты уже пишешь на Python, неделя пройдёт быстро. Если нет, ничего сверхсложного здесь тоже нет: нужных вещей немного, и каждую можно проверить за минуту в консоли. В ядре каждое задание оформлено как пакет (папка с кодом, которую Python умеет устанавливать и импортировать) с тестами (маленькими программами, которые проверяют, что код даёт ожидаемый ответ). Если pip install -e . (команда установки пакета в режиме разработки) или падающий импорт стоят полчаса, до содержания дело не доходит.

Теория

На пальцах. a = [1, 2], затем b = a, затем b.append(3). Теперь и a равно [1, 2, 3]: список один, а имён у него два. Присваивание не копирует, а привязывает к объекту ещё одно имя. Ловушка из вопроса 1 устроена так же: acc=[] создаёт один список в момент выполнения def, и все вызовы без аргумента дописывают в него же.

  • Модель данных. Переменная всего лишь имя, привязанное к объекту; присваивание не копирует. Изменяемые (list, dict, тензоры) и неизменяемые (int, str, tuple) объекты. Отсюда видно, что обе ловушки диагностики (вопросы 1 и 4) на самом деле одна и та же ловушка

На пальцах. Файл на 50 ГБ, окно в 1 МБ. Список всех окон попытался бы держать в памяти все 50 000 окон сразу. Генератор с yield отдаёт одно окно, ждёт, пока его обработают, и только потом читает следующее. В памяти всегда одно окно, 1 МБ, при файле любого размера.

  • Итераторы и генераторы. yield превращает функцию в ленивый (вычисляющий по запросу) поток. Вывод: даталоадер (код, который подаёт данные в модель), читающий корпус в 50 ГБ батчами (порциями примеров), должен быть генератором, а не списком

На пальцах. Запись @timed над def f(...) означает ровно f = timed(f) сразу после определения: имя f теперь указывает на обёртку, а исходная функция живёт внутри неё. Блок with obj: вызывает obj.__enter__() на входе и obj.__exit__(...) на выходе, даже если внутри было исключение.

  • Декораторы и контекстные менеджеры. @torch.no_grad() и with torch.no_grad(): используют один и тот же объект двумя способами. Кто понял, как это возможно, тот понял оба механизма
  • Классы и dataclasses. Конфиг модели как @dataclass даёт типы, значения по умолчанию и repr бесплатно
  • Пакеты. pyproject.toml, виртуальное окружение (отдельный набор библиотек для одного проекта), pip install -e .. Почему editable-установка: тесты импортируют пакет так же, как пользователь, и правка кода видна без переустановки
  • Тесты. pytest, assert, фикстуры (подготовленные данные и объекты, которые pytest передаёт в тест), -k для выбора тестов. Тест работает как исполнимая спецификация: nanolm целиком построен так, что задание считается сделанным, когда проходят тесты эталона
  • Профилирование до оптимизации. cProfile и line_profiler. Интуиция о том, где тормозит код, ошибается чаще, чем кажется
  • Git. commit, branch, merge против rebase, разрешение конфликта, .gitignore для весов и данных (веса в git: самая частая гигиеническая ошибка ML-репозиториев)
  • Shell и удалённая работа. пайпы, grep, find, переменные окружения, ssh-ключи, tmux: сессия переживает обрыв соединения, и обучение не умирает вместе с ноутбуком

Практика. Создать пакет foundation/ с pyproject.toml; в нём генератор, читающий большой текстовый файл окнами фиксированной длины без загрузки целиком; декоратор timed; три функции с тестами. Намеренно медленную функцию найти через cProfile, ускорить и показать разницу числом. Всё под git, с осмысленными коммитами и одной веткой, влитой через rebase.

Результаты недели

  • Могу с нуля за 10 минут создать пакет с pyproject.toml, установить его в editable-режиме и запустить pytest.
  • Могу написать генератор, читающий файл любого размера окнами, и объяснить, почему память остаётся O(1).
  • Могу найти через cProfile самую дорогую функцию незнакомого скрипта и подтвердить ускорение замером.
  • Могу создать ветку, сделать rebase на свежий main и разрешить конфликт без потери изменений.

Самопроверка

  1. Почему изменяемое значение по умолчанию оказывается ловушкой, и как её обойти?
  2. Как torch.no_grad() может работать и как декоратор, и как with?
  3. Чем editable-установка отличается от обычной и зачем она в проекте с тестами?

Ресурсы: The Python Tutorial (docs.python.org); Luciano Ramalho, Fluent Python (2-е изд., 2022), главы про модель данных, итераторы и декораторы; Pro Git (Scott Chacon, Ben Straub); MIT The Missing Semester of Your CS Education.


Неделя F2. NumPy и формы тензоров

NumPy (библиотека для работы с массивами чисел) и тензоры (многомерные массивы: вектор, таблица, стопка таблиц) лежат в основе всего кода курса. Главное умение недели: предсказывать форму результата (размеры по каждой оси, например (32, 10)) до запуска кода. Половина ошибок в ML-коде происходит из-за молча сработавшего broadcasting (автоматического растягивания массивов до общей формы), который дал тензор правильной длины и неправильного смысла. Звучит грозно, но правил там всего два, и ниже они разобраны на числах.

Теория

На пальцах. Массив 3×4 из int64 лежит в памяти одной полосой из 12 чисел по 8 байт. Чтобы перейти к следующему столбцу, сдвигаемся на 8 байт, к следующей строке на 32: strides = (32, 8). .T не трогает полосу, а только меняет шаги местами: strides = (8, 32). Буфер тот же, правило другое. Поэтому транспонирование бесплатно.

  • Что такое массив. Буфер памяти + shape + strides + dtype. strides говорят, на сколько байт сдвинуться, чтобы перейти к следующему элементу по оси
  • View (новый взгляд на тот же буфер, без копирования) против копии. Транспонирование, срез, reshape непрерывного массива дают view: меняются только shape и strides. Вывод, который понадобится в неделе 1 ядра: транспонирование бесплатно, поэтому W можно хранить в любой ориентации
  • Непрерывность. После .T массив не непрерывен; некоторые операции требуют копии (np.ascontiguousarray, в PyTorch .contiguous(), и поэтому .view иногда падает, а .reshape нет)

На пальцах. a формы (3, 1), столбец [0, 10, 20]; b формы (1, 4), строка [1, 2, 3, 4]. Формы выравниваются справа: 1 и 4 → 4, 3 и 1 → 3. Результат формы (3, 4), таблица сумм каждого с каждым: [[1, 2, 3, 4], [11, 12, 13, 14], [21, 22, 23, 24]]. Та же механика даёт ловушку. x = [1, 2, 3] формы (3,) минус он же формы (3, 1) хотели дать три нуля, а дают таблицу 3×3 всех попарных разностей.

  • Broadcasting (автоматическое растягивание осей размера 1). Формы выравниваются справа, ось размера 1 растягивается, остальное даёт ошибку. Главная ловушка: (B,) минус (B, 1) даёт (B, B) без единого предупреждения. Противоядие: assert x.shape == (...) после каждой нетривиальной операции

На пальцах. x формы (2, 3), x.max(axis=1) имеет форму (2,): по максимуму на строку. x − m выравнивает (2, 3) и (2,) справа: 3 против 2, ошибка. Хуже, если x квадратная, (3, 3): ошибки нет, но из элемента [i, j] вычитается максимум строки j, а не i. С keepdims=True у m форма (2, 1), и каждая строка вычитает свой максимум.

  • Редукции по оси и keepdims. x - x.max(axis=1, keepdims=True): первый шаг стабильного softmax (функции, превращающей набор чисел в вероятности); без keepdims строки вычитаются не из тех строк
  • Индексация. Базовая (срезы) возвращает view, «причудливая» (массивы индексов, маски) возвращает копию. x[np.arange(B), targets] выбирает логит (сырой выход модели до softmax) правильного класса в каждой строке, основа кросс-энтропии
  • Векторизация. Цикл Python тратит работу интерпретатора на каждый элемент, векторная операция делает один вызов цикла на C. Разница в 50–200 раз не оптимизация, а условие, при котором эксперименты вообще успевают закончиться
  • einsum как язык форм. 'bij,bjk->bik' записывает батчевый матмул; 'bhqd,bhkd->bhqk' записывает оценки внимания. Если можешь записать операцию в einsum, ты точно знаешь её формы

Практика. Без единого цикла по данным: матрица попарных расстояний через ‖a‖² + ‖b‖² − 2aᵀb; one-hot через индексацию; стабильный softmax по произвольной оси; батчевый матмул через einsum с проверкой против @. Для попарных расстояний сделать замер: цикл против векторизации на 2000×2000, записать отношение времён. У каждой строки кода комментарий с формой результата.

Результаты недели

  • Могу предсказать форму результата любой цепочки broadcasting-операций и заранее сказать, где будет ошибка.
  • Могу реализовать матрицу попарных расстояний без циклов и объяснить, откуда в ней −2aᵀb.
  • Могу объяснить через strides, почему .T не копирует данные, а причудливая индексация копирует.
  • Могу переписать любую операцию из attention в einsum и обратно.

Самопроверка

  1. Что получится из (B,) − (B, 1) и как такую ошибку поймать автоматически?
  2. Зачем keepdims=True в стабильном softmax?
  3. Почему векторизованный код быстрее цикла в десятки раз, если считает то же самое?

Ресурсы: документация NumPy, разделы NumPy: the absolute basics for beginners и раздел Broadcasting; документация numpy.einsum.


Неделя F3. PyTorch и autograd на практике

PyTorch (главная библиотека для обучения нейросетей) умеет считать производные любой программы сам. Этот механизм называется autograd (автоматическое дифференцирование): ты пишешь forward, то есть вычисление ответа модели, а градиенты (производные лосса, числа-меры ошибки, по каждому параметру) он строит за тебя. Неделя 2 ядра попросит написать собственный autograd. Здесь задача обратная: научиться пользоваться готовым так, чтобы понимать, что он делает, и уметь поймать его на ошибке.

На пальцах. x = torch.tensor(2.0, requires_grad=True), y = x * x, y.backward(). Теперь x.grad равен 4: PyTorch запомнил, что y получен возведением x в квадрат, и применил правило (x²)' = 2x в точке 2. Никаких формул руками, только цепочка операций, которую он записал по дороге.

Теория

  • Тензор = массив NumPy + устройство + граф. device, dtype, перенос между CPU и GPU и цена переноса
  • Динамический граф. Граф строится во время forward, каждой операцией заново. Поэтому в модели можно писать обычные if и циклы

На пальцах. w = 1.0, лосс L = 3·w. После первого backward() w.grad = 3. После второго без zero_grad() w.grad = 6: новый градиент прибавился к старому. Если это случайность, на втором шаге оптимизатор видит сумму двух градиентов, на третьем трёх, и шаги растут. Если намеренно: это накопление: 4 микробатча по 8 примеров, backward() после каждого (лосс поделён на 4), один step(). Это то же, что один батч из 32.

  • .backward() накапливает градиенты в .grad, а не перезаписывает. Отсюда обязательный zero_grad(), и отсюда же бесплатное накопление градиента по нескольким микробатчам, которое понадобится в неделе 10
  • no_grad, inference_mode, detach. Три способа сказать «здесь граф не нужен», с разной строгостью
  • nn.Module. parameters(), state_dict, регистрация подмодулей, train() и eval(), и почему забытый eval() меняет результат (dropout, batchnorm)
  • Dataset и DataLoader. num_workers, pin_memory, shuffle: где данные готовятся параллельно с обучением
  • Канонический цикл обучения. Пять строк, которые пишутся на автомате: forward → loss → zero_grad → backward → step

На пальцах. Батч из 8 примеров и модель с тысячами параметров: она обязана выучить эти 8 наизусть, лосс падает почти до нуля за сотню шагов. Если лосс в задаче на 8 классов застрял около ln 8 ≈ 2.08, модель предсказывает все классы поровну, то есть не учится вовсе. Ищи ошибку в коде (забытый step, перепутанные метки, отсоединённый граф), а не подбирай learning rate.

  • Отладка. Первый тест любой модели: переобучить один батч до лосса около нуля. Если не получается, ошибка в коде, а не в гиперпараметрах. Второй тест: проверка градиента через torch.autograd.gradcheck в float64
  • Синхронизация. CUDA асинхронна (CPU ставит ядра в очередь и идёт дальше, не дожидаясь GPU); .item(), .cpu(), print(tensor) заставляют CPU ждать GPU (вопрос 30 диагностики)

Практика. Логистическая регрессия двумя способами: градиент выписан руками и градиент от autograd, совпадение до 1e-6. Затем MLP на MNIST (или любом небольшом наборе) с Dataset, DataLoader, циклом обучения, валидацией, сохранением и загрузкой state_dict. Отдельно показать, что модель переобучается на одном батче, и что забытый zero_grad() ломает обучение (записать, как именно).

Результаты недели

  • Могу написать цикл обучения с валидацией и чекпоинтом с пустого файла за 15 минут.
  • Могу сверить ручной градиент с autograd и объяснить расхождение, если оно есть.
  • Могу объяснить, почему .backward() накапливает градиенты, и использовать это для накопления по микробатчам.
  • Могу диагностировать неработающую модель тестом переобучения на одном батче.

Самопроверка

  1. Что произойдёт, если забыть optimizer.zero_grad()? Опиши поведение лосса.
  2. Чем отличаются detach(), torch.no_grad() и torch.inference_mode()?
  3. Почему проверку градиента делают в float64?

Ресурсы: официальные PyTorch Tutorials, серия Learn the Basics и A Gentle Introduction to torch.autograd; Andrej Karpathy, видео The spelled-out intro to neural networks and backpropagation: building micrograd.


Неделя F4. Линейная алгебра

Линейная алгебра звучит страшнее, чем она есть. Это не полный курс, а ровно то, что понадобится в ядре: умножение матриц с четырёх сторон, ранг (сколько независимых направлений в матрице), ортогональность (когда преобразование сохраняет длины и углы), спектральное разложение и SVD (два способа разложить матрицу на простые множители; каждый ниже показан на числах). Каждое утверждение недели проверяется в NumPy в две строки. Так и надо учить: не поверил формуле, написал две строки, увидел числа.

Теория

  • Матрица задаёт линейное отображение, произведение задаёт композицию. Отсюда некоммутативность и то, почему W₁W₂x = Wx (неделя 1 ядра)

На пальцах. A = [[1, 2], [3, 4]], B = [[5, 6], [7, 8]], AB = [[19, 22], [43, 50]]. Строка на столбец: 19 = 1·5 + 2·7. Сумма внешних произведений: первый столбец A на первую строку B даёт [[5, 6], [15, 18]], второй столбец на вторую строку даёт [[14, 16], [28, 32]]; сумма снова [[19, 22], [43, 50]]. В ∂L/∂W = XᵀG столбцы Xᵀ соответствуют примерам батча, и каждое слагаемое даёт вклад одного примера.

  • Четыре взгляда на AB: скалярные произведения строк на столбцы; столбцы AB как комбинации столбцов A; строки как комбинации строк B; **сумма внешних произведений** столбцов A на строки B. Последний взгляд объясняет, почему градиент по весам Xᵀ G складывается из вкладов отдельных примеров батча
  • Ранг, образ, ядро. Ранг равен числу независимых направлений. uvᵀ имеет ранг 1, BA при B ∈ ℝ^{d×r} имеет ранг не больше r. Это вся идея LoRA (неделя 15)

На пальцах. Поворот на 90°: Q = [[0, −1], [1, 0]]. Q·(3, 4) = (−4, 3): длина была 5 и осталась 5. Обратный поворот задаёт Qᵀ = [[0, 1], [−1, 0]], и это же Q⁻¹: у ортогональной матрицы обращение бесплатно.

  • Ортогональность и проекции. Проекция на подпространство, ортогональные матрицы сохраняют длины и углы. Поворот в RoPE (неделя 7) задаётся ортогональной матрицей, поэтому он не меняет норму q и k
  • Собственные векторы симметричных матриц. Спектральная теорема: A = QΛQᵀ с ортогональной Q. Положительная полуопределённость
  • SVD: A = UΣVᵀ для любой матрицы. Лучшее приближение ранга k по норме Фробениуса получается, если оставить k старших сингулярных чисел; ошибка равна корню из суммы квадратов отброшенных. На этом стоят PCA (F6) и низкоранговые методы

На пальцах. f(x) = ½(x₁² + 100·x₂²), Гессиан diag(1, 100), число обусловленности κ = 100 / 1 = 100. Градиентный спуск умножает каждую координату на (1 − η·λ): x₁ на 1 − η, x₂ на 1 − 100η. При η = 0.019 множитель для x₂ равен −0.9, то есть сходится. Но x₁ уменьшается всего в 0.981 раза за шаг, и до 10⁻⁶ нужно около 720 шагов. При η = 0.021 множитель для x₂ равен −1.1: x₂ растёт на 10% за шаг и за 10 шагов увеличивается в 2.6 раза. Предел шага задаёт крутое направление, а скорость задаёт пологое.

  • Нормы и обусловленность. Фробениусова и спектральная нормы; число обусловленности (κ = λ_max / λ_min для симметричной положительно определённой матрицы) показывает, насколько матрица растягивает ошибки. Связь с обучением: для квадратичной функции градиентный спуск сходится только при η < 2/λ_max, где λ_max есть старшее собственное значение Гессиана

Практика. Проверить численно четыре взгляда на произведение матриц. Сжать любую матрицу (например, изображение в оттенках серого) через SVD для k = 1…50 и построить ошибку от k, сверить с суммой отброшенных σ². Реализовать степенной метод для старшего собственного вектора и сравнить с np.linalg.eigh. Запустить градиентный спуск на квадратичной функции с η чуть меньше и чуть больше 2/λ_max и увидеть сходимость и расходимость.

Дополнительно: обусловленность на числах. Для квадратичной цели градиентный спуск с оптимальным шагом η = 2/(λ_min + λ_max) сходится со скоростью (κ − 1)/(κ + 1) за шаг. При κ = 100 до точности 10⁻⁶ нужно порядка 700 шагов, при κ = 10 около 70. Отсюда два практических приёма: нормализация признаков работает как предобуславливание (сжимает спектр), а ridge-регуляризация сдвигает все собственные значения на λ и уменьшает κ. В практику: сравнить κ = 1 и κ = 10 и сверить число шагов с предсказанием. Это задача тренажёра gd_steps_to_tol.

Грам–Шмидт, QR и почему МНК не решают через (AᵀA)⁻¹. Достаточно одной строки: κ(AᵀA) = κ(A)², поэтому нормальные уравнения удваивают потерю точности, а QR или SVD работают с самой A. По желанию: МНК тремя способами на плохо обусловленной матрице и сравнение ошибок.

Результаты недели

  • Могу объяснить произведение матриц всеми четырьмя способами и показать, какой из них даёт ∂L/∂W = XᵀG.
  • Могу реализовать приближение низкого ранга через SVD и предсказать его ошибку до запуска.
  • Могу реализовать степенной метод и объяснить, от чего зависит скорость его сходимости.
  • Могу вывести границу η < 2/λ_max для квадратичной функции и подтвердить её экспериментом.

Самопроверка

  1. Почему ранг BA не больше r и что это значит для LoRA?
  2. Как связаны SVD матрицы данных и собственные векторы её ковариационной матрицы?
  3. Почему слишком большой learning rate приводит к расходимости? Объясни через собственные значения.

Ресурсы: Li, Mathematical Pathways to Machine Learning (2026), гл. 11, с. 71–75, упражнения 11.1–11.6 (обусловленность и скорость сходимости); 4.2–4.2.1 и 9.3.2 (QR и МНК) (лицензия CC BY-NC: только ссылка и номера задач); Gilbert Strang, Introduction to Linear Algebra; курс MIT 18.06 (Strang); 3Blue1Brown, Essence of linear algebra; Deisenroth, Faisal, Ong, Mathematics for Machine Learning (2020), главы 2–4.


Неделя F5. Матанализ и вероятность

Две половины одной недели. Производные (скорость изменения функции) нужны для обучения: модель учится, сдвигая параметры туда, где лосс убывает быстрее всего. Вероятность нужна для лосса и для оценки: модель предсказывает не «правильный токен», а распределение вероятностей по всем токенам, и качество такого предсказания надо уметь измерить. Если школьный матанализ подзабыт: это не беда: всё нужное ниже выводится заново на маленьких числах. Трек D ядра дальше углубляет вероятность на задачах; здесь даётся язык, без которого эти задачи не прочитать.

Матанализ

На пальцах. f(x) = x² в точке 3. Шагнём на 0.01: (3.01² − 9) / 0.01 = 6.01. На 0.001: 6.001. Наклон стремится к 6 = 2·3. Лишняя часть равна самому шагу: ((x + h)² − x²)/h = 2x + h. Центральная разность (3.01² − 2.99²)/0.02 даёт ровно 6: ошибки первого порядка сокращаются. Отсюда f(3.01) ≈ 9 + 6·0.01 = 9.06, а точно 9.0601: производная и есть лучшее линейное приближение.

  • Производная как лучшее линейное приближение. f(x + h) ≈ f(x) + f'(x)h. В многомерном случае роль f' играет градиент или Якобиан
  • Почему градиент указывает направление наискорейшего роста. Изменение ∇fᵀh при ‖h‖ = 1 максимально, когда h сонаправлен с ∇f (неравенство Коши–Буняковского). Это одна строка, и в ней вся мотивация градиентного спуска
  • Цепное правило для скаляров, затем для векторов: произведение Якобианов
  • Разложение Тейлора второго порядка f(x+h) ≈ f + ∇fᵀh + ½hᵀHh: откуда берётся ограничение на шаг и почему кривизна (Гессиан) важна
  • Выпуклость. У выпуклой функции любой локальный минимум глобальный. Логистическая регрессия выпукла, нейросеть нет
  • Экспонента и логарифм. log(ab) = log a + log b, производная log, почему лосс считают в логарифмах (произведение вероятностей по токенам → сумма)

На пальцах. 9 часов в неделю ты делишь между чтением x и кодом y, польза равна 2 ln x + ln y. На оптимуме последний час приносит одинаковую пользу в обоих делах: 2/x = 1/y, то есть x = 2y. Вместе с x + y = 9 выходит x = 6, y = 3. Эта общая польза последнего часа λ = 2/6 = 1/3 и есть множитель Лагранжа. Дай 10 часов вместо 9: оптимум x = 20/3, y = 10/3, польза выросла на 0.316, почти ровно на λ ≈ 0.333.

  • Оптимизация с ограничением. Максимизировать f при условии g = c: на оптимуме ∇f = λ∇g, градиент цели параллелен градиенту ограничения (иначе можно сдвинуться вдоль ограничения и выиграть). Множитель λ работает как теневая цена (сколько стоит лишняя единица ресурса): на столько вырастет лучший результат, если ослабить ограничение на единицу. Встретится в треке D: D11 (вероятности в сумме дают 1) и D20 (бюджет вычислений)

Вероятность

  • Случайная величина, PMF (вероятность каждого значения дискретной величины), PDF (плотность непрерывной), CDF (P(X ≤ x)). Матожидание и дисперсия
  • Линейность матожидания работает без независимости. Самый полезный факт в задачах на подсчёт

На пальцах. 10 000 человек, болеет 1%, то есть 100. Тест находит 99 из них. Из 9 900 здоровых ошибочно положителен 1%, то есть 99 человек. Положительных всего 198, больных среди них 99: половина, а не 99%. Если бы болело 10%, из 1 080 положительных больных было бы 990, то есть 92%. Точность теста та же, изменилась базовая частота (доля больных до теста).

  • Условная вероятность, формула Байеса и базовая частота (вопрос 18 диагностики)
  • Распределения: Бернулли, биномиальное, категориальное, нормальное. Категориальное распределение и есть ровно выход softmax
  • ЗБЧ и ЦПТ на уровне следствий. Стандартная ошибка среднего равна σ/√n. Вывод: accuracy на 100 примерах имеет шум порядка ±5 п.п., и разница в 2 п.п. между моделями на таком наборе не считается результатом (неделя 18 ядра)

На пальцах. 10 бросков, 7 орлов. Вероятность именно такой последовательности при вероятности орла p равна p⁷(1 − p)³. При p = 0.5 это 0.00098, при p = 0.7 уже 0.00222, при p = 0.8 снова меньше, 0.00168. Максимум достигается при p = 0.7 = 7/10. Это и есть MLE (оценка максимального правдоподобия): выбрать параметр, при котором наблюдённое наиболее вероятно.

  • MLE. Для монеты: максимизировать pᵏ(1−p)^{n−k} → p̂ = k/n. Минимизация кросс-энтропии в обучении LM и есть MLE, записанный с другой стороны

На пальцах. Фраза «я пью чай». Пусть текст начинается со слова «я» с вероятностью 0.1. После «я» слово «пью» идёт с вероятностью 0.2, а после «я пью» слово «чай» с вероятностью 0.3. Вероятность всей фразы: 0.1 · 0.2 · 0.3 = 0.006.

  • Цепное правило вероятности. p(a, b, c) = p(a) · p(b | a) · p(c | a, b): совместную вероятность (что всё случилось вместе) можно собрать из условных, добавляя по одному элементу. Это тождество, оно верно для любого распределения без всяких допущений. Языковая модель работает ровно так: на каждом шаге она выдаёт p(следующее слово | всё, что было раньше), а вероятность текста равна произведению. Чтобы не тонуть в крошечных числах, складывают логарифмы: ln 0.1 + ln 0.2 + ln 0.3 ≈ −5.12, ровно ln 0.006. Лосс LM равен этой сумме со знаком минус (неделя 4)

На пальцах. X гауссиана со средним 1 и дисперсией 4 (стандартное отклонение 2). Возьмём Y = 3X + 2. Среднее ведёт себя как обычное число: 3·1 + 2 = 5. Разброс растягивается втрое: отклонение 6, дисперсия 9·4 = 36. Сдвиг на 2 разброс не меняет. И Y снова гауссиана, колокол просто переехал и расширился.

  • Аффинное преобразование гауссианы (аффинное значит «умножить и прибавить»). Если X ~ N(μ, σ²), то aX + b ~ N(aμ + b, a²σ²). Сумма двух независимых гауссиан тоже гауссиана, и дисперсии складываются. Отсюда масштаб 1/√d во внимании (неделя 7): сумма d независимых слагаемых имеет дисперсию в d раз больше

На пальцах. Резинка с нарисованными точками: растяни её вдвое, точек столько же, а на сантиметр их вдвое меньше. С плотностью так же. X ~ N(0, 1), в нуле плотность ≈ 0.399. У Y = 2X те же вероятности размазаны по вдвое более длинным отрезкам, поэтому плотность в нуле 0.399 / 2 ≈ 0.199. Сверка с правилом выше: Y ~ N(0, 4), её плотность в нуле 1/(2·√(2π)) ≈ 0.199. Совпало.

  • Замена переменных для плотности. Если Y = g(X) и g монотонна, то p_Y(y) = p_X(x) · |dx/dy|, где x = g⁻¹(y). Множитель |dx/dy| поправляет на растяжение. В многомерном случае его место занимает модуль определителя Якобиана (неделя F4). Вероятности при замене сохраняются, плотности нет

На пальцах. В кармане две монеты: честная (орёл с вероятностью 0.5) и кривая (орёл 0.9). Достаём одну наугад, 50 на 50, бросаем и показываем только результат. Какая монета в руке, не видно: это латентная (скрытая) переменная. Вероятность орла 0.5·0.5 + 0.5·0.9 = 0.7. Выпал орёл: по Байесу монета кривая с вероятностью 0.45 / 0.7 ≈ 0.64.

  • Латентная переменная и маргинализация. p(x) = Σ_z p(z) · p(x | z): чтобы найти вероятность того, что видно, суммируют по всем значениям скрытого z (маргинализация, «просуммировать скрытое»). Если вместо двух монет взять две гауссианы, получится смесь гауссиан: сначала монетка выбирает гауссиану, потом из неё берут число. Так устроены многие генеративные модели картинок: у изображения есть скрытый код, из которого оно рождается (неделя 20)

Практика. Численная проверка производных из диагностики конечными разностями. Монте-Карло: смоделировать задачу про тест на болезнь и сверить с формулой Байеса; смоделировать среднее n бросков для n = 10, 100, 1000 и увидеть 1/√n на графике разброса. Вывести MLE для среднего и дисперсии нормального распределения.

Критические точки по Гессиану. Знаки собственных значений Гессиана в точке нулевого градиента определяют, что перед тобой: все положительные означают минимум, все отрицательные максимум, разные знаки седло. В глубоких сетях сёдел несравнимо больше, чем локальных минимумов, и это одна из причин, почему «застрять в локальном минимуме» не главная проблема обучения.

Результаты недели

  • Могу вывести градиенты xᵀAx, (wᵀx − y)² и логистического лосса без подсказок.
  • Могу объяснить за 2 минуты, почему антиградиент указывает направление наискорейшего убывания.
  • Могу решить задачу на формулу Байеса и проверить ответ симуляцией.
  • Могу оценить шум accuracy на тестовом наборе размера n и сказать, значима ли разница.

Самопроверка

  1. Почему линейность матожидания не требует независимости, а сложение дисперсий требует?
  2. Выведи MLE для вероятности орла у монеты.
  3. Что говорит разложение Тейлора второго порядка о выборе шага градиентного спуска?

Ресурсы: Li, Mathematical Pathways to ML (2026), гл. 7 (особенно 7.4 и пример 7.7, backprop маленькой сети на числах) и гл. 8, упражнения 7.1–7.5: облегчённая альтернатива MML, если диагностика дала < 50% (CC BY-NC: только ссылка); Deisenroth, Faisal, Ong, Mathematics for Machine Learning (2020), главы 5–6; 3Blue1Brown, Essence of calculus; Joseph Blitzstein, Jessica Hwang, Introduction to Probability (2-е изд., 2019) и курс Harvard Stat 110.


Неделя F6. Классический ML, минимально

Классический ML (машинное обучение до нейросетей: простые модели, которые целиком помещаются на одном листе) здесь сведён ровно к пяти вещам: логистическая регрессия (предсказание «да/нет» по взвешенной сумме признаков), k-means (разбиение точек на k кучек), PCA (поиск направлений, вдоль которых данные разбросаны сильнее всего), дерево решений (цепочка вопросов «признак больше порога?») и метрики классификации (числа, по которым судят, хороша ли модель). Всё реализуется на NumPy без sklearn (библиотеки с готовыми реализациями); sklearn разрешён только для сверки ответа. Если ты никогда не писал ни одного алгоритма сам: это лучшая неделя, чтобы начать: каждый из них занимает 20–40 строк.

Почему только это: на ML coding интервью на LLM-роли классический алгоритм просят реализовать редко, и почти всегда это один из четырёх алгоритмов выше. Они короткие и проверяют владение формами и оптимизацией. SVM, бустинг, наивный Байес здесь намеренно не реализуются: их цена в часах несоразмерна вероятности вопроса. Для секции общего ML бустинг достаточно уметь объяснить устно: краткое повторение в конце недели.

Теория

  • Логистическая регрессия. p = σ(wᵀx + b), лосс: бинарная кросс-энтропия. Градиент по весам Xᵀ(p − y)/n имеет ту же форму «предсказание минус цель», что у softmax + CE в неделе 2 ядра. Задача выпукла, значит, минимум один. L2-регуляризация не даёт весам уйти в бесконечность на линейно разделимых данных

На пальцах. Точки на прямой: 1, 2, 10, 11; начальные центры 1 и 2. Назначаем: точка 1 идёт к центру 1, точки 2, 10, 11 к центру 2. Пересчитываем центры: 1 и (2 + 10 + 11)/3 ≈ 7.67. Снова назначаем: точка 2 теперь ближе к центру 1 (расстояние 1 против 5.67). Новые центры 1.5 и 10.5, дальше ничего не меняется. Сумма квадратов расстояний на каждом шаге только падала: 145 → 48.7 → 17.6 → 1.

  • k-means (алгоритм Ллойда). Чередовать: назначить каждую точку ближайшему центру; пересчитать центры как средние. Каждый шаг не увеличивает сумму квадратов расстояний → алгоритм сходится, но к локальному минимуму. Отсюда k-means++: следующий центр выбирается с вероятностью, пропорциональной квадрату расстояния до ближайшего уже выбранного
  • PCA. Центрировать данные; главные компоненты совпадают с правыми сингулярными векторами X (они же собственные векторы ковариации, неделя F4). Доля объяснённой дисперсии равна σᵢ² / Σσⱼ². Без центрирования первая компонента указывает на среднее, а не на направление разброса

На пальцах. Четыре уже центрированные точки: (2, 2), (−2, −2), (1, −1), (−1, 1). Сумма квадратов их длин 20. Спроецируем на направление (1, 1)/√2: проекции 2√2, −2√2, 0, 0, сумма их квадратов 16 (сохранённый разброс), а на ошибку реконструкции (квадрат расстояния от точки до её проекции) остаётся 4. На направление (1, 0): сохранено 10, ошибка 10. Всегда сохранено + ошибка = 20: это теорема Пифагора для каждой точки. Сохранить больше и ошибиться меньше оказывается одной задачей, и в обоих смыслах побеждает (1, 1)/√2.

  • PCA: две задачи с одним ответом. Главные компоненты одновременно максимизируют дисперсию проекции и минимизируют ошибку реконструкции, потому что сумма этих двух величин постоянна. Линейный автоэнкодер (кодировщик h = W_e x сжимает d чисел в k < d, декодер x̂ = W_d h разжимает обратно, обучение по MSE) находит то же подпространство, что первые k компонент PCA, но базис в нём может быть любым, не обязательно ортонормированным. Нелинейный автоэнкодер обобщает эту идею
  • k-means как мостик к EM. Шаг Ллойда состоит из двух ходов: угадать скрытое (номер кластера каждой точки) и обновить параметры (центры) при угаданном скрытом. EM (expectation–maximization, алгоритм для моделей с латентными переменными из F5) делает то же самое мягко: вместо «точка в кластере 2» он говорит «кластер 2 с вероятностью 0.7, кластер 1 с вероятностью 0.3»
  • Дерево решений. Жадно выбирать признак и порог, максимально уменьшающие нечистоту (Gini или энтропию). Жадность нужна потому, что оптимальное дерево искать слишком дорого. Глубина остаётся главным регулятором переобучения. Нормализация признаков не нужна: пороги инвариантны к монотонным преобразованиям

На пальцах. Положительных примеров 10. Модель назвала положительным только один, и он действительно положительный: precision 1.0, recall 0.1. Среднее арифметическое 0.55 выглядит прилично. F1 = 2·1.0·0.1 / (1.0 + 0.1) ≈ 0.18, близко к худшей из двух метрик, и провал recall не спрятать.

  • Метрики. Матрица ошибок (таблица «предсказано × на самом деле»); precision (доля верных среди предсказанных положительных), recall (доля найденных среди всех положительных), F1. F1 берётся как гармоническое среднее, потому что оно близко к меньшему из двух и не даёт скрыть провал одной метрики. Все три зависят от порога

На пальцах. Положительные примеры получили скоры 0.9 и 0.4, отрицательные 0.6 и 0.2. Пар «положительный–отрицательный» четыре: 0.9 > 0.6, 0.9 > 0.2, 0.4 > 0.2 упорядочены верно, 0.4 < 0.6 нет. AUC = 3/4 = 0.75. Порог для этого не понадобился, и если все скоры возвести в квадрат, порядок и AUC не изменятся.

  • ROC-AUC. ROC показывает зависимость TPR (recall) от FPR (доли отрицательных, ошибочно названных положительными) при движении порога. **AUC равен вероятности того, что случайный положительный пример получит скор выше случайного отрицательного.** Из этой интерпретации сразу следует способ считать AUC через ранги и то, что AUC не зависит от порога и от монотонного преобразования скоров

Практика. Реализовать на NumPy: логистическую регрессию с градиентным спуском; k-means с k-means++; PCA через SVD; дерево решений глубины ≤ 4 с Gini; функции precision, recall, F1 и ROC-AUC двумя способами (через ранги и трапециями по кривой), совпадение до 1e-9. Каждую реализацию сверить с sklearn на одних данных.

Если в процессе есть секция общего ML. В Яндексе, Сбере и Т-Банке общему ML отводят отдельный час, и спрашивают там шире, чем пять алгоритмов выше. Хватает повторить четыре темы на уровне «объяснить за минуту»; 10 вопросов для самопроверки лежат в разделе rapid-fire банка вопросов, подраздел «Классический ML».

На пальцах. Модель выдала вероятность 0.9 десяти письмам, и спамом из них оказались 6. Для этой группы модель обещала 90%, а попала в 60%: она переуверена. Калибровку (совпадение предсказанной вероятности с наблюдаемой частотой) проверяют так: разбивают предсказания на корзины по вероятности и в каждой сравнивают среднюю вероятность с долей положительных.

  • Калибровка. Логистическая регрессия обучается на log loss и обычно откалибрована; бустинг и долго обученные сети часто переуверены. Лечат монотонным пересчётом скоров на отложенной выборке: Platt scaling (логистическая регрессия поверх скора) или изотоническая регрессия. AUC от этого не меняется, а log loss и Brier score (средний квадрат разницы между вероятностью и исходом) улучшаются. Для нейросетей и LLM то же делает temperature scaling (деление логитов на одно подобранное число)
  • Деревья и ансамбли. Бэггинг и случайный лес усредняют глубокие деревья, обученные на бутстреп-выборках, и снижают дисперсию. Градиентный бустинг строит неглубокие деревья по очереди, каждое по антиградиенту лосса текущего ансамбля, и снижает смещение. Его главные регуляторы: темп обучения, глубина и число деревьев с ранней остановкой
  • Разбиение и утечки. Данные со временем делят по времени, данные с повторами одного пользователя делят по пользователям, иначе тест измеряет запоминание. Утечку (признак, который в момент предсказания ещё неизвестен) выдают подозрительно высокое качество и один признак с огромной важностью
  • Метрики ранжирования. Классификация оценивает каждый объект отдельно, ранжирование оценивает порядок выдачи. NDCG@k (сумма релевантностей с весом, падающим по позиции, делённая на лучшую возможную сумму) и MAP (средняя точность на позициях релевантных документов) наказывают за нужный документ на 10-м месте сильнее, чем на 2-м. В RAG ту же роль играют recall@k и MRR (неделя 21 ядра)

Результаты недели

  • Могу реализовать логистическую регрессию, k-means, PCA и дерево глубины ≤ 4 с пустого файла, каждый за 20 минут.
  • Могу вывести градиент логистического лосса и показать, что он совпадает по форме с градиентом softmax + CE.
  • Могу посчитать ROC-AUC через ранги и объяснить, почему это то же самое, что площадь под ROC-кривой.
  • Могу выбрать метрику под задачу с дисбалансом классов и обосновать выбор за минуту.
  • Могу за минуту объяснить калибровку, разницу бэггинга и бустинга, разбиение по времени и NDCG.

Самопроверка

  1. Почему k-means сходится и почему не обязательно к глобальному минимуму?
  2. Что будет с PCA, если забыть центрировать данные?
  3. Что означает ROC-AUC = 0.5 и ROC-AUC = 0.3 и что делать во втором случае?

Ресурсы: James, Witten, Hastie, Tibshirani, *An Introduction to Statistical Learning* (2-е изд., 2021; издание с Python 2023), главы про классификацию, деревья и обучение без учителя; Christopher Bishop, *Pattern Recognition and Machine Learning* (2006), для тех, кто хочет вывода глубже; scikit-learn User Guide, раздел Model evaluation, только для сверки определений метрик.


Выходной контроль

Модуль пройден, когда выполнено всё из списка, без AI и без подглядывания:

  1. Повторить диагностику и набрать ≥ 24 из 30, отвечая с выводом, а не по памяти ключей.
  2. За 45 минут с пустого файла: логистическая регрессия на NumPy с ручным градиентом, проверкой градиента конечными разностями и функцией ROC-AUC.
  3. За 5 минут вслух: формы всех тензоров в forward и backward одного линейного слоя с батчем.

Если пункт не прошёл, вернуться к соответствующей неделе F, а не идти в ядро с дырой: неделя 1 ядра начинается ровно с того места, где заканчивается этот модуль.

В приложении у недели есть навыки для самооценки, вопросы с проверкой ответа, задачи с кодом на Python и тьютор по материалам курса.

Учиться в приложении: тьютор, задачи с кодом
← НазадПрограмма курса Дальше →Неделя 1. Нейросети и градиенты

С нуля
С нуля: курс по LLM

  • Главная
  • Программа курса
  • Приложение
  • Конфиденциальность
  • Условия

Текст курса распространяется по лицензии CC BY-NC-SA 4.0, код nanolm по лицензии Apache-2.0.