[파이썬 머신러닝 완벽 가이드] 6장 정리

나린·2025년 5월 13일

머신러닝

목록 보기
6/9

https://colab.research.google.com/drive/1G1jMN8tJ01jNVXtIO8czPbXICL7cX01G?usp=sharing
https://colab.research.google.com/drive/1W1zZHWxPY8iQnKqc-0ic-J75l5caXflV?usp=sharing
https://colab.research.google.com/drive/190Y0a1PPXvW8xx8LFBJROJmP6XKB9kZe?usp=sharing

1. 차원 축소(Dimension Reduction) 개요

  • 차원 축소는 다차원 데이터를 저차원으로 축소하여 새로운 데이터 세트를 생성하는 기법임
  • 차원이 높아질수록 데이터 간 거리가 멀어지고, 희소한 구조로 인해 예측 신뢰도가 떨어질 수 있음
  • 고차원 데이터에서는 상관관계가 높은 피처들이 많아 다중 공선성 문제로 인해 모델 성능 저하 가능성이 있다.
  • 차원을 줄이면 다음과 같은 이점이 있다:
    모델 성능 향상 가능
    데이터 해석이 쉬워지고 시각화가 가능
    학습 속도 및 처리 효율 향상

특히 3차원 이하로 축소하면 시각화를 통한 데이터 분포 파악이 용이하다.
차원 축소는 크게 피처 선택(feature selection)피처 추출(feature extraction)로 나뉜다.

피처 선택은

불필요하거나 종속성이 강한 피처를 제거

데이터의 대표적인 주요 피처만 선택

피처 추출은

기존 피처를 압축하여 더 의미 있는 새로운 피처로 변환

원래의 피처와는 완전히 다른 형태의 데이터로 변형됨

피처 추출은 단순한 압축이 아니라 데이터를 더 잘 설명할 수 있는 공간으로 매핑하는 과정이다.

예시:

학생 데이터를 성적, 활동 등 여러 요소로 구성 → 이를 학업 성취도, 문제 해결력 등과 같은 잠재적 특성(Latent Factor)으로 변환할 수 있다.
차원 축소는 단순한 데이터 압축이 아닌 데이터의 잠재적인 의미(요소)를 추출하는 것이 핵심이다.

PCA, SVD, NMF는 대표적인 차원 축소 알고리즘으로, 이미지와 텍스트 분야에서 활용도가 높다.

이미지 분야:

수많은 픽셀로 구성된 이미지를 더 적은 수의 피처로 압축 가능.

함축된 이미지 표현은 과적합 위험을 줄이고 예측 성능을 향상시킬 수 있다.

텍스트 분야:

문서는 수많은 단어(피처)로 구성되며, 이는 고차원 데이터를 형성.

차원 축소 알고리즘은 단어 구성에서 숨겨진 의미(Semantic)주제(Topic)를 추출.

특히 SVD와 NMF는 시맨틱 토픽 모델링에서 자주 사용됨.

2. PCA(Principal Component Analysis)

PCA 개요

PCA(Principal Component Analysis)는 가장 대표적인 차원 축소 기법

PCA는 여러 변수 간에 존재하는 상관관계를 이용해 이를 대표하는 주성분(Principal Component)을 추출해 차원을 축소하는 기법입니다.

PCA로 차원을 축소할 때는 기존 데이터의 정보 유실이 최소화되는 것이 당연합니다.

이를 위해서 PCA는 가장 높은 분산을 가지는 데이터의 축을 찾아 이 축으로 차원을 축소하는데, 이것이 PCA의 주성분이 됩니다(즉, 분산이 데이터의 특성을 가장 잘 나타내는 것으로 간주합니다).

키와 몸무게 2개의 피처를 가지고 있는 데이터 세트가 다음과 같이 구성돼 있다고 가정해 보겠습니다.

이 2 개의 피처를 한 개의 주성분을 가진 데이터 세트로 차원 축소를 할 수 있습니다. 데이터 변동성이
가장 큰 방향으로 축을 생성하고 , 새롭게 생성된 축으로 데이터를 투영하는 방식입니다.

PCA는 가장 큰 데이터 변동성(Variance)을 기반으로 첫 번째 벡터 축을 생성합니다.

두 번째 축은 첫 번째 벡터 축에 직각이 되는 직교 벡터로 설정합니다.

세 번째 축은 두 번째 축과 직각이 되도록 생성합니다.

이렇게 생성된 축들에 원본 데이터를 투영함으로써, 벡터 축의 개수만큼의 차원으로 원본 데이터가 축소됩니다.

PCA는 원본 데이터의 피처 개수에 비해 매우 작은 주성분으로 원본 데이터의 총 변동성을 대부분 설명할 수 있는 분석법이다.

PCA는 입력 데이터의 공분산 행렬을 고유값 분해하고, 고유벡터에 입력 데이터를 선형 변환한다.

고유벡터는 PCA의 주성분 벡터이며, 입력 데이터의 분산이 큰 방향을 나타낸다.

고윳값은 고유벡터의 크기를 나타내며, 입력 데이터의 분산을 의미한다.

선형 변환은 특정 벡터에 행렬 A를 곱해 새로운 벡터로 변환하는 것으로, 공간을 바꾸는 개념이다.

분산은 한 변수의 변동을 의미하고, 공분산은 두 변수 간의 변동 관계를 의미한다.

