Матрица: виды, операции и работа с ней в Python

Матрица: виды, операции и работа с ней в Python Полезное

Матрица — это прямоугольная таблица чисел, записанная в строки и столбцы; число на пересечении строки i и столбца j обозначают a_ij. Размер матрицы задают как m x n, где m — количество строк, n — количество столбцов. Ниже разберем виды матриц, шесть базовых операций (сложение, умножение на число, умножение матриц, транспонирование, определитель, обратную матрицу) и покажем каждую кодом на Python с библиотекой numpy и фактическим выводом.

Важно сразу развести два близких понятия. Матрица — это сама таблица чисел (объект). Определитель (детерминант) — это одно число, которое считается только для квадратной матрицы и характеризует ее. То есть определитель не «часть» матрицы, а результат операции над ней.

Из чего состоит матрица

Элемент a_ij стоит на пересечении i-й строки и j-го столбца. Нумерация в математике идет с 1, а в Python (numpy) — с 0, это частый источник путаницы при переносе формул в код.

Диагональ, идущая из левого верхнего угла в правый нижний, называется главной. Диагональ из правого верхнего в левый нижний — побочной. Оба понятия нужны для определителя матриц 2×2 и 3×3.

Запись размера читают в порядке «строки на столбцы»: матрица 2 x 3 — это две строки и три столбца, а не наоборот. От размера зависит, какие операции применимы: сложение требует полного совпадения размеров, а умножение — согласования по внутренней размерности. Поэтому перед любой операцией полезно проверить .shape.

Виды матриц

Классификация помогает понять, какие операции вообще применимы: например, определитель и обратная матрица определены только для квадратных.

Вид Условие Пример
Прямоугольная m != n 2 x 3
Квадратная m == n 3 x 3
Вектор-строка / вектор-столбец одна строка / один столбец 1 x n / m x 1
Нулевая все элементы = 0 любой размер
Диагональная вне главной диагонали нули квадратная
Единичная (E) на главной диагонали 1, остальное 0 квадратная
Симметричная a_ij == a_ji квадратная

Единичная матрица E играет для умножения матриц ту же роль, что число 1 для обычного умножения: A x E = A. Через нее определяют обратную матрицу.

Сложение и вычитание

Складывать и вычитать можно только матрицы одинакового размера: операция идет поэлементно, результат — матрица того же размера.

import numpy as np

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

print(A + B)
# [[ 6  8]
#  [10 12]]
print(B - A)
# [[4 4]
#  [4 4]]

Если размеры не совпадают, честнее получить ошибку, чем молчаливый неверный результат. Учтите: у numpy есть broadcasting, и он может «растянуть» несовпадающие формы вместо ошибки — для строгого поэлементного сложения проверяйте, что A.shape == B.shape.

Умножение на число

Каждый элемент умножается на скаляр. Размер не меняется.

print(3 * A)
# [[ 3  6]
#  [ 9 12]]

Умножение матриц

Здесь скрыта самая частая ошибка новичков в numpy. Оператор * — это поэлементное умножение (произведение Адамара), а не матричное. Матричное умножение — это оператор @ (или np.dot). Это разные операции с разным результатом:

print(A * B)   # поэлементно
# [[ 5 12]
#  [21 32]]
print(A @ B)   # матричное умножение
# [[19 22]
#  [43 50]]

Матричное умножение согласовано по размерам не всегда: A x B определено, только если число столбцов A равно числу строк B. При этом (m x k) @ (k x n) дает матрицу (m x n). Элемент результата в позиции (i, j) — сумма произведений i-й строки первой матрицы на j-й столбец второй.

Разберем элемент результата вручную на A @ B. Верхний левый элемент — это первая строка A, умноженная на первый столбец B: 15 + 27 = 5 + 14 = 19. Именно 19 стоит в позиции (0, 0) в выводе выше. Так же считается каждый из оставшихся элементов.

C = np.array([[1, 2, 3], [4, 5, 6]])       # 2 x 3
D = np.array([[7, 8], [9, 10], [11, 12]])  # 3 x 2

print(C @ D)
# [[ 58  64]
#  [139 154]]
print(C.shape, D.shape, (C @ D).shape)
# (2, 3) (3, 2) (2, 2)

Отсюда следствие: в общем случае A @ B не равно B @ A — матричное умножение некоммутативно (переставлять множители нельзя).

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

При транспонировании строки и столбцы меняются местами: элемент a_ij переходит в позицию a_ji, размер m x n становится n x m. В numpy это атрибут .T.

print(C.T)
# [[1 4]
#  [2 5]
#  [3 6]]
print(C.T.shape)
# (3, 2)

Определитель (детерминант)

Определитель — это число, определенное только для квадратной матрицы. Для матрицы 2×2 он равен разности произведений главной и побочной диагоналей: det = a11 * a22 — a12 * a21.

