# 자동미분(Automatic Differentiation)과의 비교

## 수치 미분의 기본 개념

수치 미분은 함수의 기울기를 근사적으로 계산하는 방법입니다. 주로 실험적인 데이터나 해석적인 미분이 어려운 함수에 대해 사용됩니다. 수치 미분은 주어진 함수 $f(x)$의 미분값을 근사적으로 구하는데 사용되며, 이때 주요하게 활용되는 방법은 차분 방법입니다.

차분 방법에서는 함수 값을 적당한 간격으로 차이를 계산하여 미분을 근사합니다. 가장 기본적인 차분 방법은 전방 차분, 후방 차분, 그리고 중앙 차분입니다. 각 방법은 미분값을 구할 때 사용하는 근사 방식에서 차이를 보입니다.

전방 차분은 다음과 같이 정의됩니다.

$$
f'(x) \approx \frac{f(x + h) - f(x)}{h}
$$

후방 차분은 다음과 같이 정의됩니다.

$$
f'(x) \approx \frac{f(x) - f(x - h)}{h}
$$

중앙 차분은 다음과 같이 정의됩니다.

$$
f'(x) \approx \frac{f(x + h) - f(x - h)}{2h}
$$

여기서 $h$는 작은 값으로, 미분을 근사하는 간격입니다. $h$의 크기에 따라 근사의 정확도가 달라지며, $h$가 너무 작거나 크면 오차가 발생할 수 있습니다.

수치 미분의 정확도는 차분 방법의 선택에 따라 달라지며, 중앙 차분이 일반적으로 더 높은 정확도를 제공합니다.

## 자동미분의 기본 개념

자동미분(Automatic Differentiation, AD)은 함수의 미분을 효율적으로 계산하는 기술입니다. 이는 수치 미분과는 다르게, 함수의 표현을 기반으로 미분값을 자동으로 계산하는 방식입니다. 자동미분은 수치 미분에서 발생할 수 있는 오차를 최소화하고, 더욱 정확한 미분 값을 제공합니다.

자동미분은 두 가지 주요 방식인 \*\*전방 방식(forward mode)\*\*과 \*\*역방향 방식(reverse mode)\*\*으로 구분됩니다.

### 전방 방식 (Forward Mode)

전방 방식에서는 각 입력에 대한 미분을 직접 계산합니다. 주어진 함수가 $f(x)$라면, 입력 $x$에 대한 미분값을 순차적으로 계산하며, 그 결과를 전달합니다. 이 방식은 함수가 여러 개의 입력을 가질 때 효율적입니다.

전방 방식의 장점은 입력이 많고 출력이 적을 때 더욱 효과적이라는 점입니다. 하지만 함수가 여러 개의 출력을 가지면, 계산이 복잡해질 수 있습니다.

### 역방향 방식 (Reverse Mode)

역방향 방식은 출력을 기준으로 미분을 계산합니다. 주어진 함수에서 각 출력에 대해 미분을 계산하고, 그 미분값을 입력으로 전달하여 최종적으로 입력에 대한 미분을 구합니다. 이 방식은 주로 함수의 출력이 많고 입력이 적을 때 유리합니다.

역방향 방식은 매우 효율적으로 미분을 계산할 수 있지만, 큰 메모리 용량을 요구할 수 있습니다. 이 방식은 딥러닝 모델의 학습에 자주 사용됩니다.

## 수치 미분과 자동미분의 비교

수치 미분과 자동미분은 모두 함수의 미분을 구하는 방법이지만, 그 접근 방식과 장단점에서 차이가 있습니다.

### 정확도와 오차

수치 미분은 근사 방법이기 때문에, 오차가 발생할 수 있습니다. 특히, 차분 방법에서 간격 $h$의 선택이 중요합니다. $h$가 너무 작으면 부동소수점 오차가 발생할 수 있으며, 너무 크면 근사의 정확도가 떨어집니다.

반면, 자동미분은 함수의 수학적 표현을 기반으로 미분을 계산하므로, 오차가 거의 발생하지 않으며, 정확도가 높습니다. 특히, 자동미분은 고차 미분을 효율적으로 계산할 수 있어, 복잡한 모델에서도 미분을 정확하게 계산할 수 있습니다.

### 계산 비용