공분산 행렬은 여러 변수 간의 공분산을 포함하는 정방형 행렬이다.

공분산 행렬에서 대각선 원소는 각 변수 (X, Y, 2) 의 분산을 의미함

  • 대각선 이외의 원소는 가능한 모든 변수 쌍 간의 공분산을 의미함
  • X, Y, Z 의 분산은 각각 3.0, 4.5, 0.91
  • X 와 Y 의 공분산은 -0.71, X 와 Z 의 공분산은 -0.24, Y 와 Z 의 공분산은 0.28

= 고유벡터는 행렬 A 를 곱하더라도 방향이 변하지 않고 그 크기만 변하는 벡터를 지칭함

Ax=axAx = ax

(A 는 행렬 , x 는 고유벡터 , a 는 스칼라값 )

  • 고유벡터는 여러 개가 존재

  • 정방 행렬은 최대 그 차원 수만큼의 고유벡터를 가질 수 있음

  • 예를 들어 2x2 행렬은 두 개의 고유벡터를 , 3x3 행렬은 3 개의 고유벡터를 가질 수 있음

  • 이렇게 고유벡터는 행렬이 작용하는 힘의 방향과 관계가 있어서 행렬을 분해하는 데 사용됨

공분산 행렬

정방행렬 (Square Matrix)
정방행렬은 열과 행이 같은 행렬을 지칭하는데 , 정방행렬 중에서 대각 원소를 중심으로 원소 값이 대칭되는 행렬 , 즉 AT=AA^T = A 인 행렬을 대칭행렬이라고 부름

대칭행렬 (Symmetric Matrix)

  • 공분산 행렬은 개별 분산값을 대각 원소로 하는 대칭행렬임
  • 이 대칭행렬은 고유값 분해와 관련해 매우 좋은 특성이 있음.
  • 대칭행렬은 항상 고유벡터를 직교행렬 (orthogonal matrix) 로, 고유값을 정방 행렬로 대각화할 수 있다는 것임

입력 데이터의 공분산 행렬을 C 라고 하면 공분산 행렬의 특성으로 인해 다음과 같이 분해할 수 있음

C=PΣPTC = P \Sigma P^T

  • P 는 n X n 의 직교행렬
  • Σ\Sigma 는 n X n 정방행렬
  • PTP^T 는 행렬 P 의 전치 행렬

위 식은 고유벡터 행렬과 고
유값 행렬로 다음과 같이 대응됨

  • 공분산 C 는 고유벡터 직교 행렬 고유값 정방 행렬 고유벡터 직교 행렬의 전치 행렬로 분해됨

  • eie_i 는 i 번째 고유벡터를 , λi\lambda_i 는 i 번째 고유벡터의 크기를 의미 는 가장 분산이 큰 방향을 가진고유벡터이며 , e2e_2e1e_1 에 수직이면서 다음으로 가장 분산이 큰 방향을 가진 고유벡터임

  • 선 형대수식까지 써가면서 강조하고 싶었던 것은 입력 데이터의 공분산 행렬이 고유벡터와 고유값으로 분
    해될 수 있으며 , 이렇게 분해된 고유벡터를 이용해 입력 데이터를 선형 변환하는 방식이 PCA 라고 함
    PCA 수행 과정

  1. 입력 데이터 세트의 공분산 행렬을 생성
  2. 공분산 행렬의 고유벡터와 고유값을 계산
  3. 고유값이 가장 큰 순으로 K 개 (PCA 변환 차수만큼 ) 만큼 고유벡터를 추출
  4. 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환
  • PCA 는 많은 속성으로 구성된 원본 데이터를 그 핵심을 구성하는 데이터로 압축한 것
  • 붓꽃 (Iris) 데이터 세트 4 개의 속성을 2 개의 PCA 차원으로 압축해 원래 데이터 세트와 압축된 데이터 세트가 어떻게 달라졌는지 확인해 보겠습니다.
  • 먼저 사이킷런의 붓꽃 데이터를 load_iris() API 를 이용해 로딩한 뒤 이 데이터를 더 편하게 시각화하기 위해 DataFrame으로 변환
from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
iris = load_iris()
# 넘파이 데이터 세트를 판다스 Dataframe으로 변환
columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
irisDF = pd.DataFrame(iris.data, columns=columns)
irisDF[ 'target' ]=iris.target
irisDF.head (3)

각 품종에 따라 원본 붓꽃 데이터 세트가 어떻게 분포돼 있는지 2 차원으로 시각화해 보겠습니다.

  • 2 차 원으로 표현하므로 두 개의 속성인 sepal length 와 sepal width 를 X 축 , Y 축으로 해 품종 데이터 분포를 나타냄
#setosa 는 세모 , versicolor는 네모 , virginica는 동그라미로 표현
markers=['^', 's' , 'o']
#setosa 의 target 값은 0, versicolor 는 1, virginica는 2. 각 target 별로 다른 모양으로 산점도로 표시
for i, marker in enumerate(markers):
    x_axis_data = irisDF[irisDF['target']==i]['sepal_length']
    y_axis_data = irisDF[irisD['target']==i]['sepal_width']
    plt.scatter(x_axis_data, y_axis_data, marker=marker, label=iris. target_names[i])
