특이값 분해

특이값 분해(SVD, Singular Value Decomposition)는 선형대수(linear algebra)의 다목적 도구(Swiss Army knife)입니다. 모든 행렬은 SVD를 가집니다. 모든 데이터 과학자(data scientist)에게 SVD는 필수 도구입니다.

유형: Build 언어: Python, Julia 선수 지식: Phase 1, Lessons 01 (Linear Algebra Intuition), 02 (Vectors & Matrices Operations), 03 (Matrix Transformations) 예상 시간: 약 120분

학습 목표

  • 거듭제곱 반복(power iteration)으로 특이값 분해(SVD, Singular Value Decomposition)를 구현하고 U, Sigma, V^T의 기하학적 의미를 설명합니다.
  • 절단 SVD(truncated SVD)를 이미지 압축(image compression)에 적용하고 압축률(compression ratio)과 복원 오차(reconstruction error)를 측정합니다.
  • SVD로 무어-펜로즈 의사역행렬(Moore-Penrose pseudoinverse)을 계산해 과잉결정 최소제곱 시스템(overdetermined least-squares system)을 풉니다.
  • SVD를 주성분 분석(PCA, Principal Component Analysis), 추천 시스템(recommendation system)의 잠재 요인(latent factor), 자연어 처리(NLP)의 잠재 의미 분석(Latent Semantic Analysis)과 연결합니다.

문제

1000x2000 크기의 행렬이 있다고 가정해 봅시다. 사용자-영화 평점(user-movie rating)일 수도 있고, 문서-용어 빈도표(document-term frequency table)일 수도 있으며, 이미지의 픽셀값(pixel value)일 수도 있습니다. 이 행렬을 압축하거나, 잡음을 제거하거나, 숨은 구조를 찾거나, 최소제곱 시스템(least-squares system)을 풀어야 합니다. 고유분해(eigendecomposition)는 정방행렬(square matrix)에서만 동작합니다. 그마저도 행렬이 선형독립 고유벡터(linearly independent eigenvector)를 충분히 갖추고 있어야 합니다.

반면 SVD는 어떤 행렬에도 동작합니다. 어떤 형태(shape), 어떤 계수(rank)에도 제약이 없습니다. SVD는 행렬을 세 개의 인수(factor)로 분해해 그 행렬이 공간에 어떤 기하학적 변환을 적용하는지 드러냅니다. 선형대수에서 가장 일반적이고 유용한 행렬분해(factorization)입니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.SVD는 고유분해(eigendecomposition)에 비해 어떤 장점이 있나요?

2.저계수 근사(Low-rank approximation)는 무엇을 의미하나요?

0/2 답변 완료

개념

SVD가 기하학적으로 하는 일

모든 행렬은 형태(shape)와 무관하게 세 가지 연산(operation)을 순서대로 수행합니다. 회전(rotate), 스케일링(scale), 다시 회전입니다. SVD는 이 분해(decomposition)를 명시적으로 보여 줍니다.

A = U * Sigma * V^T

      m x n     m x m    m x n    n x n
     (any)    (rotate)  (scale)  (rotate)

임의의 행렬 A에 대해 SVD는 아래 세 인수(factor)를 만듭니다.

  • V^T는 입력 공간(input space), 즉 n차원 공간의 벡터를 회전시킵니다.
  • Sigma는 각 축(axis)을 따라 늘리거나(stretch) 줄입니다(compress).
  • U는 결과를 출력 공간(output space), 즉 m차원 공간으로 회전시킵니다.
graph LR
    A["Input space (n-dim)\nData cloud\n(arbitrary orientation)"] -->|"V^T\n(rotate)"| B["Scaled space\nAligned with axes\nthen scaled by Sigma"]
    B -->|"U\n(rotate)"| C["Output space (m-dim)\nRotated to output\norientation"]

SVD에게 행렬을 건네면 SVD는 이렇게 말합니다. "이 행렬은 입력 구(input sphere)를 먼저 V^T로 회전시키고, Sigma로 늘려 타원체(ellipsoid)로 만든 뒤, U로 다시 회전시킵니다." 특이값(singular value)은 바로 그 타원체 축(axis)의 길이입니다.

전체 분해(decomposition)

형태가 m x n인 행렬 A에 대해:

A = U * Sigma * V^T

where:
  U     is m x m, orthogonal (U^T U = I)
  Sigma is m x n, diagonal (singular values on the diagonal)
  V     is n x n, orthogonal (V^T V = I)

