- Authors

- Name
- Youngju Kim
- @fjvbn20031
AI/ML을 위한 수학 완전 정복
머신러닝과 딥러닝을 깊이 이해하려면 그 바탕에 깔린 수학을 알아야 합니다. 많은 개발자들이 "왜 이 수식이 이렇게 작동하는가?"라는 질문에 명확한 답을 얻지 못한 채로 프레임워크를 사용합니다. 이 글은 그 공백을 채우기 위한 시도입니다.
단순히 수식을 나열하는 것이 아니라, 각 개념이 왜 중요한지, 딥러닝의 어느 부분에서 어떻게 쓰이는지를 중심으로 설명합니다. Python/NumPy 코드를 함께 제공하여 수식을 직접 확인할 수 있습니다.
1. 선형대수(Linear Algebra)
선형대수는 딥러닝의 가장 기본적인 언어입니다. 신경망의 순전파(Forward Pass)는 사실 행렬 곱셈의 연속이고, 임베딩은 고차원 벡터 공간의 한 점입니다.
1.1 벡터(Vector)
벡터의 정의와 기하학적 의미
벡터는 크기(magnitude)와 방향(direction)을 동시에 가진 양입니다. 수학적으로는 수들의 순서 있는 목록이며, 차원 공간의 한 점을 가리키는 화살표로 시각화할 수 있습니다.
딥러닝에서 벡터는 도처에 등장합니다. 입력 데이터, 은닉층의 활성화값, 임베딩 표현 등이 모두 벡터입니다.
벡터 덧셈과 스칼라 곱
두 벡터의 덧셈은 성분별(element-wise)로 이루어집니다.
스칼라 와 벡터의 곱은 각 성분에 를 곱합니다.
내적(Dot Product)
내적은 두 벡터의 "유사성"을 측정하는 연산입니다.
기하학적으로, 내적은 두 벡터의 크기와 사이각 의 코사인으로도 표현됩니다.
내적이 양수이면 두 벡터는 같은 방향을, 음수이면 반대 방향을, 0이면 직각을 이룹니다.
코사인 유사도(Cosine Similarity)
두 벡터의 방향 유사성만을 측정하려면 크기로 정규화합니다.
값의 범위는 이며, 1이면 완전히 같은 방향, -1이면 완전히 반대 방향을 의미합니다. 자연어 처리에서 단어 임베딩의 유사도를 측정할 때 자주 사용됩니다.
벡터 노름(Vector Norm)
노름은 벡터의 "크기(길이)"를 측정하는 함수입니다.
- 노름(맨해튼 거리):
- 노름(유클리드 거리):
- 노름(최댓값 노름):
정규화(Regularization)에서 은 희소성(sparsity)을, 는 가중치의 전반적 크기 축소를 유도합니다.
import numpy as np
a = np.array([3.0, 4.0])
b = np.array([1.0, 2.0])
# 내적
dot_product = np.dot(a, b) # 3*1 + 4*2 = 11
print(f"내적: {dot_product}")
# 노름
l2_norm_a = np.linalg.norm(a) # sqrt(9+16) = 5
print(f"L2 노름: {l2_norm_a}")
# 코사인 유사도
cosine_sim = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"코사인 유사도: {cosine_sim:.4f}")
# 다양한 노름
v = np.array([3.0, -4.0, 1.0])
print(f"L1 노름: {np.linalg.norm(v, ord=1)}") # 8.0
print(f"L2 노름: {np.linalg.norm(v, ord=2)}") # 5.099...
print(f"Linf 노름: {np.linalg.norm(v, ord=np.inf)}") # 4.0
1.2 행렬(Matrix)
행렬 표기법
행렬은 수들을 직사각형 형태로 배열한 것입니다. 행렬은 개의 행(row)과 개의 열(column)을 가집니다.
딥러닝에서 가중치 행렬 는 레이어 간의 선형 변환을 나타냅니다.
행렬 곱셈(Matrix Multiplication)
가 행렬이고 가 행렬일 때, 곱 는 행렬이며:
핵심은 앞 행렬의 열 수 = 뒤 행렬의 행 수가 일치해야 한다는 것입니다. 결과 행렬의 shape은 입니다.
행렬 곱셈은 교환법칙이 성립하지 않습니다: (일반적으로).
전치행렬(Transpose)
행과 열을 교환한 행렬입니다.
성질:
역행렬(Inverse Matrix)
정방행렬 의 역행렬 는 를 만족합니다. 역행렬이 존재하려면 행렬식이 0이 아니어야 합니다.
행렬식(Determinant)
행렬의 행렬식:
행렬식은 해당 행렬이 나타내는 선형 변환이 공간을 얼마나 "늘리거나 줄이는지"를 나타냅니다. 이면 차원이 축소되어 역행렬이 존재하지 않습니다.
랭크(Rank)
행렬의 랭크는 선형 독립인 행(또는 열)의 최대 수입니다. 랭크가 낮을수록 행렬이 나타내는 선형 변환의 출력 공간 차원이 낮습니다. Low-rank approximation은 모델 압축(LoRA 등)의 핵심 개념입니다.
import numpy as np
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
B = np.array([[9, 8, 7],
[6, 5, 4],
[3, 2, 1]])
# 행렬 곱
C = np.dot(A, B)
print("행렬 곱 결과:\n", C)
# 전치행렬
print("A의 전치행렬:\n", A.T)
# 행렬식
A2 = np.array([[3.0, 1.0],
[2.0, 4.0]])
print(f"행렬식: {np.linalg.det(A2):.2f}") # 3*4 - 1*2 = 10
# 역행렬
A_inv = np.linalg.inv(A2)
print("역행렬:\n", A_inv)
print("A * A_inv (항등행렬):\n", np.round(A2 @ A_inv))
# 랭크
print(f"A의 랭크: {np.linalg.matrix_rank(A)}") # 2 (종속 행이 있으므로)
1.3 고유값과 고유벡터(Eigenvalues & Eigenvectors)
정의
정방행렬 에 대해, 다음 방정식을 만족하는 비영 벡터 를 고유벡터(eigenvector), 스칼라 를 **고유값(eigenvalue)**이라 합니다.
이 식의 의미는 강력합니다: 행렬 가 벡터 를 변환할 때, 방향은 변하지 않고 크기만 배 바뀝니다.
고유값 분해(Eigendecomposition)
가 대칭 행렬이면 직교하는 고유벡터들로 분해할 수 있습니다.
여기서 는 고유벡터들을 열로 가지는 직교 행렬이고, 는 고유값들을 대각에 가지는 대각 행렬입니다.
PCA(주성분 분석)와의 관계
데이터 행렬 의 공분산 행렬 의 고유벡터들이 주성분(Principal Components)입니다. 가장 큰 고유값에 대응하는 고유벡터가 데이터의 분산이 가장 큰 방향, 즉 가장 중요한 특징 방향입니다.
SVD(특이값 분해, Singular Value Decomposition)
고유값 분해는 정방행렬에만 적용되지만, SVD는 모든 행렬에 적용됩니다.
- : 직교 행렬 (왼쪽 특이벡터)
- : 대각 행렬 (특이값, 0 이상)
- : 직교 행렬 (오른쪽 특이벡터)
SVD는 행렬 근사, 노이즈 제거, 추천 시스템, 자연어 처리의 LSA 등에 핵심적으로 사용됩니다. LoRA(Low-Rank Adaptation)도 SVD의 저랭크 근사 아이디어를 활용합니다.
import numpy as np
# 고유값과 고유벡터
A = np.array([[4.0, 2.0],
[1.0, 3.0]])
eigenvalues, eigenvectors = np.linalg.eig(A)
print("고유값:", eigenvalues)
print("고유벡터 (열 벡터):\n", eigenvectors)
# 검증: A*v = lambda*v
v0 = eigenvectors[:, 0]
lam0 = eigenvalues[0]
print("A*v:", A @ v0)
print("lambda*v:", lam0 * v0)
# SVD
M = np.array([[1, 2, 3],
[4, 5, 6]])
U, S, Vt = np.linalg.svd(M, full_matrices=False)
print(f"U shape: {U.shape}")
print(f"S (특이값): {S}")
print(f"Vt shape: {Vt.shape}")
# 저랭크 근사 (rank-1 approximation)
rank1_approx = np.outer(S[0] * U[:, 0], Vt[0, :])
print("원본 행렬:\n", M)
print("rank-1 근사:\n", rank1_approx)
# PCA 예제
from numpy.linalg import eig
# 데이터 생성
np.random.seed(42)
data = np.random.randn(100, 2)
data[:, 1] = data[:, 0] * 0.8 + data[:, 1] * 0.2 # 상관관계 추가
# 공분산 행렬
cov = np.cov(data.T)
eigenvals, eigenvecs = eig(cov)
# 첫 번째 주성분
idx = np.argsort(eigenvals)[::-1]
principal_component = eigenvecs[:, idx[0]]
print("첫 번째 주성분:", principal_component)
print("설명 분산 비율:", eigenvals[idx[0]] / eigenvals.sum())
1.4 딥러닝과 선형대수
선형 변환으로서의 레이어
신경망의 완전 연결 레이어(Fully Connected Layer)는 본질적으로 선형 변환입니다.
여기서:
- : 입력 벡터
- : 가중치 행렬
- : 편향 벡터
- : 출력 벡터
여러 레이어를 거치는 것은 연속된 선형 변환입니다. 활성화 함수가 없으면 여러 레이어를 쌓아도 단일 선형 변환과 동일하므로, 비선형 활성화 함수(ReLU, Sigmoid 등)가 필수적입니다.
배치 처리와 행렬 곱셈
개의 샘플을 동시에 처리할 때:
여기서 이고 입니다. GPU는 이런 대규모 행렬 곱셈을 병렬로 처리하는 데 최적화되어 있습니다.
2. 미적분(Calculus)
미적분은 딥러닝에서 "어떻게 배우는가"를 담당합니다. 손실 함수를 최소화하는 경사 하강법은 전적으로 미분에 의존합니다.
2.1 미분(Differentiation)
극한과 미분의 정의
함수 의 에서의 미분은 극한으로 정의됩니다.
이는 에서 함수의 순간 변화율, 즉 그래프의 접선 기울기입니다.
기본 미분 규칙
- 거듭제곱 법칙:
- 합 법칙:
- 곱 법칙:
- 몫 법칙:
- 연쇄 법칙(Chain Rule):
딥러닝에서 자주 쓰이는 함수들의 미분:
- (Sigmoid)
편미분(Partial Derivative)
다변수 함수에서 하나의 변수에 대한 미분입니다. 다른 변수들은 상수로 취급합니다.
예를 들어, 이면:
그래디언트(Gradient)
모든 편미분을 모은 벡터입니다.
그래디언트는 함수값이 가장 빠르게 증가하는 방향을 가리킵니다. 따라서 손실 함수를 최소화하려면 그래디언트의 반대 방향으로 이동합니다(경사 하강법).
야코비안(Jacobian) 행렬
벡터 함수 의 모든 1차 편미분을 담은 행렬입니다.
헤시안(Hessian) 행렬
스칼라 함수의 2차 편미분을 담은 정방 행렬입니다.
헤시안의 고유값은 함수의 곡률을 나타냅니다. 모든 고유값이 양수이면 극소, 모두 음수이면 극대, 혼재하면 안장점(Saddle Point)입니다.
import numpy as np
# 수치 미분 (기울기 확인용)
def numerical_gradient(f, x, h=1e-5):
grad = np.zeros_like(x)
for i in range(len(x)):
x_plus = x.copy()
x_plus[i] += h
x_minus = x.copy()
x_minus[i] -= h
grad[i] = (f(x_plus) - f(x_minus)) / (2 * h)
return grad
# 예제 함수: f(x, y) = x^2 + 2y^2
def f(x):
return x[0]**2 + 2 * x[1]**2
x0 = np.array([3.0, 4.0])
grad = numerical_gradient(f, x0)
print(f"수치 그래디언트: {grad}") # [6.0, 16.0]
print(f"해석적 그래디언트: [{2*x0[0]}, {4*x0[1]}]")
# PyTorch를 이용한 자동 미분
try:
import torch
x = torch.tensor([3.0, 4.0], requires_grad=True)
y = x[0]**2 + 2 * x[1]**2
y.backward()
print(f"PyTorch 그래디언트: {x.grad}")
except ImportError:
print("PyTorch 미설치")
2.2 체인 룰과 역전파(Backpropagation)
체인 룰(Chain Rule)
함수의 합성에서 미분을 계산하는 규칙입니다.
다변수로 확장하면:
역전파 알고리즘
신경망의 훈련에서 손실 함수 을 각 가중치 에 대해 미분해야 합니다. 네트워크가 여러 레이어의 합성 함수이므로, 체인 룰을 반복 적용합니다.
2-레이어 신경망의 예:
을 구하려면:
역전파는 출력에서 입력 방향으로 이 체인을 효율적으로 계산합니다.
import numpy as np
# 간단한 신경망 역전파 예제
class SimpleNet:
def __init__(self):
self.W1 = np.random.randn(4, 3) * 0.1
self.b1 = np.zeros(4)
self.W2 = np.random.randn(2, 4) * 0.1
self.b2 = np.zeros(2)
def relu(self, x):
return np.maximum(0, x)
def relu_grad(self, x):
return (x > 0).astype(float)
def forward(self, x):
self.x = x
self.z1 = self.W1 @ x + self.b1
self.h = self.relu(self.z1)
self.z2 = self.W2 @ self.h + self.b2
return self.z2
def backward(self, dL_dz2):
# 레이어 2 그래디언트
dL_dW2 = np.outer(dL_dz2, self.h)
dL_db2 = dL_dz2
dL_dh = self.W2.T @ dL_dz2
# ReLU 그래디언트
dL_dz1 = dL_dh * self.relu_grad(self.z1)
# 레이어 1 그래디언트
dL_dW1 = np.outer(dL_dz1, self.x)
dL_db1 = dL_dz1
return dL_dW1, dL_db1, dL_dW2, dL_db2
net = SimpleNet()
x = np.array([1.0, 2.0, 3.0])
output = net.forward(x)
dL_dout = np.array([1.0, -1.0]) # 손실의 그래디언트
grads = net.backward(dL_dout)
print(f"W1 그래디언트 shape: {grads[0].shape}")
print(f"W2 그래디언트 shape: {grads[2].shape}")
2.3 최적화 이론
극값 조건
- 1차 조건(필요 조건): 극값에서
- 2차 조건(충분 조건): 헤시안 의 부호로 극소/극대 판단
볼록 함수(Convex Function)
함수 가 볼록이면 두 점의 직선보다 함수값이 항상 낮거나 같습니다.
볼록 함수의 극소는 항상 전역 최솟값입니다. 딥러닝의 손실 함수는 일반적으로 볼록하지 않아서 전역 최솟값을 보장할 수 없습니다.
경사 하강법(Gradient Descent)
여기서 는 학습률(learning rate)입니다. 학습률이 너무 크면 발산하고, 너무 작으면 수렴이 느립니다.
변형들:
- 확률적 경사 하강법(SGD): 매 스텝마다 무작위 샘플 하나(또는 미니배치)를 사용
- 모멘텀(Momentum): 이전 그래디언트 방향을 누적하여 가속
- Adam: 적응적 학습률, 현재 딥러닝에서 가장 널리 쓰임
import numpy as np
import matplotlib.pyplot as plt
# 경사 하강법 구현
def gradient_descent(grad_f, init_x, learning_rate=0.1, steps=100):
x = init_x.copy()
history = [x.copy()]
for _ in range(steps):
grad = grad_f(x)
x -= learning_rate * grad
history.append(x.copy())
return x, np.array(history)
# 예제: f(x, y) = x^2 + 4y^2 최소화
def f(x):
return x[0]**2 + 4 * x[1]**2
def grad_f(x):
return np.array([2*x[0], 8*x[1]])
init_x = np.array([3.0, 3.0])
final_x, history = gradient_descent(grad_f, init_x, learning_rate=0.1, steps=50)
print(f"시작점: {init_x}")
print(f"수렴 지점: {final_x}")
print(f"최솟값: {f(final_x):.6f}")
# Adam 옵티마이저 구현
def adam(grad_f, init_x, alpha=0.01, beta1=0.9, beta2=0.999, eps=1e-8, steps=100):
x = init_x.copy()
m = np.zeros_like(x)
v = np.zeros_like(x)
for t in range(1, steps + 1):
g = grad_f(x)
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * g**2
m_hat = m / (1 - beta1**t)
v_hat = v / (1 - beta2**t)
x -= alpha * m_hat / (np.sqrt(v_hat) + eps)
return x
final_adam = adam(grad_f, init_x)
print(f"Adam 수렴 지점: {final_adam}")
3. 확률과 통계(Probability & Statistics)
확률과 통계는 딥러닝의 불확실성을 다루는 언어입니다. 손실 함수의 설계, 정규화, 모델 평가 모두 확률적 사고에 기반합니다.
3.1 확률 기초
확률 공리(Kolmogorov Axioms)
- (비음성)
- (전체 확률은 1)
- 서로소인 사건들에 대해 (가산 가법성)
조건부 확률
사건 가 발생했다는 조건 하에 사건 의 확률:
베이즈 정리(Bayes' Theorem)
베이즈 정리는 "증거를 바탕으로 믿음을 업데이트"하는 원리입니다.
- : 사전 확률(Prior) - 증거 이전의 믿음
- : 우도(Likelihood) - 가설 A 하에서 증거 B의 확률
- : 사후 확률(Posterior) - 증거를 본 후 업데이트된 믿음
이는 분류 모델 평가, 나이브 베이즈 분류기, 베이지안 신경망 등에 광범위하게 사용됩니다.
확률의 전체 법칙
3.2 확률 분포
이산 확률 분포
베르누이 분포(Bernoulli Distribution)
결과가 0 또는 1인 단일 시행:
,
이항 분포(Binomial Distribution)
번의 베르누이 시행에서 성공 횟수:
포아송 분포(Poisson Distribution)
단위 시간/공간에서 사건 발생 횟수:
연속 확률 분포
균등 분포(Uniform Distribution)
구간 에서 모든 값이 동일한 확률 밀도:
정규 분포(Normal/Gaussian Distribution)
평균 , 표준편차 로 특징지어집니다. 중심 극한 정리(Central Limit Theorem)에 의해 많은 자연 현상이 정규 분포를 따릅니다. 딥러닝에서 가중치 초기화, 노이즈 모델링 등에 자주 사용됩니다.
다변량 정규 분포(Multivariate Normal Distribution)
여기서 는 평균 벡터, 는 공분산 행렬입니다. VAE(Variational Autoencoder)의 잠재 공간 모델링에 핵심적으로 사용됩니다.
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
# 정규 분포
mu, sigma = 0, 1
x = np.linspace(-4, 4, 100)
pdf = stats.norm.pdf(x, mu, sigma)
print(f"P(-1 < X < 1) = {stats.norm.cdf(1) - stats.norm.cdf(-1):.4f}") # 68.27%
print(f"P(-2 < X < 2) = {stats.norm.cdf(2) - stats.norm.cdf(-2):.4f}") # 95.45%
print(f"P(-3 < X < 3) = {stats.norm.cdf(3) - stats.norm.cdf(-3):.4f}") # 99.73%
# 다변량 정규 분포 샘플링
mean = np.array([0, 0])
cov = np.array([[1, 0.7],
[0.7, 1]])
samples = np.random.multivariate_normal(mean, cov, size=1000)
print(f"샘플 형태: {samples.shape}")
print(f"샘플 평균: {samples.mean(axis=0)}")
print(f"샘플 공분산:\n{np.cov(samples.T)}")
# 이항 분포
n, p = 10, 0.5
k = np.arange(0, n+1)
pmf = stats.binom.pmf(k, n, p)
print(f"\n이항 분포 B(10, 0.5):")
print(f"P(X=5) = {stats.binom.pmf(5, n, p):.4f}")
3.3 기댓값과 분산
기댓값(Expected Value)
기댓값의 선형성:
분산(Variance)
표준편차:
공분산(Covariance)
두 확률변수의 선형 관계를 측정합니다.
상관계수(Correlation)
값의 범위는 이며, 크기와 무관하게 선형 관계의 강도만을 측정합니다.
공분산 행렬(Covariance Matrix)
차원 확률벡터의 모든 쌍의 공분산:
PCA의 핵심 입력이며, 데이터의 분산 구조를 담고 있습니다.
3.4 최대우도추정(Maximum Likelihood Estimation, MLE)
우도함수(Likelihood Function)
파라미터 가 주어졌을 때, 관측된 데이터 가 발생할 확률:
MLE는 이 우도를 최대화하는 를 찾습니다.
로그 우도(Log-Likelihood)
곱셈을 덧셈으로 변환하여 계산을 쉽게 합니다.
로그는 단조 증가 함수이므로 로그 우도를 최대화하는 와 우도를 최대화하는 는 동일합니다.
MLE 유도 예제: 정규 분포
데이터 이 를 따른다고 가정할 때:
에 대해 미분하고 0으로 놓으면: (표본 평균)
에 대해 미분하면: (표본 분산)
딥러닝의 Cross-Entropy와 MLE
분류 문제에서 Cross-Entropy 손실을 최소화하는 것은 MLE와 동치입니다.
이는 실제 데이터 분포 하에서 모델의 로그 우도를 최대화하는 것과 같습니다.
import numpy as np
from scipy.optimize import minimize_scalar
# MLE 예제: 베르누이 분포
data = np.array([1, 0, 1, 1, 0, 1, 1, 1, 0, 1]) # 동전 던지기
n = len(data)
k = data.sum()
# 해석적 MLE: p_hat = k/n
p_mle = k / n
print(f"MLE 추정값: p = {p_mle:.2f}") # 0.7
# 로그 우도 함수
def neg_log_likelihood(p):
if p <= 0 or p >= 1:
return np.inf
return -(k * np.log(p) + (n - k) * np.log(1 - p))
result = minimize_scalar(neg_log_likelihood, bounds=(0.01, 0.99), method='bounded')
print(f"수치 최적화 MLE: p = {result.x:.4f}")
# Cross-Entropy 손실 구현
def cross_entropy(y_true, y_pred, eps=1e-15):
y_pred = np.clip(y_pred, eps, 1 - eps)
return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
# 이진 분류 예제
y_true = np.array([1, 0, 1, 1, 0])
y_pred = np.array([0.9, 0.1, 0.8, 0.7, 0.2])
loss = cross_entropy(y_true, y_pred)
print(f"\nCross-Entropy 손실: {loss:.4f}")
3.5 정보 이론(Information Theory)
엔트로피(Entropy)
확률 분포 의 불확실성 또는 정보량:
균등 분포일 때 엔트로피가 최대이고, 하나의 결과만 가능할 때 0입니다.
KL Divergence
두 분포 와 사이의 "거리"(단, 비대칭):
이며 일 때만 0입니다. VAE의 손실 함수, 정책 최적화(PPO 등)에 사용됩니다.
크로스 엔트로피(Cross-Entropy)
실제 분포 와 모델 분포 사이의 크로스 엔트로피:
는 상수이므로 Cross-Entropy를 최소화하는 것은 KL Divergence를 최소화하는 것과 동치입니다.
상호 정보량(Mutual Information)
두 확률변수가 공유하는 정보:
특징 선택, 표현 학습의 이론적 기반이 됩니다.
import numpy as np
def entropy(p):
"""이산 분포의 엔트로피 계산"""
p = np.array(p)
p = p[p > 0] # 0 제거 (0*log(0) = 0으로 취급)
return -np.sum(p * np.log2(p))
def kl_divergence(p, q, eps=1e-15):
"""KL Divergence D_KL(P||Q) 계산"""
p = np.array(p) + eps
q = np.array(q) + eps
p = p / p.sum()
q = q / q.sum()
return np.sum(p * np.log(p / q))
def cross_entropy_distributions(p, q, eps=1e-15):
"""분포 간 Cross-Entropy 계산"""
p = np.array(p)
q = np.array(q) + eps
return -np.sum(p * np.log(q))
# 균등 분포 (최대 엔트로피)
uniform = [0.25, 0.25, 0.25, 0.25]
print(f"균등 분포 엔트로피: {entropy(uniform):.4f} bits") # 2.0 bits
# 집중된 분포 (낮은 엔트로피)
concentrated = [0.97, 0.01, 0.01, 0.01]
print(f"집중 분포 엔트로피: {entropy(concentrated):.4f} bits")
# KL Divergence
true_dist = [0.4, 0.3, 0.2, 0.1]
model_dist = [0.35, 0.35, 0.15, 0.15]
kl = kl_divergence(true_dist, model_dist)
print(f"\nKL Divergence: {kl:.4f}")
# Cross-Entropy = H(P) + KL(P||Q)
ce = cross_entropy_distributions(true_dist, model_dist)
h_p = entropy(true_dist) * np.log(2) # nats로 변환
print(f"Cross-Entropy: {ce:.4f}")
print(f"H(P) + KL: {h_p + kl:.4f}")
4. 수치 해석(Numerical Methods)
딥러닝 구현에서 수학적 이론과 실제 컴퓨터 연산 사이의 간극을 이해하는 것은 중요합니다.
4.1 부동소수점 표현
컴퓨터에서 실수는 부동소수점으로 표현됩니다.
FP32 (단정밀도)
- 32비트: 부호 1비트, 지수 8비트, 가수 23비트
- 정밀도: 약 7자리 십진수
- 범위: 약
FP16 (반정밀도)
- 16비트: 부호 1비트, 지수 5비트, 가수 10비트
- 정밀도: 약 3자리 십진수
- GPU 메모리 절약, 속도 향상 (Mixed Precision Training)
- 단점: 오버플로우/언더플로우 위험
BF16 (Brain Float 16)
- 16비트: 부호 1비트, 지수 8비트, 가수 7비트
- FP32와 같은 지수 범위 (오버플로우 위험 낮음)
- Google TPU에서 도입, 현재 AI 훈련에 널리 사용
import numpy as np
# 부동소수점 정밀도 이슈
a = np.float32(0.1)
b = np.float32(0.2)
print(f"0.1 + 0.2 = {a + b}") # 정확히 0.3이 아닐 수 있음
# 수치 오버플로우
x_large = np.float16(65000)
print(f"FP16 큰 값: {x_large}")
overflow = np.float16(70000)
print(f"FP16 오버플로우: {overflow}") # inf
# numpy dtype 비교
for dtype in [np.float16, np.float32, np.float64]:
info = np.finfo(dtype)
print(f"{dtype.__name__}: max={info.max:.3e}, min_pos={info.tiny:.3e}")
4.2 수치 안정성
Softmax의 수치 불안정성과 해결
Softmax 함수:
큰 값이 입력되면 가 오버플로우를 일으킵니다.
안정적인 Softmax 구현:
수학적으로 동치이지만 수치적으로 훨씬 안정합니다.
import numpy as np
def naive_softmax(x):
"""불안정한 Softmax"""
return np.exp(x) / np.sum(np.exp(x))
def stable_softmax(x):
"""수치적으로 안정한 Softmax"""
x_shifted = x - np.max(x) # 최댓값을 빼서 안정화
return np.exp(x_shifted) / np.sum(np.exp(x_shifted))
# 일반적인 경우
x_normal = np.array([1.0, 2.0, 3.0])
print("일반 입력:")
print(f" Naive: {naive_softmax(x_normal)}")
print(f" Stable: {stable_softmax(x_normal)}")
# 큰 값의 경우
x_large = np.array([1000.0, 2000.0, 3000.0])
print("\n큰 입력:")
try:
result = naive_softmax(x_large)
print(f" Naive: {result}")
except RuntimeWarning as e:
print(f" Naive: 오버플로우 경고!")
print(f" Stable: {stable_softmax(x_large)}")
# Log-Softmax (Cross-Entropy와 함께 사용)
def log_softmax(x):
x_shifted = x - np.max(x)
return x_shifted - np.log(np.sum(np.exp(x_shifted)))
logits = np.array([2.0, 1.0, 0.1])
print(f"\nLog-Softmax: {log_softmax(logits)}")
5. 선형 회귀의 수학적 분석
선형 회귀는 딥러닝의 기본 블록입니다. 수학적으로 철저히 이해하면 더 복잡한 모델을 이해하는 기초가 됩니다.
5.1 최소제곱법(Ordinary Least Squares)
데이터 에 대해 선형 모델 를 학습합니다.
손실 함수(Mean Squared Error):
정규 방정식(Normal Equation)
행렬 형태로 손실 함수를 미분하고 0으로 놓으면:
이것이 최소제곱 해입니다. 행렬 가 가역(invertible)이어야 하며, 이는 특징들이 선형 독립일 때 성립합니다.
5.2 Ridge와 Lasso 회귀
Ridge 회귀(L2 정규화)
정규 방정식:
를 더하면 가 항상 가역이 됩니다. 가중치들이 0에 가깝게 유지되어 과적합을 방지합니다.
Lasso 회귀(L1 정규화)
L1 정규화는 일부 가중치를 정확히 0으로 만드는 희소 해(sparse solution)를 유도합니다. 이는 자동 특징 선택(feature selection)의 효과가 있습니다.
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge, Lasso
# 데이터 생성
np.random.seed(42)
n, d = 100, 10
X = np.random.randn(n, d)
true_w = np.array([1.0, -2.0, 3.0, 0.0, 0.0, 0.0, 0.5, -0.5, 0.0, 0.0])
y = X @ true_w + np.random.randn(n) * 0.5
# OLS (정규 방정식)
X_b = np.column_stack([np.ones(n), X]) # 편향 항 추가
w_ols = np.linalg.lstsq(X_b, y, rcond=None)[0]
print("OLS 계수 (편향 제외):", w_ols[1:].round(2))
# Ridge
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
print("Ridge 계수:", ridge.coef_.round(2))
# Lasso (희소 해)
lasso = Lasso(alpha=0.1)
lasso.fit(X, y)
print("Lasso 계수:", lasso.coef_.round(2))
print("0이 된 계수 수:", (np.abs(lasso.coef_) < 0.01).sum())
# 정규 방정식 직접 구현
def ridge_normal_equation(X, y, lam=1.0):
n, d = X.shape
A = X.T @ X + lam * np.eye(d)
b = X.T @ y
return np.linalg.solve(A, b)
w_ridge = ridge_normal_equation(X, y, lam=1.0)
print("\n정규 방정식 Ridge:", w_ridge.round(2))
5.3 기울기의 기하학적 의미
등고선과 그래디언트의 관계
그래디언트 는 항상 등고선에 수직(직교)합니다. 경사 하강법에서 이 방향의 반대로 이동하면 가장 빠르게 함수값을 줄일 수 있습니다.
정규화의 기하학적 해석
Ridge 회귀는 구 안에서 MSE를 최소화하는 것과 동치입니다. 원형 제약 조건이므로 희소 해를 만들지 않습니다.
Lasso는 다이아몬드 모양 안에서 MSE를 최소화합니다. 다이아몬드의 꼭짓점에서 해가 자주 발생하므로 희소 해가 나타납니다.
6. 종합: 딥러닝 수학 통합
6.1 신경망의 수학적 관점
완전 연결 신경망 개 레이어:
순전파: 선형대수(행렬 곱) + 활성화 함수
손실 계산: 정보 이론(Cross-Entropy) or 통계(MSE)
역전파: 미적분(체인 룰) → 그래디언트 계산
파라미터 업데이트: 최적화 이론(경사 하강법, Adam)
6.2 Attention 메커니즘과 행렬 연산
트랜스포머의 Attention:
- : 쿼리, 키, 값 행렬 (선형대수)
- 스케일링: 수치 안정성 (수치 해석)
- Softmax: 확률 분포 변환 (확률 이론)
- 내적 : 유사도 측정 (벡터 내적)
import numpy as np
def attention(Q, K, V, mask=None):
"""
Scaled Dot-Product Attention
Q, K, V: (seq_len, d_k) 또는 (batch, heads, seq_len, d_k)
"""
d_k = Q.shape[-1]
# 스케일된 내적 유사도
scores = Q @ K.T / np.sqrt(d_k) # (seq_len, seq_len)
# 마스킹 (선택적)
if mask is not None:
scores = np.where(mask, scores, -1e9)
# Softmax로 확률 분포 변환
attention_weights = stable_softmax_2d(scores)
# 가중합
output = attention_weights @ V
return output, attention_weights
def stable_softmax_2d(x):
x_max = x.max(axis=-1, keepdims=True)
x_shifted = x - x_max
exp_x = np.exp(x_shifted)
return exp_x / exp_x.sum(axis=-1, keepdims=True)
# 예제: 3개 토큰, d_k=4
seq_len, d_k = 3, 4
Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_k)
output, weights = attention(Q, K, V)
print(f"Attention 출력 shape: {output.shape}")
print(f"Attention 가중치 (합 = 1):\n{weights.round(3)}")
print(f"각 행의 합: {weights.sum(axis=-1)}")
마무리: 다음 단계
이 가이드에서는 AI/ML의 핵심 수학 기초를 살펴봤습니다. 정리하면:
- 선형대수: 데이터와 모델 파라미터를 표현하고 변환하는 언어
- 미적분: 모델이 "어떻게 배우는가"를 설명하는 도구
- 확률과 통계: 불확실성을 다루고, 손실 함수의 이론적 근거를 제공
- 수치 해석: 이론을 실제 컴퓨터에서 안정적으로 구현하기 위한 지식
다음 학습 단계를 추천합니다:
- Gilbert Strang의 선형대수: MIT OCW에서 무료 제공
- Pattern Recognition and Machine Learning (Bishop): 확률론적 관점의 ML
- Deep Learning (Goodfellow et al.): 딥러닝 이론의 바이블
- 실습: NumPy로 신경망 바닥부터 구현해보기
수학은 이해의 도구입니다. 모든 수식을 외울 필요는 없지만, 각 개념이 왜 존재하는지, 딥러닝의 어느 부분에서 어떻게 작동하는지를 이해하면 훨씬 강력한 실무 능력을 갖출 수 있습니다.
참고 자료
- Gilbert Strang, Introduction to Linear Algebra, 6th Edition
- Ian Goodfellow et al., Deep Learning, MIT Press (2016)
- Christopher Bishop, Pattern Recognition and Machine Learning, Springer (2006)
- 3Blue1Brown YouTube - Essence of Linear Algebra, Essence of Calculus
- fast.ai - Practical Deep Learning for Coders (수학 직관 설명)