[2022_하계_모각코] 6회차(08/15)
6회 차 목표
차원축소
3. LDA(Linear Discriminant Analysis)
LDA 개요
LDA는 선형 판별 분석법으로 불리며, PCA와 매우 유사하다. LDA는 PCA와 유사하게 입력 데이터 세트를 저차원 공간에 투영해 차원을 축소하는 기법이지만 중요한 차이는 LDA는 지도학습의 분류에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원을 축소한다. PCA는 입력 데이터의 변동성의 가장 큰 축을 찾았지만, LDA는 입력 데이터의 결정 값 클래스를 최대한으로 분리할 수 있는 축을 찾는다.
LDA는 특정 공간상에서 클래스 분리를 최대화하는 축을 찾기 위해 클래스간 분산과 클래스 내부 분산의 비율을 최대화하는 방식으로 차원을 축소한다. 즉, 클래스 간 분산은 최대한 크게 가져가고, 클래스 내부의 분산은 최대한 작게 가져가는 방식이다. 다음 그림은 좋은 클래스 분리를 위해 클래스간 분산이 크고 클래스 내 분산이 작은 것을 표현한 것이다.
붓꽃 데이터에 LDA 적용하기
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)
(150, 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)
plt.legend(loc='upper right')
plt.xlabel('lda_component_1')
plt.ylabel('lda_component_2')
plt.show()
4. SVD(Singular Value Decomposition)
SVD 개요
SVD 역시 PCA와 유사한 행렬 분해 기법을 이용한다. PCA의 경우 정방행렬만을 고유벡터로 분해할 수 있지만 SVD는 정방행렬 뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있다. SVD값은 특이값 분해로 불리며 행렬 U와 V에 속한 벡터는 특이벡터이며, 모든 특이벡터는 서로 직교하는 성질을 가진다.
A = U ∑ Vt
∑는 대각행렬이며 행렬의 대각에 위치한 값만 0이 아니고 나머지 위치의 값은 모두 0이다. ∑이 위치한 0이 아닌 값이 바로 행렬 A의 특이값이다. SVD는 A의 m*n 일때 U의 차원이 m*m, ∑ 차원이 m*n, VT의 차원이 n*n으로 분해한다.
m x n 크기의 행렬 A는 m x m 크기의 행렬 U와 m x n 크기의 ∑ 그리고 n x n 크기의 Vt로 나뉜다. (위 식에서 행렬 V의 전치 행렬(Transpose)은 편의상 Vt라 표기) 행렬 U와 V에 속한 벡터를 특이 벡터(Singular Vector)라 한다. 모든 특이 벡터는 서로 직교하는 성질을 가진다. ∑는 직사각 대각 행렬이며, 행렬의 대각에 위치한 값만 0이 아니고 나머지 위치의 값은 모두 0이다. m < n인 경우 첫 번째와 같이 분해되며, m > n인 경우 두 번째와 같이 분해된다. ∑의 0이 아닌 대각 원소값을 특이값(Singular Value)라고 한다.
numpy.linalg.svd
import numpy as np
from numpy.linalg import svd
#4*4 랜덤 행렬 a생성
np.random.seed(121)
a=np.random.randn(4,4)
print(np.round(a,3))
[[-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]]
이렇게 생성된 a행렬에 SVD를 적용해 U,sigma,Vt를 도출하자. SVD 분해는 numpy.linallg 행렬, 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]]
U 행렬이 4*4 Vt 행렬이 4*4로 반환됐고 sigma의 경우 1차원 행렬이 (4,)로 반환됐다.
#sigma를 다시 0을 포함한 대칭 행렬로 변환
Sigma_mat=np.diag(Sigma)
a_=np.dot(np.dot(U,Sigma_mat),Vt)
print(np.round(a_,3))
[[-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]]
U,sigma,Vt를 이용해 a_는 원본 행렬 a와 동일하게 복원됨을 알 수 있다. 이번에는 데이터 세트가 로우 간 의존성이 있을 경우 어떻게 sigma 값이 변하고, 이에 따른 차원 축소가 진행될 수 있는지 알아보겠다.
a[2]=a[0]+a[1]
a[3]=a[0]
print(np.round(a,3))
[[-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 ]]
#다시 SVD를 수행해 Sigma 값 확인
U,Sigma,Vt=svd(a)
print(U.shape,Sigma.shape,Vt.shape)
print('Sigma Value:\n',np.round(Sigma,3))
(4, 4) (4,) (4, 4)
Sigma Value:
[2.663 0.807 0. 0. ]
이전과 차원은 같지만 Sigma 값 중 2개가 0으로 변했다. 즉 선형 독립인 로우 벡터의 개수가 2개라는 의미이다. 이렇게 분해된 U,Sigma,Vt 를 이용해 다시 원본 행렬로 복원해 보겠다.
#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))
(4, 2) (2, 2) (2, 4)
[[-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 ]]
이번에는 Truncated SVD를 이용해 행렬을 분해해 보겠다. Truncated SVD ∑행렬에 있는 대각 원소 ,즉 특이값 중 상위 일부 데이터만 추출해 분해는 하는 방식이다. 이렇게 분해하면 인위적으로 더 작은 차원의 U,∑,Vt로 분해하기 떄문에 원본 행렬을 정확하게 다시 원복할 수는 없다. 하지만 데이터 정보가 압축되어 분해됨에도 불구하고 상당한 수준으로 원본 행렬을 근사할 수 있다. 당연한 이야기이지만 원래 차원의 차수에 가깝게 잘라낼수록 원본 행렬에 더 가깝게 복원할 수 있다.
from scipy.sparse.linalg import svds
임의의 원본 행렬 6*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('\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]]
사이킷런 TruncatedSVD 클래스를 이용한 변환
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')
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')
print((iris_pca - iris_tsvd).mean())
print((pca.components_ - tsvd.components_).mean())
2.3597212147249043e-15
6.938893903907228e-18
모두 0에 가까운 값이므로 2개의 변환이 서로 동일함을 알 수 있다. 즉 데이터 세트가 스케일링으로 데이터 중심이 동일해지면 사이킷런의 SVD와 PCA는 동일한 변환을 수행한다. 이는 PCA가 SVD 알고리즘으로 구현되었음을 의미한다. 하지만 PCA는 밀집 행렬에 대한 변환만 가능하며 SVD는 희소 행렬에 대한 변환도 가능하다.
5. NMF(Non-Negative Matrix Factorization)
NMF 개요
NMF는 Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사 방식의 변형이다. NMF는 원본 행렬 내의 모든 원소 값이 모두 양수라는게 보장되면 다음과 같이 좀 더 간단하게 두 개의 기반 양수 행렬로 분해될 수 있는 기법을 지칭한다.
6회 차 회고록(2022.08.15)
이번 6회 차를 마지막으로 하계 모각코도 종료되었다. 3시간 동안 혼자서 공부하려면 집중해서 하기 어렵지만, 블로그에 기록도 하고 다른 친구들과 함께 했기 때문에 열심히 할 수 있었다. 개강을 얼마 남겨 두지 않은 상황에서, 방학이 이렇게 지나가는 것이 아쉽지만 모각코를 포함해서 나름 알차게 보낸 것 같아서 뿌듯하다.