Для матрицы 3×3 работает правило треугольников (правило Саррюса): к сумме трех произведений вдоль главной диагонали и параллельных ей троек добавляют со знаком минус три произведения вдоль побочной диагонали. Для матриц 4×4 и крупнее формулы вручную громоздки, на практике их считают программно (через разложение, а не перебором).

A = np.array([[1, 2], [3, 4]])
print(np.linalg.det(A))
# -2.0000000000000004

Ожидаемое точное значение — ровно -2 (14 — 23). Дробный «хвост» -2.0000000000000004 — это не ошибка расчета, а погрешность чисел с плавающей точкой: np.linalg.det считает определитель не по школьной формуле, а через LU-разложение в float. Если нужно целое точное значение, результат стоит округлить: round(np.linalg.det(A)) даст -2. Для матрицы 3×3 в этом примере значение получается точным:

M = np.array([[2, 1, 1], [1, 3, 2], [1, 0, 0]])
print(np.linalg.det(M))
# -1.0

Определитель отвечает на ключевой вопрос: обратима ли матрица. Если det = 0, матрица вырожденная и обратной не имеет.

Обратная матрица

Обратная матрица A^-1 — это такая матрица, что A @ A^-1 = A^-1 @ A = E (единичная). Она существует только для квадратной невырожденной матрицы, то есть когда det(A) не равен нулю (вырожденная — когда det = 0).

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

A = np.array([[1, 2], [3, 4]])
A_inv = np.linalg.inv(A)
print(A_inv)
# [[-2.   1. ]
#  [ 1.5 -0.5]]
print(A @ A_inv)
# [[1.0000000e+00 0.0000000e+00]
#  [8.8817842e-16 1.0000000e+00]]

Проверка A @ A^-1 дает почти единичную матрицу: вне диагонали стоит 8.88e-16 вместо ровного нуля — снова эффект плавающей точки, а не ошибка. Для сравнения матриц с такой погрешностью используйте np.allclose(A @ A_inv, np.eye(2)), а не строгое равенство.

Если попробовать обратить вырожденную матрицу, numpy честно сообщит о проблеме — у нее нет обратной:

S = np.array([[1, 2], [2, 4]])
print(np.linalg.det(S))
# 0.0
# np.linalg.inv(S) -> LinAlgError: Singular matrix

Выводы

  • Матрица — таблица чисел размера m x n; определитель и обратная матрица существуют только для квадратных.
  • Сложение и вычитание идут поэлементно и требуют одинакового размера; умножение на число тоже поэлементно.
  • В numpy * — это поэлементное умножение, а матричное — @; для @ число столбцов первой матрицы должно равняться числу строк второй, и порядок множителей менять нельзя.
  • Определитель — одно число: если он равен нулю, матрица вырожденная и обратной не имеет.
  • Результаты np.linalg.det и inv содержат погрешность плавающей точки; сравнивать через np.allclose, при необходимости округлять.

Где применяется / связь с практикой

Матрицы — рабочий инструмент в IT, а не только школьная тема. В компьютерной графике и игровых движках повороты, масштаб и сдвиг объектов задаются умножением координат на матрицу преобразования. Ниже — поворот точки (1, 0) на 90 градусов:

import numpy as np, math

theta = math.pi / 2
R = np.array([[math.cos(theta), -math.sin(theta)],
              [math.sin(theta),  math.cos(theta)]])
print(np.round(R @ np.array([1, 0]), 8))
# [0. 1.]

В машинном обучении и анализе данных выборка хранится как матрица «объекты x признаки», а обучение линейных моделей и нейросетей — это операции над матрицами. Решение систем линейных уравнений (например, 2x + y = 5 и x + 3y = 10) тоже сводится к матрицам:

K = np.array([[2, 1], [1, 3]])
b = np.array([5, 10])
print(np.linalg.solve(K, b))
# [1. 3.]  ->  x = 1, y = 3

Освойте тему на практике

Матричная запись встречается в графике, компьютерном зрении, ML, обработке сигналов и экономических расчетах. Разобраться с линейной алгеброй как основой этих задач помогает курс Прикладная математика: там теория идет вместе с практикой на Python. Посмотреть формат и уровень занятий без оплаты можно на открытых уроках.

FAQ

Чем отличается * от @ в numpy?
* перемножает матрицы поэлементно (нужны одинаковые размеры), @ выполняет матричное умножение (нужно согласование: столбцы первой = строки второй). Результаты разные, это частый источник ошибок.

Почему np.linalg.det возвращает не целое число, а вроде -2.0000000000000004?
Функция считает определитель через LU-разложение в числах с плавающей точкой, поэтому возможна крошечная погрешность. Для целочисленного ответа округлите результат через round().

Как понять, что у матрицы нет обратной?
Посчитайте определитель: если он равен нулю, матрица вырожденная и обратной не имеет. Попытка вызвать np.linalg.inv для нее вызовет ошибку LinAlgError: Singular matrix.

OTUS Журнал
Скидка 5% 12-19 октября на курсы (popup)
Бесплатные открытые уроки (поп-ап)