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

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

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

Рисунок 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
1from typing import Any
2import numpy as np
3import plotly.graph_objects as go
4from plotly.subplots import make_subplots
5import streamlit as st
6
7
8def create_figure(a: float, b: float, c: float, d: float, 
9                  vector: np.ndarray, grid_range: int = 5, grid_step: int = 1
10                 ) -> go.Figure:
11    """
12    Создаёт фигуру с двумя подграфиками (1x2):
13
14    1) Исходное пространство с заданным вектором v.
15    2) Деформированное пространство с образом вектора v'.
16
17    Параметры:
18        a, b, c, d (float): Элементы матрицы A = [[a, b], [c, d]].
19        vector (np.ndarray): Исходный вектор (длина 2).
20        grid_range (int): Диапазон значений координатной сетки.
21        grid_step (int): Шаг между линиями сетки.
22
23    Возвращает:
24        go.Figure: Фигура с двумя подграфиками.
25    """
26    xs: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
27    ys: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
28    matrix: np.ndarray = np.array([[a, b], [c, d]])
29
30    fig: go.Figure = make_subplots(
31        rows=1,
32        cols=2,
33        subplot_titles=["Исходное пространство", "Деформированное пространство"],
34        horizontal_spacing=0.02
35    )
36
37    def add_vector_annotation(x: float, y: float, xref: str, yref: str, 
38                              color: str, label: str,
39                              row: int = 1, col: int = 1, 
40                              text_xshift: int = 10, text_yshift: int = -10) -> None:
41        """Добавляет стрелку и подпись в указанном подграфике."""
42        fig.add_annotation(
43            x=x, y=y,
44            ax=0, ay=0,
45            xref=xref, yref=yref,
46            axref=xref, ayref=yref,
47            showarrow=True,
48            arrowhead=3,
49            arrowcolor=color,
50            arrowsize=1,
51            arrowwidth=2,
52            text="",
53            row=row,
54            col=col
55        )
56        fig.add_annotation(
57            x=x, y=y,
58            xref=xref, yref=yref,
59            text=label,
60            showarrow=False,
61            xshift=text_xshift,
62            yshift=text_yshift,
63            row=row,
64            col=col
65        )
66
67    # Исходное пространство
68    for x_val in xs:
69        fig.add_trace(
70            go.Scatter(
71                x=[x_val, x_val],
72                y=[-grid_range, grid_range],
73                mode='lines',
74                line=dict(color='lightgray', dash='dash'),
75                showlegend=False
76            ),
77            row=1, col=1
78        )
79    for y_val in ys:
80        fig.add_trace(
81            go.Scatter(
82                x=[-grid_range, grid_range],
83                y=[y_val, y_val],
84                mode='lines',
85                line=dict(color='lightgray', dash='dash'),
86                showlegend=False
87            ),
88            row=1, col=1
89        )
90    add_vector_annotation(1, 0, "x", "y", "red", "i", row=1, col=1)
91    add_vector_annotation(0, 1, "x", "y", "green", "j", row=1, col=1)
92    add_vector_annotation(vector[0], vector[1], "x", "y", "blue", "v", row=1, col=1)
93
94    # Деформированное пространство
95    for x_val in xs:
96        line_y = np.linspace(-grid_range, grid_range, 100)
97        original_points = np.array([[x_val, y_val] for y_val in line_y])
98        transformed = original_points @ matrix.T
99        fig.add_trace(
100            go.Scatter(
101                x=transformed[:, 0],
102                y=transformed[:, 1],
103                mode='lines',
104                line=dict(color='lightgray', dash='dash'),
105                showlegend=False
106            ),
107            row=1, col=2
108        )
109    for y_val in ys:
110        line_x = np.linspace(-grid_range, grid_range, 100)
111        original_points = np.array([[x_val, y_val] for x_val in line_x])
112        transformed = original_points @ matrix.T
113        fig.add_trace(
114            go.Scatter(
115                x=transformed[:, 0],
116                y=transformed[:, 1],
117                mode='lines',
118                line=dict(color='lightgray', dash='dash'),
119                showlegend=False
120            ),
121            row=1, col=2
122        )
123
124    v_prime: np.ndarray = matrix @ vector
125    add_vector_annotation(v_prime[0], v_prime[1], "x2", "y2", "blue", "v'", row=1, col=2)
126    i_prime: np.ndarray = matrix @ np.array([1, 0])
127    j_prime: np.ndarray = matrix @ np.array([0, 1])
128    add_vector_annotation(i_prime[0], i_prime[1], "x2", "y2", "red", "i'", row=1, col=2)
129    add_vector_annotation(j_prime[0], j_prime[1], "x2", "y2", "green", "j'", row=1, col=2)
130
131    fig.update_layout(
132        width=1000,
133        height=500,
134        margin=dict(l=0, r=0, t=50, b=50),
135        xaxis=dict(
136            domain=[0, 0.45],
137            range=[-grid_range, grid_range],
138            scaleanchor="y",
139            scaleratio=1,
140            constrain="domain"
141        ),
142        yaxis=dict(
143            domain=[0, 1],
144            range=[-grid_range, grid_range],
145            scaleanchor="x",
146            scaleratio=1,
147            constrain="domain"
148        ),
149        xaxis2=dict(
150            domain=[0.55, 1],
151            range=[-grid_range, grid_range],
152            scaleanchor="y2",
153            scaleratio=1,
154            constrain="domain"
155        ),
156        yaxis2=dict(
157            domain=[0, 1],
158            range=[-grid_range, grid_range],
159            scaleanchor="x2",
160            scaleratio=1,
161            constrain="domain"
162        )
163    )
164
165    return fig
166
167def main() -> None:
168    """Главная функция приложения Streamlit."""
169    st.title("Интерактивная визуализация линейной деформации")
170
171    st.subheader("Коэффициенты матрицы линейного отображения")
172    col1, col2 = st.columns(2)
173    a: float = col1.number_input("a:", value=1.0, step=0.1)
174    b: float = col2.number_input("b:", value=0.0, step=0.1)
175    col3, col4 = st.columns(2)
176    c: float = col3.number_input("c:", value=0.0, step=0.1)
177    d: float = col4.number_input("d:", value=1.0, step=0.1)
178
179    st.subheader("Координаты исходного вектора")
180    v1: float = st.number_input("v₁:", value=2.0, step=0.1)
181    v2: float = st.number_input("v₂:", value=1.0, step=0.1)
182    vector: np.ndarray = np.array([v1, v2])
183
184    fig: go.Figure = create_figure(a, b, c, d, vector)
185    st.plotly_chart(fig, use_container_width=True)
186
187if __name__ == '__main__':
188    main()
189    

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • Связь с нормализующими потоками в глубинном обучении

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

    где — якобиан одного слоя преобразования. Когда преобразование сохраняет объём (то есть ), оно становится проще и эффективнее в вычислениях. Такие преобразования называются volume-preserving.

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

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

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

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

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

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

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

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

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

