Level 2: 수학 기초
📐

Level 2

편미분

AI가 여러 파라미터를 동시에 학습하는 비밀

5분 12초
편미분 강의 영상
강의 영상 보기 (새 탭에서 재생)YouTube

학습 내용

편미분 (Partial Derivatives)

학습 목표

이 레슨을 마치면 여러분은:

  • 편미분이 무엇인지, 왜 필요한지 직관적으로 이해합니다
  • 다른 변수를 상수로 취급하고 하나의 변수만 미분하는 방법을 익힙니다
  • 그래디언트 벡터의 의미와 방향을 파악합니다
  • Python으로 편미분을 수치적으로 계산하고 시각화할 수 있습니다
  • 신경망에서 각 가중치마다 편미분이 필요한 이유를 연결 지어 설명할 수 있습니다

"변수가 여러 개? 한 번에 하나씩! 나머지는 잠시 고정하고, 관심 있는 변수만 살펴보세요."


1. 비유로 시작하기 -- 요리 레시피 조절

편미분을 이해하는 가장 쉬운 방법은 요리를 떠올리는 것입니다.

여러분이 라면을 끓이고 있다고 상상해 보세요. 맛을 결정하는 변수가 두 가지 있습니다:

  • 소금 양 (변수 x)
  • 설탕 양 (변수 y)

"소금을 조금 더 넣으면 맛이 어떻게 변할까?" 이 질문에 답하려면 설탕은 그대로 두고 소금만 바꿔봐야 합니다. 반대로 "설탕을 줄이면?" 이라는 질문에는 소금을 고정하고 설탕만 변화시켜야 하죠.

이것이 바로 편미분의 핵심 아이디어입니다!

상황고정하는 것변화시키는 것관찰하는 것
소금의 영향 확인설탕 양소금 양맛의 변화
설탕의 영향 확인소금 양설탕 양맛의 변화
신경망에서 w1의 영향w2, w3, ...w1손실의 변화

2. 편미분이란?

변수가 여러 개인 함수에서, 하나의 변수에 대해서만 미분하는 것. 나머지 변수는 모두 "상수"로 취급합니다.

기호와 읽는 법

일반 미분은 d를 쓰지만, 편미분은 특별한 기호 **(파셜)**을 씁니다:

기호읽는 법의미
df/dx"df dx"변수가 1개일 때 일반 미분
f/x"파셜 f 파셜 x" 또는 "델 f 델 x"변수가 여러 개일 때 x에 대한 편미분
f/y"파셜 f 파셜 y"y에 대한 편미분

일반 미분 vs 편미분 비교

구분일반 미분 (derivative)편미분 (partial derivative)
변수 개수1개2개 이상
기호df/dxf/x
다른 변수 처리해당 없음상수 취급
비유직선 도로의 경사산의 동쪽/북쪽 경사를 따로 측정

3. 손으로 계산하기 -- 단계별 예제

예제 함수

f(x,y)=x2+2xy+y2f(x, y) = x^2 + 2xy + y^2

x에 대한 편미분 (y는 상수 취급)

각 항을 하나씩 살펴봅시다:

y를 상수로 보고 x에 대해 미분결과
x^2x를 미분하면 2x2x
2xyy는 상수이므로 2y * x를 미분2y
y^2x가 없으므로 상수0

따라서: df/dx = 2x + 2y

y에 대한 편미분 (x는 상수 취급)

x를 상수로 보고 y에 대해 미분결과
x^2y가 없으므로 상수0
2xyx는 상수이므로 2x * y를 미분2x
y^2y를 미분하면 2y2y

따라서: df/dy = 2x + 2y

이 경우 두 편미분이 같게 나왔지만, 이것은 우연입니다. 대부분의 함수에서는 다릅니다!


4. Python으로 편미분 수치 계산하기

수학 공식 없이도 편미분을 계산할 수 있습니다. 아이디어는 간단합니다: "x를 아주 조금 바꿔보고, f가 얼마나 변했는지 확인한다"

이것이 바로 수치 미분입니다. 이전 레슨의 미분과 같은 아이디어이지만, 다른 변수를 고정한다는 차이만 있습니다.

실행해보기: 수치 편미분 계산

