
В этой статье мы залезем под капот одному из линейных способов понижения размерности признакового пространства данных, а именно, подробно ознакомимся с математической стороной метода главных компонент (Principal Components Analysis, PCA).
Содержание статьи:
Примечания автора
Вместо введения
1) Значение принципа максимизации дисперсии в методе главных компонент
1.1) Принцип максимизации дисперсии
1.2) Синтетический пример: метод главных компонент в действии
2) Под капотом метода главных компонент
2.1) Преобразование матрицы признакового пространства
2.2) Связка преобразования признакового пространства с принципом максимизации дисперсии через ковариационную матрицу
2.3) Метод множителей Лагранжа: приведение уравнения
2.4) Переход от задачи дифференцирования функции Лагранжа по вектору
2.5) Аналитическое решение: поиск собственных значений и собственных векторов матрицы
Вместо заключения
Примечания автора
Данная статья, как и большинство моих предыдущих работ, не пропитана глубокой теорией, скорее наоборот — строгие теоретические выкладки здесь приводятся по минимуму. Вместо формального подхода к описанию метода главных компонент, рассмотрим один синтетический пример, разобрав который, мы сможем лучше понять механизм действия метода.
— В первой части статьи мы посмотрим на то, что происходит с данными после их преобразования методом главных компонент. Изучать преобразования мы будем на простом примере.
— Во второй части мы рассмотрим ответы на вопросы, связанные с тем, как происходят эти преобразования, то есть рассмотрим математику процесса.
Весь код в статье написан на языке python 3.
Для лучшего усвоения материала, читателю следует разбираться в основах аналитической геометрии, уметь перемножать матрицы и находить их определители, решать системы линейных уравнений с несколькими неизвестными, находить частные производные от матричных выражений, понимать суть основных характеристик случайных величин (математическое ожидание, вариация, ковариация, корреляция) и безусловно, читателю необходимо иметь хорошее представление об основных моделях машинного обучения. Это минимальный набор знаний, с которым стоит подойти к изучению статьи, остальные знания мы получим в процессе.
Вместо введения
Перед тем как перейти к первой части статьи, давайте определим основные цели понижения размерности признакового пространства. В самом общем виде, исследователь понижает размерность исходных данных в следующих случаях:
Во-первых, вследствие того, что признаков настолько много, что вычисления происходят значительно дольше требуемого времени. Например, в интернет-магазине требуется в считаные доли секунд предложить потенциальному покупателю рекомендацию. В этой ситуации, признаков на основании которых может быть дана рекомендация очень много и требуется уменьшить размерность, чтобы уменьшить время расчетов, сохранив при этом как можно больше объясняющей информации.
Во-вторых, вследствие того, что вычисления с использованием большого количества данных становятся слишком энергозатратными и экономически не выгодными.
В-третьих, и это больше применимо к методу главных компонент, из-за того, что данные почти всегда содержат шум. Как известно, шум является одним из источников переобучения моделей машинного обучения. С помощью метода главных компонент можно убрать шум. Предполагается, что дисперсия шума мала относительно дисперсии самих данных, и после преобразования данных методом главных компонент, преобразованные данные (компоненты), дисперсии которых окажутся малы, мы будем считать шумом. Их можно смело исключить из последующего обучения, предполагая, что качество модели обучения, как минимум, не понизится.
Именно эти цели в большинстве случаев преследуют датасайнтисты при понижении размерности исходного признакового пространства.
Казалось бы, самое простое и очевидное решение — убрать по какому-либо заданному параметру неугодные признаки. И действительно, методы отбора признаков очень распространены. Существуют одномерные методы отбора признаков, жадные методы, отбор на основе моделей и др. Но ведь, удалив из расчетов какой-то признак, мы рискуем потерять часть важной информации. Отсюда возникла идея, а почему бы не преобразовать признаки таким образом, чтобы можно было, скажем из числа исходных признаков
Сделаем небольшое замечание. Метод главных компонент, безусловно, не всегда преобразовывает признаковое пространство таким образом, что модели, которые обучаются на преобразованном пространстве, показывают качество лучше, чем на исходном. При этом, для того, чтобы преобразование сработало, необходимо выполнений некоторых условий матрицы исходного признакового пространства
А теперь, давайте перейдем, на мой взгляд, к самому неловкому моменту в теоретической части метода главных компонент — принципу максимизации дисперсии.
1. Максимизация дисперсии в методе главных компонент
1.1 Принцип максимизации дисперсии
Существует такое предположение, что чем больше дисперсия значений рассматриваемого признака
Представьте, что мы имеем обучающую выборку и один из признаков выборки представлен всего одним значением, допустим "
Конечно, такой пример, ни в коем случае не доказывает вышеобозначенных утверждений. Я предлагаю просто напросто поверить в эти предположения об эквивалентности дисперсии и меры информативности данных. После того, как мы в это поверим, все остальные объяснения метода главных компонент будут проходить на ура, то есть иметь вполне себе математические обоснования. Для тех, кто хочет более подробно ознакомиться с этим принципом рекомендую из литературы труд Кендалла М. и Стюарта А. — «Многомерный статистический анализ и временные ряды».
1.2 Пример: метод главных компонент в действии
Для того, чтобы лучше понимать суть метода максимизации, предлагаю Вашему вниманию простой пример.
Мы смоделируем матрицу исходных признаков
Приступим!
Сгенерируем 5000 объетов. Пусть это будут какие-то абстрактные изделия, например, муфты металлические соединительные.
Давайте определимся как мы будем генерировать эти 5000 объектов в матрице
Для каждого изделия сгенеририруем 4 признака
С целью установления связи между первыми двумя признаками, при генерации данных, используем параметры их матрицы ковариации
Теперь определим вектор истинных значений целевой переменной
Для генерации истинных значений целевой переменной
, где
Как следует из формулы, каждый признак вносит свой собственный вклад в определении значений истинных ответов, в соответствии с заданными коэффициентами. При определении коэффициентов мы закладываем логику, что длина и диаметр должны в большей степени определять вес изделия, нежели 3-й и 4-й признаки. Также мы увязываем значения коэффициентов с размерами дисперсий признаков, но весьма экстравагантным способом:
где
Заметьте, мы уравновесили первый и второй коэффициенты. Для чего мы это делаем? А все для того, чтобы нас никто не смог упрекнуть в искусственном увеличении значимости первого признака при генерации
Таким образом, мы можем смело заявить о том, что первый и второй признаки, без учета различий в значениях дисперсий, вносят практически равнозначный вклад в генерацию значений истинных ответов, а следовательно, в дальнейшем, мы сможем хоть как-то оценить «чистое» влияние дисперсии признака на его информативность.
Смотрим код
Импортируем библиотеки
# импортируем библиотеки
import numpy as np
import pandas as pd
import matplotlib
from matplotlib import pyplot as plt
%matplotlib inline
import seaborn as sns; sns.set()
import random
from sklearn.linear_model import LinearRegression
from sklearn.decomposition import PCA
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import normalize
Генерируем исходное пространство и вектор истинных ответов
np.random.seed(0)
# инициализируем математическое ожидание двух случайных величин (признаки: длина и диаметр изделия)
mu = np.array((100.,100))
# инициализируем матрицу ковариации двух случайных величин (признаки: длина и диаметр изделия)
cov = np.array([[1.3,0.15],[0.6,0.8]])
# инициализируем количество объектов
N = 5000
# формируем матрицу признаков с параметрми mu и cov
X12 = np.dot(np.random.randn(N, 2), cov) + mu
# выделяем из матрицы X12 1-й вектор значений признаков
x1 = X12[:,0]
# выделяем из матрицы X12 2-й вектор значений признаков
x2 = X12[:,1]
# формируем шум для 3-го вектора значений признаков и смещаем среднее
e3 = np.array([random.uniform(49.05,50.95) for i in range(N)])
# формируем 3-й вектор значений признаков
x3 = (x1/3 + x2/10)/5 + e3
# формируем шум для 4-го вектора значений признаков и смещаем среднее
e4 = np.array([random.uniform(350.5,351.5) for i in range(N)])
# формируем 4-й вектор значений признаков
x4 = (x1/100 + x2*2)/10 + e4
# формируем матрицу исходного признакового пространства
X = np.vstack((np.vstack((np.vstack((x1,x2)),x3)),x4)).T
# записываем правило определения истинных значений целевой переменной
def y(X, r_0, R):
R = R.reshape(-1, R.shape[0])
e = np.array([random.uniform(-1.,1.) for i in range(N)]).reshape(-1,1)
y = r_0 + np.dot(X, R.T) + e
return y
# инициализируем вектор коэффициентов
r_0 = 0
R = np.array([1.3, 1.3, 0.33, 0.33])
# формируем вектор истинных значений
y = y(X, r_0, R)
# формируем таблицу pandas с исходными данными матрицы X и вектора y
dataframe = pd.DataFrame(X)
columns_x = ['Длина, мм', 'Диаметр, мм', '3-й признак', '4-й признак']
dataframe = pd.DataFrame(np.hstack((X,y)))
dataframe.columns = columns_x + ['Вес изделия']
Давайте визуализируем парные отношения признаков и целевой переменной нашего небольшого датасета. Для этого воспользуемся функцией pairplot из библиотеки seaborn.
Визуализируем отношения признаков друг с другом
# print ('График №1 "Парные отношения признаков и истинных ответов"')
sns_plot = sns.pairplot(dataframe)
sns_plot.fig.suptitle('График №1 "Парные отношения признаков и истинных ответов"',
y = 1.03, fontsize=14, fontweight='bold')
plt.show()
# сохраним график в файл
sns_plot.savefig('graph_1.png')

