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는 [[[로 시작하는것을 알 수 있습니다. 숫자는 동일하네요.
- data 속성:
- 각 이미지가 1차원 벡터로 변환되어 있음.
- 예: (400, 4096) 크기 (이미지가 400개, 각 이미지가 64x64 픽셀, 4096 = 64 * 64)
- 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()


'머신러닝 > 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 |