Пояснение

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

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

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

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

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

1import streamlit as st
2import numpy as np
3import plotly.graph_objects as go
4from plotly.subplots import make_subplots
5from typing import Any
6
7def create_figure(a: float, b: float, c: float, d: float, grid_range: int = 5, grid_step: int = 1
8                 ) -> go.Figure:
9    """
10    Создаёт фигуру с двумя подграфиками (1x2):
11
12    1) Исходное пространство и единичный квадрат.
13    2) Деформированное пространство с образом единичного квадрата.
14
15    Параметры:
16        a, b, c, d (float): Элементы матрицы A = [[a, b], [c, d]].
17        grid_range (int): Диапазон значений координатной сетки.
18        grid_step (int): Шаг между линиями сетки.
19
20    Возвращает:
21        go.Figure: Фигура с двумя подграфиками.
22    """
23    # Создаём сетку координат
24    xs: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
25    ys: np.ndarray = np.arange(-grid_range, grid_range + grid_step, grid_step)
26    matrix: np.ndarray = np.array([[a, b], [c, d]])
27
28    # Создаём подграфики
29    fig: go.Figure = make_subplots(
30        rows=1,
31        cols=2,
32        subplot_titles=["Исходное пространство", "Деформированное пространство"],
33        horizontal_spacing=0.02
34    )
35
36    def add_vector_annotation(x: float, y: float, xref: str, yref: str,
37                              color: str, label: str,
38                              row: int = 1, col: int = 1,
39                              text_xshift: int = 10, text_yshift: int = -10) -> None:
40        """
41        Добавляет на фигуру стрелку (от (0,0) до (x,y)) и текстовую подпись рядом.
42        """
43        # Стрелка
44        fig.add_annotation(
45            x=x, y=y,
46            ax=0, ay=0,
47            xref=xref, yref=yref,
48            axref=xref, ayref=yref,
49            showarrow=True,
50            arrowhead=3,
51            arrowcolor=color,
52            arrowsize=1.0,
53            arrowwidth=2,
54            text="",
55            row=row,
56            col=col
57        )
58        # Подпись вектора
59        fig.add_annotation(
60            x=x, y=y,
61            xref=xref, yref=yref,
62            text=label,
63            showarrow=False,
64            xshift=text_xshift,
65            yshift=text_yshift,
66            row=row,
67            col=col
68        )
69
70    # -----------------------------
71    # 1) Исходное пространство
72    # -----------------------------
73    # Рисуем координатную сетку
74    for x_val in xs:
75        fig.add_trace(
76            go.Scatter(
77                x=[x_val, x_val],
78                y=[-grid_range, grid_range],
79                mode='lines',
80                line=dict(color='lightgray', dash='dash'),
81                showlegend=False
82            ),
83            row=1, col=1
84        )
85    for y_val in ys:
86        fig.add_trace(
87            go.Scatter(
88                x=[-grid_range, grid_range],
89                y=[y_val, y_val],
90                mode='lines',
91                line=dict(color='lightgray', dash='dash'),
92                showlegend=False
93            ),
94            row=1, col=1
95        )
96
97    # Рисуем базис: i, j
98    add_vector_annotation(1, 0, "x", "y", "red", "i", row=1, col=1)
99    add_vector_annotation(0, 1, "x", "y", "green", "j", row=1, col=1)
100
101    # Рисуем единичный квадрат (параллелограмм) в исходном пространстве
102    square: np.ndarray = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
103    fig.add_trace(
104        go.Scatter(
105            x=square[:, 0],
106            y=square[:, 1],
107            mode='lines',
108            fill='toself',
109            fillcolor='rgba(255,165,0,0.15)',  # оранжевый, прозрачность 15%
110            line=dict(color='orange'),
111            showlegend=False
112        ),
113        row=1, col=1
114    )
115
116    fig.update_xaxes(range=[-grid_range, grid_range], scaleanchor="y", scaleratio=1, row=1, col=1)
117    fig.update_yaxes(range=[-grid_range, grid_range], row=1, col=1)
118
119    # -----------------------------
120    # 2) Деформированное пространство
121    # -----------------------------
122    for x_val in xs:
123        line_y = np.linspace(-grid_range, grid_range, 100)
124        original_points = np.array([[x_val, y_val] for y_val in line_y])
125        transformed = original_points @ matrix.T
126        fig.add_trace(
127            go.Scatter(
128                x=transformed[:, 0],
129                y=transformed[:, 1],
130                mode='lines',
131                line=dict(color='lightgray', dash='dash'),
132                showlegend=False
133            ),
134            row=1, col=2
135        )
136    for y_val in ys:
137        line_x = np.linspace(-grid_range, grid_range, 100)
138        original_points = np.array([[x_val, y_val] for x_val in line_x])
139        transformed = original_points @ matrix.T
140        fig.add_trace(
141            go.Scatter(
142                x=transformed[:, 0],
143                y=transformed[:, 1],
144                mode='lines',
145                line=dict(color='lightgray', dash='dash'),
146                showlegend=False
147            ),
148            row=1, col=2
149        )
150
151    # Образы базиса: i -> i' и j -> j'
152    i_prime: np.ndarray = matrix @ np.array([1, 0])
153    j_prime: np.ndarray = matrix @ np.array([0, 1])
154    add_vector_annotation(i_prime[0], i_prime[1], "x2", "y2", "red", "i'", row=1, col=2)
155    add_vector_annotation(j_prime[0], j_prime[1], "x2", "y2", "green", "j'", row=1, col=2)
156
157    # Рисуем образ единичного квадрата в деформированном пространстве
158    square_transformed: np.ndarray = square @ matrix.T
159    fig.add_trace(
160        go.Scatter(
161            x=square_transformed[:, 0],
162            y=square_transformed[:, 1],
163            mode='lines',
164            fill='toself',
165            fillcolor='rgba(255,165,0,0.15)',
166            line=dict(color='orange'),
167            showlegend=False
168        ),
169        row=1, col=2
170    )
171
172    # Настройка осей и размеров подграфиков
173    fig.update_layout(
174        width=1000,
175        height=500,
176        margin=dict(l=0, r=0, t=50, b=50),
177        xaxis=dict(
178            domain=[0, 0.45],
179            range=[-grid_range, grid_range],
180            scaleanchor="y",
181            scaleratio=1,
182            constrain="domain"
183        ),
184        yaxis=dict(
185            domain=[0, 1],
186            range=[-grid_range, grid_range],
187            scaleanchor="x",
188            scaleratio=1,
189            constrain="domain"
190        ),
191        xaxis2=dict(
192            domain=[0.55, 1],
193            range=[-grid_range, grid_range],
194            scaleanchor="y2",
195            scaleratio=1,
196            constrain="domain"
197        ),
198        yaxis2=dict(
199            domain=[0, 1],
200            range=[-grid_range, grid_range],
201            scaleanchor="x2",
202            scaleratio=1,
203            constrain="domain"
204        )
205    )
206
207    return fig
208
209def main() -> None:
210    """Главная функция приложения Streamlit."""
211    st.title("Интерактивная визуализация линейной деформации")
212
213    st.subheader("Коэффициенты матрицы линейного отображения")
214    col1, col2 = st.columns(2)
215    a: float = col1.number_input("a:", value=1.0, step=0.1)
216    b: float = col2.number_input("b:", value=0.0, step=0.1)
217    col3, col4 = st.columns(2)
218    c: float = col3.number_input("c:", value=0.0, step=0.1)
219    d: float = col4.number_input("d:", value=1.0, step=0.1)
220
221    # Вычисляем площадь (|det A|) и выводим в отдельном блоке
222    matrix: np.ndarray = np.array([[a, b], [c, d]])
223    area_scale: float = np.linalg.det(matrix)
224    st.markdown(f"**det A:** {area_scale:.2f}")
225    st.markdown(f"**Изменение площади (|det A|):** {abs(area_scale):.2f}")
226    
227    fig: go.Figure = create_figure(a, b, c, d)
228    st.plotly_chart(fig, use_container_width=True)
229
230if __name__ == '__main__':
231    main()
232    

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

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

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