plt.legend()
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()

  • Setosa 품종은 sepal width > 3.0 그리고 sepal length ≤ 6.0 조건에서 일정하게 분포

  • Versicolor, Virginica는 단순한 sepal 조건만으로는 분류 어려움

  • PCA를 통해 4개의 속성을 2개로 축소해 2차원 시각화 진행

  • PCA 적용 전 모든 속성은 스케일링 필요

  • 사이킷런의 StandardScaler를 이
    용해 평균이 0, 분산이 1 인 표준 정규 분포로 iris 데이터 세트의 속성값들을 변환 예졔

from sklearn.preprocessing import StandardScaler
# Target 값을 제외한 모든 속성 값을 StandardScaler를 이용해 표준 정규 분포를 가지는 값들로 변환
iris_scaled = StandardScaler().fit_transform(irisDF.iloc[:, :-1])
  • 스케일링이 적용된 데이터 세트에 PCA 를 적용해 4 차원 (4 개 속성 ) 의 붓꽃 데이터를 2 차원 (2 개의PCA 속성 ) PCA 데이터로 변환해 보겠습니다.
  • PCA 클래스는 생성 파라미터로 n_components를 입력받음
  • n_components 는 PCA 로 변환 할 차원의 수를 의미하므로 여기서는 2 로 설정함
  • 이후에 fit(입력 데이터 세트) 과 transtorm(입력데이터 세트)을 호출해 PCA 로 변환을 수행함
from sklearn. decomposition import PCA
pca = PCA(n_components=2)
# fit()과 transform()을 호출해 PCA 변환 데이터 반환
pca. fit(iris_scaled)
iris_pca = pca. transform(iris_scaled)
print(iris_pca. shape)

  • PCA 객체의 transform() 메서드를 호출해 원본 데이터 세트를 (150, 2) 의 데이터 세트로 iris pca 객체 변수로 반환
  • iris_pca 는 변환된 PCA 데이터 세트를 150x2 넘파이 행렬
  • 이를 DataFrame으로 변환한 뒤 데이터값을 확인
# PCA 변환된 데이터의 칼럼명을 각각 pca_component_1, pca_component_2 로 명명
pca_columns=[ 'pca_component_1', 'pca_component_2' ]
irisDF_pca = pd.DataFrame(iris_pca, columns=pca_columns)
irisDF_pca['target']=iris.target
irisDF_pca.head (3)

  • 2 개의 속성으로 PCA 변환된 데이터 세트를 2 차원상에서 시각화해 보겠습니다.
  • pca_component_1 속성을 X 축으로 , pca_component_2 속성을 Y 축으로 해서 붓꽃 품종이 어떻게 분포되는지 확인
# setosa 를 세모 , versicolor를 네모 , virginica를 동그라미로 표시
markers=['^', 's', 'o']
# pca_component_1올 x 축 , pc_component_2 를 y 축으로 scatter plot 수행
for i, marker in enumerate(markers):
    x_axis_data = irisDF_pca[irisDF_pca['target']==i]['pca_component_1']
    y_axis_data = irisDF_pca[irisDF_pca['target']==i]['pca_component_2']
    plt.scatter(x_axis_data, y_axis_data, marker=marker, label=iris.target_names[i])
plt.legend
plt.xlabel('pca_component_1')
plt.ylabel('pca_component_2' )
plt. show()

  • PCA 변환 후 pca_component_1 축을 기준으로 Setosa는 명확하게 분리됨
  • Versicolor와 Virginica는 일부 겹치지만 비교적 잘 구분됨
  • 이는 pca_component_1이 원본 데이터의 변동성을 잘 반영했기 때문
  • PCA 변환을 수행한 PCA 객체의 explained_variance_ratio_ 속성은 전체
    변동성에서 개별 PCA 컴포넌트별로 차지하는 변동성 비율을 제공하고 있음
print(pca.explained_variance_ratio_)

  • 첫 번째 PCA 성분 pca_component_1: 전체 변동성의 약 72.9% 차지
  • pca_component_2: 약 22.8% 차지
  • 두 성분으로 원본 데이터의 약 95% 변동성 설명 가능
  • 원본 데이터와 PCA 변환 데이터에 각각 RandomForestClassifier 적용 후
    → cross_val_score()를 이용해 3-폴드 교차 검증 정확도 비교
  • 먼저 원본 데이터 기준으로 모델 성능 평가 진행첫 번째 PCA 성분 pca_component_1: 전체 변동성의 약 72.9% 차지
  • 두 번째 성분 pca_component_2: 약 22.8% 차지
  • 두 성분으로 원본 데이터의 약 95% 변동성 설명 가능
  • 원본 데이터와 PCA 변환 데이터에 각각 RandomForestClassifier 적용 후
    → cross_val_score()를 이용해 3-폴드 교차 검증 정확도 비교
  • 먼저 원본 데이터 기준으로 모델 성능 평가 진행
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

rcf = RandomForestClassifier (random_state=156)
scores = cross_val_score(rcf, iris.data, iris.target, scoring='accuracy' ,cv=3)
print('원본 데이터 교차 검증 개별 정확도 :', scores)
print('원본 데이터 평균 정확도 :', np.mean(scores))

기존 4 차원 데이터를 2 차원으로 PCA 변환한 데이터 세트에 랜덤 포레스트를 적용

