본문 바로가기

AI/대학원

PCA 와 FDA 실습 및 분석

개요

AI 프로그래밍 수업 시간에 수행한 PCA 와 FDA 실습 및 분석 내용을 기록으로 남긴다.

 

문제

다음에 대해 주성분 분석(PCA) 및 피셔 판별 분석(FDA)을 수행합니다.
데이터 파일의 MNIST 데이터 집합(클래스 '1', '5', '6'이 있는 MNIST 데이터 집합)에 대해 PCA 와 FDA 을 수행합니다,

 

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
from google.colab import drive
drive.mount('/content/drive')
train_x = np.load('/content/drive/MyDrive/data_files/mnist_train_images.npy')
train_y = np.load('/content/drive/MyDrive/data_files/mnist_train_labels.npy')
test_x = np.load('/content/drive/MyDrive/data_files/mnist_test_images.npy')
test_y = np.load('/content/drive/MyDrive/data_files/mnist_test_labels.npy')

n_train = train_x.shape[0]
n_test = test_x.shape[0]

print ("The number of training images : {}, shape : {}".format(n_train, train_x.shape))
print ("The number of testing images : {}, shape : {}".format(n_test, test_x.shape))
idx = np.random.randint(train_x.shape[0])
img = train_x[idx].reshape(28,28)

plt.figure(figsize = (6,6))
plt.imshow(img,'gray')
plt.title("Label : {}".format(np.argmax(train_y[idx,:])))
plt.xticks([])
plt.yticks([])
plt.show()

 

(1) 학습 데이터 세트에 PCA를 적용

### 1. PCA 적용 및 시각화 ###
# PCA 적용 (2개의 주성분)
pca = PCA(n_components=2)
pca.fit(train_x)
train_x_pca = pca.transform(train_x)
test_x_pca = pca.transform(test_x)

# 클래스별 색상 설정
colors = {1: 'red', 5: 'blue', 6: 'green'}
markers = {'train': 'o', 'test': 's'}  # 훈련 데이터는 원, 테스트 데이터는 사각형으로 표시

# train_y와 test_y가 원-핫 인코딩되어 있으므로 이를 단일 클래스 값으로 변환
train_y = np.argmax(train_y, axis=1)
test_y = np.argmax(test_y, axis=1)

# 훈련 데이터 시각화
plt.figure(figsize=(10, 8))
for class_label in np.unique(train_y):  # train_y에 있는 각 클래스를 순회
    plt.scatter(train_x_pca[train_y == class_label, 0],
                train_x_pca[train_y == class_label, 1],
                c=colors[class_label], label=f'Train Class {class_label}', marker=markers['train'], alpha=0.5)

# 테스트 데이터 시각화
for class_label in np.unique(test_y):  # test_y에 있는 각 클래스를 순회
    plt.scatter(test_x_pca[test_y == class_label, 0],
                test_x_pca[test_y == class_label, 1],
                c=colors[class_label], label=f'Test Class {class_label}', marker=markers['test'], alpha=0.5)

# 레이블 추가 및 플롯 설정
plt.xlabel('First principal component')
plt.ylabel('Second principal component')
plt.title('PCA projection of MNIST data (Classes 1, 5, 6)')
plt.legend(loc='best')  # 범례 추가
plt.grid(True)
plt.show()

 

(2)  k 차원 표현에서 데이터 재구성하기

# 데이터셋 평균을 구하는 함수
def compute_mean(X):
    return np.mean(X, axis=0)

# PCA를 사용하여 k차원으로 복원하는 함수
def pca_reconstruction(X, pca, k):
    W_k = pca.components_[:k]  # 상위 k개의 주성분 벡터
    X_centered = X - pca.mean_  # PCA 학습 시 계산된 평균을 사용해 중심화
    X_reconstructed = pca.mean_ + X_centered.dot(W_k.T).dot(W_k)  # 복원된 데이터
    return X_reconstructed

# 원본 이미지와 복원 이미지를 시각화하는 함수
def plot_reconstructed_images(original, reconstructed, k, n=10):
    plt.figure(figsize=(20, 4))
    for i in range(n):
        # 원본 이미지
        plt.subplot(2, n, i + 1)
        plt.imshow(original[i].reshape(28, 28), cmap='gray')
        plt.title("Original")
        plt.axis('off')

        # 복원된 이미지
        plt.subplot(2, n, i + n + 1)
        plt.imshow(reconstructed[i].reshape(28, 28), cmap='gray')
        plt.title(f"Recon (k={k})")
        plt.axis('off')
    plt.show()

# 복원할 차원 수 설정
k_values = [1, 2, 5, 10, 20, 50, 100]

# 테스트 데이터 복원 및 시각화
for k in k_values:
    # k차원으로 복원
    X_reconstructed = pca_reconstruction(test_x, pca, k)

    # 임의의 10개의 샘플 선택
    random_indices = np.random.choice(test_x.shape[0], 10, replace=False)
    original_images = test_x[random_indices]
    reconstructed_images = X_reconstructed[random_indices]

    # 원본 및 복원된 이미지 시각화
    plot_reconstructed_images(original_images, reconstructed_images, k)

  • 작은 k 값 (1, 2, 5)에서는 복원이 상당히 제한적이며, 이미지가 매우 흐릿하게 나타납니다. 숫자의 구체적인 형태는 잘 유지되지 않습니다.
  • 중간 k 값 (10, 20)에서는 복원 품질이 크게 향상되며, 숫자의 윤곽과 디테일이 훨씬 더 잘 나타납니다.
  • 큰 k 값 (50, 100)에서는 거의 원본 이미지와 구분할 수 없을 정도로 복원 품질이 높습니다.
  • 따라서, k 값이 증가함에 따라 복원된 이미지의 품질이 점점 더 좋아지며, k가 50 정도만 되어도 원본 이미지와 거의 차이가 없는 복원이 가능합니다.

 

