미분이란
- 미분(differentiation)은 변수의 움직임에 따른 함수 값의 변화를 측정하기 위한 도구
f′(x)=h→0limhf(x+h)−f(x)
그림으로 이해하는 미분

- 미분은 함수 f의 주어진 점 (x,f(x))에서 접선의 기울기를 구함
- 한 점에서 접선의 기울기를 알면, 어느 방향으로 점을 움직여야 함수값이 증가/감소하는지를 알 수 있음
경사하강법

- 원래의 함수값에 미분값을 더하는/빼는 것을 경사상승법(gradient ascent)/경사하강법(gradient descent)라 하며, 함수의 극대/극소값 위치를 구할 때 사용

- 두 방법 모두 극값에 도달하면 업데이트를 멈춤

코드로 보는 스칼라 경사하강법
Fortran
Input: gradient, init, lr, eps, Output: var
var = init
grad = gradient(var)
while(abs(grad) > eps):
var = var - lr * grad
grad = gradient(var)
- 수치해석을 통한 미분 시 미분값이 정확히 0이 되는 것은 불가능하므로 종료 기준인
eps
를 설정
var = var - lr * grad
에서 업데이트(x−λf′(x))를 진행하며, lr
은 학습률 λ로 미분을 통한 업데이트 속도를 조절
Python
def func(val):
fun = sym.poly(x**2 + 2*x + 3)
return fun.subs(x, val), fun
def func_gradient(fun, val):
_, function = fun(val)
diff = sym.diff(function, x)
return diff.subs(x, val), diff
def gradient_dexcent(fun, init_point, lr = 1e-2, epsilon=1e-5):
cnt = 0
val = init_point
diff, _ = func_gradient(fun, init_point)
while np.abs(diff) > epsilon:
val = val - lr * diff
diff, _ = func_gradient(fun, val)
cnt += 1
print("함수: {}, 연산횟수: {}, 최소점: ({}, {})".format(fun(val)[1], cnt, val, fun(val)[0]))
gradient_descent(fun=func, init_point=np.random.uniform(-2, 2))
변수가 벡터라면?: Gradient 벡터 ∇
- 벡터가 입력인 다변수 함수의 경우 편미분(partial differentiation)을 사용
∂xif(x)=h→0limhf(x+hei)−f(x)
- 각 변수 별로 편미분을 계산한 gradient(기울기) 벡터를 이용하여 경사하강법에 사용
∇f=(∂x1f,∂x2f,…,∂xdf)
- 미분값 f′(x) 대신 벡터 ∇f를 사용하여 변수 x=(x1,…,xd)를 동시에 업데이트 가능