pca_X = irisDF_pca[[ 'pca_component_1', 'pca_component_2']]
scores_pca = cross_val_score(rcf, pca_X, iris.target, scoring='accuracy', cv=3 )
print('PCA 변환 데이터 교차 검증 개별 정확도 :', scores_pca)
print('PCA 변환 데이터 평균 정확도 :', np.mean(scores_pca) )

  • PCA 변환 후 속성이 4개 → 2개로 50% 감소
  • 예측 정확도는 약 8% 하락, 성능 감소가 다소 큰 편
  • 그러나 속성이 절반으로 줄었음에도 예측 성능이 상당 부분 유지됨
  • PCA가 데이터의 핵심 특성을 잘 유지하고 있음을 보여줌

더 많은 피처를 가진 데이터 세트를 적은 PCA 컴포넌트 기반으로 변환한 뒤 , 예측 영향
도가 어떻게 되는지 변환된 PCA 데이터 세트에 기반해서 비교해 보겠습니다.

  • 사용할 데이터 세트는 UCI Machine Learing Repository에 있는 신용카드 고객 데이터 세트 (Credit Card Clients Data Set)
# header 로 의미 없는 첫 행 제거 , 110c 로 기존 1d 제거
import pandas as pd
df = pd.read_excel('/content/sample_data/default of credit card clients.xls', header=1, sheet_name='Data').iloc[0:, 1:]
print(df.shape)
df.head(3)


-- 신용카드 데이터 세트: 30,000개 레코드, 24개 속성

  • default payment next month: 다음 달 연체 여부 → Target 변수
  • 연체: 1, 정상납부: 0
  • PAY_0 → PAY_1 으로 칼럼명 변경
  • default payment next month → default 로 칼럼명 축약
  • Target 변수 default → y_target에 저장
  • 피처 데이터는 default 칼럼을 제외한 DataFrame 으로 생성
df.rename(columns={'PAY_0':'PAY_1', 'default payment next month': 'default'}, inplace=True)
y_target = df['default']
X_features = df.drop('default', axis=1)
  • 해당 데이터 세트는 23 개의 속성 데이터 세트가 있으나 각 속성끼리 상관도가 매우 높음
  • DataFrame 의 corr()를 이용해 각 속성 간의 상관도를 구한 뒤 이를 시본 (Seaborn)의 heatmap으로 시각화
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
corr = X_features.corr()
plt.figure(figsize=(14, 14))
sns.heatmap(corr, annot=True, fmt=' .1g')

  • BILL_AMT1 ~ BILL_AMT6 6 개 속성끼리의 상관도가 대부분 0.9 이상으로 매우 높음
  • 이보다는 낮지만 PAY 1 ~ PAY 6 까지의 속성 역시 상관도가 높음
  • 이렇게 높은 상관도를 가진 속성들은 소수의 PCA 만으로도 자연스럽게 이 속성들의 변동성을 수용 가능
  • 이 BILLAMT1 ~ BILL_AMT6 까지 6 개 속성을 2 개의 컴포넌트로 PCA 변환한 뒤 개별 컴포넌트의 변동성을 explained_variance_ratio 속성으로 알아보겠습니다.
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
#BILL_ANT1 ~ BILL_AMT6 까지 6 개의 속성명 생성
cols_bill = ['BILL_AMT'+str(i) for i in range(1, 7)]
print('대상 속성명 :', cols_bill)
# 2 개의 PCA 속성을 가진 PCA 객체 생성하고 , explained_variance_ratio_ 계산을 위해 fit( ) 호출
scaler = StandardScaler()
df_cols_scaled = scaler. fit_transform(X_features[cols_bill])
pca = PCA(n_components=2)
pca.fit(df_cols_scaled)
print('PCA Component 별 변동성 :', pca.explained_variance_ratio_)

  • 단 2 개의 PCA 컴포넌트만으로도 6 개 속성의 변동성을 약 95% 이상 설명할 수 있음
  • 특히 첫 번째 PCA 죽으로 90% 의 변동성을 수용할 정도로 이 6 개 속성의 상관도가 매우 높음
  • 이번에는 원본 데이터 세트와 6 개의 컴포넌트로 PCA 변환한 데이터 세트의 분류 예측 결과를 상호 비
    교해 보겠습니다.
  • 먼저 원본 데이터 세트에 랜덤 포레스트를 이용해 타깃 값이 디폴트 값을 3 개의 교차 검증 세트로 분류 예측
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
rcf = RandomForestClassifier(n_estimators=300, random_state=156)
scores = cross_val_score(rcf, X_features, y_target, scoring='accuracy', cv=3 )
print('CV=3 인 경우의 개별 Fold 세트별 정확도 :',  scores)
print('평균 정확도:{0:.4f}'.format(np.mean(scores)))

  • 3 개의 교차 검증 세트에서 평균 예측 정확도는 약 81.70% 를 나타냄
  • 이번에는 6 개의 컴포넌트로 PCA 변환한 데이터 세트에 대해서 동일하게 분류 예측을 적용해 보겠습니다.
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = scaler.fit_transform(X_features)

pca = PCA(n_components=6)
df_pca = pca.fit_transform(df_scaled)
scores_pca = cross_val_score(rcf, df_pca, y_target, scoring='accuracy', cv=3)


print('CV=3 인 경우의 PCA 변환된 개별 Fold 세트별 정확도 :', scores_pca)
print('PCA 변환 데이터 세트 평균 정확도 : {:.4f}'.format(np.mean(scores_pca)))

  • 전체 23개 속성 중 6개의 PCA 컴포넌트만 사용
  • 원본 데이터 기반 분류 대비 1~2% 수준의 예측 성능 저하
  • 1/4 수준의 속성만으로도 유사한 성능 유지
  • PCA의 우수한 차원 축소 및 정보 압축 능력을 입증하는 결과

