4.6. Определитель

Автор: Кирилл Долматов

В этой статье мы разберём понятие определителя. Обычно его рассматривают очень формально — как некоторую числовую характеристику матрицы.

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

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

Рисунок 4.5.1: Изменение пространства и площади до деформации и после

Изменение пространства и площади до деформации и после

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

Деформация пространства

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

Линейная деформация векторов

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

Умножим вектор на матрицу слева и получим новый вектор :

Деформация вектора v1 под действием умножения матрицы A1

На рисунке выше мы видим, что исходный вектор повернулся на 270 градусов под действием умножения матрицы . Данная матрица — это частный случай матрицы поворота, которая в двумерном пространстве имеет следующий вид:

Рассмотрим теперь другой наглядный пример действия матричного умножения на исходный вектор. В качестве матрицы возьмем :

Умножим вектор на матрицу слева и получим новый вектор :

Деформация вектора v1 под действием умножения матрицы A2

На рисунке выше мы видим, что исходный вектор трансформировался под действием умножения матрицы : развернулся по обоим осям и растянулся в два раза по оси абсцисс. Данная матрица — это частный случай матрицы растяжения, которая в двумерном пространстве имеет следующий вид:

Мы рассмотрели как с помощью матрицы можно задавать деформацию вектора, а также увидели, какие есть наглядные примеры таких деформаций. Давайте пойдём дальше и попробуем понять, как матрица действует на всё множество векторов, то есть на векторное пространство в целом.

Линейная деформация пространства

Зная, как матрица действует на отдельные векторы, теперь мы можем проследить, как деформируется всё пространство. Для этого нам нужно построить все возможные векторы из данного пространства и к каждому из них применить деформацию, задаваемую некоторой матрицей.

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

  • Шаг 1: представим каждый вектор пространства не как стрелку, а как точку, которая соответствует конечной точке вектора.
  • Шаг 2: множество всех таких точек можно представить как новую координатную сетку, наложенную на исходную.
  • Шаг 3: применим деформацию ко всем векторам пространства, тогда каждая точка будет двигаться в какую-то другую точку на плоскости, а новая координатная сетка — деформироваться. При этом также сохраним и исходную координатную сетку, чтобы понять, как подействовала деформация относительно начального положения.

Рисунок 4.5.4: Схематичная визуализация деформации векторного пространства

Схематичная визуализация деформации векторного пространства

Таким образом, применяя линейное преобразование ко всем векторам пространства, мы не просто изменяем отдельные точки, а получаем новое представление о том, как преобразуется всё пространство: его форма, ориентация и масштаб. Это позволяет нам перейти от локального понимания действия матрицы к глобальному — увидеть, как матрица изменяет само полотно пространства.

Примечание

Важно: В линейной алгебре множество допустимых деформаций не является произвольным: все они должны удовлетворять определённым свойствам. Более строго эти свойства будут сформулированы при рассмотрении линейных отображений.

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

  1. Все линии координатной сетки остаются параллельными и на равных расстояниях.
  2. Начало координат зафиксировано и не меняется.

Чтобы вы смогли более наглядно пронаблюдать, как изменяется заданный вектор и всё пространство в целом под действием деформации, мы подготовили код с интерактивной визуализацией с помощью библиотек plotly и streamlit.

Код для визуализации линейной деформации двумерного пространства

Для запуска:

  1. Создайте .py файл, например app.py .
  2. Вставьте туда написанный ниже код.
  3. Установите необходимые зависимости.
  4. Запустите приложение командой: streamlit run app.py
from typing import Any
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import streamlit as st


