#!/usr/bin/env python3


1. PCA -- eigen face


PCA는 원본 데이터 표현보다 분석하기에 더 적합한 표현을 찾을 수 있으리란 생각에서 출발

이미지는 RGB의 강도가 기록된 픽셀로 구성

PCA를 이용하여 LFW(Labeled Faces in the Wild) 데이터셋의 이미지에서 특성을 추출


# library import

from sklearn.datasets import fetch_lfw_people # fetch_lfw_people library

import matplotlib.pyplot as plt

import matplotlib

import numpy as np


# matplotlib 설정

matplotlib.rc('font', family='AppleGothic'# 한글출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 표시


people = fetch_lfw_people(min_faces_per_person=20, resize=0.7, color=False# 추출된 사진의 서로 다른 얼굴의 수, 사진 비율, 흑백여부

image_shape = people.images[0].shape # people객체 image의 첫번째 원소의 모양

print('people.images의 형태 \n{}'.format(people.images.shape)) # people객체의 image 형태: (3023, 87, 65)

print('class 갯수 \n{}'.format(len(people.target_names))) # people객체의 class 갯수, 62


fig, axes = plt.subplots(2, 5, # figure객체와 2x5의 plot객체를 각각 할당

                         subplot_kw={'xticks': (), 'yticks': ()}) # subplot의 축 설정; x축 눈굼없게, y축 눈금없게


# axes.ravel() : 리스트를 1차원으로

for target, image, ax in zip(people.target, people.images, axes.ravel()): # people.target, people.images, axes.ravel()에서 하나씩 원소 할당

    ax.imshow(image) # imshow로 그림 출력

    ax.set_title(people.target_names[target]) # 각 subplot의 타이틀

plt.gray() # 그림 흑백

plt.show() # 그래프 출력

LFW 데이터셋에 있는 이미지의 샘플



1 -1 people 객체 분석

# 각 target이 나타난 횟수 계산

counts = np.bincount(people.target) # people.target의 빈도 계산


# target별 이름과 횟수 출력

### enumerate() : 각 원소에 인덱스 부여

### print('{0:25}, {1:3}').format(var1, var2):  첫번째 {}에 var1과 25자리까지, 두번째{}에 var2와 3자리를 부여

### print({0!s}.format(var1)): print문에 전달된 형식이 숫자라도 문자로 처리

print('frequency')

for i, (count, name) in enumerate(zip(counts, people.target_names)):

    print('{0:25} {1:3}'.format(name, count), end= '\t'# name에서 25자리, count에서 3자리, 끝은 탭

    if (i + 1) % 3 == 0: # 3의 배수이면, 즉 4번째 원소부터

        print() # 개행처리


# Geoge W Bush와 Colin Powell의 이미지가 많음을 확인


### 데이터셋의 편중을 막기 위해 50개의 이미지만 선택

mask = np.zeros(people.target.shape, dtype=np.bool) # 3023개의 boolean타입 False 생성


people_unique = np.unique(people.target) # 중복된 값 제외

for target in people_unique: # 중복을 제거한 target리스트에서 한개의 원소 선택

# people.target(3023개의 리스트)중 선택된 원소와 같은 것만 출력 ==> [0] 리스트의 원소로 접근 ==> False의 갯수 50개까지 출력

# 이 논리 값을 mask의 인덱스로 사용 후 True로 변환

    mask[np.where(people.target == target)[0][:50]] = 1 

x_people = people.data[mask] # 훈련 데이터 생성

y_people = people.target[mask] # 테스트 데이터 생성


# 전처리 메소드 import

from sklearn.preprocessing import MinMaxScaler


scaler = MinMaxScaler()

x_people_scaled = scaler.fit_transform(x_people) # 전처리 메소드 적용


# 머신 러닝 library import

from sklearn.neighbors import KNeighborsClassifier

from sklearn.model_selection import train_test_split


# 전처리한 데이터를 분할

x_train, x_test, y_train, y_test = \

  train_test_split(x_people_scaled, y_people, # 분할할 데이터

                   stratify=y_people, random_state=0) # 그룹화할 데이터, 랜덤상태


# 머신 러닝 라이브러리 import

knn = KNeighborsClassifier(n_neighbors=1) # 이웃의 수

knn.fit(x_train, y_train) # 모델 학습


print('knn.score(x_test, y_test) \n최근접이웃의 일반화 세트 점수: {:.3f}'.format(

    knn.score(x_test, y_test))) # 0.248

정확도가 24.8%, 무작위로 분류하는 정확도는 1/62 = 1.6%


얼굴의 유사도를 측정하기 위해 원본 픽셀 공간에서 거리를 계산하는 것은 비효율적

각 픽셀의 회색톤 값을 다른 이미지에서 동일한 위치에 있는 픽셀 값과 비교. 이런 방식은 사람이 얼굴 이미지를 인식하는 것과는 많이 다르고, 픽셀을 있는 그대로 비교하는 방식으로는 얼굴의 특징을 잡아내기 어려움


2. PCA whitening 분석


픽셀을 비교할 때 얼굴 위치가 한 픽셀만 오른쪽으로 이동해도 큰 차이를 만들어 완전히 다른얼굴로 인식

주성분으로 변환하여 거리를 계산하여 정확도를 높이는 방법을 사용, PCA whitening 옵션을 사용하여 주성분의 스케일이 같아지도록 조정 ==> whitening 옵션 없이 변환 후 StandardScaler를 적용하는 것과 같음


# library import

import mglearn

mglearn.plots.plot_pca_whitening()

plt.show()

whitening옵션을 사용한 PCA 데이터 변환


데이터가 회전하는 것뿐만 아니라 스케일도 조정되어 그래프가 원 모양으로 바뀜


2-1. PCA  머신 러닝

# library import

from sklearn.decomposition import PCA


# PCA 모델 생성 및 적용

pca = PCA(n_components=100, whiten=True, random_state=0) # 주성분 갯수, whitening option, 랜덤상태

pca.fit(x_train) # PCA 학습

x_train_pca = pca.transform(x_train) # PCA를 데이터에 적용

x_test_pca = pca.transform(x_test)


# PCA를 적용한 데이터 형태

print('x_train_pca.shape \ntrain형태:{}'.format(x_train_pca.shape)) # (1547, 100)

print('x_test_pca.shape \ntest형태:{}'.format(x_test_pca.shape)) # (516, 100)


# 머신 러닝 모델 생성 및 학습

knn = KNeighborsClassifier(n_neighbors=1) # 이웃의 수

knn.fit(x_train_pca, y_train) # 모델 학습

print('테스트 세트 정확도: \n{:.3f}'.format(knn.score(x_test_pca, y_test))) # 0.314


모델의 정확도가 23%에서 31%로 상승


이미지 데이터일 경우엔 계산한 주성분을 쉽게 시각화 가능

주성분이 나타내는 것은 입력 데이터 공간에서의 어떤 방향임을 기억

입력 차원이 87 x 65픽셀의 흑백 이미지이고 따라서 주성분또한 87 x 65


print('pca.components_.shape \n{}'.format(pca.components_.shape)) # (100, 5655)


fig, axes = plt.subplots(3, 5, # subplot 3x5를 axes에 할당

                         subplot_kw={'xticks': (), 'yticks': ()}) # subplot 축 설정


for i, (comp, ax) in enumerate(zip(pca.components_, axes.ravel())): # pca.components_와 axes.ravel()을 하나씩 순서대로 할당한 후 인덱스 부여

    ax.imshow(comp.reshape(image_shape)) # image_shape= (87, 65)

    ax.set_title('pricipal component {}'.format(i+1)) # image title

plt.gray() # 사진 흑백

plt.show() # 사진 출력

얼굴 데이터셋의 pricipal component중 처음 15개


첫 번째 주성분은 얼굴과 배경의 명암차이를 기록한 것으로 추정

두번째 주성분은 오른쪽과 왼쪽 조명의 차이를 담고 있는 것으로 보임

이런 방식이 원본 픽셀 값을 사용하는 것보다 더 의미 있지만, 사람이 얼굴을 인식하는 방식과는 거리가 멈

알고리즘이 데이터를 해석하는 방식은 사람의 방식과는 상당히 다름


PCA 모델을 이해하기 위해 몇개의 주성분을 사용해 원본 데이터를 재구성

몇 개의 주성분으로 데이터를 줄이고 원래 공간으로 되돌릴 수 있음

원래 특성 공간으로 되돌리는 작업은 inverse_transform 메소드를 사용


mglearn.plots.plot_pca_faces(x_train, x_test, image_shape) # 훈련데이터, 테스트데이터, 이미지크기(87x65)

plt.gray() # 그림 흑백

plt.show() # 그림 출력

주성분 갯수에 따른 세 얼굴 이미지의 재구성



'python 머신러닝 -- 비지도학습 > PCA(주성분 분석)' 카테고리의 다른 글

PCA  (0) 2018.03.19

#!/usr/bin/env python3


PCA


PCA주성분 분석은 특성들이 통계적으로 상관관계가 없도록 데이터를 회전시키는 것입니다.

회전한 뒤에 데이터를 설명하는 데 얼마나 중요하냐에 따라 새로운 특성 중 일부만 선택합니다.


다음은 PCA분석의 algorithm입니다.

import mglearn

import matplotlib

import matplotlib.pyplot as plt


matplotlib.rc('font', family='AppleGothic') # 한글출력

plt.rcParams['axes.unicode_minus'] = False # 축 - 표시


mglearn.plots.plot_pca_illustration()

plt.show()

PCA를 이용한 데이터 변환

왼쪽 위 그래프는 원본 데이터 포인트를 색으로 구분해 표시, 이 알고리즘은 먼저 'component1'이라고 쓰여 있는, Variant(분산)이 가장 큰 방향을 찾습니다. 이 방향(벡터)가 가장 많은 정보를 담고 있는 방향이고 ==> 특성들의 상관관계가 가장 큰 방향입니다.


그 다음에 이 algorithm은 첫 번째 방향과 직각인 방향 중에서 가장 많은 정보를 담은 방향을 찾습니다.

2차원에서는 직각 방향은 하나지만 3차원이상부터는 무수히 많은 직각 방향이 존재하고, 이 그래프에서 화살표 머리방향은 의미가 없습니다.


이런 방법으로 찾은 방향이 데이터의 주된 Variant(분산)의 방향이라해서 principal component(주성분)이라합니다.

일반적으로 원본 특성 개수만큼의 주성분 존재합니다.


오른쪽 위 그래프는 주성분1과 2를 각각 x축과 y축에 나란하도록 회전시켰습니다.

회전하기전에 데이터에서 평균을 빼서 중심을 원점에 맞춥니다.


PCA에 의해 회전된 두 축은 독립이므로 변환된 데이터의 correlation matirx(상관관계 행렬)이 대각선 방향(자기자신)을 제외하고는 0이 나옵니다.


PCA는 주성분의 일부만 남기는 차원 축소 용도로 사용할 수 있습니다. 왼쪽 아래 그림은 첫 번재 주성분만 유지하려고하며 2차원 데이터셋이 1차원 데이터셋으로 감소하지만 단순히 원본특성 중 하나만 남기는 것이 아니라, 첫번째 방향의 성분을 유지하도록 데이터를 가공합니다.


마지막으로 데이터에 다시 평균을 더해서 반대로 회전(오른쪽 아래 그림)

이 데이터들은 원래 특성 공간에 놓여 있지만 첫 번째 주성분의 정보만 담고 있음


이 변환은 데이터에서 노이즈를 제거하거나 주성분에서 유지되는 정보를 시각화하는데 사용됩니다.



PCA이 가장 널리 사용되는 분야는 고차원 데이터셋의 시각화영역입니다.

breast cancer와 같은 데이터셋은 특성이 30개나 있어서 30개중 2개를 택하는 경우의 수인 435개의 산점도를 그려야하므로 단순한 시각화가 비효율적입니다.


그러나 'malignant' 'benign' 두 클래스에 대해 각 특성의 히스토그램을 그리면 보다 쉽게 해석 가능

from sklearn.datasets import load_breast_cancer

import numpy as np


cancer = load_breast_cancer()


# 특성이 30개이므로  5X2 3set의 plot 객체 생성합니다.

# set 1

fig, axes = plt.subplots(5, 2)

malignant = cancer.data[cancer.target == 0]

benign = cancer.data[cancer.target == 1]

target_set = np.array([malignant, benign])


for i, ax in zip(np.arange(10), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:, i], bins=50) # bins: histogram 간격

        ax.hist(t[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(()) 

        ax.set_xlabel('feature size') 

        ax.set_ylabel('frequency')

axes[0, 0].legend(['malignant', 'benign'], loc=(0.85, 1.1), bbox_to_anchor=(0.85, 1.1), fancybox=True, shadow=True)

fig.tight_layout()

plt.show()

특성 1 ~ 10까지의 breast cancer 히스토그램


# set 2

fig, axes = plt.subplots(5, 2) 


for i, ax in zip(np.arange(10, 20), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:,i], bins=50) 

        ax.hist(t[:, i], bins=bins, alpha=0.5)

        ax.hist(benign[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(())

fig.tight_layout()

plt.show()

특성 11 ~ 20까지의 breast cancer의 히스토그램


# set 3

fig, axes = plt.subplots(5, 2) 


for i, ax in zip(np.arange(20, 30), axes.ravel()):

    for t in target_set:

        _, bins = np.histogram(cancer.data[:,i], bins=50)

        ax.hist(malignant[:, i], bins=bins, alpha=0.5)

        ax.hist(benign[:, i], bins=bins, alpha=0.5)

        ax.set_title(cancer.feature_names[i])

        ax.set_yticks(())

fig.tight_layout()

plt.show()

특성 21 ~ 30까지의 breast cancer의 히스토그램

각 특성에 대해 특정 간격(bin)에 얼마나 많은 데이터 포인트가 나타나는지 횟수를 나타냈습니다.

'smoothness error' 특성은 두 히스토그램이 거의 겹쳐져 불필요한 특성이고

'worst concave point'는 두 히스토그램이 확실히 구분되어 매우 유용한 특성입니다.


그러나 이 그래프는 특성 간의 상호작용이나 상호작용이 클래스와 어떤 관련이 있는지는 알려주지 못합니다.



PCA 주성분분석으로 breast cancer 데이터를 분석해보겠습니다.

PCA를 사용하면 주요 상호작용을 찾아낼 수 있어 데이터 분석에 더 용이합니다.

PCA를 사용하기전 StandardScaler사용(평균=0, 분산=1)하여 전처리를 하겠습니다.

from sklearn.preprocessing import StandardScaler


scaler = StandardScaler()

scaler.fit(cancer.data)

x_scaled = scaler.transform(cancer.data)


# 기본값일 때 PCA는 데이터를 회전과 이동만 시키고 모든 주성분을 유지합니다.

# 데이터의 차원을 줄이려면 PCA객체를 만들 때 얼마나 많은 성분을 유지할지 알려주어야 합니다.

from sklearn.decomposition import PCA


pca = PCA(n_components=2)

pca.fit(x_scaled)

x_pca = pca.transform(x_scaled)


x_scaled_shape =  x_scaled.shape

print('{}'.format(x_scaled_shape))

# (569, 30)


x_pca_shape = x_pca.shape

print('{}'.format(x_pca_shape))

# (569, 2)


targets = np.unique(cancer.target)

markers = ['^', 'o']

for target, marker in zip(targets, markers):

    plt.scatter(x_pca[cancer.target==target][:, 0], x_pca[cancer.target==target][:, 1],

                s=20, alpha=0.7, marker=marker, edgecolors='k')


plt.legend(['malgnent', 'reglgent'], loc=1)

plt.gca().set_aspect('equal') # x축과 y축의 길이를 같게합니다.

plt.xlabel('first component', size=15)

plt.ylabel('second component', size=15)

plt.show()

두 개의 주성분을 사용해 그린 breast cancer 2차원 scatter plot


PCA의 단점은 그래프의 두 축을 해석하기가 쉽지 않다는 것입니다.

주성분은 원본데이터에 있는 어떤 방향에 대응하는 여러 특성이 조합된 형태이며

PCA객체가 학습될 때 components_ 속성에 주성분이 저장됩니다.

주성분의 구성 요소

print('{}'.format(pca.components_.shape))

# (2, 30)

print('{}'.format(pca.components_)) # 주성분 출력

# [[ 0.21890244  0.10372458  0.22753729  0.22099499  0.14258969  0.23928535

#    0.25840048  0.26085376  0.13816696  0.06436335  0.20597878  0.01742803

#    0.21132592  0.20286964  0.01453145  0.17039345  0.15358979  0.1834174

#    0.04249842  0.10256832  0.22799663  0.10446933  0.23663968  0.22487053

#    0.12795256  0.21009588  0.22876753  0.25088597  0.12290456  0.13178394]

#  [-0.23385713 -0.05970609 -0.21518136 -0.23107671  0.18611302  0.15189161

#    0.06016536 -0.0347675   0.19034877  0.36657547 -0.10555215  0.08997968

#   -0.08945723 -0.15229263  0.20443045  0.2327159   0.19720728  0.13032156

#    0.183848    0.28009203 -0.21986638 -0.0454673  -0.19987843 -0.21935186

#    0.17230435  0.14359317  0.09796411 -0.00825724  0.14188335  0.27533947]]

components_의 각 행은 주성분 하나씩을 나타내며 중요도에 따라 정렬되어 있습니다.

열은 원본 데이터의 특성에 대응하는 값입니다.



히트맵으로 그려보면

from mpl_toolkits.axes_grid1 import make_axes_locatable


image = plt.matshow(pca.components_, cmap='viridis')

plt.yticks([0, 1], ['first', 'second'])

plt.xticks(range(len(cancer.feature_names)), cancer.feature_names,

           rotation=90, ha='left')

plt.xlabel('feature', size=15)

plt.ylabel('principal component', size=15)


ax = plt.gca() # GetCurrentAxis

divider = make_axes_locatable(ax)

cax = divider.append_axes('right', size='5%', pad='5%')

plt.colorbar(image, cax=cax)

plt.show()

breast cancer에서 찾은 처음 두개의 주성분 히트맵

첫번째 주성분의 모든 특성은 부호가 같음, 모든 특성 사이에 양의 상관관계가 있습니다.

두번째 주성분은 부호가 섞여있음을 알 수 있습니다.

모든 특성이 섞여 있기 때문에 축이 가지는 의미를 알기가 어려습니다.



참고 자료: 

[1]Introduction to Machine Learning with Python, Sarah Guido

+ Recent posts