Глава 8. ПОНИЖЕНИЕ РАЗМЕРНОСТИ

8.1. Цель анализа главных компонент (PCA - Principal Component Analysis)

\[ \text{Максимизировать: } \text{Var}(z) = \mathbf{w}^T S \mathbf{w} \quad \text{при условии } \|\mathbf{w}\|_2 = 1 \] (где \(z = \mathbf{w}^T \mathbf{x}\) - проекция данных \(\mathbf{x}\) на направление \(\mathbf{w}\), S - ковариационная матрица данных)

Объяснение: PCA ищет направления (главные компоненты), которые максимизируют дисперсию проецируемых данных, при этом компоненты ортогональны друг другу.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Пример данных
# X = np.random.rand(100, 5) # 100 образцов, 5 признаков

# Важно: PCA чувствителен к масштабу, обычно требуется стандартизация
# scaler = StandardScaler()
# X_scaled = scaler.fit_transform(X)

# k - желаемое количество компонент
# k = 2
# pca = PCA(n_components=k)
# X_pca = pca.fit_transform(X_scaled) # Применяем PCA к масштабированным данным

# print(f"Shape after PCA: {X_pca.shape}") # (100, k)
# print(f"Explained variance ratio: {pca.explained_variance_ratio_}")
# print(f"Total explained variance: {np.sum(pca.explained_variance_ratio_)}")

8.2. Ковариационная матрица для PCA

