머신러닝&딥러닝/Python

[scikit-learn] PCA 기능

e냥냥 2022. 5. 18. 17:17
728x90

 

PCA(Principal Component Aanalysis)를 공부하면서 machine learning 페이지를 참고하여 정리한 내용입니다.

 

주성분 분석(PCA)은 데이터의 여러 feature를 주성분(PC)이라고 하는 대표적인 feature로 차원을 축소하는 알고리즘입니다. 이 방법은 데이터의 많은 정보를 축약하여 효과적으로 높은 차원의 데이터값의 변화를 설명할 수 있습니다. 

 

이 튜토리얼에서는 먼저 Scikit-learn을 사용하여 PCA를 구현한 다음 코드를 사용하여 단계별 구현과 PCA 알고리즘 이면의 완전한 개념을 이해하기 쉬운 방법으로 설명하고 있습니다. 


1. scikit-learn을 사용하여 PCA 구축

scikit-learn 패키지의 decomposition 모듈은 데이터를 principal 구성 요소로 간단하게 맞추고 변환할 수 있는 PCA개체를 제공합니다. 

 

데이터 가져오기

예제로 mnist 데이터셋을 임포트해보겠습니다. 학습의 편의를 위해 숫자 0,1,2에 대한 레코드만 포함하도록 가져오겠습니다. 이 데이터 세트에는 이미지별 28x28=784 픽셀로 구성되어 있어 설명 변수(x)로 787개의 열이 있고 각 행이 나타내는 숫자 레이블을 알려주는 y 변수가 있습니다.

# Load Packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import MinMaxScaler


pd.set_option('max_rows', 500)
pd.set_option('max_columns', 500)
np.set_printoptions(suppress=True)
%matplotlib inline

# Import Data
df = pd.read_csv(
    'https://raw.githubusercontent.com/selva86/datasets/master/mnist_012.csv')

# Prepare X and Y
Y = df.loc[:, '0']
X = df.drop(['0'], axis=1)

# Scaling
# scaler = MinMaxScaler()
# X = pd.DataFrame(scaler.fit_transform(X), columns = X.columns, index = X.index)

print(df.shape)
df.head()

[그림1]

'0' feature가 숫자 레코드(y)를 의미하여 1~784 feature는 설명 변수(x)를 의미합니다. 설명 변수로 PC를 생성하고 산점도로 표현하기 위해 실제 y를 기반으로 색을 구분할 것입니다. 일반적으로 x가 충분한 정보를 갖고 있다면 명확한 클러스터로 구분될 것입니다.

 

일반적으로 데이터 값의 '단위' 및 '크기'에 따라서 데이터의 분산량이 왜곡될 수 있기 때문에 표준화를 진행합니다. 이미지의 각 셀값은 0-255 사이의 값을 가지므로 굳이 표준화를 진행하지 않았습니다. 

 

주요 구성 요소 구축
먼저 PCA() 클래스를 초기화하고 x에서 fit_transform()을 호출하여 주요 구성 요소의 가중치를 동시에 계산한 다음 x를 변환하여 x의 새 주요 구성 요소 집합을 생성하여 df_pca 개체에 저장합니다. 

# PCA
pca = PCA()
df_pca = pca.fit_transform(X=X)

# Store as dataframe and print
df_pca = pd.DataFrame(df_pca)
print(df_pca.shape)  # > (3147, 784)
df_pca.round(2).head()

[그림2]

df_pca 데이터 프레임은 원본 데이터 x와 동일한 차원을 갖습니다.(3147,784) 각 행은 PCA모델에 의해 산출된 PC들이며 열은 feature를 의미합니다.

 

2절에서는 scikit-learn 패키지 결과와 직접 계산해본 결과의 차이를 비교해 보도록 하겠습니다. (0,0)의 값이 -134.27인 것을 기억하십시오. 

2. 직접 계산해서 scikit-learn 과 비교

Step1: 가중치(고유벡터)를 가져옵니다.

# Principal Components Weights (Eigenvectors)
df_pca_loadings = pd.DataFrame(pca.components_)
df_pca_loadings.head()

[그림3] PCA를 통해 구한 고유벡터

각 행은 주성분의 가중치를 포함하는 값으로 이 시점의 값에 주의를 기울일 필요는 없습니다. 

 

Step2: 평균 중심 데이터 계산

X_mean = X - X.mean()
display(X_mean.head())

[그림4]

위의 데이터 프레임에서 각 열 자체의 각 셀에서 각 열의 평균을 빼서 각 열의 평균을 0으로 만듭니다. 

 

Step3: pca.components_와 X_mean의 내적

