PCA 기초개념 및 알고리즘 구현실습

2019-02-08

.

# Youtube ‘허민석’님 영상을 공부하고 노트형식으로 정리한 자료입니다.

  • youtube채널 : https://www.youtube.com/user/TheEasyoung
  • 영상제목 : [머신러닝] PCA 차원 축소 알고리즘 및 파이썬 구현 (주성분 분석)

# PCA 목적

  • 다차원의 데이터를 3차원 이하로 축소하여 차트로 시각화

  • 이미지 데이터의 노이즈 감소

  • 적은 차원의 공간에 저장하므로써 메모리를 절약

  • 데이터의 차원을 축소하므로써 데이터를 처리하는 시간 및 저장공간 절약

# PCA 알고리즘

  • 만약에 2차원 공간상에 데이터들이 분포한다고 할때 어떻게 차원을 줄일 수 있을까

  • 가장 먼저생각할 수 있는 방법은 하나의 축에 점을 전부 몰아넣는 것이다. 이럴경우 정보가 겹치거나 유실되는 경우가 발생한다.

  • 그렇다면 좀 더 발전된 방법이 없을까?

  • 데이터의 분산이 가장 넓은 축의 직선을 찾는다. (Principal Component를 찾는다)

  • 이럴경우 정보가 겹치거나 유실되는 것을 최소화할 수 있다.

  • 이 Principal Component는 수학적으로 접근했을때 Eigen vector from Covariance Matrix다. Eigenvalue는 데이터 차원수만큼 존재하는데 우리는 가장 넓게 퍼져있는 Eigenvector를 설정해야한다. 그러려면 Eigenvalue가 가장 큰 즉 분산이 가장 넓은 Eigenvector를 설정해햐한다.

  • 다시말해 공분산행렬에서 Eigenvalue가 가장 높은 것의 Eigenvector를 기준으로 점들을 전부 옮기면 된다.

# 실제 예시 데이터를 이용한 PCA 알고리즘 구현 실습

1. 예제 데이터 생성

# Eating, exercise habbit and their body shape
df = pd.DataFrame(columns=['calory', 'breakfast', 'lunch', 'dinner', 'exercise', 'body_shape'])
df.loc[0] = [1200, 1, 0, 0, 2, 'Skinny']
df.loc[1] = [2800, 1, 1, 1, 1, 'Normal']
df.loc[2] = [3500, 2, 2, 1, 0, 'Fat']
df.loc[3] = [1400, 0, 1, 0, 3, 'Skinny']
df.loc[4] = [5000, 2, 2, 2, 0, 'Fat']
df.loc[5] = [1300, 0, 0, 1, 2, 'Skinny']
df.loc[6] = [3000, 1, 0, 1, 1, 'Normal']
df.loc[7] = [4000, 2, 2, 2, 0, 'Fat']
df.loc[8] = [2600, 0, 2, 0, 0, 'Normal']
df.loc[9] = [3000, 1, 2, 1, 1, 'Fat']
df
calory breakfast lunch dinner exercise body_shape
0 1200 1 0 0 2 Skinny
1 2800 1 1 1 1 Normal
2 3500 2 2 1 0 Fat
3 1400 0 1 0 3 Skinny
4 5000 2 2 2 0 Fat
5 1300 0 0 1 2 Skinny
6 3000 1 0 1 1 Normal
7 4000 2 2 2 0 Fat
8 2600 0 2 0 0 Normal
9 3000 1 2 1 1 Fat

2. features 와 target 데이터를 분리

# X is feature vectors
X = df[['calory', 'breakfast', 'lunch', 'dinner', 'exercise']]

# Y is labels
Y = df[['body_shape']]

3. feature 데이터에 대한 노멀라이징 실시

from sklearn.preprocessing import StandardScaler
x_std = StandardScaler().fit_transform(X)
x_std
C:\Users\minman\Anaconda3\lib\site-packages\sklearn\preprocessing\data.py:625: DataConversionWarning: Data with input dtype object were all converted to float64 by StandardScaler.
  return self.partial_fit(X, y)
C:\Users\minman\Anaconda3\lib\site-packages\sklearn\base.py:462: DataConversionWarning: Data with input dtype object were all converted to float64 by StandardScaler.
  return self.fit(X, **fit_params).transform(X)





