Skip to main content

선형대수

선형대수(Linear Algebra)는 머신러닝의 수학적 기반입니다. PCA(주성분 분석)는 고유값 분해에 기반하고, 선형회귀는 행렬 연산으로 해를 구하며, 추천 시스템은 SVD(특이값 분해)를 활용합니다. NumPy의 np.linalg 모듈은 이러한 연산을 효율적으로 수행합니다.

학습 목표

  • 행렬 곱셈과 내적 연산을 수행할 수 있다
  • 역행렬, 행렬식, 행렬의 랭크를 계산할 수 있다
  • 고유값 분해(Eigendecomposition)의 개념을 이해하고 NumPy로 구현할 수 있다
  • SVD의 기본 원리를 이해하고 차원 축소에 활용할 수 있다

왜 중요한가

ML 알고리즘의 내부를 이해하려면 선형대수가 필수입니다. sklearn이 자동으로 처리해주지만, 모델의 동작 원리를 이해하고 디버깅하려면 행렬 연산의 기초를 알아야 합니다. 또한 대규모 데이터에서 차원 축소, 특성 추출, 최적화 문제를 직접 다룰 때 선형대수 지식이 필요합니다.

행렬 곱셈과 내적

import numpy as np

# 원소별 곱셈 (element-wise)
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(A * B)          # [[ 5 12] [21 32]]

# 행렬 곱셈 (matrix multiplication)
print(A @ B)          # [[19 22] [43 50]]
print(np.dot(A, B))   # 동일 결과
print(np.matmul(A, B))  # 동일 결과

# 벡터 내적 (dot product)
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(np.dot(v1, v2))    # 32 (= 1*4 + 2*5 + 3*6)
Python 3.5+에서 @ 연산자는 행렬 곱셈 전용입니다. *는 원소별 곱셈입니다. 이 둘을 혼동하면 결과가 완전히 달라지므로 주의하세요.

전치, 역행렬, 행렬식

A = np.array([[1, 2], [3, 4]])

# 전치 행렬
print(A.T)
# [[1 3]
#  [2 4]]

# 역행렬
A_inv = np.linalg.inv(A)
print(A_inv)
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 검증: A @ A_inv ≈ 단위 행렬
print(A @ A_inv)
# [[1. 0.]
#  [0. 1.]]

# 행렬식 (determinant)
det = np.linalg.det(A)
print(f"행렬식: {det:.1f}")  # -2.0

# 행렬의 랭크
rank = np.linalg.matrix_rank(A)
print(f"랭크: {rank}")  # 2

# 대각합 (trace)
print(np.trace(A))  # 5 (= 1 + 4)

연립방정식 풀기

# 2x + 3y = 8
# 4x + y = 6
# → Ax = b 형태

A = np.array([[2, 3], [4, 1]])
b = np.array([8, 6])

# 해 구하기
x = np.linalg.solve(A, b)
print(x)  # [1. 2.] → x=1, y=2

# 검증
print(A @ x)  # [8. 6.] = b
np.linalg.solve()np.linalg.inv(A) @ b보다 수치적으로 더 안정적이고 빠릅니다. 연립방정식을 풀 때는 항상 solve()를 사용하세요.

고유값 분해

고유값 분해(Eigendecomposition)는 정방행렬을 고유값과 고유벡터로 분해하는 것입니다. PCA의 핵심 원리입니다.
# 공분산 행렬 예시
C = np.array([[4, 2],
              [2, 3]])

# 고유값과 고유벡터
eigenvalues, eigenvectors = np.linalg.eig(C)

print("고유값:", eigenvalues)        # [5.23607 1.76393]
print("고유벡터:\n", eigenvectors)
# [[ 0.788  -0.615]
#  [ 0.615   0.788]]

# 검증: Av = λv
for i in range(len(eigenvalues)):
    v = eigenvectors[:, i]
    lam = eigenvalues[i]
    print(f"Av = {C @ v}, λv = {lam * v}")
    # 두 결과가 같으면 올바른 분해

특이값 분해 (SVD)

SVD(Singular Value Decomposition)는 모든 행렬(정방이 아니어도)에 적용할 수 있는 분해 방법입니다. 차원 축소, 노이즈 제거, 추천 시스템 등에 활용됩니다.
# 4x3 행렬
A = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9],
              [10, 11, 12]])

# SVD 분해
U, S, Vt = np.linalg.svd(A, full_matrices=False)

print(f"U shape: {U.shape}")    # (4, 3)
print(f"S shape: {S.shape}")    # (3,) — 특이값
print(f"Vt shape: {Vt.shape}")  # (3, 3)

# 복원 검증
A_reconstructed = U @ np.diag(S) @ Vt
print(np.allclose(A, A_reconstructed))  # True

# 차원 축소: 상위 k개 특이값만 사용
k = 2
A_reduced = U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]
print(f"원본 shape: {A.shape}, 축소 복원 shape: {A_reduced.shape}")

노름 (Norm)

v = np.array([3, 4])

# L2 노름 (유클리드 거리)
print(np.linalg.norm(v))        # 5.0

# L1 노름 (맨해튼 거리)
print(np.linalg.norm(v, ord=1)) # 7.0

# 행렬의 프로베니우스 노름
M = np.array([[1, 2], [3, 4]])
print(np.linalg.norm(M, 'fro'))  # 5.477

AI/ML에서의 활용

  • PCA: 공분산 행렬의 고유값 분해로 주성분을 추출합니다
  • 선형회귀: 정규방정식 (X^T X)^{-1} X^T y로 최적 가중치를 구합니다
  • 추천 시스템: 사용자-아이템 행렬을 SVD로 분해하여 잠재 요인을 추출합니다
  • 정규화: L1/L2 노름이 Lasso/Ridge 회귀의 정규화 항입니다
  • 임베딩: Word2Vec, GloVe 등 단어 임베딩이 행렬 분해에 기반합니다
고유값 분해는 정방행렬에만 적용 가능하며, SVD는 모든 행렬에 적용 가능합니다. 실대칭행렬(예: 공분산 행렬)에서는 고유값 분해와 SVD가 동일한 결과를 줍니다. 실무에서는 SVD가 더 범용적으로 사용됩니다.
eigh()는 대칭(에르미트) 행렬 전용으로, eig()보다 빠르고 수치적으로 안정적입니다. 공분산 행렬처럼 항상 대칭인 행렬에는 eigh()를 사용하세요. 반환되는 고유값도 항상 오름차순으로 정렬됩니다.

체크리스트

  • @ 연산자와 * 연산자의 차이를 설명할 수 있다
  • 역행렬과 행렬식을 계산할 수 있다
  • np.linalg.solve()로 연립방정식을 풀 수 있다
  • 고유값과 고유벡터의 의미를 설명하고 계산할 수 있다
  • SVD의 기본 원리를 이해하고 차원 축소에 활용할 수 있다

다음 문서