python
import numpy as np # 함수 정의: f(x, y) = x^2 + 2xy + y^2 def f(x, y): return x**2 + 2*x*y + y**2 # 수치 편미분 함수 def partial_derivative_x(f, x, y, h=1e-7): """x에 대한 편미분: y는 고정하고 x만 살짝 변화""" return (f(x + h, y) - f(x - h, y)) / (2 * h) def partial_derivative_y(f, x, y, h=1e-7): """y에 대한 편미분: x는 고정하고 y만 살짝 변화""" return (f(x, y + h) - f(x, y - h)) / (2 * h) # 점 (x, y) = (1, 2)에서 편미분 계산 x0, y0 = 1, 2 df_dx = partial_derivative_x(f, x0, y0) df_dy = partial_derivative_y(f, x0, y0) print("=== f(x,y) = x^2 + 2xy + y^2 ===") print(f"점 ({x0}, {y0})에서:") print(f" df/dx (수치 계산) = {df_dx:.6f}") print(f" df/dx (수학 공식 2x+2y) = {2*x0 + 2*y0}") print() print(f" df/dy (수치 계산) = {df_dy:.6f}") print(f" df/dy (수학 공식 2x+2y) = {2*x0 + 2*y0}") print() # 다른 점에서도 확인 print("=== 여러 점에서 편미분 확인 ===") test_points = [(0, 0), (1, 0), (0, 1), (3, -1), (-2, 4)] print(f"{'(x,y)':<12} {'df/dx num':<14} {'df/dx exact':<14} {'df/dy num':<14} {'df/dy exact':<14}") print("-" * 68) for x, y in test_points: num_dx = partial_derivative_x(f, x, y) num_dy = partial_derivative_y(f, x, y) exact_dx = 2*x + 2*y exact_dy = 2*x + 2*y print(f"({x:>2},{y:>2}) {num_dx:<14.6f} {exact_dx:<14} {num_dy:<14.6f} {exact_dy:<14}")

수치 계산 결과와 수학 공식 결과가 거의 완벽하게 일치하는 것을 볼 수 있습니다!


5. 두 번째 예제 -- 편미분이 다른 경우

이번에는 두 편미분이 다른 함수를 살펴봅시다:

g(x,y)=x2y+3xy3g(x, y) = x^2 y + 3x - y^3

  • gx=2xy+3\frac{\partial g}{\partial x} = 2xy + 3 (y는 상수 취급)
  • gy=x23y2\frac{\partial g}{\partial y} = x^2 - 3y^2 (x는 상수 취급)

실행해보기: 비대칭 편미분 확인

python
import numpy as np def g(x, y): return x**2 * y + 3*x - y**3 def partial_x(f, x, y, h=1e-7): return (f(x + h, y) - f(x - h, y)) / (2 * h) def partial_y(f, x, y, h=1e-7): return (f(x, y + h) - f(x, y - h)) / (2 * h) # 점 (2, 1)에서 확인 x0, y0 = 2, 1 print("=== g(x,y) = x^2 * y + 3x - y^3 ===") print(f"점 ({x0}, {y0})에서:") print(f" dg/dx numerical: {partial_x(g, x0, y0):.6f}") print(f" dg/dx formula (2xy + 3): {2*x0*y0 + 3}") print(f" dg/dy numerical: {partial_y(g, x0, y0):.6f}") print(f" dg/dy formula (x^2 - 3y^2): {x0**2 - 3*y0**2}") print() print("이번에는 두 편미분이 다릅니다!") print(f" dg/dx = {2*x0*y0 + 3}, dg/dy = {x0**2 - 3*y0**2}")

6. 그래디언트 벡터 -- 편미분을 모아놓은 화살표

모든 편미분을 하나로 묶으면 그래디언트(gradient) 벡터가 됩니다.

f=(fx,fy)\nabla f = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right)

그래디언트 벡터의 핵심 성질:

성질설명
방향함수값이 가장 빠르게 증가하는 방향
크기그 방향으로의 변화율 (경사의 급함 정도)
반대 방향함수값이 가장 빠르게 감소하는 방향

경사하강법에서는 손실 함수의 그래디언트 반대 방향으로 이동합니다. 산 꼭대기에서 가장 가파른 내리막으로 내려가는 것과 같은 원리입니다!


7. 시각화: 등고선과 그래디언트 방향

그래디언트가 어떤 방향을 가리키는지 눈으로 확인해 봅시다.

실행해보기: 등고선 위의 그래디언트 화살표

