차원의 저주 : 훈련 샘플이 너무 커서 훈련을 느리게하고, 좋은 솔루션을 찾기 어렵게 만드는 것
실전에서는 특성 수를 크게 줄여서 문제를 해결 가능한 범위로 변경하는 방법으로 해결함
잡음이나 불필요한 사항을 걸러내기 때문에 성능이 높아질 수도 있다. 일반적으로는 '훈련 속도'만 빨라지는 경우가 많다.
데이터 시각화에도 유용하다. 보이지 않던 패턴을 감지해 중요한 인사이트를 얻을 수 있다.
차원을 축소시키면 일부 데이터가 유실된다. 따라서 훈련 속도가 빨라질 수는 있지만 성능이 조금 나빠질 수 있다. 또한, 작업 파이프라인이 조금 복잡해지고 유지 관리가 어려워진다. 그렇기 때문에 먼저 원본 데이터로 시스템을 훈련하는 것을 권장한다.
고차원 데이터셋은 저차원일 때보다 예측이 더 불안정.
훈련 세트의 차원이 클수록 과대적합 위험이 커짐.
대부분의 훈련 샘플은 모든 차원에 걸쳐 균일하게 퍼져있지 않음.


![]() | ![]() |
|---|
d차원 매니폴드는 d차원 초평면(hyperplane)으로 보일 수 있는 n차원 공간의 일부
스위스 롤의 경우에는 d=2, n=3
매니폴드 학습 : 훈련 샘플이 놓여 있는 매니폴드(manifold)를 모델링하는 식으로 작동. 대부분 실제 고차원 데이터셋이 더 낮은 저차원 매니폴드에 가깝게 놓여 있다는 매니폴드 가정에 근거함.
매니폴드 가정은 '처리해야 할 작업이 저차원의 매니폴드 공간에 표현되면 더 간단해질 것'이라는 가정과 병행되기도 함.
![]() | ![]() |
|---|
![]() | ![]() |
|---|
정리하자면, 훈련시키기 전에 훈련 세트의 차원을 감소시키면 훈련 속도는 빨라지지만 항상 더 나은 해결책이 되는 것은 아님. 전적으로 데이터셋에 달린 문제.
주성분 분석(principal component analysis, PCA) : 가장 인기 있는 차원 축소 알고리즘. 데이터에 가장 가까운 초평면을 정의한 다음, 데이터를 이 평면에 투영시키는 방법
올바른 초평면을 선택하는 것이 중요.

PCA는 훈련 세트에서 분산이 최대인 축을 탐색함. 그 축에 직교하고 남은 분산을 최대한 보존하는 두 번째 축을 찾음.
고차원 데이터셋이라면, PCA는 데이터셋에 있는 차원의 수만큼 세 번째, 네 번째, ... , n번째 축을 찾음.
i 번째 축을 이 데이터의 i번째 주성분(principal component, PC)라고 함.
훈련 세트의 주성분은 특잇값 분해(singular value decomposition, SVD)로 훈련 세트 행렬 를 세 개 행렬의 행렬 곱셈인 로 분해하여 구할 수 있음. 찾고자 하는 모든 주성분의 단위벡터가 에 담겨 있음.
import numpy as np
X = [...] # 임의의 3D 데이터셋
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt[0]
c2 = Vt[1]
주성분을 모두 추출했다면 처음 d개의 주성분으로 정의한 초평면에 투영하여 데이터셋의 차원을 d차원으로 축소시킬 수 있음. 이 초평면은 분산을 최대한 보존하는 투영.
초평면에 훈련 세트를 투영하고 d차원으로 축소된 데이터셋 를 얻기 위해서는 행렬 와 의 첫 d열로 구성된 행렬 를 행렬 곱해야 함.
W2 = Vt[:2].T
X2D = X_centered @ W2
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X2D = pca.fit_transform(X)
pca.explained_variance_ratio_
# array([0.7578477 , 0.15186921])
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', as_frame=False)
X_train, y_train = mnist.data[:60_000], mnist.target[:60_000]
X_test, y_test = mnist.data[60_000:], mnist.target[60_000:]
pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1 # d == 154
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_train)
pca.n_components_
# 154
분산을 차원 수에 대한 함수로 그리는 방법도 있음 (cumsum을 그래프로 그림)

