Фаза 01 · урок 12
Операции с тензорами
Цель урока: Вы строите трансформер. Прямой проход выглядит аккуратно. Вы запускаете его и получаете: RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x768 and 512x768) . Вы смотрите на формы. Пробуете транспонирование. Теперь сообщение:…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Что такое тензор
- Формы тензоров в глубоком обучении
- Как устроена раскладка в памяти
- Правила broadcasting
- Einsum: универсальная тензорная операция
- Соберите это
- Шаг 1: хранение тензора и шаги
- Шаг 2: reshape, squeeze, unsqueeze
- Шаг 3: транспонирование и permute
- Шаг 4: поэлементные операции и редукции
- Шаг 5: broadcasting с NumPy
- Шаг 6: операции einsum
- Шаг 7: механизм внимания через einsum
- Используйте это
- Scratch и NumPy
- Scratch и PyTorch
- Каждый слой нейронной сети как тензорная операция
- Подготовьте к выпуску
- Упражнения
- Ключевые термины
- Дополнительное чтение
Тензоры — общий язык данных и глубокого обучения. Через них проходит каждое изображение, каждое предложение и каждый градиент.
Тип: Практика Язык: Python Предварительные требования: фаза 1, уроки 01 (Интуиция линейной алгебры), 02 (Векторы, матрицы и операции) Время: ~90 минут
Цели обучения
- Реализовать с нуля класс тензора с формой, шагами, изменением формы, транспонированием и поэлементными операциями
- Применять правила broadcasting для работы с тензорами разных форм без копирования данных
- Писать выражения einsum для скалярных произведений, умножений матриц, внешних произведений и пакетных операций
- Прослеживать точные формы тензоров на каждом шаге многоголового внимания
Проблема
Вы строите трансформер. Прямой проход выглядит аккуратно. Вы запускаете его и получаете: RuntimeError: mat1 and mat2 shapes cannot be multiplied (32x768 and 512x768). Вы смотрите на формы. Пробуете транспонирование. Теперь сообщение: Expected 4D input (got 3D input). Вы добавляете unsqueeze. Ломается что-то ещё.
Ошибки формы — самая распространённая ошибка в коде глубокого обучения. Концептуально они несложны: у каждой операции есть контракт формы, — но быстро множатся. В трансформере связаны десятки reshape, transpose и broadcast-операций. Одна неверная ось — и ошибка каскадируется. Хуже того, некоторые ошибки формы вовсе не вызывают исключений. Они тихо выдают мусор, выполняя broadcasting по неверному измерению или суммируя по неверной оси.
Матрицы описывают попарные отношения между двумя наборами объектов. Настоящие данные не помещаются в два измерения. Пакет из 32 RGB-изображений размером 224x224 — это четырёхмерный тензор: (32, 3, 224, 224). Self-attention с 12 головами также четырёхмерен: (batch, heads, seq_len, head_dim). Нужна структура данных, обобщающаяся на любое число измерений, с операциями, которые чисто композиционируются между всеми ними. Эта структура — тензор. Освойте его операции, и ошибки форм станут тривиально отлаживаемыми.
Концепция
Что такое тензор
Тензор — это многомерный массив чисел с единым типом данных. Число измерений — его ранг (или порядок). Каждое измерение является осью. Форма — это кортеж, перечисляющий размер вдоль каждой оси.
Общее число элементов равно произведению всех размеров. Форма (2, 3, 4) содержит 2 * 3 * 4 = 24 элемента.
Формы тензоров в глубоком обучении
Разные типы данных по соглашению соответствуют определённым формам тензоров.
PyTorch использует NCHW (каналы первыми). TensorFlow по умолчанию использует NHWC (каналы последними). Несовпадение раскладок вызывает незаметные замедления или ошибки.
Как устроена раскладка в памяти
Двумерный массив в памяти — это одномерная последовательность байтов. Шаги (strides) указывают, сколько элементов нужно пропустить, чтобы переместиться на один шаг вдоль каждой оси.
Транспонирование не перемещает данные. Оно меняет шаги местами, делая тензор неконтинуальным (non-contiguous): элементы строки больше не соседствуют в памяти.
Правила broadcasting
Broadcasting позволяет выполнять операции над тензорами разных форм без копирования данных. Выравнивайте формы справа. Два измерения совместимы, когда они равны или одно из них равно 1. Недостающие измерения дополняются единицами слева.
Tensor A: (8, 1, 6, 1)
Tensor B: (7, 1, 5)
Padded B: (1, 7, 1, 5)
Result: (8, 7, 6, 5)
Einsum: универсальная тензорная операция
Суммирование Эйнштейна помечает каждую ось буквой. Оси, присутствующие во входных данных, но отсутствующие в результате, суммируются. Оси, присутствующие в обоих местах, сохраняются.
Основные шаблоны: i,i-> (скалярное произведение), i,j->ij (внешнее произведение), ii-> (след), ij->ji (транспонирование), bij,bjk->bik (пакетное умножение матриц), bhtd,bhsd->bhts (оценки внимания).
tensor-broadcast
Соберите это
Код находится в code/tensors.py. Каждый шаг ссылается на реализацию в этом файле.
Шаг 1: хранение тензора и шаги
Тензор хранит плоский список чисел вместе с метаданными формы. Шаги сообщают логике индексации, как сопоставлять многомерные индексы плоским позициям.
class Tensor:
def __init__(self, data, shape=None):
if isinstance(data, (list, tuple)):
self._data, self._shape = self._flatten_nested(data)
elif isinstance(data, np.ndarray):
self._data = data.flatten().tolist()
self._shape = tuple(data.shape)
else:
self._data = [data]
self._shape = ()
if shape is not None:
total = reduce(lambda a, b: a * b, shape, 1)
if total != len(self._data):
raise ValueError(
f"Cannot reshape {len(self._data)} elements into shape {shape}"
)
self._shape = tuple(shape)
self._strides = self._compute_strides(self._shape)
@staticmethod
def _compute_strides(shape):
if len(shape) == 0:
return ()
strides = [1] * len(shape)
for i in range(len(shape) - 2, -1, -1):
strides[i] = strides[i + 1] * shape[i + 1]
return tuple(strides)
Для формы (3, 4) шаги равны (4, 1): чтобы перейти на одну строку, пропустите 4 элемента; чтобы перейти на один столбец, пропустите 1 элемент.
Шаг 2: reshape, squeeze, unsqueeze
Reshape меняет форму, не меняя порядок элементов. Общее число элементов должно оставаться тем же. Используйте -1 для одного измерения, чтобы вывести его размер автоматически.
t = Tensor(list(range(12)), shape=(2, 6))
r = t.reshape((3, 4))
r = t.reshape((-1, 3))
Squeeze удаляет оси размера 1. Unsqueeze вставляет одну. Unsqueeze критичен для broadcasting: вектор смещений (D,), добавляемый к пакету (B, T, D), нужно развернуть до (1, 1, D).
t = Tensor(list(range(6)), shape=(1, 3, 1, 2))
s = t.squeeze()
v = Tensor([1, 2, 3])
u = v.unsqueeze(0)
Шаг 3: транспонирование и permute
Transpose меняет местами две оси. Permute переупорядочивает все оси. Так вы преобразуете NCHW в NHWC и обратно.
mat = Tensor(list(range(6)), shape=(2, 3))
tr = mat.transpose(0, 1)
t4d = Tensor(list(range(24)), shape=(1, 2, 3, 4))
perm = t4d.permute((0, 2, 3, 1))
После transpose или permute тензор становится неконтинуальным в памяти. В PyTorch view не работает с неконтинуальными тензорами: используйте reshape либо сначала вызовите .contiguous().
Шаг 4: поэлементные операции и редукции
Поэлементные операции (сложение, умножение, вычитание) применяются независимо к каждому элементу и сохраняют форму. Редукции (sum, mean, max) сворачивают одну или несколько осей.
a = Tensor([[1, 2], [3, 4]])
b = Tensor([[10, 20], [30, 40]])
c = a + b
d = a * 2
s = a.sum(axis=0)
Глобальный average pooling в CNN: (B, C, H, W).mean(axis=[2, 3]) даёт (B, C). Усредняющий pooling по последовательности в NLP: (B, T, D).mean(axis=1) даёт (B, D).
Шаг 5: broadcasting с NumPy
Функция demo_broadcasting_numpy() в tensors.py показывает основные шаблоны.
activations = np.random.randn(4, 3)
bias = np.array([0.1, 0.2, 0.3])
result = activations + bias
images = np.random.randn(2, 3, 4, 4)
scale = np.array([0.5, 1.0, 1.5]).reshape(1, 3, 1, 1)
result = images * scale
a = np.array([1, 2, 3]).reshape(-1, 1)
b = np.array([10, 20, 30, 40]).reshape(1, -1)
outer = a * b
Попарное расстояние через broadcasting: измените форму (M, 2) на (M, 1, 2), а (N, 2) на (1, N, 2), вычтите, возведите в квадрат, просуммируйте по последней оси и извлеките квадратный корень. Результат: (M, N).
Шаг 6: операции einsum
Функции demo_einsum() и demo_einsum_gallery() разбирают каждый распространённый шаблон.
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
dot = np.einsum("i,i->", a, b)
A = np.array([[1, 2], [3, 4], [5, 6]], dtype=float)
B = np.array([[7, 8, 9], [10, 11, 12]], dtype=float)
matmul = np.einsum("ik,kj->ij", A, B)
batch_A = np.random.randn(4, 3, 5)
batch_B = np.random.randn(4, 5, 2)
batch_mm = np.einsum("bij,bjk->bik", batch_A, batch_B)
Вычислительная стоимость свёртки равна произведению размеров всех индексов (сохраняемых и суммируемых). Для bij,bjk->bik при B=32, I=128, J=64, K=128: 32 * 128 * 64 * 128 = 33,554,432 операций умножения-сложения.
Шаг 7: механизм внимания через einsum
Функция demo_attention_einsum() реализует многоголовое внимание от начала до конца.
B, H, T, D = 2, 4, 8, 16
E = H * D
X = np.random.randn(B, T, E)
W_q = np.random.randn(E, E) * 0.02
Q = np.einsum("bte,ek->btk", X, W_q)
Q = Q.reshape(B, T, H, D).transpose(0, 2, 1, 3)
scores = np.einsum("bhtd,bhsd->bhts", Q, K) / np.sqrt(D)
weights = softmax(scores, axis=-1)
attn_output = np.einsum("bhts,bhsd->bhtd", weights, V)
concat = attn_output.transpose(0, 2, 1, 3).reshape(B, T, E)
output = np.einsum("bte,ek->btk", concat, W_o)
Каждый шаг — тензорная операция: проекция (matmul через einsum), разбиение на головы (reshape + transpose), оценки внимания (пакетный matmul через einsum), взвешенная сумма (пакетный matmul через einsum), объединение голов (transpose + reshape), выходная проекция (matmul через einsum).
Используйте это
Scratch и NumPy
| Операция | Scratch (класс Tensor) | NumPy |
|---|---|---|
| Создание | Tensor([[1,2],[3,4]]) |
np.array([[1,2],[3,4]]) |
| Изменение формы | t.reshape((3,4)) |
a.reshape(3,4) |
| Транспонирование | t.transpose(0,1) |
a.T or a.transpose(0,1) |
| Squeeze | t.squeeze(0) |
np.squeeze(a, 0) |
| Сумма | t.sum(axis=0) |
a.sum(axis=0) |
| Einsum | N/A | np.einsum("ij,jk->ik", a, b) |
Scratch и PyTorch
import torch
t = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
t.shape
t.stride()
t.is_contiguous()
t.reshape(3, 2)
t.unsqueeze(0)
t.transpose(0, 1)
t.transpose(0, 1).contiguous()
torch.einsum("ik,kj->ij", A, B)
PyTorch добавляет autograd, поддержку GPU и оптимизированные ядра BLAS. Семантика форм идентична. Если вы понимаете версию с нуля, сообщения PyTorch об ошибках формы становятся понятными.
Каждый слой нейронной сети как тензорная операция
| Операция | Тензорная форма | Einsum |
|---|---|---|
| Линейный слой | Y = X @ W.T + b |
"bd,od->bo" + bias |
| Attention QKV | Q = X @ W_q |
"btd,dh->bth" |
| Оценки внимания | Q @ K.T / sqrt(d) |
"bhtd,bhsd->bhts" |
| Выход внимания | softmax(scores) @ V |
"bhts,bhsd->bhtd" |
| Batch norm | (X - mu) / sigma * gamma |
поэлементно + broadcast |
| Softmax | exp(x) / sum(exp(x)) |
поэлементно + редукция |
Подготовьте к выпуску
Этот урок создаёт два повторно используемых промпта:
-
outputs/prompt-tensor-shapes.md— систематический промпт для отладки несовпадений форм тензоров. Включает таблицы решений для каждой распространённой операции (matmul, broadcast, cat, Linear, Conv2d, BatchNorm, softmax) и таблицу поиска исправлений. -
outputs/prompt-tensor-debugger.md— пошаговый отладочный промпт, который можно вставить в любого AI-ассистента, когда ошибка формы блокирует работу. Передайте ему текст ошибки и формы тензоров, чтобы получить точное исправление.
Упражнения
-
Легко — круговой путь изменения формы. Возьмите тензор формы
(2, 3, 4). Измените его форму на(6, 4), затем на(24,), а затем снова на(2, 3, 4). На каждом шаге убедитесь, что порядок элементов сохранён, напечатав плоские данные. -
Средне — реализуйте broadcasting. Дополните класс
Tensorметодомbroadcast_to(shape), который расширяет измерения размера 1 до целевой формы. Затем измените_elementwise_op, чтобы он автоматически применял broadcasting перед операцией. Проверьте на формах(3, 1)и(1, 4), получающих(3, 4). -
Сложно — постройте einsum с нуля. Реализуйте базовую функцию
einsum(subscripts, *tensors), обрабатывающую как минимум: скалярное произведение (i,i->), умножение матриц (ij,jk->ik), внешнее произведение (i,j->ij) и транспонирование (ij->ji). Разберите строку индексов, определите сокращаемые индексы и переберите все сочетания индексов. Сравните результаты сnp.einsum. -
Сложно — трекер форм внимания. Напишите функцию, принимающую
batch_size,seq_len,embed_dimиnum_headsи выводящую точную форму на каждом шаге многоголового внимания: вход, проекция Q/K/V, разбиение на головы, оценки внимания, веса softmax, взвешенная сумма, объединение голов, выходная проекция. Проверьте результат относительно выводаdemo_attention_einsum().
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| Тензор | «Матрица, но с большим числом измерений» | Многомерный массив с единым типом и определёнными формой, шагами и операциями |
| Ранг | «Число измерений» | Число осей. Ранг матрицы равен 2, а не её матричному рангу |
| Форма | «Размер тензора» | Кортеж, перечисляющий размер вдоль каждой оси. (2, 3) означает 2 строки, 3 столбца |
| Шаг | «Как размещена память» | Число элементов, которое нужно пропустить, чтобы сдвинуться на одну позицию вдоль каждой оси |
| Broadcasting | «Оно просто работает, когда формы различаются» | Строгий набор правил: выравнивать справа; измерения должны быть равны либо одно из них должно быть 1 |
| Континуальный | «Тензор обычный» | Элементы последовательно хранятся в памяти без зазоров или переупорядочивания относительно логической раскладки |
| Einsum | «Навороченный способ написать matmul» | Общая нотация, выражающая любое тензорное сокращение, внешнее произведение, след или транспонирование одной строкой |
| View | «То же, что reshape» | Тензор, использующий тот же буфер памяти, но с другими метаданными формы/шагов. Не работает на неконтинуальных данных |
| Свёртка | «Суммирование по индексу» | Общая операция, при которой общий индекс между тензорами перемножается и суммируется, создавая результат меньшего ранга |
| NCHW / NHWC | «Формат PyTorch или TensorFlow» | Соглашения о раскладке в памяти для тензоров изображений. В NCHW каналы идут до пространственных измерений, в NHWC — после них |
Дополнительное чтение
- NumPy Broadcasting — канонические правила с визуальными примерами
- PyTorch Tensor Views — когда представления работают, а когда копируют данные
- einops — библиотека, делающая изменение формы тензоров читаемым и безопасным
- The Illustrated Transformer — визуализирует формы тензоров, проходящие через внимание
- Einstein Summation in NumPy — полная документация einsum с примерами
Источник: Tensor Operations — оригинал Навигация: назад: 01.11 — Сингулярное разложение · Фаза 1 — Математические основы · Полный каталог · далее: 01.13 — Численная устойчивость.