Все курсы > Линейная алгебра > Занятие 3
Рассмотрим тему смены базиса. На первом занятии курса мы уже находили координаты вектора в новом базисе при условии, что векторы нового базиса перпендикулярны (ортогональны) друг другу.
Ноутбук к сегодняшнему занятию⧉
Переход в исходный базис
Столбцы матрицы преобразований — новые базисные векторы после проведения преобразования. Зададим два новых базисных вектора a и b.
Можно сказать, что в новом базисе векторы a и b имеют координаты соотвественно $\begin{bmatrix} 1 \\ 0 \end{bmatrix}$ и $\begin{bmatrix} 0 \\ 1 \end{bmatrix}$. При этом, в старой (исходной) системе координат, новый базис имеет координаты $\begin{bmatrix} 3 \\ 1 \end{bmatrix}$ и $\begin{bmatrix} 1 \\ 1 \end{bmatrix}$.
Как следствие, матрица преобразований в исходный базис будет выглядеть как
$$ A_{old} = \begin{bmatrix} 3 & 1 \\ 1 & 1 \end{bmatrix} $$
Возьмем вектор в новой системе координат $r_{new} = \begin{bmatrix} -\frac{3}{2} \\ \frac{1}{2} \end{bmatrix} $.
Выразим этот вектор в исходном базисе с помощью координат нового базиса, записанных в старом базисе $ A_{old} $. Другими словами, перенесем вектор $r_{new}$ в соответствии с координатами $ A_{old} $.
$$ r_{old} = A_{old} \cdot r_{new} = \begin{bmatrix} 3 & 1 \\ 1 & 1 \end{bmatrix} \cdot \begin{bmatrix} -\frac{3}{2} \\ \frac{1}{2} \end{bmatrix} = \begin{bmatrix} 5 \\ 2 \end{bmatrix}$$
Продемонстрируем с помощью Питона.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# новый базис a = np.array([3, 1]) b = np.array([1, 1]) # старый базис i = np.array([1, 0]) j = np.array([0, 1]) r_old = np.array([5, 2]) ax = plt.axes() plt.xlim([-0.07, 5]) plt.ylim([-0.07, 5]) plt.grid() ax.arrow(0, 0, a[0], a[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, b[0], b[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, r_old[0], r_old[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') plt.show() |

1 2 3 4 5 6 7 8 9 10 11 |
# соединим координаты нового базиса # то есть в смысле нового базиса они равны [1, 0], [0, 1] # новый базис в "обычной" системе координат A_old = np.array([[3, 1], [1, 1]]) # возьмем вектор в новом базисе r_new = np.array([1.5, 0.5]) # возврат к исходной системе координат np.dot(A_old, r_new) |
1 |
array([5., 2.]) |
Переход в новый базис
Выполним обратное преобразование из исходного базиса в новый. Для этого нам понадобится обратная матрица. Другими словами, нам нужно найти исходный базис в координатах нового базиса (т.е. такой, который переводит новый базис (синие стрелки) в исходный (зеленые)).
1 2 3 |
# найдем "наш" базис в координатах "нового" базиса A_new = np.linalg.inv(A_old) A_new |
1 2 |
array([[ 0.5, -0.5], [-0.5, 1.5]]) |
1 2 |
# "вид" из нового базиса на исходный np.dot(A_new, i), np.dot(A_new, j) |
1 |
(array([ 0.5, -0.5]), array([-0.5, 1.5])) |
Выразим вектор $r_{new}$ в исходных координатах.
1 2 |
r_new = np.dot(A_new, r_old) r_new |
1 |
array([1.5, 0.5]) |
Ортонормальный базис
Решим аналогичную задачу, но с тем отличием, что новый базис будет ортонормальным (векторы ортогональны и имеют единичную длину).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# новый базис a = np.array([1/np.sqrt(2), 1/np.sqrt(2)]) b = np.array([-1/np.sqrt(2), 1/np.sqrt(2)]) # старый базис i = np.array([1, 0]) j = np.array([0, 1]) # координаты вектора в старом базисе r_old = np.array([1/np.sqrt(2), 3/np.sqrt(2)]) ax = plt.axes() plt.xlim([-1, 2]) plt.ylim([-0.07, 3]) plt.grid() ax.arrow(0, 0, a[0], a[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, b[0], b[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'b', ec = 'b') ax.arrow(0, 0, i[0], i[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, j[0], j[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'g', ec = 'g') ax.arrow(0, 0, r_old[0], r_old[1], width = 0.02, head_width = 0.1, head_length = 0.2, length_includes_head = True, fc = 'r', ec = 'r') plt.show() |