The singular values sigma_1 >= sigma_2 >= ... >= sigma_r > 0
where r = rank(A)

U의 열은 좌특이벡터(left singular vector)이고, V의 열은 우특이벡터(right singular vector)입니다. Sigma의 대각 원소(diagonal entry)는 특이값(singular value)입니다. 특이값은 항상 음수가 아니며(non-negative) 관례적으로 내림차순(decreasing order)으로 정렬합니다.

좌특이벡터, 특이값, 우특이벡터

SVD의 각 성분(component)은 서로 다른 기하학적 의미를 가집니다.

우특이벡터(V의 열): 입력 공간 R^n의 정규직교기저(orthonormal basis)입니다. 행렬이 출력 공간의 직교 방향(orthogonal direction)으로 보내는 입력 방향(input direction)이며, 정의역(domain)의 자연 좌표계(natural coordinate system)로 볼 수 있습니다.

특이값(Sigma의 대각 원소): 스케일링 인자(scaling factor)입니다. i번째 특이값은 행렬이 i번째 우특이벡터 방향의 벡터를 얼마나 늘리는지를 알려 줍니다. 특이값이 0이면 그 방향은 완전히 눌려서(crushed) 사라집니다.

좌특이벡터(U의 열): 출력 공간 R^m의 정규직교기저입니다. i번째 좌특이벡터는 i번째 우특이벡터가 스케일링된 뒤 도착하는 출력 방향(output direction)입니다.

관계는 아래와 같습니다.

A * v_i = sigma_i * u_i

행렬 A는 i번째 우특이벡터 v_i를
sigma_i 배만큼 늘려서 i번째 좌특이벡터 u_i로 보냅니다.

이 식은 임의의 행렬이 무엇을 하는지 좌표 단위(coordinate-by-coordinate)로 보여 줍니다.

외적 형태(Outer product form)

SVD는 계수 1짜리(rank-1) 행렬들의 합(sum)으로 쓸 수 있습니다.

A = sigma_1 * u_1 * v_1^T + sigma_2 * u_2 * v_2^T + ... + sigma_r * u_r * v_r^T

각 항 sigma_i * u_i * v_i^T는 rank-1 행렬, 즉 외적(outer product)입니다.
전체 행렬은 r개 rank-1 행렬의 합이며, r은 행렬의 계수(rank)입니다.

이 형태(form)는 저계수 근사(low-rank approximation)의 기반입니다. 각 항은 구조의 한 층(layer)을 더해 줍니다. 첫 번째 항은 가장 중요한 패턴(pattern)을, 두 번째 항은 그 다음으로 중요한 패턴을 포착합니다. 이 합을 절단(truncate)하면 주어진 계수에서 가능한 최선의 근사(best approximation)를 얻습니다.

Rank-1 approx:    A_1 = sigma_1 * u_1 * v_1^T
                  (가장 지배적인 패턴 한 개를 포착)

Rank-2 approx:    A_2 = sigma_1 * u_1 * v_1^T + sigma_2 * u_2 * v_2^T
                  (가장 중요한 패턴 두 개를 포착)

Rank-k approx:    A_k = 상위 k개 항의 합
                  (Eckart-Young 정리에 의해 최적)

고유분해(Eigendecomposition)와의 관계

SVD와 고유분해는 깊게 연결되어 있습니다. A의 특이값과 특이벡터는 A^T A, A A^T의 고유값(eigenvalue)과 고유벡터(eigenvector)에서 직접 나옵니다.

A^T A = V * Sigma^T * U^T * U * Sigma * V^T
      = V * Sigma^T * Sigma * V^T
      = V * D * V^T

where D = Sigma^T * Sigma is a diagonal matrix with sigma_i^2 on the diagonal.

So:
- 우특이벡터 (V)는 A^T A의 고유벡터입니다.
- 특이값의 제곱 (sigma_i^2)은 A^T A의 고유값입니다.

Similarly:
A A^T = U * Sigma * V^T * V * Sigma^T * U^T
      = U * Sigma * Sigma^T * U^T

So:
- 좌특이벡터 (U)는 A A^T의 고유벡터입니다.
- A A^T의 고유값 역시 sigma_i^2입니다.

