[Python] - PCA(주성분분석) 실습

2020. 6. 30. 19:05ML in Python/Python

 PCA는 Principal component analysis의 약자로 차원의 저주를 해결하기 위한 방법 중 하나이다. 많은 변수들 사이에서 수학적인 연산을 통해 PC score를 얻어내고, 높은 PC score를 기반으로 LogisticRegression회귀분석을 진행한다. 이번 게시글에서는 이를 Python으로 실습하는 과정을 풀어나갈 것이다. 

 만약 이론과 원리에 대해 알고 싶다면 아래 링크를 통해 확인하길 바란다.

https://todayisbetterthanyesterday.tistory.com/22

 

[Data Analysis] 차원 축소법 - PCA(주성분 분석)

 이 게시글은 PCA의 이해와 수학적 과정만을 다룬다. Python 실습코드를 따라가보면 훨씬 이해가 잘 될 것이다. 아래 링크를 남겨놓겠다. https://todayisbetterthanyesterday.tistory.com/16 [Python] - PCA(주..

todayisbetterthanyesterday.tistory.com

 sklearn datasets을 통해 꽃의 종류를 예측하는 데이터인 iris 데이터를 사용하여

1. 데이터를 로드하고 확인 (feature, target)

2. 기술통계량적 확인 작업

3. PCA함수를 활용해 PC score를 얻음

4. PC score를 통한 회귀분석 & confusion matrix를 통한 분류 성능 확인

이 순서로 진행을 할 것이다. 

1. 데이터 로드 및 데이터 파악

 라이브러리 import 

# 실습 데이터셋 라이브러리. 
# iris - 꽃잎 데이터, 꽃 받침/길이 등을 기반으로 꽃의 종류를 예측(분류 데이터셋)
from sklearn import datasets

# PCA를 위한 라이브러리
from sklearn.decomposition import PCA

# 자료처리/시각화 라이브러리
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

 iris 데이터를 부르고, 구조 확인

iris = datasets.load_iris()
dir(iris) # 포함된 객체 확인

dir(iris) 결과

 실습의 편의를 위해, 독립변수 앞의 2개 사용

X = iris.data[:.[0,2]]
y = iris.target

## X feature 변수 파악

print(X.shape)

feature_names = [iris.feature_names[0],iris.feature_names[2]]
print(feature_names)

df_X = pd.DataFrame(X)
df_X.head()

1. print(X.shape) 2. print(feature_names) 3.df_X.head()

## Y target 변수 파악

print(y.shape)

df_Y = pd.DataFrame(y)
df_Y.head()

y.shape/ df.haed()

 위의 X,y의 데이터 셋 확인결과 둘다 150개의 row가 존재한다는 것을 확인할 수 있었고, feature데이터는 꽃받침과 꽃잎의 길이 데이터라는 것을 확인할 수 있었다. df_Y.head()의 출력물이 전부 0이기에 아직 target데이터의 형태를 확정할 순 없다.

## Y target 변수 파악

print(set(y))
iris.target_names

1. print(set(y)) 2. iris.target_names

 위를 확인해본 결과, y(target)데이터는 0,1,2로 이루어진 범주형 데이터이고, 이는 꽃의 종류를 나타내는 변수라는 것을 확인할 수 있었다. 

 즉, 이번 데이터셋은 꽃받침 길이, 꽃잎 길이를 확인하여 꽃의 종류를 확인하는 과정이라는 것을 알아낼 수 있다. 

 

1. 기술통계량(데이터 분포) 확인

# 결측치 여부 파악

print(df_X.isnull().sum())
print(df_Y.isnull().sum())

X,Y의 결측치 - 0

 데이터의 결측치가 존재하지 않는 것을 확인했다. 

# target data부터 확인

df_Y[0].value_counts().plot(kind='bar')
plt.show()

df_Y[0].value_counts().plot(kind='bar')

 위의 그래프를 확인해 보면, target데이터의 3가지 종류의 데이터가 골고루 50개씩 존재한다는 것을 알 수 있다. 특정 데이터에 몰려있지 않은것이 확인 되어, 분류 학습을 진행하는 데 문제가 없다는 것을 알 수 있다.

# feature data 확인

for i in range(df_X.shape[1]):
	sns.displot(df_X[i])
	plt.title(feature_names[i])
	plt.show()

