본문 바로가기
머신러닝/PCA(Principal Component Analysis)

PCA - eigenface

by 미생22 2024. 6. 1.
728x90

AT&T와 캠브리지 대학의 공동 연구 데이터입니다.

이 중에서 한 사람의 얼굴로 분석을 해보려고 합니다.

우선은 sklearn의 datasets에 있구요. 이 이름은 fetch_olivetti_faces입니다.

 

얼굴 인식용으로 사용하긴 하지만 pca를 갓 공부할때도 자주 공부데이터로 쓰인다고 합니다. 한 사람당 10장의 데이터가 있고, 우리는 20번 분의 10장 데이터를 가져오겠습니다.

 

우리가 가져오는 데이터는 이미지의 pixel값입니다.

 

 

이 그림을 그릴건데, 2행 5열로 그릴겁니다. 이분이 올리베티 데이터의 20번 분이십니다.

이 10장의 데이터를 PCA로 주성분을 2개로 만든거죠. 픽셀의 크기는 보존되는거고, 10장의 사진이 두개의 큰 성분으로 바꼈다고 생각하면 정확합니다. pca 후 사진이 흐릿해지긴 하지만, 표정은 변하지 않는다는 장점이 있습니다.

 

코드로 불러오겠습니다.

 

from sklearn.datasets import fetch_olivetti_faces

faces_all = fetch_olivetti_faces()
print(faces_all.DESCR)

 

datasets에 있는 데이터다 보니까 DESCR가 있습니다. 해당 문서에 대해 자세히 표시해뒀네요.

 

 

faces_all

 

dict형태로 담겨있네요. keys()를 확인해보겠습니다.

 

faces_all.keys()

dict_keys(['data', 'images', 'target', 'DESCR'])

 

20번째 사진이므로 K=20으로 고정합니다.

K = 20
faces = faces_all.images[faces_all.target == K] #target이 20인 images만 가져오겠다는 뜻
faces

 

이 array들은 전부 pixel입니다. 그래서 imshow()를 통해 이미지를 그릴 수 있습니다.

 

import matplotlib.pyplot as plt

plt.imshow(faces[1])

 

이제 한 사람이 갖고있는 10장의 이미지들을 전부 그려보겠습니다.

 

2*5형태로 그릴거라 subplot을 시켜줘야합니다.

N = 2
M = 5

