머신러닝

[ECC 머신러닝 스터디] 6장 차원 축소

machinecreator24 2024. 11. 22. 12:57

01 차원 축소 개요

차원이 증가할수록 데이터 포인트 간의 거리가 기하급수적으로 멀어지면서 sparse한 구조를 가지게 됨. 수백 개 이상의 피처로 구성된 데이터 세트의 경우, 상대적으로 적은 차원에서 학습된 모델보다 예측 신뢰도가 떨어지며, 피처가 많을 경우 개별 피처간의 상관관계가 높을 가능성이 큼.

 

매우 많은 다차원의 피처를 차원 축소해 줄일 경우, 직관적으로 데이터 해석 가능. 차원 축소를 통해, 시각적으로 데이터를 압축해서 표현하고, 학습 크기가 줄어들어 학습에 필요한 처리 능력도 동시에 줄임.

 

차원축소: 

1. 피처 선택 (feature selection) : 특정 피처에 종속성이 강한 불필요한 피처는 아에 제거하고, 데이터의 특징을 잘 나타내는 주요 피처만 선택

2. 피처 추출 (feature extraction): 기존 피처를 저차원의 중요 피처로 압축해서 추출하는것. 새롭게 추출된 중요 특성은 기존의 피처를 압축한 것이므로 기존과 완전히 다른값. 

  •     피처를 함축적으로 더 잘 설명할 수 있는 다른 공간으로 매핑해 추출
  •     기존 피처가 인지하기 어려웠던 잠재적인 요소(latent factor)을 추출하는 것을 의미

 

차원 축소 대표 알고리즘: PCA, SVD, NMF

  • 차원 축소 알고리즘은 많은 픽셀로 이뤄진 이미지 데이터에서 잠재된 특성을 피처로 도출해 함축적인 형태의 이미지 변환과 압축을 수행 할 수 있음.
  • 변환된 이미지는 원본 이미지보다 적은 차원이기에, 과적합(오버피팅)영향력이 작아져서 오히려 원본 데이터로 예측하는 것 보다 예측 성능을 더 끌어올릴 수 있다.
  • 차원 축소 알고리즘은 텍스트 문서의 숨겨진 의미를 추출하는 것에도 사용됨. 문서 내 단어들의 구성에서 숨겨져있는 시맨틱 (Semantic)의미나 토픽을 잠재 요소로 간주하여 찾아냄 ex) SVD, NMF

 

02  PCA(Principal Component Analysis)

 

PCA(Principal Component Analysis)는 가장 대표적인 차원 축소 기법. PCA는 여러 변수 간에 존재하는 상관관계를 이용해 이를 대표하는 주성분(Principal Component)를 추출해 차원 축소하는 기법. 기존 데이터의 정보 유실이 최소화됨. PCA는 가장 높은 분산을 가지는 데이터의 축을 찾아 이 축으로 차원을 축소하며 이것이 주성분이 됨.

 

PCA는 제일 먼저 가장 큰 데이터 변동성(Variance)을 기반으로 첫 번째 벡터 축을 생성하고, 두번째 축은 이 벡터 축에 직각이 되는 벡터(직교 벡터) 축으로 합니다. 세번째 축은 다시 두번째 축과 직각이 되는 벡터를 설정하는 방식으로 축을 생성합니다. 이렇게 생성된 벡터 축에 원본 데이터를 투영하면 벡터 축의 개수만큼의 차원으로 원본 데이터가 차원 축소됩니다.

 

 

PCA, 즉 주성분 분석은 이처럼 원본 데이터의 피처 개수에 비해 매우 작은 주성분으로 원본 데이터의 총 변동성을 대부분 설명할 수 있는 분석법. PCA를 선형대수 관점에서 해석해 보면, 입력 데이터의 공분산 행렬(Covariance Matrix)을 고유값 분해하고, 고유벡터에 입력 데이터를 선형 변환하는 것. 고유벡터가 PCA의 주성분 벡터로서 입력 데이터의 분산이 큰 방향, 고윳값(eigenvalue)은 고유벡터의 크기를 나타내며, 동시에 입력 데이터의 분산을 나타냅니다. 

 

 