수치 미분은 간단한 계산을 통해 미분을 구할 수 있지만, 계산 비용이 크고, 여러 번 함수 값을 계산해야 하기 때문에 비효율적일 수 있습니다. 특히, 차분 방법에서 $h$ 값을 변화시켜가며 반복적인 계산을 수행해야 하므로 시간과 메모리 소비가 많습니다.

자동미분은 계산을 효율적으로 처리할 수 있으며, 특히 역방향 방식에서는 출력에 대한 미분을 계산한 후 입력에 대한 미분을 한 번에 처리할 수 있어 계산 비용이 상대적으로 적습니다. 따라서 자동미분은 대규모 문제에서 효율적으로 사용할 수 있습니다.

### 고차 미분

고차 미분을 구할 때, 수치 미분은 계산이 복잡하고 오차가 커질 수 있습니다. 예를 들어, 두 번째 미분을 계산하려면 수치 미분에서 중앙 차분을 두 번 적용해야 하므로 계산 비용이 크게 증가합니다.

반면, 자동미분은 고차 미분을 매우 효율적으로 계산할 수 있습니다. 자동미분의 역방향 방식에서는, 중간 결과를 저장하고 이를 활용하여 고차 미분을 계산할 수 있습니다. 따라서 자동미분은 고차 미분이 중요한 문제에서 매우 유용합니다.

### 구현의 용이성

수치 미분은 알고리즘이 간단하고, 직접 구현하기 쉬운 장점이 있습니다. 함수에 대한 정보를 알 필요 없이, 주어진 함수 값을 계산하여 미분을 근사할 수 있습니다.

자동미분은 복잡한 구현을 요구할 수 있습니다. 특히, 자동미분을 구현하려면, 함수의 수학적 구조를 잘 이해하고 있어야 하며, 이를 처리할 수 있는 도구(예: TensorFlow, PyTorch, JAX 등)가 필요합니다. 이러한 도구들은 자동미분을 구현할 때 필요한 복잡한 세부 사항을 처리해주지만, 내부적으로는 수학적 복잡성을 다룹니다.

## 수치 미분과 자동미분의 비교 테이블

| 항목       | 수치 미분                 | 자동미분                   |
| -------- | --------------------- | ---------------------- |
| 정확도      | 근사값을 계산하므로 오차가 발생 가능  | 수학적 미분을 그대로 계산하여 오차 없음 |
| 계산 비용    | 함수 호출이 여러 번 필요하며 비효율적 | 계산 효율적, 특히 고차 미분에 강함   |
| 고차 미분 처리 | 복잡하고 오차가 커질 수 있음      | 고차 미분 계산에 유리           |
| 구현 용이성   | 간단한 구현 가능             | 구현 복잡, 외부 라이브러리 필요     |
| 사용 분야    | 간단한 문제에서 유용           | 대규모 데이터, 복잡한 모델에서 유리   |

이 표는 수치 미분과 자동미분의 주요 차이점을 간략하게 비교한 것입니다. 각 방법은 문제의 특성과 요구에 따라 선택적으로 사용할 수 있습니다.

## 수치 미분과 자동미분의 적용 예시

### 예시 1: 수치 미분을 통한 함수의 미분 계산

수치 미분을 사용하여 간단한 함수의 미분을 계산해 보겠습니다. 함수 $f(x) = x^2 + 3x + 2$에 대해 수치 미분을 적용하여 그 미분값을 근사적으로 구해보겠습니다.

우리는 중앙 차분 방법을 사용하여 이 함수를 미분합니다. 중앙 차분 방법은 다음과 같이 정의됩니다.

f′(x)≈f(x+h)−f(x−h)2hf'(x) \approx \frac{f(x + h) - f(x - h)}{2h}

이 방법을 사용하여 함수 $f(x) = x^2 + 3x + 2$에 대한 미분을 근사적으로 계산할 수 있습니다. 이 함수의 실제 미분값은 $f'(x) = 2x + 3$입니다.

다음은 Octave 코드 예시입니다.

```octave
% 함수 정의
f = @(x) x.^2 + 3*x + 2;

% 중앙 차분 방법을 이용한 미분 근사
h = 1e-5;  % 작은 간격
x = 1;     % 미분을 계산할 x 값

% 중앙 차분
df_approx = (f(x + h) - f(x - h)) / (2 * h);
disp(['근사된 미분값: ', num2str(df_approx)]);
```

