Developer's Development

3.2.12 [머신러닝] 차원 축소 본문

데이터 분석과 머신러닝, 딥러닝/머신러닝

3.2.12 [머신러닝] 차원 축소

mylee 2025. 8. 3. 18:38
차원의 저주 (Curse of Dimensionality)

 

데이터의 차원이 증가함에 따라 발생하는 문제를 지칭한다. 고차원 데이터에서는 데이터 포인트 간의 거리가 멀어지고 희소성이 증가하며, 모델 학습과 일반화가 어려워지는 현상이 나타난다.

 

  • 차원의 저주가 미치는 영향

1. 모델 성능 저하 : 고차원 데이터는 모델이 패턴을 학습하기 어려워 과적합 또는 성능 저하를 초래한다.

2. 계산 복잡도 증가 : 차원이 증가하면 학습에 필요한 계산량이 기하급수적으로 증가한다.

3. 시각화의 어려움 : 고차원 데이터를 인간이 이해하거나 시각화하기 어려워진다.

 

  • 차원의 저주 해결법

차원을 줄여서 데이터의 본질적인 특성을 유지하면서도 문제를 단순화해야 한다.

 

 

차원 축소를 위한 접근 방법

 

  • 차원 축소

고차원 데이터를 저차원으로 변환하여 데이터를 간소화하는 과정이다. 데이터의 중요한 패턴을 유지하면서 차원을 감소시켜 차원의 저주를 완화한다.

 

  • 차원 축소 필요성

- 모델의 성능이 잘 안나올 때 차원 축소를 해볼 수 있다.

- 각각의 특징은 하나의 특징을 하나의 차원에 넣어서 학습한다. 따라서 컬럼이 많으면 많을수록 고차원의 공간에서 특징을 찾아야 하므로, 차원이 작은 데서 학습하는 것이 훨씬 효율이 높다.

- 그러므로 학습을 조금이라도 더 잘하게 하기 위해서 유사한 컬럼을 합쳐서 특징을 설명할 수 있다면 합치는 것이 좋다.

 

  • 주요 차원 축소 기법

1. 특성 선택 (Feature Selection)

  - 데이터를 구성하는 중요한 특성만 선택하는 방법이다.

  - ex) 유의미한 변수 선택, 상관분석 등

2. 특성 추출 (Feature Extraction)

  - 기존 특성에서 새로운 저차원 특성을 생성하는 방법이다.

  - ex) PCA, LLE, t-SNE 등

 

👉 단변량 회귀 분석 : 하나의 독립 변수와 하나의 종속 변수 간의 관계를 분석하는 회귀 분석 기법이다.

👉 다변량 회귀 분석 : 여러 개의 독립 변수와 하나의 종속 변수 간의 관계를 분석하는 회귀 분석 기법이다.

 

  • 차원 축소의 장점

1. 모델 성능 향상 : 과적합을 방지하고 일반화를 개선할 수 있다.

2. 시간 및 저장 공간 절약 : 계산량과 메모리 사용량이 감소한다.

3. 시각화 용이 : 데이터를 2D 또는 3D로 표현하여 이해도를 높일 수 있다.

 

 

PCA (Principal Component Analysis, 주성분 분석)

 

데이터의 분산을 최대한 보존하는 방향으로 데이터를 선형 변환하여 저차원으로 축소하는 기법이다.

PCA로 차원 축소 시에는 기존 데이터의 정보 유실이 최소화되는 것이 당연하다. 이를 위하여 PCA는 가장 높은 분산을 가지는 데이터의 축을 찾아 차원을 축소하고, 이것이 PCA의 주성분이 된다.

 

  • PCA의 주요 과정

1. 데이터 정규화 : 모든 특성을 동일한 스케일로 조정한다.

2. 공분산 행렬 계산 : 데이터의 분산과 상관관계를 계산한다.

3. 고유벡터와 고유값 계산 : 공분산 행렬의 고유벡터와 고유값을 계산하여 주성분을 구한다.

4. 주성분 선택 및 변환 : 가장 큰 고유값에 해당하는 고유벡터를 선택하여 데이터를 저차원으로 투영한다.

* 주성분은 데이터 행렬 X의 공분산 행렬 C의 고유벡터를 이용해 구한다.

 

👉 PCA는 데이터를 가장 잘 설명할 수 있는, 데이터의 분산을 최대화하는 방향으로 축을 결정

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris

iris_data = load_iris()
iris_df = pd.DataFrame(iris_data.data, columns=iris_data.feature_names)
iris_df['target'] = iris_data.target
iris_df.describe()

# 특성 선택을 통한 시각화 (2개의 특성만 선택하여 시각화)
markers = ['^', 's', 'o']