이 연결은 세 가지를 알려 줍니다.

  1. 특이값은 항상 실수이며 음수가 아닙니다. 양의 준정부호(positive semi-definite) 행렬의 고유값의 제곱근(square root)이기 때문입니다.
  2. A^T A의 고유분해로도 SVD를 계산할 수 있지만, 조건수(condition number)가 제곱되면서 수치적 정밀도(numerical precision)를 잃습니다. 전용 SVD 알고리즘(algorithm)은 이를 피합니다.
  3. A가 정방행렬이며 대칭(symmetric) 양의 준정부호이면 SVD와 고유분해는 같은 결과가 됩니다.

절단 SVD(Truncated SVD): 저계수 근사(low-rank approximation)

에카르트-영-미르스키 정리(Eckart-Young-Mirsky theorem)는 프로베니우스 노름(Frobenius norm)과 스펙트럼 노름(spectral norm) 양쪽에서 A의 최선의 rank-k 근사가 상위 k개 특이값과 그에 대응하는 특이벡터만 유지한 것임을 보장합니다.

A_k = U_k * Sigma_k * V_k^T

where:
  U_k     is m x k  (U의 처음 k개 열)
  Sigma_k is k x k  (Sigma의 좌상단 k x k 블록)
  V_k     is n x k  (V의 처음 k개 열)

근사 오차 = sigma_{k+1}  (spectral norm 기준)
         = sqrt(sigma_{k+1}^2 + ... + sigma_r^2)  (Frobenius norm 기준)

이것은 단순히 "좋은" 근사가 아니라, 주어진 계수 k에서 증명 가능한 최선의 근사입니다. 다른 어떤 rank-k 행렬도 A에 더 가까울 수 없습니다.

성분상대 크기rank-3 근사에 포함?
sigma_1가장 큼포함
sigma_2포함
sigma_3중간-큼포함
sigma_4중간미포함 (오차)
sigma_5중간-작음미포함 (오차)
sigma_6작음미포함 (오차)
sigma_7매우 작음미포함 (오차)
sigma_8극히 작음미포함 (오차)

특이값이 빠르게 감소(decay)하면 작은 k로도 행렬의 대부분을 포착할 수 있습니다. 천천히 감소한다면 저계수 구조(low-rank structure)가 약하다는 뜻입니다.

SVD를 사용한 이미지 압축(image compression)

흑백 이미지(grayscale image)는 픽셀 명도(pixel intensity)로 이루어진 행렬입니다. 800x600 이미지는 480,000개의 값을 가집니다. SVD를 쓰면 훨씬 적은 값으로 근사할 수 있습니다.

원본 이미지: 800 x 600 = 480,000 values

rank k SVD:
  U_k:      800 x k values
  Sigma_k:  k values
  V_k:      600 x k values
  Total:    k * (800 + 600 + 1) = k * 1401 values

  k=10:   14,010 values   (원본의 2.9%)
  k=50:   70,050 values  (원본의 14.6%)
  k=100: 140,100 values  (원본의 29.2%)

k가 작을수록 압축률은 좋아지지만 시각적 품질(visual quality)은 떨어집니다. 자연 이미지(natural image)는 특이값이 빠르게 감소하는 특성이 있습니다. 앞쪽 특이값은 형태(shape)와 그레이디언트(gradient) 같은 큰 구조(broad structure)를 포착하고, 뒤쪽 값은 세부 묘사(detail)와 잡음(noise)을 포착합니다. 계수 50 정도만 사용해도 원본과 거의 비슷해 보이면서 저장 공간(storage)을 크게 줄일 수 있습니다.

추천 시스템(Recommendation system)에서의 SVD

넷플릭스 프라이즈(Netflix Prize)로 유명해진 아이디어입니다. 사용자-영화 평점 행렬이 있는데, 대부분의 항목(entry)이 비어 있는 상황입니다.

             Movie1  Movie2  Movie3  Movie4  Movie5
  User1      [  5      ?       3       ?       1  ]
  User2      [  ?      4       ?       2       ?  ]
  User3      [  3      ?       5       ?       ?  ]
  User4      [  ?      ?       ?       4       3  ]

  ? = 알 수 없는 평점

핵심 아이디어는 이 평점 행렬이 저계수(low rank)라는 점입니다. 사용자 취향은 완전히 독립적이지 않습니다. 액션 대 드라마, 신작 대 구작, 사색적 대 본능적 같은 몇 개의 잠재 요인(latent factor)이 선호도(preference)의 대부분을 설명합니다.

