
학습 내용
편미분 (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/dx | f/x |
| 다른 변수 처리 | 해당 없음 | 상수 취급 |
| 비유 | 직선 도로의 경사 | 산의 동쪽/북쪽 경사를 따로 측정 |
3. 손으로 계산하기 -- 단계별 예제
예제 함수
x에 대한 편미분 (y는 상수 취급)
각 항을 하나씩 살펴봅시다:
| 항 | y를 상수로 보고 x에 대해 미분 | 결과 |
|---|---|---|
| x^2 | x를 미분하면 2x | 2x |
| 2xy | y는 상수이므로 2y * x를 미분 | 2y |
| y^2 | x가 없으므로 상수 | 0 |
따라서: df/dx = 2x + 2y
y에 대한 편미분 (x는 상수 취급)
| 항 | x를 상수로 보고 y에 대해 미분 | 결과 |
|---|---|---|
| x^2 | y가 없으므로 상수 | 0 |
| 2xy | x는 상수이므로 2x * y를 미분 | 2x |
| y^2 | y를 미분하면 2y | 2y |
따라서: df/dy = 2x + 2y
이 경우 두 편미분이 같게 나왔지만, 이것은 우연입니다. 대부분의 함수에서는 다릅니다!
4. Python으로 편미분 수치 계산하기
수학 공식 없이도 편미분을 계산할 수 있습니다. 아이디어는 간단합니다: "x를 아주 조금 바꿔보고, f가 얼마나 변했는지 확인한다"
이것이 바로 수치 미분입니다. 이전 레슨의 미분과 같은 아이디어이지만, 다른 변수를 고정한다는 차이만 있습니다.
실행해보기: 수치 편미분 계산
pythonimport 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. 두 번째 예제 -- 편미분이 다른 경우
이번에는 두 편미분이 다른 함수를 살펴봅시다:
- • (y는 상수 취급)
- • (x는 상수 취급)
실행해보기: 비대칭 편미분 확인
pythonimport 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) 벡터가 됩니다.
그래디언트 벡터의 핵심 성질:
| 성질 | 설명 |
|---|---|
| 방향 | 함수값이 가장 빠르게 증가하는 방향 |
| 크기 | 그 방향으로의 변화율 (경사의 급함 정도) |
| 반대 방향 | 함수값이 가장 빠르게 감소하는 방향 |
경사하강법에서는 손실 함수의 그래디언트 반대 방향으로 이동합니다. 산 꼭대기에서 가장 가파른 내리막으로 내려가는 것과 같은 원리입니다!
7. 시각화: 등고선과 그래디언트 방향
그래디언트가 어떤 방향을 가리키는지 눈으로 확인해 봅시다.
실행해보기: 등고선 위의 그래디언트 화살표
pythonimport 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 표면 플롯
pythonimport 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을 바꾸면? 이 모든 편미분을 모으면 = 그래디언트 벡터!
실행해보기: 간단한 뉴런의 편미분
pythonimport 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/dx | x에 대한 편미분 | "x 방향의 경사" |
| 그래디언트 | 모든 편미분을 모은 벡터 | 가장 가파른 오르막 방향의 화살표 |
| 그래디언트 반대 | 가장 빠르게 감소하는 방향 | 가장 가파른 내리막 방향 |
| 신경망에서 | 각 가중치마다 편미분 계산 | 각 재료를 따로 조절해서 최적의 맛 찾기 |
학습 체크리스트
- • 편미분이 "한 변수에 대해서만 미분하고 나머지는 상수 취급"임을 안다
- • f(x,y) = x^2 + 2xy + y^2의 편미분을 손으로 계산할 수 있다
- • 그래디언트가 "가장 빠르게 증가하는 방향"임을 안다
- • 경사하강법이 그래디언트의 반대 방향으로 가는 이유를 설명할 수 있다
- • Python으로 수치 편미분을 계산하는 방법을 이해한다
- • 신경망에서 각 가중치마다 편미분이 필요한 이유를 안다
다음 강의 예고
"연쇄법칙 (Chain Rule)" 합성함수의 미분! 딥러닝 역전파의 핵심 원리를 배웁니다.
레슨 정보
- 레벨
- Level 2: 수학 기초
- 예상 소요 시간
- 5분 12초
- 참고 영상
- YouTube 링크
💡실습 환경 안내
코드 블록의 ▶ 실행 버튼을 누르면 브라우저에서 바로 Python을 실행할 수 있습니다.
별도 설치 없이 NumPy, Matplotlib 등 기본 라이브러리를 사용할 수 있습니다.