import pandas as pd
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings(action='ignore')
import matplotlib
from IPython.display import set_matplotlib_formats
set_matplotlib_formats('retina')
matplotlib.rc('font', family='AppleGothic')
matplotlib.rc('axes', unicode_minus=False)
pd.options.display.max_rows=100
pd.options.display.max_columns=100
pd.set_option('display.float_format', '(:.2f)'.format)
8. 차원 축소¶
많은 경우에 머신러닝 문제는 훈련 샘플 각각이 수천 심지어 수백만 개의 특성을 가지고 있는데, 이는 훈련을 느리게 할 뿐만 아니라, 좋은 솔루션을 찾기 어렵게 만든다. 이런 문제를 종종 차원의 저주(curse of dimensionality) 라고 한다.
- 차원을 축소시키면 일부 정보가 유실됩니다. 그래서 훈련 속도가 빨라질 수는 있지만 시스템의 성능이 조금 나빠질 수 있습니다. 또한 작업 파이프라인이 조금 더 복잡하게 되고 유지 관리가 어려워집니다. 그러므로 차원 축소를 고려하기 전에 훈련이 너무 느린지 먼저 원본 데이터로 시스템을 훈련시켜 봐야 합니다. 그러나 어떤 경우에는 훈련 데이터의 차원을 축소시키면 잡음이나 불필요한 세부사항을 걸러내므로 성능을 높일 수 있습니다.(일반적으로는 훈련 속도만 빨라집니다)
8.1 차원의 저주¶
고차원의 데이터셋은 매우 희박한 상태일 수 있습니다.. 즉, 대부분의 훈련 데이터가 서로 멀리 떨어져 있습니다. 물론 이는 새로운 샘플도 훈련 샘플과 멀리 떨어져 있을 가능성이 높다는 뜻입니다. 이 경우 예측을 위해 훨씬 많은 외삽(extrapolation)을 해야 하기 때문에 저차원일 때보다 예측이 더 불안정합니다. 간단히 말해 훈련 세트의 차원이 클수록 과대적합 위험이 커집니다.
이론적으로 차원의 저주를 해결하는 해결책 하나는 훈련 샘플의 밀도가 충분히 높아질 때까지 훈련 세트의 크기를 키우는 것이지만, 불행하게도 실제로는 일정 밀도에 도달하기 위해 필요한 훈련 샘플 수는 차원 수가 커짐에 따라 기하급수적으로 늘어납니다.
8.2 차원 축소를 위한 접근 방법¶
투영(projection)과 매니폴드 학습(manifold learning)
8.2.1 투영¶
대부분의 실전문제는 훈련 샘플이 모든 차원에 걸쳐 균일하게 퍼져 있지 않습니다. 많은 특성은 거의 변화가 없는 반면, 다른 특성들은 서로 강하게 연관되어 있습니다. 결과적으로 모든 훈련 샘플이 사실 고차원 공간 안의 저차원 부분 공간(subspace)(또는 가까이)에 놓여 있습니다.
투영은, 예를 들어 3차원 공간에 평면에 가깝에 놓여있는 데이터 셋을 2차원으로 수직으로 투영하는 경우가 있다. 하지만 스위스 롤 데이터셋처럼 부분 공간이 뒤틀리거나 휘어 있으면 2차원 평면에 투영하게 될 경우 층이 서로 뭉개지게 된다.
8.2.2 매니폴드 학습¶
스위스 롤은 2D 매니폴드의 한 예입니다. 간단히 말해 2D 매니폴드는 고차원 공간에서 휘어지거나 뒤틀린 2D모양입니다.
많은 차원 축소 알고리즘이 훈련 샘플이 놓여 있는 매니폴드를 모델링하는 식으로 작동합니다. 이를 매니폴드 학습(manifold learning)이라고 합니다. 이는 대부분 실제 고차원 데이터셋이 더 낮은 저차원 매니폴드에 가깝게 놓여 있다는 매니폴드 가정(manifold assumption) 또는 매니폴드 가설(manifold hypothesis)에 근거합니다.
모델을 훈련시키기 전에 훈련 세트의 차원을 감소시키면 훈련 속도는 빨라지지만 항상 더 낫거나 간단한 솔루션이 되는 것은 아닙니다. 이는 전적으로 데이터셋에 달렸습니다.
8.3 PCA¶
주성분 분석(Principal Component Analysis = PCA)은 가장 인기 있는 차원 축소 알고리즘입니다. 먼저 데이터에 가장 가까운 초평면(hyperplane)을 정의한 다음, 데이터를 이 평면에 투영시킵니다.
8.3.1 분산 보존¶
저차원의 초평면에 훈련 세트를 투영하기 전에 먼저 올바른 초평면을 선택해야 합니다. 다른 방향으로 투영하는 것보다 분산이 최대로 보존되는 축을 선택하는 것이 정보가 가장 적게 손실되므로 합리적입니다. 즉, 원본 데이터셋과 투영된 것 사이의 평균 제곱 거리를 최소화하는 축을 선택합니다.
8.3.2 주성분¶
PCA는 훈련 세트에서 분산이 최대인 축을 찾습니다. 또한 첫 번째 축에 직교하고 남은 분산을 최대한 보존하는 두 번째 축을 찾습니다. 이런 방식으로 데이터셋에 있는 차원의 수만큼 세 번째, 네 번쩨,... 축을 찾습니다.
i번째 축을 정의하는 단위 벡터를 i번째 주성분(Principal Component = PC)이라고 부릅니다.
그럼 훈련 세트의 주성분을 어떻게 찾을까요? 다행히 특잇값 분해(Singular Value Decomposition = SVD)라는 표준 행렬 분해 기술이 있어서 훈련 세트 행렬 X를 세 개 행렬의 점곱으로 분해할 수 있습니다.
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]
--------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-3-a93d2ae7438b> in <module> ----> 1 X_centered = X - X.mean(axis=0) 2 U, s, Vt = np.linalg.svd(X_centered) NameError: name 'X' is not defined
위 코드는 넘파이의 svd() 함수를 사용해 훈련 세트의 모든 주성분을 구한 후 처음 두 개의 PC를 추출합니다.
8.3.3 d차원으로 투영하기¶
주성분을 모두 추출해냈다면 처음 d개의 주성분으로 정의한 초평면에 투영하여 데이터셋의 차원을 d차원으로 축소시킬 수 있습니다. 이 초평면은 분산을 가능한 한 최대로 보존하는 투영임을 보장합니다.
W2 = Vt.T[:, :2]
X2D = X_centered.dot(W2)
위 코드는 첫 두 개의 주성분으로 정의된 평면에 훈련 세트를 투영합니다.
8.3.4 사이킷런 사용하기¶
from sklearn.decomposition import PCA
pca = PCA(n_component = 2)
X2D = pca.fit_transform(X)
PCA 모델을 사용해 데이터셋의 차원을 2로 줄이는 코드입니다.(사이킷런의 PCA모델은 자동으로 데이터를 중앙에 맞춰줍니다.)
PCA 변환기를 데이터셋에 학습시키고 나면 components 변수를 사용해 주성분을 확인할 수 있습니다(이 변수에는 주성분이 행 벡터로 포함되어 있으므로 첫 번째 주성분은 pca.components.T[:, 0]이 됩니다).
8.3.5 설명된 분산의 비율¶
explained_varianceratio 변수에 저장된 주성분의 설명된 분산의 비율(explained variance ratio) 도 유용한 정보 중 하나입니다.
ex) array([0.84, 0.14]) -> 데이터셋 분산의 84%가 첫 번째 축에 놓여 있고 14%가 두번째 축에 놓여 있음을 알려준다. 세번 째 축에는 2% 미만이 남아 있을 것이므로 아주 적은 양의 정보가 들어 있다고 생각해도 된다.)
pca.explained_variance_ratio_
8.3.6 적절한 차원 수 선택하기¶
충분한 분산(예를 들면 95%)이 될 때까지 더해야할 차원 수를 선택한다. 데이터 시각화를 위해 차원을 축소하는 경우에는 차원을 2개나 3개로 줄이는 것이 일반적이다.
pca = PCA()
pca.fit(X_train)
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1
그런 다음 n_components=d로 설정하여 PCA를 다시 실행한다.
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_train)
또 다른 방법은 설명된 분산을 차원 수에 대한 함수로 그리는 것입니다.(cumsum을 그래프로 그리면 됨) 일반적으로 이 그래프에는 설명된 분산의 빠른 성장이 멈추는 변곡점이 있는데, 이를 데이터셋에 내재된 고유 차원으로 생각할 수 있다.
8.3.7 압축을 위한 PCA¶
차원을 축소하고 난 후에는 훈련 세트의 크기가 줄어듭니다. 예를 들어 MNIST 데이터 셋에 분산의 95%를 유지하도록 PCA를 적용하면, 각 샘플은 원래의 784개 특성이 아니라 150개 정도만 가지고 있을 것십니다. 대부분의 분산은 유지되었지만 데이터셋은 원본 크기의 20% 미만이 되었습니다. 이는 상당한 압축률이고 (SVM같은) 분류 알고리즘의 속도를 크게 높일 수 있습니다.
또한 압축된 데이터셋에 PCA 투영의 변환을 반대로 적용하려 784개의 차원으로 되돌릴 수도 있습니다. 물론 투영에서 일정량의 정보(유실된 5%의 분산)를 잃어버렸기 때문에 이렇게 해도 원본 데이터셋을 얻을 수는 없습니다. 하지만 원본 데이터와 매우 비슷할 것입니다. 언본 데이터와 재구성된 데이터(압축 후 원복한 것) 사이의 평균 제곱 거리를 재구성 오차(reconstruction error) 라고 합니다.
pca = PCA(n_components = 154)
X_reduced = pca.fit_transform(X_train)
X_recovered = pca.inverse_transform(X_reduced)
8.3.8 점진적 PCA¶
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)
x_mm = np.memmap(filename, dtype='float32', mode="readonly", shape=(m, n))
batch_size = m // n_batches
inc_pca = IncrementalPCA(n_components=154, batch_size=batch_size)
inc_pca.fit(X_mm)
8.3.9 랜덤 PCA¶
랜덤 PCA (Randomized PCA) = 확률적인 알고리즘으로, 첫 d개의 주성분에 대한 근삿값을 빠르게 찾습니다.
rnd_pca = PCA(n_components=154, svd_solver="randomized")
X_reduced = rnd_pca.fit_transform(X_train)
8.4 커널 PCA¶
커널 PCA (Kernel PCA = kPCA) = 복잡한 비선형 투영으로의 차원 축소를 가능하게 함, 투영된 후에 샘플의 군집을 유지하거나 꼬인 매니폴드에 가까운 데이터셋을 펼칠 때 유용하다.
ex) 선형 커널, RBF 커널, 시그모이드 커널
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components=2, kernel='rbf', gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)
8.4.1 커널 선택과 하이퍼파라미터 튜닝¶
kPCA는 비지도 학습이기 때문에 좋은 커널과 하이퍼파라미터를 선택하기 위한 명확한 성능 측정 기준이 없습니다. 하지만 차원 축소는 종종 지도 학습(예를 들면 분류)의 전처리 단계로 활용되므로 그리드 탐색을 사용하여 주어진 문제에서 성능이 가장 좋은 커널과 하이퍼파라미터를 선택할 수 있습니다.
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
clf = Pipeline([
("kpca", KernelPCA(n_components=2)),
("log_reg", LogisticRegression())
])
param_grid = [{
"kpca__gamma": np.linspace(0.03, 0.05, 10),
"kpca__kernel": ["rbf", "sigmoid"]
}]
grid_search = GridSearchCV(clf, param_grid, cv=3)
grid_search.fit(X, y)
print(grid_search.best_params_)
축소된 공간에 있는 샘플에 대해 선형 PCA를 역전시키면 재구성된 데이터 포인트는 원본 공간이 아닌 특성 공간에 놓이게 됩니다. 이 특성 공간은 무한 차원이기 때문에 재구성된 포인트를 계산할 수 없고 재구성에 따른 실제 에러를 계산할 수 없습니다. 다행히 재구성된 포인트에 가깝게 매핑된 원본 공간의 포인트를 찾을 수 있습니다. 이를 재구성 원상(pre-image)라고 부릅니다. 원상을 얻게 되면 원본 샘플과의 제곱 거리를 측정할 수 있습니다. 그래서 재구성 원상의 오차를 최소화하는 커널과 하이퍼파라미터를 선택할 수 잇습니다.
재구성하는 한 가지 방법은 투영된 샘플을 훈련 세트로, 원본 샘플을 타깃으로 하는 지도 학습 회귀 모델을 훈련시키는 것입니다. 사이킷런에서는 fit_inverse_transform=True로 지정하면 이를 자동으로 수행합니다.
rbf_pca = KernelPCA(n_components=2, kernel="rbf", gamma=0.0433,
fit_inverse_transform=True)
X_reduced = rbf_pca.fit_transform(X)
X_preimage = rbf_pca.inverse_transform(X_reduced)
from sklearn.metrics import mean_squared_error
mean_squared_error(X, X_preimage)
이렇게 되면 재구성 원상 오차를 최소화하는 커널과 하이퍼파라미터를 찾기 위해 교차 검증으로 그리드 탐색을 사용할 수 있습니다.
8.5 LLE¶
지역 선형 임베딩(Locally Linear Embedding = LLE)은 또 다른 강력한 비선형 차원 축소(nonlinear dimensionality reduction = NLDR) 기술입니다. 이 또한 투영에 의존하지 않는 매니폴드 학습입니다. 간단히 말해 LLE는 먼저 각 훈련 샘플이 가장 가까운 이웃(closest neighbor)에 얼마나 선형적으로 연관되어 있는지 측정합니다. 그런 다음 국부적인 관계가 가장 잘 보존되는 훈련 세트의 저차원 표현을 찾습니다. 이는 특히 잡음이 너무 많지 않은 경우 꼬인 매니폴드를 펼치는 데 잘 작동합니다.
예를 들어 사이킷런의 LocallyLinearEmbedding을 사용해 스위스 롤을 펼칩니다.
from sklearn.minifold import LocallyLinearEmbedding
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10)
X_reduced = lle.fit_transform(X)
--------------------------------------------------------------------------- ModuleNotFoundError Traceback (most recent call last) <ipython-input-6-423a03e06ae4> in <module> ----> 1 from sklearn.minifold import LocallyLinearEmbedding 2 3 lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10) 4 X_reduced = lle.fit_transform(X) ModuleNotFoundError: No module named 'sklearn.minifold'
- 이 알고리즘은 대량의 데이터셋에 적용하기는 어렵습니다.
8.6 다른 차원 축소 기법¶
- 다차원 스케일링(Multidimensional Scaling = MDS) : 샘플 간의 거리를 보존하면서 차원을 축소합니다.
- Isomap : 샘플을 가장 가까운 이웃과 연결하는 식으로 그래프를 만듭니다. 그런 다음 샘플 간의 지오데식 거리(geodesic distance)를 유지하면서 차원을 축소합니다.
- t-SNE(t-Distributed Stochastic Neighbor Embedding) : 비슷한 샘플은 가까이, 비슷하지 않은 샘플은 멀리 떨어지도록하면서 차원을 축소한다. 주로 시각화에 많이 사용되며 특히 고차원 공간에 있는 샘플의 군집을 시각화할 때 사용된다.
- 선형 판별 분석(Linear Discriminant Analysis = LDA)은 사실 분류 알고리즘 입니다. 하지만 훈련 과정에서 클래스 사이를 가장 잘 구분하는 축을 학습합니다. 이 축은 데이터가 투영되는 초평면을 정의하는 데 사용할 수 있습니다. 이 알고리즘의 장점은 투영을 통해 가능한 한 클래스를 멀리 떨어지게 유지시키므로 SVM 분류기 같은 다른 분류 알고리즘을 적용하기 전에 차원을 축소시키는 데 좋스빈다.
8.7 연습문제¶
'Data Analysis > Hands On ML' 카테고리의 다른 글
[Hands On ML] 7. 앙상블 학습과 랜덤 포레스트 (0) | 2021.11.04 |
---|---|
[Hands On ML] 6. 결정 트리 (0) | 2021.11.04 |
[Hands On ML] 5. 서포트 벡터 머신 (0) | 2021.11.04 |
[Hands On ML] 4. 모델 훈련 (0) | 2021.11.04 |
[Hands On ML] 3. 분류 (0) | 2021.11.04 |