np.linalg: решение систем, обратная матрица, разложения

Урок — обзор практической линейной алгебры в NumPy: как решать системы, обращать матрицы и применять разложения, понимая, зачем они нужны.

np.linalg — подмодуль NumPy с операциями линейной алгебры (решение систем, обратная матрица, определитель, собственные значения, разложения), реализованными через быстрые библиотеки LAPACK.

Решение систем линейных уравнений: solve

Классическая задача: найти x в системе A·x = b, где A — матрица коэффициентов, b — вектор правых частей. Наивный путь — вычислить обратную матрицу и умножить: x = A⁻¹·b. Но это и медленнее, и менее точно. Правильный инструмент — np.linalg.solve(A, b), который решает систему напрямую (через разложение), не вычисляя обратную матрицу.

import numpy as np
# 2x + y = 5
#  x + 3y = 10
A = np.array([[2.0, 1.0],
              [1.0, 3.0]])
b = np.array([5.0, 10.0])

x = np.linalg.solve(A, b)
print(x)                    # [x, y]
print(np.allclose(A @ x, b))  # проверка: подставили обратно

Вывод:

[1. 3.]
True

Решение x=1, y=3 подтверждается обратной подстановкой. Запомните правило: почти никогда не нужно явно обращать матрицу для решения системы — используйте solve.

Линейная алгебра — почему это сердце ML

Стоит объяснить, почему линейной алгебре уделяется столько внимания именно в курсе по NumPy. Дело в том, что подавляющее большинство методов анализа данных и машинного обучения формулируются на её языке. Линейная регрессия — это решение системы (или метод наименьших квадратов) для нахождения коэффициентов. Метод главных компонент (PCA) — это SVD или собственное разложение ковариационной матрицы. Рекомендательные системы раскладывают матрицу «пользователи × товары». Нейросети — это каскады матричных умножений с нелинейностями между ними. Даже простое сглаживание, фильтрация сигналов, решение дифференциальных уравнений сводятся к матричным операциям. Поэтому, освоив np.linalg, вы получаете не узкий набор математических функций, а фундаментальный инструментарий, на котором стоит вся прикладная численная математика. И поскольку NumPy реализует эти операции через оптимизированные библиотеки, вы получаете производительность мирового уровня «бесплатно» — нужно лишь правильно сформулировать задачу. Именно эта связка «выразительный высокоуровневый интерфейс + быстрые библиотеки внизу» делает NumPy фундаментом научного Python.

Обратная матрица и определитель: inv, det

np.linalg.inv(A) даёт обратную матрицу (такую, что A·A⁻¹ = E), а np.linalg.det(A) — определитель. Определитель полезен как индикатор: если он близок к нулю, матрица вырождена (необратима), и система либо не имеет решения, либо имеет их бесконечно много. Попытка обратить такую матрицу даёт ошибку или огромные неустойчивые числа.

import numpy as np
A = np.array([[2.0, 1.0],
              [1.0, 3.0]])

print(np.linalg.det(A))      # определитель: 2*3 - 1*1 = 5
inv = np.linalg.inv(A)
print(inv)
print(np.allclose(A @ inv, np.eye(2)))  # A·A⁻¹ = E

Вывод:

5.000000000000001
[[ 0.6 -0.2]
 [-0.2  0.4]]
True

Определитель 2×2 считается элементарно — это видно на чистом Python (для интуиции):

def det2(m):
    return m[0][0] * m[1][1] - m[0][1] * m[1][0]

A = [[2, 1], [1, 3]]
print("det =", det2(A))
print("Вырождена?" , abs(det2(A)) < 1e-12)

Вывод:

det = 5
Вырождена? False

Что значит «обусловленность» и почему она важна

Определитель говорит, обратима ли матрица в принципе, но на практике важнее более тонкое понятие — обусловленность. Матрица может быть формально обратима (определитель не равен нулю), но «почти вырождена»: её определитель крошечный, и обращение усиливает любые погрешности входных данных в огромное число раз. Решение системы с плохо обусловленной матрицей численно неустойчиво — крошечная ошибка в правой части даёт огромную ошибку в ответе. Это коварно, потому что код не падает, а молча возвращает неправильный результат. Признак проблемы — очень большие или очень маленькие числа в обратной матрице, странно большие сингулярные числа, определитель, близкий к машинному нулю. Когда вы решаете системы или обращаете матрицы из реальных данных, полезно проверять обусловленность (через np.linalg.cond) и с осторожностью относиться к результатам, если она велика. Это объясняет, почему solve предпочтительнее inv: он не только быстрее, но и численно устойчивее, так как не вычисляет потенциально неустойчивую обратную матрицу явно.

