들어가며
선형대수학(Linear Algebra)은 현대 수학과 공학의 핵심 기초입니다. 데이터 과학, 머신러닝, 컴퓨터 그래픽스, 양자역학에 이르기까지 거의 모든 분야에서 활용됩니다. 이 가이드는 벡터와 행렬의 기초부터 SVD(특이값 분해)까지, 선형대수학의 핵심 개념을 체계적으로 다룹니다.
**참고 자료:**
- Gilbert Strang, MIT OCW 18.06 Linear Algebra
- 3Blue1Brown: Essence of Linear Algebra (YouTube)
- NumPy 공식 문서 (numpy.org)
- scikit-learn PCA 문서
1. 벡터 (Vectors)
벡터의 정의와 표현
벡터(vector)는 크기(magnitude)와 방향(direction)을 모두 가진 수학적 객체입니다. $n$차원 벡터 $\mathbf{v}$는 다음과 같이 표현합니다.
$$\mathbf{v} = \begin{pmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{pmatrix} \in \mathbb{R}^n$$
벡터 연산
**덧셈과 스칼라 곱:**
$$\mathbf{u} + \mathbf{v} = \begin{pmatrix} u_1 + v_1 \\ u_2 + v_2 \end{pmatrix}, \quad c\mathbf{v} = \begin{pmatrix} cv_1 \\ cv_2 \end{pmatrix}$$
**내적(Dot Product):**
$$\mathbf{u} \cdot \mathbf{v} = \sum_{i=1}^{n} u_i v_i = \|\mathbf{u}\| \|\mathbf{v}\| \cos\theta$$
내적이 0이면 두 벡터는 서로 직교(orthogonal)합니다.
**외적(Cross Product)** — 3차원에서만 정의:
$$\mathbf{u} \times \mathbf{v} = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ u_1 & u_2 & u_3 \\ v_1 & v_2 & v_3 \end{vmatrix}$$
노름 (Norm)
- **L1 노름:** $\|\mathbf{v}\|_1 = \sum_{i} |v_i|$
- **L2 노름 (유클리드):** $\|\mathbf{v}\|_2 = \sqrt{\sum_{i} v_i^2}$
- **Lp 노름:** $\|\mathbf{v}\|_p = \left(\sum_{i} |v_i|^p\right)^{1/p}$
**단위벡터 (Unit Vector):**
$$\hat{\mathbf{v}} = \frac{\mathbf{v}}{\|\mathbf{v}\|}$$
벡터 정의
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
벡터 덧셈
print("덧셈:", a + b) # [5 7 9]
스칼라 곱
print("스칼라 곱:", 3 * a) # [3 6 9]
내적
dot = np.dot(a, b)
print("내적:", dot) # 32
외적
cross = np.cross(a, b)
print("외적:", cross) # [-3 6 -3]
L2 노름
norm_l2 = np.linalg.norm(a)
print("L2 노름:", norm_l2) # 3.7416...
L1 노름
norm_l1 = np.linalg.norm(a, ord=1)
print("L1 노름:", norm_l1) # 6.0
단위벡터
unit = a / np.linalg.norm(a)
print("단위벡터:", unit)
print("크기 확인:", np.linalg.norm(unit)) # 1.0
두 벡터의 각도
cos_theta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
angle = np.arccos(cos_theta)
print("각도 (라디안):", angle)
print("각도 (도):", np.degrees(angle))
2. 행렬 (Matrices)
행렬의 정의와 종류
$m \times n$ 행렬은 $m$개의 행과 $n$개의 열로 이루어진 수의 직사각형 배열입니다.
$$A = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}$$
**주요 행렬 종류:**
| 종류 | 정의 | 예시 |
| --------------------- | --------------------------- | -------------------------------------------- |
| 단위행렬 (Identity) | $I_{ij} = 1$ (i=j), 0 (i≠j) | $I_2 = \begin{pmatrix}1&0\\0&1\end{pmatrix}$ |
| 대각행렬 (Diagonal) | 대각선 외 모두 0 | $\text{diag}(d_1, d_2)$ |
| 대칭행렬 (Symmetric) | $A = A^T$ | — |
| 직교행렬 (Orthogonal) | $A^T A = I$ | 회전 행렬 |
행렬 연산
**행렬 곱 (Matrix Multiplication):**
$(AB)_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}$
행렬 곱은 교환 법칙이 성립하지 않습니다: $AB \neq BA$ (일반적으로)
**전치행렬 (Transpose):**
$(A^T)_{ij} = A_{ji}$
**역행렬 (Inverse):**
정방행렬 $A$의 역행렬 $A^{-1}$은 $AA^{-1} = A^{-1}A = I$를 만족합니다. 역행렬은 행렬식이 0이 아닐 때만 존재합니다.
행렬 정의
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
행렬 덧셈
print("행렬 덧셈:\n", A + B)
행렬 곱
C = A @ B
print("행렬 곱:\n", C)
원소별 곱 (Hadamard product)
print("원소별 곱:\n", A * B)
전치행렬
print("전치행렬:\n", A.T)
역행렬
A_inv = np.linalg.inv(A)
print("역행렬:\n", A_inv)
검증: A @ A_inv = I
print("검증 (A @ A_inv):\n", A @ A_inv)
단위행렬
I = np.eye(3)
print("3x3 단위행렬:\n", I)
대각행렬
D = np.diag([1, 2, 3])
print("대각행렬:\n", D)
대칭행렬 생성
S = A @ A.T
print("대칭행렬 (A @ A^T):\n", S)
print("대칭 확인:", np.allclose(S, S.T))
3. 행렬식 (Determinant)
행렬식의 정의와 계산
행렬식(determinant)은 정방행렬에서 정의되는 스칼라 값으로, 행렬의 다양한 특성을 담고 있습니다.
**2×2 행렬식:**
$$\det\begin{pmatrix} a & b \\ c & d \end{pmatrix} = ad - bc$$
**3×3 행렬식 (사루스 법칙, Sarrus' Rule):**
$$\det(A) = a_{11}(a_{22}a_{33} - a_{23}a_{32}) - a_{12}(a_{21}a_{33} - a_{23}a_{31}) + a_{13}(a_{21}a_{32} - a_{22}a_{31})$$
행렬식의 성질
1. $\det(I) = 1$
2. $\det(AB) = \det(A)\det(B)$
3. $\det(A^T) = \det(A)$
4. $\det(A^{-1}) = 1/\det(A)$
5. 행렬식이 0이면 역행렬이 존재하지 않음 (특이행렬, singular matrix)
6. 기하학적으로: 행 벡터들이 이루는 평행육면체의 부피
크라머 공식 (Cramer's Rule)
연립방정식 $A\mathbf{x} = \mathbf{b}$의 해:
$$x_i = \frac{\det(A_i)}{\det(A)}$$
여기서 $A_i$는 $A$의 $i$번째 열을 $\mathbf{b}$로 대체한 행렬입니다.
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
B = np.array([[2, -1, 0],
[-1, 2, -1],
[0, -1, 2]])
행렬식 계산
det_A = np.linalg.det(A)
det_B = np.linalg.det(B)
print("det(A):", det_A) # 거의 0 (특이행렬에 가까움)
print("det(B):", det_B) # 4.0
성질 검증: det(A@B) = det(A)*det(B)
print("det(A)*det(B):", det_A * det_B)
print("det(A@B):", np.linalg.det(A @ B))
크라머 공식으로 연립방정식 풀기
A_sys = np.array([[2, 1], [5, 3]], dtype=float)
b_sys = np.array([1, 2], dtype=float)
det_A_sys = np.linalg.det(A_sys)
x1 계산
A1 = A_sys.copy()
A1[:, 0] = b_sys
x1 = np.linalg.det(A1) / det_A_sys
x2 계산
A2 = A_sys.copy()
A2[:, 1] = b_sys
x2 = np.linalg.det(A2) / det_A_sys
print(f"x1 = {x1}, x2 = {x2}")
numpy linalg solve로 검증
x = np.linalg.solve(A_sys, b_sys)
print("numpy solve:", x)
4. 연립방정식과 가우스 소거법
선형 연립방정식
$n$개의 미지수에 대한 $m$개의 선형방정식:
$$a_{11}x_1 + a_{12}x_2 + \cdots + a_{1n}x_n = b_1$$
$$a_{21}x_1 + a_{22}x_2 + \cdots + a_{2n}x_n = b_2$$
$$\vdots$$
$$a_{m1}x_1 + a_{m2}x_2 + \cdots + a_{mn}x_n = b_m$$
행렬 형태: $A\mathbf{x} = \mathbf{b}$
첨가행렬 (Augmented Matrix)
$$[A|\mathbf{b}] = \left(\begin{array}{ccc|c} a_{11} & \cdots & a_{1n} & b_1 \\ \vdots & \ddots & \vdots & \vdots \\ a_{m1} & \cdots & a_{mn} & b_m \end{array}\right)$$
가우스 소거법 (Gaussian Elimination)
행 기본 연산(Elementary Row Operations):
1. 두 행 교환: $R_i \leftrightarrow R_j$
2. 행에 스칼라 곱: $R_i \leftarrow c \cdot R_i$
3. 행의 배수 더하기: $R_i \leftarrow R_i + c \cdot R_j$
이 연산으로 행사다리꼴(Row Echelon Form, REF) 또는 기약행사다리꼴(Reduced REF)을 만듭니다.
LU 분해
$A = LU$: 하삼각행렬 $L$과 상삼각행렬 $U$의 곱으로 분해
from scipy import linalg
연립방정식: 2x + y = 5, x + 3y = 10
A = np.array([[2, 1], [1, 3]], dtype=float)
b = np.array([5, 10], dtype=float)
numpy linalg.solve
x = np.linalg.solve(A, b)
print("해:", x) # [1. 3.]
가우스-조르단 소거법 구현
def gauss_jordan(A, b):
n = len(b)
첨가행렬 생성
aug = np.hstack([A.astype(float), b.reshape(-1, 1).astype(float)])
for col in range(n):
피벗 선택 (부분 피벗팅)
max_row = np.argmax(np.abs(aug[col:, col])) + col
aug[[col, max_row]] = aug[[max_row, col]]
피벗 행 정규화
aug[col] = aug[col] / aug[col, col]
다른 행에서 소거
for row in range(n):
if row != col:
aug[row] -= aug[row, col] * aug[col]
return aug[:, -1]
solution = gauss_jordan(A.copy(), b.copy())
print("가우스-조르단 해:", solution)
LU 분해
P, L, U = linalg.lu(A)
print("L 행렬:\n", L)
print("U 행렬:\n", U)
print("P 행렬:\n", P)
LU 분해로 연립방정식 풀기
x_lu = linalg.solve(A, b)
print("LU 분해 해:", x_lu)
5. 벡터 공간 (Vector Spaces)
벡터 공간의 정의
집합 $V$가 벡터 공간이 되려면 덧셈과 스칼라 곱에 대해 다음 8가지 공리를 만족해야 합니다:
1. **닫힘 (Closure):** $\mathbf{u}, \mathbf{v} \in V \Rightarrow \mathbf{u} + \mathbf{v} \in V$
2. **교환법칙:** $\mathbf{u} + \mathbf{v} = \mathbf{v} + \mathbf{u}$
3. **결합법칙:** $(\mathbf{u} + \mathbf{v}) + \mathbf{w} = \mathbf{u} + (\mathbf{v} + \mathbf{w})$
4. **영벡터 존재:** $\mathbf{v} + \mathbf{0} = \mathbf{v}$
5. **역원 존재:** $\mathbf{v} + (-\mathbf{v}) = \mathbf{0}$
6. **스칼라 닫힘:** $c \in \mathbb{R}, \mathbf{v} \in V \Rightarrow c\mathbf{v} \in V$
7. **분배법칙 1:** $c(\mathbf{u} + \mathbf{v}) = c\mathbf{u} + c\mathbf{v}$
8. **분배법칙 2:** $(c + d)\mathbf{v} = c\mathbf{v} + d\mathbf{v}$
선형독립 (Linear Independence)
벡터 집합 $\{\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_k\}$이 **선형독립**이란:
$$c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots + c_k\mathbf{v}_k = \mathbf{0} \implies c_1 = c_2 = \cdots = c_k = 0$$
즉, 자명한 해(trivial solution)만 존재해야 합니다.
기저 (Basis)와 차원 (Dimension)
벡터 공간 $V$의 **기저(basis)**는 $V$를 생성(span)하는 선형독립인 벡터들의 집합입니다.
- **차원(dimension):** 기저의 원소 개수
- $\mathbb{R}^n$의 표준기저: $\mathbf{e}_1, \mathbf{e}_2, \ldots, \mathbf{e}_n$
행공간, 열공간, 영공간
행렬 $A \in \mathbb{R}^{m \times n}$에 대해:
- **행공간 (Row Space):** $A$의 행 벡터들로 생성되는 공간, $\subseteq \mathbb{R}^n$
- **열공간 (Column Space):** $A$의 열 벡터들로 생성되는 공간, $\subseteq \mathbb{R}^m$
- **영공간 (Null Space):** $A\mathbf{x} = \mathbf{0}$의 해 집합, $\subseteq \mathbb{R}^n$
- **차원 정리:** $\text{rank}(A) + \text{nullity}(A) = n$
랭크 (Rank)
$\text{rank}(A) = \dim(\text{Row Space}) = \dim(\text{Column Space})$
A = np.array([[1, 2, 3],
[4, 5, 6],
[2, 4, 6]]) # 3행은 1행의 2배 (선형종속)
랭크 계산
rank = np.linalg.matrix_rank(A)
print("랭크:", rank) # 2
영공간 (null space) 계산
from scipy.linalg import null_space
ns = null_space(A)
print("영공간:\n", ns)
열공간의 기저 (QR 분해 활용)
Q, R = np.linalg.qr(A)
print("열공간의 기저 (Q의 처음 rank개 열):\n", Q[:, :rank])
선형독립 확인 예시
v1 = np.array([1, 0, 0])
v2 = np.array([0, 1, 0])
v3 = np.array([0, 0, 1])
M = np.column_stack([v1, v2, v3])
print("v1, v2, v3의 랭크:", np.linalg.matrix_rank(M)) # 3 = 선형독립
v4 = np.array([1, 2, 3])
v5 = np.array([2, 4, 6]) # v5 = 2*v4
M2 = np.column_stack([v4, v5])
print("v4, v5의 랭크:", np.linalg.matrix_rank(M2)) # 1 = 선형종속
6. 선형 변환 (Linear Transformations)
선형 변환의 정의
함수 $T: V \to W$가 **선형 변환**이 되려면:
1. $T(\mathbf{u} + \mathbf{v}) = T(\mathbf{u}) + T(\mathbf{v})$ (가산성)
2. $T(c\mathbf{v}) = cT(\mathbf{v})$ (동차성)
행렬 표현
모든 선형 변환은 행렬로 표현됩니다: $T(\mathbf{x}) = A\mathbf{x}$
**핵 (Kernel):** $\ker(T) = \{\mathbf{v} \in V : T(\mathbf{v}) = \mathbf{0}\}$
**상 (Image):** $\text{Im}(T) = \{T(\mathbf{v}) : \mathbf{v} \in V\}$
**차원 정리:** $\dim(\ker T) + \dim(\text{Im} T) = \dim V$
기하학적 변환
2차원에서의 주요 변환 행렬:
**회전 행렬 (각도 $\theta$):**
$$R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}$$
**x축 반사:**
$$F_x = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}$$
**균등 확대/축소 (배율 s):**
$$S = \begin{pmatrix} s & 0 \\ 0 & s \end{pmatrix}$$
회전 변환
def rotation_matrix(theta):
return np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
45도 회전
theta = np.pi / 4
R = rotation_matrix(theta)
print("45도 회전 행렬:\n", R)
벡터 변환
v = np.array([1, 0])
v_rotated = R @ v
print("회전된 벡터:", v_rotated) # [0.707, 0.707]
전단 변환 (shear)
shear = np.array([[1, 1],
[0, 1]])
단위 정사각형의 꼭짓점 변환
corners = np.array([[0, 1, 1, 0, 0],
[0, 0, 1, 1, 0]])
transformed = shear @ corners
print("전단 변환 후 꼭짓점:\n", transformed)
직교 행렬 검증
print("R^T @ R =\n", R.T @ R) # 단위행렬
print("det(R) =", np.linalg.det(R)) # 1
7. 내적 공간 (Inner Product Spaces)
내적의 정의
내적(inner product) $\langle \cdot, \cdot \rangle: V \times V \to \mathbb{R}$은 다음을 만족합니다:
1. **양정치성:** $\langle \mathbf{v}, \mathbf{v} \rangle \geq 0$, 등호는 $\mathbf{v} = \mathbf{0}$일 때만
2. **대칭성:** $\langle \mathbf{u}, \mathbf{v} \rangle = \langle \mathbf{v}, \mathbf{u} \rangle$
3. **선형성:** $\langle a\mathbf{u} + b\mathbf{v}, \mathbf{w} \rangle = a\langle \mathbf{u}, \mathbf{w} \rangle + b\langle \mathbf{v}, \mathbf{w} \rangle$
코시-슈바르츠 부등식
$$|\langle \mathbf{u}, \mathbf{v} \rangle| \leq \|\mathbf{u}\| \cdot \|\mathbf{v}\|$$
등호는 $\mathbf{u}$와 $\mathbf{v}$가 평행할 때 성립합니다.
그람-슈미트 과정 (Gram-Schmidt Process)
선형독립인 벡터들로부터 정규직교기저(orthonormal basis)를 구성합니다.
주어진 벡터 $\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_k$에 대해:
$$\mathbf{u}_1 = \mathbf{v}_1$$
$$\mathbf{u}_2 = \mathbf{v}_2 - \frac{\langle \mathbf{v}_2, \mathbf{u}_1 \rangle}{\langle \mathbf{u}_1, \mathbf{u}_1 \rangle} \mathbf{u}_1$$
일반적으로:
$$\mathbf{u}_k = \mathbf{v}_k - \sum_{j=1}^{k-1} \frac{\langle \mathbf{v}_k, \mathbf{u}_j \rangle}{\langle \mathbf{u}_j, \mathbf{u}_j \rangle} \mathbf{u}_j$$
마지막으로 각 $\mathbf{u}_i$를 정규화합니다.
def gram_schmidt(vectors):
"""그람-슈미트 정규직교화"""
basis = []
for v in vectors:
w = v.copy().astype(float)
for b in basis:
w -= np.dot(v, b) * b
norm = np.linalg.norm(w)
if norm > 1e-10:
basis.append(w / norm)
return np.array(basis)
선형독립인 벡터들
v1 = np.array([1, 1, 0])
v2 = np.array([1, 0, 1])
v3 = np.array([0, 1, 1])
그람-슈미트 적용
basis = gram_schmidt([v1, v2, v3])
print("정규직교기저:\n", basis)
직교성 확인
print("내적 확인 (0이어야 함):")
print("e1 dot e2:", np.dot(basis[0], basis[1]))
print("e1 dot e3:", np.dot(basis[0], basis[2]))
print("e2 dot e3:", np.dot(basis[1], basis[2]))
단위벡터 확인
print("노름 확인 (1이어야 함):")
for i, b in enumerate(basis):
print(f" e{i+1} 노름:", np.linalg.norm(b))
scipy의 그람-슈미트 (QR 분해 활용)
A = np.column_stack([v1, v2, v3])
Q, R = np.linalg.qr(A)
print("QR 분해 Q 행렬:\n", Q)
8. 고유값과 고유벡터 (Eigenvalues & Eigenvectors)
정의
정방행렬 $A$에 대해 다음을 만족하는 스칼라 $\lambda$와 영이 아닌 벡터 $\mathbf{v}$:
$$A\mathbf{v} = \lambda\mathbf{v}$$
- $\lambda$: **고유값 (eigenvalue)**
- $\mathbf{v}$: $\lambda$에 대응하는 **고유벡터 (eigenvector)**
고유벡터는 $A$에 의해 변환될 때 방향은 유지되고 크기만 $\lambda$배 변합니다.
특성 방정식
$$\det(A - \lambda I) = 0$$
이 다항식(characteristic polynomial)의 근이 고유값입니다.
**2×2 예시:**
$$A = \begin{pmatrix} 4 & 1 \\ 2 & 3 \end{pmatrix}$$
$$\det(A - \lambda I) = (4-\lambda)(3-\lambda) - 2 = \lambda^2 - 7\lambda + 10 = (\lambda-2)(\lambda-5) = 0$$
따라서 $\lambda_1 = 2$, $\lambda_2 = 5$
고유값 분해 (Eigendecomposition)
대각화 가능한 행렬 $A$는 다음과 같이 분해됩니다:
$$A = P \Lambda P^{-1}$$
여기서 $\Lambda = \text{diag}(\lambda_1, \lambda_2, \ldots, \lambda_n)$이고 $P$의 열들은 고유벡터입니다.
대칭 행렬의 스펙트럼 정리
실수 대칭 행렬 $A = A^T$는:
1. 모든 고유값이 실수
2. 서로 다른 고유값에 대한 고유벡터는 직교
3. $A = Q \Lambda Q^T$ (직교 대각화 가능)
from scipy.linalg import eig
일반 행렬의 고유값/고유벡터
A = np.array([[4, 1],
[2, 3]])
eigenvalues, eigenvectors = np.linalg.eig(A)
print("고유값:", eigenvalues) # [5., 2.]
print("고유벡터 (열):\n", eigenvectors)
검증: A @ v = lambda * v
for i in range(len(eigenvalues)):
lam = eigenvalues[i]
v = eigenvectors[:, i]
print(f"\nlambda={lam:.2f}:")
print(" A @ v:", A @ v)
print(" lambda * v:", lam * v)
고유값 분해: A = P @ Lambda @ P^{-1}
P = eigenvectors
Lambda = np.diag(eigenvalues)
A_reconstructed = P @ Lambda @ np.linalg.inv(P)
print("\n재구성 행렬:\n", A_reconstructed)
print("원래 행렬과 동일:", np.allclose(A, A_reconstructed))
대칭 행렬 (실수 고유값 보장)
B = np.array([[2, 1, 0],
[1, 3, 1],
[0, 1, 2]])
eigenvalues_B, eigenvectors_B = np.linalg.eigh(B) # 대칭 행렬 전용
print("\n대칭 행렬 고유값:", eigenvalues_B)
print("직교성 확인 (Q^T @ Q = I):\n",
np.round(eigenvectors_B.T @ eigenvectors_B))
거듭제곱: A^n = P @ Lambda^n @ P^{-1}
n = 5
Lambda_n = np.diag(eigenvalues ** n)
A_power = P @ Lambda_n @ np.linalg.inv(P)
print(f"\nA^{n}:\n", A_power)
print("직접 계산과 비교:\n", np.linalg.matrix_power(A, n))
9. 특이값 분해 (SVD - Singular Value Decomposition)
SVD의 정의
임의의 $m \times n$ 행렬 $A$는 다음과 같이 분해됩니다:
$$A = U \Sigma V^T$$
- $U \in \mathbb{R}^{m \times m}$: 좌특이벡터(left singular vectors), 직교행렬
- $\Sigma \in \mathbb{R}^{m \times n}$: 특이값 대각행렬 ($\sigma_1 \geq \sigma_2 \geq \cdots \geq 0$)
- $V \in \mathbb{R}^{n \times n}$: 우특이벡터(right singular vectors), 직교행렬
기하학적 의미
$A$는 다음 세 변환의 합성으로 볼 수 있습니다:
1. $V^T$: 회전/반사
2. $\Sigma$: 좌표축 방향 확대/축소
3. $U$: 회전/반사
PCA와의 관계
데이터 행렬 $X$의 SVD를 수행하면 주성분 분석(PCA)을 수행한 것과 동일합니다.
$X = U \Sigma V^T$에서 $V$의 열들이 주성분(principal components)이 됩니다.
이미지 압축
특이값을 내림차순으로 정렬하면 상위 $k$개의 특이값으로 근사 행렬을 구성할 수 있습니다:
$$A_k = \sum_{i=1}^{k} \sigma_i \mathbf{u}_i \mathbf{v}_i^T$$
기본 SVD
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
U, S, Vt = np.linalg.svd(A)
print("U 형태:", U.shape) # (4, 4)
print("S (특이값):", S) # 특이값
print("Vt 형태:", Vt.shape) # (3, 3)
재구성 검증
Sigma = np.zeros(A.shape)
Sigma[:min(A.shape), :min(A.shape)] = np.diag(S)
A_reconstructed = U @ Sigma @ Vt
print("재구성 오차:", np.linalg.norm(A - A_reconstructed))
이미지 압축 시뮬레이션
def svd_compress(matrix, k):
"""상위 k개 특이값으로 행렬 압축"""
U, S, Vt = np.linalg.svd(matrix)
k개 성분만 사용
U_k = U[:, :k]
S_k = S[:k]
Vt_k = Vt[:k, :]
return U_k @ np.diag(S_k) @ Vt_k
테스트 행렬 (이미지 시뮬레이션)
np.random.seed(42)
img = np.random.randn(100, 100)
다양한 압축 비율 테스트
for k in [5, 10, 20, 50]:
compressed = svd_compress(img, k)
error = np.linalg.norm(img - compressed, 'fro') / np.linalg.norm(img, 'fro')
compression_ratio = k * (100 + 100 + 1) / (100 * 100)
print(f"k={k}: 상대 오차={error:.4f}, 압축비={compression_ratio:.4f}")
PCA 구현 (SVD 활용)
def pca_svd(X, n_components):
"""SVD를 이용한 PCA"""
데이터 중심화
X_centered = X - X.mean(axis=0)
SVD 수행
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
주성분으로 투영
X_pca = X_centered @ Vt[:n_components].T
설명 분산 비율
explained_variance_ratio = (S[:n_components]**2) / (S**2).sum()
return X_pca, Vt[:n_components], explained_variance_ratio
예시 데이터
np.random.seed(42)
X = np.random.randn(100, 5)
X_pca, components, evr = pca_svd(X, 2)
print("\nPCA 설명 분산 비율:", evr)
print("투영된 데이터 형태:", X_pca.shape)
10. 행렬 분해 (Matrix Factorizations)
주요 분해 방법 비교
| 분해 | 형태 | 적용 조건 | 주요 용도 |
| ----------- | ------------ | ----------- | ------------------ |
| LU 분해 | A = LU | 정방행렬 | 연립방정식 |
| QR 분해 | A = QR | 임의 행렬 | 최소제곱법, 고유값 |
| Cholesky | A = LL^T | 양정치 대칭 | 수치 최적화 |
| SVD | A = UΣV^T | 임의 행렬 | PCA, 이미지 압축 |
| 고유값 분해 | A = PΛP^{-1} | 정방행렬 | 스펙트럼 분석 |
Cholesky 분해
양정치(positive definite) 대칭 행렬에 대해: $A = LL^T$
from scipy import linalg
QR 분해
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 10]], dtype=float)
Q, R = np.linalg.qr(A)
print("Q (직교 행렬):\n", Q)
print("R (상삼각 행렬):\n", R)
print("Q^T @ Q = I 확인:\n", np.round(Q.T @ Q))
Cholesky 분해
B = np.array([[4, 2, 2],
[2, 3, 1],
[2, 1, 3]], dtype=float)
L = np.linalg.cholesky(B)
print("\nCholesky L:\n", L)
print("L @ L^T = A 확인:\n", L @ L.T)
LU 분해 (scipy)
P, L_lu, U = linalg.lu(A)
print("\nLU 분해:")
print("P:\n", P)
print("L:\n", L_lu)
print("U:\n", U)
print("P @ L @ U = A 확인:\n", np.round(P @ L_lu @ U))
11. AI/ML에서의 선형대수학
신경망에서의 행렬 곱
신경망의 순전파(forward propagation)는 연속된 행렬 곱으로 표현됩니다:
$$\mathbf{h}^{(l)} = f(W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)})$$
- $W^{(l)}$: 가중치 행렬
- $\mathbf{b}^{(l)}$: 편향 벡터
- $f$: 활성화 함수
배치 처리 시 입력을 행렬 $X \in \mathbb{R}^{n \times d}$로 표현하여 병렬 계산이 가능합니다.
그래디언트와 야코비안
손실 함수 $L$에 대한 가중치 행렬의 그래디언트:
$$\frac{\partial L}{\partial W} = \frac{\partial L}{\partial \mathbf{h}} \mathbf{x}^T$$
PCA로 차원 축소
고차원 특성 공간에서 주요 분산 방향을 찾아 차원을 축소합니다.
Transformer의 Attention 메커니즘
Self-Attention은 세 행렬 $Q, K, V$ (Query, Key, Value)를 이용합니다:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V$$
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
1. 간단한 신경망 순전파
np.random.seed(42)
n_samples, input_dim, hidden_dim, output_dim = 32, 784, 128, 10
X = np.random.randn(n_samples, input_dim)
W1 = np.random.randn(input_dim, hidden_dim) * 0.01
b1 = np.zeros(hidden_dim)
W2 = np.random.randn(hidden_dim, output_dim) * 0.01
b2 = np.zeros(output_dim)
순전파
H1 = np.maximum(0, X @ W1 + b1) # ReLU 활성화
logits = H1 @ W2 + b2
print("logits 형태:", logits.shape) # (32, 10)
2. PCA로 차원 축소
iris = load_iris()
X_iris = iris.data # (150, 4)
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_iris)
print("\nPCA 후 형태:", X_pca.shape) # (150, 2)
print("설명 분산 비율:", pca.explained_variance_ratio_)
print("누적 설명 분산:", pca.explained_variance_ratio_.sum())
3. Scaled Dot-Product Attention
def scaled_dot_product_attention(Q, K, V):
d_k = Q.shape[-1]
Q @ K^T / sqrt(d_k)
scores = Q @ K.T / np.sqrt(d_k)
Softmax
exp_scores = np.exp(scores - scores.max(axis=-1, keepdims=True))
attention_weights = exp_scores / exp_scores.sum(axis=-1, keepdims=True)
가중 합
output = attention_weights @ V
return output, attention_weights
예시
seq_len, d_model = 5, 8
Q = np.random.randn(seq_len, d_model)
K = np.random.randn(seq_len, d_model)
V = np.random.randn(seq_len, d_model)
output, weights = scaled_dot_product_attention(Q, K, V)
print("\nAttention 출력 형태:", output.shape) # (5, 8)
print("Attention 가중치 합:", weights.sum(axis=-1)) # 모두 1
4. Word Embedding 유사도
word_embeddings = {
"king": np.array([0.8, 0.3, 0.1, 0.9]),
"queen": np.array([0.7, 0.4, 0.2, 0.8]),
"man": np.array([0.6, 0.1, 0.7, 0.2]),
"woman": np.array([0.5, 0.2, 0.8, 0.1])
}
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print("\n단어 유사도:")
print("king-queen:", cosine_similarity(
word_embeddings["king"], word_embeddings["queen"]))
print("man-woman:", cosine_similarity(
word_embeddings["man"], word_embeddings["woman"]))
king - man + woman ≈ queen (analogy)
analogy = word_embeddings["king"] - word_embeddings["man"] + word_embeddings["woman"]
sim_queen = cosine_similarity(analogy, word_embeddings["queen"])
print("king - man + woman vs queen 유사도:", sim_queen)
12. 퀴즈
**정답**: 두 벡터의 내적이 0이면 두 벡터는 서로 직교(orthogonal)합니다.
**설명**: 내적 공식 $\mathbf{u} \cdot \mathbf{v} = \|\mathbf{u}\|\|\mathbf{v}\|\cos\theta$에서 $\cos 90° = 0$이므로, 두 벡터가 직각을 이룰 때 내적이 0이 됩니다. 이를 이용해 정규직교기저를 구성하거나 직교 투영을 계산합니다.
**정답**: 정방행렬의 행렬식(determinant)이 0이 아닐 때 역행렬이 존재합니다.
**설명**: $\det(A) \neq 0$이면 $A$는 가역행렬(invertible matrix)입니다. 행렬식이 0인 행렬은 특이행렬(singular matrix)이라 하며 역행렬이 존재하지 않습니다. 이는 행렬의 랭크(rank)가 행렬의 크기보다 작음을 의미하고, 해당 선형 연립방정식은 유일한 해를 가지지 않습니다.
**정답**: 고유벡터는 선형 변환에 의해 방향이 바뀌지 않는 벡터이고, 고유값은 그 벡터가 몇 배로 늘어나는지를 나타냅니다.
**설명**: $A\mathbf{v} = \lambda\mathbf{v}$에서 행렬 $A$로 변환해도 벡터 $\mathbf{v}$의 방향은 유지되고 크기만 $\lambda$배 변합니다. $\lambda > 1$이면 늘어남, $0 < \lambda < 1$이면 줄어듦, $\lambda < 0$이면 방향이 반전됩니다. 고유값 분해는 PCA, 행렬 거듭제곱 등에 활용됩니다.
**정답**: 중심화된 데이터 행렬에 SVD를 적용하면 PCA와 동일한 결과를 얻습니다. SVD의 우특이벡터(V)가 PCA의 주성분(principal components)에 해당합니다.
**설명**: 데이터 행렬 $X$를 중심화한 후 SVD를 수행하면 $X = U\Sigma V^T$가 됩니다. 이때 $V$의 열벡터들이 주성분이며, 특이값 $\sigma_i$의 제곱에 비례하는 분산을 설명합니다. 계산적으로 공분산 행렬의 고유값 분해보다 수치적으로 안정적이므로 sklearn의 PCA도 내부적으로 SVD를 사용합니다.
**정답**: $m \times n$ 행렬 $A$에 대해 $\text{rank}(A) + \text{nullity}(A) = n$이 성립합니다.
**설명**: rank(A)는 열공간(column space)의 차원이고, nullity(A)는 영공간(null space)의 차원입니다. 열의 수 $n$은 항상 이 두 차원의 합과 같습니다. 예를 들어 3×4 행렬의 랭크가 3이라면 영공간의 차원은 1입니다. 이 정리는 연립방정식의 해의 구조를 이해하는 데 핵심적입니다.
정리 및 학습 로드맵
선형대수학의 핵심 개념들은 서로 긴밀하게 연결되어 있습니다:
벡터 → 행렬 → 행렬식 → 연립방정식
↓ ↓
벡터 공간 → 선형 변환 → 내적 공간
↓
고유값/고유벡터 → SVD → AI/ML 응용
**다음 단계로 추천하는 학습 자료:**
1. **Gilbert Strang의 Linear Algebra (MIT OCW 18.06)**: 가장 체계적인 선형대수학 강의
2. **3Blue1Brown - Essence of Linear Algebra**: 시각적 직관 이해에 탁월
3. **NumPy 공식 문서**: 선형대수 모듈 (`numpy.linalg`) 실습
4. **scikit-learn PCA 문서**: 실제 ML 파이프라인에서의 활용
5. **Matrix Methods in Data Analysis (MIT 18.065)**: 고급 응용 과정
선형대수학은 단순히 수식을 외우는 것이 아니라 행렬과 벡터의 기하학적 의미를 이해하는 것이 핵심입니다. 꾸준한 코드 실습과 시각화를 통해 깊은 이해를 쌓아가시길 바랍니다.
현재 단락 (1/466)
선형대수학(Linear Algebra)은 현대 수학과 공학의 핵심 기초입니다. 데이터 과학, 머신러닝, 컴퓨터 그래픽스, 양자역학에 이르기까지 거의 모든 분야에서 활용됩니다. 이 ...