Рисунок 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. Линейность определителя по каждому столбцу:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Название

Применение

NumPy

Это наиболее универсальный и часто используемый инструмент для работы с числами на CPU. Функция `numpy.linalg.det()` оптимизирована за счёт использования библиотеки [LAPACK](https://github.com/Reference-LAPACK), что обеспечивает высокую скорость и низкий расход памяти при работе с матрицами, особенно если их размер невелик. Если вы не используете специализированные функции глубокого обучения или символьные вычисления, то `numpy` — оптимальный выбор.

SciPy

Модуль `scipy.linalg` предоставляет схожие возможности с `numpy`, но может быть удобен, если вы работаете с более специализированными алгоритмами линейной алгебры или вам требуется поддержка некоторых редких матричных форматов. Скорость и расход памяти будут примерно на том же уровне, что и в `numpy`.

PyTorch и TensorFlow

Эти библиотеки разработаны в первую очередь для задач глубокого обучения и автоматического дифференцирования. Они поддерживают вычисления на GPU, что может быть преимуществом при обработке больших матриц или батчей данных. Но для вычисления определителя небольших матриц на CPU они могут накладывать дополнительные издержки по сравнению с `numpy`. Если вы уже работаете с моделями нейронных сетей, то использовать встроенные функции (`torch.det()` или `tf.linalg.det()`) будет удобнее, так как это позволяет избежать лишних преобразований данных.

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

NumPy

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

1import numpy as np
2
3A = np.array([[1, 2],
4              [3, 4]])
5det_A = np.linalg.det(A)
6print("Определитель матрицы A (numpy): ", det_A)
7
8# Output:
9# Определитель матрицы A (NumPy): -2.00

SciPy

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

1import numpy as np
2from scipy.linalg import det
3
4A = np.array([[1, 2],
5              [3, 4]])
6det_A = det(A)
7print(f"Определитель матрицы A (SciPy): {det_A:.2f}")
8
9# Output:
10# Определитель матрицы A (SciPy): -2.00

PyTorch

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

1import torch
2
3A = torch.tensor([[1.0, 2.0],
4                  [3.0, 4.0]])
5det_A = torch.det(A)
6print(f"Определитель матрицы A (PyTorch): {det_A.item():.2f}")
7
8# Output:
9# Определитель матрицы A (PyTorch): -2.00

TensorFlow

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

1import tensorflow as tf
2
3A = tf.constant([[1.0, 2.0],
4                 [3.0, 4.0]])
5det_A = tf.linalg.det(A)
6print(f"Определитель матрицы A (TensorFlow): {det_A.numpy():.2f}")
7
8# Output:
9# Определитель матрицы A (TensorFlow): -2.00

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

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

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

где:

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

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

  1. Нормализация распределения.

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

  2. Связь с объёмом.

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

  3. Чувствительность к вырожденности.

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

  4. Применение в алгоритмах.

    В моделях типа Gaussian Mixture Models (GMM) для каждой компоненты рассчитывается своя ковариационная матрица, и её определитель влияет на вклад компоненты в общую вероятность наблюдения.

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



Чтобы добавить в заметки выделенный текст, нажмите Ctrl + E
Предыдущий параграф4.5. Системы линейных уравнений: продвинутые методы решения
Следующий параграф5.1. О чём мы поговорим в этой главе