python
import numpy as np import matplotlib.pyplot as plt # 함수 정의: f(x,y) = x^2 + y^2 (단순한 그릇 모양) def f(x, y): return x**2 + y**2 # 편미분 (해석적) def grad_f(x, y): return 2*x, 2*y # (df/dx, df/dy) # 격자 생성 x = np.linspace(-3, 3, 200) y = np.linspace(-3, 3, 200) X, Y = np.meshgrid(x, y) Z = f(X, Y) # 그래디언트를 표시할 점들 gx = np.linspace(-2.5, 2.5, 8) gy = np.linspace(-2.5, 2.5, 8) GX, GY = np.meshgrid(gx, gy) GU, GV = grad_f(GX, GY) fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # 왼쪽: 등고선 + 그래디언트 화살표 ax1 = axes[0] contour = ax1.contour(X, Y, Z, levels=15, cmap='coolwarm') ax1.clabel(contour, inline=True, fontsize=8) ax1.quiver(GX, GY, GU, GV, color='black', alpha=0.7, scale=50) ax1.set_title('Contour + Gradient Arrows', fontsize=13) ax1.set_xlabel('x') ax1.set_ylabel('y') ax1.set_aspect('equal') ax1.grid(True, alpha=0.3) # 오른쪽: 등고선 + 반대방향(경사하강 방향) 화살표 ax2 = axes[1] contour2 = ax2.contour(X, Y, Z, levels=15, cmap='coolwarm') ax2.clabel(contour2, inline=True, fontsize=8) ax2.quiver(GX, GY, -GU, -GV, color='red', alpha=0.7, scale=50) ax2.set_title('Contour + Gradient DESCENT Arrows', fontsize=13) ax2.set_xlabel('x') ax2.set_ylabel('y') ax2.set_aspect('equal') ax2.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('gradient_visualization.png', dpi=80, bbox_inches='tight') plt.show() print("왼쪽: 그래디언트 방향 (바깥으로 = 값 증가 방향)") print("오른쪽: 경사하강 방향 (안쪽으로 = 값 감소 방향, 즉 최솟값을 향해!)")

화살표를 관찰해 보세요:

  • 왼쪽 그림: 그래디언트는 등고선에 수직이며, 바깥쪽(값이 커지는 방향)을 가리킵니다
  • 오른쪽 그림: 반대 방향(경사하강 방향)은 중심(최솟값)을 향합니다

8. 3D 표면 위의 그래디언트

더 입체적으로 함수의 모양과 그래디언트를 확인해 봅시다.

실행해보기: 3D 표면 플롯

python
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # 함수: f(x,y) = x^2 + y^2 x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X**2 + Y**2 fig = plt.figure(figsize=(12, 5)) # 왼쪽: 3D 표면 ax1 = fig.add_subplot(121, projection='3d') ax1.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8) ax1.set_title('f(x,y) = x^2 + y^2', fontsize=13) ax1.set_xlabel('x') ax1.set_ylabel('y') ax1.set_zlabel('f(x,y)') # 특정 점 표시 points = [(2, 1), (-1, 2), (0, 0)] for px, py in points: pz = px**2 + py**2 ax1.scatter([px], [py], [pz], color='red', s=80, zorder=5) # 오른쪽: 등고선 + 특정 점의 그래디언트 ax2 = fig.add_subplot(122) contour = ax2.contourf(X, Y, Z, levels=20, cmap='viridis', alpha=0.7) plt.colorbar(contour, ax=ax2, label='f(x,y)') for px, py in points: gx, gy = 2*px, 2*py # 그래디언트 ax2.plot(px, py, 'ro', markersize=8) ax2.annotate('', xy=(px + gx*0.15, py + gy*0.15), xytext=(px, py), arrowprops=dict(arrowstyle='->', color='red', lw=2)) ax2.text(px + 0.1, py + 0.2, f'({px},{py})', fontsize=9, color='white', fontweight='bold') ax2.set_title('Contour + Gradient at Points', fontsize=13) ax2.set_xlabel('x') ax2.set_ylabel('y') ax2.set_aspect('equal') plt.tight_layout() plt.savefig('3d_gradient.png', dpi=80, bbox_inches='tight') plt.show() print("빨간 점에서 빨간 화살표 = 그래디언트 방향 (가장 빠른 증가 방향)") print("(0,0)에서는 그래디언트가 (0,0) = 이미 최솟값!")

9. 신경망과 편미분의 연결

신경망에는 수백만 개의 가중치(파라미터)가 있습니다. 각 가중치가 손실 함수에 미치는 영향을 알려면 각각에 대한 편미분이 필요합니다.

text
손실 L = f(w1, w2, w3, ..., w1000000) dL/dw1 = w1을 바꾸면 손실이 얼마나 변하나? dL/dw2 = w2를 바꾸면 손실이 얼마나 변하나? ... dL/dw1000000 = w1000000을 바꾸면? 이 모든 편미분을 모으면 = 그래디언트 벡터!

실행해보기: 간단한 뉴런의 편미분

