Модуль 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 ядра
| Неделя | Тема | Закрывает блок диагностики |
|---|---|---|
| F1 | Python и инженерная гигиена | 1 (Python), 6 (системное мышление) |
| F2 | NumPy и формы тензоров | 2 (формы и NumPy) |
| F3 | PyTorch и 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
- Что напечатает код и почему?
def add(x, acc=[]): acc.append(x) return acc add(1) print(add(2)) - Что вернёт
sum(x * x for x in range(4))? Сколько памяти займёт то же выражение сrange(10**9)? Изменится ли ответ, если заменить круглые скобки на квадратные? - Напишите декоратор
timed, который печатает время выполнения любой функции и возвращает её результат без изменений. - Что напечатает код и почему?
a = [[0] * 3] * 3 a[0][0] = 1 print(a) - Скрипт работает 40 минут. Как узнать, какая именно функция тратит время? Назовите инструмент и команду.
Блок 2. Формы тензоров, NumPy, линейная алгебра
A.shape == (32, 10),b.shape == (10,). Какая форма уA + b? Что произойдёт приA + b[:, None]?X.shape == (B, D),W.shape == (D, F). Какая форма уX @ Wи сколько операций умножения-сложения выполняется?x = np.arange(12).reshape(3, 4),y = x.T. Копирует ли.Tданные? Чему станет равноy[0, 0]послеx[0, 0] = 100?- Каков ранг матрицы
u vᵀдля ненулевых векторовu ∈ ℝⁿ,v ∈ ℝᵐ? - Матрица
Qортогональна. Чему равны‖Qx‖иQ⁻¹?
Блок 3. Производные
- Найдите производную
σ(x) = 1 / (1 + e^{−x})и выразите её черезσ(x). - Найдите производную
f(x) = log(1 + eˣ). - Найдите градиент
f(x) = xᵀAxпо векторуx. Как он упрощается для симметричнойA? - Найдите градиент
L(w) = (wᵀx − y)²поw. L = sin(u),u = x². НайдитеdL/dx.
Блок 4. Вероятность
- Честную монету бросают 3 раза. Какова вероятность хотя бы одного орла?
- Чему равны матожидание и дисперсия
Binomial(n, p)? - Болезнь у 1% людей. Тест даёт положительный результат у 99% больных и у 1% здоровых. Человек получил положительный результат. Какова вероятность, что он болен?
XиYнезависимы,Var(X) = Var(Y) = 1. Чему равнаVar(X − Y)?- Монета выпадает орлом с вероятностью
p. Сколько бросков в среднем нужно до первого орла включительно?
Блок 5. Базовый ML
- Лосс на обучении падает, на валидации растёт. Что происходит и какие три действия можно предпринять?
- Положительный класс составляет 1% данных. Модель всегда предсказывает «отрицательный». Какая у неё accuracy? Почему это плохо и какую метрику смотреть вместо неё?
- Зачем нужен отдельный test-набор, если уже есть validation?
- Какую функцию потерь минимизирует логистическая регрессия и почему не MSE?
- Приведите два конкретных примера утечки данных (data leakage).
Блок 6. Системное мышление
- Модель на 1 млрд параметров. Сколько памяти занимают её веса в fp32? В bf16?
- Обучение идёт 10 часов, загрузка GPU держится на 20%. Где вы будете искать причину в первую очередь?
- Назовите четыре вещи, без которых эксперимент нельзя воспроизвести через месяц.
- Что делает
git bisectи в какой ситуации он незаменим? - В цикле обучения на каждом шаге вызывается
loss.item()для печати. Почему это может заметно замедлить обучение на GPU?
Ключи ответов
Блок 1.
[1, 2]. Значение по умолчанию вычисляется один раз, при определении функции, и список живёт между вызовами. Правильно:acc=Noneи внутриif acc is None: acc = [].14(0+1+4+9). Генератор ленивый: памятьO(1)при любомrange. Со списком ответ тот же, но сначала материализуется список из 10⁹ элементов, а это десятки гигабайт.- Ключевые элементы:
functools.wraps(fn), обёртка с*args, **kwargs,time.perf_counter()до и после,return result. Безwrapsтеряются имя и docstring функции; безreturnдекоратор ломает любую функцию с результатом. [[1, 0, 0], [1, 0, 0], [1, 0, 0]]. Умножение списка копирует ссылки, а не объекты: все три строки указывают на один и тот же список. Правильно:[[0] * 3 for _ in range(3)].python -m cProfile -s cumtime script.py(илиline_profilerдля построчного). Засчитывается любой профилировщик; не засчитывается «расставлю print со временем»: это измерение того, что уже подозреваешь, а не поиск.
Блок 2.
(32, 10):bрастягивается по первой оси.b[:, None]имеет форму(10, 1); сравнение справа:1и10совместимы,10и32нет → ошибка broadcasting.(B, F);B·D·Fумножений-сложений, то есть2BDFFLOPs. Это та же двойка, из которой в неделе 9 получится2Nна токен.- Не копирует:
.Tвозвращает view с переставленными strides.y[0, 0] == 100. - Ранг 1: каждый столбец равен
u, умноженному на число. ‖Qx‖ = ‖x‖(ортогональные преобразования сохраняют длины),Q⁻¹ = Qᵀ.
Блок 3.
σ'(x) = σ(x)(1 − σ(x)).f'(x) = eˣ / (1 + eˣ) = σ(x). Полезно запомнить: производная softplus и есть сигмоида.∇f = (A + Aᵀ)x; для симметричнойAполучается2Ax.∇L = 2(wᵀx − y)x.dL/dx = 2x·cos(x²).
Блок 4.
1 − (1/2)³ = 7/8.E = np,Var = np(1 − p): это суммаnнезависимых Бернулли.0.5. Положительных больных:0.01 · 0.99 = 0.0099; положительных здоровых:0.99 · 0.01 = 0.0099. Ответ «99%» означает классическую ошибку: базовую частоту проигнорировали.2. Дисперсии независимых величин складываются даже при вычитании:Var(−Y) = Var(Y).1/p: это матожидание геометрического распределения.
Блок 5.
- Переобучение. Действия: регуляризация (weight decay, dropout), ранняя остановка, больше данных или аугментации, уменьшить модель. Засчитываются любые три.
- 99%. Модель бесполезна: она не находит ни одного положительного. Смотреть recall, precision, F1, PR-AUC; accuracy при сильном дисбалансе ничего не говорит.
- По validation выбирают гиперпараметры и модель, поэтому оценка на нём оптимистично смещена. Test трогают один раз, в самом конце.
- Бинарную кросс-энтропию, то есть отрицательное лог-правдоподобие Бернулли. С сигмоидой MSE даёт невыпуклую задачу и почти нулевой градиент при уверенной ошибке (сигмоида насыщена). Засчитывается любой из двух аргументов с объяснением.
- Например: нормализация по всему датасету до разбиения на train/test; дубликаты или почти-дубликаты между train и test; признак, вычисленный по информации из будущего; один пользователь или пациент одновременно в train и test.
Блок 6.
- fp32: 4 байта × 10⁹ = 4 ГБ; bf16: 2 ГБ.
- Даталоадер (CPU не успевает готовить батчи), синхронизации CPU–GPU, слишком маленький батч. И главное: не гадать, а запустить профилировщик.
- Зафиксированные сиды; версии кода (commit hash) и зависимостей; конфиг со всеми гиперпараметрами; версия данных. Засчитываются любые четыре разумных.
- Двоичный поиск по истории коммитов: находит коммит, внёсший регрессию,
за
log₂(n)проверок. Незаменим, когда «раньше работало» и между «работало» и «сломалось» сотни коммитов. .item()копирует значение на CPU и заставляет CPU ждать, пока GPU закончит все поставленные в очередь ядра. Асинхронность пропадает: GPU простаивает, пока CPU готовит следующий шаг.
Как считать результат
| Итог (из 30) | Доля | Что делать |
|---|---|---|
| ≥ 24 | ≥ 80% | Сразу в неделю 1 ядра |
| 15–23.5 | 50–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и разрешить конфликт без потери изменений.
Самопроверка
- Почему изменяемое значение по умолчанию оказывается ловушкой, и как её обойти?
- Как
torch.no_grad()может работать и как декоратор, и какwith? - Чем 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и обратно.
Самопроверка
- Что получится из
(B,) − (B, 1)и как такую ошибку поймать автоматически? - Зачем
keepdims=Trueв стабильном softmax? - Почему векторизованный код быстрее цикла в десятки раз, если считает то же самое?
Ресурсы: документация 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(), и отсюда же бесплатное накопление градиента по нескольким микробатчам, которое понадобится в неделе 10no_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()накапливает градиенты, и использовать это для накопления по микробатчам. - Могу диагностировать неработающую модель тестом переобучения на одном батче.
Самопроверка
- Что произойдёт, если забыть
optimizer.zero_grad()? Опиши поведение лосса. - Чем отличаются
detach(),torch.no_grad()иtorch.inference_mode()? - Почему проверку градиента делают в
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для квадратичной функции и подтвердить её экспериментом.
Самопроверка
- Почему ранг
BAне большеrи что это значит для LoRA? - Как связаны SVD матрицы данных и собственные векторы её ковариационной матрицы?
- Почему слишком большой 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и сказать, значима ли разница.
Самопроверка
- Почему линейность матожидания не требует независимости, а сложение дисперсий требует?
- Выведи MLE для вероятности орла у монеты.
- Что говорит разложение Тейлора второго порядка о выборе шага градиентного спуска?
Ресурсы: 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.
Самопроверка
- Почему k-means сходится и почему не обязательно к глобальному минимуму?
- Что будет с PCA, если забыть центрировать данные?
- Что означает 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 и без подглядывания:
- Повторить диагностику и набрать ≥ 24 из 30, отвечая с выводом, а не по памяти ключей.
- За 45 минут с пустого файла: логистическая регрессия на NumPy с ручным градиентом, проверкой градиента конечными разностями и функцией ROC-AUC.
- За 5 минут вслух: формы всех тензоров в forward и backward одного линейного слоя с батчем.
Если пункт не прошёл, вернуться к соответствующей неделе F, а не идти в ядро с дырой: неделя 1 ядра начинается ровно с того места, где заканчивается этот модуль.