위의 Octave 코드에서, `f(x)`는 주어진 함수이고, `h`는 미분 간격을 나타냅니다. `x` 값에 대해 중앙 차분을 사용하여 미분값을 근사적으로 계산합니다. 출력은 해당 $x$ 값에서의 근사된 미분값입니다.

### 예시 2: 자동미분을 통한 함수의 미분 계산

자동미분을 이용하여 동일한 함수 $f(x) = x^2 + 3x + 2$의 미분을 계산해 보겠습니다. 자동미분은 함수의 수학적 표현을 기반으로 미분을 직접 계산하는 방식입니다. 이 예시에서는 Python의 `autograd` 라이브러리를 사용하여 자동미분을 구현합니다.

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x):
    return x**2 + 3*x + 2

# 미분 함수 생성
grad_f = grad(f)

# 미분값 계산
x = 1.0
df_approx = grad_f(x)
print(f"자동미분을 이용한 미분값: {df_approx}")
```

위의 Python 코드에서는 `autograd` 라이브러리의 `grad` 함수를 사용하여 $f(x) = x^2 + 3x + 2$의 미분을 자동으로 계산합니다. 이때 `grad_f(x)`는 함수 $f(x)$에 대한 미분값을 반환합니다. 자동미분은 정확한 미분값을 제공합니다.

### 수치 미분과 자동미분의 성능 비교

#### 예시 3: 고차 미분 계산

수치 미분에서는 고차 미분을 계산할 때마다 추가적인 계산이 필요하고, 오차가 누적되는 문제가 발생할 수 있습니다. 반면, 자동미분은 고차 미분을 효율적으로 계산할 수 있습니다.

예를 들어, $f(x) = x^3 + 2x^2 + x + 1$에 대해 두 번째 미분을 계산해보겠습니다.

1. 수치 미분으로 두 번째 미분을 계산하기 위해 중앙 차분을 두 번 적용합니다.

$$
f''(x) \approx \frac{f'(x + h) - f'(x - h)}{2h}
$$

1. 자동미분을 사용하면, 두 번째 미분을 바로 계산할 수 있습니다.

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x):
    return x**3 + 2*x**2 + x + 1

# 미분 함수 생성
grad_f = grad(f)        # 첫 번째 미분
grad_f2 = grad(grad_f)  # 두 번째 미분

# 두 번째 미분값 계산
x = 1.0
df2_approx = grad_f2(x)
print(f"자동미분을 이용한 두 번째 미분값: {df2_approx}")
```

위의 Python 코드에서는 `grad_f2`를 사용하여 함수의 두 번째 미분값을 바로 계산합니다. 자동미분에서는 고차 미분을 한 번에 효율적으로 처리할 수 있습니다.

## 자동미분의 장점

자동미분의 주요 장점은 **정확성**과 **효율성**입니다. 특히 복잡한 함수나 다변량 함수에 대해 자동미분은 높은 정확도를 유지하면서 미분을 계산할 수 있습니다. 고차 미분의 경우에도, 자동미분은 적은 비용으로 정확한 결과를 제공합니다.

수치 미분은 함수의 값들을 반복적으로 계산해야 하기 때문에 계산 비용이 증가하며, 작은 $h$ 값을 선택할 때 발생하는 부동소수점 오차도 문제를 일으킬 수 있습니다. 반면, 자동미분은 함수의 수학적 구조를 바탕으로 미분을 직접적으로 계산하므로, 계산 비용이 상대적으로 적고 정확도가 높습니다.

### 예시 4: 다변량 함수에 대한 미분 계산

자동미분은 다변량 함수에 대해서도 효율적으로 미분을 계산할 수 있습니다. 예를 들어, 두 변수 $x\_1$과 $x\_2$에 대한 함수 $f(x\_1, x\_2) = x\_1^2 + x\_2^3 + x\_1x\_2$에 대해 두 변수에 대한 편미분을 자동미분으로 계산해 보겠습니다.

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x1, x2):
    return x1**2 + x2**3 + x1*x2

# 미분 함수 생성
grad_f_x1 = grad(f, 0)  # x1에 대한 편미분
grad_f_x2 = grad(f, 1)  # x2에 대한 편미분

