티스토리 뷰
개념¶
- PCA : Principal Component Analysis
- 기존의 데이터는 속성 하나하나가 좌표축으로 이루어진 다차원 공간에 위치하고 있습니다.
- 속성들은 둘 간에 서로 연관되어 있는데 이를 수치화한 것을 상관계수(Correlation Coefficient) 라고 합니다. 이를 확장하여 모든 속성들을 고려했을 때, 가장 전체 데이터를 잘 표현할 수 있는 방향(벡터)를 찾을 수 있을 것입니다.
- 이렇게 모든 속성에서 가장 중요한 방향(주성분)을 찾아 나가는 것을 PCA 라고 합니다. 다르게 표현하면 전체 데이터에서 가장 분산이 큰 방향 을 찾는 것입니다.
- 첫번째 주성분을 찾으면 그것과 직각이 되는 방향 중에서 가장 분산이 큰 성분을 찾습니다. 이렇게 원본 속성 만큼의 주성분을 차례대로 찾아나갈 수 있습니다. (속성이 2개면 주성분은 2개, 3개면 주성분은 3개 등등)
기본 예시¶
- 예를 들기 위해, Iris 데이터를 사용하고 시각화를 위해 속성을 2개로 제한하겠습니다.
In [1]:
# import warnings
# warnings.filterwarnings('ignore')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
In [3]:
from sklearn.datasets import load_iris
iris = load_iris()
In [4]:
col1 = 2 # petal length
col2 = 3 # petal width
plt.scatter(iris.data[:,col1], iris.data[:,col2], s=60)
plt.xlabel(iris.feature_names[col1])
plt.ylabel(iris.feature_names[col2])
plt.axis('equal')
Out[4]:
PCA 로 주성분 찾기¶
- 주성분, 즉 분산이 가장 큰 방향을 PCA 를 이용해 찾아보겠습니다.
In [5]:
from sklearn.decomposition import PCA
pca = PCA()
pca.fit(iris.data[:,[col1,col2]])
pca.components_
Out[5]:
In [6]:
# 벡터곱이 0, 즉 두 성분은 직교한다
c = pca.components_
(c[0]*c[1]).sum()
Out[6]:
- 위의 결과에서 pcacomponets\ 를 설명하겠습니다.
- 각 행은 주성분을 나타냅니다. 즉 (0.9, 0.4) 와 (-0.4, 0.9) 두 개입니다. 그리고 (0.9, 0.4) 의 성분은 각각 x축, y축 방향을 나타냅니다.
- 아래에서 주성분을 직선으로 그려보겠습니다.
In [7]:
plt.scatter(iris.data[:,col1], iris.data[:,col2], s=60)
scale=5
plt.plot([0,pca.components_[0,0]*scale], [0,pca.components_[0,1]*scale],'r-')
scale=2
plt.plot([0,pca.components_[1,0]*scale], [0,pca.components_[1,1]*scale],'g--')
plt.xlabel(iris.feature_names[col1])
plt.ylabel(iris.feature_names[col2])
plt.axis('equal')
Out[7]:
- PCA 결과를 다른 식으로 해석하면, 데이터를 가장 잘 표현하는 새로운 축들로 데이터를 회전변환 하는 것입니다.
- 아래에서 원본 데이터를 새로운 좌표계로 변환하겠습니다.
In [8]:
X_pca = pca.transform(iris.data[:,[col1,col2]])
X_pca.shape
Out[8]:
In [11]:
iris.data[0,[col1,col2]]
Out[11]:
In [10]:
X_pca[0]
Out[10]:
In [9]:
plt.scatter(iris.data[:,col1], iris.data[:,col2], c='gray', alpha=0.2, label='org')
plt.hlines([0],-4,4, alpha=0.2)
plt.vlines([0],-1,1, alpha=0.2)
plt.scatter(X_pca[:,0], X_pca[:,1], label='pca')
plt.legend()
plt.axis('equal')
Out[9]:
In [11]:
X_pca.mean(axis=0)
Out[11]:
정규화 후 PCA 적용¶
- PCA 도 SVM 처럼 정규화가 반드시 필요합니다.
- 스케일이 다르면 분산이 달라지기 때문입니다.
- 즉 스케일이 큰 쪽으로 그림이 늘어나기 때문에 스케일이 큰 속성이 주성분이 될 확률이 높아집니다.
In [13]:
X_norm = iris.data[:,[col1,col2]]
X_norm = (X_norm - X_norm.mean(axis=0)) / X_norm.std(axis=0)
plt.hlines([0], -2, 2, alpha=0.2)
plt.vlines([0], -2, 2, alpha=0.2)
plt.scatter(X_norm[:,0], X_norm[:,1])
Out[13]:
In [14]:
pca2 = PCA()
pca2.fit(X_norm)
X_norm_pca = pca2.transform(X_norm)
In [15]:
plt.scatter(X_norm[:,0], X_norm[:,1], c='gray', alpha=0.2, label='org')
plt.hlines([0],-2,2, alpha=0.2)
plt.vlines([0],-2,2, alpha=0.2)
plt.scatter(X_norm_pca[:,0], X_norm_pca[:,1], label='pca')
plt.legend()
Out[15]:
다차원 공간의 PCA (Iris 속성 4개 모두 사용)¶
In [12]:
pca = PCA()
pca.fit(iris.data)
pca.components_
Out[12]:
In [26]:
(pca.components_[0] * pca.components_[2]).sum()
Out[26]:
In [19]:
X_pca = pca.transform(iris.data)
display(X_pca.shape, X_pca[:2])
- X_pca 는 원본 속성을 PCA 로 변환한 4개의 새로운 속성값을 가집니다.
- PCA 는 원본 속성의 속성 갯수를 줄이는 용도로 많이 사용합니다.
- 앞에 있는 주성분이 중요하므로 앞 쪽에 있는 주성분 2개만 적용해 보겠습니다.
In [27]:
pca = PCA(2) # n_components=2
pca.fit(iris.data)
pca.components_
Out[27]:
- 첫번째 주성분은 3번째 속성의 값이 큽니다. 즉, petal length 속성이 중요하게 고려되었습니다.
- 두번째 주성분은 1번째와 2번째 속성의 값이 큽니다. sepal length 와 sepal width 의 역할이 큽니다.
In [28]:
X_pca = pca.transform(iris.data)
display(X_pca.shape, X_pca[:2])
- 원본 속성이 4개인데 주성분을 2개로 해서 변환한다는 것은, 4차원 데이터를 2차원으로 차원을 줄인 효과를 가집니다. (다르게는 가장 중요한 속성 2가지로 요약했다는 의미입니다.)
- 속성이 2개로 줄었으므로, 2차원 그래프를 그릴 수 있습니다.
In [29]:
plt.figure(figsize=[12,8])
plt.scatter(X_pca[:,0], X_pca[:,1], c=iris.target, s=60)
plt.xlabel('PCA1')
plt.ylabel('PCA2')
plt.axis('equal')
plt.colorbar(shrink=0.5)
Out[29]:
- 원본 속성과의 비교를 위해 petal length 와 sepal length 의 산점도를 그려보겠습니다.
In [32]:
plt.scatter(iris.data[:,2], iris.data[:,0], c=iris.target)
Out[32]:
PCA 변환 후 분류¶
- 원점으로 돌아가 PCA 로 변환한 후 분류 알고리즘을 적용해 보겠습니다.
- PCA 도 정규화와 마찬가지로 속성을 변환하는 것이므로, 훈련세트에 대해 변환기준을 세워야 합니다.
In [18]:
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target)
pca = PCA(2)
X_train_pca = pca.fit_transform(X_train)
X_test_pca = pca.transform(X_test)
model = SVC()
model.fit(X_train_pca, y_train)
train_score = model.score(X_train_pca, y_train)
test_score = model.score(X_test_pca, y_test)
train_score, test_score
Out[18]:
- 위 결과에서 모든 속성을 고려했을 때 점수보다는 낮지만 PCA 변환 없이 속성 2개만 고려했을 때 보다는 점수가 높아졌음을 알 수 있습니다.
- PCA 변환의 장점은, 속성간의 상관관계를 좀 더 잘 이해할 수 있고 속성의 차원을 줄일 수 있으며 시각화가 가능하다는 점입니다.
정규화¶
- PCA 로 변환하기 전에, 속성들의 정규화 전처리가 꼭 필요합니다.
- 속성들의 스케일이 차이가 나면 올바른 주성분을 찾기가 어렵습니다.
- 위의 Iris 데이터의 경우 스케일이 크게 차이 나지 않기 때문에 그냥 적용한 것일 뿐입니다.
In [62]:
X_mean = X_train.mean(axis=0)
X_std = X_train.std(axis=0)
X_train_norm = (X_train-X_mean)/X_std
X_test_norm = (X_test-X_mean)/X_std
pca = PCA(2)
X_train_norm_pca = pca.fit_transform(X_train_norm)
X_test_norm_pca = pca.transform(X_test_norm)
In [63]:
pca.components_
Out[63]:
In [64]:
model = SVC()
model.fit(X_train_norm_pca, y_train)
train_score = model.score(X_train_norm_pca, y_train)
test_score = model.score(X_test_norm_pca, y_test)
train_score, test_score
Out[64]:
- 예상과는 다르게 정규화를 적용한 이후에 성적이 오히려 더 나빠졌습니다.
- 첫번째 주성분을 보면 원본 속성들이 모두 고루 적용된 것을 볼 수 있습니다.
- 이러한 점이 특성 하나를 중시하는 것보다 좋지 못한 결과가 나오게 한 것으로 추측합니다.
속성 중요도 추출¶
- 속성이 많은 유방암 데이터에 적용해 보겠습니다.
In [3]:
from sklearn.datasets import load_breast_cancer
from sklearn.decomposition import PCA
cancer = load_breast_cancer()
X = cancer.data
X = (X - X.mean(axis=0)) / X.std(axis=0)
pca = PCA(2)
pca.fit(X)
pca.components_
Out[3]:
In [7]:
X_pca = pca.transform(X)
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="C:/Windows/Fonts/nanumgothic.ttf").get_name()
# h2gprm.ttf, uni_hsr.ttf, hynaml.ttf, nanumgothic.ttf
rc('font', family=font_name)
plt.scatter(X_pca[:,0], X_pca[:,1], c=cancer.target, alpha=0.3)
plt.xlabel('주성분-1')
plt.ylabel('주성분-2')
plt.title('유방암 데이터 주성분 분석')
Out[7]:
In [17]:
plt.matshow(pca.components_, interpolation='none', cmap='autumn',
vmin=pca.components_.min(), vmax=pca.components_.max())
plt.yticks([0,1],['PCA1','PCA2'])
plt.colorbar()
plt.xticks(range(30), cancer.feature_names, rotation=60, ha='left')
print('')
- 첫번째 주성분은 모두 부호가 같습니다. 즉 서로 양의 상관관계를 가집니다.
- 첫번째 주성분의 특징을 상관관계도와 비교해 보면 좋겠습니다.
In [18]:
plt.figure(figsize=[8,8])
plt.imshow(np.corrcoef(cancer.data.T), interpolation='none', cmap='autumn')
plt.colorbar(shrink=0.7)
Out[18]:
- 첫번째 주성분은 원본 속성의 0번 속성과, 두번째 주성분은 원본 속성의 9번 속성과 비슷합니다.
'beginner > 파이썬 머신러닝 기초' 카테고리의 다른 글
비지도학습-DBSCAN(군집화) (0) | 2019.04.09 |
---|---|
비지도학습-군집 (0) | 2019.04.08 |
비지도학습-스케일 (0) | 2019.04.08 |
지도학습-비선형회귀 (0) | 2019.04.05 |
지도학습-릿지와 라쏘 (0) | 2019.04.02 |