Внешне датасет удался!
На графиках, не вооруженным глазом заметно, что длина изделий существенно лучше объясняют вес изделий, чем их диаметр. А 3-й и 4-й признаки, хоть и имеют схожее распределение со значениями веса изделий, очевидно, что в них содержится много шума.
Давайте еще раз акцентируем внимание на первых двух признаках и их влиянии на целевую переменную. Построим более детализированные графики.
Смотрим код
Визуализируем отношение длины, диаметра и целевой переменной
# формируем графики отображения зависимости признаков "Длина", "Диаметр" и целевой переменной "Вес изделия"
fig, (ax1, ax2, ax3) = plt.subplots(nrows = 1, ncols = 3, figsize = (16, 6))
fig.suptitle('График №2 "Отношение длины, диаметра и целевой переменной"', fontsize=14, fontweight='bold')
fig.subplots_adjust(top = 0.85)
ax1.scatter(dataframe['Длина, мм'],dataframe['Диаметр, мм'], color = 'green')
ax1.set_title('Зависимость признаков: \n Длина и Диаметр')
ax1.set_xlabel('Длина, мм')
ax1.set_ylabel('Диаметр, мм')
ax2.scatter(dataframe['Длина, мм'],dataframe['Вес изделия'], color = 'green')
ax2.set_title('Зависимость целевой переменной от \n длины')
ax2.set_xlabel('Длина, мм')
ax2.set_ylabel('Вес изделия')
ax3.scatter(dataframe['Диаметр, мм'],dataframe['Вес изделия'], color = 'green')
ax3.set_title('Зависимость целевой переменной от \n диаметра')
ax3.set_xlabel('Диаметр, мм')
ax3.set_ylabel('Вес изделия')
plt.show()
# сохраним график в файл
fig.savefig('graph_2.png')