\[ S = \frac{1}{n - 1} (X - \bar{X})^T (X - \bar{X}) \quad (\text{для данных, где строки - признаки}) \] \[ S = \frac{1}{n - 1} (X' - \bar{X'})^T (X' - \bar{X'}) \quad (\text{где } X' \text{ - транспонированная } X, \text{строки - образцы}) \] (где \(\bar{X}\) - матрица средних, \(n\) - число образцов)

Объяснение: Ковариационная матрица отражает попарные зависимости (ковариации) между признаками и является центральной для PCA, так как ее собственные векторы определяют главные компоненты.

Реализация:


import numpy as np

# X - данные (n_samples, n_features)
# scaler = StandardScaler() # Предполагаем, что данные уже центрированы (вычтено среднее)
# X_centered = scaler.fit_transform(X) # Или X_centered = X - np.mean(X, axis=0)

# Вычисляем ковариационную матрицу
# rowvar=False означает, что столбцы - это переменные (признаки)
# cov_matrix = np.cov(X_centered, rowvar=False)

# Или вручную (для центрированных данных):
# n_samples = X_centered.shape[0]
# cov_matrix_manual = (1 / (n_samples - 1)) * (X_centered.T @ X_centered)

# print("Covariance Matrix shape:", cov_matrix.shape)

8.3. Собственное разложение (Eigen Decomposition) для PCA

\[ S\mathbf{w} = \lambda\mathbf{w} \] (где S - ковариационная матрица, \(\mathbf{w}\) - собственный вектор (главная компонента), \(\lambda\) - собственное значение (дисперсия вдоль \(\mathbf{w}\)))

Объяснение: PCA использует собственное разложение ковариационной матрицы для нахождения собственных значений (которые представляют дисперсию) и собственных векторов (которые являются главными компонентами).

Реализация:


import numpy as np

# cov_matrix - ковариационная матрица (должна быть квадратной)
# Пример ковариационной матрицы
# cov_matrix = np.array([[2.0, 0.5], [0.5, 1.0]])

# Выполняем собственное разложение
# eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)

# Сортируем по убыванию собственных значений
# sort_indices = np.argsort(eigenvalues)[::-1]
# eigenvalues_sorted = eigenvalues[sort_indices]
# eigenvectors_sorted = eigenvectors[:, sort_indices] # Столбцы - собственные векторы

# print("Eigenvalues (variances):", eigenvalues_sorted)
# print("Eigenvectors (principal components):\n", eigenvectors_sorted)

8.4. Сингулярное разложение (SVD - Singular Value Decomposition)

\[ X = U \Sigma V^T \] (где X - матрица данных (n x m), U - ортогональная матрица (n x n или n x k), \(\Sigma\) - диагональная матрица сингулярных значений (n x m или k x k), \(V^T\) - ортогональная матрица (m x m или k x m))

Объяснение: SVD факторизует матрицу на ортогональные компоненты (U, V) и диагональную матрицу сингулярных значений (\(\Sigma\)). Позволяет понижать размерность путем усечения \(\Sigma\) (отбрасывания малых сингулярных значений) и соответствующих столбцов/строк U и V.

Реализация:


import numpy as np

# X - матрица данных (n_samples, n_features)
# X_centered = X - np.mean(X, axis=0) # Обычно применяют к центрированным данным

# Выполняем SVD
# full_matrices=False для экономичной формы (U:(n,k), S:(k,), Vh:(k,m)) где k=min(n,m)
# U, S, Vh = np.linalg.svd(X_centered, full_matrices=False)
# V = Vh.T # V содержит сингулярные векторы как столбцы

# print("Shape of U:", U.shape)
# print("Shape of S:", S.shape) # Вектор сингулярных значений
# print("Shape of V:", V.shape)

# Связь с собственными значениями ковариационной матрицы:
# eigenvalues = (S**2) / (n_samples - 1)

8.5. Ошибка реконструкции для PCA

\[ \text{Error} = \|X - \hat{X}\|_F^2 \] \[ \hat{X} = Z W^T + \bar{X} \] (где \(\hat{X}\) - реконструированные данные, Z - данные в пространстве главных компонент (проекции), W - матрица главных компонент (собственных векторов), \(\bar{X}\) - вектор средних значений исходных признаков, \(\|\cdot\|_F\) - норма Фробениуса)

Объяснение: Ошибка реконструкции количественно определяет потерю информации при понижении размерности с помощью PCA. Это квадрат нормы разности между исходными и восстановленными данными.

Реализация:


import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# X - исходные данные
# k - количество компонент
# scaler = StandardScaler()
# X_scaled = scaler.fit_transform(X)

# pca = PCA(n_components=k)
# Z = pca.fit_transform(X_scaled) # Данные в пространстве PCA

# Реконструкция данных
# X_reconstructed_scaled = pca.inverse_transform(Z)

# Обратное масштабирование (если применялось)
# X_reconstructed = scaler.inverse_transform(X_reconstructed_scaled)

# Вычисление ошибки реконструкции (на исходных данных)
# reconstruction_error = np.linalg.norm(X - X_reconstructed, 'fro')**2

# Или через собственные значения (если PCA делался вручную):
# Ошибка = сумма отброшенных собственных значений * (n_samples - 1) ??? Уточнить

# Ошибка реконструкции как доля потерянной дисперсии:
# total_variance = np.sum(pca.explained_variance_)
# reconstruction_error_variance = total_variance - np.sum(pca.explained_variance_[:k]) # Не совсем ошибка реконструкции
# Или 1 - np.sum(pca.explained_variance_ratio_)

# Пример с вычислением по формуле X_hat = Z @ W.T + mean_X (как в OCR)
# W = pca.components_.T # Главные компоненты (собственные векторы)
# mean_X = scaler.mean_ # Средние исходных признаков
# X_hat = Z @ W.T + mean_X # Реконструкция (если Z получено из масштабированных данных без среднего)
                         # Если Z получено из центрированных данных, то + mean_X не нужно.
                         # Если PCA из sklearn, inverse_transform правильнее.

# Используем inverse_transform из sklearn:
# reconstruction_error_sklearn = np.linalg.norm(X - X_reconstructed, 'fro')**2
# print(f"Reconstruction Error (Frobenius norm squared): {reconstruction_error_sklearn}")

8.6. Доля объясненной дисперсии

\[ \text{Explained Variance Ratio (comp } i) = \frac{\lambda_i}{\sum_{j=1}^{m} \lambda_j} \] (где \(\lambda_i\) - собственное значение (дисперсия), соответствующее i-й главной компоненте, m - общее число признаков)

Объяснение: Доля объясненной дисперсии количественно определяет долю общей дисперсии данных, которая захватывается каждой главной компонентой.

Реализация:


import numpy as np

# eigenvalues - отсортированные собственные значения ковариационной матрицы
# eigenvalues_sorted = np.array([5.0, 1.5, 0.5]) # Пример

# explained_variance_ratio = eigenvalues_sorted / np.sum(eigenvalues_sorted)
# print("Explained Variance Ratio:", explained_variance_ratio) # [5/7, 1.5/7, 0.5/7]

# Или из объекта PCA scikit-learn:
# pca = PCA(n_components=k).fit(X_scaled)
# print("Explained Variance Ratio (from sklearn):", pca.explained_variance_ratio_)

8.7. Кумулятивная объясненная дисперсия

\[ \text{Cumulative Explained Variance (first k comps)} = \sum_{i=1}^{k} \frac{\lambda_i}{\sum_{j=1}^{m} \lambda_j} \]

Объяснение: Кумулятивная объясненная дисперсия оценивает общую долю дисперсии, захваченную первыми k главными компонентами. Помогает выбрать оптимальное количество компонент.

Реализация:


import numpy as np

# explained_variance_ratio - доли объясненной дисперсии для каждой компоненты
# explained_variance_ratio = np.array([0.7, 0.2, 0.05, 0.03, 0.02]) # Пример

# cumulative_explained_variance = np.cumsum(explained_variance_ratio)
# print("Cumulative Explained Variance:", cumulative_explained_variance)
# [0.7  0.9  0.95 0.98 1.  ]

# Или из объекта PCA scikit-learn:
# pca = PCA().fit(X_scaled) # Сначала обучить со всеми компонентами
# cumulative_variance = np.cumsum(pca.explained_variance_ratio_)
# print("Cumulative Explained Variance (from sklearn):", cumulative_variance)

8.8. Случайная проекция (Random Projection)

\[ X_{\text{proj}} = XR, \quad R \sim \mathcal{N}(0, 1) \quad (\text{или другие распределения}) \] (где X - исходные данные (n x m), R - случайная матрица проекции (m x k), k - целевая размерность, \(k \ll m\))

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

Реализация (используя scikit-learn):


import numpy as np
from sklearn.random_projection import GaussianRandomProjection

# X - исходные данные (n_samples, n_features)
# k - желаемая низкая размерность
# X_data = np.random.rand(100, 50)
# k_dim = 5

# rp = GaussianRandomProjection(n_components=k_dim, random_state=0)
# X_projected = rp.fit_transform(X_data)

# print(f"Original shape: {X_data.shape}")
# print(f"Projected shape: {X_projected.shape}") # (100, k_dim)
# print("Random projection matrix shape:", rp.components_.shape) # (k_dim, n_features)

8.9. Матрица расстояний Isomap

\[ d_{ij}^{\text{Isomap}} = \text{Кратчайший путь между } i \text{ и } j \text{ на графе } G \] \[ G = (X, \text{ребра между } \epsilon\text{-соседями или k-ближайшими соседями}) \]

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

Реализация (используя scikit-learn):


import numpy as np
from sklearn.manifold import Isomap

# X - исходные данные (n_samples, n_features)
# k_neighbors - количество соседей для построения графа
# n_components_isomap - целевая размерность
# X_data = # ... ваши данные, например, Swiss roll
# k_neighbors = 10
# n_components_isomap = 2

# isomap = Isomap(n_neighbors=k_neighbors, n_components=n_components_isomap)
# X_isomap = isomap.fit_transform(X_data)

# print(f"Shape after Isomap: {X_isomap.shape}")
# Матрицу геодезических расстояний можно получить из isomap.dist_matrix_
# print("Geodesic distance matrix shape:", isomap.dist_matrix_.shape)

8.10. Функция стресса MDS (Multidimensional Scaling Stress)

\[ \text{Stress} = \sqrt{ \frac{\sum_{iОбъяснение: Функция стресса измеряет расхождение между исходными расстояниями и расстояниями во вложенном (низкоразмерном) пространстве в многомерном шкалировании (MDS).

Реализация (используя scikit-learn):


import numpy as np
from sklearn.manifold import MDS

# X - исходные данные или матрица попарных расстояний
# n_components_mds - целевая размерность
# X_data = # ... ваши данные
# n_components_mds = 2

# metric=True означает, что MDS пытается сохранить исходные расстояния (метрическое MDS)
# metric=False (по умолчанию) использует SMACOF (неметрическое MDS)
# mds = MDS(n_components=n_components_mds, metric=True, random_state=0, normalized_stress='auto')
# X_mds = mds.fit_transform(X_data)

# Значение стресса сохраняется в mds.stress_
# print(f"MDS Stress: {mds.stress_}")

8.11. Многомерное шкалирование (MDS - Multidimensional Scaling)

\[ X_{\text{MDS}} = \arg \min_{Y} \text{Stress}(Y, D) \] (где Y - координаты в низкоразмерном пространстве, D - матрица исходных попарных расстояний/непохожестей)

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

Реализация (используя scikit-learn): См. реализацию в 8.10.

8.12. NMF (Неотрицательная матричная факторизация / Non-Negative Matrix Factorization)

\[ X \approx WH, \quad W \ge 0, H \ge 0 \] (где X - исходная неотрицательная матрица (n x m), W - матрица базисных компонент (n x k), H - матрица коэффициентов/активаций (k x m), k - ранг факторизации)

Объяснение: NMF факторизует неотрицательную матрицу на две неотрицательные матрицы меньшего ранга. Часто используется в тематическом моделировании (topic modeling) и обработке изображений (выделение частей).

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import NMF

# X - исходная неотрицательная матрица (n_samples, n_features)
# k - количество компонент (ранг)
# X_data_nonneg = np.abs(np.random.rand(50, 30)) # Пример неотрицательных данных
# k_nmf = 5

# nmf_model = NMF(n_components=k_nmf, init='random', random_state=0, max_iter=300)
# W = nmf_model.fit_transform(X_data_nonneg) # Матрица W (компоненты)
# H = nmf_model.components_              # Матрица H (коэффициенты)

# print(f"Shape of W: {W.shape}") # (n_samples, k) - Если fit_transform
# В стандартной нотации W - это (n_features, k), H - (k, n_samples)
# sklearn NMF возвращает W как (n_samples, k), а H как (k, n_features)
# print(f"Shape of H (components_): {H.shape}") # (k, n_features)

# Реконструкция: X_approx = W @ H

8.13. Цель ICA (Анализ независимых компонент / Independent Component Analysis)

\[ \text{Максимизировать: не-Гауссовость } (\mathbf{s}) \quad \text{или Минимизировать: Взаимную информацию } I(\mathbf{s}) \] \[ \mathbf{s} = WX \] (где X - наблюдаемые смешанные сигналы, W - разделяющая матрица, \(\mathbf{s}\) - оцененные независимые источники)

Объяснение: ICA разделяет смешанные сигналы на статистически независимые компоненты путем максимизации их не-Гауссовости (или минимизации взаимной информации).

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import FastICA

# X - матрица наблюдаемых сигналов (n_samples, n_features)
# k - количество компонент для извлечения
# X_mixed = # ... ваши смешанные сигналы
# k_ica = # ... количество источников

# ica = FastICA(n_components=k_ica, random_state=0)
# S_estimated = ica.fit_transform(X_mixed) # Оцененные независимые компоненты (источники)
# W_mixing = ica.mixing_ # Оцененная матрица смешивания A (X = S @ A.T)
# W_unmixing = ica.components_ # Оцененная разделяющая матрица W (S = X @ W.T)

# print(f"Shape of estimated sources: {S_estimated.shape}")

8.14. Модель факторного анализа (Factor Analysis)

\[ \mathbf{x} = \Lambda \mathbf{z} + \boldsymbol{\epsilon}, \quad \boldsymbol{\epsilon} \sim \mathcal{N}(0, \Psi) \] (где \(\mathbf{x}\) - наблюдаемые переменные, \(\mathbf{z}\) - скрытые латентные факторы (\(\sim \mathcal{N}(0, I)\)), \(\Lambda\) - матрица факторных нагрузок, \(\boldsymbol{\epsilon}\) - уникальные факторы/ошибки, \(\Psi\) - диагональная ковариационная матрица ошибок)

Объяснение: Факторный анализ моделирует наблюдаемые переменные как линейные комбинации скрытых (латентных) факторов плюс шум (уникальные факторы).

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import FactorAnalysis

# X - исходные данные (n_samples, n_features)
# k - количество латентных факторов
# X_data = # ... ваши данные
# k_factors = 3

# fa = FactorAnalysis(n_components=k_factors, random_state=0)
# X_factors = fa.fit_transform(X_data) # Значения латентных факторов для данных

# print(f"Shape of factor scores: {X_factors.shape}") # (n_samples, k_factors)
# print("Factor Loadings (Lambda):\n", fa.components_.T) # (n_features, k_factors)
# print("Noise Variance (Psi diagonal):\n", fa.noise_variance_) # (n_features,)

8.15. Преобразование Kernel PCA (Ядерный PCA)

\[ K = \Phi(X) \Phi(X)^T \quad (\text{Матрица Грама ядра}) \] \[ \text{Собственное разложение: } K \boldsymbol{\alpha} = \lambda \boldsymbol{\alpha} \] (Проекция новой точки \(\mathbf{x}\): \(\sum_i \alpha_i k(\mathbf{x}_i, \mathbf{x})\), где \(k\) - функция ядра)

Объяснение: Ядерный PCA применяет PCA в высокоразмерном пространстве признаков, неявно определенном функцией ядра \(k\), позволяя находить нелинейные главные компоненты.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import KernelPCA

# X - исходные данные (n_samples, n_features)
# k - количество компонент
# kernel_type - тип ядра ('rbf', 'poly', 'sigmoid', 'linear', etc.)
# X_data = # ... ваши нелинейно разделимые данные
# k_kpca = 2
# kernel_type = 'rbf' # Радиальная базисная функция (Гауссово ядро)

# kpca = KernelPCA(n_components=k_kpca, kernel=kernel_type, gamma=None, random_state=0) # gamma - параметр для RBF
# X_kpca = kpca.fit_transform(X_data)

# print(f"Shape after Kernel PCA: {X_kpca.shape}") # (n_samples, k_kpca)

8.16. LDA (Критерий Фишера / Fisher's Criterion)

\[ \text{Максимизировать: } J(\mathbf{w}) = \frac{\mathbf{w}^T S_B \mathbf{w}}{\mathbf{w}^T S_W \mathbf{w}} \] (где \(S_B\) - матрица межклассовой ковариации, \(S_W\) - матрица внутриклассовой ковариации)

Объяснение: Линейный дискриминантный анализ (LDA) находит проекцию, которая максимизирует разделение между классами путем оптимизации отношения межклассовой дисперсии к внутриклассовой дисперсии.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

# X - исходные данные (n_samples, n_features)
# y - метки классов (n_samples,)
# k - количество компонент (<= n_classes - 1)
# X_data = # ... ваши данные для классификации
# y_labels = # ... метки классов
# k_lda = # min(n_features, n_classes - 1)

# lda = LinearDiscriminantAnalysis(n_components=k_lda)
# X_lda = lda.fit_transform(X_data, y_labels) # Используем и X, и y

# print(f"Shape after LDA: {X_lda.shape}") # (n_samples, k_lda)

8.17. Робастный PCA (RPCA)

\[ \min_{L, S} \|L\|_* + \lambda \|S\|_1 \quad \text{при условии } X = L + S \] (где \(L\) - низкоранговая компонента, \(S\) - разреженная компонента (ошибки/выбросы), \(\|L\|_*\) - ядерная норма (сумма сингулярных значений), \(\|S\|_1\) - L1 норма (сумма абсолютных значений элементов))

Объяснение: RPCA разлагает матрицу на низкоранговую компоненту (основная структура) и разреженную компоненту (выбросы, ошибки), делая PCA робастным к аномалиям.

Реализация (используя библиотеку r_pca, если установлена):


# Требуется установка: pip install rpca
# import numpy as np
# from r_pca import R_pca

# X - исходная матрица (может содержать выбросы)
# X_data_corrupted = # ... ваши данные с выбросами

# Инициализируем модель RPCA
# rpca = R_pca(X_data_corrupted)
# L, S = rpca.fit(max_iter=1000, iter_print=100) # Выполняем разложение

# print("Shape of Low-Rank matrix L:", L.shape)
# print("Shape of Sparse matrix S:", S.shape)
# print("Percentage of non-zero elements in S:", np.sum(np.abs(S)>1e-6) / S.size * 100)

8.18. Hessian LLE (Локально-линейное вложение с Гессианом)

\[ \text{Минимизировать: } \|\mathbf{Y} W - \mathbf{Y}\|^2_F \quad \text{(LLE часть)} \] \[ \text{при условии сохранения локальной структуры Гессиана} \] (Hessian LLE использует оценку касательного пространства и его кривизны (Гессиана) для сохранения локальной геометрии)

Объяснение: Hessian LLE сохраняет локальные геометрические структуры (включая информацию о кривизне), оптимизируя низкоразмерное вложение.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.manifold import LocallyLinearEmbedding

# X - исходные данные
# k_neighbors - количество соседей
# n_components_hlle - целевая размерность
# X_data = # ... ваши данные на многообразии
# k_neighbors = 10
# n_components_hlle = 2

# hessian_lle = LocallyLinearEmbedding(n_neighbors=k_neighbors,
#                                      n_components=n_components_hlle,
#                                      method='hessian', # Используем гессиановский собственный метод
#                                      random_state=0)
# X_hlle = hessian_lle.fit_transform(X_data)

# print(f"Shape after Hessian LLE: {X_hlle.shape}")

8.19. Цель Laplacian Eigenmaps (Лапласовы собственные карты)

\[ \text{Минимизировать: } \sum_{i,j} w_{ij}\|\mathbf{y}_i - \mathbf{y}_j\|^2 = \text{Tr}(Y^T L Y) \] (где \(w_{ij}\) - вес ребра между i и j в графе соседей (например, 1 если соседи, 0 иначе), \(\mathbf{y}_i\) - низкоразмерное представление точки i, L - Лапласиан графа \(L = D - W\))

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

Реализация (используя scikit-learn):


import numpy as np
from sklearn.manifold import SpectralEmbedding

# X - исходные данные
# k - количество компонент (целевая размерность)
# n_neighbors_le - количество соседей для построения графа
# X_data = # ... ваши данные
# k_le = 2
# n_neighbors_le = 10

# affinity='nearest_neighbors' или 'rbf'
# laplacian = SpectralEmbedding(n_components=k_le,
#                               affinity='nearest_neighbors',
#                               n_neighbors=n_neighbors_le,
#                               random_state=0)
# X_laplacian = laplacian.fit_transform(X_data)

# print(f"Shape after Laplacian Eigenmaps: {X_laplacian.shape}")

8.20. Реконструкция автоэнкодером

\[ \hat{X} = \text{Decoder}(\text{Encoder}(X)) \] (где Encoder сжимает X в латентное представление, Decoder восстанавливает \(\hat{X}\) из него)

Объяснение: Автоэнкодеры минимизируют ошибку реконструкции путем сжатия данных в латентное представление (кодирование) и последующего восстановления (декодирования).

Реализация (концептуально, с Keras/TensorFlow):


# import tensorflow as tf
# from tensorflow.keras.layers import Input, Dense
# from tensorflow.keras.models import Model

# X_data = # ... ваши данные
# input_dim = X_data.shape[1]
# encoding_dim = 32 # Размер латентного пространства

# # --- Построение модели ---
# input_layer = Input(shape=(input_dim,))
# # Энкодер
# encoded = Dense(128, activation='relu')(input_layer)
# encoded = Dense(64, activation='relu')(encoded)
# encoded = Dense(encoding_dim, activation='relu')(encoded) # Латентное представление
# # Декодер
# decoded = Dense(64, activation='relu')(encoded)
# decoded = Dense(128, activation='relu')(decoded)
# decoded = Dense(input_dim, activation='sigmoid')(decoded) # Выходной слой (сигмоид для [0,1] или линейный)

# # Модели
# autoencoder = Model(input_layer, decoded) # Полный автоэнкодер
# encoder = Model(input_layer, encoded) # Отдельно энкодер

# # --- Компиляция и обучение ---
# autoencoder.compile(optimizer='adam', loss='mse') # Обучаем минимизировать MSE
# autoencoder.fit(X_data, X_data, epochs=50, batch_size=32, validation_split=0.2)

# --- Реконструкция ---
# X_reconstructed = autoencoder.predict(X_data)

8.21. Латентное представление автоэнкодера

\[ Z = \text{Encoder}(X) \]

Объяснение: Латентное представление (Z) сжимает входные данные в пространство меньшей размерности, захватывая основные характеристики данных для последующих задач.

Реализация (концептуально, с Keras/TensorFlow):


# После обучения автоэнкодера (см. 8.20)

# X_data = # ... ваши данные
# latent_representation = encoder.predict(X_data) # Получаем сжатое представление

# print(f"Shape of latent representation Z: {latent_representation.shape}") # (n_samples, encoding_dim)

8.22. Цель разреженного PCA (Sparse PCA)

\[ \text{Максимизировать: } \|\mathbf{z}\|_2^2 = \|X\mathbf{w}\|_2^2 \quad (\text{дисперсию проекции}) \] \[ \text{при условии разреженности вектора нагрузки } \mathbf{w} \text{ (например, } \|\mathbf{w}\|_1 \le C \text{ или } \|\mathbf{w}\|_0 \le k) \]

Объяснение: Разреженный PCA вводит разреженность (много нулей) в главные компоненты (векторы нагрузок \(\mathbf{w}\)), чтобы улучшить их интерпретируемость.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.decomposition import SparsePCA

# X - исходные данные (обычно центрированные)
# k - количество компонент
# alpha - параметр L1 регуляризации (контролирует разреженность)
# X_centered = X - np.mean(X, axis=0)
# k_spca = 5
# alpha_spca = 1.0 # Подбирается экспериментально

# spca = SparsePCA(n_components=k_spca, alpha=alpha_spca, random_state=0)
# Z = spca.fit_transform(X_centered) # Данные в разреженном PCA пространстве

# print("Shape after Sparse PCA:", Z.shape)
# print("Sparse PCA components (loadings):\n", spca.components_) # Строки - компоненты
# print("Number of non-zero elements in first component:", np.sum(np.abs(spca.components_[0]) > 1e-6))

8.23. Цель t-SNE (t-distributed Stochastic Neighbor Embedding)

\[ \text{Минимизировать: } \text{KL}(P || Q) = \sum_{i \neq j} p_{ij} \log \frac{p_{ij}}{q_{ij}} \] (где \(p_{ij}\) - сходство между точками i и j в исходном пространстве (основано на Гауссовом ядре), \(q_{ij}\) - сходство в низкоразмерном пространстве (основано на t-распределении Стьюдента))

Объяснение: t-SNE минимизирует дивергенцию Кулльбака-Лейблера между распределениями сходства точек в высокоразмерном и низкоразмерном пространствах. Особенно хорош для визуализации.

Реализация (используя scikit-learn):


import numpy as np
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# X - исходные данные
# k - целевая размерность (обычно 2 или 3 для визуализации)
# perplexity - параметр, связанный с числом ближайших соседей (обычно 5-50)
# X_data = # ... ваши данные
# k_tsne = 2
# perplexity_tsne = 30.0

# tsne = TSNE(n_components=k_tsne, perplexity=perplexity_tsne, random_state=0, n_iter=300)
# X_tsne = tsne.fit_transform(X_data)

# print(f"Shape after t-SNE: {X_tsne.shape}")
# Значение KL дивергенции после оптимизации: tsne.kl_divergence_

# Визуализация (если k_tsne=2)
# plt.figure(figsize=(6, 6))
# plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y_labels) # Раскрасить по меткам, если есть
# plt.title('t-SNE Visualization')
# plt.xlabel('t-SNE Component 1')
# plt.ylabel('t-SNE Component 2')
# plt.show()

8.24. Градиент t-SNE

\[ \frac{\partial \text{KL}(P || Q)}{\partial \mathbf{y}_i} = 4 \sum_{j \neq i} (p_{ij} - q_{ij}) q_{ij} (\mathbf{y}_i - \mathbf{y}_j) (1 + \|\mathbf{y}_i - \mathbf{y}_j\|^2)^{-1} \] (Упрощенная форма градиента, используемая в оптимизации положений \(\mathbf{y}_i\) в низкоразмерном пространстве)

Объяснение: Градиент целевой функции t-SNE используется для обновления положений точек во вложенном (низкоразмерном) пространстве, чтобы привести распределение \(Q\) в соответствие с \(P\).

Реализация: Градиент вычисляется внутри реализации `sklearn.manifold.TSNE` или других библиотек, реализующих t-SNE.

8.25. UMAP (Uniform Manifold Approximation and Projection)

\[ \text{Оптимизировать (упрощенно): } \sum_{i,j} w_{ij}^{\text{high}} \log p_{ij} - \sum_{i,j} w_{ij}^{\text{low}} \log q_{ij} \] (Минимизация перекрестной энтропии между графом соседей в исходном и вложенном пространствах, \(p_{ij}\) и \(q_{ij}\) - вероятности существования ребра)

Объяснение: UMAP сохраняет как локальные, так и глобальные структуры данных путем оптимизации графового представления данных в исходном и низкоразмерном пространствах, часто быстрее t-SNE и лучше сохраняет глобальную структуру.

Реализация (используя библиотеку umap-learn):


# Требуется установка: pip install umap-learn
# import numpy as np
# import umap
# import matplotlib.pyplot as plt

# X - исходные данные
# k - целевая размерность
# n_neighbors_umap - количество соседей
# min_dist_umap - минимальное расстояние между точками во вложенном пространстве
# X_data = # ... ваши данные
# k_umap = 2
# n_neighbors_umap = 15
# min_dist_umap = 0.1

# reducer = umap.UMAP(n_components=k_umap,
#                    n_neighbors=n_neighbors_umap,
#                    min_dist=min_dist_umap,
#                    random_state=0)
# X_umap = reducer.fit_transform(X_data)

# print(f"Shape after UMAP: {X_umap.shape}")

# Визуализация (если k_umap=2)
# plt.figure(figsize=(6, 6))
# plt.scatter(X_umap[:, 0], X_umap[:, 1], c=y_labels) # Раскрасить по меткам, если есть
# plt.title('UMAP Visualization')
# plt.xlabel('UMAP Component 1')
# plt.ylabel('UMAP Component 2')
# plt.show()
Предыдущая глава    Следующая глава

Другие статьи по этой теме: