PCA — principalkomponentanalys
Kunna reducera dimensioner med PCA och tolka förklarad varians.
Förkunskaper
Intuition
PCA hittar de riktningar där datan varierar mest, och projicerar ner på dem.
Tänk dig ett moln av punkter som ligger utsträckt längs en diagonal. Den första principalkomponenten är den linjen. Den andra är vinkelrät mot den, längs den näst största spridningen. Och så vidare.
Varför variation? Därför att en riktning där allt ligger på samma ställe inte skiljer datapunkterna åt — den bär ingen information om vad som är olika.
Tre saker PCA används till:
| Användning | Hur |
|---|---|
| Komprimering | 100 kolumner → 10, med 95 % av variationen kvar |
| Visualisering | projicera ner till 2D och rita |
| Avbrusning | kasta de sista komponenterna, som ofta är mätbrus |
Den avgörande förberedelsen: centrera alltid datan (dra bort medelvärdet), och skala om kolumnerna har olika enheter. Annars mäter PCA enheter i stället för struktur.
Formellt
Två ekvivalenta beskrivningar:
- Egenvektorerna till kovariansmatrisen (efter centrering), sorterade efter egenvärde.
- Högersingulärvektorerna från SVD av den centrerade .
Den andra är vad man faktiskt beräknar — den är numeriskt stabilare och behöver aldrig bilda kovariansmatrisen. Sambandet är .
Förklarad varians för komponent :
Hur många komponenter? Tre vanliga regler:
| Regel | Hur |
|---|---|
| Tröskel | behåll tills kumulativ varians når 90–95 % |
| Armbågen | leta efter knäet i scree-plotten |
| Nedströms | välj den som ger bäst resultat i den modell som följer |
Den sista är den ärligaste när PCA är ett förbehandlingssteg.
Skalning är inte valfritt. Har du vikt i gram (0–100 000) och längd i meter (0–2) kommer den första principalkomponenten att vara vikt, oavsett vad datan egentligen handlar om. Variansen är inte enhetslös. Standardisera först, om inte alla kolumner redan har samma enhet.
Läckage: fit på träningsdatan, transform på både träning och test. Att köra fit_transform på allt är samma fel som med skalning.
Begränsningar:
| Begränsning | Betyder |
|---|---|
| Linjär | fångar inte krökta grenrör — en spiral rullas inte ut |
| Varians ≠ användbarhet | den viktigaste riktningen för klassificering kan ha låg varians |
| Svårtolkade komponenter | varje komponent är en blandning av alla originalvariabler |
| Känslig för avvikare | en extrem punkt kan dominera första komponenten |
Den andra punkten är viktig: PCA är oövervakad och vet inget om etiketterna. Behöver du den riktning som bäst separerar klasser är LDA rätt verktyg, inte PCA.
För visualisering är t-SNE och UMAP ofta bättre på att visa klusterstruktur — men de bevarar inte avstånd globalt och deras utdata ska inte matas vidare in i en modell.
Kod
import numpy as np
# 800 punkter i 30 dimensioner — men egentligen bara 4 latenta faktorer plus brus
rng = np.random.default_rng(0)
Z = rng.normal(size=(800, 4))
X = Z @ rng.normal(size=(4, 30)) + 0.5 * rng.normal(size=(800, 30))
Xc = X - X.mean(axis=0) # CENTRERA — annars mäter PCA fel sak
U, s, Vt = np.linalg.svd(Xc, full_matrices=False)
andel = s**2 / (s**2).sum()
print(np.round(s[:8], 1))
# [196.9 152.2 134.3 120.5 16.6 16.1 15.9 15.7]
# ↑ fyra stora, sedan ett tydligt hopp ner till brusnivån
print(np.round(andel[:6], 4)) # [0.3892 0.2324 0.1811 0.1458 0.0028 0.0026]
print(np.round(np.cumsum(andel)[:6], 4)) # [0.3892 0.6215 0.8026 0.9484 0.9511 0.9537]
print("komponenter för 95 %:", int(np.searchsorted(np.cumsum(andel), 0.95) + 1)) # 5
# Rekonstruktionsfel: hur mycket går förlorat vid k komponenter?
for k in (2, 4, 10):
Xk = U[:, :k] * s[:k] @ Vt[:k]
print(f" k={k:>2} relativt fel {np.linalg.norm(Xc - Xk) / np.linalg.norm(Xc):.4f}")
# k= 2 relativt fel 0.6152
# k= 4 relativt fel 0.2273 ← de fyra verkliga faktorerna
# k=10 relativt fel 0.1913 ← knappt bättre; resten är brus
# Skalningens betydelse: en kolumn i en annan enhet
Xo = X.copy(); Xo[:, 0] *= 10_000
def andelar(M, n=2):
s2 = np.linalg.svd(M - M.mean(axis=0), compute_uv=False) ** 2
return np.round((s2 / s2.sum())[:n], 3)
print("utan skalning:", andelar(Xo)) # [1. 0.]
print("med skalning: ", andelar((Xo - Xo.mean(0)) / Xo.std(0))) # [0.332 0.247]
# ↑ utan standardisering äter den stora kolumnen upp hela första komponenten
I praktiken lägger man PCA i en Pipeline tillsammans med skalningen och modellen, så att fit körs om inom varje korsvalideringsveck:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
pipe = Pipeline([("skala", StandardScaler()),
("pca", PCA(n_components=20)),
("klassificerare", LogisticRegression(max_iter=2000))])
print(cross_val_score(pipe, X, y, cv=5).mean())
Variera n_components och låt resultatet avgöra — det är den ärligaste regeln när PCA är ett förbehandlingssteg. Ofta slår ett urval av komponenter alla, eftersom de sista bär mest brus.
Behärskning innebär
- Utför PCA och tolkar förklarad varians
- Vet varför centrering och skalning behövs
- Känner till PCA:s begränsningar
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- Mathematics for Machine Learning (Deisenroth m.fl.) — fri att läsa online (författarnas utgåva)
- scikit-learn User Guide (BSD-3) — BSD-3-Clause
- Dive into Deep Learning (CC BY-SA 4.0) — CC BY-SA 4.0