1 2 3 |
# выпишем вектор в исходном базисе еще раз r_old = np.array([1/np.sqrt(2), 3/np.sqrt(2)]) r_old |
1 |
array([0.70710678, 2.12132034]) |
1 2 |
# проверим ортонормальность нового базиса np.linalg.norm(a), np.linalg.norm(b), np.dot(a, b) |
1 |
(0.9999999999999999, 0.9999999999999999, 0.0) |
Возьмем вектор с координатами в новом базисе $ r_{new} = \begin{bmatrix} 2 \\ 1 \end{bmatrix} $. Преобразуем его в вектор в старом базисе.
1 2 3 4 5 6 7 8 9 |
# координаты вектора в новом базисе r_new = np.array([2, 1]) # новый базис в наших координатах A_old = np.array([[1/np.sqrt(2), -1/np.sqrt(2)], [1/np.sqrt(2), 1/np.sqrt(2)]]) # найдем r_old np.dot(A_old, r_new) |
1 |
array([0.70710678, 2.12132034]) |
Выполним обратное преобразование, т.е. снова выразим вектор в новом базисе.
1 2 3 |
A_new = np.linalg.inv(A_old) np.dot(A_new, r_old) |
1 |
array([2., 1.]) |
Так как новый базис ортонормален, мы можем использовать проекции на векторы нового базиса a и b.
1 2 |
scalar_proj_r_on_a = np.dot(r_old, a) / np.linalg.norm(a) ** 2 scalar_proj_r_on_a |
1 |
2.0 |
1 2 |
scalar_proj_r_on_b = np.dot(r_old, b) / np.linalg.norm(b) ** 2 scalar_proj_r_on_b |
1 |
1.0000000000000002 |
1 2 3 4 |
# так как базис имеет длину 1, нормализовывать векторы базиса не обязательно scalar_proj_r_on_a = np.dot(r_old, a) / np.linalg.norm(a) scalar_proj_r_on_b = np.dot(r_old, b) / np.linalg.norm(b) scalar_proj_r_on_a, scalar_proj_r_on_b |
1 |
(1.9999999999999998, 1.0) |
Преобразования в новом базисе
Предположим, что у нас есть вектор в новом базисе, и мы хотим повернуть его на 45 градусов против часовой стрелки. Проблема в том, что мы умеем поворачивать векторы только в старой, обычной системе координат.
Решением могло бы быть:
- Преобразование нового вектора в старый базис
- Поворот в этом базисе
- Возврат к новому базису
Если предствить, что $A_{old}$ преобразует новый базис в старый, $R$ вращает на 45 градусов в старом базисе, а $A_{old}^{-1} = A_{new}$ возвращает вектор из старого базиса в новый, то в целом преобразование можно записать, как
$$ r_{new, 45^{\circ}} = A_{new} \cdot (R \cdot (A_{old} \cdot r_{new})) $$
Посмотрим как это можно реализовать на практике.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# возьмем новый базис в старой системе координат A_old = np.array([[3, 1], [1, 1]]) # найдем матрицу поворота против часовой стрелки # в обычной системе координат theta = np.radians(45) Rotate = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) # найдем новый базис в новой системе координат A_new = np.linalg.inv(A_old) A_new |
1 2 |
array([[ 0.5, -0.5], [-0.5, 1.5]]) |
1 2 |
# возьмем вектор в новом базисе r_new = np.array([1.5, 0.5]) |
1 2 3 4 5 |
# (1) сначала преобразовать "новый" вектор в обычную систему координат # (2) затем применить вращение # (3) после этого вернуть вектор в новый базис rotate45_new = A_new @ (Rotate @ (A_old @ r_new)) rotate45_new |
1 |
array([-1.41421356, 6.36396103]) |
Мы получили повернутый на 45 градусов вектор в новом базисе.
Ортогональные матрицы
Мы привыкли работать в ортонормальном базисе, например, для двумерного стандартного базиса $\begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix}$ легко запомнить, что матрица расширения векторов в два раза будет выглядеть как $\begin{bmatrix} 2 & 0 \\ 0 & 2 \end{bmatrix}$.
Математически у ортогональной матрицы также есть преимущества. В частности, для ортогональной матрицы справедливо следующее
$$ AA^T = I $$
$$ A^T = A^{-1} $$
Другими словами, как умножение на транспонированную матрицу, так и умножение на обратную матрицу дают единичную матрицу, а значит транспонированная матрица равна обратной.
Если кроме того, векторы ортогональной матрицы имеют единичную длину, то матрица становится ортонормальной.
1 2 3 4 5 6 7 8 9 |
# пример ортонормальной матрицы A = np.array([[1, 0, 0], [0, 1, 0], [0, 0, -1]]) # возьмем вектор-столбцы матрицы i = A[:, 0] j = A[:, 1] k = A[:, 2] |
1 2 |
# убедимся в ортогональности np.dot(i, j).round(), np.dot(j, k).round(), np.dot(k, i).round() |
1 |
(0, 0, 0) |
1 2 |
# найдем норму каждого из векторов np.linalg.norm(i), np.linalg.norm(j), np.linalg.norm(k) |
1 |
(1.0, 1.0, 1.0) |
1 |
np.array_equal(A.T, np.linalg.inv(A)) |
1 |
True |
Ортогональная матрица увеличивает пространство на единицу, а значит ее определитель равен 1 или $-1$. При отрицательном определителе пространство переворачивается (в частности, вектор i становится слева от j).
1 |
np.linalg.det(A) |
1 |
-1.0 |
Дополнительно замечу, что так как $ AA^T = A^TA $, то можно сказать, что у ортогональной матрицы ортогональны как столбцы, так и строки.
В качестве вывода можно сказать, что с ортогональным (и тем более ортонормальным) базисом работать всегда проще:
- проще найти обратную матрицу
- преобразование обратимо
- легко найти проекцию через скалярное произведение
Процесс Грама-Шмидта
Посмотрим как найти ортонормальный базис. Используемый для этого метод называется процессом Грама-Шмидта (the Gram-Schmidt process).
Описание
Рассмотрим абстрактный пример в двумерном пространстве. Возьмем векторы $v_1$ и $v_2$ и найдем через них ортонормальный базис $e_1$ и $e_2$. Вначале убедимся, что это два линейно независимых векторов (иначе пространство схлопнется и подобрать базис не получится). Линейную независимость можно проверить через определитель.
Кроме того, возможно будет полезно еще раз заметить, что одно и то же пространство, а точнее линейную оболочку могут образовывать разные базисы.