3. LDA(Linear Discriminant Analysis)

LDA 개요

  • LDA(Linear Discriminant Analysis)는 PCA와 유사한 차원 축소 기법이지만 지도학습 기반
  • PCA는 데이터의 분산을 최대화하는 축을 찾음
  • LDA는 클래스 간 분산을 최대화하고 클래스 내부 분산을 최소화하는 축을 찾음
  • LDA는 분류 성능 향상 목적으로 사용되며, 클래스 구분이 잘되는 저차원 공간으로 투영
  • 클래스 간 거리는 멀고, 클래스 내 데이터는 모여 있는 공간을 찾는 것이 핵심

  • LDA의 수행 과정은 PCA와 유사하지만 핵심 차이는 사용되는 행렬에 있음
  • PCA는 공분산 행렬을 사용해 고유벡터를 구함
  • LDA는 클래스 간 분산 행렬(Between-class scatter matrix)
    클래스 내부 분산 행렬(Within-class scatter matrix)를 사용
  • 이 두 행렬의 비율을 최적화하여 고유벡터를 도출
  • 행렬에 기반해 고유벡터를 구하고 입력 데
    이터를 투영한다는 점

LDA 를 구하는 과정

  1. 클래스 내부와 클래스 간 분산 행렬을 구합니다. 이 두 개의 행렬은 입력 데이터의 결정 값 클래스별로 개별 피처의 평균 벡터 (mean vector) 를 기반으로 구하기
  2. 클래스 내부 분산 행렬을 Sw 클래스 간 분산 행렬을 S, 라고 하면 다음 식으로 두 행렬을 고유벡터로 분해할 수 있음
  3. 고유값이 가장 큰 순으로 K 개 (LDA 변환 차수만큼 ) 추출함
  4. 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환함

붓꽃 데이터 세트에 LDA 적용하기

  • 붓꽃 데이터 세트를 사이킷런의 LDA 를 이용해 변환하여 품종별로 시각화
  • LDA 를 LinearDiscriminantAnalysis 클래스로 제공합니다.
  • 붓꽃 데이터 세트를 로드하고 표준 정규 분포로 스케일링합니다.
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris
iris = load_iris()
iris_scaled = StandardScaler().fit_transform(iris.data)
  • 2 개의 컴포넌트로 붓꽃 데이터를 LDA 변환하겠습니다. PCA 와 다르게 LDA 에서 한 가지 유의해야 할 점은 LDA 는 실제로는 PCA 와 다르게 비지도학습이 아닌 지도학습이라는 것입니다. 즉 , 클래스의 결정 값이 변환 시에 필요
  • 다음 lda 객체의 fit() 메서드를 호출할 때 결정값이 입력됐음에 유의해야 함
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(iris_scaled, iris.target)
iris_lda = lda.transform(iris_scaled)
print(iris_lda.shape)

  • 이제 LDA 변환된 입력 데이터 값을 2 차원 평면에 품종별로 표현해 보겠습니다.
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
lda_columns=['lda_component_1' , 'lda_component_2']
irisDF_lda = pd.DataFrame(iris_lda, columns=lda_columns)
irisDF_lda['target']=iris.target
#setosa는 세모 , versicolor는 네모 , virginica는 동그라미로 표현
markers=['^', 's', 'o']
#setosa 의 target 값은 0 , versicolor는 1, virginica는 2. 각 target 별로 다른 모양으로 산정도로 표시
for i, marker in enumerate (markers):
    x_axis_data = irisDF_lda[irisDF_lda['target']==i]['lda_component_1']
    y_axis_data = irisDF_lda[irisDF_lda['target']==i]['lda_component_2']
    plt.scatter(x_axis_data, y_axis_data, marker=marker, label=iris.target_names[i])
plt.legend(loc='upper right')
plt.xlabel('lda_component_1')
plt.ylabel('lda_component_2')
plt.show()

  • LDA 로 변환된 붓꽃 데이터 세트를 시각화해보면 PCA로 변환된 데이터와 좌우 대칭 형태로 많이 닮아
    있음을 알 수 있음.

SVD 개요

4.SVD(Singular Value Decomposition)

  • SVD 역시 PCA 와 유사한 행렬 분해 기법을 이용함
  • PCA 의 경우 정방행렬 (즉, 행과 열의 크기가 같은 행렬) 만을 고유벡터로 분해할 수 있지만, SVD 는 정방행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있음
  • 일반적으로 SVD 는 m X n 크기의 행렬 A 를 다음과 같이 분해하는 것을 의미함

