20220608 TIL 차원의 저주
차원의 저주
-
머신러닝에서 훈련 샘플이 수백, 수천만개 특성을 가지고 있으면 훈련이 느리고, 좋은 결과값을 얻기 어렵게 되는 현상.
-
학습 데이터 숫자가 차원 수보다 적어져 모델의 성능이 저하되는 현상.
- 차원의 갯수가 늘어날수록, 개별 차원 내에서 학습할 데이터 수가 적어진다.
-
변수가 증가한다고 반드시 차원의 저주가 발생하는것이 아닌, 관측치보다 변수 수가 많아지는 경우에 차원의 저주 문제가 발생하는것.
해결 방법
-
데이터셋의 크기 증가.
-
훈련 샘플의 밀도가 높아질때까지 데이터를 모아 훈련 세트의 크기를 키우는 것.
-
개인적인 생각으로는 머신러닝의 대부분 문제에 이걸 가져다 대면 풀리는, 치트키같은 존재라고 생각한다.
-
옛날에 진행했던 프로젝트에서 dying Relu가 문제가 되었던 케이스가 간만에 생각났다.
-
그때도 각 클래스의 이미지 갯수 부족이 원인으로 지목되었고, 데이터 증강을 걸어 해결했던 기억이 났다.
-
-
다만 이 방법의 단점이 늘 그러하듯, 훈련 샘플을 위한 데이터의 확보가 힘들다.
- 특히 차원의 갯수가 늘어날수록 밀도의 유지에 필요한 데이터의 양이 기하급수적으로 늘어난다.
-
-
PCA 등의 차원 축소 기법을 사용
-
raw data에 비해 훈련 속도가 빨라지고, 시각화 역시 편해진다.
-
다만, 일부 정보가 유실될수 있으므로 raw data로 훈련을 해보고 훈련 시간 체크후 사용할것을 권장함.
-
차원 축소 기법
- 데이터의 차원을 축소하는 기법인 투영(projection), 매니폴드 학습(manifold learning), 대표적인 차원 축소 알고리즘인 주성분분석(PCA)가 있다.
투영(projection)
-
학습 데이터셋은 고차원 공간에서 저차원 부분 공간에 위치한다.
- 고차원 데이터 특성중 일부 특성으로 데이터를 표현할수 있음을 의미함.
- 위 이미지처럼 3차원 공간의 데이터를 2차원상으로 투영시켜 차원을 축소하는 기법이라 투영(projection)기법이다.
매니폴드 학습(manifold learning)
-
매니폴드(manifold, 다양체)
- 위상수학과 기하학에서, 다양체는 국소적으로 유클리드 공간과 닮은 위상 공간이다.
- 즉, 국소적으로는 유클리드 공간과 구별할 수 없으나, 대역적으로 독특한 위상수학적 구조를 가질 수 있다.
- (출처 : 위키피디아)
-
위 이미지는 스위스롤이라 불리는 대표적인 2D-매니폴드이다.
-
해당 이미지를 단순히 2차원으로 투영하게 된다면, 중간에 있는것과 같은 의미불명의 데이터를 얻게된다.
-
하지만 3차원에서 보았을때 스위스 롤은 말려있는 2차원 도형(2D 매니폴드)이므로, 말려있는 2차원 도형을 펴서 투영하게 된다면 오른쪽처럼 명확한 데이터를 얻을수 있게 된다.
-
이런식으로, 고차원에서 보았을때 매니폴드가 나타난다면 이상적인 데이터를 산출해낼수 있다.
-
이러한 매니폴드를 모델링 하는 방식으로 대부분의 차원 축소 알고리즘이 작동하며, 이를 매니폴드 학습이라고 한다.
-
매니폴드 학습은 매니폴드 가정 또는 매니폴드 가설에 의해
고차원인 실제 데이터셋이 더 낮은 저차원 매니폴드에 가깝게 놓여있다고 가정
한다.
주성분분석(PCA)
-
가장 대표적인 차원 축소 알고리즘이다.
-
데이터에 가장 가까운 초평면을 구하고, 데이터를 이 초평면에 투영한다.
분산 보존
-
초평면에 데이터를 투영하기 전에 데이터를 보존하기 가장 적절한 초평면을 찾아야 한다.
-
PCA는 데이터의 분산이 최대화 되는 축을 찾는다.
- 즉, 원본 데이터셋과 투영된 데이터셋 사이의 평균제곱거리를 최소화 하는 축을 찾는다.
- c1축에 투영한 데이터는 c2에 투영한 데이터보다 분산이 크다(즉, 데이터가 더 잘 보존되었다)
주성분
-
PCA는 다음과 같은 단계로 이루어진다
-
학습 데이터셋에서 분산이 최대인 축(axis)을 찾는다.
-
이렇게 찾은 첫번째 축에서 직교하면서 분산이 최대인 두번째 축을 찾는다.
-
첫번째와 두번째 축에 직교하면서 분산을 최대로 보존하는 세번째 축을 찾는다.
-
1~3의 방법을 반복하며 데이터셋의 차원수(특성 수)만큼의 축을 찾는다.
-
-
위 과정에서 i번째 축을 정의하는 단위벡터를 i번째 주성분(PC, Principal Component)이라고 한다.
구하는 방법
공분산(Covariance)
-
두개의 특성 또는 변수 간의 상관정도를 나타낸 값.
-
이론적인 면에 대해서는 정리가 잘 되어있는 블로그가 있어 링크를 첨부한다.
-
- 이후에 나오는 코드들 역시 이 글에서 참조했다.
-
- 공분산과 PCA의 상관관계
-
분산을 최대로 만드는 축 = 공분산의 고유벡터
-
공분산의 고유벡터의 열벡터 = 주성분(PC)
-
-
고유값의 설명된 분산비율(explained variance ratio)
-
PCA기법은 데이터셋을 새로운 특성부분으로 압축하므로, 가장 많은 정보(=분산)을 가진 고유벡터(=주성분) 일부만 선택하게 된다.
-
고유값의 크기에 따라 정렬하였을때, 최상위 k개의 분산비율을 고유값의 설명된 분산비율(explained variance ratio)이라고 한다.
- 즉, 각각의 주성분 벡터가 이루는 축에 투영했을때 결과의 분산 비율이다.
-
전체 고유값의 합에서 해당 고유값의 비율을 나타내는 것으로 구한다.
-
결론 :
해당 고유값이 얼마나 정보를 잘 보존하는지에 대한 비율
이라 생각하면 좋다.
-
Scikit-learn에서의 PCA 계산
-
총 세가지 방법이 존재하는데, 요약하면 다음과 같다.
- eigen-decomposer를 이용하여 공분산 행렬로부터 공분산 $C$의
고유값(eigenvalue) w
와고유벡터(eigenvector) v
를 구하는 방법- np.linalg.eig
- SVD를 사용하여 공분산 행렬을 사용하지 않고 구하는 방법
- np.linalg.svd
- Scikit-Learn의 PCA를 사용하여 구하는 방법.
- eigen-decomposer를 이용하여 공분산 행렬로부터 공분산 $C$의
-
이번 글에서는 마지막의 Scikit-Learn을 이용하는 방법을 주로 다룰것이다.
-
사용 예시는 다음과 같다.
from sklearn.decomposition import PCA
# n_components = 구할 주성분벡터의 갯수
# X = 구하려는 데이터
pca = PCA(n_components = 3)
pca.fit(X)
# 특이값
print('singular value :', pca.singular_values_)
# 특이값 벡터. 이때, 고유값 분해 방식으로 푼것과 부호가 다르지만, 축을 이루는 벡터는 같으므로 상관없다.
print('singular vector :\n', pca.components_.T)
# 공분산 행렬의 고유값
print('eigen_value :', pca.explained_variance_)
# 공분산 고유값의 설명된 분산 비율
print('explained variance ratio :', pca.explained_variance_ratio_)
explained variance ratio 해석
-
explained variance ratio : [0.84248607 0.14631839 0.01119554]
-
원 데이터셋 분산의 84.2%가 첫번째 주성분 축에 놓여있고, 14.6%가 두번째 주성분 축에 놓여있음을 의미한다.
-
세번째 주성분 축에는 1.1%정도의 매우 적은 정보만 존재함을 알수 있다.
-
따라서, 첫번째 주성분과 두번째 주성분을 이용해 3차원 데이터셋 X를 2차원에 투영할 경우, 원래 데이터셋의 분산에서 1.1%의 분산을 잃게 된다.
- 즉, 원래 데이터셋의 정보에서 1.1%를 손실한다고 봐도 될것이다.
-
적절한 차원수 선택하기
-
explained variance ratio를 이용해 축소할 차원의 수를 정할수도 있다.
-
누적된 분산의 비율이 95%가 되는 차원을 선택하는 방식으로 진행한다.
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1
print('선택할 차원 수 :', d)
- PCA를 구할때, n_components에 0~1 사이의 값을 지정하는것으로 누적 분산 비율을 지정하여 PCA 계산을 수행할수 있다.
pca = PCA(n_components=0.95)
X_proj = pca.fit_transform(X)
print('principal component vec :\n', pca.components_.T)
데이터셋의 차원 축소
- MNIST 데이터셋에 분산의 95%만 유지하도록 하는 예제 코드는 다음과 같다.
from tensorflow.keras.datasets import mnist
# MNIST load
(train_x, train_y), (test_x, test_y) = mnist.load_data()
# reshape
train_x = train_x.reshape(-1, 28*28)
pca = PCA(n_components=0.95)
# PCA 계산 후 투영
X_reduced = pca.fit_transform(train_x)
-
압축된 데이터셋은 PCA 투영변환을 반대로 적용하여 다시 원 데이터의 차원으로 복원할수 있다.
- 이때, 위에서 5%의 분산(=정보)를 잃었기 때문에 손실이 발생한다.
- 원 데이터와 복원한 데이터간 평균제곱오차를
재구성 오차
라고 한다.
- 원 데이터와 복원한 데이터간 평균제곱오차를
- 압축한 데이터셋 X_reduced는 다음과 같이 복원 가능하다.
X_recovered = pca.inverse_transform(X_reduced)
-
원데이터 / 복원결과 비교 이미지
Incremental PCA (IPCA)
-
scikit-learn 기준, SVD를 수행하기 위해
전체 학습 데이터셋을 메모리에 올려야 한다
는 단점이 존재한다. -
이러한 단점을 보완하기 위해 Incremental PCA(IPCA)알고리즘이 만들어졌다.
-
학습 데이터셋을 미니배치로 나눈뒤 하나의 미니배치를 입력으로 넣는다.
-
IPCA는 학습 데이터셋이 클때 유용하다.
from sklearn.decomposition import IncrementalPCA
# 데이터셋을 쪼갤 미니배치 숫자
n_batches = 100
inc_pca = IncrementalPCA(n_components=154)
# train_x를 배치로 쪼개 partial_fit을 통해 부분학습시킨다.
for batch_x in np.array_split(train_x, n_batches):
# 참고한 코드에서는 이 반복문이 꽤 길었는지 progress bar 대용으로 점을 찍는 코드가 있었다.
# tqdm등의 라이브러리로 진행도를 표시하는편이 좋을듯하다.
inc_pca.partial_fit(batch_x)
X_reduced = inc_pca.transform(train_x)
# 복원은 마찬가지로 inverse_transform()으로!
X_recovered_inc_pca = inc_pca.inverse_transform(X_reduced)
Kernel PCA (KPCA)
커널 함수
-
정의
- x를 기저함수 변환 후 변환된 독립변수벡터를 내적한 값을 하나의 함수로 나타낸 함수.
-
주 사용처
- 저차원 데이터를 고차원에 매핑하여 비선형 데이터에 SVM등을 적용시키기 위함.
-
SVM에 사용할수 있듯이, PCA에도 이러한 커널함수를 이용한 투영이 가능한데, 이를 Kernel PCA라고 한다.
-
스위스롤 데이터에 Kernel PCA를 적용하는 예제코드이다.
from sklearn.datasets import make_swiss_roll
# 스위스롤 데이터 제작
X, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)
from sklearn.decomposition import KernelPCA
# 커널함수는 가우시안 RBF 커널함수를 사용했다.
rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)
참고한 글
-
- 이 글에서 거의 대부분의 내용을 참고했다.
-
- 기본적인 매니폴드의 개념에 대해 정리된 글이다.
-
- Scikit-Learn에서 사용하는 PCA 계산 방식인 특이값 분해 방법에 관한 이론이 정리되어 있다.
-
- explained variance ratio에 대한 정의를 이 글에서 명확히 알수 있었다.
- SVM에 관한 정리글
- 6번 문단의 Kernel SVM에서 커널 함수를 이용한 SVM과 커널 함수의 개념에 대해 알수 있었다.
- 커널 SVM에 관한 정리글
- 커널 함수의 정의를 참조했다.