fig = plt.figure(figsize=(10, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05) #margin입니다.

for n in range(N*M):
    ax = fig.add_subplot(N, M, n+1) #n+1은 subplot의 위치를 지정하는겁니다.
    ax.imshow(faces[n], cmap=plt.cm.bone) #흑백이므로 bone을 설정해줍니다.
    ax.grid(False)
    ax.xaxis.set_ticks([]) #좌표는 없애라는 뜻
    ax.yaxis.set_ticks([]) #y도 마찬가지로 좌표를 없애라고 합니다.
plt.tight_layout() #첫번째행과 두번째행 사이에 간격이 있어서 이를 좁히라고 합니다.
plt.show()

subplots_adjust는 margin을 나타냅니다.

n+1은 subplot의 위치를 지정하는겁니다.

흑백이므로 bone을 설정해줍니다.

ax.xaxis.set_ticks([]) 는 해당 좌표를 없애라는 뜻입니다.

.tight_layout() 는 첫번째행과 두번째행 사이에 간격이 있어서 이를 좁히라고 합니다.

 

from sklearn.decomposition import PCA

pca = PCA(n_components=2) #과감하게 2로 잡아보겠습니다.

X = faces_all.data[faces_all.target == K]
X.shape

(10, 4096)

 

faces_all의 images가 아닌 data에서 X정보를 가져옵니다. fetch_olivetti_faces 데이터셋의 data와 images는 같은 데이터를 다르게 표현한 것입니다. data는 각 이미지가 1차원 벡터로 변환된 형태이며, images는 각 이미지가 2차원 배열로 제공됩니다. 두 속성은 같은 픽셀 값을 포함하고 있습니다. 위의 faces_all 데이터에서도 마찬가지로 data는 [[로 시작하고 images는 [[[로 시작하는것을 알 수 있습니다. 숫자는 동일하네요. 

 

  1. data 속성:
    • 각 이미지가 1차원 벡터로 변환되어 있음.
    • 예: (400, 4096) 크기 (이미지가 400개, 각 이미지가 64x64 픽셀, 4096 = 64 * 64)
  2. images 속성:
    • 각 이미지가 원래 2차원 형태로 저장되어 있음.
    • 예: (400, 64, 64) 크기 (이미지가 400개, 각 이미지가 64x64 픽셀)

 

import numpy as np

np.sqrt(4096)

64.0

 

10장의 사진이 64 by 64의 픽셀로 이루어져있다는 뜻입니다.

 

X[0].shape

(4096,)

 

10장 중 한 장의 사진입니다.

이제 이렇게 가져온 X를 pca에 fit_transform 시키겠습니다.

 

W = pca.fit_transform(X)
W.shape

(10, 2)

 

10장의 사진이 2개의 주성분으로 표시되는 것을 확인할 수 있습니다.

W를 살펴보겠습니다.

 

W

array([[-2.2136686,  4.213517 ],
       [ 2.2680957,  3.7376165],
       [ 0.6599964,  3.1209497],
       [-1.7500491, -1.4096581],
       [-3.3963842, -1.5552832],
       [-3.335589 , -1.7309325],
       [ 3.385142 , -2.1805747],
       [ 3.9338179, -1.5998918],
       [-3.2094407, -1.2286694],
       [ 3.6580768, -1.3670728]], dtype=float32)

 

10장의 사진은 변함이 없는데, 2로 바뀐 것은 10장의 사진을 표현하는 벡터가 10개인거고 벡터를 표현할때 2개로 표현한다는 겁니다.

 

X_inverse = pca.inverse_transform(W)
X_inverse

이렇게 만든 W를 inverse_transform 시켜서 X_inverse를 얻습니다.

 

X_inverse.shape

(10, 4096)

 

네, 이제 이 값을 통해 그림을 그려보겠습니다.

위 그림그리는 코드를 그대로 들고오겠습니다. 대신 faces가 아니라 X_inverse가 들어갑니다.
그리고 X_inverse[n]을 reshape(64, 64)로 잡습니다. 원래 그림이 64 by 64였기 때문이죠.

 

N = 2
M = 5

fig = plt.figure(figsize=(10, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)

for n in range(N*M):
    ax = fig.add_subplot(N, M, n+1) 
    ax.imshow(X_inverse[n].reshape(64, 64), cmap=plt.cm.bone)
    ax.grid(False)
    ax.xaxis.set_ticks([])
    ax.yaxis.set_ticks([])
plt.tight_layout()
plt.show()

 

reshape을 시킨 이유를 코드로 살펴보면,

 

faces[0].shape

(64, 64)

 

X_inverse[0].shape

(4096,)

 

faces는 64 by 64였는데 X_inverse는 4096 by 1이었기 때문입니다.

이렇게 해서 얻은 X_inverse로 그린 10장의 사진은 화질이 떨어져있네요. 귀도 거의 안보이고,, 10번째 사진은 입모양이 아예 닫혀있네요 ㅎㅎ

 

이제 eigenface에 대해 보여드리겠습니다.
components에는 두개의 벡터가 들어있습니다. 그래서 pca.components_[0]과 pca.components_[1]은 두 벡터로 보여질 수 있습니다.
이 것을 각각 face_p1, face_p2로 주고 이걸 그려보겠습니다.
mean, p1, p2를 각각 그리면 이렇게 됩니다.

 

face_mean = pca.mean_.reshape(64, 64) #pca.mean_의 shape이 (4096,)이기 때문에 바꾼겁니다.
face_p1 = pca.components_[0].reshape(64, 64)
face_p2 = pca.components_[1].reshape(64, 64)

plt.figure(figsize=(12, 7))
plt.subplot(131) #1행 3열 중에 첫번째 것
plt.imshow(face_mean, cmap=plt.cm.bone)
plt.grid(False)
plt.xticks([]); plt.yticks([]); plt.title('mean')

plt.subplot(132) #1행 3열 중에 두번째것
plt.imshow(face_p1, cmap=plt.cm.bone)
plt.grid(False)
plt.xticks([]); plt.yticks([]); plt.title('face_p1')

plt.subplot(133) #1행 3열 중에 세번째것
plt.imshow(face_p2, cmap=plt.cm.bone)
plt.grid(False)
plt.xticks([]); plt.yticks([]); plt.title('face_p2')

plt.show()

 

 

10장의 사진인 이 세장의 사진으로 모두 그릴 수 있다는 겁니다. mean(원점)과 eigenface1, eigenface2로 모든 얼굴이 설명 가능하다는 겁니다.

쉽게 생각하면 이렇습니다. mean값 즉 중앙값을 두고 오른쪽 두개의 벡터만큼 움직이면 표시가 가능하다는 겁니다.

이렇게 생각하시면 됩니다.

 

이제 mean에서 저 벡터들 방향만큼 지정된 간격으로 곱하면서 그림을 그려보도록 하겠습니다.

-5부터 10까지 10개의 숫자를 일정한 간격으로 뽑아냅니다.

import numpy as np

N = 2
M = 5

w = np.linspace(-5, 10, N*M)
w

array([-5.        , -3.33333333, -1.66666667,  0.        ,  1.66666667,
        3.33333333,  5.        ,  6.66666667,  8.33333333, 10.        ])

 

face_p1에 의해 어떤 변화가 생기는지 보겠습니다.

 

fig = plt.figure(figsize=(10, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)

for n in range(N*M):
	ax = fig.add_subplot(N, M, n+1)
    ax.imshow(face_mean + w[n]*face_p1, cmap=plt.cm.bone)
    ax.xaxis.set_ticks([])
    ax.yaxis.set_ticks([])
    plt.title('Weight :' + str(round(w[n])))
    
plt.tight_layout()
plt.show()

 

고개가 옆으로 돌아가는 느낌이죠?

이번에는 face_p2에 의해 어떤 변화가 생기는지 보겠습니다.

 

fig = plt.figure(figsize=(10, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)

for n in range(N*M):
	ax = fig.add_subplot(N, M, n+1)
    ax.imshow(face_mean + w[n]*face_p2, cmap=plt.cm.bone)
    ax.xaxis.set_ticks([])
    ax.yaxis.set_ticks([])
    plt.title('Weight :', str(round(w[n])))
    
plt.tight_layout()
plt.show()

 

점점 웃고있네요

 

이제 face_p1, face_p2에 의한 영향을 전부 한번에 그려보겠습니다.

 

nx, ny = (5, 5) #nx와 ny를 각각 5로 잡습니다.
x = np.linspace(-5, 8, nx) # -5에서 8까지의 범위를 5개의 점으로 나눕니다.
y = np.linspace(-5, 8, ny)

w1, w2 = np.meshgrid(x, y)

np.meshgrid 함수는 두 개의 1차원 배열(x와 y)을 입력받아 두 개의 2차원 배열을 생성합니다. 이 배열들은 x와 y의 조합으로 만들어진 격자 점을 나타냅니다.
#w1은 각 행이 x 배열을 반복한 형태이고, w2는 각 열이 y 배열을 반복한 형태입니다.

 

w1.shape

(5, 5)

 

나중에 실제 이미지 데이터랑 결합시키기 위해서 shape을 바꿔놓겠습니다.

 

w1 = w1.reshape(-1,)
w2 = w2.reshape(-1,)
#-1이라고 하면 그에 해당하는 행이나 열은 신경쓰지 말고 다른것에 맞추라는 뜻입니다.
#하나만 확인해보겠습니다.

w1.shape

(25,)

 

reshape(-1,) 이라고 하면 그에 해당하는 행이나 열은 신경쓰지 말고 다른것에 맞추라는 뜻입니다.

1열로 잘 만들어진 것을 확인할 수 있습니다.

 

w1

array([-5.  , -1.75,  1.5 ,  4.75,  8.  , -5.  , -1.75,  1.5 ,  4.75,
        8.  , -5.  , -1.75,  1.5 ,  4.75,  8.  , -5.  , -1.75,  1.5 ,
        4.75,  8.  , -5.  , -1.75,  1.5 ,  4.75,  8.  ])

 

이제 face_p1과 face_p2를 둘다 고려해 두개에 의해 어떤 변화가 생기는지 보겠습니다.

 

fig = plt.figure(figsize=(10, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)

for n in range(N*M):
    ax = fig.add_subplot(N, M, n+1)
    ax.imshow(face_mean + w1[n] * face_p1 + w2[n] * face_p2, cmap=plt.cm.bone)
    ax.xaxis.set_ticks([])
    ax.yaxis.set_ticks([])
    plt.title('Weight :' + str(round(w1[n])) + ',' + str(round(w2[n])), color='gray')

plt.tight_layout()
plt.show()

 

 

 

728x90

'머신러닝 > PCA(Principal Component Analysis)' 카테고리의 다른 글

MNIST using PCA and kNN  (1) 2024.06.04
HAR using PCA  (0) 2024.06.04
PCA - wine 데이터  (0) 2024.06.01
PCA - iris 데이터  (0) 2024.06.01
PCA란?  (0) 2024.05.31