Собственные значения и векторы: eig

Собственные значения и векторы — фундамент многих методов (PCA, анализ устойчивости, спектральные методы). Собственный вектор v матрицы A — это направление, которое матрица лишь масштабирует, не поворачивая: A·v = λ·v, где λ — собственное значение (коэффициент масштаба). np.linalg.eig возвращает массив собственных значений и матрицу собственных векторов (по столбцам).

import numpy as np
A = np.array([[2.0, 0.0],
              [0.0, 3.0]])

values, vectors = np.linalg.eig(A)
print("Собственные значения:", values)
print("Собственные векторы (по столбцам):")
print(vectors)

Вывод:

Собственные значения: [2. 3.]
Собственные векторы (по столбцам):
[[1. 0.]
 [0. 1.]]

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

Сингулярное разложение: svd

SVD (singular value decomposition) — пожалуй, самое полезное разложение в анализе данных. Любую матрицу A можно представить как A = U·Σ·Vᵀ, где Σ — диагональ из неотрицательных сингулярных чисел, упорядоченных по убыванию. SVD лежит в основе понижения размерности (PCA), сжатия, рекомендательных систем, вычисления псевдообратной матрицы. np.linalg.svd возвращает три части разложения.

import numpy as np
A = np.array([[3.0, 0.0],
              [0.0, 2.0],
              [0.0, 0.0]])

U, S, Vt = np.linalg.svd(A)
print("Сингулярные числа:", S)   # по убыванию
print("U.shape:", U.shape, "Vt.shape:", Vt.shape)

Вывод:

Сингулярные числа: [3. 2.]
U.shape: (3, 3) Vt.shape: (2, 2)

Сингулярные числа показывают «важность» каждого направления. Оставив только крупнейшие из них, можно приблизить матрицу меньшим объёмом данных — это и есть идея сжатия и PCA.

Геометрический смысл собственных значений и SVD

Чтобы линейная алгебра не воспринималась как набор магических функций, полезна геометрическая картина. Любую матрицу можно мыслить как преобразование пространства: она растягивает, сжимает, поворачивает и отражает векторы. Собственные векторы — это особые направления, которые преобразование не поворачивает, а только масштабирует, и собственное значение говорит, во сколько раз. SVD идёт дальше и раскладывает любое преобразование на три простых шага: поворот, масштабирование вдоль осей (на сингулярные числа) и ещё один поворот. Поэтому сингулярные числа показывают, насколько сильно преобразование «растягивает» пространство в каждом из главных направлений. Если одно из них близко к нулю, преобразование почти «схлопывает» соответствующее направление — это и есть геометрический смысл вырожденности. А в анализе данных крупнейшие сингулярные числа указывают направления наибольшей изменчивости данных, что и лежит в основе метода главных компонент (PCA): отбросив мелкие сингулярные числа, мы сохраняем главную структуру данных, теряя лишь «шум». Эта геометрическая интуиция делает абстрактные разложения осязаемыми.

Нормы: norm

np.linalg.norm вычисляет «длину» вектора или «величину» матрицы. Для вектора по умолчанию это евклидова норма (корень из суммы квадратов) — геометрическая длина. Нормы повсюду: измерение расстояний, регуляризация, нормировка векторов в единичную длину.

import numpy as np
v = np.array([3.0, 4.0])

print(np.linalg.norm(v))        # 5.0 — евклидова длина (теорема Пифагора)
print(np.linalg.norm(v, 1))     # 7.0 — сумма модулей (L1)
print(v / np.linalg.norm(v))    # нормировка в единичную длину

Вывод:

5.0
7.0
[0.6 0.8]

Евклидова норма [3, 4] равна 5 — это гипотенуза прямоугольного треугольника. Проверим формулу на чистом Python:

import math

def euclid_norm(v):
    return math.sqrt(sum(x * x for x in v))