선형 변환은 특정 벡터에 행렬 A를 곱해 새로운 벡터로 변환하는 것을 의미. 보통 분산은 한 개의 특정한 변수의 데이터 변동을 의미하나, 공분산은 두 변수 간의 변동을 의미한다. 고유벡터는 행렬 A를 곱하더라도 방향이 변하지 않고, 그 크기만 변하는 벡터를 지칭. 공분산 행렬은 정방행렬(Diagonal Matrix)이며 대칭행렬(Symmetric Matrix)입니다. 정방행렬은 열과 행이 같은 행렬을 지칭하는데, 정방행렬 중에서 대각 원소를 중심으로 원소 값이 대칭되는 행렬, 대칭행렬이라고 부릅니다. 공분산 행렬은 개별 분산값을 대각 원소로 하는 대칭행렬입니다. 대칭행렬은 고유값 분해와 관련해 매우 좋은 특성이 있다. 

 

입력 데이터의 공분산 행렬이 고유벡터와 고유값으로 분해될 수 있으며, 이렇게 분해된 고유벡터를 이용해 입력 데이터를 선형 변환하는 방식이 PCA

1. 입력 데이터 세트의 공분산 행렬을 생성.

2. 공분산 행렬의 고유벡터와 고유값을 계산합니다.

3. 고유값이 가장 큰 순으로 K개(PCA 변환 차수만큼)만큼 고유벡터를 추출합니다.

4. 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환합니다.

 

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)

 

#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[irisDF['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()

 

from sklearn.prepocessing import StandardScaler

#Target 값을 제외한 모든 속성 값을 StandardScaler를 이용해 표준 정규 분포를 가지는 값들로 변환
iris_scaled = StandardSclaer().fit_transform(irisDF.iloc[:, :-1])

 

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 환된 데이터의 컬럼명을 각각 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)
 
#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()
print(pca.explained_variance_ratio_)
 
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))
 
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))
# header로 의미 없는 첫 행 제거, iloc로 기존 id 제거
import pandas as pd

df = pd.read_excel('pca_credit_card.xls', header=1, sheet_name='Data').iloc[0:,1:]
print(df.shape)
df.head(3)
 
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)
 
X_features.info()
 
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')
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

#BILL_AMT1 ~ BILL_AMT6 까지 6개의 속성명 생성
cols_bill = ['BILL_AMT'+str(i) for i in range(1,7)]
cols_pay = ['PAY_' + str(i) for i in range(1, 7)]
cols_amt = ['PAY_AMT' + str(i) for i in range(1, 7)]
print(cols_bill)
cols_bill.extend(cols_pay)
cols_bill.extend(cols_amt)
print('대상 속성명:',cols_bill)

# 2개의 PCA 속성을 가진 PCA 객체 생성하고, explained_variance_ratio_ 계산 위해 fit( ) 호출
scaler = StandardScaler()
df_cols_scaled = scaler.fit_transform(X_features[cols_bill])
X_features.loc[:, cols_bill] = df_cols_scaled
pca = PCA(n_components=2)
pca.fit(df_cols_scaled)
print('PCA Component별 변동성:', pca.explained_variance_ratio_)
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)))
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# 원본 데이터셋에 먼저 StandardScaler적용
scaler = StandardScaler()
df_scaled = scaler.fit_transform(X_features)

# 6개의 Component를 가진 PCA 변환을 수행하고 cross_val_score( )로 분류 예측 수행. 
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 변환 데이터 셋 평균 정확도:{0:.4f}'.format(np.mean(scores_pca)))

 

 

03  LDA(Linear Discriminant Analysis)

LDA(Linear Discriminant Analysis)는 선형 판별 분석법으로 불리며, PCA와 유사. 입력 데이터 세트를 저차원 공간에 투영해 차원을 축소하는 기법이지만, 지도학습의 분류에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원을 축소.

 