# 편미분값 계산
x1, x2 = 1.0, 2.0
df_x1 = grad_f_x1(x1, x2)
df_x2 = grad_f_x2(x1, x2)
print(f"x1에 대한 편미분값: {df_x1}")
print(f"x2에 대한 편미분값: {df_x2}")
```

위의 Python 코드에서는 `grad(f, 0)`과 `grad(f, 1)`을 사용하여 각각 $x\_1$과 $x\_2$에 대한 편미분을 계산합니다. 자동미분을 통해 다변량 함수의 미분도 정확하고 효율적으로 처리할 수 있습니다.

## 수치 미분과 자동미분의 결합

수치 미분과 자동미분은 독립적으로 사용할 수 있지만, 두 방법을 결합하면 더 강력한 계산 도구가 될 수 있습니다. 예를 들어, 자동미분을 사용하여 함수의 미분을 계산하고, 수치 미분을 사용하여 자동미분의 결과가 정확한지 확인하는 방법이 있을 수 있습니다. 이를 통해 계산 결과의 신뢰성을 높일 수 있습니다.

### 예시 5: 자동미분과 수치 미분의 결합

함수 $f(x) = x^3 + 2x^2 + x + 1$에 대해 자동미분과 수치 미분을 결합하여 첫 번째 미분을 계산하고 그 결과를 비교해 보겠습니다. 수치 미분의 중앙 차분 방법과 자동미분을 사용하여 동일한 미분값을 계산하고, 그 결과의 차이를 확인합니다.

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x):
    return x**3 + 2*x**2 + x + 1

# 미분 함수 생성
grad_f = grad(f)  # 첫 번째 미분

# 수치 미분: 중앙 차분
def central_difference(f, x, h=1e-5):
    return (f(x + h) - f(x - h)) / (2 * h)

# 계산
x = 1.0
df_approx = central_difference(f, x)
df_auto = grad_f(x)

print(f"수치 미분을 이용한 미분값: {df_approx}")
print(f"자동미분을 이용한 미분값: {df_auto}")
```

이 예제에서는 `central_difference` 함수를 사용하여 수치 미분을 계산하고, `grad_f(x)`로 자동미분을 계산합니다. 출력된 두 미분값은 매우 근접하게 나오며, 이는 자동미분의 정확성을 확인하는 데 도움이 됩니다.

### 수치 미분과 자동미분의 정확도 차이

#### 예시 6: 고차원 함수에서의 비교

고차원 함수에서는 수치 미분이 자동미분보다 더 큰 오차를 발생시킬 수 있습니다. 예를 들어, 다변량 함수 $f(x\_1, x\_2, x\_3) = x\_1^2 + x\_2^3 + x\_3 + x\_1x\_2x\_3$의 편미분을 계산한다고 가정해 봅시다. 이 경우, 자동미분은 매우 정확한 결과를 제공하며, 수치 미분은 근사값에 의존하므로 오차가 누적될 수 있습니다.

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x1, x2, x3):
    return x1**2 + x2**3 + x3 + x1*x2*x3

# 미분 함수 생성
grad_f_x1 = grad(f, 0)  # x1에 대한 편미분
grad_f_x2 = grad(f, 1)  # x2에 대한 편미분
grad_f_x3 = grad(f, 2)  # x3에 대한 편미분

# 계산
x1, x2, x3 = 1.0, 2.0, 3.0
df_x1 = grad_f_x1(x1, x2, x3)
df_x2 = grad_f_x2(x1, x2, x3)
df_x3 = grad_f_x3(x1, x2, x3)

print(f"x1에 대한 편미분값: {df_x1}")
print(f"x2에 대한 편미분값: {df_x2}")
print(f"x3에 대한 편미분값: {df_x3}")
```

자동미분은 다변량 함수에 대해 정확한 편미분 값을 계산하는 데 유리하며, 수치 미분은 특히 고차원 문제에서 그 오차가 커질 수 있습니다. 이로 인해 다변량 함수에 대해 자동미분이 더욱 적합하다는 점을 알 수 있습니다.

### 수치 미분에서의 오차 문제

수치 미분에서 $h$ 값을 너무 작게 설정하면, 부동소수점 오차가 발생할 수 있습니다. 반대로, 너무 큰 $h$ 값을 사용하면, 근사값의 정확도가 떨어집니다. 이러한 오차는 함수의 미분값에 영향을 미칠 수 있습니다.

#### 예시 7: $h$ 값에 따른 오차 분석

다음은 함수 $f(x) = e^x$에 대해 수치 미분을 사용할 때, $h$ 값을 변화시켰을 때 오차가 어떻게 변하는지를 보여주는 예시입니다.

```python
import numpy as np