sns.displot(df_X[i])

 첫 번째 그래프는 sepal length(꽃받침 길이)의 분포로 정규분포 형태는 아니지만, 가운데 데이터(5,6,7)에 많은 데이터가 분포한 것을 볼 수 있다. 

 두 번째 그래프는 petal length(꽃잎 길이)의 분포로 이중 봉우리 형태를 띈 것을 볼 수 있다.

 두 그래프 모두 정규분포와 같은 이상적인 변수는 아니지만, 이상치가 너무 많거나 연속적이지 못한 것이 아니기에 충분히 학습작업을 하는데 있어서 타당하다고 판단할 수 있다.

3. PCA함수를 활용해 PC score를 얻음

# PCA 함수를 활용하여 PC를 얻어낸다.
pca = PCA(n_components = 2) # feature 변수 개수가 2개
pca.fit(X)

pca.fit(X) 결과

print(pca.explained_variance_) # 이것은 eigen value를 의미함
PCscore = pca.transform(X)
PCscore[0:5] # X의 자료에 eigen vector를 곱한 값, 새로운 공간에서 좌표값으로 나타남

1. pca.explained_variance_  2.PCscore[0:5]

 pca.explained_variance_는 PCA를 통해 얻어진 eigen value값이다. 그리고 PCscore는 원래 feature 데이터에 eigen vector를 곱한 값이다. 이는 새로운 공간에서 좌표값으로 나타난다. 

이 PC score는 수학적 연산으로 또한 얻어낼 수 있다.

# PC score 구하기

eigens_v = pca.components_.transpose()
# column vector가 eigen vector가 되도록 transpose()
# 현재 2X2라 티가 안나지만, nxp의 경우는 shape를 통해 확인가능

# PCscore를 구하기위해 eigen vector를 곱하기 전에 centering 작업
mX = np.matrix(X)
for i in range(X.shape[1]):
	mX[:,i] = mX[:,i]-np.mean(X[:,i])
dfmX = pd.DataFrame(mX)

# PC score를 구하기 위해 eigen vector를 곱함
(mX * eigens_v)[0:5]

직접적인 연산을 통해 구한 PCscore

 아래 PC score가 위에 pca.fit(X)를 통해 구한 PCscore와 동일한 것을 확인할 수 있다. 즉, 이것은 PCscore란 원래 feature 데이터에 centering작업을 진행 후 (평균을 빼주는 작업) eigen vector를 곱한 것이 PCscore라는 것을 다시 한 번 확인한 것이다. 

# PC score scatter
plt.scatter(PCscore[:,0],PCscore[:,1]) # 0-X축 / 1-Y축
plt.show()

PCscore의 scatter

# centering된 원래 데이터

plt.scatter(dfmX[0],dfmX[1])
origin = [0],[0]  #origin pint
plt.quiver(*origin,eigens_v[1,:],color=["r","b"],scale=3)
plt.show()

 

centering된 원래 데이터

 먼저 아래 그래프를 보면 X,Y관점에서 모두 널리 퍼져있는 형태이고, X가 커질수록 Y가 증가하기에 correlation이 높다고 볼 수 있다. 그렇기에 X와 Y가 비슷한 변수라고 할 수 있다. 즉 PC를 뽑았을 때 축하나가 큰 역할을 차지하여 2개의 변수를 1개의 PCscore사용으로 차원을 축소시킬수 있다는 뜻이다.

 아래 그래프는 plt.quiver를 통해 축을 직접 그린 것이다. 점들을 축에 정사영을 내린 것으로 빨간 축을 기준으로는 굉장히 넓게 분포하는 것을 확인할 수 있고(PC1), 파란 축을 기준으로는 비교적 조밀하게 분포한다(PC2). 이 데이터를 origin을 정확하게 하고 scale을 조정하면 위의 PCscore분포와 같은 분포를 띄게 만들 수 있다. 

 위의 그래프는 eigen vector를 통해 PC score를 나타낸 것으로, 각각의 PC에 정사영을 내려 새로운 공간에서 최대한 한 축이 많은 설명을 하게끔 새롭게 만들어 낸 것이다. 위의 그림을 보자면 PC1은 많은 것을 설명하고 PC2는 적은 것을 설명하기에 PC1만으로 축소시켜서 회귀분석을 진행할 수 있다. 

 이제는 4개의 변수를 모두 사용하여 PCscore를 선별해내고 이를 통해 분류를 진행했을 때, 같은 개수의 변수를 사용한 분류와 성능차이가 얼마나 나는지 확인해 볼 것이다. 