(3) 훈련 데이터 세트에 대해 FDA를 수행

# FDA를 적용하여 이차원으로 투영하는 함수
def perform_fda(train_x, train_y, test_x):
    lda = LDA(n_components=2)  # 2차원으로 투영할 FDA 모델 생성

    # 훈련 데이터에 대해 학습
    lda.fit(train_x, train_y)

    # 훈련 데이터와 테스트 데이터를 각각 투영
    train_x_lda = lda.transform(train_x)  # 훈련 데이터 변환
    test_x_lda = lda.transform(test_x)  # 테스트 데이터 변환

    return train_x_lda, test_x_lda

# FDA 적용
train_x_lda, test_x_lda = perform_fda(train_x, train_y, test_x)

# 클래스별 색상 설정
colors = {1: 'red', 5: 'blue', 6: 'green'}
markers = {'train': 'o', 'test': 's'}  # 훈련 데이터는 원, 테스트 데이터는 사각형으로 표시

# 훈련 데이터 시각화
plt.figure(figsize=(10, 8))
for class_label in np.unique(train_y):
    plt.scatter(train_x_lda[train_y == class_label, 0],
                train_x_lda[train_y == class_label, 1],
                c=colors[class_label], label=f'Train Class {class_label}', marker=markers['train'], alpha=0.5)

# 테스트 데이터 시각화
for class_label in np.unique(test_y):
    plt.scatter(test_x_lda[test_y == class_label, 0],
                test_x_lda[test_y == class_label, 1],
                c=colors[class_label], label=f'Test Class {class_label}', marker=markers['test'], alpha=0.5)

# 레이블 추가 및 플롯 설정
plt.xlabel('First discriminant direction')
plt.ylabel('Second discriminant direction')
plt.title('FDA projection of MNIST data (Classes 1, 5, 6)')
plt.legend(loc='best')  # 범례 추가
plt.grid(True)
plt.show()

 

(4)  PCA와 FDA에서 얻은 저차원 표현에 대해 scikit-learn을 사용하여 가장 가까운 이웃(1-NN) 분류를 구현

# 1-NN 분류기를 학습하고 테스트 데이터에 대해 정확도를 평가하는 함수
def evaluate_1nn(train_x, train_y, test_x, test_y):
    knn = KNeighborsClassifier(n_neighbors=1)
    knn.fit(train_x, train_y)  # 훈련 데이터를 사용하여 분류기 학습
    predictions = knn.predict(test_x)  # 테스트 데이터에 대한 예측
    accuracy = accuracy_score(test_y, predictions)  # 정확도 계산
    return accuracy

# PCA 및 FDA 차원 축소 결과에 따른 1-NN 분류 평가
def evaluate_classification(train_x_pca, test_x_pca, train_x_fda, test_x_fda, train_y, test_y):
    # PCA에 대한 1-NN 평가
    pca_accuracy = evaluate_1nn(train_x_pca, train_y, test_x_pca, test_y)

    # FDA에 대한 1-NN 평가
    fda_accuracy = evaluate_1nn(train_x_fda, train_y, test_x_fda, test_y)

    return pca_accuracy, fda_accuracy

# 성능 평가 및 결과 시각화
pca_accuracy, fda_accuracy = evaluate_classification(train_x_pca, test_x_pca, train_x_lda, test_x_lda, train_y, test_y)

print(f"PCA with reduced dimensions: Accuracy = {pca_accuracy:.4f}")
print(f"FDA with reduced dimensions: Accuracy = {fda_accuracy:.4f}")

# 정확도 결과 시각화 (단일 비교)
labels = ['PCA', 'FDA']
accuracies = [pca_accuracy, fda_accuracy]

plt.bar(labels, accuracies, color=['blue', 'green'])
plt.ylim(0, 1)
plt.ylabel('Accuracy')
plt.title('1-NN Classification Accuracy (PCA vs FDA)')
plt.show()
PCA with reduced dimensions: Accuracy = 0.8998
FDA with reduced dimensions: Accuracy = 0.9648

  • FDA는 클래스 간 분리도를 최대화하기 때문에 1-NN 분류에서 PCA보다 더 높은 성능을 보입니다. PCA는 클래스 정보를 사용하지 않기 때문에 분류 성능이 다소 제한될 수 있습니다.
  • FDA의 장점: FDA는 차원이 적더라도 클래스 간의 차이를 잘 반영하여 더 나은 분류 성능을 보일 수 있습니다. 이번 결과에서도 이를 확인할 수 있습니다.
  • PCA의 장점: PCA는 클래스 정보 없이 데이터의 전체적인 분산을 최대한 보존하는 방식으로 투영하므로, 주성분을 사용해도 어느 정도의 성능을 유지할 수 있습니다.
  • 따라서, 지도 학습이 필요한 상황에서는 FDA가 더 나은 선택이 될 수 있으며, 비지도 학습 환경에서는 PCA가 적절한 선택일 수 있습니다.