# 함수 정의
def f(x):
    return np.exp(x)

# 수치 미분: 중앙 차분
def central_difference(f, x, h=1e-5):
    return (f(x + h) - f(x - h)) / (2 * h)

# 정확한 미분값
exact_derivative = np.exp(1)  # f'(1) = e^1

# 다양한 h 값에 대해 오차 분석
h_values = [1e-1, 1e-2, 1e-3, 1e-4, 1e-5]
errors = []

for h in h_values:
    df_approx = central_difference(f, 1, h)
    error = abs(df_approx - exact_derivative)
    errors.append(error)

# 결과 출력
for h, error in zip(h_values, errors):
    print(f"h = {h}, 오차 = {error}")
```

위의 코드에서, `central_difference`를 사용하여 다양한 $h$ 값에 대해 수치 미분을 계산하고, 각 $h$ 값에 대해 발생하는 오차를 출력합니다. 이 예시에서는 오차가 $h$ 값에 따라 어떻게 변화하는지 알 수 있습니다. 작은 $h$ 값을 사용할 때 과도한 부동소수점 오차가 발생하며, 적당한 $h$ 값을 선택하는 것이 중요합니다.

## 자동미분의 활용

자동미분은 특히 딥러닝, 최적화 문제, 물리학 시뮬레이션 등 다양한 분야에서 강력하게 활용됩니다. 자동미분을 사용하면 함수의 미분을 명시적으로 정의할 필요 없이, 함수의 그래디언트를 빠르고 정확하게 계산할 수 있습니다. 이는 파라미터 최적화 문제에서 중요한 역할을 합니다.

자동미분을 사용하는 주요 예시는 **신경망 학습**입니다. 신경망에서는 손실 함수에 대해 각 가중치에 대한 기울기(그래디언트)를 계산하고 이를 기반으로 가중치를 업데이트합니다. 이 과정에서 자동미분은 기울기 계산을 매우 효율적으로 처리합니다.

## 자동미분과 딥러닝

딥러닝에서는 신경망의 파라미터(가중치와 편향)를 최적화하기 위해 손실 함수의 미분을 계산해야 합니다. 이때 자동미분은 필수적인 역할을 하며, 신경망 훈련에서의 효율성 향상에 크게 기여합니다. 자동미분은 역전파(backpropagation) 알고리즘을 통해 미분을 계산하고, 이 값을 사용해 파라미터를 갱신하는 방식입니다.

### 자동미분의 활용 예시: 신경망 훈련

신경망에서는 입력 값과 가중치를 기반으로 출력값을 계산하고, 이를 손실 함수와 비교하여 오차를 구합니다. 그 후, 자동미분을 통해 오차에 대한 미분 값을 계산하고, 이를 바탕으로 가중치를 업데이트합니다. 이 과정은 `경사 하강법(Gradient Descent)` 또는 그 변형 알고리즘을 사용하여 파라미터를 최적화하는 데 사용됩니다.

#### 예시 8: 역전파 알고리즘을 사용한 자동미분

다음은 파이썬의 `autograd` 라이브러리를 사용하여 간단한 2층 신경망을 구현한 예시입니다. 이 예시에서는 입력과 출력, 가중치 및 손실 함수에 대해 자동미분을 계산하고, 역전파 알고리즘을 통해 가중치를 업데이트합니다.

```python
import autograd.numpy as np
from autograd import grad

# 활성화 함수 (시그모이드 함수)
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# 손실 함수: 평균 제곱 오차
def loss_function(weights, X, y):
    z = np.dot(X, weights)  # 가중합
    y_pred = sigmoid(z)     # 예측값
    loss = np.mean((y - y_pred) ** 2)  # MSE
    return loss

# 입력 데이터 및 타겟
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])  # XOR 문제
y = np.array([[0], [1], [1], [0]])

# 가중치 초기화
weights = np.random.randn(2, 1)

# 자동미분 함수 생성
grad_loss = grad(loss_function)  # 손실 함수에 대한 미분 생성

