← Назад к вопросам
Как определить процент потерянной дисперсии при использовании метода PCA?
1.0 Junior🔥 121 комментариев
#Статистика и A/B тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Определение потери дисперсии при использовании PCA
PCA (Principal Component Analysis) — мощный метод снижения размерности, но он неизбежно приводит к потере информации. Правильное определение этой потери критично для выбора оптимального количества компонент. Расскажу о практических методах, которые использовал в различных проектах.
1. Основная концепция: Explained Variance Ratio
Каждый главный компонент объясняет определённый процент дисперсии данных. Потеря дисперсии = 100% минус объяснённая дисперсия.
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import numpy as np
import matplotlib.pyplot as plt
# Загружаем данные
X = load_iris().data
print(f"Оригинальная размерность: {X.shape[1]}")
# Применяем PCA со всеми компонентами
pca_full = PCA()
pca_full.fit(X)
# Explained Variance Ratio
explained_variance = pca_full.explained_variance_ratio_
cumulative_variance = np.cumsum(explained_variance)
print("\nДисперсия, объяснённая каждым компонентом:")
for i, var in enumerate(explained_variance):
print(f"PC{i+1}: {var:.4f} ({var*100:.2f}%)")
print("\nКумулятивная объяснённая дисперсия:")
for i, cum_var in enumerate(cumulative_variance):
print(f"PC1-PC{i+1}: {cum_var:.4f} ({cum_var*100:.2f}%)")
# Потеря дисперсии
print(f"\nПотеря при использовании 2 компонент: {(1 - cumulative_variance[1])*100:.2f}%")
2. График "Scree Plot" для визуализации потери
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# График 1: Индивидуальная дисперсия
axes[0].bar(range(1, len(explained_variance) + 1), explained_variance, alpha=0.7, color='steelblue')
axes[0].plot(range(1, len(explained_variance) + 1), explained_variance, 'ro-', linewidth=2)
axes[0].set_xlabel('Номер компонента')
axes[0].set_ylabel('Объяснённая дисперсия')
axes[0].set_title('Scree Plot: Дисперсия по компонентам')
axes[0].grid(True, alpha=0.3)
# График 2: Кумулятивная дисперсия
axes[1].plot(range(1, len(cumulative_variance) + 1), cumulative_variance, 'bo-', linewidth=2, markersize=8)
axes[1].axhline(y=0.95, color='r', linestyle='--', label='95% порог')
axes[1].axhline(y=0.90, color='orange', linestyle='--', label='90% порог')
axes[1].set_xlabel('Количество компонент')
axes[1].set_ylabel('Кумулятивная объяснённая дисперсия')
axes[1].set_title('Кумулятивная дисперсия')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Определение количества компонент для 95% дисперсии
n_components_95 = np.argmax(cumulative_variance >= 0.95) + 1
print(f"\nКоличество компонент для 95% дисперсии: {n_components_95}")
print(f"Потеря дисперсии: {(1 - cumulative_variance[n_components_95 - 1])*100:.2f}%")
3. Методы выбора оптимального числа компонент
Метод 1: Пороговое значение (90%, 95%, 99%)
def find_n_components(pca, threshold=0.95):
"""
Находит количество компонент, необходимых для объяснения threshold% дисперсии
"""
cumulative_var = np.cumsum(pca.explained_variance_ratio_)
n_components = np.argmax(cumulative_var >= threshold) + 1
actual_variance_retained = cumulative_var[n_components - 1]
variance_lost = 1 - actual_variance_retained
return n_components, actual_variance_retained, variance_lost
n_comp_90, var_90, loss_90 = find_n_components(pca_full, threshold=0.90)
n_comp_95, var_95, loss_95 = find_n_components(pca_full, threshold=0.95)
n_comp_99, var_99, loss_99 = find_n_components(pca_full, threshold=0.99)
print(f"90% дисперсии: {n_comp_90} компонент, потеря {loss_90*100:.2f}%")
print(f"95% дисперсии: {n_comp_95} компонент, потеря {loss_95*100:.2f}%")
print(f"99% дисперсии: {n_comp_99} компонент, потеря {loss_99*100:.2f}%")
Метод 2: Elbow Method (метод локтя)
# Находим "локоть" — точку, где прирост дисперсии замедляется
differences = np.diff(explained_variance)
print("\nПримерный прирост дисперсии между компонентами:")
for i, diff in enumerate(differences):
print(f"PC{i+1} → PC{i+2}: {diff:.4f}")
# Визуально найти локоть
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(explained_variance) + 1), explained_variance, 'o-', linewidth=2, markersize=8)
plt.xlabel('Номер компонента')
plt.ylabel('Объяснённая дисперсия')
plt.title('Метод локтя: где кривая замедляет рост?')
plt.grid(True, alpha=0.3)
plt.show()
# Автоматический поиск локтя
elbows = []
for i in range(1, len(differences)):
if differences[i-1] - differences[i] > 0.02: # Примерный порог
elbows.append(i+1)
print(f"\nПредполагаемый локоть на компоненте: {elbows[0] if elbows else 'не найден'}")
Метод 3: Kaiser Criterion (собственные значения > 1)
# Для нормализованных данных
eigenvalues = pca_full.explained_variance_
kaisier_n_components = np.sum(eigenvalues > 1)
print(f"\nПо критерию Кайзера (eigenvalue > 1): {kaisier_n_components} компонент")
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(eigenvalues) + 1), eigenvalues, 'o-', linewidth=2, markersize=8)
plt.axhline(y=1, color='r', linestyle='--', label='Kaiser Criterion (λ=1)')
plt.xlabel('Номер компонента')
plt.ylabel('Собственное значение')
plt.title('Критерий Кайзера')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
4. Практический пример: Полный анализ потери информации
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
# Загружаем данные
X = load_digits().data
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# PCA со всеми компонентами
pca = PCA()
pca.fit(X_scaled)
# Анализ потери информации
print("===== АНАЛИЗ ПОТЕРИ ИНФОРМАЦИИ =====")
print(f"Оригинальная размерность: {X.shape[1]}")
print(f"Количество выборок: {X.shape[0]}")
# Сценарии использования
scenarios = [
("Максимальное сжатие (50% дисперсии)", 0.50),
("Среднее сжатие (80% дисперсии)", 0.80),
("Минимальное сжатие (95% дисперсии)", 0.95),
("Практически без потерь (99% дисперсии)", 0.99),
]
for scenario_name, threshold in scenarios:
n_comp, var_retained, var_lost = find_n_components(pca, threshold)
print(f"\n{scenario_name}:")
print(f" Компонент: {n_comp} / {pca.n_components_}")
print(f" Объяснённая дисперсия: {var_retained*100:.2f}%")
print(f" Потеря дисперсии: {var_lost*100:.2f}%")
print(f" Сжатие: {X.shape[1] / n_comp:.1f}x")
5. Связь между потерей дисперсии и производительностью модели
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# Загружаем данные с классами
X, y = load_digits(return_X_y=True)
X_scaled = StandardScaler().fit_transform(X)
# PCA
pca = PCA()
pca.fit(X_scaled)
cumulative_var = np.cumsum(pca.explained_variance_ratio_)
# Проверяем производительность для разного количества компонент
scores_pca = []
for n_comp in range(1, len(pca.explained_variance_ratio_) + 1, 5):
pca_temp = PCA(n_components=n_comp)
X_pca = pca_temp.fit_transform(X_scaled)
rf = RandomForestClassifier(n_estimators=10, random_state=42)
score = cross_val_score(rf, X_pca, y, cv=3, scoring='accuracy').mean()
loss = (1 - cumulative_var[n_comp - 1]) * 100
scores_pca.append((n_comp, score, loss))
print(f"Компонент: {n_comp}, Accuracy: {score:.3f}, Потеря дисперсии: {loss:.2f}%")
# Визуализация
components = [x[0] for x in scores_pca]
accuracies = [x[1] for x in scores_pca]
losses = [x[2] for x in scores_pca]
fig, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(components, accuracies, 'b-o', linewidth=2, markersize=8, label='Accuracy')
ax1.set_xlabel('Количество компонент')
ax1.set_ylabel('Точность', color='b')
ax1.tick_params(axis='y', labelcolor='b')
ax2 = ax1.twinx()
ax2.plot(components, losses, 'r--o', linewidth=2, markersize=8, label='Потеря дисперсии (%)')
ax2.set_ylabel('Потеря дисперсии (%)', color='r')
ax2.tick_params(axis='y', labelcolor='r')
plt.title('Влияние потери дисперсии на производительность')
fig.tight_layout()
plt.show()
6. Практические рекомендации
- Для EDA и визуализации: 95% дисперсии — хороший компромисс
- Для снижения шума: 80-90% позволяет отфильтровать помехи
- Для максимальной скорости: 50-70% при приемлемом качестве
- Всегда проверяйте: Как потеря дисперсии влияет на метрику вашей задачи
Потеря дисперсии — это не просто число, это реальная потеря информации. Выбор порога зависит от конкретной задачи и баланса между сжатием данных и качеством результата.