차원 축소
실제로 머신 러닝 문제를 다룰 때에는 훈련 샘플이 수천 심지어 수백만 개의 특성을 갖고 있을 것입니다.
이런 많은 특성은 훈련을 느리게 하고, 좋은 솔루션을 찾기 어렵게 만듭니다.
이런 문제를 차원의 저주라고 합니다.
다행히도 실전 문제에서는 특성 수를 크게 줄여서
불가능한 문제를 가능한 범위로 바꿀 수 있는 경우가 많습니다.
차원의 축소는 분류 문제에 크게 중요하지 않은 부분을 걸러내어 특성 수를 줄이는 것입니다.
차원을 축소시키면 일부 정보가 유실되므로 속도는 빨라질 수 있지만 시스템의 성능이 나빠질 수 있습니다.
그리고 작업 파이프라인이 복잡해지고 유지 관리가 어려워집니다.
따라서 차원 축소를 고려하기 전에 훈련이 너무 느린지먼저 테스트해봐야합니다.
어떤 경우에는 차원을 축소시키면 잡음이나 불필요한 세부사항을 걸러내서 성능이 향상될 수도 있습니다.
그러나 일반적으로는 속도만 빨라집니다.
또한 훈련 속도를 높이는 것 외에
데이터 시각화에도 유용합니다.
3차원보다 높은 차원의 고차원 데이터는 우리가 시각적으로 파악하기 힘듭니다.
고차원 데이터를 우리가 파악할 수 있는 차원으로 차원축소하여 압축된 그래프로 그릴 수 있습니다.
이런 그래프를 보고 중요한 통찰을 얻을 수도 있습니다.
차원 축소를 위한 접근 방법
차원을 감소시키는 두 가지 중요한 접근법인 투영과 매니폴드 학습을 살펴보겠습니다.
투영
대부분의 실전 문제는 훈련 샘플이 모든 차원에 걸쳐 균일하게 퍼져 있지 않고,
많은 특성은 거의 변화가 없는 반면,
다른 특성들은 서로 강하게 연관되어 있습니다.
결과적으로 모든 훈련 샘플이 고차원 공간 안의 저차원 부분 공간에 놓여 있습니다.
위의 3차원 데이터셋은 모든 훈련 샘플이 거의 평면 형태로 놓여 있습니다.
이것이 고차원 공간 안의 저차원 부분 공간입니다.
모든 샘플을 이 부분 공간에 수직으로 투영하면 아래와 같이 됩니다.
차원 축소를 할 때 투영이 언제나 최선의 방법은 아닙니다.
많은 경우 스위스 롤 데이터셋처럼 부분 공간이 뒤틀려있거나 휘어 있기도 합니다.
이걸 평면에 투영시키면 왼쪽 그림처럼 뭉개지는데,
우리가 원하는 건 스위스 롤을 펼쳐서 오른쪽처럼 데이터셋을 차원 축소하는 것입니다.
매니폴드 학습
스위스 롤은 2D 매니폴드의 한 예입니다.
2D 매니폴드는 고차원 공간에서 휘거나 뒤틀린 2D 모양입니다.
d차원 매니폴드는 국부적으로 d차원 초평면으로 보일 수 있는 n차원 공간의 일부입니다. (d < n)
많은 차원 축소 알고리즘이 훈련 샘플이 놓여 있는 매니폴드를 모델링하는 식으로 작동합니다.
이를 매니폴드 학습이라고 합니다.
이는 대부분 실제 고차원 데이터셋이 더 낮은 저차원 매니폴드에 가깝게 놓여 있다는 매니폴드 가정에 근거합니다.
매니폴드 가정은 종종 처리해야 할 작업이 저차원의 매니폴드 공간에 표현되면
더 간단해질 거라는 가정과 병행되곤 합니다.
이런 가정이 항상 유효하지는 않습니다.
예를 들어 아래의 1행의 그래프는 3차원 공간에서는 결정 경계가 복잡하지만 2차원 공간에서는 아주 간단합니다.
하지만 2행의 그래프는 3차원 공간에서는 결정 경계가 간단하지만 2차원 공간에서는 복잡합니다.
그러니까, 훈련 세트의 차원을 감소시키면 속도는 빨라지지만 항상 더 낫거나 간단한 솔루션이 되지는 않습니다.
어떻게 될지는 전적으로 데이터셋에 달려 있습니다.
PCA
주성분 분석(PCA)는 가장 인기 있는 차원 축소 알고리즘입니다.
먼저 데이터에 가장 가까운 초평면을 정의한 다음 데이터를 이 평면에 투영시킵니다.
분산 보존
저차원의 평면에 훈련 세트를 투영하기 전에 올바른 초평면을 선택해야 합니다.
데이터를 다른 방향으로 투영하는 것보다
분산이 최대로 보존되는 축을 선택하는 것이 정보가 가장 적게 손실되므로 합리적입니다.
즉, 원본 데이터셋과 투영된 데이터셋 사이의 평균 제곱 거리를 최소화하면 좋습니다.
주성분
PCA는 훈련 세트에서 분산이 최대인 축을 찾습니다.
고차원 데이터셋이라면 PCA는 이전의 두 축에 직교하는 세 번째 축을 찾으며
데이터셋에 있는 차원의 수만큼 네 번째, 다섯 번째, ..., n번째 축을 찾습니다.
i번째 축을 이 데이터의 i번째 주성분(PC)이라고 부릅니다.
훈련 세트의 주성분은 특잇값 분해(SVD)라는 표준 행렬 분해 기술을 이용해서
훈련 세트 행렬 X를 세 개 행렬의 행렬 곱셈인 \( U \Sigma V^{T} \)로 분해했을 때,
모든 주성분의 단위 벡터가 V에 다음과 같이 담겨있습니다.
\[ \displaystyle V = \begin{pmatrix} \vert & \vert & & \vert \newline c_{1} & c_{2} & \dots & c_{n} \newline \vert & \vert & & \vert \end{pmatrix} \]
아래는 넘파이의 svd 함수를 이용해 훈련 세트의 주성분을 구하고
처음 두 개의 PC를 정의하는 두 개의 단위 벡터를 추출하는 코드입니다.
PCA는 데이터셋의 평균이 0이라고 가정하기 때문에 평균을 빼줘서 데이터를 원점에 맞춰줬습니다.
사이킷런의 PCA는 이 작업을 대신 처리해주지만 직접 구현하거나 다른 라이브러리를 사용한다면
데이터를 꼭 원점에 맞춰줘야 합니다.
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]
d차원으로 투영하기
초평면에 훈련 세트를 투영하고 d차원으로 축소된 데이터셋을 얻기 위해서는
행렬 \(X\)와 \(V\) 첫 d열로 구성된 행렬 \(W_{d}\)를 행렬 곱셈하면 됩니다.
\[ X_{d_proj} = XW_{d} \]
다음 코드는 첫 두 개의 주성분으로 정의된 평면에 훈련 세트를 투영합니다.
W2 = Vt.T[:, :2]
X2D = X_centered.dot(W2)
scikit-learn을 이용한 PCA
사이킷런의 PCA 모델은 앞서 본 방법처럼 SVD 분해 방법을 사용하여 구현합니다.
사용법은 코드를 참고하세요.
적절한 차원 수 선택하기
축소할 차원 수를 임의로 정하기보다는 충분한 분산이 될 때까지 더해야 할 차원 수를 선택하는 것이 간단합니다.
코드를 참고하세요.
압축을 위한 PCA
차원을 축소하면 훈련 세트의 크기가 줄어듭니다.
위의 경우에는 784개의 특성이 154개로 줄어들었으니 데이터셋의 크기가 20% 미만이 되었습니다.
이는 엄청난 압축률이고 이런 크기 축소는 분류 알고리즘의 속도를 크게 높일 수 있습니다.
또한 압축된 데이터셋에 PCA 투영의 변환을 반대로 적용해서 784개의 차원으로 되돌릴 수도 있습니다.
역변환 공식은 아래와 같습니다.
\[ \displaystyle X_{recovered} = X_{d\_proj}W_{d}^{T} \]
투영 과정에서 일정량의 정보를 잃어버렸기 때문에 완전한 원본 데이터셋을 얻을 순 없습니다.
원본 데이터와 재구성된 데이터 사이의 평균 제곱 거리를 재구성 오차라고 합니다.
다음 코드는 원본 데이터를 재구성하는 코드입니다.
랜덤 PCA
PCA 모델을 만들 때 svd_solver 매개변수를 'randomized'로 지정하면
사이킷런은 랜덤 PCA라는 확률적 알고리즘을 사용해 처음 d개의 주성분에 대한 근삿값을 빠르게 찾습니다.
이 방법은 d가 n보다 많이 작으면 완전 svd보다 훨씬 빠릅니다.
from sklearn.decomposition import PCA
rnd_pca = PCA(n_components=154, svd_solver='randomized')
X_reduced = rnd_pca.fit_transform(X_train)
점진적 PCA
PCA는 SVD 알고리즘을 위해서 전체 훈련 세트를 메모리에 올려야 하는 것이 문제였습니다.
이 문제를 해결하기 위해 점진적 PCA(IPCA) 알고리즘이 개발되었습니다.
이 방법은 훈련 세트를 미니 배치로 나눈 뒤 IPCA 알고리즘에 한 번에 하나씩 주입합니다.
이 방식은 훈련 세트가 클 때와 온라인으로 PCA를 적용할 때 유용합니다.
다음 코드는 MNIST 데이터셋을 100개의 미니배치로 나누고 사이킷런의 IncrementalPCA 클래스에 주입해서
데이터셋의 차원을 154차원으로 줄입니다.
전체 훈련 세트를 사용하는 fit()이 아니라 partial_fit()을 미니배치마다 호출해야 합니다.
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)
전체 데이터를 메모리에 올리기 힘들 때 또 다른 대처 방법은
넘파이의 memmap 파이썬 클래스를 사용해서 하드 디스크의 이진 파일에 저장된 매우 큰 배열을
메모리에 들어 있는 것처럼 다루는 것입니다.
이 파이썬 클래스는 필요할 때 데이터를 메모리에 적재합니다.
IncrementalPCA는 특정 순간에 배열의 일부만 사용하기 때문에 메모리 부족 문제를 해결할 수 있습니다.
아래는 이런 방법을 사용하는 예시 코드입니다.
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)
커널 PCA
커널 PCA(kPCA)를 이용하면 차원 축소를 위한 비선형 투영을 수행할 수 있습니다.
투영된 후에 샘플의 군집을 유지하거나 꼬인 매니폴드에 가까운 데이터셋을 펼칠 때에도 유용합니다.
다음 코드는 사이킷런의 KernelPCA를 이용해 RBF 커널로 kPCA를 적용합니다.
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components=2, kernel='rbf', gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)
커널 선택과 하이퍼파라미터 튜닝
kPCA는 비지도 학습이기 때문에 좋은 커널과 하이퍼파라미터를 선택하기 위한 명확한 성능 측정 기준이 없습니다.
하지만 차원 축소는 종종 지도 학습의 전처리 단계로 활용되므로
그리드 탐색을 사용해 주어진 문제에서 성능이 가장 좋은 커널과 하이퍼파라미터를 선택할 수 있습니다.
아래는 kPCA를 사용할 때 GridSearchCV를 사용해서 가장 좋은 커널과 gamma 파라미터를 찾는 코드입니다.
완전한 비지도 학습으로 가장 낮은 재구성 오차를 만드는 커널과 하이퍼파라미터를 선택하는 방식도 있습니다.
하지만 재구성은 선형 PCA만큼 쉽지 않습니다.
커널 트릭 덕분에 이 변환은 특성 맵을 이용해 훈련 세트를 무한 차원의 특성 공간에 매핑한 다음,
변환된 데이터셋을 선형 PCA에 사용해 2D로 투영한 것과 수학적으로 동일합니다.
축소된 공간에 있는 샘플에 대해 선형 PCA를 역전시키면 재구성된 데이터는 원본 공간이 아닌 특성 공간에 놓이는데,
이 특성 공간은 무한 차원이라서 재구성된 포인트를 계산할 수 없고,
재구성에 따른 실제 에러를 계산할 수 없습니다.
다행히 재구성된 포인트에 가깝게 매핑된 원본 공간의 포인트를 찾을 수는 있습니다.
이것을 재구성 원상이라고 합니다.
재구성을 하는 방법 중 하나는
투영된 샘플을 훈련 세트로, 원본 샘플을 타깃으로 하는 지도 학습 회귀 모델을 훈련시키는 것입니다.
사이킷런에서는 fit_inverse_transform=True 로 설정하면 이걸 자동으로 수행합니다.
KernelPCA에서는 fit_inverse_transform=False가 기본값이고 inverse_transform 메소드를 가지고있지 않습니다.
이 메소드는 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)
LLE
지역 선형 임베딩(LLE)은 또 다른 강력한 비선형 차원 축소(NLDR) 기술입니다.
이전 알고리즘처럼 투영에 의존하지 않는 매니폴드 학습입니다.
LLE는 먼저 각 훈련 샘플이 가장 가까운 이웃에 얼마나 선형적으로 연관되어있는지를 측정합니다.
그 다음 국부적 관계가 가장 잘 보존되는 훈련 세트의 저차원 표현을 찾습니다.
이 방법은 특히 잡음이 너무 많지 않은 경우 꼬인 매니폴드를 피는 데 효과적입니다.
다음 코드는 사이킷런의 LocallyLinearEmbedding을 사용해 스위스 롤을 펼칩니다.
from sklearn.manifold import LocallyLinearEmbedding
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10)
X_reduced = lle.fit_transform(X)
다른 차원 축소 기법들
- 랜덤 투영
- 다차원 스케일링
- Isomap
- t-SNE
- 선형 판별 분석(LDA)
'DATA > 머신 러닝' 카테고리의 다른 글
[머신 러닝] 퍼셉트론 (0) | 2022.01.21 |
---|---|
[머신 러닝] 비지도 학습 (0) | 2022.01.19 |
[머신 러닝] 서포트 벡터 머신 (support vector machine) (0) | 2022.01.17 |
[머신 러닝] 경사 하강법 (Gradient descent) (0) | 2022.01.16 |
[머신 러닝] 분류 (Classification) (0) | 2022.01.15 |