4. PC score를 통한 회귀분석 & confusion matrix를 통한 분류 성능 확인

 앞의 과정은 변수 4개를 사용하여 PCA를 진행하는 것이기에 일부 중복이 있다.

X2 = iris.data
pca2 = PCA(n_components = 4)  # 4개 변수를 모두 사용
pca2.fit(X2)

pca2.fix(X2)

pca2.explained_variance_  #PC2의 eigen_vector를 구함

pca2.explained_variance_

 이 결과를 보면 첫 번째 PC가 두 번째 PC에 비해 20배의 값을 보인다. 이는 즉, 설명할 수 있는 부분이 첫 번째 PC가 두 번째 PC보다 월등히 많다는 것을 의미한다. 그렇기에 이 데이터에선 많아야 두개의 PC를 사용하여 분석을 할 것이라고 판단할 수 있다. 

PCs = pca2.transform(X2)[:,0:2] # 위에서 판단한 높은 PC 두개를 선택함

 PCscore를 선택할 때,

1. 보통 위의 pca.explained_variance_의 방법을 통해 수치적으로도 확인을 할 수 있고,

2. 또한 막대그래프로 PCscore를 그려 기울기가 완만해지는 부분의 PC를 제거하고 선택하는 방법도 있다. 

3. 그리고, 해당 분야에서 많이 사용하는 parameter 개수를 기준으로 선택하는 방법도 있다. ( ex, 유전자분석에서는 보통 10개의 PC를 사용한다 -> 10개 사용)

# Logistic Regression을 위한 라이브러리
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
clf = LogisticRegression(solver = "sag", multi_class = "multinational").fit(X2,y)

 targer의 범주가 3개인 multiple Logistic regression이기에 multi_class의 지정이 필요하다. 그리고 또한 해를 구하는 알고리즘을 지정해줘야한다. 여기서는 "sag"를 선택했다. 

Logistic Regression 결과창

기존 4개의 변수를 가공없이 Logistic Regression을 진행한 결과이다. 위의 결과창을 보면 "the coef_ did not converge"를 확인할 수 있다. 즉 beta 값이 수렴하지 않아 해를 찾지 못한다는 뜻이다. 이는 모델을 단순화할 필요성이 생기고 PCA를 통해 X의 변수를 줄일 수 있는 지 알아볼 필요가 있다. 그리고 우리는 위에서 두 개의 PCscore만 사용하는 것이 충분하다는 것을 확인했다. 그렇기에 2개의 PCscore를 사용한 Logistic fitting을 진행하겠다.

clf2 = LogisticRegression(solver = "sag", multi_class = "multinational").fit(PCs,y)

 이 LogisticRegression fitting은 아무런 결과창이 뜨지 않았다. 즉 학습이 오류없이 잘 됐다는 뜻이다. PCs에 위에서 2개의 PC만 저장을 하였고 이를 사용해서 fitting을 진행했다.

clf2.predict(PCs)

clf2.predict(PCs)

confusion_matrix(y,clf2.predict(PCs))

clf2 predict의 confusion matrix

 위의 결과창을 보면 0은 다맞추고, 1에서는 3개가 틀렸고, 2에서는 2개가 틀린 것을 확인할 수 있다. 이정도는 충분히 정확도가 높다고 판단된다. 과연 그러면, PCscore가 아닌 변수 2개만을 사용한 Logistic Regression은 어떨 것인가?

clf = LogisticRegression(solver = "sag", max_iter = 1000, random_state = 0, multi_class = "multinomial").fit(X2[:,0:2],y)

 위에서 random_state는 매번 달라지는 학습결과를 고정해준 것이다. 그리고 mat_iter는 알고리즘이 해를 찾아가는 반복횟수를 뜻한다. 즉, 증가할 수록 해를 찾는데 오랜 시간을 찾는 것이다. 그리고 이 결과 fitting이 완료되었다.

confusion_matrix(y,clf.predict(X2[:,0:2]))

2개 변수를 사용한 multiple logistic regression 결과

 위의 결과를 보면 0은 잘 맞춘 것을 확인할 수 있다. 하지만, 1과 2에서 각각 13,14개의 오답이 나타났다. 반면 앞에서 PCscore 2개를 활용하여 예측한 것의 정확도는 매우 높았다. 즉 PCA가 단순 multiple Logistic Regression보다 좋은 성능을 보인다는 것을 확인할 수 있었다.