print(euclid_norm([3, 4]))      # sqrt(9 + 16) = sqrt(25)
print(euclid_norm([1, 2, 2]))   # sqrt(1 + 4 + 4) = 3

Вывод:

5.0
3.0

Когда применять какое разложение

Линейная алгебра богата на инструменты, и новичку трудно понять, что когда брать. Дадим ориентир по задачам. Нужно решить систему уравнений (найти неизвестные по уравнениям) — solve. Нужно понять, обратима ли матрица или насколько она «хороша» — det, matrix_rank, cond. Нужно найти главные направления и понизить размерность данных — svd (основа PCA). Нужно проанализировать устойчивость системы, найти резонансы, диагонализовать преобразование — eig. Нужно измерить длину или расстояниеnorm. Нужно решить переопределённую систему методом наименьших квадратов (больше уравнений, чем неизвестных, как в линейной регрессии) — lstsq. Эта карта «задача → инструмент» важнее, чем заучивание сигнатур: понимая, какой вопрос вы задаёте данным, вы быстро находите нужную функцию. А детали вызова всегда можно посмотреть в документации. Линейная алгебра — это язык, на котором формулируются регрессия, понижение размерности, рекомендательные системы и многое в машинном обучении, поэтому понимание «что зачем» окупается во всей дальнейшей работе с данными.

Сводка np.linalg

ФункцияНазначение
solve(A, b)решить систему A·x = b (предпочтительно)
inv(A)обратная матрица
det(A)определитель (индикатор вырожденности)
eig(A)собственные значения и векторы
svd(A)сингулярное разложение
norm(x)норма (длина) вектора или матрицы
matrix_rank(A)ранг матрицы

Подводные камни

  • Решать систему через inv. inv(A) @ b медленнее и менее точно, чем solve(A, b).
  • Обращать вырожденную матрицу. Если det близок к нулю, inv даст мусор или ошибку. Проверяйте.
  • Точность float. Результаты содержат крошечные погрешности (например, det = 5.0000000000001). Сравнивайте через allclose.
  • Комплексные собственные значения. eig для некоторых матриц возвращает комплексные числа — это нормально, учитывайте dtype результата.

Лучшие практики

  • Для решения систем всегда используйте solve, а не явное обращение матрицы.
  • Проверяйте вырожденность через det или matrix_rank перед обращением.
  • Проверяйте корректность через обратную подстановку и np.allclose.
  • SVD — ваш универсальный инструмент для понижения размерности и устойчивых вычислений.

Главная мысль этого урока — не запомнить все функции, а понять, что NumPy даёт готовый, надёжный и быстрый инструментарий для всей классической линейной алгебры, опираясь на проверенные библиотеки LAPACK. Ваша роль — правильно сформулировать задачу (решить систему? разложить? измерить?) и выбрать соответствующую функцию, доверив численные тонкости и скорость библиотеке. Понимание геометрического смысла операций и численных подводных камней (обусловленность, точность float) превращает эти функции из «чёрных ящиков» в осмысленные инструменты.

Итог

  • solve решает системы напрямую — быстрее и точнее, чем через inv.
  • det сигнализирует о вырожденности; близкий к нулю определитель означает необратимость.
  • eig и svd раскрывают внутреннюю структуру матрицы; SVD — основа PCA и сжатия.
  • norm измеряет длину; евклидова норма — геометрическое расстояние.
Проверьте себя
1. Почему для решения системы A·x = b предпочтительнее np.linalg.solve(A, b), а не inv(A) @ b?
AПотому что solve работает только с квадратными матрицами
BПотому что solve решает систему напрямую через разложение — это быстрее и численно точнее, чем явное вычисление обратной матрицы
CПотому что inv не существует в NumPy
DПотому что solve всегда возвращает целые числа
2. О чём сигнализирует определитель матрицы, близкий к нулю?
AМатрица идеально обусловлена
BМатрица вырождена (необратима): попытка её обратить даст неустойчивый результат или ошибку
CВсе элементы матрицы равны нулю
DМатрица обязательно симметрична
3. Для чего на практике чаще всего применяют сингулярное разложение (SVD)?
AДля сортировки элементов матрицы
BДля понижения размерности и сжатия (PCA): крупнейшие сингулярные числа задают наиболее важные направления
CДля проверки, является ли матрица целочисленной
DДля умножения матриц быстрее обычного
Поддержать проект