파이썬 머신러닝 완벽 가이드 - 7. Dimension Reduction(2) (SVD, NMF)

Mios·2022년 10월 12일
1
post-thumbnail

3. SVD(Singular Value Decomposition, 특이 값 분해)

: PCA와 유사. 정방 행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용 가능

  • Full SVD

    • A=UVTA = U \sum V^T
    • AA : 행렬, UU, VV: 특이벡터(Singular vector)로 된 행렬, \sum : 대각행렬
    • \sum : 대각행렬 : 행렬의 대각에 위치한 값만 0이 아니고 나머지 위치의 값은 모두 0인 행렬. 여기서 0이 아닌 값이 행렬 A의 특이값
    • AA : MxN 행렬일 때 —분해→ UU: MxM 행렬, \sum : MxN 행렬, VTV^T: NxN 행렬
  • Compact SVD (일반적)

    • UU : MxP 행렬, \sum : PxP 행렬, VTV^T: PxN 행렬
    • \sum 의 비대각인 부분과 대각원소 중에 특이값이 0인 부분도 모두 제거되고,
      제거된 \sum에 대응되는 UUVV원소도 함께 제거해 차원을 줄인 형태로 SVD를 적용한다.
  • Truncated SVD

    • 특이값 중 상위 일부 데이터만 추출해 분해하는 방식.
    • 인위적으로 더 작은 차원의 행렬들로 분해하기 때문에 원본행렬을 정확하게는 복원할 수 없다.

  • SVD 사용: 보통 넘파이나 사이파이 라이브러리를 이용

    from numpy.linalg import svd
    # or
    from scipy.linalg import svd
    
    # 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))
    
    > [[-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 = svd(a) # a = 원본 행렬
    print(U.shape, Sigma.shape, Vt.shape)
    print('U matrix:\n',np.round(U, 3))
    print('Sigma Value:\n',np.round(Sigma, 3)) # 대각행렬 => 대각에 위치한 값 == 1,  외에 나머지 값 0
    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]]
  • 다시 행렬 A로 복원해보기 ⇒ UVT=AU \> * \> \sum \> * \> V^T \> = A

    • 단, \sum 의 경우 0이 아닌 값만 1차원으로 추출했으므로, 다시 0을 포함한 대칭행렬로 변환 뒤 내적(*) 수행

      # Sima를 다시 0 을 포함한 대칭행렬로 변환
      Sigma_mat = np.diag(Sigma)
      a_ = np.dot(np.dot(U, Sigma_mat), Vt)
      print(np.round(a_, 3))
    • 데이터 세트가 로 우간 의존성이 있을 경우, 어떻게 \sum 값이 변하고, 이에 따른 차원 축소 진행되는지 알아보기

    • AA 행렬 ⇒ 3번째 로우 = 1번째 로우 + 2번째 로우 & 4번째 로우 = 1번째 로우

      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 ]]
    • AA 행렬을 다시 SVD 분해

      # 다시 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.   ]
      • 이전과 차원은 같지만, \sum 값 중 2개가 0으로 변함 ⇒ 선형 독립의 로우 벡터의 수가 2개라는 의미 (행렬의 랭크가 2)
    • 이제 다시 행렬 A를 복원해볼 것
      U,,VTU, \sum, V^T 전체 데이터 사용하지 않고, \sum 의 0에 대응되는 U,,VTU, \sum, V^T 를 제외하고 복원해보겠음
      ⇒ 즉, UU 행렬 중 선행 두 개의 열만 추출 하고, VTV^T의 경우는 선행 두 개의 행만 추출 복원해보는 것

      ```python
      # 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 : \sum 행렬에 있는 대각원소, 즉 특이값 중 상위 일부 데이터만 추출해 분해하는 방식

    • 이렇게 분해하면, 인위적으로 더 작은 차원의 U,,VTU, \sum, V^T 로 분해하기에, 원본 행렬을 정확히 원복할 수는 없음
    • 그러나, 데이터 정보가 압축되어 분해됨에도 불구하고 상당한 수준으로 워본 행렬을 근사할 수 있음
  • Truncated SVD 사용 : 사이파이에서만 지원됨

    from scipy.sparse.linalg import svds
    • 검증 수행 순서
      1. 임의의 원본 행렬 6x6을 Normal SVD로 분해해 ⇒ 행렬의 차원, \sum 행렬 내 특이값 확인
      2. 다시 Truncated SVD로 분해해 ⇒ 행렬의 차원, \sum 행렬 내 특이값 확인
      3. Truncated SVD로 분해된 행렬의 내적을 계산해서 ⇒ 원상 복구하여 원본데이터와 비교

      import numpy as np
      from scipy.sparse.linalg import svds # Truncated SVD
      from scipy.linalg import svd # Nomarl 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]]
    • 6 ⇒ 4 차원 & Truncated SVD 적용 후 원상 복구 하면, 완벽하진 않지만 근사하게 복원됨

  • 사이킷런 TruncatedSVD 클래스를 이용한 변환

    • 사이파이의 SVDs와 같이 U,,VTU, \sum, V^T 행렬을 반환하지는 않음.

    • 사이킷런의 PCA 클래스와 유사하게, fit(), transform()으로 원본 데이터를 몇 개의 주요 컴포넌트로 차원축소해 변환

    • 즉, 원본 데이터를 Truncated SVD 방식으로 분해된 UU * \sum 행렬에 선형변환하여 생성

      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개의 주요 component로 TruncatedSVD 변환
      tsvd = TruncatedSVD(n_components=2)
      tsvd.fit(iris_ftrs)
      iris_tsvd = tsvd.transform(iris_ftrs)
      
      # 2개의 주요 component로 TruncatedSVD 변환 (비교를 위해)
      pca = PCA(n_components=2)
      pca.fit(iris_ftrs)
      iris_pca = pca.transform(iris_ftrs)
      
      # TruncatedSVD 변환 데이터를 왼쪽에, PCA변환 데이터를 오른쪽에 표현 
      fig, (ax1, ax2) = plt.subplots(figsize=(18,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')
      ax1.set_xlabel('TruncatedSVD Component 1')
      ax1.set_ylabel('TruncatedSVD Component 2')
      ax2.set_xlabel('PCA Component 1')
      ax2.set_ylabel('PCA Component 2')

    • TruncatedSVD 역시 PCA와 유사하게 변환 후 품종별로 어느정도 클러스터링이 가능할정도로 고유성 가지고 있음

    • 사실 두 클래스를 모두 뜨덩보면, 모두 SVD를 이용해 행렬을 분해함 ⇒ 원본 데이터를 스케일링으로 변환 후에 적용해보면 거의 동일함

      from sklearn.preprocessing import StandardScaler
      
      # iris 데이터를 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.3419865583888347e-15
      > 6.245004513516506e-17
      • 모두 0에 가까운 값이므로, 2개의 변환이 서로 동일함을 알 수 있음
      • 즉, 데이터 세트가 스케일링으로 데이터 중심이 동일해지면, SVD와 PCA는 동일한 변환을 수행
      • 이는 PCA가 SVD 알고리즘으로 구현됐음을 의미
      • 그러나, PCA는 밀집 행렬(Dense Matrix)에 대한 변환만 가능하며, SVD는 희소 행렬(Sparse Matrix)에 대한 변환도 가능
      • 또한 SVD는 텍스트의 토픽 모델링 기법인 LSA(Latent Semantic Analysis)의 기반 알고리즘임.

4. NMF(Non-Negative Matrix Factorization)

: 원본 행렬 내의 모든 원소값이 모두 양수(0 이상)라는 게 보장되면, 두 개의 기반 양수 행렬로 분해될 수 있는 기법
: Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사 방식의 변형

  • W×HVW \times H \approx V
    • 일반적으로 길고 가는 행렬 WW(원본 행렬과 행크기 같고 열크기 보다 작은 행렬) X 작고 넓은 행렬 HH (원본 행렬의 행 크기보다 작고 열 크기와 같은 행렬)로 분해된다.
    • WW : 원본 행에 대해서 이 잠재요소의 값이 얼마나 되는지에 대응
    • HH : 이 잠재요소가 원본 열(원본 속성)로 어떻게 구성됐는지를 나타냄
  • 사용
    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')

  • NMF와 SVD와 유사하게 이미지 압축을통한 패턴 인식, 텍스트의 토픽 모델링 기법, 문서 유사도 및 클러스터링, 추천 시스템에 활발히 적용 됨

5. 정리

  • PCA
    • 입력 데이터의 변동성이 가장 큰 축을 구하고, 다시 이 축에 직각인 축을 반복적으로 축소하려는 차원의 개수만큼 구한 뒤 입력 데이터를 이 축들에 투영해 차원을 축소하는 방식
    • 입력 데이터의 공분산 행렬을 기반으로, 고유 벡터를 생성하고, 이 고유 벡터에 입력 데이터를 선형변환하는 방식
  • LDA
    • 입력 데이터의 결정값 클래스를 최대한으로 분리할 수 있는 축을 찾아 차원을 축소하는 방식
  • SVD, NMF
    • 고차원 행렬을 두 개의 저차원 행렬로 분리하는 행렬기법
    • 원본 행렬에서 잠재된 요소를 추출하기 때문에 토픽 모델이나 추천시스템에서 사용된다.
profile
mios의 데이터 놀이터 | Instagram@data.decision (하단 홈 아이콘 버튼)

0개의 댓글