array([[-1.35205803,  0.        , -1.3764944 , -1.28571429,  1.        ],
       [ 0.01711466,  0.        , -0.22941573,  0.14285714,  0.        ],
       [ 0.61612771,  1.29099445,  0.91766294,  0.14285714, -1.        ],
       [-1.18091145, -1.29099445, -0.22941573, -1.28571429,  2.        ],
       [ 1.89972711,  1.29099445,  0.91766294,  1.57142857, -1.        ],
       [-1.26648474, -1.29099445, -1.3764944 ,  0.14285714,  1.        ],
       [ 0.18826125,  0.        , -1.3764944 ,  0.14285714,  0.        ],
       [ 1.04399418,  1.29099445,  0.91766294,  1.57142857, -1.        ],
       [-0.15403193, -1.29099445,  0.91766294, -1.28571429, -1.        ],
       [ 0.18826125,  0.        ,  0.91766294,  0.14285714,  0.        ]])

4. feature 데이터에 대한 공분산행렬 확인

# features are columns from x_std
features = x_std.T
covariance_matrix = np.cov(features)
covariance_matrix
array([[ 1.11111111,  0.88379717,  0.76782385,  0.89376551, -0.93179808],
       [ 0.88379717,  1.11111111,  0.49362406,  0.81967902, -0.71721914],
       [ 0.76782385,  0.49362406,  1.11111111,  0.40056715, -0.76471911],
       [ 0.89376551,  0.81967902,  0.40056715,  1.11111111, -0.63492063],
       [-0.93179808, -0.71721914, -0.76471911, -0.63492063,  1.11111111]])

5. 공분산행렬로부터 ‘EigenVectors’ 와 ‘EigenValues’ 확인

eig_vals, eig_vecs = np.linalg.eig(covariance_matrix)
eig_vecs
array([[ 0.508005  ,  0.0169937 , -0.84711404,  0.11637853,  0.10244985],
       [ 0.44660335,  0.36890361,  0.12808055, -0.63112016, -0.49973822],
       [ 0.38377913, -0.70804084,  0.20681005, -0.40305226,  0.38232213],
       [ 0.42845209,  0.53194699,  0.3694462 ,  0.22228235,  0.58954327],
       [-0.46002038,  0.2816592 , -0.29450345, -0.61341895,  0.49601841]])
eig_vals
array([4.0657343 , 0.8387565 , 0.07629538, 0.27758568, 0.2971837 ])
# We reduce dimension to 1 dimension, since 1 eigenvector has 73% (enough) variances
eig_vals[0] / sum(eig_vals)
0.7318321731427544

6. Eigenvector상에 데이터들을 배치

  • Eigenvalue가 가장 큰값의 Eigenvector를 기준으로 projection해준다.
  • 단위벡터에 대한 투영길이는 내적이므로 x_std.dot(eig_vecs.T[0])을 하였다.
eig_vecs.T[0]
array([ 0.508005  ,  0.44660335,  0.38377913,  0.42845209, -0.46002038])
projected_X = x_std.dot(eig_vecs.T[0])
projected_X
array([-2.22600943, -0.0181432 ,  1.76296611, -2.73542407,  3.02711544,
       -2.14702579, -0.37142473,  2.59239883, -0.39347815,  0.50902498])
result = pd.DataFrame(projected_X, columns=['PC1'])
result['y-axis'] = 0.0
result['label'] = Y
result
PC1 y-axis label
0 -2.226009 0.0 Skinny
1 -0.018143 0.0 Normal
2 1.762966 0.0 Fat
3 -2.735424 0.0 Skinny
4 3.027115 0.0 Fat
5 -2.147026 0.0 Skinny
6 -0.371425 0.0 Normal
7 2.592399 0.0 Fat
8 -0.393478 0.0 Normal
9 0.509025 0.0 Fat
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.lmplot('PC1', 'y-axis', data=result, fit_reg=False,  
           # x-axis, y-axis, data, no line
           scatter_kws={"s": 50}, 
           # marker size
           hue="label")

plt.title('PCA result')

plt.show()

pca_25_0

7. Scikit-learn에서 제공하는 PCA 클래스 실습

  • Scikit-learn에서 제공하는 PCA 클래스를 이용하면 위에서 numpy 등을 이용해서 복잡하게 계산할 필요가 없다.
from sklearn import decomposition

pca = decomposition.PCA(n_components=1)
# n_components=1 -> 1차원으로 축소하겠다.

sklearn_pca_x = pca.fit_transform(x_std)
# fit_transform -> 특징행렬을 낮은 차원의 근사행렬로 변환하겠다.
  • components_ 메서드를 이용하면 위에서 구한 Eigenvector를 구할 수 있다.
pca.components_
array([[ 0.508005  ,  0.44660335,  0.38377913,  0.42845209, -0.46002038]])
sklearn_result = pd.DataFrame(sklearn_pca_x, columns=['PC1'])
sklearn_result['y-axis'] = 0.0
sklearn_result['label'] = Y

sns.lmplot('PC1', 'y-axis', data=sklearn_result, fit_reg=False,
           scatter_kws={"s": 50},
           hue="label")

plt.title('PCA result')
plt.show()

pca_31_0