Линейная алгебра — программируем с NumPy

Добрый день, уважаемые читатели. В математике существует весомый раздел — линейная алгебра. Её начало берёт с решений систем линейных уравнений, но углубление в экскурс истории выходит за рамки этой статьи. Сегодня мы займемся разбором основных понятий линейной алгебры, таких как матрица, определитель матрицы , метод Гаусса и другие, а также запрограммируем некоторые вещи, используя замечательный пакет NumPy.

Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!

Линейная алгебра с нуля — матрицы

Сложно дать точное определение понятию «матрица». Тут ситуация такая же, как и с понятием «множество» в той самой теории множеств. Мы ограничимся тем, что матрица — набор элементов, представляющий собой таблицу, которая имеет столбцы и строки.

В математике обычно матрица записывается следующим образом:

    \[A_{i x j} = \begin{pmatrix}a_{11} & a_{12} & \ldots & a_{1j}\\a_{21} & a_{22} & \ldots & a_{2j}\\\vdots & \vdots & \ddots & \vdots \\a_{i1} & a_{i2} & \ldots & a_{ij}\\\end{pmatrix}\]

Как правило, матрицы обозначаются заглавными буквами, а i и j — размерность матрицы (кол-во строк и кол-во столбцов соответственно). К примеру, определим матрицу размеров 3 на 2.

Элементы матрицы обычно обозначаются как a_{ij}, т. е. элемент, который находится на пересечении i-ой строки и j-ого столбца.


    \[A_{3x2} = \begin{pmatrix}1 & 2\\3 & 4\\5 & 6\\\end{pmatrix}\]

Теперь рассмотрим несколько видов матриц.

Квадратной матрицей называется матрица, в которой кол-во строк и кол-во столбцов равны. Определим матрицу 3 на 3:


    \[A = \begin{pmatrix}1 & 2 & 3\\4 & 5 & 6\\7 & 8 & 9\\\end{pmatrix}\]

Нулевой матрицей называется матрица, все элементы которой равны 0. Выражаясь математическим языком:

    \[\forall i, j : a_{ij} = 0\]