빠진 값을 채워 넣은 평점 행렬에 SVD를 적용하면 아래처럼 분해됩니다.

  • U: 잠재 요인 공간(latent factor space)에서의 사용자 프로필
  • Sigma: 각 잠재 요인의 중요도
  • V^T: 잠재 요인 공간에서의 영화 프로필

어떤 사용자가 어떤 영화에 줄 예측 평점은 사용자 프로필과 영화 프로필의 내적(dot product)에 특이값으로 가중치를 둔 값입니다. 저계수 근사가 비어 있는 항목을 채워 줍니다.

실무에서는 빠진 데이터를 직접 다루는 사이먼 펑크(Simon Funk)의 점진적(incremental) SVD나 교대 최소제곱(ALS, Alternating Least Squares) 같은 변형(variant)을 사용합니다. 핵심 아이디어는 동일합니다. SVD에 기반한 잠재 요인 분해(latent factor decomposition)입니다.

NLP의 SVD: 잠재 의미 분석(Latent Semantic Analysis)

잠재 의미 분석(LSA, Latent Semantic Analysis), 또는 잠재 의미 색인(LSI, Latent Semantic Indexing)은 단어-문서 행렬(term-document matrix)에 SVD를 적용합니다.

             Doc1   Doc2   Doc3   Doc4
  "cat"      [  3      0      1      0  ]
  "dog"      [  2      0      0      1  ]
  "fish"     [  0      4      1      0  ]
  "pet"      [  1      1      1      1  ]
  "ocean"    [  0      3      0      0  ]

rank k=2로 SVD를 수행한 뒤:

  각 문서는 2차원 "개념 공간(concept space)" 위의 한 점이 됩니다.
  각 단어 역시 같은 2차원 공간 위의 한 점이 됩니다.
  유사한 주제(topic)의 문서끼리 가깝게 모입니다.
  의미가 비슷한 단어끼리 가깝게 모입니다.

catdog은 육상 반려동물(land pet) 개념 근처에 놓이고, fishocean은 바다(water) 개념 근처에 놓입니다. LSA는 가공되지 않은 텍스트(raw text)에서 의미적 유사성(semantic similarity)을 포착한 초기 성공 사례 중 하나입니다. 현대의 단어 임베딩(word embedding)인 워드투벡(Word2Vec)과 글로브(GloVe)도 이 아이디어의 후손으로 볼 수 있습니다.

잡음 제거(Noise reduction)를 위한 SVD

잡음이 포함된 데이터에서는 신호(signal)가 상위 특이값에 집중되고, 잡음(noise)은 여러 특이값에 퍼져 있습니다. 절단(truncation)은 잡음 바닥(noise floor)을 제거하는 역할을 합니다.

깨끗한 신호의 특이값:

성분크기종류
sigma_1매우 큼신호
sigma_2신호
sigma_3중간신호
sigma_4거의 0무시 가능
sigma_5거의 0무시 가능

잡음이 섞인 신호의 특이값:

성분크기종류
sigma_1매우 큼신호
sigma_2신호
sigma_3중간신호
sigma_4작음잡음
sigma_5작음잡음
sigma_6작음잡음
sigma_7작음잡음
graph TD
    A["All singular values"] --> B{"Clear gap?"}
    B -->|"Above gap"| C["Signal: keep these (top k)"]
    B -->|"Below gap"| D["Noise: discard these"]
    C --> E["Reconstruct with A_k to get denoised version"]

이는 신호 처리(signal processing), 과학적 측정, 데이터 정제(data cleaning)에서 사용됩니다. 가산 잡음(additive noise)으로 손상된 행렬이 있을 때, 절단 SVD는 신호와 잡음을 분리하는 원리적인(principled) 방법입니다.

SVD를 통한 의사역행렬(pseudoinverse)

무어-펜로즈 의사역행렬 A+는 역행렬(inverse)을 정방이 아닌 행렬과 특이행렬(singular matrix)에까지 일반화합니다. SVD를 쓰면 계산이 단순합니다.

If A = U * Sigma * V^T, then:

A+ = V * Sigma+ * U^T

where Sigma+ is formed by:
  1. Sigma를 전치(transpose)합니다.
  2. 0이 아닌 대각 원소 sigma_i를 1/sigma_i로 바꿉니다.
  3. 0인 원소는 그대로 0으로 둡니다.