Шаг 1. Направление первого вектора $v_1$ изменять не будем, нормализуем его и назовем $e_1$ (зеленая стрелка).
Шаг 2. Вектор $e_2$ можно представить как составляющую в направлении вектора $e_1$ ($proj_{e_1} v_2$, желтый вектор) и перпендикулярную ему составляющую.
Составляющая в направлении вектора $e_1$ представляет собой векторную проекцию $v_2$ на $e_1$, то есть
$$ proj_{e_1} v_2 = \frac{v_2 \cdot e_1}{|| e_1 ||} \frac{e_1}{|| e_1 ||} = \frac{v_2 \cdot e_1}{1} \frac{e_1}{1} = (v_2 \cdot e_1) e_1 $$
Шаг 3. Перпендикулярную $e_1$ составляющую (фиолетовый вектор, назовем его $u_2$) можно представить, как разницу векторов $v_2$ и $proj_{e_1} v_2$, то есть
$$ u_2 = v_2-proj_{e_1} v_2 = v_2-(v_2 \cdot e_1) e_1 $$
Шаг 4. Мы нашли вектор $u_2$ перпендикулярный вектору $e_1$. Для того чтобы превратить $u_2$ в $e_2$ остается его нормализовать
$$ e_2 = \frac{u_2}{|| u_2 || } $$
Для большего количества измерений процесс будет выглядеть аналогично. Обобщим процесс для трех измерений.
$$ e_1 = \frac{v_1}{|| v_1 ||} $$
$$ u_2 = v_2-proj_{e_1} v_2 \rightarrow e_2 = \frac{u_2}{|| u_2 ||} $$
$$ u_3 = v_3-proj_{e_1} v_3-proj_{e_2} v_3 \rightarrow e_3 = \frac{u_3}{|| u_3 ||} $$
Пример
Предположим, у нас есть некоторый неортономальный базис линейно независимых векторов, формирующих трехмерную линейную оболочку. У нас есть вектор в этом базисе, и мы хотим выполнить его отражение. Опять же матрицы отражения в неортонормальном базисе у нас нет. Как поступить?
- Найти ортонормальный базис в задаваемом исходными векторами пространстве
- Взять матрицу отражения в ортонормальном базисе и выполнить отражение
- Вернем вектор из ортонормального базиса в исходный
Приведем векторы в исходном неортонормальном базисе
1 2 3 |
v1 = np.array([1, 1, 1]) v2 = np.array([2, 0, 1]) v3 = np.array([3, 1, -1]) |
Ортонормальный базис
Проверим, что векторы линейно независимы.
1 2 3 |
V = np.concatenate([v1, v2, v3]).reshape(3, 3).T np.linalg.det(V), np.linalg.matrix_rank(V) |
1 |
(6.0, 3) |
Найдем первый базисный вектор $e_1$.
1 2 |
e1 = v1 / np.linalg.norm(v1) e1 |
1 |
array([0.57735027, 0.57735027, 0.57735027]) |
Найдем второй базисный вектор $e_2$.
1 2 3 4 5 6 |
# нормализовывать e1 не надо vector_proj_v2_to_e1 = np.dot(v2, e1) * e1 u2 = v2 - vector_proj_v2_to_e1 e2 = u2 / np.linalg.norm(u2) e2 |
1 |
array([ 7.07106781e-01, -7.07106781e-01, -1.57009246e-16]) |
Найдем третий базисный вектор $e_3$.
1 2 3 4 5 6 |
vector_proj_v3_to_e1 = np.dot(v3, e1) * e1 vector_proj_v3_to_e2 = np.dot(v3, e2) * e2 u3 = v3 - vector_proj_v3_to_e1 - vector_proj_v3_to_e2 e3 = u3 / np.linalg.norm(u3) e3 |
1 |
array([ 0.40824829, 0.40824829, -0.81649658]) |
Проверим ортогональность и единичную длину.
1 |
np.dot(e1, e2).round(), np.dot(e2, e3).round(), np.dot(e1, e3).round() |
1 |
(-0.0, 0.0, 0.0) |
1 |
np.linalg.norm(e1), np.linalg.norm(e2), np.linalg.norm(e3) |
1 |
(1.0, 1.0, 1.0) |
Соединим их в матрицу. Эта матрица будет переводить вектор из ортонормального базиса в неортонормальный.
1 2 |
from_ort_basis = np.concatenate([e1, e2, e3]).reshape(3, 3).T from_ort_basis |
1 2 3 |
array([[ 5.77350269e-01, 7.07106781e-01, 4.08248290e-01], [ 5.77350269e-01, -7.07106781e-01, 4.08248290e-01], [ 5.77350269e-01, -1.57009246e-16, -8.16496581e-01]]) |
Сразу найдем обратную матрицу, которая преобразует вектор из неортонормального базиса в ортонормальный.
1 2 3 |
# воспользуемся свойством ортонормальной матрицы # inv(ort) = ort.T to_ort_basis = from_ort_basis.T |
QR-разложение
Замечу, что ортонормальный базис также можно найти с помощью разложения матрицы на Q и R компоненты (QR-decomposition). Первый компонент этого разложения Q и будет ортонормальным базисом. Приведем пример.
1 2 |
Q, R = np.linalg.qr(V) Q |
1 2 3 |
array([[-0.57735027, 0.70710678, -0.40824829], [-0.57735027, -0.70710678, -0.40824829], [-0.57735027, 0. , 0.81649658]]) |
Убедимся в его ортонормальности.
1 2 3 4 |
q1, q2, q3 = Q[:, 0], Q[:, 1], Q[:, 2] np.dot(q1, q2).round(), np.dot(q2, q3).round(), np.dot(q1, q3).round(), \ np.linalg.norm(q1).round(), np.linalg.norm(q2).round(), np.linalg.norm(q3).round() |
1 |
(-0.0, 0.0, 0.0, 1.0, 1.0, 1.0) |
Подробнее о разложениях матрицы мы поговорим на следующем занятии. Вернемся к нашей задаче.
Матрица отражения
Возьмем матрицу отражения в ортонормальном базисе. В данном случае ее задача будет заключаться в том, чтобы отразить вектор относительно плоскости, задаваемой $e_1$ и $e_2$.
1 2 3 4 5 6 |
# зададим матрицу отражения в базисе e1, e2, e3 # векторы e1 и e2 трогать не будем # вектор e3 будет обратным Reflect = np.array([[1, 0, 0], [0, 1, 0], [0, 0, -1]]) |
Отражение и смена базиса
Теперь возьмем вектор в исходном неортонормальном базисе.
1 2 |
# зададим вектор r базисе векторов v1, v2, v3 r = np.array([2, 3, 5]) |
Последовательно применим смену базиса, отражение и возвращение в исходный базис.
1 2 3 |
# отображение в исходном базисе через промежуточный ортонормальный r_new_reflected = from_ort_basis @ (Reflect @ (to_ort_basis @ r)) r_new_reflected |
1 |
array([3.66666667, 4.66666667, 1.66666667]) |
В результате мы получили отраженный вектор в исходном неортонормальном базисе.
Подведем итог
Мы рассмотрели тему смены базиса, в частности, перехода к новому базису, возвращения к исходному, свойства ортонормального базиса и способ перехода к ортонормальному базису, называемый процессом Грама-Шмидта.