A=UΣVTA=U \Sigma V^T

  • SVD 는 특이값 분해(Singular Value Decomposition)로 불림
  • 행렬 U 와 V 에 속한 벡터는 특이벡터(singular vector)
    ㅔ 모든 특이벡터는 서로 직교하는 성질을 가짐
  • Z 는 대각행렬이며, 대각에 위치한 값만 0 이 아니고 나머지 위치 값은 모두 0
  • Z 대각선에 위치한 0 이 아닌 값이 행렬 A 의 특이값(singular values)
    A 의 차원이 m×n 일 때:
    U 의 차원: m×m
    Z 의 차원: m×n
    VTV^T 의 차원: n×n
  • 일반적으로는 다음과 같이 SVD를 간소화하여 적용
  • Z의 비대각인 부분과 대각 원소 중 특이값이 0인 부분은 모두 제거
  • 제거된 Z에 대응되는 U와 V의 원소도 함께 제거하여 차원을 축소
  • 이렇게 컴팩트한 형태의 SVD를 적용하면, A의 차원이 m × n일 때:
    U의 차원: m × p
    Z의 차원: p × p
    VTV^T 의 차원: p × n
  • Truncated SVD는 Z의 대각 원소 중 상위 몇 개만 선택하여, 해당하는 U와 V의 원소도 함께 제거하여 차원을 더 줄이는 방식임
  • 일반적인 SVD는 보통 NumPy나 SciPy 라이브러리를 이용해 수행함
  • NumPy의 SVD 함수는 numpy.linalg.svd

넘파이의 svd 모듈 임포트

import numpy as np
from numpy.linalg import svd

4X4 랜덤 행렬 a 생성

np.random.seed(121)
a = np.random.randn(4, 4)
print(np.round (a, 3))