의사역행렬은 최소제곱 문제(least-squares problem)를 풉니다. Ax = b에 정확한 해(exact solution)가 없을 때, x = A+ b가 최소제곱 해이며, 이는 ||Ax - b||를 최소화합니다.

과잉결정 시스템(overdetermined system, 방정식이 미지수보다 많은 경우):

  [1  1]         [3]
  [2  1] x   =   [5]       정확한 해가 존재하지 않습니다.
  [3  1]         [6]

  x_ls = A+ b = V * Sigma+ * U^T * b

이는 정규방정식(normal equation) (A^T A)^(-1) A^T b와 같은 결과를 주지만 수치적 안정성(numerical stability)이 더 좋습니다.

수치적 안정성의 장점

A^T A의 고유분해를 계산하면 특이값이 제곱됩니다. 이는 조건수 역시 제곱시켜 수치 오차(numerical error)를 키웁니다.

예시:
  A의 특이값: [1000, 1, 0.001]
  A의 조건수: 1000 / 0.001 = 10^6

  A^T A의 고유값: [10^6, 1, 10^{-6}]
  A^T A의 조건수: 10^6 / 10^{-6} = 10^{12}

  SVD를 직접 계산: 조건수 10^6에서 동작
  A^T A 경유 계산:  조건수 10^{12}에서 동작
                   (정밀도 6자리 추가 손실)

현대의 SVD 알고리즘, 예를 들어 골럽-카한 이중대각화(Golub-Kahan bidiagonalization)는 A^T A를 만들지 않고 A에서 직접 동작합니다. 그래서 np.linalg.eig(A.T @ A)보다 np.linalg.svd(A)를 사용해야 합니다.

PCA와의 연결

주성분 분석(PCA)은 평균을 뺀(centered) 데이터에 대한 SVD입니다. 이는 비유가 아니라 말 그대로 동일한 계산입니다.

데이터 행렬 X (n_samples x n_features)를 평균 제거(centered)해 두면:

공분산 행렬: C = (1/(n-1)) * X^T X

PCA는 C의 고유벡터를 찾습니다. 그런데:

  X = U * Sigma * V^T    (X의 SVD)

  X^T X = V * Sigma^2 * V^T

  C = (1/(n-1)) * V * Sigma^2 * V^T

따라서 주성분(principal component)은 정확히 우특이벡터 V와 같습니다.
각 성분의 설명 분산(explained variance)은 sigma_i^2 / (n-1)입니다.

사이킷런(sklearn)의 PCA도 고유분해가 아니라 SVD로 구현되어 있습니다. 더 빠르고 수치적 안정성이 좋기 때문입니다. Lesson 10에서 배운 차원 축소(dimensionality reduction)는 내부적으로 SVD입니다.

직접 만들기

Step 1: 거듭제곱 반복으로 SVD 구현

핵심 아이디어는 가장 큰 특이값과 그에 해당하는 벡터를 찾기 위해 A^T A 또는 A A^T에 거듭제곱 반복(power iteration)을 적용하는 것입니다. 그런 다음 행렬을 디플레이션(deflate, 이미 찾은 성분을 빼는 작업)한 뒤 다음 특이값을 반복해서 찾습니다.

import numpy as np

def power_iteration(M, num_iters=100):
    n = M.shape[1]
    v = np.random.randn(n)
    v = v / np.linalg.norm(v)

    for _ in range(num_iters):
        Mv = M @ v
        v = Mv / np.linalg.norm(Mv)

    eigenvalue = v @ M @ v
    return eigenvalue, v

def svd_from_scratch(A, k=None):
    m, n = A.shape
    if k is None:
        k = min(m, n)

    sigmas = []
    us = []
    vs = []

    A_residual = A.copy().astype(float)

    for _ in range(k):
        AtA = A_residual.T @ A_residual
        eigenvalue, v = power_iteration(AtA, num_iters=200)

        if eigenvalue < 1e-10:
            break

        sigma = np.sqrt(eigenvalue)
        u = A_residual @ v / sigma

        sigmas.append(sigma)
        us.append(u)
        vs.append(v)

        A_residual = A_residual - sigma * np.outer(u, v)

    U = np.column_stack(us) if us else np.empty((m, 0))
    S = np.array(sigmas)
    V = np.column_stack(vs) if vs else np.empty((n, 0))

    return U, S, V

Step 2: 넘파이(NumPy)와 비교하기