Единичной матрицей является матрица, у которой все элементы вне главной диагонали равны 0, а элементы на главной диагонали равны 1. Такая матрица является квадратной. К примеру, определим единичную матрицу размером 3 на 3:

    \[A = \begin{pmatrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\\\end{pmatrix}\]

Единичная матрица является подвидом диагональной матрицы — матрицы, у которой все элементы вне главной диагонали равны 0, а остальные имеют произвольное значение.

Треугольная матрица — матрица, все элементы выше или ниже главной диагонали равны 0. Выделяют два вида треугольных матриц — верхняя треугольная матрица:

    \[A = \begin{pmatrix}1 & 0 & 0\\2 & 3 & 0\\4 & 5 & 6\\\end{pmatrix}\]

и нижняя треугольная матрица:

    \[A = \begin{pmatrix}1 & 2 & 3\\0 & 4 & 5\\0 & 0 & 5\\\end{pmatrix}\]

Теперь перейдём к операциям над матрицами.

Сложение, вычитание, деление и умножение матриц

С операциями сложения, вычитания и деления всё просто — эти операции выполняются поэлементно. К примеру:

    \[\begin{pmatrix}1 & 2\\1 & 2\\\end{pmatrix} + \begin{pmatrix}2 & 3\\2 & 3\\\end{pmatrix} = \begin{pmatrix}3 & 5\\3 & 5\\\end{pmatrix}\]

    \[\begin{pmatrix}1 & 2\\1 & 2\\\end{pmatrix} \cdot\begin{pmatrix}2 & 3\\2 & 3\\\end{pmatrix} =\begin{pmatrix}2 & 6\\2 & 6\\\end{pmatrix}\]

    \[\begin{pmatrix}4 & 2\\4 & 2\\\end{pmatrix} /\begin{pmatrix}2 & 2\\2 & 2\\\end{pmatrix} =\begin{pmatrix}2 & 1\\2 & 1\\\end{pmatrix}\]

Однако, линейная алгебра располагает матричное умножение иным способом: результатом умножения матрицы A_{p \cdot k} и матрицы B_{k \cdot n} будет матрица C_{p \cdot n}. Очень важно, чтобы кол-во столбцов у матрицы А совпадало с кол-вом строк матрицы В.

Умножение воспроизводится следующим образом: элемент c_{ij} матрицы, которую мы получаем в итоге, равен скалярному произведению i-ой строки матрицы А и j-ого столбца матрицы В.

Разберём пример:

    \[A = \begin{pmatrix}1 & 2 & 3\\4 & 5 & 6\\\end{pmatrix} B = \begin{pmatrix}1 & 2 \\3 & 4 \\5 & 6\\\end{pmatrix}\]

Первым элементом первой строки результирующей матрицы будет скалярное произведение первой строки матрицы А и первого столбца матрицы В, т.е. 1 \cdot 1 + 2 \cdot 3 + 3 \cdot 5 = 1 + 6 + 15 = 22.

Вторым элементом второй строки будет скалярное произведение второй строки матрицы А и второго столбца матрицы В, т.е. 4 \cdot 2 + 5 \cdot 4 + 6 \cdot 6 = 8 + 20 + 36 = 64.

Далее по аналогии мы получаем результат:

    \[A \times B = \begin{pmatrix}22 & 28\\49 & 64\\\end{pmatrix}\]

Теперь, когда мы изучили главную трудность на пути изучения матриц, перейдём к транспонированию матриц.

Транспонирование

Транспонирование матрицы — операция «переворачивания», которая заключается в одном очень простом правиле — строки матрицы становятся столбцами, а столбцы матрицы становятся строками.

Транспонированная матрица от матрицы A обозначается как A^T, и если А имеет форму m \times n, то A^T будет иметь размер n \times m. Разберём на практическом примере:

    \[A = \begin{pmatrix}1 & 2 & 3 & 4\\5 & 6 & 7 & 8\\9 & 10 & 11 & 12\\\end{pmatrix}\]


    \[A^T = \begin{pmatrix}1 & 5 & 9\\2 & 6 & 10\\3 & 7 & 11\\4 & 8 & 12\\\end{pmatrix}\]

Далее проследуем к понятию определителя матрицы.

Определитель матрицы

Определитель матрицы — скалярное значение, которое существует только у квадратной матрицы.

Если вдаваться в определения, то определителем матрицы называется сумма всех возможных произведений элементов матрицы с разных строк и с разных столбцов со знаком sign \sigma, где \sigma — перестановка, которая переводит номера строчек в номера столбцов. Звучит не очень, правда? Определение запутанное, однако по нему определитель матрицы никто не рассчитывает. Существует несколько методов, о которых мы поговорим в следующей статье, так как статья растянется в 3 раза и потеряет нить повествования.

Мы разберем два случая — определитель матрицы размером 2 \times 2 и матрицы размером 3 \times 3. Очевидно, что для матрицы из одного элемента определитель такой матрицы равен самому элементу.

Для матрицы размером 2 \times 2 определитель равен разности произведения элементов на главной диагонали и произведения элементов на побочной диагонали.

Определитель матрицы обозначается как матрица, заключенная в прямых скобках. Рассмотрим практический пример:

    \[\begin{vmatrix}4 & 3\\5 & 2\\\end{vmatrix} = 4 \cdot 2 - 3 \cdot 5 = 8 - 15 = -7\]

Для матрицы размером 3 \times 3 используют правило треугольника, но мы рассмотрим более простое правило:

    \[\begin{vmatrix}a_{11} & a_{12} & a_{13}\\a_{21} & a_{22} & a_{23}\\a_{31} & a_{32} & a{33}\\\end{vmatrix} = a_{11} \cdot \begin{vmatrix} a_{22} & a_{23} \\ a_{32} &a_{33}\end{vmatrix} - a_{12} \cdot \begin{vmatrix} a_{21} & a_{23} \\ a_{31} & a_{33}\end{vmatrix} + a_{13} \cdot \begin{vmatrix} a_{21} & a_{22} \\ a{31} & a_{32}\end{vmatrix} =\]


    \[= a_{11} \cdot (a_{22} \cdot a_{33} - a_{23} \cdot a_{32}) - a_{12} \cdot (a_{21} \cdot a_{32} - a_{22} \cdot a_{31} ) + a_{13} \cdot (a_{21} \cdot a_{32} - a_{22} \cdot a_{31})\]

Здесь прослеживается некая тенденция насчёт рекурсивного вычисления определителя, однако это долго и неэффективно.

Свойства определителя

  • определитель матрицы равен определителю транспонированной матрицы
  • если у матрицы имеется нулевая строка, её определитель равен 0
  • если матрица имеет две одинаковых строки, её определитель равен нулю
  • определитель матрицы не изменится, если к какой-либо строке добавить другую строчку, умноженную на число
  • определитель треугольной и диагональных матриц равен произведению элементов главной диагонали
  • если матрица имеет две пропорциональных строки, её определитель равен нулю
  • определитель единичной матрицы равен 1
  • определитель нулевой матрицы равен нулю

Основываясь на этих свойствах, был разработан метод Гаусса вычисления определителя, который мы обязательно рассмотрим в следующей статье, которая также затронет миноры, алгебраические дополнения и теорему Лапласа.

Программируем на Python

Для программирования матриц как массивов мы будем использовать NumPy (от англ. Numeric Python) — великолепный пакет для математических вычислений, написанный на С и Fortran.

Чтобы создать матрицу, воспользуемся функцией np.array, передав туда двумерный список с числами:

import numpy as np

A = np.array([[1, 2, 3], [4, 5, 6]])
A
-----------------------------
array([[1, 2, 3], 
       [4, 5, 6]])

Для создания единичной и нулевой матриц воспользуемся функциями np.eye и np.zeros соответственно.

A = np.eye(3)
A
----------------------------
array([[1., 0., 0.], 
       [0., 1., 0.], 
       [0., 0., 1.]])
A = np.zeros((3, 3))
A
---------------------------
array([[0., 0., 0.], 
       [0., 0., 0.], 
       [0., 0., 0.]])

Различие в аргументах очевидно, так как единичная матрица может быть только квадратной и в качестве аргумента принимает кол-во строк и столбцов. А np.zeros предназначена не только для создания двумерных массивов, поэтому мы в качестве аргумента передаем форму массива (в нашем случае размерность равна 3 \times 3).

Транспонировать матрицу (если вдаваться в детали, то правильнее сказать массив, но ради удобства мы будем называть двумерный массив матрицей) можно тремя способами:

  • у каждого массива есть атрибут .T , который хранит в себе транспонированную матрицу
  • у каждого массива есть метод .transpose(), который возвращает транспонированную матрицу
  • такой же функционал реализован в np.transpose()
A = np.array([[1, 2, 3], [4, 5, 6]])

A.T, A.transpose(), np.transpose(A)
-------------------------------------
(array([[1, 4], 
        [2, 5], 
        [3, 6]]), 
array([[1, 4], 
       [2, 5], 
       [3, 6]]), 
array([[1, 4], 
       [2, 5], 
       [3, 6]]))

Чтобы узнать размер матрицы, воспользуемся атрибутом .shape, который есть у каждого экземпляра класса np.ndarray.

A.shape, A.T.shape
-----------------------------------
((3, 2), (2, 3))

Чтобы вычислить определитель матрицы, воспользуемся функцией np.linalg.det:

A = np.array([[1, 2, 3], [0, 4, 5], [0, 0, 6]])
np.linalg.det(A)
-----------------------------------
23.999999999999993

Модуль np.linalg очень интересен. В нем «запрограммирована» почти вся линейная алгебра :)

Заключение

В этой статье мы рассмотрели базовые понятия линейной алгебры, с которых и начинается изучение этой дисциплины. Также мы реализовали транспонирование матриц и вычисление определителя матрицы с помощью NumPy. В следующей статье мы детально рассмотрим более сложные, но не менее интересные вещи.

На этом статья заканчивается. Если вас интересует не только линейная алгебра, но и математический анализ, советую прочитать «Производная. Базовые определения и термины«.

А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.