python
import numpy as np # 간단한 뉴런: y = w1*x1 + w2*x2 + b # 손실: L = (y - target)^2 # 현재 값 설정 w1, w2, b = 0.5, -0.3, 0.1 x1, x2 = 2.0, 3.0 target = 1.0 # 순전파 y = w1 * x1 + w2 * x2 + b L = (y - target) ** 2 print("=== 간단한 뉴런의 편미분 ===") print(f"입력: x1={x1}, x2={x2}") print(f"가중치: w1={w1}, w2={w2}, b={b}") print(f"출력: y = {w1}*{x1} + {w2}*{x2} + {b} = {y}") print(f"목표: target = {target}") print(f"손실: L = (y - target)^2 = ({y} - {target})^2 = {L}") print() # 수치 편미분 계산 h = 1e-7 # dL/dw1: w1만 살짝 변화 y_plus = (w1 + h) * x1 + w2 * x2 + b y_minus = (w1 - h) * x1 + w2 * x2 + b dL_dw1 = ((y_plus - target)**2 - (y_minus - target)**2) / (2 * h) # dL/dw2: w2만 살짝 변화 y_plus = w1 * x1 + (w2 + h) * x2 + b y_minus = w1 * x1 + (w2 - h) * x2 + b dL_dw2 = ((y_plus - target)**2 - (y_minus - target)**2) / (2 * h) # dL/db: b만 살짝 변화 y_plus = w1 * x1 + w2 * x2 + (b + h) y_minus = w1 * x1 + w2 * x2 + (b - h) dL_db = ((y_plus - target)**2 - (y_minus - target)**2) / (2 * h) # 해석적 편미분으로 검증 # dL/dy = 2(y - target), dy/dw1 = x1, dy/dw2 = x2, dy/db = 1 dL_dy = 2 * (y - target) exact_dL_dw1 = dL_dy * x1 exact_dL_dw2 = dL_dy * x2 exact_dL_db = dL_dy * 1 print("--- 수치 편미분 vs 해석적 편미분 ---") print(f"dL/dw1: 수치={dL_dw1:.6f}, 해석적={exact_dL_dw1:.6f}") print(f"dL/dw2: 수치={dL_dw2:.6f}, 해석적={exact_dL_dw2:.6f}") print(f"dL/db: 수치={dL_db:.6f}, 해석적={exact_dL_db:.6f}") print() # 경사하강법 한 스텝 lr = 0.1 w1_new = w1 - lr * dL_dw1 w2_new = w2 - lr * dL_dw2 b_new = b - lr * dL_db y_new = w1_new * x1 + w2_new * x2 + b_new L_new = (y_new - target) ** 2 print("--- 경사하강법 한 스텝 (학습률=0.1) ---") print(f"w1: {w1:.4f} -> {w1_new:.4f}") print(f"w2: {w2:.4f} -> {w2_new:.4f}") print(f"b: {b:.4f} -> {b_new:.4f}") print(f"손실: {L:.6f} -> {L_new:.6f}") if L > 0: print(f"손실이 {((L - L_new) / L * 100):.1f}% 감소했습니다!")

보세요! 각 가중치에 대한 편미분을 계산하고, 그 반대 방향으로 조금씩 이동하면 손실이 줄어듭니다. 이것이 바로 신경망 학습의 핵심입니다.


10. 핵심 요약

개념설명비유
편미분한 변수만 미분, 나머지 고정소금만 조절하고 설탕은 고정
기호 df/dxx에 대한 편미분"x 방향의 경사"
그래디언트모든 편미분을 모은 벡터가장 가파른 오르막 방향의 화살표
그래디언트 반대가장 빠르게 감소하는 방향가장 가파른 내리막 방향
신경망에서각 가중치마다 편미분 계산각 재료를 따로 조절해서 최적의 맛 찾기

학습 체크리스트

  • 편미분이 "한 변수에 대해서만 미분하고 나머지는 상수 취급"임을 안다
  • f(x,y) = x^2 + 2xy + y^2의 편미분을 손으로 계산할 수 있다
  • 그래디언트가 "가장 빠르게 증가하는 방향"임을 안다
  • 경사하강법이 그래디언트의 반대 방향으로 가는 이유를 설명할 수 있다
  • Python으로 수치 편미분을 계산하는 방법을 이해한다
  • 신경망에서 각 가중치마다 편미분이 필요한 이유를 안다

다음 강의 예고

"연쇄법칙 (Chain Rule)" 합성함수의 미분! 딥러닝 역전파의 핵심 원리를 배웁니다.

레슨 정보

레벨
Level 2: 수학 기초
예상 소요 시간
5분 12초
참고 영상
YouTube 링크

💡실습 환경 안내

코드 블록의 ▶ 실행 버튼을 누르면 브라우저에서 바로 Python을 실행할 수 있습니다.

별도 설치 없이 NumPy, Matplotlib 등 기본 라이브러리를 사용할 수 있습니다.