1. PCA
1.1 PCA란?
- 데이터 집합 내에 존재하는 각 데이터의 차이를 가장 잘 나타내주는 요소를 찾아 내는 방법
- 통계 데이터 분석(주성분 분석), 데이터 압축(차원 감소), 노이즈 제거 등 다양한 분야에서 사용
1.2 간단한 PCA의 개념
- 주성분 분석 : 차원축소와 변수추출 기법으로 널리 쓰이고 있음
- 데이터의 분산을 최대한 보존하면서 서로 직교하는 새 기저(축)를 찾가, 고차원 공간의 표본들을 선형 연관성이 없는 저차원 공간으로 변환하는 기법
- 변수추출 : 기존 변수를 조합해 새로운 변수를 만드는 기법(변수 선택)과 구분해야 함
1.3 고유값과 고유벡터
- 임의의 n × n 행렬 A 에 대하여, 0이 아닌 솔루션 벡터 x가 존재한다면 숫자 λ는 행렬 A의 고유값라고 할 수 있다.
- Ax = λx
- 이 때, 솔루션 벡터 x는 고유값 λ에 대응하는 고유벡터이다.
1.4 코드로 실습
1
2
3
4
5
6
7
8
from numpy import array
from numpy import mean
from numpy import cov
from numpy.linalg import eig
# 매트릭스 정의
A = array([[1, 2], [3, 4], [5, 6]])
print(A)
1
2
3
[[1 2]
[3 4]
[5 6]]
1.4.1 평균 계산
1
2
M = mean(A.T, axis = 1)
print(M)
1
[3. 4.]
1.4.2 원 행렬에서 평균을 뺌
1
2
C = A - M
print(C)
1
2
3
[[-2. -2.]
[ 0. 0.]
[ 2. 2.]]
1.4.3 공분산 행렬을 찾음
1
2
V = cov(C.T)
print(V)
1
2
[[4. 4.]
[4. 4.]]
1.4.4 고유값과 고유벡터를 계산
1
2
3
values, vectors = eig(V)
print(vectors)
print(values)
1
2
3
[[ 0.70710678 -0.70710678]
[ 0.70710678 0.70710678]]
[8. 0.]
1.4.5 고유벡터에 다시 원데이터를 투영
1
2
P = vectors.T.dot(C.T)
print(P.T)
1
2
3
[[-2.82842712 0. ]
[ 0. 0. ]
[ 2.82842712 0. ]]
1.4.6 PCA를 사용하여 한번에 하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from numpy import array
from sklearn.decomposition import PCA
# PCA 객체 생성
pca = PCA(2)
# data 적용
pca.fit(A)
# 고유값과 고유벡터
print(pca.components_)
print(pca.explained_variance_)
print()
# 고유벡터에 다시 원데이터 투영
B = pca.transform(A)
print(B)
1
2
3
4
5
6
7
[[ 0.70710678 0.70710678]
[ 0.70710678 -0.70710678]]
[8.00000000e+00 2.25080839e-33]
[[-2.82842712e+00 2.22044605e-16]
[ 0.00000000e+00 0.00000000e+00]
[ 2.82842712e+00 -2.22044605e-16]]
2. sklearn을 이용하여 연습
2.1 데이터 만들기
1
2
3
4
5
6
import seaborn as sns
sns.set_style('whitegrid')
rng = np.random.RandomState(13)
X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T
X.shape
1
(200, 2)
2.2 데이터의 생김새
1
2
3
plt.scatter(X[:,0], X[:,1])
plt.axis('equal')
plt.show()
2.3 PCA fit
1
2
pca = PCA(n_components=2, random_state=13)
pca.fit(X)
1
PCA(n_components=2, random_state=13)
2.4 벡터와 분산값
1
pca.components_
1
2
array([[ 0.47802511, 0.87834617],
[-0.87834617, 0.47802511]])
- 벡터는 components_ 로 확인
1
pca.explained_variance_
1
array([1.82531406, 0.13209947])
- 분산값은 explained_variance_로 확인
2.5 주성분 벡터를 그릴 함수 작성
1
2
3
4
5
6
def draw_vector(v0, v1, ax=None):
ax = ax or plt.gca()
arrowprops = dict(arrowstyle='->',
linewidth=2, color='black',
shrinkA=0, shrinkB=0)
ax.annotate('', v1, v0, arrowprops=arrowprops)
2.6 그리기
1
2
3
4
5
6
plt.scatter(X[:, 0], X[:, 1], alpha=0.4)
for length, vector in zip(pca.explained_variance_, pca.components_):
v = vector * 3 * np.sqrt(length)
draw_vector(pca.mean_, pca.mean_ + v)
plt.axis('equal')
plt.show()
2.7 n_components를 1로 두고 해보기
1
2
3
4
5
6
pca = PCA(n_components=1, random_state=13)
pca.fit(X)
X_pca = pca.transform(X)
print(pca.components_)
print(pca.explained_variance_)
1
2
[[0.47802511 0.87834617]]
[1.82531406]
2.8 Linear regression과 비슷해 보임
1
2
3
4
5
X_new = pca.inverse_transform(X_pca)
plt.scatter(X[:, 0], X[:, 1], alpha=0.3)
plt.scatter(X_new[:, 0], X_new[:, 1], alpha=0.9)
plt.axis('equal')
plt.show()
3. Iris data 로 실습
3.1 Data load
1
2
3
4
5
6
7
8
import pandas as pd
from sklearn.datasets import load_iris
iris = load_iris()
iris_pd = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_pd['species'] = iris.target
iris_pd.head(3)
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | species | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 |
3.2 특성 4개를 pairplot으로 확인해보기
1
2
3
4
sns.pairplot(iris_pd, hue = 'species', height = 3,
x_vars = ['sepal length (cm)', 'petal width (cm)'],
y_vars = ['petal length (cm)', 'sepal width (cm)'])
plt.show()
- 이렇게 보면 어떤 특성을 가지고 있는지 알아보기 좀 힘든듯 싶다.
3.3 Scaler 적용
1
2
3
4
from sklearn.preprocessing import StandardScaler
iris_ss = StandardScaler().fit_transform(iris.data)
iris_ss[:3]
1
2
3
array([[-0.90068117, 1.01900435, -1.34022653, -1.3154443 ],
[-1.14301691, -0.13197948, -1.34022653, -1.3154443 ],
[-1.38535265, 0.32841405, -1.39706395, -1.3154443 ]])
3.4 Pca결과를 return하는 함수 작성
1
2
3
4
5
6
7
from sklearn.decomposition import PCA
def get_pca_data(ss_data, n_components = 2):
pca = PCA(n_components=n_components)
pca.fit(ss_data)
return pca.transform(ss_data), pca
3.5 pca 함수 적용
1
2
iris_pca, pca = get_pca_data(iris_ss, n_components=2)
iris_pca.shape
1
(150, 2)
1
pca.mean_
1
array([-1.69031455e-15, -1.84297022e-15, -1.69864123e-15, -1.40924309e-15])
1
pca.components_
1
2
array([[ 0.52106591, -0.26934744, 0.5804131 , 0.56485654],
[ 0.37741762, 0.92329566, 0.02449161, 0.06694199]])
3.6 Pca 결과를 dataframe으로 변환하는 함수 생성
1
2
def get_pd_from_pca(pca_data, cols=['pca_component_1', 'pca_component_2']):
return pd.DataFrame(pca_data, columns=cols)
3.7 2개의 특성으로 pca한것의 DataFrame
1
2
3
iris_pd_pca = get_pd_from_pca(iris_pca)
iris_pd_pca['species'] = iris.target
iris_pd_pca.head(3)
pca_component_1 | pca_component_2 | species | |
---|---|---|---|
0 | -2.264703 | 0.480027 | 0 |
1 | -2.080961 | -0.674134 | 0 |
2 | -2.364229 | -0.341908 | 0 |
- 원래 4개의 특성을 가지고 있던 데이터를 pca하여 2개의 특성으로 압축시킨것
3.8 Pca한 특성 그려보기
1
2
3
sns.pairplot(iris_pd_pca, hue = 'species', height = 5,
x_vars = ['pca_component_1'], y_vars = 'pca_component_2')
plt.show()
3.9 PCA한 데이터의 설명력
1
2
3
4
5
6
import numpy as np
def print_variance_ratio(pca):
print('variance_ratio: ', pca.explained_variance_ratio_)
print('sum of variance_ratio: ', np.sum(pca.explained_variance_ratio_))
print_variance_ratio(pca)
1
2
variance_ratio: [0.72962445 0.22850762]
sum of variance_ratio: 0.9581320720000164
- 2개의 주성분으로 압축한 데이터는 기존 데이터의 95.8%의 설명력을 가진다.
3.10 4개의 특성을 모두 사용한 Randomforest
1
2
3
4
5
6
7
8
9
10
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
def rf_scores(X, y, cv = 5):
rf = RandomForestClassifier(random_state=13, n_estimators= 100)
scores_rf = cross_val_score(rf, X, y, scoring='accuracy', cv = cv)
print('Score : ', np.mean(scores_rf))
rf_scores(iris_ss, iris.target)
1
Score : 0.96
- iris데이터의 4개의 모든 특성을 적용하여 RandomForest를 적용하면 accuracy는 0.96 나온다.
3.11 PCA를 한 데이터를 사용한 Randomforest
1
2
3
pca_X = iris_pd_pca[['pca_component_1', 'pca_component_2']]
rf_scores(pca_X, iris.target)
1
Score : 0.9066666666666666
- 2개의 데이터로 PCA한 iris 데이터의 accuracy는 0.9정도 나온다
- 아무래도 PCA를 하여서 조금이라도 데이터의 손실이 있었기에, 기존의 4개의 특성을 사용했을떄보단 accuracy는 작게 나온다
- 그래도 4개의 특성을 절반을 줄여 0.96의 accuracy에서 0.9의 accuracy가 나온것은 괜찮은 성과라고 생각해도 될듯 하다.(개인차 있음)
4. Wine data로 실습
4.1 Data load
1
2
3
4
5
6
import pandas as pd
wine_url = 'https://raw.githubusercontent.com/PinkWink/ML_tutorial/master/dataset/wine.csv'
wine = pd.read_csv(wine_url, index_col=0)
wine.head()
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | color | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 | 1 |
1 | 7.8 | 0.88 | 0.00 | 2.6 | 0.098 | 25.0 | 67.0 | 0.9968 | 3.20 | 0.68 | 9.8 | 5 | 1 |
2 | 7.8 | 0.76 | 0.04 | 2.3 | 0.092 | 15.0 | 54.0 | 0.9970 | 3.26 | 0.65 | 9.8 | 5 | 1 |
3 | 11.2 | 0.28 | 0.56 | 1.9 | 0.075 | 17.0 | 60.0 | 0.9980 | 3.16 | 0.58 | 9.8 | 6 | 1 |
4 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 | 1 |
4.2 와인 색상분류(red/white)를 위한 X, y 분리
1
2
3
wine_y = wine['color']
wine_X = wine.drop(['color'], axis = 1)
wine_X.head()
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 |
1 | 7.8 | 0.88 | 0.00 | 2.6 | 0.098 | 25.0 | 67.0 | 0.9968 | 3.20 | 0.68 | 9.8 | 5 |
2 | 7.8 | 0.76 | 0.04 | 2.3 | 0.092 | 15.0 | 54.0 | 0.9970 | 3.26 | 0.65 | 9.8 | 5 |
3 | 11.2 | 0.28 | 0.56 | 1.9 | 0.075 | 17.0 | 60.0 | 0.9980 | 3.16 | 0.58 | 9.8 | 6 |
4 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 |
4.3 StandardScaler 적용
1
2
wine_ss = StandardScaler().fit_transform(wine_X)
wine_ss[:3]
1
2
3
4
5
6
7
8
9
array([[ 0.14247327, 2.18883292, -2.19283252, -0.7447781 , 0.56995782,
-1.10013986, -1.44635852, 1.03499282, 1.81308951, 0.19309677,
-0.91546416, -0.93722961],
[ 0.45103572, 3.28223494, -2.19283252, -0.59764007, 1.1979747 ,
-0.31132009, -0.86246863, 0.70148631, -0.11507303, 0.99957862,
-0.58006813, -0.93722961],
[ 0.45103572, 2.55330026, -1.91755268, -0.66069923, 1.02669737,
-0.87476278, -1.09248586, 0.76818761, 0.25811972, 0.79795816,
-0.58006813, -0.93722961]])
4.4 2개로 PCA
1
2
pca_wine, pca = get_pca_data(wine_ss, n_components=2)
pca_wine.shape
1
(6497, 2)
1
print_variance_ratio(pca)
1
2
variance_ratio: [0.25346226 0.22082117]
sum of variance_ratio: 0.47428342743236185
- 총 12개의 특성을 가지고 있는 와인 데이터를 2개의 성분으로 주성분 분석하면, 전체 데이터의 0.47정도의 설명력을 가진다.
- 설명력이 부족하긴 하지만 1/6로 데이터를 줄인것 가지고 진행을 해보자
4.5 데이터 프레임 만들고 pairplot 그려보기
1
2
3
4
5
6
7
pca_columns = ['pca_component_1', 'pca_component_2']
pca_wine_pd = pd.DataFrame(pca_wine, columns= pca_columns)
pca_wine_pd['color'] = wine_y.values
sns.pairplot(pca_wine_pd, hue = 'color', height= 5,
x_vars = ['pca_component_1'], y_vars = ['pca_component_2'])
plt.show()
- 그냥 눈으로 보기에는 y값(color)이 주황색점으로 파란색점이 잘 나뉘어져 있는것으로 보인다
4.6 RandomForest 적용
1
rf_scores(wine_ss, wine_y)
1
Score : 0.9935352638124
- iris 데이터에서 생성한 RandomForest 함수를 사용
- 원래의 12개 특성을 가지고 있는 데이터를 가지고 RandomForest를 적용하면 0.99가 나온다
1
2
pca_X = pca_wine_pd[['pca_component_1', 'pca_component_2']]
rf_scores(pca_X, wine_y)
1
Score : 0.981067803635933
- 2개의 주성분분석을 한 데이터를 가지고 RandomForest를 적용하면 0.98이 나온다
- 생각보다 설명력이 47% 밖에 안되는 PCA 데이터를 가지고 적용하였는데도, 의외로 원래의 데이터와 큰 차이가 안난다
4.7 주성분 3개로 해보기
1
2
3
4
5
6
7
8
pca_wine, pca = get_pca_data(wine_ss, n_components=3)
print_variance_ratio(pca)
cols = ['pca_1', 'pca_2', 'pca_3']
pca_wine_pd = get_pd_from_pca(pca_wine, cols=cols)
pca_X = pca_wine_pd[cols]
rf_scores(pca_X, wine_y)
1
2
3
variance_ratio: [0.25346226 0.22082117 0.13679223]
sum of variance_ratio: 0.6110756621838703
Score : 0.9832236631728548
- 3개의 주성분으로 생성 후 RandomForest를 하였을때 accuracy는 0.98이 나옴
- 2개의 주성분으로 하였을땐 원래 데이터의 0.47의 설명력을 가졌고, 3개의 주성분으로 하였을땐 0.61의 설명력을 가짐
- 의외로 주성분을 2개에서 3개로 올렸을떄 설명력은 0.14정도가 차이가 났는데, accuracy는 크게 상승하지 않았음
4.8 주성분 3개로 표현한 데이터를 프레임으로 생성
1
2
3
pca_wine_plot = pca_X
pca_wine_plot['color'] = wine_y.values
pca_wine_plot.head()
pca_1 | pca_2 | pca_3 | color | |
---|---|---|---|---|
0 | -3.348438 | 0.568926 | -2.727386 | 1 |
1 | -3.228595 | 1.197335 | -1.998904 | 1 |
2 | -3.237468 | 0.952580 | -1.746578 | 1 |
3 | -1.672561 | 1.600583 | 2.856552 | 1 |
4 | -3.348438 | 0.568926 | -2.727386 | 1 |
4.9 3D로 그려보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from mpl_toolkits.mplot3d import Axes3D
markers = ['^', 'o']
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
for i, marker in enumerate(markers):
x_axis_data = pca_wine_plot[pca_wine_plot['color'] == i]['pca_1']
y_axis_data = pca_wine_plot[pca_wine_plot['color'] == i]['pca_2']
z_axis_data = pca_wine_plot[pca_wine_plot['color'] == i]['pca_3']
ax.scatter(x_axis_data, y_axis_data, z_axis_data,
s=20, alpha=0.5, marker=marker)
ax.view_init(30, 80)
plt.show()