np.random.seed(42)
A = np.random.randn(5, 4)

U_ours, S_ours, V_ours = svd_from_scratch(A)
U_np, S_np, Vt_np = np.linalg.svd(A, full_matrices=False)

print("Our singular values:", np.round(S_ours, 4))
print("NumPy singular values:", np.round(S_np, 4))

A_reconstructed = U_ours @ np.diag(S_ours) @ V_ours.T
print(f"Reconstruction error: {np.linalg.norm(A - A_reconstructed):.8f}")

Step 3: 이미지 압축 데모

def compress_image_svd(image_matrix, k):
    U, S, Vt = np.linalg.svd(image_matrix, full_matrices=False)
    compressed = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
    return compressed

image = np.random.seed(42)
rows, cols = 200, 300
image = np.random.randn(rows, cols)

for k in [1, 5, 10, 20, 50]:
    compressed = compress_image_svd(image, k)
    error = np.linalg.norm(image - compressed) / np.linalg.norm(image)
    original_size = rows * cols
    compressed_size = k * (rows + cols + 1)
    ratio = compressed_size / original_size
    print(f"k={k:>3d}  error={error:.4f}  storage={ratio:.1%}")

Step 4: 잡음 제거

np.random.seed(42)
clean = np.outer(np.sin(np.linspace(0, 4*np.pi, 100)),
                 np.cos(np.linspace(0, 2*np.pi, 80)))
noise = 0.3 * np.random.randn(100, 80)
noisy = clean + noise

U, S, Vt = np.linalg.svd(noisy, full_matrices=False)
denoised = U[:, :5] @ np.diag(S[:5]) @ Vt[:5, :]

print(f"Noisy error:    {np.linalg.norm(noisy - clean):.4f}")
print(f"Denoised error: {np.linalg.norm(denoised - clean):.4f}")
print(f"Improvement:    {(1 - np.linalg.norm(denoised - clean) / np.linalg.norm(noisy - clean)):.1%}")

Step 5: 의사역행렬

A = np.array([[1, 1], [2, 1], [3, 1]], dtype=float)
b = np.array([3, 5, 6], dtype=float)

U, S, Vt = np.linalg.svd(A, full_matrices=False)
S_inv = np.diag(1.0 / S)
A_pinv = Vt.T @ S_inv @ U.T

x_svd = A_pinv @ b
x_lstsq = np.linalg.lstsq(A, b, rcond=None)[0]
x_pinv = np.linalg.pinv(A) @ b

print(f"SVD pseudoinverse solution:  {x_svd}")
print(f"np.linalg.lstsq solution:   {x_lstsq}")
print(f"np.linalg.pinv solution:    {x_pinv}")

사용해보기

전체 데모는 code/svd.py에 있습니다. 이미지 압축, 추천 시스템, 잠재 의미 분석, 잡음 제거에 SVD를 적용합니다.

python svd.py

줄리아(Julia) 버전은 code/svd.jl에 있습니다. 줄리아의 기본 svd() 함수와 LinearAlgebra 패키지로 같은 개념을 보여 줍니다.

julia svd.jl

산출물 만들기

이 lesson의 검수 대상은 아래 세 가지입니다.

  • code/svd.py: Python으로 구현한 SVD, 압축, 추천, LSA, 잡음 제거, 의사역행렬 데모
  • code/svd.jl: 줄리아로 구현한 SVD 데모
  • outputs/skill-svd.md: 실제 프로젝트에서 SVD를 언제, 어떻게 적용할지 판단하는 스킬(skill)

