Пришло время
познакомиться с одной из фундаментальных возможностей пакета NumPy–матричных и
векторных вычислений. На одном из прошлых занятий мы с вами уже видели, как
можно поэлементно умножать один вектор на другой или одну матрицу на другую:
a = np.arange(1, 10).reshape(3, 3)
b = np.arange(10, 19).reshape(3, 3)
a*b
В консоли увидим
результат:
array([[
10, 22, 36],
[ 52, 70, 90],
[112, 136,
162]])
Матричное умножение
Но если нам
нужно выполнить именно матричное умножение, то есть, строки одной матрицы
умножать на столбцы другой и результаты складывать:
то для этого
следует использовать специальные функции и операторы. Начнем с функций. Итак,
чтобы перемножить две матрицы a иbпо всем правилам
математики, запишем следующую команду:
Эта функция
возвращает новую матрицу (двумерный массив) с результатом умножения:
array([[ 84, 90, 96],
[201,
216, 231],
[318, 342,
366]])
Тот же результат
можно получить и с помощью функции:
Считается, что
этот вариант предпочтительнее использовать при умножении матриц.
Векторное умножение
Аналогичные
операции можно выполнять и с векторами. Математически, если у нас имеются два
вектора:
то их умножение
можно реализовать в двух видах:
и
Первое умножение
реализуется либо через функцию:
a = np.arange(1, 10)
b = np.ones(9)
np.dot(a, b) # значение 45
Либо, более
предпочтительной функцией для внутреннего умножения векторов:
Второй вариант
умножения (внешнее умножение векторов) реализуется с помощью функции:
получим
результат в виде следующей матрицы:
array([[1.,
1., 1., 1., 1., 1., 1., 1., 1.],
[2., 2., 2., 2., 2., 2., 2., 2., 2.],
[3., 3., 3., 3., 3., 3., 3., 3., 3.],
[4., 4., 4., 4., 4., 4., 4., 4., 4.],
[5., 5., 5., 5., 5., 5., 5., 5., 5.],
[6., 6., 6., 6., 6., 6., 6., 6., 6.],
[7., 7., 7., 7., 7., 7., 7., 7., 7.],
[8., 8., 8., 8., 8., 8., 8., 8., 8.],
[9., 9., 9., 9., 9., 9., 9., 9., 9.]])
Операция
умножения матриц и векторов используется довольно часто, поэтому в пакете NumPy имеется весьма
полезный перегруженный оператор, заменяющий функцию matmul:
или, с
использованием матриц:
a.resize(3, 3)
b.resize(3, 3)
a @ b # аналог np.matmul(a, b)
Умножение вектора на матрицу
Наконец,
рассмотрим умножение вектора на матрицу. Это также можно записать двумя
способами:
или
Для реализации
первого способа, зададим одномерный вектор и двумерную матрицу:
a = np.array([1,2,3])
b = np.arange(4,10).reshape(3,2) # матрица 3x2
И, затем,
воспользуемся уже знакомой нам функцией dot:
np.dot(a, b) # array([40, 46])
При такой
записи, когда одномерный массив записан первым аргументом, а матрица – вторым,
получаем умножение вектора-строки на матрицу, то есть, первый способ.
Для реализации
второго способа аргументы нужно поменять местами: сначала указать матрицу, а
затем, вектор. Но, если мы сейчас это сделаем с нашими массивами, то получим
ошибку:
np.dot(b, a) # несогласованность размеров
Дело в том, что
массив a должен
представлять вектор длиной два элемента, так как матрица b имеет размер в
3 строки и 2 столбца:
Определим массивa в два элемента
и умножим на матрицу b:
a = np.array([1, 2])
np.dot(b, a) # array([14, 20, 26])
Получаем
вектор-строку (одномерный массив) как результат умножения. Обратите внимание,
по правилам математики вектор aдолжен быть вектором-столбцом, то есть,
быть представленным в виде:
a.shape = -1, 1 # вектор-столбец 2x1
Но мы
использовали вектор-строку. В NumPyтак тоже можно делать и это не приведет
к ошибке. Результат будет именно умножение матрицы как бы на вектор-столбец. Ну
а если использовать вектор-столбец, то и на выходе получим вектор-столбец:
np.dot(b, a) # вектор-столбец 3x1
Этого же результат можно достичь, используя оператор @ (перегрузка функции matmul):
a @ b # вектор-столбец 3x1
Результат будет
тем же. Вот так в NumPyвыполняется умножение матриц, векторов и
вектора на матрицу.
Элементы линейной алгебры
Из высшей
математики хорошо известно, что матрицы можно использовать для решения систем
линейных уравнений. Для этого в NumPyсуществует модуль linalg. Давайте
рассмотрим некоторые из его функций.
Предположим,
имеется квадратная матрица 3x3:
a = np.array([(1, 2, 3), (1, 4, 9), (1, 8, 27)])
Первым делом
вычислим ранг этой матрицы, чтобы быть уверенным, что она состоит из линейно
независимых строк и столбцов:
np.linalg.matrix_rank(a) # рангравен 3
Если ранг
матрицы совпадает с ее размерностью, значит, она способна описывать систему из
трех независимых линейных уравнений. В нашем случае, система уравнений будет
иметь вид:
Здесь -
некие числа линейного уравнения. Например, возьмем их равными:
y = np.array([10, 20, 30])
Тогда корни
уравнения можно вычислить с помощью функции solve:
np.linalg.solve(a, y) # array([-5. , 10. , -1.66666667])
Другой способ
решения этой же системы линейных уравнений возможен через вычисление обратной
матрицы. Изначально, уравнение можно записать в векторно-матричном виде:
Откуда получаем
решения :
На уровне пакета
NumPy это делается
так:
invA = np.linalg.inv(a) # вычисление обратной матрицы
invA @ y # вычисление корней
Конечно, я здесь
представил лишь примеры использования модуля linalg. Приводить все
функции нет смысла, так как они имеют довольно специализированное назначение и
специалисты в своих областях без труда смогут ими воспользоваться. Для полноты
картины я лишь приведу список наиболее характерных функций, чтобы вы знали
возможности расширения linalg.
Функция
|
Описание
|
linalg.cholesky()
|
Разложение
Холецкого
|
linalg.qr()
|
QR-разложение
матрицы
|
linalg.svd()
|
Сингулярное
(SVD) разложение матрицы
|
linalg.norm()
|
Норма
матрицы или вектора
|
linalg.cond()
|
Число
обусловленности матрицы
|
linalg.det()
|
Определитель
(детерминант) матрицы
|
linalg.matrix_rank()
|
Вычисление
ранга матрицы по алгоритму SVD
|
np.trace()
|
Сумма
диагональных элементов массива
|
linalg.eig()
|
Вычисление
собственных значений и правых собственных векторов
|
linalg.eigvals()
|
Вычисление
собственных значений матрицы
|
linalg.solve()
|
Решение
линейного матричного уравнения
|
linalg.tensorsolve()
|
Решение
линейного тензорного уравнения
|
linalg.lstsq()
|
Решает
задачу поиска наименьших квадратов для линейного матричного уравнения
|
linalg.inv()
|
Вычисление
обратной матрицы
|
linalg.pinv()
|
Вычисление
псевдообратной (Мура-Пенроуза) матрицы
|
linalg.tensorinv()
|
Вычисление
обратного тензора (N-мерного массива)
|
Конечно, это не
все математические функции пакета NumPy. Полное
описание смотрите на сайте с официальной документацией:
https://numpy.org/doc/stable/