2.3. Линейная алгебра¶
К этому моменту мы уже умеем загружать наборы данных в тензоры и выполнять с ними базовые математические операции. Чтобы приступить к созданию сложных моделей, нам также понадобятся некоторые инструменты из линейной алгебры. В этом разделе мы кратко рассмотрим наиболее важные концепции, начиная с арифметики скаляров и заканчивая матричным умножением.
import torch
2.3.1. Скаляры¶
Большая часть повседневной математики заключается в работе с числами по одному. Формально такие значения называют скалярами. Например, температура в Пало-Альто составляет приятные \(72\) градусов по Фаренгейту. Если вы хотите перевести температуру в градусы Цельсия, вы вычисляете выражение \(c = \frac{5}{9}(f - 32)\), подставляя \(f\) вместо \(72\). В этом уравнении значения \(5\), \(9\) и \(32\) являются постоянными скалярами. Переменные \(c\) и \(f\) в общем случае обозначают неизвестные скаляры.
Скаляры обозначаются обычными строчными буквами (например, \(x\), \(y\) и \(z\)), а пространство всех (непрерывных) вещественных скаляров — как \(\mathbb{R}\). Для удобства мы опустим строгие определения пространств: просто помните, что запись \(x \in \mathbb{R}\) является формальным способом сказать, что \(x\) является вещественным скаляром. Символ \(\in\) (читается как «в») обозначает принадлежность к множеству. Например, \(x, y \in \{0, 1\}\) указывает на то, что \(x\) и \(y\) — это переменные, которые могут принимать только значения \(0\) или \(1\).
Скаляры реализуются как тензоры, содержащие только один элемент. Ниже мы присваиваем значения двум скалярам и выполняем привычные операции сложения, умножения, деления и возведения в степень.
x = torch.tensor(3.0)
y = torch.tensor(2.0)
x + y, x * y, x / y, x**y
2.3.2. Векторы¶
Для наших текущих целей вектор можно рассматривать как массив скаляров фиксированной длины. Как и в коде, мы называем эти скаляры элементами вектора (синонимами являются компоненты и записи). Когда векторы представляют примеры из реальных наборов данных, их значения имеют практическое значение. Например, если мы обучаем модель для прогнозирования риска дефолта по кредиту, мы можем сопоставить каждому заявителю вектор, компоненты которого соответствуют таким величинам, как его доход, стаж работы или количество предыдущих дефолтов. Если бы мы изучали риск сердечного приступа, каждый вектор мог бы представлять пациента, а его компоненты — соответствовать последним жизненным показателям, уровню холестерина, количеству минут физической активности в день и т. д. Векторы обозначают жирными строчными буквами (например, \(\mathbf{x}\), \(\mathbf{y}\) и \(\mathbf{z}\)).
Векторы реализуются в виде тензоров нулевого порядка \(1^{\textrm{st}}\). В общем случае такие тензоры могут иметь произвольную длину, ограниченную только объёмом памяти. Внимание: в Python, как и в большинстве языков программирования, нумерация элементов вектора начинается с \(0\), что называется индексацией с нуля, тогда как в линейной алгебре нумерация элементов начинается с \(1\) (индексация с единицы).
x = torch.arange(3)
x
Мы можем обозначить элемент вектора с помощью индекса. Например, \(x_2\) обозначает второй элемент \(\mathbf{x}\). Поскольку \(x_2\) является скаляром, мы не выделяем его жирным шрифтом. По умолчанию мы визуализируем векторы, располагая их элементы вертикально:
Здесь \(x_1, \ldots, x_n\) — элементы вектора. Далее мы будем различать такие столбцовые векторы и строчные векторы, элементы которых расположены горизонтально. Напомним, что доступ к элементам тензора осуществляется с помощью индексации.
x[2]
Чтобы указать, что вектор содержит \(n\) элементов, мы записываем
\(\mathbf{x} \in \mathbb{R}^n\). Формально \(n\) называют
размерностью вектора. В коде это соответствует длине тензора, которую
можно получить с помощью встроенной функции Python len.
len(x)
Мы также можем получить длину с помощью атрибута shape. Форма — это
кортеж, указывающий длину тензора вдоль каждой оси. Тензоры с одной осью
имеют форму, состоящую из одного элемента.
x.shape
Зачастую слово «размерность» используется для обозначения как количества осей, так и длины вдоль конкретной оси. Чтобы избежать путаницы, мы будем использовать термин порядок для обозначения количества осей, а термин размерность — исключительно для обозначения количества компонент.
2.3.3. Матрицы¶
Точно так же, как скаляры являются тензорами нулевого порядка \(0^{\textrm{th}}\), а векторы — тензорами первого порядка \(1^{\textrm{st}}\), матрицы являются тензорами второго порядка \(2^{\textrm{nd}}\). Матрицы обозначают жирными заглавными буквами (например, \(\mathbf{X}\), \(\mathbf{Y}\) и \(\mathbf{Z}\)), а в коде представляют тензорами с двумя осями. Запись \(\mathbf{A} \in \mathbb{R}^{m \times n}\) означает, что матрица \(\mathbf{A}\) содержит \(m \times n\) действительных скалярных значений, расположенных в виде \(m\) строк и \(n\) столбцов. Если \(m = n\), говорят, что матрица является квадратной. Визуально любую матрицу можно представить в виде таблицы. Чтобы сослаться на отдельный элемент, индексы строки и столбца записывают в виде нижних индексов, например, \(a_{ij}\) — это значение, принадлежащее строке \(\mathbf{A}\) и столбцу \(i^{\textrm{th}}\) матрицы \(j^{\textrm{th}}\):
В коде матрица \(\mathbf{A} \in \mathbb{R}^{m \times n}\)
представляется тензором \(2^{\textrm{nd}}\)-го порядка с формой
(\(m\), \(n\)). Любой тензор \(m \times n\) подходящего
размера можно преобразовать в матрицу \(m \times n\), передав нужную
форму в reshape:
A = torch.arange(6).reshape(3, 2)
A
Иногда нам нужно поменять оси местами. Когда мы меняем строки и столбцы матрицы местами, полученная матрица называется транспонированной. Формально транспонирование матрицы \(\mathbf{A}\) обозначается как \(\mathbf{A}^\top\), и если \(\mathbf{B} = \mathbf{A}^\top\), то \(b_{ij} = a_{ji}\) для всех \(i\) и \(j\). Таким образом, транспонирование матрицы размера \(m \times n\) даёт матрицу размера \(n \times m\):
В коде мы можем получить транспонированную матрицу следующим образом:
A.T
[Симметричные матрицы — это подмножество квадратных матриц, равных своим транспонированным: :math:`mathbf{A} = mathbf{A}^top`.] Следующая матрица является симметричной:
A = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
A == A.T
Матрицы удобны для представления наборов данных. Обычно строки соответствуют отдельным записям, а столбцы — различным атрибутам.
2.3.4. Тензоры¶
Хотя в машинном обучении можно далеко продвинуться, используя только скаляры, векторы и матрицы, в конечном итоге вам может потребоваться работать с тензорами более высокого порядка. Тензоры предоставляют общий способ описания обобщений массивов порядка \(n^{\textrm{th}}\). Мы называем программные объекты класса тензоров «тензорами» именно потому, что они также могут иметь произвольное количество осей. Хотя использование слова тензор для обозначения как математического объекта, так и его реализации в коде может показаться запутанным, из контекста обычно ясно, что имеется в виду. Мы обозначаем общие тензоры заглавными буквами специального начертания (например, \(\mathsf{X}\), \(\mathsf{Y}\) и \(\mathsf{Z}\)), а механизм их индексации (например, \(x_{ijk}\) и \([\mathsf{X}]_{1, 2i-1, 3}\)) естественным образом вытекает из механизма индексации матриц.
Тензоры станут ещё более важными, когда мы начнем работать с изображениями. Каждое изображение поступает в виде тензора \(3^{\textrm{rd}}\)-го порядка, оси которого соответствуют высоте, ширине и каналу. В каждой пространственной позиции интенсивности каждого цвета (красного, зеленого и синего) объединяются вдоль канала. Кроме того, набор изображений в коде представляется тензором \(4^{\textrm{th}}\)-го порядка, где различные изображения индексируются по первой оси. Тензоры более высокого порядка конструируются, как и векторы и матрицы, путем увеличения количества компонент формы.
torch.arange(24).reshape(2, 3, 4)
2.3.5. Основные свойства арифметики тензоров¶
Скаляры, векторы, матрицы и тензоры более высокого порядка обладают рядом полезных свойств. Например, поэлементные операции дают результаты, форма которых совпадает с формой операндов.
A = torch.arange(6, dtype=torch.float32).reshape(2, 3)
B = A.clone() # Assign a copy of A to B by allocating new memory
A, A + B
Элементное произведение двух матриц называется их произведением Адамара (обозначается \(\odot\)). Мы можем записать элементы произведения Адамара двух матриц \(\mathbf{A}, \mathbf{B} \in \mathbb{R}^{m \times n}\):
A * B
Сложение или умножение скаляра и тензора дает результат той же формы, что и исходный тензор. При этом каждый элемент тензора складывается (или умножается) со скаляром.
a = 2
X = torch.arange(24).reshape(2, 3, 4)
a + X, (a * X).shape
2.3.6. Сведение¶
Часто нам нужно вычислить сумму элементов тензора. Чтобы обозначить сумму элементов вектора \(\mathbf{x}\) длины \(n\), мы используем запись \(\sum_{i=1}^n x_i\). Для этого существует простая функция:
x = torch.arange(3, dtype=torch.float32)
x, x.sum()
Чтобы выразить суммы по элементам тензоров произвольной формы, мы просто суммируем по всем его осям. Например, сумму элементов матрицы \(m \times n\) \(\mathbf{A}\) можно записать как \(\sum_{i=1}^{m} \sum_{j=1}^{n} a_{ij}\).
A.shape, A.sum()
По умолчанию вызов функции суммирования сводит тензор по всем его
осям, в результате чего получается скаляр. Наши библиотеки также
позволяют задать оси, по которым следует выполнять сведение тензора.
Чтобы просуммировать все элементы вдоль строк (ось 0), мы указываем
axis=0 в sum. Поскольку входная матрица сводится по оси 0 для
формирования выходного вектора, эта ось отсутствует в форме выходных
данных.
A.shape, A.sum(axis=0).shape
Указание axis=1 уменьшит размерность столбцов (ось 1) путём
суммирования элементов всех столбцов.
A.shape, A.sum(axis=1).shape
Сведение матрицы по строкам и столбцам с помощью суммирования эквивалентно суммированию всех её элементов.
A.sum(axis=[0, 1]) == A.sum() # Same as A.sum()
Связанной величиной является среднее, также называемое средним
арифметическим. Мы вычисляем среднее, деля сумму на общее количество
элементов. Поскольку вычисление среднего встречается очень часто, для
него предусмотрена специальная функция библиотеки, работающая аналогично
sum.
A.mean(), A.sum() / A.numel()
Аналогично функция вычисления среднего также может выполнять редукцию тензора по определённым осям.
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
2.3.7. Сумма без редукции¶
Иногда бывает полезно [сохранить количество осей неизменным] при вызове функции для вычисления суммы или среднего. Это важно, когда мы хотим использовать механизм широковещательной передачи.
sum_A = A.sum(axis=1, keepdims=True)
sum_A, sum_A.shape
Например, поскольку sum_A сохраняет свои две оси после суммирования
каждой строки, мы можем разделить A на sum_A с помощью
транслирования, чтобы создать матрицу, в которой сумма каждой строки
равна \(1\).
A / sum_A
Если мы хотим вычислить кумулятивную сумму элементов тензора A вдоль
некоторой оси, например axis=0 (по строкам), мы можем вызвать
функцию cumsum. По своей сути эта функция не уменьшает размерность
входного тензора ни по одной из осей.
A.cumsum(axis=0)
2.3.8. Скалярные произведения¶
До сих пор мы выполняли только поэлементные операции, суммирование и вычисление средних значений. И если бы это было всё, что мы могли делать, линейная алгебра не заслуживала бы отдельного раздела. К счастью, именно здесь становится интереснее. Одна из самых фундаментальных операций — скалярное произведение. Для двух векторов \(\mathbf{x}, \mathbf{y} \in \mathbb{R}^d\) их скалярное произведение \(\mathbf{x}^\top \mathbf{y}\) (также называемое внутренним произведением, \(\langle \mathbf{x}, \mathbf{y} \rangle\)) представляет собой сумму произведений элементов, стоящих на одинаковых позициях: \(\mathbf{x}^\top \mathbf{y} = \sum_{i=1}^{d} x_i y_i\).
Скалярное произведение двух векторов — это сумма произведений элементов, стоящих на одинаковых позициях.
y = torch.ones(3, dtype = torch.float32)
x, y, torch.dot(x, y)
Или мы можем вычислить скалярное произведение двух векторов, выполнив поэлементное умножение с последующим суммированием:
torch.sum(x * y)
Скалярные произведения полезны в самых разных контекстах. Например, если задан некоторый набор значений, обозначаемый вектором \(\mathbf{x} \in \mathbb{R}^n\), и набор весов, обозначаемый вектором \(\mathbf{w} \in \mathbb{R}^n\), взвешенная сумма значений из \(\mathbf{x}\) согласно весам \(\mathbf{w}\) может быть выражена как скалярное произведение \(\mathbf{x}^\top \mathbf{w}\). Когда веса неотрицательны и в сумме дают \(1\), то есть \(\left(\sum_{i=1}^{n} {w_i} = 1\right)\), скалярное произведение представляет собой взвешенное среднее. После нормировки двух векторов так, чтобы их длина стала равна единице, скалярное произведение выражает косинус угла между ними. Позже в этом разделе мы формально введём понятие длины.
2.3.9. Произведение матрицы на вектор¶
Теперь, когда мы умеем вычислять скалярные произведения, мы можем приступить к пониманию произведения между \(m \times n\) матрицей \(\mathbf{A}\) и \(n\)-мерным вектором \(\mathbf{x}\). Для начала представим нашу матрицу через её векторы строк
где каждый \(\mathbf{a}^\top_{i} \in \mathbb{R}^n\) — это вектор-строка, представляющая собой \(i^\textrm{th}\) строку матрицы \(\mathbf{A}\).
Произведение матрицы на вектор \(\mathbf{A}\mathbf{x}\) — это просто вектор-столбец длины \(m\), \(i^\textrm{th}\)-й элемент которого равен скалярному произведению \(\mathbf{a}^\top_i \mathbf{x}\):
Мы можем рассматривать умножение на матрицу \(\mathbf{A}\in \mathbb{R}^{m \times n}\) как преобразование, которое проецирует векторы из \(\mathbb{R}^{n}\) в \(\mathbb{R}^{m}\). Эти преобразования оказываются чрезвычайно полезными. Например, повороты можно представить как умножение на определённые квадратные матрицы. Произведение матрицы на вектор также описывает ключевое вычисление, используемое для определения выходов каждого слоя нейронной сети на основе выходов предыдущего слоя.
Чтобы выразить произведение матрицы на вектор в коде, мы используем
функцию mv. Обратите внимание, что размерность столбцов A (его
длина вдоль оси 1) должна совпадать с размерностью вектора x (его
длиной). В Python есть удобный оператор @, который позволяет
выполнять как произведения матрицы на вектор, так и произведения матриц
(в зависимости от аргументов). Таким образом, мы можем записать A@x.
A.shape, x.shape, torch.mv(A, x), A@x
2.3.10. Матрица–матричное умножение¶
Разобравшись с попарным умножением и умножением матрицы на вектор, вы без труда освоите матричное умножение.
Предположим, у нас есть две матрицы \(\mathbf{A} \in \mathbb{R}^{n \times k}\) и \(\mathbf{B} \in \mathbb{R}^{k \times m}\):
Пусть \(\mathbf{a}^\top_{i} \in \mathbb{R}^k\) обозначает вектор-строку, представляющую \(i^\textrm{th}\) строку матрицы \(\mathbf{A}\), а \(\mathbf{b}_{j} \in \mathbb{R}^k\) — вектор-столбец, соответствующий \(j^\textrm{th}\) столбцу матрицы \(\mathbf{B}\):
Чтобы получить матричное произведение \(\mathbf{C} \in \mathbb{R}^{n \times m}\), мы просто вычисляем каждый элемент \(c_{ij}\) как скалярное произведение строки \(i^{\textrm{th}}\) матрицы \(\mathbf{A}\) и столбца \(j^{\textrm{th}}\) матрицы \(\mathbf{B}\), то есть \(\mathbf{a}^\top_i \mathbf{b}_j\):
Матричное умножение \(\mathbf{AB}\) можно рассматривать как
выполнение \(m\) матрично-векторных произведений или
\(m \times n\) скалярных произведений с последующим объединением
результатов в одну \(n \times m\) матрицу. В следующем фрагменте мы
выполняем матричное умножение для A и B. Здесь A — это
матрица с двумя строками и тремя столбцами, а B — матрица с тремя
строками и четырьмя столбцами. После умножения мы получаем матрицу с
двумя строками и четырьмя столбцами.
B = torch.ones(3, 4)
torch.mm(A, B), A@B
Термин матричное умножение часто сокращают до матричного умножения, и его не следует путать с произведением Адамара.
2.3.11. Нормы¶
Одним из наиболее полезных операторов линейной алгебры являются нормы. Неформально норма вектора показывает, насколько он велик. Например, \(\ell_2\) норма измеряет (евклидову) длину вектора. Здесь мы используем понятие размера, которое относится к величине компонент вектора (а не его размерности).
Норма — это функция \(\| \cdot \|\), которая отображает вектор в скаляр и удовлетворяет следующим трём свойствам:
Для любого вектора \(\mathbf{x}\), если умножить (все его элементы) на скаляр \(\alpha \in \mathbb{R}\), его норма также изменится пропорционально:
(2.3.10)¶\[\|\alpha \mathbf{x}\| = |\alpha| \|\mathbf{x}\|.\]Для любых векторов \(\mathbf{x}\) и \(\mathbf{y}\) нормы удовлетворяют неравенству треугольника:
(2.3.11)¶\[\|\mathbf{x} + \mathbf{y}\| \leq \|\mathbf{x}\| + \|\mathbf{y}\|.\]Норма вектора неотрицательна и обращается в нуль только в том случае, если сам вектор равен нулю:
(2.3.12)¶\[\|\mathbf{x}\| > 0 \textrm{ for all } \mathbf{x} \neq 0.\]
Многие функции являются допустимыми нормами, и различные нормы кодируют разные понятия размера. Евклидова норма, которую мы все изучили в школьной геометрии при вычислении длины гипотенузы прямоугольного треугольника, представляет собой квадратный корень из суммы квадратов элементов вектора. Формально она называется нормой \(\ell_2\) и выражается как
Метод norm вычисляет норму \(\ell_2\).
u = torch.tensor([3.0, -4.0])
torch.norm(u)
Норма \(\ell_1\) также широко используется, а соответствующая мера называется манхэттенским расстоянием. По определению, норма \(\ell_1\) представляет собой сумму абсолютных значений элементов вектора:
По сравнению с \(\ell_2\) нормой, она менее чувствительна к выбросам. Для вычисления \(\ell_1\) нормы мы берём сумму абсолютных значений.
torch.abs(u).sum()
Как \(\ell_2\), так и \(\ell_1\) нормы являются частными случаями более общих \(\ell_p\) норм:
В случае матриц ситуация сложнее. Ведь матрицы можно рассматривать как совокупности отдельных элементов и как объекты, которые действуют на векторы и преобразуют их в другие векторы. Например, мы можем спросить, во сколько раз произведение матрицы на вектор \(\mathbf{X} \mathbf{v}\) может быть больше, чем \(\mathbf{v}\). Такой подход приводит к тому, что называется спектральной нормой. Пока же мы введём норму Фробениуса*, которая вычисляется гораздо проще и определяется как квадратный корень из суммы квадратов элементов матрицы:
Норма Фробениуса ведёт себя так, как если бы она была нормой \(\ell_2\) вектора, имеющего форму матрицы. Вызов следующей функции вычисляет норму Фробениуса матрицы.
torch.norm(torch.ones((4, 9)))
Хотя мы не хотим забегать слишком далеко вперёд, мы уже можем сформировать интуитивное понимание того, почему эти понятия полезны. В глубоком обучении мы часто решаем задачи оптимизации: максимизируем вероятность, присвоенную наблюдаемым данным; максимизируем доход, связанный с рекомендательной моделью; минимизируем расстояние между предсказаниями и истинными значениями; минимизируем расстояние между представлениями фотографий одного и того же человека, одновременно максимизируя расстояние между представлениями фотографий разных людей. Эти расстояния, которые составляют цели алгоритмов глубокого обучения, часто выражаются через нормы.
2.3.12. Обсуждение¶
В этом разделе мы рассмотрели весь необходимый аппарат линейной алгебры, который понадобится вам для понимания значительной части современного глубокого обучения. Однако линейная алгебра гораздо шире, и многие её разделы полезны для машинного обучения. Например, матрицы можно разлагать на множители, и такие разложения позволяют выявить низкоразмерную структуру в реальных наборах данных. Существуют целые подразделы машинного обучения, посвящённые использованию разложений матриц и их обобщений на тензоры высших порядков для обнаружения структуры в данных и решения задач прогнозирования. Но эта книга сосредоточена на глубоком обучении. Мы полагаем, что после того как вы начнёте применять машинное обучение к реальным наборам данных, у вас появится больше желания изучать математику глубже. Поэтому, хотя мы оставляем за собой право ввести дополнительные математические сведения позже, данный раздел мы завершаем здесь.
Если вы хотите узнать больше о линейной алгебре, существует множество отличных книг и онлайн-ресурсов. Для более углубленного интенсивного курса рекомендуем обратить внимание на Strang (1993), Kolter (2008) и Petersen and Pedersen (2008).
Подведём итоги:
Скаляры, векторы, матрицы и тензоры — основные математические объекты, используемые в линейной алгебре, имеющие соответственно ноль, одну, две и произвольное количество осей.
Тензоры можно нарезать или сворачивать вдоль заданных осей с помощью индексации или операций, таких как
sumиmean, соответственно.Поэлементные произведения называются произведениями Адамара. В отличие от них, скалярные произведения, произведения матрицы на вектор и произведения матриц не являются поэлементными операциями и, как правило, возвращают объекты, форма которых отличается от форм операндов.
По сравнению с поэлементными произведениями (произведениями Адамара), матричные произведения требуют значительно больше времени на вычисление (кубическое, а не квадратичное время).
Нормы отражают различные понятия величины вектора (или матрицы) и обычно применяются к разности двух векторов для измерения расстояния между ними.
Среди распространённых норм векторов выделяют \(\ell_1\) и \(\ell_2\) нормы, а среди норм матриц — спектральную и Фробениусову нормы.
2.3.13. Упражнения¶
Докажите, что транспонирование транспонированной матрицы дает саму матрицу: \((\mathbf{A}^\top)^\top = \mathbf{A}\).
Даны две матрицы \(\mathbf{A}\) и \(\mathbf{B}\). Докажите, что операции сложения и транспонирования коммутируют: \(\mathbf{A}^\top + \mathbf{B}^\top = (\mathbf{A} + \mathbf{B})^\top\).
Для любой квадратной матрицы \(\mathbf{A}\), является ли \(\mathbf{A} + \mathbf{A}^\top\) всегда симметричной? Можете ли вы доказать этот результат, используя только результаты предыдущих двух упражнений?
В этом разделе мы определили тензор
Xформы (2, 3, 4). Каков результат вычисленияlen(X)? Запишите ответ, не прибегая к написанию кода, а затем проверьте его с помощью кода.Для тензора
Xпроизвольной формы всегда лиlen(X)соответствует длине определённой осиX? Какая это ось?Запустите
A / A.sum(axis=1)и посмотрите, что получится. Можете ли вы проанализировать результаты?При перемещении между двумя точками в центре Манхэттена какое расстояние нужно преодолеть с точки зрения координат, то есть с точки зрения авеню и улиц? Можно ли двигаться по диагонали?
Рассмотрим тензор формы (2, 3, 4). Каковы формы результатов суммирования по осям 0, 1 и 2?
Подайте тензор с тремя или более осями в функцию
linalg.normи наблюдайте за его выводом. Что вычисляет эта функция для тензоров произвольной формы?Рассмотрим три большие матрицы, например \(\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}\), \(\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}\) и \(\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{14}}\), инициализированные значениями из нормального распределения. Необходимо вычислить произведение \(\mathbf{A} \mathbf{B} \mathbf{C}\). Есть ли различия в объёме потребляемой памяти и скорости вычислений в зависимости от того, вычисляется ли \((\mathbf{A} \mathbf{B}) \mathbf{C}\) или \(\mathbf{A} (\mathbf{B} \mathbf{C})\)? Почему?
Рассмотрим три большие матрицы, например \(\mathbf{A} \in \mathbb{R}^{2^{10} \times 2^{16}}\), \(\mathbf{B} \in \mathbb{R}^{2^{16} \times 2^{5}}\) и \(\mathbf{C} \in \mathbb{R}^{2^{5} \times 2^{16}}\). Есть ли разница в скорости вычислений в зависимости от того, вычисляем ли мы \(\mathbf{A} \mathbf{B}\) или \(\mathbf{A} \mathbf{C}^\top\)? Почему? Что изменится, если инициализировать \(\mathbf{C} = \mathbf{B}^\top\) без копирования памяти? Почему?
Рассмотрим три матрицы, например \(\mathbf{A}, \mathbf{B}, \mathbf{C} \in \mathbb{R}^{100 \times 200}\). Построим тензор с тремя осями, объединив \([\mathbf{A}, \mathbf{B}, \mathbf{C}]\). Какова его размерность? Выделите второй элемент по третьей оси, чтобы получить \(\mathbf{B}\). Убедитесь, что ваш ответ верен.