01. 차원 축소(Dimension Reduction) 개요
차원 축소 알고리즘에는 PCA, LDA, SVD, NMF 등이 있다.
차원 축소란, 매우 많은 피처로 구성된 다차원 데이터 세트의 차원을 축소해 새로운 차원의 데이터 세트를 생성하는 것이다.
차원 축소를 통해 피처 수를 줄이면 더 직관적으로 데이터를 해석할 수 있고, 시각적 표현과 학습에 필요한 데이터 처리 과정을 줄일 수 있다.
02. PCA(Principal Component Analysis
PCA 개요
PCA란 가장 대표적인 차원 축소 기법으로, 여러 변수 간에 존재하는 상관 관계를 이용해 이를 대표하는 주성분을 추출해 차원을 축소하는 기법이다.
PCA는 정보 유실을 최소화하기 위해 가장 높은 분산(Variance)을 가지는 데이터의 축을 찾아 이 축으로 차원을 축소한다.
즉, 데이터 변동성이 가장 큰 방향으로 축을 생성하고, 새롭게 생성된 축으로 데이터를 투영하는 방식이다.
선형대수 관점에서, PCA는 입력 데이터의 공분산 행렬(Covariance Matrix)을 고유값 분해하고, 이렇게 구한 고유벡터에 입력 데이터를 선형 변환하는 것이다.
- 선형 변환 : 특정 벡터에 행렬 A를 곱해 새로운 벡터로 변환하는 것을 의미한다.
- 분산 : 한 개의 특정한 변수의 데이터 변동
- 공분산 : 두 변수 간의 변동( Ex. Cov(X, Y) > 0 )
- 고유벡터 : 행렬 A를 곱하더라도 방향이 변하지 않고 그 크기만 변하는 벡터
- 공분산 행렬 : 공분산을 행렬로 표현한 것으로, 정방행렬(열과 행이 같은 행렬)이자 대칭행렬(대각 원소를 중심으로 대칭인 행렬)이다.
따라서, PCA란 입력 데이터의 공분산 행렬이 고유벡터와 고유값으로 분해될 수 있으며, 이렇게 분해된 고유벡터를 이용해 입력 데이터를 선형 변환하는 방식이다.
PCA 스텝
- 입력 데이터 세트의 공분산 행렬을 생성한다.
- 공분산 행렬의 고유벡터와 고유값을 계산한다.
- 고유값이 가장 큰 순으로 K개(PCA 변환 차수)만큼 고유벡터를 추출한다.
- 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환한다.
사이킷런의 붓꽃데이터를 이용해 PCA를 수행한다.
#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[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()
Versicolor와 virginica의 경우 두 피처로는 분류가 어려운 것을 알 수 있다.
PCA로 4개의 속성을 2개로 압축한다.
PCA는 속성의 스테일에 영향을 받으므로 각 속성값을 동일한 스케일로 변환해야 한다.
# PCA는 속성값을 동일한 스케일로 변환하는 것이 필요
from sklearn.preprocessing import StandardScaler
# Target 값을 제외한 모든 속성 값을 StandardScaler를 이용하여 표준 정규 분포를 가지는 값들로 변환
iris_scaled = StandardScaler().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)
(150, 2)
PCA클래스를 이용해 변환하였다.
n_components는 PCA로 변환할 차원의 수를 의미하므로 2로 설정한다.
이제 변환된 데이터 세트를 다시 2차원 상에서 시각화한다.(코드 생략)
변환 후 versicolor와 virginica가 구분이 비교적 잘 됐음을 확인할 수 있다.
PCA의 첫 번쨰 새로운 축은 pca_component_1이 원본 데이터의 변동성을 잘 반영했기 때문이다.
PCA객체의 explained_variance_ratio_속성을 이용해 전체 변동성에서 컴포넌트별로 차지하는 변동성 비율을 알 수 있다.
# pca 컴포넌트별 변동성 비율
print(pca.explained_variance_ratio_)
[0.72962445 0.22850762]
다음으로 좀 더 많은 피처를 가진 데이터 세트 Credit Card Clients Data Set를 이용해 PCA를 수행한다.
먼저 DataFrame의 corr()를 이용해 각 속성 간의 상관도를 구한 뒤 이를 시각화한다.
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')
# 높은 상관도를 가진 BILL1~6, PAY1~6 PCA 변환
높은 상관도를 가진 속성들은 소수의 PCA만으로도 자연스롭게 속성들의 변동성을 수용할 수 있다.
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_)
['BILL_AMT1', 'BILL_AMT2', 'BILL_AMT3', 'BILL_AMT4', 'BILL_AMT5', 'BILL_AMT6']
대상 속성명: ['BILL_AMT1', 'BILL_AMT2', 'BILL_AMT3', 'BILL_AMT4', 'BILL_AMT5', 'BILL_AMT6', 'PAY_1', 'PAY_2', 'PAY_3', 'PAY_4', 'PAY_5', 'PAY_6', 'PAY_AMT1', 'PAY_AMT2', 'PAY_AMT3', 'PAY_AMT4', 'PAY_AMT5', 'PAY_AMT6']
PCA Component별 변동성: [0.36180187 0.20618472]
2개의 PCA conponents로 6개의 속성 변동성을 약 95% 이상 설명할 수 있음을 알 수 있다.
PCA로 변환된 데이터 셋과 전체 피처를 포함한 데이터 셋으로 각각 정확도를 측정한 결과, 1~2%의 예측 성능 저하가 발생했다.
이는 PCA의 뛰어난 압축 능력을 잘 보여준다.
PCA는 차원 축소를 통해 데이터를 쉽게 인지하는 데 활용할 수 있지만, 특히 컴퓨터 비전(Computer Vision) 분야에서 뛰어나게 활용된다.
03. LDA(Linear Discriminant Analysis)
LDA 개요
LDA는 선형 판별 분석법이라 불리며, 지도학습의 분류(Classfication)에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원을 축소한다.
PCA는 입력 데이터의 변동성의 가장 큰 축을 찾았지만, LDA는 입력 데이터의 결정 값 클래스를 최대한으로 분리할 수 있는 축을 찾는다.
이를 위해 클래스 간 분산은 최대한 크게 가지고, 클래스 내부의 분산은 최대한 작게 가져간다.
LDA 스텝
- 클래스 내부와 클래스 간 분산 행렬을 구한다. 이 두 행렬은 입력 데이터의 결정 값 클래스별로 개별 피처의 평균 벡터를 기반으로 구한다.
- 클래스 내부 분산, 클래스 간 분산 행렬을 고유 벡터로 분해한다.
- 고유값이 가장 큰 순으로 K개(LDA 변환 차수만큼) 추출한다.
- 고유값이 가장 큰 순으로 추출된 고유벡터를 이용해 새롭게 입력 데이터를 변환한다.
붓꽃 데이터 세트에 LDA 적용하기
PCA와 마찬가지로 붓꽃데이터 세트를 표준 정규 분포로 스케일링 후 LDA 변환한다.
이때, LDA는 지도학습이므로 클래스의 결정 값이 변환시에 필요하다.
lda = LinearDiscriminantAnalysis(n_components=2)
# lda는 지도학습 이므로 학습 시 target 값 입력!
lda.fit(iris_scaled, iris.target)
iris_lda = lda.transform(iris_scaled)
print(iris_lda.shape)
(150, 2)
변환된 데이터 값을 시각화한다.
PCA로 변환된 데이터와 좌우 대칭 형태로 많이 닮았다.
04. SVD(Singular Value Decomposition)
SVD 개요
SVD 역시 PCA와 유사항 행렬 분해 기법을 이용하지만, SVD는 정방행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있다.
SVD는 특이값 분해로 불리며, 행렬 U와 V에 속한 벡터는 특이벡터(singular vector)이며, 모든 특이벡터는 서로 직교하는 성질을 가진다.
Truncated SVD는 시그마의 대각원소 중에 상위 몇 개만 추출해서 여기에 대응하는 U와 V의 원소도 함께 제거해 더욱 차원을 줄인 형태로 분해하는 것이다.
SVD 수행을 위해 넘파이의 SVD 모듈인 numpy.linalg.svd를 로딩하고, 개별 로우끼리의 의존성이 없는 랜덤 행렬을 생성한다.
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.014 0.63 1.71 -1.327]
[ 0.402 -0.191 1.404 -1.969]]
생성된 행렬에 SVD를 적용하면 U 행렬, Sigma 행렬, V 전치 행렬을 반환한다.
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))
(4, 4) (4,) (4, 4)
U matrix:
[[-0.079 -0.318 0.867 0.376]
[ 0.383 0.787 0.12 0.469]
[ 0.656 0.022 0.357 -0.664]
[ 0.645 -0.529 -0.328 0.444]]
Sigma Value:
[3.423 2.023 0.463 0.079]
V transpose matrix:
[[ 0.041 0.224 0.786 -0.574]
[-0.2 0.562 0.37 0.712]
[-0.778 0.395 -0.333 -0.357]
[-0.593 -0.692 0.366 0.189]]
분해된 행렬은 다시 원본 행렬로 복원된다.
이번에는 데이터 세트가 로우 간 의존성이 있을 경우 SVD를 수행한다.
(의존성을 부여한 행렬)
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.542 0.899 1.041 -0.073]
[-0.212 -0.285 -0.574 -0.44 ]]
(Sigma 행렬 값 확인)
(4, 4) (4,) (4, 4)
Sigma Value:
[2.663 0.807 0. 0. ]
이전과 차원은 같지만 2개의 값이 0으로 변했다.
즉, 선형 독립인 로우 벡터의 개수가 2이다.
이번에는 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('\nTruncated 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)
원본 행렬:
[[0.11133083 0.21076757 0.23296249 0.15194456 0.83017814 0.40791941]
[0.5557906 0.74552394 0.24849976 0.9686594 0.95268418 0.48984885]
[0.01829731 0.85760612 0.40493829 0.62247394 0.29537149 0.92958852]
[0.4056155 0.56730065 0.24575605 0.22573721 0.03827786 0.58098021]
[0.82925331 0.77326256 0.94693849 0.73632338 0.67328275 0.74517176]
[0.51161442 0.46920965 0.6439515 0.82081228 0.14548493 0.01806415]]
분해 행렬 차원: (6, 6) (6,) (6, 6)
Sigma값 행렬: [3.2535007 0.88116505 0.83865238 0.55463089 0.35834824 0.0349925 ]
Truncated SVD 분해 행렬 차원: (6, 4) (4,) (4, 6)
Truncated SVD Sigma값 행렬: [0.55463089 0.83865238 0.88116505 3.2535007 ]
Truncated SVD로 분해 후 복원 행렬:
[[0.19222941 0.21792946 0.15951023 0.14084013 0.81641405 0.42533093]
[0.44874275 0.72204422 0.34594106 0.99148577 0.96866325 0.4754868 ]
[0.12656662 0.88860729 0.30625735 0.59517439 0.28036734 0.93961948]
[0.23989012 0.51026588 0.39697353 0.27308905 0.05971563 0.57156395]
[0.83806144 0.78847467 0.93868685 0.72673231 0.6740867 0.73812389]
[0.59726589 0.47953891 0.56613544 0.80746028 0.13135039 0.03479656]]
Truncated SVD는 일부 데이터만 추출해 분해하므로 근사적으로 복원된다.
사이킷런 TruncatedSVD 클래스를 이용한 변환
사이킷런의 TruncatedSVD 클래스는 사이파이의 svds와 같이 U,Sigma, Vt 행렬을 반환하지 않는다.
대신 PCA 클래스와 유사하게 fit(), transform()을 호출해 원본 데이터를 몇 개의 주요 컴포넌트로 차원을 축소해 변환한다.
from sklearn.decomposition import TruncatedSVD, PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
iris = load_iris()
iris_ftrs = iris.data
# 2개의 주요 component로 TruncatedSVD 변환
tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_ftrs)
iris_tsvd = tsvd.transform(iris_ftrs)
# Scatter plot 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')
TruncatedSVD 변환 역시 PCA와 유사하게 뛰어난 고유성을 가진다.
데이터 세트가 스케일링으로 데이터 중심이 동일해지면 사이킷런의 SVD와 PCA는 동일한 변환을 수행한다.
이는 PCA가 SVD 알고리즘으로 구현됐음을 의미한다.
하지만 PCA는 밀집 행렬에 대한 변환만 가능하며 SVD는 희소 행렬에 대한 변환도 가능하다.
SVD는 테스트의 토픽 모델링 기법인 LSA의 기반 알고리즘이다.
05. NMF(Non-Negative Matrix Factorization)
NMF 개요
NMF는 Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사 방식의 변형이다.
NMF는 원본 행렬 내의 모든 원소 값이 모두 양수라는 게 보장되면 좀 더 간단하게 두 개의 기반 양수 행렬로 분해될 수 있는 기법을 지칭한다.
NMF는 SBD와 유사하게 이미지 변환 및 압축, 텍스트의 토픽 도출 등의 영역에서 사용된다.
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는 추천 영역에서 활발하게 적용된다.
평가하지 않은 상품에 대한 잠재적인 요소를 추출해 이를 통해 평가 순위를 예측하고, 높은 순위로 예측된 상품을 추천해주는 방식이다.(잠재 요소 기반 추천 방식)
# 회고
차원 축소!
아직 머신러닝 중에서는 생소한 개념이다.
여러 행렬이 나오는데 정말 어렵다. 선형대수학 공부좀 열심히 할걸...
'Euron > 정리' 카테고리의 다른 글
[Week11] 07. 군집화(1) (0) | 2023.11.15 |
---|---|
[Week 8] 🥑💰 PyCaret 및 EDA를 사용한 아보카도 가격 회귀 💹 (0) | 2023.11.06 |
[Week8] 05. 회귀 - 캐글 실습 (1) | 2023.10.19 |
[Week5] 05. 회귀(2) (1) | 2023.10.08 |
[Week5] 05. 회귀(1) (1) | 2023.10.07 |