# Compute PC1 for row 1.
np.dot(df_pca_loadings.loc[0, :], X_mean.loc[0, :])

step1과 step2의 데이터 프레임을 내적하면 scikit-learn 패키지를 사용했을 때와 동일한 결과를 얻을 수 있습니다. 예를들어 step1(df_pca_loadings_)의 첫 행과 step2(X_mean)의 첫 행을 내적하면 df_pca의 위치 (0,0)에 있는 값과 같습니다.(-134.27)

3. PC별 설명가능한 분산 비율

explain_variance_ratio_을 통해 각 PC가 전체 정보 중 얼마나 기여하고 있는지 확인할 수 있습니다. 

print(pca.explained_variance_ratio_.round(2)[:10])

PC는 일반적으로 내림차순으로 정렬되며 PC1 22%, PC2 10%를 기여하고 있습니다. 

[0.22 0.1  0.06 0.06 0.04 0.04 0.03 0.03 0.02 0.02]

4. 클러스터링 플롯

설명 비율이 높은 두 PC에 대해 산점도를 그리면 0, 1, 2 레이블 클러스터링이 명확하게 나뉜 것을 볼 수 있습니다. encircle 함수는 클러스터 내의 점을 둘러싸는 선을 그려줄 수 있도록 정의하겠습니다. 참조 사이트에서는 1, 2 PC에 대해서 시각화 하였으나 저는 0, 1 PC에 대해서 시각화하였습니다. 

from scipy.spatial import ConvexHull

def encircle(x, y, ax=None, **kw):
    if not ax:
        ax = plt.gca()
    p = np.c_[x, y]
    hull = ConvexHull(p)
    poly = plt.Polygon(p[hull.vertices, :], **kw)
    ax.add_patch(poly)

# Scatterplot against PC1 and PC2
fig, ax = plt.subplots(1,1, figsize=(16,12))

# Row masks for each category
rows_0 = Y==0;
rows_1 = Y==1; 
rows_2 = Y==2; 

# Plot
ax.scatter(df_pca.loc[rows_0.tolist(), 0], df_pca.loc[rows_0.tolist(), 1], c='blue', edgecolor='k', s=120, label='Zero')
ax.scatter(df_pca.loc[rows_1.tolist(), 0], df_pca.loc[rows_1.tolist(), 1], c='red', edgecolor='k', s=120, label='One')
ax.scatter(df_pca.loc[rows_2.tolist(), 0], df_pca.loc[rows_2.tolist(), 1], c='green', edgecolor='k', s=120, label='Two')

# Encircle the boundaries
encircle(df_pca.loc[rows_0.tolist(), 0], df_pca.loc[rows_0.tolist(), 1], ec="blue", fc="none", linewidth=2.5)
encircle(df_pca.loc[rows_1.tolist(), 0], df_pca.loc[rows_1.tolist(), 1], ec="firebrick", fc="none", linewidth=2.5)
encircle(df_pca.loc[rows_2.tolist(), 0], df_pca.loc[rows_2.tolist(), 1], ec="green", fc="none", linewidth=2.5)

# Shading
encircle(df_pca.loc[rows_1.tolist(), 0], df_pca.loc[rows_1.tolist(), 1], ec="k", fc="firebrick", alpha=0.05)
encircle(df_pca.loc[rows_0.tolist(), 0], df_pca.loc[rows_0.tolist(), 1], ec="k", fc="blue", alpha=0.05)
encircle(df_pca.loc[rows_2.tolist(), 0], df_pca.loc[rows_2.tolist(), 1], ec="k", fc="green", alpha=0.05)

# Labels
ax.set_title("MNIST Data for 1, 2 & 3: Scatterplot of First Two PCA directions", fontsize=22)
ax.set_xlabel("1st Principal Component", fontsize=22)
ax.set_ylabel("2nd Principal Component", fontsize=22)
ax.legend(loc='best', title='Transaction Type', fontsize=16)
plt.show()

[그림5]

그림을 보면 어느 정도 겹침이 있긴 하지만 같은 범주에 속하는 점들이 뚜렷하게 군집되어 있고, 이는 처음 두 PC만으로도 레이블을 서로 구분할 수 있을 만큼 충분히 많은 정보를 갖고 있다는 것을 증명합니다. .

 

6. 원래 feature로 재변환

pca 객체에는 inverse_transform() 메서드를 사용하여 원래 데이터로 반환할 수 도 있습니다. 

 

 

 

 

감사합니다 :)

 

 

참고자료

machine learning plus​

datascienceschool.net

Alex 데이터 분석가의 블로그

 

728x90
loading