def create_figure(a: float, b: float, c: float, d: float, 
                  vector: np.ndarray, grid_range: int = 5, grid_step: int = 1
                 ) -> go.Figure:
    """
    Создаёт фигуру с двумя подграфиками (1x2):

    1) Исходное пространство с заданным вектором v.
    2) Деформированное пространство с образом вектора v'.

    Параметры:
        a, b, c, d (float): Элементы матрицы A = [[a, b], [c, d]].
        vector (np.ndarray): Исходный вектор (длина 2).
        grid_range (int): Диапазон значений координатной сетки.
        grid_step (int): Шаг между линиями сетки.

    Возвращает:
        go.Figure: Фигура с двумя подграфиками.
    """
    xs: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
    ys: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
    matrix: np.ndarray = np.array([[a, b], [c, d]])

    fig: go.Figure = make_subplots(
        rows=1,
        cols=2,
        subplot_titles=["Исходное пространство", "Деформированное пространство"],
        horizontal_spacing=0.02
    )

    def add_vector_annotation(x: float, y: float, xref: str, yref: str, 
                              color: str, label: str,
                              row: int = 1, col: int = 1, 
                              text_xshift: int = 10, text_yshift: int = -10) -> None:
        """Добавляет стрелку и подпись в указанном подграфике."""
        fig.add_annotation(
            x=x, y=y,
            ax=0, ay=0,
            xref=xref, yref=yref,
            axref=xref, ayref=yref,
            showarrow=True,
            arrowhead=3,
            arrowcolor=color,
            arrowsize=1,
            arrowwidth=2,
            text="",
            row=row,
            col=col
        )
        fig.add_annotation(
            x=x, y=y,
            xref=xref, yref=yref,
            text=label,
            showarrow=False,
            xshift=text_xshift,
            yshift=text_yshift,
            row=row,
            col=col
        )

    # Исходное пространство
    for x_val in xs:
        fig.add_trace(
            go.Scatter(
                x=[x_val, x_val],
                y=[-grid_range, grid_range],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=1
        )
    for y_val in ys:
        fig.add_trace(
            go.Scatter(
                x=[-grid_range, grid_range],
                y=[y_val, y_val],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=1
        )
    add_vector_annotation(1, 0, "x", "y", "red", "i", row=1, col=1)
    add_vector_annotation(0, 1, "x", "y", "green", "j", row=1, col=1)
    add_vector_annotation(vector[0], vector[1], "x", "y", "blue", "v", row=1, col=1)

    # Деформированное пространство
    for x_val in xs:
        line_y = np.linspace(-grid_range, grid_range, 100)
        original_points = np.array([[x_val, y_val] for y_val in line_y])
        transformed = original_points @ matrix.T
        fig.add_trace(
            go.Scatter(
                x=transformed[:, 0],
                y=transformed[:, 1],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=2
        )
    for y_val in ys:
        line_x = np.linspace(-grid_range, grid_range, 100)
        original_points = np.array([[x_val, y_val] for x_val in line_x])
        transformed = original_points @ matrix.T
        fig.add_trace(
            go.Scatter(
                x=transformed[:, 0],
                y=transformed[:, 1],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=2
        )

    v_prime: np.ndarray = matrix @ vector
    add_vector_annotation(v_prime[0], v_prime[1], "x2", "y2", "blue", "v'", row=1, col=2)
    i_prime: np.ndarray = matrix @ np.array([1, 0])
    j_prime: np.ndarray = matrix @ np.array([0, 1])
    add_vector_annotation(i_prime[0], i_prime[1], "x2", "y2", "red", "i'", row=1, col=2)
    add_vector_annotation(j_prime[0], j_prime[1], "x2", "y2", "green", "j'", row=1, col=2)

    fig.update_layout(
        width=1000,
        height=500,
        margin=dict(l=0, r=0, t=50, b=50),
        xaxis=dict(
            domain=[0, 0.45],
            range=[-grid_range, grid_range],
            scaleanchor="y",
            scaleratio=1,
            constrain="domain"
        ),
        yaxis=dict(
            domain=[0, 1],
            range=[-grid_range, grid_range],
            scaleanchor="x",
            scaleratio=1,
            constrain="domain"
        ),
        xaxis2=dict(
            domain=[0.55, 1],
            range=[-grid_range, grid_range],
            scaleanchor="y2",
            scaleratio=1,
            constrain="domain"
        ),
        yaxis2=dict(
            domain=[0, 1],
            range=[-grid_range, grid_range],
            scaleanchor="x2",
            scaleratio=1,
            constrain="domain"
        )
    )

    return fig

def main() -> None:
    """Главная функция приложения Streamlit."""
    st.title("Интерактивная визуализация линейной деформации")

    st.subheader("Коэффициенты матрицы линейного отображения")
    col1, col2 = st.columns(2)
    a: float = col1.number_input("a:", value=1.0, step=0.1)
    b: float = col2.number_input("b:", value=0.0, step=0.1)
    col3, col4 = st.columns(2)
    c: float = col3.number_input("c:", value=0.0, step=0.1)
    d: float = col4.number_input("d:", value=1.0, step=0.1)

    st.subheader("Координаты исходного вектора")
    v1: float = st.number_input("v₁:", value=2.0, step=0.1)
    v2: float = st.number_input("v₂:", value=1.0, step=0.1)
    vector: np.ndarray = np.array([v1, v2])

    fig: go.Figure = create_figure(a, b, c, d, vector)
    st.plotly_chart(fig, use_container_width=True)

if __name__ == '__main__':
    main()
    

Итак, теперь у нас есть визуальная интуиция, которая поможет лучше понять линейные деформации: как они изменяют и отдельные векторы, и всё пространство в целом. Таким образом, у нас есть все необходимые знания, чтобы понять, какой смысл скрывается за определителем.

Геометрический смысл определителя

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

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

Изменение площади пространства при деформации

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

Этот коэффициент по модулю как раз и будет равен определителю. Чтобы его найти, давайте рассмотрим единичный квадрат, натянутый на векторы нашего базиса и :

Рисунок 4.5.5: Единичный квадрат, натянутый на стандартный базис

Единичный квадрат, натянутый на стандартный базис

Изменение площади данного квадрата будет отражать изменение площади любой области и всего пространства в целом. Пространство можно полностью разложить на непересекающиеся единичные квадраты (как клетки бесконечной клетчатой сетки). Так как линейная деформация изменяет площадь одного такого квадрата, то благодаря линейности преобразования площадь каждого квадрата изменится одинаково.

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

Теперь давайте рассмотрим несколько примеров деформаций и проанализируем, как меняется площадь.

Растяжение пространства

В качестве примера возьмём следующую матрицу :

Мы ранее уже рассмотрели матрицы растяжения и увидели, что происходит с векторами при линейной деформации пространства. Поэтому найдём новые координаты наших базисных векторов и :

Теперь отобразим исходный единичный квадрат и полученный прямоугольник:

Рисунок 4.5.6: Растяжение пространства

Растяжение пространства

При линейной деформации пространства исходный единичный квадрат может перейти в параллелограмм, отрезок или точку. Напомним формулу для подсчёта площади параллелограмма: , где — сторона параллелограмма, а  — высота, проведённая к этой стороне. Тогда после деформации пространства матрицей коэффициент изменения площади равен: .

Отражение пространства

Теперь добавим к рассмотренному выше растяжению пространства его отражение. Возьмём следующую матрицу :

Найдём новые координаты наших базисных векторов и :

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

Рисунок 4.5.7: Поворот пространства

Поворот пространства

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

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

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

Скос пространства

Возьмём следующую матрицу :

Найдём новые координаты наших базисных векторов и :

Теперь отобразим исходный единичный квадрат, а также полученный параллелограмм:

Рисунок 4.5.8: Скос пространства

Скос пространства

В данном случае легко заметить, что изменения площади не произошло, и после деформации она по-прежнему равна .

  • Связь с нормализующими потоками в глубинном обучении В задачах глубинного обучения можно отметить интересную связку с определителем. В современных генеративных моделях, таких как нормализующие потоки (например, RealNVP или Glow), используется формула изменения плотности:

где — якобиан одного слоя преобразования. Когда преобразование сохраняет объём (то есть ), оно становится проще и эффективнее в вычислениях. Такие преобразования называются volume-preserving. Примеры: поворот, скос, ортогональные матрицы — всё это деформации, при которых определитель равен . Они активно используются в слоях типа affine coupling или -свёртках, параметризуемых с помощью LU-разложений. Например, в PyTorch логарифм определителя можно быстро посчитать так:

P, L, U = torch.linalg.lu(W)  # W — матрица свёртки
logdet = torch.sum(torch.log(torch.abs(torch.diag(U))))

Примечание

Важно: А значит, снова срабатывает хорошо знакомое правило: если матрица треугольная — определитель равен произведению диагонали.

Сплющивание пространства

Возьмём следующую матрицу :

Найдём новые координаты наших базисных векторов и :

Теперь отобразим исходный единичный квадрат и полученный отрезок:

Рисунок 4.5.9: Сплющивание пространства

Сплющивание пространства

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

Пояснение

Выше мы везде считали определитель как изменение площади единичного квадрата при деформации. Это было сделано для лучшего визуального понимания. В действительности мы можем считать определитель как ориентированную площадь (учитывая знак, который показывает, сохраняется или меняется ориентация) параллелограмма, натянутого на векторы, которые уложены как векторы-столбцы в матрицу , задающую деформацию:

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

Чтобы вы смогли более наглядно увидеть, как под действием деформации изменяется пространство и базисные векторы, а также чему равен определитель, мы подготовили код с интерактивной визуализацией посредством библиотек plotly и streamlit.

Код для визуализации линейной деформации двумерного пространства и изменения площади

Алгоритм запуска кода тот же.

import streamlit as st
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from typing import Any

def create_figure(a: float, b: float, c: float, d: float, grid_range: int = 5, grid_step: int = 1
                 ) -> go.Figure:
    """
    Создаёт фигуру с двумя подграфиками (1x2):

    1) Исходное пространство и единичный квадрат.
    2) Деформированное пространство с образом единичного квадрата.

    Параметры:
        a, b, c, d (float): Элементы матрицы A = [[a, b], [c, d]].
        grid_range (int): Диапазон значений координатной сетки.
        grid_step (int): Шаг между линиями сетки.

    Возвращает:
        go.Figure: Фигура с двумя подграфиками.
    """
    # Создаём сетку координат
    xs: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
    ys: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
    matrix: np.ndarray = np.array([[a, b], [c, d]])

    # Создаём подграфики
    fig: go.Figure = make_subplots(
        rows=1,
        cols=2,
        subplot_titles=["Исходное пространство", "Деформированное пространство"],
        horizontal_spacing=0.02
    )

    def add_vector_annotation(x: float, y: float, xref: str, yref: str,
                              color: str, label: str,
                              row: int = 1, col: int = 1,
                              text_xshift: int = 10, text_yshift: int = -10) -> None:
        """
        Добавляет на фигуру стрелку (от (0,0) до (x,y)) и текстовую подпись рядом.
        """
        # Стрелка
        fig.add_annotation(
            x=x, y=y,
            ax=0, ay=0,
            xref=xref, yref=yref,
            axref=xref, ayref=yref,
            showarrow=True,
            arrowhead=3,
            arrowcolor=color,
            arrowsize=1.0,
            arrowwidth=2,
            text="",
            row=row,
            col=col
        )
        # Подпись вектора
        fig.add_annotation(
            x=x, y=y,
            xref=xref, yref=yref,
            text=label,
            showarrow=False,
            xshift=text_xshift,
            yshift=text_yshift,
            row=row,
            col=col
        )

    # -----------------------------
    # 1) Исходное пространство
    # -----------------------------
    # Рисуем координатную сетку
    for x_val in xs:
        fig.add_trace(
            go.Scatter(
                x=[x_val, x_val],
                y=[-grid_range, grid_range],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=1
        )
    for y_val in ys:
        fig.add_trace(
            go.Scatter(
                x=[-grid_range, grid_range],
                y=[y_val, y_val],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=1
        )

    # Рисуем базис: i, j
    add_vector_annotation(1, 0, "x", "y", "red", "i", row=1, col=1)
    add_vector_annotation(0, 1, "x", "y", "green", "j", row=1, col=1)

    # Рисуем единичный квадрат (параллелограмм) в исходном пространстве
    square: np.ndarray = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
    fig.add_trace(
        go.Scatter(
            x=square[:, 0],
            y=square[:, 1],
            mode='lines',
            fill='toself',
            fillcolor='rgba(255,165,0,0.15)',  # оранжевый, прозрачность 15%
            line=dict(color='orange'),
            showlegend=False
        ),
        row=1, col=1
    )

    fig.update_xaxes(range=[-grid_range, grid_range], scaleanchor="y", scaleratio=1, row=1, col=1)
    fig.update_yaxes(range=[-grid_range, grid_range], row=1, col=1)

    # -----------------------------
    # 2) Деформированное пространство
    # -----------------------------
    for x_val in xs:
        line_y = np.linspace(-grid_range, grid_range, 100)
        original_points = np.array([[x_val, y_val] for y_val in line_y])
        transformed = original_points @ matrix.T
        fig.add_trace(
            go.Scatter(
                x=transformed[:, 0],
                y=transformed[:, 1],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=2
        )
    for y_val in ys:
        line_x = np.linspace(-grid_range, grid_range, 100)
        original_points = np.array([[x_val, y_val] for x_val in line_x])
        transformed = original_points @ matrix.T
        fig.add_trace(
            go.Scatter(
                x=transformed[:, 0],
                y=transformed[:, 1],
                mode='lines',
                line=dict(color='lightgray', dash='dash'),
                showlegend=False
            ),
            row=1, col=2
        )

    # Образы базиса: i -> i' и j -> j'
    i_prime: np.ndarray = matrix @ np.array([1, 0])
    j_prime: np.ndarray = matrix @ np.array([0, 1])
    add_vector_annotation(i_prime[0], i_prime[1], "x2", "y2", "red", "i'", row=1, col=2)
    add_vector_annotation(j_prime[0], j_prime[1], "x2", "y2", "green", "j'", row=1, col=2)

    # Рисуем образ единичного квадрата в деформированном пространстве
    square_transformed: np.ndarray = square @ matrix.T
    fig.add_trace(
        go.Scatter(
            x=square_transformed[:, 0],
            y=square_transformed[:, 1],
            mode='lines',
            fill='toself',
            fillcolor='rgba(255,165,0,0.15)',
            line=dict(color='orange'),
            showlegend=False
        ),
        row=1, col=2
    )

    # Настройка осей и размеров подграфиков
    fig.update_layout(
        width=1000,
        height=500,
        margin=dict(l=0, r=0, t=50, b=50),
        xaxis=dict(
            domain=[0, 0.45],
            range=[-grid_range, grid_range],
            scaleanchor="y",
            scaleratio=1,
            constrain="domain"
        ),
        yaxis=dict(
            domain=[0, 1],
            range=[-grid_range, grid_range],
            scaleanchor="x",
            scaleratio=1,
            constrain="domain"
        ),
        xaxis2=dict(
            domain=[0.55, 1],
            range=[-grid_range, grid_range],
            scaleanchor="y2",
            scaleratio=1,
            constrain="domain"
        ),
        yaxis2=dict(
            domain=[0, 1],
            range=[-grid_range, grid_range],
            scaleanchor="x2",
            scaleratio=1,
            constrain="domain"
        )
    )

    return fig

def main() -> None:
    """Главная функция приложения Streamlit."""
    st.title("Интерактивная визуализация линейной деформации")

    st.subheader("Коэффициенты матрицы линейного отображения")
    col1, col2 = st.columns(2)
    a: float = col1.number_input("a:", value=1.0, step=0.1)
    b: float = col2.number_input("b:", value=0.0, step=0.1)
    col3, col4 = st.columns(2)
    c: float = col3.number_input("c:", value=0.0, step=0.1)
    d: float = col4.number_input("d:", value=1.0, step=0.1)

    # Вычисляем площадь (|det A|) и выводим в отдельном блоке
    matrix: np.ndarray = np.array([[a, b], [c, d]])
    area_scale: float = np.linalg.det(matrix)
    st.markdown(f"**det A:** {area_scale:.2f}")
    st.markdown(f"**Изменение площади (|det A|):** {abs(area_scale):.2f}")
    
    fig: go.Figure = create_figure(a, b, c, d)
    st.plotly_chart(fig, use_container_width=True)

if __name__ == '__main__':
    main()
    

Теперь давайте посмотрим на результат кода выше.

Пусть дана матрица , такая что:

Тогда деформация пространства под действием линейного отображения, заданного матрицей , будет выглядеть следующим образом:

Рисунок 4.5.10: Анимация деформации пространства

Анимация деформации пространства

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

Примечание

Важно: Определитель — это -мерный ориентированный объём, натянутый на векторы матрицы, задающей линейное отображение (деформацию пространства).

Связь с анализом данных: важность определителя в PCA

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

Метод главных компонент (Principal Component Analysis, PCA) решает эту задачу. Он находит новое ортонормированное базисное пространство (новые оси), в котором можно выразить данные, и при этом:

  • эти новые оси упорядочены по убыванию дисперсии — то есть первая главная компонента направлена туда, где у данных наибольший разброс;
  • если оставить только первые компонент (осей), то в проекции на это ‑мерное подпространство данные сохраняют максимум своей изменчивости.

Но есть и геометрический взгляд на PCA: если — ковариационная матрица наших данных (то есть матрица, которая описывает, как признаки расползлись в пространстве), а  — матрица из  ортонормированных векторов (направлений, вдоль которых мы хотим проектировать данные), то PCA ищет такие векторы , чтобы максимизировать объём проекции точек данных. Этот объём можно выразить через определитель:

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

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

Это поможет нам не только закрепить интуицию, но и понять, как определитель вычисляется формально, особенно в случае размерностей выше трёх, где визуализация уже не столь очевидна.

Формальное определение

Для квадратной матрицы её определитель обозначается как (от англ. determinant) и вычисляется по формуле:

где:

  • — перестановка;
  • — множество всех перестановок на  -элементном множестве;
  • — знак перестановки ;
  • — элемент матрицы , выбираемый из  -й строки и  -го столбца.

Как вы видите, в формуле используются перестановки. Разбирать подробно, что это такое, сейчас мы не будем. Но для контекста дадим формальное определение, покажем, как их можно задавать и как задаётся знак перестановки.

Перестановки

Пусть — конечное множество из  занумерованных элементов. Перестановкой называется биективное отображение .

Как задавать перестановки

С помощью таблицы значений, где под каждым элементом пишется его образ :

Знак перестановки

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

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

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

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

Невырожденные матрицы

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

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

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

  1. Система линейных уравнений имеет только нулевое решение .
  2. Матрица представляется в виде произведения матриц элементарных преобразований.
  3. Матрица обратима.
  4. Определитель матрицы отличен от нуля.

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

Вычисление в малых размерностях

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

Для пространств малой размерности существуют явные формулы (которые выводятся из формулы, приведённой в определении определителя), и по ним легко вычислять определитель. Давайте их рассмотрим.

  1. Если :

  1. Если :

Схематично данную формулу можно изобразить так:

Рисунок 4.5.11: Схематичная формула для определителя в двумерном пространстве

Схематичная формула для определителя в двумерном пространстве

Пример:

  1. Если :

Схематично данную формулу можно изобразить так:

Рисунок 4.5.12: Схематичная формула для определителя в трёхмерном пространстве

Схематичная формула для определителя в трёхмерном пространстве

Пример:

Здесь полезно, глядя на формулы вычисления определителя, задуматься о виде матриц, для которых его вычисление будет намного проще. Рассмотрим верхне- и нижнетреугольные матрицы.

Для них определитель будет вычисляться следующим образом:

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

Как вы думаете, чему равен определитель для каждой из матриц, задающих элементарные преобразования?

Ответ (не открывайте сразу, сначала подумайте сами!)
  1. Прибавление одной строки к другой с множителем ( ):
  2. Перестановка двух строк:
  3. Умножение строки на ненулевую константу :

Давайте рассмотрим свойства определителя, чтобы понять, какие операции можно совершать с исходной матрицей, не повлияв на значение определителя.

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

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

Пусть , тогда на неё можно смотреть как на набор из  векторов, уложенных как вектор-столбцы, то есть:

Тогда определитель можно рассматривать как функцию от столбцов матрицы , то есть:

И можно сформулировать следующие свойства:

  1. Линейность определителя по каждому столбцу:

  1. При изменении позиции столбца меняется знак определителя:

  1. Если у матрицы есть два одинаковых столбца, то определитель равен нулю:

  1. Если один из столбцов умножить на одно и то же число, то и весь определитель умножится на это же число:

  1. Если к одному столбцу матрицы прибавить другой, умноженный на коэффициент, то определитель не изменится:

  1. Все сформулированные выше свойства для столбцов также верны и для строк, так как:

  1. Мультипликативность определителя:

  1. Определитель от обратной матрицы:

Как видите, на практике может быть эффективно приводить исходную матрицу элементарными преобразованиями к треугольному виду, попутно запоминая некоторые коэффициенты, которые можно вынести за определитель. Давайте рассмотрим ещё один способ вычисления определителя через разложение по строке (столбцу).

Миноры и алгебраические дополнения

Мы рассмотрели, как можно вычислить определитель через явную формулу, а также раскрыли её для случаев малой размерности. Давайте рассмотрим ещё один способ подсчёта определителя.

Для этого введём ряд дополнительных понятий. Пусть — некоторая матрица с элементом . Рассмотрим матрицу , полученную путем вычёркивания строки и  столбца из матрицы .

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

Определитель матрицы обозначается и называется минором матрицы или -минором. Число называется алгебраическим дополнением элемента или -алгебраическим дополнением матрицы .

Также выделяют присоединённую матрицу для , которая определяется так:

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

Присоединённая матрица в ML

Если размер матрицы небольшой () и требуется вычислить производную по обратной матрице, можно использовать следующую формулу:

Такой подход используется в DL-библиотеках (например, JAX, PyTorch) при написании кастомных градиентов для маленьких матриц , особенно в задачах обучения свёрточных фильтров с фиксированным размером ядра (например, в 2D SPP‑модулях). Это быстрее и точнее, чем использовать универсальный torch.inverse().

Теперь, когда у нас есть представление о минорах и алгебраических дополнениях, мы можем использовать их для альтернативного способа вычисления определителя.

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

Разложение по строке (столбцу)

Пусть дана некоторая матрица , тогда:

  • Для любой -строки верно разложение:

  • Для любого -столбца верно разложение:

Давайте рассмотрим конкретный пример, разложим по второму столбцу:

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

Вычисление в Python

Существует несколько популярных библиотек, с помощью которых можно найти определитель матрицы. Ниже кратко сравним их и приведём примеры использования.

Название Применение
NumPy Это наиболее универсальный и часто используемый инструмент для работы с числами на CPU. Функция numpy.linalg.det() оптимизирована за счёт использования библиотеки LAPACK, что обеспечивает высокую скорость и низкий расход памяти при работе с матрицами, особенно если их размер невелик. Если вы не используете специализированные функции глубокого обучения или символьные вычисления, то numpy — оптимальный выбор.
SciPy Модуль scipy.linalg предоставляет схожие возможности с numpy, но может быть удобен, если вы работаете с более специализированными алгоритмами линейной алгебры или вам требуется поддержка некоторых редких матричных форматов. Скорость и расход памяти будут примерно на том же уровне, что и в numpy.
PyTorch и TensorFlow Эти библиотеки разработаны в первую очередь для задач глубокого обучения и автоматического дифференцирования. Они поддерживают вычисления на GPU, что может быть преимуществом при обработке больших матриц или батчей данных. Но для вычисления определителя небольших матриц на CPU они могут накладывать дополнительные издержки по сравнению с numpy. Если вы уже работаете с моделями нейронных сетей, то использовать встроенные функции (torch.det() или tf.linalg.det()) будет удобнее, так как это позволяет избежать лишних преобразований данных.

Теперь давайте на практике посмотрим, как с помощью этих библиотек можно вычислить определитель. Начнём с самой универсальной — numpy.

NumPy

Самая часто используемая библиотека для численных вычислений. Функция numpy.linalg.det() вычисляет определитель массива, используя оптимизированные алгоритмы из LAPACK.

import numpy as np

A = np.array([[1, 2],
              [3, 4]])
det_A = np.linalg.det(A)
print("Определитель матрицы A (numpy): ", det_A)

# Output:
# Определитель матрицы A (NumPy): -2.00

SciPy

В библиотеке scipy есть модуль scipy.linalg, где функция det() также позволяет вычислять определитель. Эта функция часто используется, если требуется использовать дополнительные возможности или типы матриц.

import numpy as np
from scipy.linalg import det

A = np.array([[1, 2],
              [3, 4]])
det_A = det(A)
print(f"Определитель матрицы A (SciPy): {det_A:.2f}")

# Output:
# Определитель матрицы A (SciPy): -2.00

PyTorch

В контексте машинного обучения при работе с тензорами можно использовать pytorch. Функция torch.det() вычисляет определитель для тензоров.

import torch

A = torch.tensor([[1.0, 2.0],
                  [3.0, 4.0]])
det_A = torch.det(A)
print(f"Определитель матрицы A (PyTorch): {det_A.item():.2f}")

# Output:
# Определитель матрицы A (PyTorch): -2.00

TensorFlow

Для тех кто использует tensorflow, также доступна функция tf.linalg.det(), которая вычисляет определитель тензоров.

import tensorflow as tf

A = tf.constant([[1.0, 2.0],
                 [3.0, 4.0]])
det_A = tf.linalg.det(A)
print(f"Определитель матрицы A (TensorFlow): {det_A.numpy():.2f}")

# Output:
# Определитель матрицы A (TensorFlow): -2.00

Если вы занимаетесь стандартными численными вычислениями или анализом данных на CPU, то numpy (или scipy) будет лучшим выбором благодаря своей скорости и низким затратам памяти. Если же вы работаете в контексте глубокого обучения и уже используете pytorch или tensorflow, имеет смысл применять их функции для согласованности вычислительного графа и возможного ускорения на GPU.

Применение определителя в задачах анализа данных

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

где:

  • — функция плотности вероятности для случайного вектора с математическим ожиданием и ковариационной матрицей ;
  • — определитель ковариационной матрицы, который играет роль нормализующей константы.

Роль определителя:

  1. Нормализация распределения. Определитель , входя в знаменатель нормирующего множителя , обеспечивает, что интеграл плотности по всему пространству равен единице. Математически это гарантирует корректное масштабирование распределения.
  2. Связь с объёмом. Геометрически определитель ковариационной матрицы отражает объём эллипсоида, описывающего область, в которой находится большая часть вероятности (так называемый контур доверительного интервала). Чем меньше , тем более «сжатое» распределение, что означает, что данные сконцентрированы в меньшем объёме пространства.
  3. Чувствительность к вырожденности. Если , это указывает на то, что ковариационная матрица близка к вырожденной — например, когда признаки почти линейно зависимы. В таких случаях модель может испытывать проблемы с числовой стабильностью: обратная матрица становится нестабильной, а оценка правдоподобия — некорректной.
  4. Применение в алгоритмах. В моделях типа Gaussian Mixture Models (GMM) для каждой компоненты рассчитывается своя ковариационная матрица, и её определитель влияет на вклад компоненты в общую вероятность наблюдения.

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