# 경사 하강법을 사용하여 가중치 업데이트
learning_rate = 0.1
for epoch in range(1000):
    grad_w = grad_loss(weights, X, y)  # 손실 함수에 대한 가중치 기울기 계산
    weights -= learning_rate * grad_w  # 가중치 업데이트

    if epoch % 100 == 0:
        current_loss = loss_function(weights, X, y)
        print(f"Epoch {epoch}: Loss = {current_loss}")
```

위 코드에서 `grad_loss`는 손실 함수에 대한 가중치의 기울기를 자동으로 계산해 주며, 경사 하강법을 통해 가중치를 갱신합니다. 이처럼 자동미분을 사용하면 신경망의 학습 과정에서 미분을 명시적으로 계산할 필요 없이 효율적으로 파라미터를 최적화할 수 있습니다.

## 자동미분의 장점과 한계

### 장점

1. **효율성**: 자동미분은 다단계 함수의 미분을 매우 효율적으로 계산할 수 있습니다. 이를 통해 복잡한 함수의 미분을 자동으로 처리할 수 있으며, 이를 신경망 훈련, 최적화 문제 등에 적용할 수 있습니다.
2. **정확성**: 자동미분은 정확한 미분을 제공합니다. 수치 미분의 경우 발생할 수 있는 근사오차를 방지할 수 있습니다.
3. **유연성**: 자동미분은 함수가 복잡하고 다단계인 경우에도 정확하게 처리할 수 있으며, 코드에서의 명시적인 미분 계산을 제거할 수 있습니다.

### 한계

1. **메모리 사용**: 자동미분은 중간 결과를 저장하는 과정에서 많은 메모리를 사용할 수 있습니다. 특히 큰 신경망 모델이나 고차원 함수에서는 메모리 부족 문제를 일으킬 수 있습니다.
2. **속도**: 자동미분은 효율적이지만, 수치 미분에 비해 계산 시간이 다소 더 걸릴 수 있습니다. 특히 작은 함수에서 수치 미분을 사용하면 더 빠른 결과를 얻을 수 있습니다.
3. **복잡성**: 매우 복잡한 함수에서 자동미분이 제대로 작동하지 않는 경우가 있을 수 있습니다. 이런 경우, 수치 미분이나 다른 방법을 사용하는 것이 필요할 수 있습니다.

## 자동미분의 최적화

자동미분은 최적화 과정에서 매우 중요한 역할을 하며, 특히 \*\*경사 하강법(Gradient Descent)\*\*과 같은 최적화 알고리즘에서 많이 사용됩니다. 최적화 문제에서는 함수의 기울기(그래디언트)를 계산하여 파라미터를 업데이트하는 방식으로 작동합니다.

경사 하강법은 손실 함수의 기울기를 따라 가며 파라미터를 조정하는 방법입니다. 이때 자동미분을 활용하면 각 파라미터에 대한 기울기를 쉽게 구할 수 있습니다.

### 예시 9: 자동미분을 활용한 경사 하강법 최적화

```python
import autograd.numpy as np
from autograd import grad

# 함수 정의
def f(x):
    return x**2 + 2*x + 1

# 함수의 기울기 계산 (미분)
grad_f = grad(f)

# 초기값 설정
x = 5.0
learning_rate = 0.1

# 경사 하강법을 사용한 최적화
for epoch in range(100):
    grad_value = grad_f(x)
    x -= learning_rate * grad_value  # 기울기를 따라 파라미터 업데이트
    if epoch % 10 == 0:
        print(f"Epoch {epoch}: x = {x}, f(x) = {f(x)}")
```

위 코드에서 경사 하강법을 사용하여 함수 $f(x) = x^2 + 2x + 1$을 최소화하는 과정을 구현했습니다. `grad_f`는 자동미분을 통해 $f(x)$의 미분값을 계산하고, 이를 이용해 파라미터 $x$를 업데이트합니다.

## 결론

자동미분은 복잡한 함수의 미분을 효율적이고 정확하게 계산할 수 있는 도구입니다. 수치 미분과 비교하여 더 높은 정확도를 제공하며, 특히 최적화 문제와 신경망 훈련에서 매우 유용합니다. 그러나 메모리 사용과 속도 측면에서 몇 가지 제약이 있을 수 있으며, 이를 보완하기 위한 다양한 최적화 기법이 연구되고 있습니다.
