# 파이썬 2와 파이썬 3 지원
from __future__ import division, print_function, unicode_literals
# 공통
import numpy as np
import os
# 일관된 출력을 위해 유사난수 초기화
np.random.seed(42)
# 맷플롯립 설정
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['xtick.labelsize'] = 12
plt.rcParams['ytick.labelsize'] = 12
# 한글출력
plt.rcParams['font.family'] = 'NanumBarunGothic'
plt.rcParams['axes.unicode_minus'] = False
# 그림을 저장할 폴더
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "dim_reduction"
차원의 저주curse of dimensionality : 훈련 샘플 각각이 수천 심지어 수백만 개의 특성을 가지고 있어 훈련을 느리게 할 뿐만 아니라, 좋은 솔루션을 찾기 어렵게 만드는 문제
CAUTION_
외삽extrapolation : 원래의 관찰 범위를 넘어서서 다른 변수와의 관계에 기초하여 변수의 값을 추정하는 과정
차원을 감소시키는 두 가지 주요한 접근법인 투영projection과 매니폴드 학습Manifold Learning을 살펴보겠습니다.
주성분 분석Principal Component Analysis(PCA) : 가장 인기 있는 차원 축소 알고리즘. 먼저 데이터에 가장 가까운 초평면hyperplane을 정의한 다음, 데이터를 이 평면에 투영시킴
NOTE_ 주성분의 방향은 일정치 않음. 훈련 세트를 조금 섞은 다음 다시 PCA를 적용하면 새로운 PC 중 일부가 원래 PC와 반대 방향일 수 있음
다음 파이썬 코드는 넘파이의 svd() 함수를 사용해 훈련 세트의 모든 주성분을 구한 후 처음 두 개의 PC를 추출
3D 데이터셋을 만듭니다:
np.random.seed(4) # 난수 생성을 위한 시드 설정
m = 60
w1, w2 = 0.1, 0.3
noise = 0.1
angles = np.random.rand(m) * 3 * np.pi / 2 - 0.5
X = np.empty((m, 3))
X[:, 0] = np.cos(angles) + np.sin(angles)/2 + noise * np.random.randn(m) / 2
X[:, 1] = np.sin(angles) * 0.7 + noise * np.random.randn(m) / 2
X[:, 2] = X[:, 0] * w1 + X[:, 1] * w2 + noise * np.random.randn(m)
X_centered = X - X.mean(axis=0)
U, s, Vt = np.linalg.svd(X_centered)
c1 = Vt.T[:, 0]
c2 = Vt.T[:, 1]
c1
array([0.93636116, 0.29854881, 0.18465208])
c2
array([-0.34027485, 0.90119108, 0.2684542 ])
Vt
array([[ 0.93636116, 0.29854881, 0.18465208], [-0.34027485, 0.90119108, 0.2684542 ], [-0.08626012, -0.31420255, 0.94542898]])
CAUTION_ PCA는 데이터셋의 평균이 0이라고 가정함. 사이킷런의 PCA 파이썬 클래스는 이 작업을 대신 처리해줌.
그러나 (앞의 코드처럼) PCA를 직접 구현하거나 다른 라이브러리를 사용한다면 먼저 데이터를 원점에 맞추는 것을 잊어서는 안 됨
다음 파이썬 코드는 첫 두 개의 주성분으로 정의된 평면에 훈련 세트를 투영함
W2 = Vt.T[:, :2]
X2D = X_centered.dot(W2)
PCA 변환이 되었음. 지금까지 분산을 가능한 한 최대로 유지하면서 어떻게 데이터셋의 차원을 특정 차원으로 축소하는지 보았음
다음은 PCA 모델을 사용해 데이터셋의 차원을 2로 줄이는 코드 (사이킷런의 PCA 모델은 자동으로 데이터를 중앙에 맞춰줌)
from sklearn.decomposition import PCA
pca = PCA(n_components = 2)
X2D = pca.fit_transform(X)
print(X.shape)
X[:5]
(60, 3)
array([[-1.01570027, -0.55091331, -0.26132626], [-0.00771675, 0.59958572, 0.03507755], [-0.95317135, -0.46453691, -0.24920288], [-0.92012304, 0.21009593, 0.02182381], [-0.76309739, 0.158261 , 0.19152496]])
print(X2D.shape)
X2D[:5]
(60, 2)
array([[ 1.26203346, 0.42067648], [-0.08001485, -0.35272239], [ 1.17545763, 0.36085729], [ 0.89305601, -0.30862856], [ 0.73016287, -0.25404049]])
PCA 변환기를 데이터셋에 학습시키고 나면 components_ 변수를 사용해 주성분을 확인할 수 있음
pca.components_
array([[-0.93636116, -0.29854881, -0.18465208], [ 0.34027485, -0.90119108, -0.2684542 ]])
설명된 분산의 비율explained variance ratio : explained_variance_ratio_ 변수에 저장된 유용한 정보중 하나로 이 값은 각 주성분의 축을 따라 있는 데이터셋의 분산 비율을 나타냄
pca.explained_variance_ratio_
array([0.84248607, 0.14631839])
이는 데이터셋 분산의 84.2%가 첫 번째 축에 놓여 있고 14.6%가 두 번째 축에 놓여 있음을 알려줌
다음 코드는 차원을 축소하지 않고 PCA를 계산한 뒤 훈련 세트의 분산을 95%로 유지하는 데 필요한 최소한의 차원 수를 계산함
from sklearn.datasets import fetch_mldata
from sklearn.model_selection import train_test_split
mnist = fetch_mldata('MNIST original')
X = mnist["data"]
y = mnist["target"]
X_train, X_test, y_train, y_test = train_test_split(X, y)
/anaconda3/envs/mlbook/lib/python3.5/site-packages/sklearn/utils/deprecation.py:77: DeprecationWarning: Function fetch_mldata is deprecated; fetch_mldata was deprecated in version 0.20 and will be removed in version 0.22 warnings.warn(msg, category=DeprecationWarning) /anaconda3/envs/mlbook/lib/python3.5/site-packages/sklearn/utils/deprecation.py:77: DeprecationWarning: Function mldata_filename is deprecated; mldata_filename was deprecated in version 0.20 and will be removed in version 0.22 warnings.warn(msg, category=DeprecationWarning)
pca = PCA()
pca.fit(X_train)
# np.cumsum 함수는 입력 배열의 원소를 차례대로 누적한 배열을 반환
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1
d
154
그런 다음 n_components=d로 설정하여 PCA를 다시 실행함.
하지만 유지하려는 주성분의 수를 지정하기보다는 보존하려는 분산의 비율을 n_components에 0.0에서 1.0사이로 설정하는 편이 훨씬 좋음
pca = PCA(n_components=0.95)
X_reduced = pca.fit_transform(X_train)
X_train.shape
(52500, 784)
X_reduced.shape
(52500, 154)
이 그래프에는 설명된 분산의 빠른 성장이 멈추는 변곡점이 있음. 여기서는 차원을 약 100으로 축소해도 설명된 분산을 크게 손해보지 않을 것
다음 코드는 MNIST 데이터셋을 154차원으로 압축하고 inverse_transform() 메서드를 사용해 784차원으로 복원함
[그림 8-9]는 원본 훈련 세트와 샘플을 압축한 후 복원한 결과를 보여줌
pca = PCA(n_components = 154)
X_reduced = pca.fit_transform(X_train)
X_recovered = pca.inverse_transform(X_reduced)
X_reduced.shape
(52500, 154)
X_recovered.shape
(52500, 784)
역변환 공식은 [식 8-3]과 같음
점진적 PCAIncremental PCA(IPCA) 알고리즘이 있음
다음 코드는 MNIST 데이터셋을 (넘파이의 array_split() 함수를 사용해) 100개의 미니배치로 나누고
사이킷런의 IncrementalPCA 파이썬 클래스에 주입하여 MNIST 데이터셋의 차원을 (이전과 같은) 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):
print(".", end="") # not shown in the book
inc_pca.partial_fit(X_batch)
X_reduced = inc_pca.transform(X_train)
....................................................................................................
X_train.shape
(52500, 784)
X_reduced.shape
(52500, 154)
또 다른 방법은 넘파이의 memmap 파이썬 클래스를 사용해 하드 디스크의 이진 파일에 저장된 매우 큰 배열을 메모리에 들어 있는 것처럼 다루는 것.
이 파이썬 클래스는 필요할 때 데이터를 메모리에 적재함. IncrementalPCA는 특정 순간에 배열의 일부만 사용하기 때문에 메모리 부족 문제를 해결함.
다음 코드처럼 이 방식을 적용하면 일반적인 fit() 메서드를 사용할 수 있음
memmap() 구조를 만들고 MNIST 데이터를 복사합니다. 이는 일반적으로 별도의 프로그램에서 먼저 수행됩니다:
from tempfile import mkdtemp
import os.path as path
filename = path.join(mkdtemp(), 'newfile.dat')
m, n = X_train.shape
X_mm = np.memmap(filename, dtype='float32', mode='write', shape=(m, n))
X_mm[:] = X_train
이제 memmap() 객체를 삭제하면 파이썬 종결자(finalizer)를 호출해서 데이터를 디스크에 저장하게 됩니다.
del X_mm
다음에 다른 프로그램에서 데이터를 로드하여 훈련에 사용합니다:
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)
IncrementalPCA(batch_size=525, copy=True, n_components=154, whiten=False)
rnd_pca = PCA(n_components=154, svd_solver="randomized", random_state=42)
X_reduced = rnd_pca.fit_transform(X_train)
이 기법은 투영된 후에 샘플의 군집을 유지하거나 꼬인 매니폴드에 가까운 데이터셋을 펼칠 때 유용
다음 코드는 사이킷런의 KernelPCA를 사용해 RBF 커널로 kPCA를 적용하고 있음
from sklearn.datasets import make_swiss_roll
X, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)
y = t > 6.9
from sklearn.decomposition import KernelPCA
rbf_pca = KernelPCA(n_components = 2, kernel="rbf", gamma=0.04)
X_reduced = rbf_pca.fit_transform(X)
함수 K(a,b) = (aT·b)2 을 2차 다항식 커널이라고 부름
머신러닝에서 커널은 변환함수 $\phi$를 계산하지 않고(또는 $\phi$를 모르더라도) 원래 벡터 a와 b에 기반하여 점곱 $\phi$(a)T·$\phi$(b)를 계산할 수 있는 함수
[식 5-10]에 가장 널리 사용되는 커널의 일부를 나열함
위 네 개의 커널이 매개변수 kernel에 지정할 수 있는 함수
선형은 "linear", 다항식은 "poly", 가우시안 RBF는 "rbf", 시그모이드는 "sigmoid"로 지정함
[그림 8-10]은 선형 커널, RBF 커널, 시그모이드(로지스틱) 커널을 사용하여 2차원으로 축소시킨 스위스 롤의 모습
다음 코드는 두 단계의 파이프라인을 만드는데, 먼저 kPCA를 사용해 차원을 2차원으로 축소하고 분류를 위해 로지스틱 회귀를 적용함
그런 다음 파이프라인 마지막 단계에서 가장 높은 분류 정확도를 얻기 위해 GridSearchCV를 사용해 kPCA의 가장 좋은 커널과 gamma 파라미터를 찾음
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(solver='liblinear'))
])
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)
GridSearchCV(cv=3, error_score='raise-deprecating', estimator=Pipeline(memory=None, steps=[('kpca', KernelPCA(alpha=1.0, coef0=1, copy_X=True, degree=3, eigen_solver='auto', fit_inverse_transform=False, gamma=None, kernel='linear', kernel_params=None, max_iter=None, n_components=2, n_jobs=None, random_state=None, remove_zero_eig=False, tol=0)), ('log_reg', LogisticRe...ty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False))]), fit_params=None, iid='warn', n_jobs=None, param_grid=[{'kpca__kernel': ['rbf', 'sigmoid'], 'kpca__gamma': array([0.03 , 0.03222, 0.03444, 0.03667, 0.03889, 0.04111, 0.04333, 0.04556, 0.04778, 0.05 ])}], pre_dispatch='2*n_jobs', refit=True, return_train_score='warn', scoring=None, verbose=0)
grid_search.best_params_
{'kpca__gamma': 0.043333333333333335, 'kpca__kernel': 'rbf'}
[그림 8-11]은 스위스 롤의 원본 3D 데이터셋(왼쪽 위)과 RBF 커널의 kPCA를 적용한 2D 데이터셋(오른쪽 위)을 보여줌
사이킷런에서는 다음 코드와 같이 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)
NOTE_ KernelPCA는 fit_inverse_transform = False가 기본값이며 inverse_transform() 메서드를 가지고 있지 않음
이 메서드는 fit_inverse_transform = True를 지정했을 때만 생성됨
그런 다음 재구성 원상 오차를 계산할 수 있음
from sklearn.metrics import mean_squared_error
mean_squared_error(X, X_preimage)
32.7863087957661
이전 알고리즘처럼 투영에 의존하지 않는 매니폴드 학습
그런 다음 국부적인 관계가 가장 잘 보존되는 훈련 세트의 저차원 표현을 찾음. 이는 특히 잡음이 너무 많지 않은 경우 꼬인 매니폴드를 펼치는데 잘 작동함
다음 코드는 사이킷런의 LocallyLinearEmbedding을 사용해 스위스 롤을 펼침
from sklearn.manifold import LocallyLinearEmbedding
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)
X_reduced = lle.fit_transform(X)
결과 2D 데이터셋이 [그림 8-12]에 나타나 있음
사이킷런은 다양한 차원 축소 기법을 제공함. 다음은 그중에서 가장 널리 사용되는 것들임([그림 8-13])
그래프에서 두 노드 사이의 지오데식 거리는 두 노드 사이의 최단 경로를 이루는 노드의 수
1.데이터셋의 차원을 축소하는 주요 목적은 무엇인가요? 대표적인 단점은 무엇인가요?
2.차원의 저주란 무엇인가요?
3.데이터셋의 차원을 축소시키고 나서 이 작업을 원복할 수 있나요? 할 수 있다면 어떻게 가능할까요? 가능하지 않다면 왜일까요?
4.매우 비선형적인 데이터셋의 차원을 축소하는 데 PCA를 사용할 수 있을까요?
5.설명된 분산을 95%로 지정한 PCA를 1,000개의 차원을 가진 데이터셋에 적용한다고 가정하겠습니다. 결과 데이터셋의 차원은 얼마나 될까요?
6.기본 PCA, 점진적 PCA, 랜덤 PCA, 커널 PCA는 어느 경우에 사용될까요?
7.어떤 데이터셋에 적용한 차원 축소 알고리즘의 성능을 어떻게 평가할 수 있을까요?
즉, 차원 축소가 너무 많은 정보를 잃지 않았다면 원본 데이터셋을 사용했을 때와 비슷한 성능이 나와야 함
8.두 개의 차원 축소 알고리즘을 연결할 수 있을까요?
9.(3장에서 소개한) MNIST 데이터셋을 로드하고 훈련 세트와 테스트 세트로 분할합니다(처음 60,000개는 훈련을 위한 샘플이고 나머지 10,000개는 테스트용입니다). 이 데이터셋에 랜덤 포레스트 분류기를 훈련시키고 얼마나 오래 걸리는지 시간을 잰 다음, 테스트 세트로 만들어진 모델을 평가합니다. 그런 다음 PCA를 사용해 설명된 분산이 95%가 되도록 차원을 축소합니다. 이 축소된 데이터셋에 새로운 랜덤 포레스트 분류기를 훈련시키고 얼마나 오래 걸리는지 확인합니다. 훈련 속도가 더 빨라졌나요? 이제 테스트 세트에서 이 분류기를 평가해보세요. 이전 분류기와 비교해서 어떤가요?
문제: (3장에서 소개한) MNIST 데이터셋을 로드하고 훈련 세트와 테스트 세트로 분할합니다(처음 60,000개는 훈련을 위한 샘플이고 나머지 10,000개는 테스트용입니다).
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
X_train = mnist['data'][:60000]
y_train = mnist['target'][:60000]
X_test = mnist['data'][60000:]
y_test = mnist['target'][60000:]
문제: 이 데이터셋에 랜덤 포레스트 분류기를 훈련시키고 얼마나 오래 걸리는지 시간을 잰 다음, 테스트 세트로 만들어진 모델을 평가합니다.
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(n_estimators=10, random_state=42)
import time
t0 = time.time()
rnd_clf.fit(X_train, y_train)
t1 = time.time()
print("훈련 시간 {:.2f}s".format(t1 - t0))
훈련 시간 6.36s
from sklearn.metrics import accuracy_score
y_pred = rnd_clf.predict(X_test)
accuracy_score(y_test, y_pred)
0.9492
문제: 그런 다음 PCA를 사용해 설명된 분산이 95%가 되도록 차원을 축소합니다.
from sklearn.decomposition import PCA
pca = PCA(n_components=0.95)
X_train_reduced = pca.fit_transform(X_train)
문제: 이 축소된 데이터셋에 새로운 랜덤 포레스트 분류기를 훈련시키고 얼마나 오래 걸리는지 확인합니다. 훈련 속도가 더 빨라졌나요?
rnd_clf2 = RandomForestClassifier(n_estimators=10, random_state=42)
t0 = time.time()
rnd_clf2.fit(X_train_reduced, y_train)
t1 = time.time()
print("훈련 시간 {:.2f}s".format(t1 - t0))
훈련 시간 9.87s
훈련이 느려졌습니다! 이 장에서 보았듯이 차원 축소는 언제나 훈련 시간을 줄여주지 못합니다. 데이터셋, 모델, 훈련 알고리즘에 따라 달라집니다.
문제: 이제 테스트 세트에서 이 분류기를 평가해보세요. 이전 분류기와 비교해서 어떤가요?
X_test_reduced = pca.transform(X_test)
y_pred = rnd_clf2.predict(X_test_reduced)
accuracy_score(y_test, y_pred)
0.9009
차원 축소를 했을 때 유용한 정보를 일부 잃었기 때문에 성능이 조금 감소되는 것이 일반적입니다. 그렇지만 이 경우에는 성능 감소가 좀 심각한 것 같습니다. PCA가 별로 도움이 되지 않네요. 훈련 시간도 느려지고 성능도 감소했습니다.
10.t-SNE 알고리즘을 사용해 MNIST 데이터셋을 2차원으로 축소시키고 맷플롯립으로 그래프를 그려보세요. 이미지의 타깃 클래스마다 10가지 색깔로 나타낸 산점도를 그릴 수 있습니다. 또는 샘플의 위치에 각기 다른 색깔의 숫자를 나타낼 수도 있고, 숫자 이미지 자체의 크기를 줄여서 그릴 수도 있습니다(모든 숫자를 다 그리면 그래프가 너무 복잡해지므로 무작위로 선택한 샘플만 그리거나, 인접한 곳에 다른 샘플이 그려져 있지 않은 경우에만 그립니다). 잘 분리된 숫자의 군집을 시각화할 수 있을 것입니다. PCA, LLE, MDS 같은 차원 축소 알고리즘을 적용해보고 시각화 결과를 비교해보세요.
MNIST 데이터셋을 (다시) 로드합니다:
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
mnist.target = mnist.target.astype(np.int)
전체 60,000개의 이미지에 차원 축소를 하면 매우 오랜 시간이 걸리므로 10,000개의 이미지만 무작위로 선택하여 사용하겠습니다:
np.random.seed(42)
m = 10000
idx = np.random.permutation(60000)[:m]
X = mnist['data'][idx]
y = mnist['target'][idx]
이제 t-SNE를 사용해 2D로 차원을 축소해 그래프로 나타냅니다:
from sklearn.manifold import TSNE
tsne = TSNE(n_components=2, random_state=42)
X_reduced = tsne.fit_transform(X)
산점도를 그리기 위해 맷플롯립의 scatter() 함수를 사용합니다. 각 숫자마다 다른 색깔을 사용합니다:
plt.figure(figsize=(13,10))
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
/anaconda3/envs/mlbook/lib/python3.5/site-packages/matplotlib/font_manager.py:1238: UserWarning: findfont: Font family ['NanumBarunGothic'] not found. Falling back to DejaVu Sans. (prop.get_family(), self.defaultFamily[fontext]))
이 그래프는 어떤 숫자가 다른 것과 구분이 쉬운지 알려 줍니다(가령, 0, 6 등이 잘 구분되어 있습니다). 그리고 어떤 숫자가 구분이 어려운지 알려 줍니다(가령, 4, 9입니다).
PCA부터 시작해 보죠. 얼마나 오래 걸리는지도 재어 보겠습니다:
from sklearn.decomposition import PCA
import time
t0 = time.time()
X_pca_reduced = PCA(n_components=2, random_state=42).fit_transform(X)
t1 = time.time()
print("PCA 시간: {:.1f}s.".format(t1 - t0))
plt.figure(figsize=(13,10))
plt.scatter(X_pca_reduced[:, 0], X_pca_reduced[:, 1], c=y, cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
PCA 시간: 0.3s.
PCA가 아주 빠르네요! 몇 개의 군집이 보이지만 너무 겹쳐져 있습니다. LLE를 사용해 보죠:
from sklearn.manifold import LocallyLinearEmbedding
t0 = time.time()
X_lle_reduced = LocallyLinearEmbedding(n_components=2, random_state=42).fit_transform(X)
t1 = time.time()
print("LLE 시간: {:.1f}s.".format(t1 - t0))
plt.figure(figsize=(13,10))
plt.scatter(X_lle_reduced[:, 0], X_lle_reduced[:, 1], c=y, cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
LLE 시간: 124.0s.
시간이 좀 걸리고 결과도 아주 좋지는 않습니다.
MDS를 시도해 보죠. 10,000개 샘플을 적용하면 이 알고리즘은 너무 오래걸리므로 2,000개만 시도해 보겠습니다:
from sklearn.manifold import MDS
m = 2000
t0 = time.time()
X_mds_reduced = MDS(n_components=2, random_state=42).fit_transform(X[:m])
t1 = time.time()
print("MDS 시간: {:.1f}s (10,000개가 아니고 2,000 MNIST 이미지에서).".format(t1 - t0))
plt.figure(figsize=(13,10))
plt.scatter(X_mds_reduced[:, 0], X_mds_reduced[:, 1], c=y[:m], cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
MDS 시간: 115.4s (10,000개가 아니고 2,000 MNIST 이미지에서).
아 이건 좋지 않아 보이네요. 모든 군집이 너무 중복되어 있습니다.
아마 이 비교에서 t-SNE가 승자같네요. 시간을 재어 보진 않았으니 한번 해보죠:
from sklearn.manifold import TSNE
t0 = time.time()
X_tsne_reduced = TSNE(n_components=2, random_state=42).fit_transform(X)
t1 = time.time()
print("t-SNE 시간: {:.1f}s.".format(t1 - t0))
plt.figure(figsize=(13,10))
plt.scatter(X_reduced[:, 0], X_reduced[:, 1], c=y, cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
t-SNE 시간: 283.9s.
분산의 95%를 보존하도록 먼저 PCA를 적용하면 속도를 높여줄 수 있는지 확인해 보겠습니다:
pca_tsne = Pipeline([
("pca", PCA(n_components=0.95, random_state=42)),
("tsne", TSNE(n_components=2, random_state=42)),
])
t0 = time.time()
X_pca_tsne_reduced = pca_tsne.fit_transform(X)
t1 = time.time()
print("PCA+t-SNE 시간 {:.1f}s.".format(t1 - t0))
plt.figure(figsize=(13,10))
plt.scatter(X_pca_tsne_reduced[:, 0], X_pca_tsne_reduced[:, 1], c=y, cmap="jet")
plt.axis('off')
plt.colorbar()
plt.show()
PCA+t-SNE 시간 151.4s.
네, 결과물에 영향을 미치지 않으면서 PCA 속도가 향상되었습니다.