LDA는 입력 데이터의 결정 값 클래스를 최대한으로 분리할 수 있는 축을 찾음. 특정 공간 내에서 클래스 분리를 최대화하는 축을 찾기 위해 클래스 간 분산(between-class-scatter)과 클래스 내부 분산(within-class-scatter)의 비율을 최대화하는 방식으로 차원을 축소.

 

PCA와의 큰 차이점: 공분산 행렬이 아니라 위에 설명한 클래스 간 분산과 클래스 내부 분산 행렬을 생성한 뒤, 이 행렬에 기반해 고유 벡터를 구하고 입력 데이터를 투영.

 

1. 클래스 내부와 클래스 간 분산 행렬을 구함. 두개의 행렬을 입력 데이터의 결정 값 클래스 별로 개별 피처의 평균 벡터를 기반으로 구한다.

2. 클래스 내부 분산행렬을 Sw, 클래스 간 분산 행렬을 Sb라고 하면, 두 행렬을 고유벡터로 분해한다.

3. 고유값이 가장 큰 순으로 K개(LDA 변환 차수만큼)추출합니다.

4. 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환합니다.

 

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)
 
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(iris_scaled, iris.target)
iris_lda = lda.transform(iris_scaled)
print(iris_lda.shape)
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 별로 다른 shape으로 scatter plot
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()

 

04 SVD (Singular Value Decomposition)

SVD도 PCA와 유사한 행렬 분해 기법 이용, 정방행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용가능.

특이값 분해라고 불리며, U와 V에 속한 벡터는 특이벡터(singular vector)이며, 모든 특이 벡터는 직교하는 성질을 가짐.

 

 

# numpy의 svd 모듈 import
import numpy as np
from numpy.linalg import svd

# 4X4 Random 행렬 a 생성 
np.random.seed(121)
a = np.random.randn(4,4)
print(np.round(a, 3))
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))

 

# Sima를 다시 0 을 포함한 대칭행렬로 변환
Sigma_mat = np.diag(Sigma)
a_ = np.dot(np.dot(U, Sigma_mat), Vt)
print(np.round(a_, 3))
a[2] = a[0] + a[1]
a[3] = a[0]
print(np.round(a,3))
# 다시 SVD를 수행하여 Sigma 값 확인 
U, Sigma, Vt = svd(a)
print(U.shape, Sigma.shape, Vt.shape)
print('Sigma Value:\n',np.round(Sigma,3))
# 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))

 

 

05 NMF (Non-Negative Matrix Factorization)

NMF는 Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사 방식의 변형, 원본 행렬 내의 모든 값이 모두 양수라는게 보장된다면 두개의 기반 양수 행렬로 분해될 수 있는 기법을 지칭.

 

 

4*6 원본행렬을 4*2 행렬과 2*6 행렬로 근사해 분해될 수 있음. 행렬 분해를 하게되면 w행렬과 h행렬을 일반적으로 길고 가는 행렬 w와 작고 넓은 행렬 h로 분해되어 잠재 요소를 특성으로 가지게 됨. 분해 행렬 w는 원본 행에 대해서 이 잠재 요소의 값이 얼마나 되는지에 대응하며, 분해 행렬 h는 이 잠재 요소가 원본 열로 어덯게 구성돼어있는지를 나타내는 행렬.

from sklearn.decomposition import NMF
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline

iris = load_iris()
iris_ftrs = iris.data
nmf = NMF(n_components=2)
nmf.fit(iris_ftrs)
iris_nmf = nmf.transform(iris_ftrs)
plt.scatter(x=iris_nmf[:,0], y= iris_nmf[:,1], c= iris.target)
plt.xlabel('NMF Component 1')
plt.ylabel('NMF Component 2')

plt.show()

 

NMF도 SVD와 유사하게 이미지 압축을 통한 패턴 인식, 텍스트의 토픽 모델링 기법, 문서 유사도 및 클러스터링에 잘 사용됨.