for i, marker in enumerate(markers):
    x = iris_df[iris_df['target'] == i]['sepal length (cm)']
    y = iris_df[iris_df['target'] == i]['sepal width (cm)']
    plt.scatter(x, y, marker=marker, label=iris_data.target_names[i])
    
plt.legend()
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()

from sklearn.decomposition import PCA

# 2개의 차원으로 차원 축소
pca = PCA(n_components=2)

X = iris_df.iloc[:, :-1]

# 차원 축소 진행
pca.fit(X)
iris_pca = pca.transform(X)

iris_pca_df = pd.DataFrame(iris_pca, columns=['pca_col1', 'pca_col2'])
iris_pca_df['target'] = iris_data.target

# 차원 축소 시각화
markers = ['^', 's', 'o']

for i, marker in enumerate(markers):
    x = iris_pca_df[iris_pca_df['target'] == i]['pca_col1']
    y = iris_pca_df[iris_pca_df['target'] == i]['pca_col2']
    plt.scatter(x, y, marker=marker, label=iris_data.target_names[i])
    
plt.legend()
plt.xlabel('pca_col1')
plt.ylabel('pca_col2')
plt.show()

# 원본 데이터와 PCA 차원 축소 데이터를 이용한 교차 검증 (학습 및 평가)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

lr_clf = LogisticRegression(max_iter=1000)
scores = cross_val_score(lr_clf, iris_data.data, iris_data.target, cv=5)
print("원본 데이터 평가:", np.mean(scores))

lr_clf_pca = LogisticRegression(max_iter=1000)
pca_scores = cross_val_score(lr_clf_pca, iris_pca_df[['pca_col1', 'pca_col2']], iris_pca_df['target'], cv=5)
print("PCA 데이터 평가:", np.mean(pca_scores))

 

  • PCA 과일 이미지 데이터

흑백 이미지 데이터 픽셀값 0(흑) ~ 255(백)

fruits = np.load('./data/fruits_300.npy')
fruits.shape    # (300, 100, 100) = 300개의 이미지 * 100행 * 100열

# 이미지 시각화 함수
def draw_fruits(arr, ratio=1):
    N = len(arr)
    rows = int(np.ceil(N / 10))
    cols = N if rows < 2 else 10
    fig, ax = plt.subplots(rows, cols, figsize=(cols * ratio, rows * ratio), squeeze=False)
    
    for i in range(rows):
        for j in range(cols):
            if i * 10 + j < N:
                ax[i, j].imshow(arr[i * 10 + j], cmap='gray_r')
            ax[i, j].axis('off')
    
    plt.show()
    
draw_fruits(fruits[:100])
draw_fruits(fruits[100:200])
draw_fruits(fruits[200:])
# 1차원 데이터로 펼치기
fruits_1d = fruits.reshape(300, 100 * 100)
fruits_1d.shape

# 50 차원으로 차원 축소
pca = PCA(n_components=50)
pca.fit(fruits_1d)

# 주성분 벡터 확인
pca.components_.shape   # (50, 10000) = (PCA로 구현한 주성분 개수, 원본 데이터 특성 개수)
(50, 10000)

draw_fruits(pca.components_.reshape(-1, 100, 100))
# 차원 축소 데이터 저장
fruits_pca = pca.transform(fruits_1d)
print(fruits_pca.shape)

np.save('./data/fruits_pca.npy', fruits_pca)

# 차원 축소 데이터 로드
fruits_pca = np.load('./data/fruits_pca.npy')

# 주성분 설명 비율 확인
print(pca.explained_variance_ratio_)
print(pca.explained_variance_ratio_.sum())
plt.plot(pca.explained_variance_ratio_)
plt.show()

# 타겟 데이터 설정
target = np.array([0] * 100 + [1] * 100 + [2] * 100)

# cross_val_score : 각 cv의 점수 반환
# cross_validate : 각 cv의 학습시간, 테스트시간, cv의 점수 반환 (평가지표 여러 개 사용 가능)
from sklearn.model_selection import cross_validate

# 원본 데이터 교차검증 점수 확인
lr_clf = LogisticRegression(max_iter=1000)
result = cross_validate(lr_clf, fruits_1d, target, cv=3)
print("원본 데이터 교차검증")
print(result)

# PCA 데이터 교차검증 점수 확인
lr_clf_pca = LogisticRegression(max_iter=1000)
pca_result = cross_validate(lr_clf_pca, fruits_pca, target, cv=3)
print("PCA 데이터 교차검증")
print(pca_result)

# 차원 축소 데이터를 원본 형태로 복구 
fruits_inverse = pca.inverse_transform(fruits_pca)
fruits_inverse.shape

# 복구 데이터 시각화
draw_fruits(fruits_inverse.reshape(-1, 100, 100))

 

 