지도 학습 작업의 전처리 단계로 차원 축소를 사용하는 경우, 다른 하이퍼파라미터와 마찬가지로 차원 수를 튜닝할 수 있음
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV
from sklearn.pipeline import make_pipeline
clf = make_pipeline(PCA(random_state=42),
RandomForestClassifier(random_state=42))
param_distrib = {
"pca__n_components" : np.arange(10, 80),
"randomforestclassifier__n_estimators" : np.arange(50, 500)
}
rnd_search = RandomizedSearchCV(clf, param_distrib, n_iter=10, cv=3,
random_state=42)
rnd_search.fit(X_train[:1000], y_train[:1000])
rnd_search.best_params_
# {'randomforestclassifier__n_estimators': 475, 'pca__n_components': 57}
차원 축소 후 훈련 세트는 훨씬 적은 공간을 차지함. 95%의 분산을 유지하도록 MNIST 데이터 세트에 PCA를 적용하면 784개의 특성 중에서 154개의 특성만 남음
→ 데이터셋의 크기는 원본의 20% 이하로 줄지만, 분산은 5%만 손실
압축된 데이터셋에 변환을 반대로 적용하여 784개의 차원으로 되돌릴 수도 있음. 투영에서 5%의 분산이 유실되었기 때문에 이렇게 해도 원본 데이터셋을 얻을 수는 없지만, 거의 원본이라고 할 수 있음.
재구성 오차(reconstruction error) : 원본 데이터와 재구성된 데이터 사이의 평균 제곱 거리
처음 d개의 주성분에 대한 근삿값을 빠르게 찾는 확률적 알고리즘
랜덤 PCA 계산 복잡도 :
완전한 SVD 계산 복잡도 :
→ d가 n보다 많이 작으면 완전 SVD보다 훨씬 빨라짐
rnd_pca = PCA(n_components=154, svd_solver="randomized", random_state=42)
X_reduced = rnd_pca.fit_transform(X_train)
PCA 구현의 문제 : SVD 알고리즘을 실행하기 위해 전체 훈련 세트를 메모리에 올려야 함
점진적 PCA(incremental PCA, IPCA) : 훈련 세트를 미니배치로 나눈 뒤 IPCA 알고리즘에 한 번에 하나씩 주입. 훈련 세트가 클 때 유용하고, 온라인으로 PCA 적용할 수 있음.
from sklearn.decomposition import IncrementalPCA
n_batches = 100
inc_pca = IncrementalPCA(n_components=154)
for X_batch in np.array_split(X_train, n_batches):
inc_pca.partial_fit(X_batch)
X_reduced = inc_pca.transform(X_train)
numpy의 mmap 클래스 : 디스크의 이진 파일에 저장된 대규모 배열을 메모리에 있는 것처럼 조작할 수 있게 함. 원하는 데이터만 메모리에 로드할 수 있음
filename = "my_mnist.mmap"
X_mmap = np.memmap(filename, dtype='float32', mode='write', shape=X_train.shape)
X_mmap[:] = X_train
X_mmap.flush()
X_mmap = np.memmap(filename, dtype='float32', mode='readonly').reshape(-1, 784)
batch_size = X_mmap.shape[0] // n_batches
inc_pca = IncrementalPCA(n_components=154, batch_size=batch_size)
inc_pca.fit(X_mmap)
원시 이진 데이터만 디스크에 저장되므로 배열을 로드할 때 데이터 타입과 배열의 크기를 지정해야함. 크기 안적으면 ID 배열을 반환.
매우 고차원인 데이터셋은 PCA가 매우 느려질 수 있음.
수만 개 이상의 특성이 있는 데이터셋을 다루는 경우에는 랜덤 투영을 사용하는 것을 고려해야 함.
랜덤 투영 알고리즘 : 랜덤한 선형 투영을 사용하여 데이터를 저차원 공간에 투영
더 많은 차원을 삭제할수록 더 많은 정보가 손실되고, 더 많은 거리가 왜곡됨. 주어진 허용 오차 이상으로 변하지 않도록 보장하기 위해 보존할 최소 차원 수를 결정하는 방정식이 구현된 함수.
from sklearn.random_projection import GaussianRandomProjection
ε = 0.1
n = 20_000
np.random.seed(42)
P = np.random.randn(d, n) / np.sqrt(d)
X = np.random.randn(m, n)
X_reduced = X @ P.T
gaussian_rnd_proj = GaussianRandomProjection(eps=ε, random_state=42)
X_reduced = gaussian_rnd_proj.fit_transform(X)
GaussianRandomProjection과 동일한 방식으로 타깃 차원을 결정하고 동일한 크기의 랜덤 행렬을 생성한 후 투영 동일하게 수행. 랜덤 행렬이 희소하다는 점은 다름.
랜덤 행렬을 생성하고 차원을 줄이는 데 있어서도 빠르고, 입력이 희소할 경우 이 변환은 희소성을 유지함.
이전 접근 방식과 동일한 거리 보존 속성을 가지며 차원 축소 품질도 비슷하기 때문에, 일반적으로 규모가 크거나 희박한 데이터셋의 경우 이 변환기를 사용하는 것이 바람직함.
components_pinv = np.linalg.pinv(gaussian_rnd_proj.components_)
X_recovered = X_reduced @ components_pinv.T
랜덤 투영은 간단하고 빠르며 효율이 높고 강력한 차원 축소 알고리즘으로, 특히 고차원 데이터셋을 다룰 때 염두에 두어야 함.
지역 선형 임베딩(locally linear embedding, LLE)은 비선형 차원 축소(nonlinear dimensionality reduction, NLDR)기술
투영에 의존하지 않는 매니폴드 학습으로,
1. 각 훈련 샘플이 최근접 이웃에 얼마나 선형적으로 연관되어 있는지 측정하고
2. 국부적인 관계가 가장 잘 보존되는 훈련 세트의 저차원 표현을 찾음
잡음이 너무 많지 않은 경우 꼬인 매니폴드를 펼치는 데 효과적
from sklearn.datasets import make_swiss_roll
from sklearn.manifold import LocallyLinearEmbedding
X_swiss, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)
X_unrolled = lle.fit_transform(X_swiss)