Несмотря на то, что ранее, мы старательно уравновешивали влияние обоих признаков при определении значений истинных ответов, на графиках отчетливо видно, что признак длины изделий, который имеет наибольшую дисперсию, значительно лучше объясняет вес изделий. Иначе можно сказать, что длина имеет больший вес относительно диаметра в определении целевой переменной.
Конечно, нельзя считать результат этого небольшого эксперимента доказательством значимости дисперсии признака для определения целевой переменной. Однако наш пример все-таки указывает на то, что принцип максимизации дисперсии может работать при определенных условиях.
Кстати, обратим внимание на то, что диаметр изделия сильно зависит от его длины. Другими словами, зная длину изделия, мы можем с хорошей точностью, определить его диаметр. Можно, конечно, наоборот — на основании диаметра определять длину, но качество будет заметно ниже.
А теперь вопрос! Почему бы нам, в соответствии с принципом максимизации дисперсии, просто взять и удалить признаки, имеющие наименьшую дисперсию? Зачем нам преобразовывать данные?
Спешу разочаровать, однозначного и строгого ответа со всеми сопутствующими теоретическими выкладками на данный вопрос в статье дано не будет. Однако, не спешим отчаиваться и продолжим рассмотрение нашего примера. Обучим модель линейной регрессии на исходных и преобразованных данных, затем сравним качество модели на обоих пространствах по размеру среднеквадратичной ошибки (Mean Squared Error,
Смотрим код
Сравним качество моделей
# инициируем таблицу ошбок среднеквадратичных отклонений
table_errors_test = pd.DataFrame(index = ['MSE_test'])
# напишем функцию определения среднеквадратичной ошибки
def error(x_train, x_test, y_train, y_test):
# инициируем модель линейной регрессии
model = LinearRegression()
# обучим модель на обучающей выборке
model_fit = model.fit(x_train,y_train)
# сформируем вектор прогнозных значений
y_pred = model_fit.predict(x_test)
# определим среднеквадратичную шибку
error = round(mean_squared_error(y_test, y_pred),3)
return error
# разделим выборку на обучающую и тестовую
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# проведем центрирование данных (функция нормирования отключена)
scaler = StandardScaler(with_mean = True, with_std = False)
scaler = scaler.fit(X_train)
X_train_norm = scaler.transform(X_train)
X_test_norm = scaler.transform(X_test)
# инициируем модель PCA с 4 компонентами
model_pca = PCA(n_components = 4)
# обучим модель на обучающей выборке
model_pca.fit(X_train_norm)
# преобразуем данные обучающей выборки
Z_train_norm = model_pca.transform(X_train_norm)
# преобразуем данные тестовой выборки
Z_test_norm = model_pca.transform(X_test_norm)
# сформируем в pandas таблицу оценок качества модели линейной регрессии в зависимости от используемых признаков
table_errors_test['Все признаки'] = error(X_train_norm, X_test_norm, y_train, y_test)
table_errors_test['3-и признака'] = error(X_train_norm[:,0:3], X_test_norm[:,0:3],y_train, y_test)
table_errors_test['Длина + Диаметр'] = error(X_train_norm[:,0:2], X_test_norm[:,0:2],y_train, y_test)
table_errors_test['Длина'] = error(X_train_norm[:,0].reshape(-1,1), X_test_norm[:,0].reshape(-1,1),
y_train, y_test)
table_errors_test['Диаметр'] = error(X_train_norm[:,1].reshape(-1,1), X_test_norm[:,1].reshape(-1,1),
y_train, y_test)
table_errors_test['Все компоненты'] = error(Z_train_norm, Z_test_norm, y_train, y_test)
table_errors_test['Три компоненты'] = error(Z_train_norm[:,0:3], Z_test_norm[:,0:3], y_train, y_test)
table_errors_test['Две компоненты'] = error(Z_train_norm[:,0:2], Z_test_norm[:,0:2], y_train, y_test)
table_errors_test['Первая компонента'] = error(Z_train_norm[:,0].reshape(-1,1),
Z_test_norm[:,0].reshape(-1,1),
y_train, y_test)
print ('Таблица №1 "Сравнение качества модели линейной регрессии, обученной на различных признаках"')
table_errors_test

Результаты полностью соответствуют нашим ожиданиям!
Давайте обратим внимание на более значимые для нас результаты:
Во-первых, как мы и предположили по результатам анализа диаграмм рассеяния, длина изделий лучше объясняет их вес. Это также видно при сравнении среднеквадратичной ошибки модели линейной регрессии, обученной на значениях длины изделия со среднеквадратичной ошибкой модели, обученной на значениях диаметра. В первом случае ошибка составляет
Во-вторых, модель, которая использует в расчетах только первую компоненту преобразованного пространства, показывает значительно лучшее качество, относительно модели, использующей единственный признак исходного пространства с наибольшей дисперсией — длину изделия. Качество отличается почти в два раза. То есть, при определенных условиях, метод главных компонент, может преобразовывать исходное пространство так, что первая компонента каким-то образом «впитывает» в себя информацию, которая хорошо объясняет значения целевой переменной.
В-третьих, качество модели, обученной на трех компонентах, не уступает качеству моделей, обученных как на всех четырех компонентах, так и на всех признаках. Учитывая, что наш датасет состоит из двух образующих признаков и двух производных от них признаков, в которые мы добавили значительное количество шума, то достижение качества модели при использовании трех компонент сопоставимого с качеством модели, использующей четыре компоненты, подтверждает мысль о том, что метод главных компонент действительно может позволять отсеивать шумовые значения в отдельные компоненты с наименьшей дисперсией. В нашем случае, при обучении модели линейной регрессии, компоненты с наименьшей дисперсией стоит исключить из процесса обучения.
Давайте теперь посмотрим на то, какие изменения претерпело признаковое пространство. Для этого сравним статистики обучающей выборки исходных признаков со статистиками той же выборки, но уже на преобразованном пространстве.
Для начала посмотрим на статистики, которые предоставляет нам метод describe библиотеки pandas
Формируем основные статистики
# формируем таблицу основных описательных статистик исходного пространства
X_train_dataframe = pd.DataFrame(X_train_norm)
X_train_dataframe.columns = columns_x
X_df_describe = X_train_dataframe.describe(percentiles = []).round(3)
# формируем матрицу преобразованного пространства
Z_train_norm = model_pca.transform(X_train_norm)
Z_train_dataframe = pd.DataFrame(Z_train_norm)
columns_z = ['1-я компонента','2-я компонента', '3-я компонента','4-я компонента']
Z_train_dataframe.columns = columns_z
# формируем таблицу основных описательных статистик исходного пространства
Z_df_describe = Z_train_dataframe.describe(percentiles = []).round(3)
# формируем сравнительную таблицу основных описательных статистик двух признаковых пространств
df_describe = pd.concat((X_df_describe, Z_df_describe), axis = 1)
columns_xz = columns_x + columns_z
columns_xz
print ('Таблица №2 "Сравнение значений основных описательных статистик исходного и преобразованного пространства"')
df_describe[columns_xz]

Из интересующего нас, здесь стоит отметить, что после преобразования исходных признаков, первая компонента имеет больший разброс данных в отличии от исходного признака с наибольшим стандартным отклонением — длины изделий.
Давайте посмотрим на матрицы ковариаций исходных и преобразованных признаков.
Сравним матрицы ковариаций
# формируем матрицу ковариации исходных признаков
X_df_cov = X_train_dataframe.cov().round(3)
# формируем матрицу ковариации преобразованных признаков
Z_df_cov = Z_train_dataframe.cov().round(3)
# формируем сравнительную таблицу ковариаций
df_cov = pd.concat((X_df_cov, Z_df_cov), axis = 1)
print ('Таблица №3 "Сравнение матрицы ковариации исходных и преобразованных признаков"')
df_cov[columns_xz].fillna('-')

Действительно, мы наблюдаем некое перераспределение дисперсии признакового пространства в компоненты преобразованного.
В теории, дисперсии должны перераспределяться следующим образом. Первая компонента объясняет максимум
изменчивости исходных переменных, то есть имеет большую дисперсию, вторая – максимум оставшейся изменчивости и т.д., при этом все компоненты должны быть декоррелированны друг к другу.
В целом нечто похожее мы и наблюдаем.
Можно обратить внимание еще и на то, что после преобразования, информация с точки зрения сохранности дисперсии у нас никуда не девается — сумма дисперсий признаков исходного и преобразованного пространств полностью совпадает.
Сравним суммы дисперсий
# сравним суммы дисперсий на исходном и преобразованном пространстве
print (round(sum(X_train_dataframe.var()), 3), '- Сумма дисперсий исходных признаков')
print (round(sum(Z_train_dataframe.var()),3), '- Сумма дисперсий преобразованных признаков')
print ()
print ('Значимость компонент:')
print (list(map(lambda x: round(x,3), model_pca.explained_variance_ratio_)))

Значимость компонент означает всего лишь долю дисперсии компоненты в сумме дисперсий компонент.
Кстати, заметим, что компоненты расположены в порядке убывания значений их дисперсий. Таким образом, наиболее значимые для анализа данных компоненты, всегда расположены в первых рядах.
Ранее, мы говорили о том, что преобразованное пространство полностью декоррелированно. Давайте убедимся в этом.
Сравним корреляции
# формируем таблицу корреляции исходных признаков
X_df_cor = X_train_dataframe.corr().round(3)
# формируем таблицу корреляции преобразованных признаков
Z_df_cor = Z_train_dataframe.corr().round(3)
# формируем сравнительную таблицу корреляции
df_cor = pd.concat((X_df_cor, Z_df_cor), axis = 1)
# df_cor.fillna(0)
print ('Таблица №4 "Сравнение корреляции исходных и преобразованных признаков"')
df_cor[columns_xz].fillna('-')

Полная декорреляция!
В завершении примера, посмотрим на графики парных отношений преобразованных признаков.
Визуализируем отношения признаков и истинных ответов в преобразованном пространстве
# print ('График №3 "Парные отношения признаков и истинных ответов"')
Z_y_train_dataframe = pd.DataFrame(np.hstack((Z_train_norm,y_train)))
Z_y_train_dataframe.columns = columns_z + ['Вес изделия']
sns_plot = sns.pairplot(Z_y_train_dataframe)
sns_plot.fig.suptitle('График №3 "Парные отношения преобразованных признаков и истинных ответов"',
y = 1.03, fontsize=14, fontweight='bold')
plt.show()
# сохраним график в файл
sns_plot.savefig('graph_3.png')

Визуализируем декорреляцию преобразованного признакового пространства
# print ('График №4 "Декорреляция нового признакового пространства"')
fig = plt.figure(figsize=(16, 8))
ax = fig.add_subplot(1, 1, 1, aspect = 'equal')
ax.grid(True, which='both')
fig.suptitle('График №4 "Декорреляция нового признакового пространства"', fontsize=14, fontweight='bold')
fig.subplots_adjust(top = 1.05)
ax.set_ylim(np.min(Z_train_norm[:,1])-1, np.max(Z_train_norm[:,1])+1)
ax.set_xlim(np.min(Z_train_norm[:,0])-1, np.max(Z_train_norm[:,0])+1)
ax.plot(Z_train_norm[:,0], Z_train_norm[:,1], 'o', color = 'green')
plt.xlabel('1-я компонента')
plt.ylabel('2-я компонента')
plt.show()
# сохраним график в файл
fig.savefig('graph_4.png')

Итак, наш пример подошел к завершению.
Мы воочию убедились, что метод главных компонент позволяет преобразовывать данные таким образом, что для обучения машинных алгоритмов (в нашем случае, модель линейной регрессии), можно использовать меньшее количество преобразованных признаков — компонент, относительно исходного пространства.
Это достигается за счет:
— перераспределения дисперсии, отражающей уровень информативности данных
— полной декорреляции исходного пространства
Теперь самое время залезть под капот алгоритма и понять все «волшебство» преобразований.
2. Под капотом алгоритма
2.1 Линейное преобразование исходного признакового пространства
Переходим сразу к делу. Для проведения преобразований, нам требуется умножить матрицу исходного признакового пространства
— во-первых, новые признаки — компоненты декоррелированны между собой;
— во-вторых, сумма дисперсий значений компонент равняется сумме дисперсий значений исходных признаков;
— в-третьих, на главной диагонали матрицы ковариации расположены дисперсии компонент в порядке убывания от большего к меньшему.
Для выполнения вышеуказанных условий и для упрощения последующих рассчетов нам следует задать одно важно условие касательно матрицы
Запишем преобразование в матричном виде:
По правилам хорошего тона укажем размерность матриц:
Матрица
Матрица
Матрица
Матрица
Обратим внимание на то, что матрица
Не знаю, насколько данная запись будет понятна читателю, но лично мне кажется логичной запись:
Понятно, что
Давайте, чтобы математика лучше усваивалась, будем рассматривать пример на конкретных цифрах. За основу возьмем данные из прошлого примера. Однако, для упрощения рассчетов, в датасете оставим только два признака — длина и диаметр изделия. И далее будем сами, без использования PCA библиотеки sklearn преобразовывать данные. После самостоятельных преобразований сравним результаты с преобразованиями с помощью sklearn.
Итак, инициируем матрицу
Инициализация матрицы признаков: длина и диаметр
np.random.seed(0)
# инициализируем математическое ожидание двух случайных величин
mu = np.array((0.,0.))
# инициализируем матрицу ковариации двух случайных величин
cov = cov
# инициализируем количество объектов
N = N
# формируем матрицу признаков с параметрами mu и cov
X = np.dot(np.random.randn(N, 2), cov) + mu
# зафиксируем матрицу признаков в таблице pandas
X_df = pd.DataFrame(X)
columns_mini = ['Длина','Диаметр']
X_df.columns = columns_mini
Размерность матрицы
Теперь, когда мы получили систему уравнений и выяснили, что нам нужно найти матрицу
2.2 Связка линейного преобразования исходного признакового пространства с принципом максимизации дисперсии через матрицу ковариации
Давайте сделаем следующий трюк. Вместо поиска матрицы
Для любителей более подробных математических выкладок есть еще один способ:
Обратим внимание на некоторые отличия в подходах. Первое уравнение в виде матричной записи предполагает, что матрица
Запишем получившуюся систему уравнений:
Надеюсь, все уловили, что мы не просто преобразовали уравнение
С размерностью матриц в данной системе уравнений такая же ситуация как и в системе, указанной выше. Матрица
Посмотрим на цифры из нашего примера, при условии того, что мы не хотим понижать размерность преобразованных данных. Тогда размерность матрицы
Самое время перейти к решению системы уравнений.
2.3 Метод множителей Лагранжа
Не будем заниматься обоснованием и доказательством метода, обозначим лишь, что данный метод является универсальным при поиске условных экстремумов. Действительно, у нас есть условие ортогональности матрицы
В соответствии с методом множителей Лагранжа, систему уравнений с условием можно записать как функцию Лагранжа:
, где
Для перехода нашей системы уравнений в функцию Лагранжа:
— Во-первых, представим матричное выражение
— Во-вторых, уравнение связи об ортогональности матрицы
— В-третьих, осуществим некоторые уточнения. С этими уточнениями можно будет подробно ознакомиться буквально через абзац. А пока смотрим на функцию Лагранжа, которая у нас получилась:
В функции Лагранжа у нас не известны
Теперь, как и было обещано, подробности об уточнениях.
В очередной раз прибегнем к такому трюку, как к замене задачи. В этот раз мы будем определять матрицу
Такая запись задачи дает нам удобство в дальнейших рассчетах. Ведь раннее нам требовалось для приравнивания
Запишем размерность главного выражения для каждого
Давайте, подумаем, что мы будем делать с функцией Лагранжа?
Верно — мы будем ее дифференцировать по
2.4 Переход от задачи дифференцирования функции Лагранжа по вектору
к задаче поиска собственных значений
и собственных векторов
матрицы 
Опустим подробности матричного дифференцирования за рамки статьи и перейдем сразу же к результату:
Приравняем производную к нулю:
Таким образом, у нас возникла ситуация, когда квадратная матрица
В очередной раз в этой статье мы уточняем задачу. В этот раз вместо поиска векторов
2.5 Аналитическое решение: поиск собственных значений и собственных векторов матрицы 
Итак, мы имеем выражение
Давайте определим матрицу
Формируем матрицу ковариации матрицы X
print ('Таблица №5 "Матрица X^TX"')
# рассчитаем матрицу ковариации X и запишем результат в таблицу pandas
X_df_cov = pd.DataFrame(np.dot(X.T,X)/(N), index = columns_mini)
X_df_cov.columns = columns_mini
X_df_cov.round(4)

Раскроем выражение
В первую очередь определяем собственные числа матрицы
Выполним алгебраические действия — умножим матрицу на вектор:
Приравняем соответствующие элементы векторов-столбцов и получим однородную систему линейных уравнений:
Сделаем ход конем: в верхнем уравнении вынесем за скобку
По определению собственный вектор
Раскроем определитель и решим квадратное уравнение:
Половина дела завершена — мы нашли собственные значения матрицы
Сумма дисперсий в исходном признаковом пространстве составляет:
Сумма дисперсий в преобразованном пространстве:
Суммы совпадают, а значит мы на верном пути. Переходим к поиску собственных векторов
Определяем собственные вектора матрицы
Для определения собственных векторов нам надо по очереди в систему уравнений, приведенную ниже, подставить, полученные значения
Определим собственный вектор
Казалось, бы выразим
Такая система уравнений имеет бесконечное множество решений, но главное здесь — соотношение между искомыми значениями
Таким же образом, определим собственный вектор, соответствующий собственному числу
В итоге, мы получили матрицу
Однако, это еще не совсем та матрица, которую мы искали. В ней могли быть совсем другие значения, конечно же с условием сохранения соотношений между
Для нормировки матрицы
Отнормируем матрицу W
W = np.array(([2.4996, -0.4],[1., 1.]))
def normalize(w):
norm = np.linalg.norm(w, axis = 0)
return w / norm
W_norm = normalize(W).round(4)
W_norm
В итоге мы получили следующую матрицу:
Давайте осуществим проверку на выполнение требования ортогональности матрицы
Выведем на экран результат перемножения матрицы W_norm^T на W_norm
print ('Проверка матрицы W на ортогональность: \n', np.dot(W_norm.T,W_norm).round(2))

Действительно, мы получили единичную матрицу. Проверка пройдена. Запишем результаты.
Собственные значения матрицы
Матрица собственных векторов:
Заметьте, если наша задача преобразовать пространство с понижением размерности, то в нашем случае мы будем умножать матрицу
Для определения собственных значений и векторов матрицы
Применим функцию linalg.eig
v, W = np.linalg.eig(X_df_cov)
print ('Собственные значения: \n', v.round(4))
print ()
print ('Матрица собственных векторов: \n', W.round(4))

Результаты сошлись. На этой мажорной ноте мы завершаем изучение математики процесса в методе главных компонент.
Вспомогательные материалы
1. Литература
1.1 Многомерный статистический анализ и временные ряды, Том 3, Кендалл М., Стюарт А., Москва, «Наука», 1976
1.2 Введение в теорию матриц, Р.Беллман, Москва, «Наука», 1969
1.3 Python для сложных задач, Дж.Вандер Плас, «Питер», 2018
2. Лекции, курсы (видео)
2.1 Курс «Поиск структуры в данных» специализации «Машинное обучение и анализ данных», МФТИ, ЯНДЕКС
2.2 Лекция «Метод главных компонент», НИУ ВШЭ, 2016
2.3 Лекция «Наилучшее приближение матрицы матрицами меньшего ранга», В.Н. Малозёмов, 2014
2.4 Лекция 9 «Факторный анализ и метод главных компонент», В.Л. Аббакумов, 2018
3. Интернет источники
3.1 Статья «Как работает метод главных компонент (PCA) на простом примере», Хабр, 2016
3.2 «Условные экстремумы и метод множителей Лагранжа», mathprofi
3.3 «Собственные значения (числа) и собственные векторы», mathprofi
3.4 «Метод главных компонент (Principal component analysis)», wiki.loginom
3.5 «Метод главных компонент (PCA)», craftappmobile, 2019
3.6 Статья «Приводим уравнение линейной регрессии в матричный вид», Хабр, 2019