연습문제

  1. (쉬움) 거듭제곱 반복 없이 전체 SVD를 처음부터 구현합니다. A^T A의 고유분해로 V와 특이값을 얻고, U = A V Sigma^{-1}로 계산합니다. 거듭제곱 반복 버전, 그리고 넘파이와 수치 정확도(numerical accuracy)를 비교해 봅니다.
  2. (중간) 실제 흑백 이미지를 불러오거나 이미지를 흑백으로 변환합니다. 계수 1, 5, 10, 25, 50, 100으로 압축하고 각 계수의 압축률과 상대 오차(relative error)를 계산합니다. 시각적으로 허용 가능한 계수가 어느 정도인지 찾아봅니다.
  3. (중간) 작은 추천 시스템을 만들어 봅니다. 10x8 사용자-영화 평점 행렬에 일부 알려진 항목(known entry)을 만들고 빠진 항목은 행 평균(row mean)으로 채웁니다. SVD를 수행한 뒤 rank-3 근사로 복원해 빠진 평점을 예측합니다. 예측이 합리적인지 확인합니다.
  4. (중간) 합성 주제(synthetic topic) 3개가 있는 100x50 단어-문서 행렬을 만듭니다. 주제마다 연관된 단어 5개를 두고 잡음을 추가합니다. SVD를 적용해 상위 3개 특이값이 나머지보다 훨씬 큰지 확인합니다. 문서를 3차원 잠재 공간(latent space)에 사영(project)하여 같은 주제의 문서가 군집(cluster)을 이루는지 확인합니다.
  5. (어려움) 계수가 3인 깨끗한 저계수 행렬을 만들고, sigma가 0.1, 0.5, 1.0, 2.0인 가우시안 잡음(Gaussian noise)을 추가합니다. 각 잡음 수준에서 k=1부터 40까지 훑어 보며 깨끗한 행렬 대비 복원 오차를 측정해 최적 절단 계수(optimal truncation rank)를 찾습니다.

핵심 용어

용어흔한 설명실제 의미
특이값 분해(SVD)어떤 행렬이든 분해한다A = U Sigma V^T로 분해한다. U, V는 직교(orthogonal) 행렬이고 Sigma는 음수가 아닌 대각 원소를 가진다. 어떤 형태, 어떤 계수의 행렬에도 동작한다
특이값(Singular value)"이 성분이 얼마나 중요한가"Sigma의 i번째 대각 원소. 행렬이 i번째 주방향(principal direction)을 얼마나 늘리는지를 측정한다. 항상 음수가 아니며 내림차순으로 정렬한다
좌특이벡터(Left singular vector)"출력 방향"U의 열. i번째 우특이벡터가 sigma_i만큼 스케일링된 뒤 도착하는 출력 공간 방향
우특이벡터(Right singular vector)"입력 방향"V의 열. 행렬이 i번째 좌특이벡터로 보내는 입력 공간 방향
절단 SVD(Truncated SVD)"저계수 근사"상위 k개 특이값과 그 벡터만 유지한다. 에카르트-영 정리에 의해 원본 행렬의 최선의 rank-k 근사가 된다
계수(Rank)"실제 차원"0이 아닌 특이값의 개수. 행렬이 실제로 사용하는 독립 방향(independent direction)의 수를 알려 준다
의사역행렬(Pseudoinverse)"일반화된 역행렬"V Sigma+ U^T. 0이 아닌 특이값은 역수로 바꾸고 0은 0으로 둔다. 정방이 아닌 행렬이나 특이행렬의 최소제곱 문제를 푼다
조건수(Condition number)"오차 민감도"sigma_max / sigma_min. 값이 크면 작은 입력 변화가 큰 출력 변화를 만들 수 있다. SVD가 이를 직접 보여 준다
잠재 요인(Latent factor)"숨은 변수"SVD가 찾은 저계수 공간의 차원. 추천에서는 장르 선호, 자연어 처리에서는 주제(topic)에 대응될 수 있다
프로베니우스 노름(Frobenius norm)"행렬 전체 크기"원소 제곱합의 제곱근. 특이값 제곱합의 제곱근과 같다. 근사 오차 측정에 사용한다
에카르트-영 정리(Eckart-Young theorem)"SVD가 최선의 압축을 준다"목표 계수 k에 대해 절단 SVD가 모든 rank-k 행렬 중 근사 오차를 최소화한다
거듭제곱 반복(Power iteration)"가장 큰 고유벡터 찾기"임의 벡터에 행렬을 반복해서 곱하고 정규화(normalize)한다. 가장 큰 고유값에 대응하는 고유벡터로 수렴한다

더 읽을거리

실습 코드

이 강의의 실습 코드 2개

svd
Code
svd
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

skill-svd

Apply SVD to real problems including compression, denoising, recommendations, and least-squares solving

Skill

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.`A = U * Sigma * V^T`에서 각 인수(factor)는 기하학적으로 어떤 연산을 나타내나요?

2.사이킷런(sklearn)이 PCA를 공분산 행렬(covariance matrix)의 고유분해 대신 SVD로 구현하는 이유는 무엇인가요?

3.절단 SVD(Truncated SVD)는 추천 시스템에서 빠진 평점을 어떻게 예측하게 하나요?

0/3 답변 완료