커널 PCA

 

PCA를 확장하여 비선형 데이터에도 적용할 수 있도록 설계된 기법이다. 커널 트릭(Kernel Trick)을 사용하여 데이터를 고차원 공간으로 매핑한 뒤, PCA를 수행한다.

 

  • 커널 PCA의 장점

1. 비선형 구조를 효과적으로 캡처한다.

2. 복잡한 데이터의 패턴을 유지하면서 차원을 축소할 수 있다.

 

 

LLE (Locally Linear Embedding, 지역 선형 임베딩)

 

고차원 데이터를 저차원으로 변환하는 비선형 차원 축소 기법이다. 데이터의 국소적인 선형 구조를 보존하며 차원을 축소한다.

 

  • LLE의 작동 원리

1. 각 데이터 포인트의 가까운 이웃(k-nearest neighbors)을 찾는다.

2. 각 데이터 포인트를 이웃 데이터 포인트의 선형 결합으로 표현한다.

3. 저차원 공간에서 같은 선형 관계를 유지하도록 데이터를 매핑한다.

 

👉 데이터 포인트를 근접한 이웃과 선형 결합으로 표현하고 이를 유지하도록 저차원 공간에 매핑

from sklearn.datasets import load_iris
from sklearn.manifold import LocallyLinearEmbedding

iris_data = load_iris()

# LLE 변환
lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10)
iris_lle = lle.fit_transform(iris_data.data)
# iris_lle.shape

iris_lle_df = pd.DataFrame(iris_lle, columns=['lle_col1', 'lle_col2'])
iris_lle_df['target'] = iris_data.target

# 차원 축소 시각화
markers = ['^', 's', 'o']

for i, marker in enumerate(markers):
    x = iris_lle_df[iris_lle_df['target'] == i]['lle_col1']
    y = iris_lle_df[iris_lle_df['target'] == i]['lle_col2']
    plt.scatter(x, y, marker=marker, label=iris_data.target_names[i])
    
plt.legend()
plt.xlabel('lle_col1')
plt.ylabel('lle_col2')
plt.show()

from sklearn.model_selection import cross_validate
from sklearn.linear_model import LogisticRegression

lr_clf = LogisticRegression(max_iter=1000)
result = cross_validate(lr_clf, iris_lle_df[['lle_col1', 'lle_col2']], iris_lle_df['target'], cv=3)
result

print(lle.n_neighbors)
print(lle.n_components)
print(lle.reconstruction_error_)

 

 

LDA (Linear Discriminant Analysis, 선형 판별 분석)

 

차원 축소 기법이면서 동시에 지도학습으로도 사용된다.

LDA는 클래스 간의 분산을 최대화하고, 클래스 내 분산을 최소화하는 축을 찾아 데이터를 변환하는 방법이다.

분류 문제에서 효과적인 차원 축소 방법으로, 데이터가 선형적으로 구분될 수 있을 때 유용하다.

 

👉 타겟 클래스 간 분리를 최대로 하는 축으로 결정

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

iris_data = load_iris()

# 데이터 스케일링 
scaler = StandardScaler()
iris_scaled = scaler.fit_transform(iris_data.data)

# LDA 변환 
lda = LinearDiscriminantAnalysis(n_components=2)
# iris_lda = lda.fit_transform(iris_data.data, iris_data.target)
iris_lda = lda.fit_transform(iris_scaled, iris_data.target)
# iris_lda.shape

iris_lda_df = pd.DataFrame(iris_lda, columns=['lda_col1', 'lda_col2'])
iris_lda_df['target'] = iris_data.target

# 차원 축소 시각화
markers = ['^', 's', 'o']

for i, marker in enumerate(markers):
    x = iris_lda_df[iris_lda_df['target'] == i]['lda_col1']
    y = iris_lda_df[iris_lda_df['target'] == i]['lda_col2']
    plt.scatter(x, y, marker=marker, label=iris_data.target_names[i])
    
plt.legend()
plt.xlabel('lda_col1')
plt.ylabel('lda_col2')
plt.show()

 

 

상관 관계 분석

 

두 개 이상의 변수 간 어떤 관계가 있는지 파악하기 위한 분석이다.

 

  • 피어슨 상관 계수

상관 관계를 나타내는 방법이 굉장히 까다로운데, '피어슨'이 이 상관관계를 어떻게 나타내야 좋을지 연구하여 상관 계수를 도출해냈다.

👉 양의 상관관계 : 해당 데이터가 커질수록 관계된 데이터도 같이 커진다. (비례 관계)

👉 음의 상관관계 : 해당 데이터가 커질수록 관계된 데이터의 값은 줄어든다. (반비례 관계)