- 이를 공간으로 표현하면, 각 점 (x,y,z) 공간에서 f(x,y) 표면을 따라
−∇f(=∇(−f))벡터를 그릴 때 위와 같이 그릴 수 있음
코드로 보는 벡터 경사하강법
Fortran
Input: gradient, init, lr, eps, Output: var
var = init
grad = gradient(var)
while(abs(grad) > eps):
var = var - lr * grad
grad = gradient(var)
- 모두 동일하나,
abs
가 아닌 norm
으로 계산하여 종료 조건 설정
Python
def eval_(fun, val):
val_x, val_y = val
fun_eval = fun.subs(x, val_x).subs(y, val_y)
return fun_eval
def func_multi(val):
x_, y_ = val
func = sym.poly(x**2 + 2*y**2)
return eval_(func, [x_, y_]), func
def func_gradient(fun, val):
x_, y_ = val
_, function = fun(val)
diff_x = sym.diff(function, x)
diff_y = sym.diff(function, y)
grad_vec = np.array([eval_(diff_x, [x_, y_]), eval_(diff_y, [x_, y_])], dtype=float)
return grad_vec, [diff_x, diff_y]
def gradient_descent(fun, init_point, lr=1e-2, epsilon=1e-5):
cnt = 0
val = init_point
diff, _ = func_gradient(fun, val)
while np.linalg.norm(diff) > epsilon:
val = val - lr * diff
diff, _ = func_gradient(fun, val)
cnt += 1
print("함수: {}, 연산횟수: {}, 최소점: ({}, {})".format(fun(val[1], cnt, val, fun(val)[0]))
pt = [np.random.uniform(-2, 2), np.random.uniform(-2, 2)]
gradient_descent(fun=func_multi, init_point=pt)
경사하강법을 이용한 선형회귀 계수 추정
np.linalg.pinv
를 이용하면 데이터를 선형모델(linear model)로 해석하는 선형회귀식을 찾을 수 있음
- 원래의 식은 Xβ=y^≈y⇒β=X+y=(XTX)−1XTy
- 역행렬 대신 경사하강법으로 선형모델을 찾는다면?
- 선형회귀의 목적함수는 ∥y−Xβ∥2이고, 이를 최소화하는 β를 찾는 것
- 다음과 같은 gradient vector를 구해야 함
∇β∥y−Xβ∥2=(∂β1∥y−Xβ∥2,…,∂βd∥y−Xβ∥2)
- ∥y−Xβ∥2가 아닌 ∥y−Xβ∥22를 최소화해도 됨
- 여기서,
∂βk∥y−Xβ∥2=∂βk{n1i=1∑n(yi−j=1∑dXijβj)2}1/2=−n∥y−Xβ∥2X⋅kT(y−Xβ) 이므로,
- X⋅kT: 행렬 X의 k번째 열벡터를 전치(transpose)시킨 것
- 그레디언트 벡터 ∇β∥y−Xβ∥2은
∇β∥y−Xβ∥2=(−n∥y−Xβ∥2X⋅1T(y−Xβ),…,−n∥y−Xβ∥2X⋅dT(y−Xβ))=−n∥y−Xβ∥2XT(y−Xβ) 가 됨
- 즉, 계산이 복잡해도 사실 Xβ를 계수 β에 대해 미분한 결과에 XT만 곱하는 것
- 결과적으로, 목적함수를 최소화하는 β를 구하는 경사하강 알고리즘은 다음과 같음
β(t+1)←β(t)−λ∇β∥∥∥∥y−Xβ(t)∥∥∥∥=β(t)+nλ∥∥∥y−Xβ(t)∥∥∥XT(y−Xβ(t))
- ∥y−Xβ∥2 대신 ∥y−Xβ∥22를 목적함수로 가정하는 경우 식이 더 간단해짐
β(t+1)←β(t)+n2λXT(y−Xβ(t))
코드 구현
import numpy as np
X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]])
y = np.dot(X, np.array([1, 2])) * 3
beta_gd = [10.1, 15.1, -6.5]
X_ = np.array([np.append(x, [1]) for x in X])
for t in range(5000):
error = y - X_ @ beta_gd
grad = -np.transpose(X_) @ error
beta_gd = beta_gd - 0.01 * grad
print(beta_gd)
확률적 경사하강법 (Stochastic Gradient Descent, SGD)
- 이론적으로, 경사하강법은 (1) 미분 가능하고 (2) 볼록(convex)한 함수에 대해 (3) 적절한 학습률과 학습 횟수를 선택했을 때만 수렴이 보장됨
- 선형회귀의 경우 목적식 ∥y−Xβ∥2은 회귀계수 β에 대해 볼록함수 → 수렴 보장
- 하지만, 비선형회귀의 경우 목적식이 볼록하지 않을 수 있음 → 수렴 보장 안됨

- SGD는 모든 데이터를 사용해 업데이트하는 대신, 데이터 한 개 또는 일부를 활용하여 업데이트
- 볼록이 아닌(non-convex) 목적함수는 SGD를 통해 최적화할 수 있음
θ(t+1)←θ(t)−∇θL(θ(t))(∵E[∇θL]≈∇θL)
- SGD가 만능은 아니지만, 딥러닝 학습의 경우 일반 경사하강법보다 일반적으로 더 낫다고 검증됨
- 데이터의 일부를 이용해 매개변수를 업데이트하기 때문에, 연산자원을 더 효율적으로 활용하는데 도움이 됨
- 전체 데이터(X, y)를 사용하지 않고, 미니배치(X(b), y(b))를 사용하므로 연산량이 b/n으로 감소
미니배치 연산
- 경사하강법은 전체 데이터 D=(X,y)를 가지고 목적함수의 그래디언트 벡터 ∇θL(D,θ)를 계산

- SGD의 경우 미니배치 D(b)=(X(b),y(b))⊂D를 가지고 그래디언트 벡터를 계산

- 미니배치는 확률적으로 임의 선택되기 때문에 목적 함수 모양이 매번 바뀌게 됨
![업로드중..]()
- 볼록이 아닌 목적식에서도 사용이 가능하기 때문에, 일반 경사하강법보다 ML/DL 학습에 효율적
하드웨어 상의 SGD
- 오늘날 모델 학습 데이터는 매우 방대함
![업로드중..]()
- 예를 들어, 256 ⨉ 256 ⨉ 3의 이미지 데이터가 100만 장 존재하는 경우, 그 크기가 약
256×256×3×1,000,000≈237bytes
- 일반 경사하강법처럼 모든 데이터를 업로드하면 메모리가 부족함
- 미니배치로 쪼개서 업로드하는 경우
![업로드중..]()
256×256×3×∣B∣≤218⋅∣B∣bytes 로 줄어듬
- GPU에서 행렬 연산과 모델 매개변수를 업데이트
- CPU는 전처리와 GPU에 업로드할 데이터 준비