Математика глубокого обучения
Broadcasting
Правило выравнивания форм — и почему оно молча создаёт тензоры не того размера
Правило целиком
Broadcasting — это одно правило, и его стоит выучить точно, потому что все ошибки с ним происходят от приблизительного знания.
Пошагово:
- более короткую форму дополнить единицами слева;
- пройти по осям и проверить: равны или одна равна ;
- результат по каждой оси — максимум из двух.
Ключевое слово —
| форма A | форма B | результат |
|---|---|---|
| — работает | ||
| ошибка |
Второй случай выглядит естественнее первого — «прибавить по одному числу к каждой строке» — и именно он не работает. Вектор выравнивается по последней оси, то есть сопоставляется с , и не подходит. Чтобы получить желаемое, нужно сказать это явно: .
Оси-единицы — это разрешение
Ось длины — не пустая формальность, а способ сказать «здесь можно растянуть». Отсюда классический приём для внешнего произведения:
Обе оси растягиваются, каждая в своём направлении. Именно так пишут попарные расстояния, матрицы масок и позиционные разности — без единого цикла.
Полезно понимать, что растягивание не копирует данные: реализация ставит страйд по растянутой оси, и один и тот же элемент читается многократно. Поэтому broadcasting дешёв по памяти на входе — но результат-то материализуется полностью, и вот здесь и подстерегает главная опасность.
Как это молча создаёт гигантские тензоры
Самая дорогая ошибка с broadcasting — не падение, а успех.
Это элементов вместо и на входе. Формы совместимы, ошибки нет, и если вы ожидали поэлементной операции — получили попарную. При батче и длине последовательности такая опечатка мгновенно даёт out-of-memory, и трассировка указывает на строку, где память кончилась, а не на ту, где потерялась ось.
Признак, по которому это узнаётся: тензор вырос, а вы этого не просили. Поэтому в рабочем коде полезно проверять формы утверждениями, а не глазами:
assert scores.shape == (batch, heads, seq, seq), scores.shape
Что делать вместо
Broadcasting неявен, и это его единственный настоящий недостаток. Явные альтернативы:
| вместо | лучше |
|---|---|
x[:, None] * y[None, :] | einsum('i,j->ij', x, y) |
x.unsqueeze(1) | rearrange(x, 'b d -> b 1 d') |
| надежды на выравнивание | assert на форму |
Первая строка — тема следующего урока: einsum делает то же самое, но каждая ось названа, поэтому пропущенную ось видно в спецификации, а не в счётчике памяти.
Посмотрите, как то же внешнее произведение выглядит на языке индексов. Обратите внимание, что здесь ничего не суммируется — все индексы остаются в выходе, поэтому число операций равно размеру результата.
Сравните две последние спецификации: они различаются одной буквой в выходе, а результат — в
раз по объёму. В einsum это видно; в записи через None-индексы — нет.
Что стоит унести
- выравнивание идёт справа, и с не совместимы, хотя выглядят так;
- ось длины — это разрешение растянуть, и её отсутствие — тоже утверждение;
- растягивание не копирует вход, но результат материализуется целиком;
- самая дорогая ошибка не падает: формы сходятся, а тензор оказывается больше нужного в сотни раз.
Источники
- NumPy — Broadcasting — Правило и примеры
- Rogozhnikov — Einops tutorial — Явные оси вместо неявного растягивания
Проверки
0 из 2Правило broadcasting
Отметьте все верные утверждения о broadcasting.
Вывести форму результата
Реализуйте
broadcast_shape(a, b)— примените правило broadcasting к двум формам.Верните
[1.0, ...result], гдеresult— форма результата, если формы совместимы, и[0.0], если нет. То есть первый элемент — флаг успеха, а остальные — сама форма.Правило:
- более короткую форму мысленно дополнить единицами слева;
- по каждой оси: совместимы, если размеры равны или один из них равен ;
- размер результата по оси — максимум из двух.
Пустой список означает скаляр и совместим с чем угодно.
Главное — направление выравнивания. Формы
[3,4]и[4]совместимы, а[3,4]и[3]— нет, потому что оси сопоставляются с конца.Загрузка редактора…
Ctrl/⌘ + Enter