![](https://velog.velcdn.com/images/nunnalin/post/c33a5f44-6c13-4454-9575-fbf6a9bf36a5/image.png)

- SVD 분해는 numpy.linalg.svd 에 파라미터로 원본 행렬을 입력하면 U 행렬 , Sigma 행렬 , V 전치 행렬을 반환

- Σ 행렬은 일반적으로 대각에만 값이 있고 나머지는 0이므로, 효율을 위해 1차원으로만 표현합니다.

U, Sigma, Vt = svd(a)
print(U.shape, Sigma.shape, Vt.shape)
print('U matrix:\n', np.round(U, 3))
print('Sigma Value: \n', np.round (Sigma, 3))
print('V transpose matrix: \n', np.round(Vt, 3))

![](https://velog.velcdn.com/images/nunnalin/post/bff62a79-c933-455b-8ed5-5aad4664be7d/image.png)
- U 행렬이 4 X 4, Vt 행렬이 4 X 4 로 반환됐고 , Sigma 의 경우는 1 차원 행렬인 (4, ) 로 반환

- 분해된 이 U, Sigma, Vr 를 이용해 다시 원본 행렬로 정확히 복원되는지 확인해 보겠습니다. 
- 원본 행렬로의 복원은 이 U, Sigma, Vr 를 내적하면 됩니다. 한 가지 유의할 것은 Sigma 의 경우 0 이 아닌 값만 1 차원으로 추출했으므로 다시 0 을 포함한 대칭행렬로 변환한 뒤에 내적을 수행해야 한다는 점임

Sigma 를 다시 0을 포함한 대칭행렬로 변환

Sigmamat = np.diag(Sigma)
a
= np.dot(np.dot(U, Sigmamat), Vt)
print(np.round(a
, 3))

- a 행렬의 3번째 행을 '1행 + 2행'으로, 4번째 행을 1행과 같게 설정
- 로우 간 의존성 부여
- SVD 수행 시 Sigma 값 중 일부가 0 또는 0에 가까운 값으로 변함 --> 축소 가능

a[2] = a[0] + a[1]
a[3] = a[0]
print(np.round(a, 3))

![](https://velog.velcdn.com/images/nunnalin/post/a04a07f5-6f95-4426-b740-41a27e11b9cd/image.png)
- 이제 a 행렬은 이전과 다르게 로우 간 관계가 매우 높아졌습니다. 이 데이터를 SVD 로 다시 분해해 보
겠습니다.

다시 SVD 를 수행해 Sigma 값 확인

U, Sigma, Vt = svd (a)
print(U.shape, Sigma.shape, Vt.shape)
print('Sigma Value: \n', np.round (Sigma, 3))

![](https://velog.velcdn.com/images/nunnalin/post/68d6c77a-1dfa-4145-8509-e84dfc06cf46/image.png)



- 행렬의 Sigma 값 중 2개가 0으로 변함 → 행렬의 랭크(Rank)는 2
- 선형 독립인 행 벡터는 2개만 있음 
- 복원 시 Sigma의 0이 아닌 앞의 2개 요소만 사용
- U 행렬은 선행 2개 열만, Vt 행렬은 선행 2개 행만 사용
- 이렇게 부분적으로 복원해도 원본과 유사한 행렬 생성 가능

#U 행렬의 경우는 Sigma 와 내적을 수행하므로 Sigma 의 앞 2 행에 대응되는 앞 2 열만 추출
U= U[:, :2]
Sigma
= np.diag(Sigma[:2])
#V 전치 행렬의 경우는 앞 2 행만 추출
Vt = Vt[:2]
print(U
. shape, Sigma.shape, Vt.shape)

U, Sigma, Vt 의 내적을 수행하며 , 다시 원본 행렬 복원

a = np .dot(np.dot(U, Sigma), Vt)
print(np.round (a_, 3))

![](https://velog.velcdn.com/images/nunnalin/post/88a5851e-5059-4525-b28d-5a3c60652c9a/image.png)

- Truncated SVD는 특이값(Sigma)의 상위 일부만 추출해 행렬을 저차원으로 분해
- 원본 행렬을 정확히 복원할 수는 없지만, 높은 수준의 근사 가능
- 차원이 원래 차원에 가까울수록 복원 정확도 향상
- Truncated SVD는 scipy의 scipy.sparse.linalg.svds에서 지원
- 일반 SVD는 scipy.linalg.svd 사용 가능
- Truncated SVD는 주로 희소 행렬(sparse matrix)에 적용됨



임의의 원본 행렬 6 X 6 을 Normal SVD 로 분해해 분해된 행렬의 차원과 Sigma 행렬 내의 특이값을 확인한 뒤 다시 Truncated SVD 로 분해해 분해된 행렬의 차원 , Sigma 행렬 내의 특이값 , 그리고 Truncated SVD 로 분해된 행렬의 내적을 계산하여 다시 복원된 데이터와 원본 데이터를 비교해 보겠습니다.

import numpy as np
from scipy.sparse.linalg import svds
from scipy.linalg import svd

원본 행렬을 출력하고 SVD 를 적용할 경우 U, Sigma, Vt 의 차원 확인

np.random.seed(121)
matrix = np.random.random((6, 6))
print('원본 행렬:\n', matrix)
U, Sigma, Vt = svd(matrix, full_matrices=False)
print('\n분해 행렬 차원 :', U.shape, Sigma.shape, Vt.shape)
print('\nsigma값 행 렬 :', Sigma)

Truncated SvD 로 Sigma 행렬의 특이값을 4 개로 하여 Truncated SVD 수행.

num_components = 4
U_tr, Sigma_tr, Vt_tr = svds(matrix, k=num_components)
print('\n Truncate SVD분해 행렬 차원', U_tr.shape, Sigma_tr.shape, Vt_tr.shape)
print('\nTruncated SVD Sigma값 행렬:' , Sigma_tr)
matrix_tr = np.dot(np.dot(U_tr, np.diag(Sigma_tr)), Vt_tr) # output of TruncatedSVD
print('\nTruncated SVD 로 분해 후 복원 행렬 :\n', matrix_tr)

![](https://velog.velcdn.com/images/nunnalin/post/d980bbe0-3921-45ce-91b4-b58589b68235/image.png)

- 6 X 6 행렬을 SVD 분해하면 U, Sigma, Vt 가 각각 (6, 6) (6,) (6, 6) 차원이지만, - Truncated SVD 의 n_components 를 4 로 설정해 U, Sigma, Vt 를 (6, 4) (4,) (4, 6) 로 각각 분해했음 
- Truncated SVD 로 분해된 행렬로 다시 복원할 경우 완벽하게 복원되지 않고 근사적으로 복원됨

## 사이킷런 TruncatedSVD 클래스를 이용한 변환
- 사이킷런의 TruncatedSVD 클래스는 PCA와 유사하게 작동
- fit()과 transform()을 사용해 원본 데이터를 저차원 주요 컴포넌트로 변환
- Truncated SVD 연산을 통해 U × Sigma 형태로 선형 변환
- 사이파이 svds처럼 U, Sigma, Vt 행렬을 직접 반환하지 않음
- 주로 차원 축소 및 희소 행렬 처리에 사용됨

from sklearn. decomposition import TruncatedSVD, PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline
iris = load_iris()
iris_ftrs = iris.data

2 개의 주요 컴포넌트로 TruncatedSVD 변환

tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_ftrs)
iris_tsvd = tsvd.transform(iris_ftrs)

산점도 2 차원으로 TruncatedSVD 변환된 데이터 표현. 품종은 색깔로 구분

plt.scatter(x=iris_tsvd[:, 0], y= iris_tsvd[:, 1], c= iris.target)
plt.xlabel ('TruncatedSVD Component 1')
plt.ylabel('TruncatedSVD Component 2')

![](https://velog.velcdn.com/images/nunnalin/post/99832bfa-fb40-4525-8b2a-65897397f72b/image.png)

- 왼쪽 그림: TruncatedSVD로 변환된 붓꽃 데이터 세트
- 오른쪽 그림: PCA로 변환된 데이터 세트
- TruncatedSVD도 PCA처럼 차원 축소 후 품종별 클러스터링이 가능함
- 변환된 속성들이 높은 분류 고유성을 유지함

사이킷런의 TruncatedSVD와 PCA 클래스 구현을 조금 더 자세히 들여다보면 두 개 클래스 모두 SVD 를 이용해 행렬을 분해합니다. 

붓꽃 데이터를 스케일링으로 변환한 뒤에 TruncatedSVD와 PCA 클래스 변환을 해보면 두 개가 거의 동일함

from sklearn.preprocessing import StandardScaler

붓꽃 데이터를 StandardScaler로 변환

scaler = StandardScaler()
iris_scaled = scaler.fit_transform(iris_ftrs)

스케일링된 데이터를 기반으로 TruncatedSVD 변환 수행

tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_scaled)
iris_tsvd = tsvd. transform(iris_scaled)

스케일링된 데이터를 기반으로 PCA 변환 수행

pca = PCA(n_components=2)
pca.fit(iris_scaled)
iris_pca = pca. transform(iris_scaled)

TruncatedSVD 변환 데이터를 왼쪽에 , PCA 변환 데이터를 오른쪽에 표현

fig, (ax1, ax2) = plt.subplots(figsize=(9, 4), ncols=2)
ax1.scatter(x=iris_tsvd[:, 0], y= iris_tsvd[:, 1], c= iris.target)
ax2.scatter(x=iris_pca[:, 0], y= iris_pca[:, 1], c= iris. target)
ax1.set_title('Truncated SVD Transformed')
ax2.set_title('PCA Transformed')

![](https://velog.velcdn.com/images/nunnalin/post/05295f16-8b3d-4db8-a0ea-96ff029e5740/image.png)

- 두 개의 변환 행렬 값과 원복 속성별 컴포넌트 비율값을 실제로 서로 비교해 보면 거의 똑같음

print((irispca - iris_tsvd).mean())
print((pca.components
-tsvd.components_).mean())

- 모든 변환 결과 값이 0에 가까워 PCA와 SVD가 동일한 변환을 수행함
- 이는 PCA가 SVD 알고리즘을 기반으로 구현되었음을 의미
- 단, PCA는 밀집 행렬만 처리 가능 / SVD는 희소 행렬도 처리 가능
- SVD는 이미지 압축, 신호 처리, 패턴 인식 등 컴퓨터 비전 분야에서 활용
- 또한 SVD는 LSA (Latent Semantic Analysis) 기반 알고리즘으로 텍스트 토픽 모델링에 사용됨

# 5. NMF(Non-Negative Matrix Factorization)
## NMF 개요
- NMF 는 Truncated SVD 와 같이 낮은 랭크를 통한 행렬 근사(Low-Rank Approximation) 방식의 변형
- NMF 는 원본 행렬 내의 모든 원소 값이 모두 양수 (0 이상 ) 라는 게 보장되면 다음과 같이 좀 더 간단하게 두 개의 기반 양수 행렬로 분해될 수 있는 기법을 지칭
![](https://velog.velcdn.com/images/nunnalin/post/0c3e3660-6e0a-4b8b-ae16-ac811c56a2d1/image.png)

- 4 X 6 원본 행렬 V 는 4 X 2 행렬 W 와 2 X 6 행렬 H 로 근사해 분해될 수 있습니다. 
= 행렬 분해 (Matrix Factorization) 는 일반적으로 SVD 와 같은 행렬 분해 기법을 통칭하는 것입니다.
- 이처럼 행렬 분해를 하게 되면 W 행렬과 H 행렬은 일반적으로 길고 가는 행렬 W(즉 , 원본 행렬의 행 크기와 같고 열 크기보다 작은 행렬) 와 작고 넓은 행렬 H(원본 행렬의 행 크기보다 작고 열 크기와 같은 행렬) 로 분해됩니다. 
- 이렇게 분해된 행렬은 잠재 요소를 특성으로 가지게 됩니다. 
- 분해 행렬 W 는 원본 행에 대해서 이
잠재 요소의 값이 얼마나 되는지에 대응하며 , 분해 행렬 H 는 이 잠재 요소가 원본 열 ( 즉 , 원본 속성)로 어떻게 구성됐는지를 나타내는 행렬입니다.
![](https://velog.velcdn.com/images/nunnalin/post/9d54b685-09d5-4904-9ef3-43546d0fb379/image.png)
- NMF 는 SVD 와 유사하게 차원 축소를 통한 잠재 요소 도출로 이미지 변환 및 압축 , 텍스트의 토픽 도술 등의 영역에서 사용되고 있습니다. 
- 사이킷런에서 NMF 는 NMF 클래스를 이용해 지원됩니다. 
- 붓꽃 데이터를 NMF 를 이용해 2 개의 컴포넌트로 변환하고 시각화해 보겠습니다.

- NMF 도 SVD 와 유사하게 이미지 압축을 통한 패턴 인식 , 텍스트의 토픽 모델링 기법 , 문서 유사도 및 클러스터링에 잘 사용됨
- 또한 영화 추천과 같은 추천 (Recommendations) 영역에 활발하게 적용
됩니다. 
- 사용자의 상품 ( 예 : 영화 ) 평가 데이터 세트인 사용자-평가 순위(user-Rating) 데이터 세트를
행렬 분해 기법을 통해 분해하면서 사용자가 평가하지 않은 상품에 대한 잠재적인 요소를 추출해 이를
통해 평가 순위 (Rating) 를 예측하고 , 높은 순위로 예측된 상품을 추천해주는 방식입니다 ( 이를 잠재 요소 (Latent Factoring) 기반의 추천 방식이라고 합니다).

# 6. 정리
- PCA, LDA, SVD, NMF는 대표적인 차원 축소 알고리즘이다.
- 차원 축소의 목적은 단순히 피처 수를 줄이는 것뿐 아니라, 데이터를 설명하는 잠재 요인 추출이다.
- 고차원 이미지나 텍스트 데이터를 이해하고 시각화하는 데 매우 유용하다.
**각 기법의 특징:**
- PCA: 데이터의 분산이 가장 큰 축을 기준으로 직각 축을 찾아 데이터를 투영함 (공분산 행렬 → 고유벡터 사용).
- LDA: 데이터의 클래스 간 분리를 극대화하는 축을 찾는 방식 (PCA와 유사하지만 목적이 다름).
- SVD: 고차원 행렬을 두 개의 저차원 행렬로 분해하는 방식, 잠재 요인 추출에 효과적.
- NMF: 음수가 아닌 행렬 분해 방식으로, 특히 추천 시스템과 토픽 모델링에서 활용됨.








profile
안녕하세요. 이화여대 컴퓨터공학과 23학번 나린입니다.

0개의 댓글