Пакетные линейные операции#
Почти все функции линейной алгебры SciPy теперь поддерживают ввод N-мерных массивов. Эти операции не были математически обобщены на тензоры высшего порядка; скорее, указанная операция выполняется на batch (или "стек") входных скаляров, векторов и/или матриц.
Рассмотрим linalg.det функция, которая отображает матрицу в скаляр.
import numpy as np
from scipy import linalg
A = np.eye(3)
linalg.det(A)
np.float64(1.0)
Иногда нам требуется определитель пакета матриц одинаковой размерности.
batch = [i*np.eye(3) for i in range(1, 4)]
batch
[array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]]),
array([[2., 0., 0.],
[0., 2., 0.],
[0., 0., 2.]]),
array([[3., 0., 0.],
[0., 3., 0.],
[0., 0., 3.]])]
Мы могли бы выполнить операцию для каждого элемента пакета в цикле или списковом включении:
[linalg.det(A) for A in batch]
[np.float64(1.0), np.float64(8.0), np.float64(27.0)]
Однако, так же как мы можем использовать правила трансляции и векторизации NumPy для создания пакета матриц в первую очередь:
i = np.arange(1, 4).reshape(-1, 1, 1)
batch = i * np.eye(3)
batch
array([[[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]],
[[2., 0., 0.],
[0., 2., 0.],
[0., 0., 2.]],
[[3., 0., 0.],
[0., 3., 0.],
[0., 0., 3.]]])
мы также можем захотеть выполнить операцию определителя для всех матриц за один вызов функции.
linalg.det(batch)
array([ 1., 8., 27.])
В SciPy мы предпочитаем термин «пакет» вместо «стек», поскольку идея обобщена на N-мерные пакеты. Предположим, входные данные — это пакет 2 x 4 матриц 3 x 3.
batch_shape = (2, 4)
i = np.arange(np.prod(batch_shape)).reshape(*batch_shape, 1, 1)
input = i * np.eye(3)
В этом случае мы говорим, что форма пакета является (2, 4)должны быть включены в старые категории. Значения, которые были в
удаленных категориях, будут установлены в NaN основная форма входных данных (3, 3). Общая форма входных данных - это сумма (конкатенация) формы пакета и основной формы.
input.shape
(2, 4, 3, 3)
Поскольку каждая матрица 3 x 3 преобразуется в скаляр нулевой размерности, мы говорим, что основная форма выхода (). Форма вывода — сумма формы пакета и основной формы, поэтому результат представляет собой массив 2 x 4.
output = linalg.det(input)
output
array([[ 0., 1., 8., 27.],
[ 64., 125., 216., 343.]])
output.shape
(2, 4)
Не все функции линейной алгебры отображаются в скаляры. Например, scipy.linalg.expm функция отображает матрицу в матрицу той же формы.
A = np.eye(3)
linalg.expm(A)
array([[2.71828183, 0. , 0. ],
[0. , 2.71828183, 0. ],
[0. , 0. , 2.71828183]])
В этом случае основная форма вывода (3, 3), поэтому с размером пакета (2, 4), мы ожидаем выходную форму (2, 4, 3, 3).
output = linalg.expm(input)
output.shape
(2, 4, 3, 3)
Обобщение этих правил на функции с несколькими входами и выходами является простым. Например, scipy.linalg.eig функция по умолчанию выдаёт два результата: вектор и матрицу.
evals, evecs = linalg.eig(A)
evals.shape, evecs.shape
((3,), (3, 3))
В этом случае основная форма выходного вектора равна (3,) и основная форма выходной матрицы (3, 3)Форма каждого выхода представляет собой форму пакета плюс основную форму, как и ранее.
evals, evecs = linalg.eig(input)
evals.shape, evecs.shape
((2, 4, 3), (2, 4, 3, 3))
Когда входных данных больше одного, нет осложнений, если формы входных данных идентичны.
evals, evecs = linalg.eig(input, b=input)
evals.shape, evecs.shape
((2, 4, 3), (2, 4, 3, 3))
Правила, когда формы не идентичны, следуют логически. Каждый вход может иметь свою собственную форму пакета, пока формы транслируемы согласно Правила трансляции NumPy. Чистая форма пакета — это транслированная форма отдельных форм пакетов, а форма каждого вывода — это чистая форма пакета плюс его основная форма.
rng = np.random.default_rng(2859239482)
# Define input core shapes
m = 3
core_shape_a = (m, m)
core_shape_b = (m, m)
# Define broadcastable batch shapes
batch_shape_a = (2, 4)
batch_shape_b = (5, 1, 4)
# Define output core shapes
core_shape_evals = (m,)
core_shape_evecs = (m, m)
# Predict shapes of outputs: broadcast batch shapes,
# and append output core shapes
net_batch_shape = np.broadcast_shapes(batch_shape_a, batch_shape_b)
output_shape_evals = net_batch_shape + core_shape_evals
output_shape_evecs = net_batch_shape + core_shape_evecs
output_shape_evals, output_shape_evecs
((5, 2, 4, 3), (5, 2, 4, 3, 3))
# Check predictions
input_a = rng.random(batch_shape_a + core_shape_a)
input_b = rng.random(batch_shape_b + core_shape_b)
evals, evecs = linalg.eig(input_a, b=input_b)
evals.shape, evecs.shape
((5, 2, 4, 3), (5, 2, 4, 3, 3))
Есть несколько функций, для которых основная размерность (т.е. длина основной формы) аргумента или вывода может быть либо 1, либо 2. В этих случаях основная размерность принимается равной 1, если массив имеет только одно измерение, и 2, если массив имеет два или более измерений. Например, рассмотрим следующие вызовы scipy.linalg.solve. Простейший случай — одиночная квадратная матрица A и один вектор b:
A = np.eye(5)
b = np.arange(5)
linalg.solve(A, b)
array([0., 1., 2., 3., 4.])
В этом случае, основная размерность A равно 2 (форма (5, 5)), основная размерность b равен 1 (форма (5,)), и основная размерность вывода равна 1 (форма (5,)).
Однако, b также может быть двумерным массивом, в котором столбцы считаются одномерными векторами.
b = np.empty((5, 2))
b[:, 0] = np.arange(5)
b[:, 1] = np.arange(5, 10)
linalg.solve(A, b)
array([[0., 5.],
[1., 6.],
[2., 7.],
[3., 8.],
[4., 9.]])
b.shape
(5, 2)
На первый взгляд может показаться, что основная форма b все еще (5,), и мы просто выполнили операцию с размерностью пакета (2,). Однако, если бы это было так, пакетная форма b будет добавлен в начало к основной форме, что приводит к b и выходные данные имеют форму (2, 5). При более тщательном рассмотрении, правильно считать основную размерность как входов, так и выхода равной 2; форма пакета — ().
Аналогично, всякий раз, когда b имеет более двух измерений, основная размерность b и выход считается равным 2. Например, для решения пакета из трёх полностью независимых линейных систем, каждая с только одной правой частью, b должен быть предоставлен как трёхмерный массив: одно измерение для формы пакета ((3,)) и два для основной формы ((5, 1)).
A = rng.random((3, 5, 5))
b = rng.random((3, 5, 1)) # batch shape (3,), core shape (5, 1)
linalg.solve(A, b).shape
(3, 5, 1)