πŸ”– Automatic Differentiation Series

  1. πŸ’» Numerical Differentiation
  2. πŸ–ŠοΈ Symbolic Differentiation
  3. πŸ€– Automatic Differentiation

λ”₯λŸ¬λ‹μ„ κ΅¬ν˜„ν•¨μ— μžˆμ–΄μ„œ κ°€μž₯ μ€‘μš”ν•œ μš”μ†Œκ°€ λ­˜κΉŒμš”? λ¬Όλ‘  λ§Žμ€ ν•™λ¬ΈμœΌλ‘œ κ΅¬μ„±λœ λ”₯λŸ¬λ‹μ˜ νŠΉμ„±μƒ λͺ¨λ“  μš”μ†Œλ“€μ΄ λ‹€ μ€‘μš”ν•˜μ§€λ§Œ, κ·Έ μ€‘μ—μ„œλ„ 특히 μ‹ κ²½μ¨μ•Όν•˜λŠ” μš”μ†Œκ°€ μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μ°Ύμ•„λ‚΄κΈ° μœ„ν•΄μ„œ λ‹€μŒμ˜ PyTorch μ½”λ“œλ₯Ό μ‚΄νŽ΄λ΄…μ‹œλ‹€.

net = nn.Sequential(
  nn.Linear(2, 1),
  nn.Sigmoid()
)

# x = ...
# y = ...
# criterion = ...
opt = optim.SGD(net.parameters(), lr=0.01)

opt.zero_grad()
loss = criterion(net(x), y)
loss.backward()
opt.step()

이λ₯Ό μ•„λ¬΄λŸ° λ”₯λŸ¬λ‹ ν”„λ ˆμž„μ›Œν¬λ₯Ό 쓰지 μ•Šκ³  κ΅¬ν˜„ν•œλ‹€κ³  μƒκ°ν•΄λ΄…μ‹œλ‹€. 일단 μ—„λ°€ν•˜κ²Œ 같은 κ΅¬ν˜„μ€ μ•„λ‹ˆμ§€λ§Œ Linear와 Sigmoid ν•¨μˆ˜ 자체의 κ΅¬ν˜„μ€ λ‹¨μˆœνžˆ ν–‰λ ¬κ³±κ³Ό λ²‘ν„°ν™”λœ sigmoid ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•  수 μžˆμœΌλ―€λ‘œ net(x)λ₯Ό λ§Œλ“œλŠ” 것은 어렡지 μ•ŠμŠ΅λ‹ˆλ‹€. λ‹€μŒμœΌλ‘œ μ—¬κΈ°μ„  criterion이 무엇인지 λͺ…μ‹œν•˜μ§€λŠ” μ•Šμ•˜μ§€λ§Œ κ°€μž₯ 기본적인 MSEλ₯Ό μ‚¬μš©ν•œλ‹€λ©΄ 이 μ—­μ‹œ κ°„λ‹¨ν•©λ‹ˆλ‹€. λ¬Έμ œλŠ” optλΆ€ν„° μ‹œμž‘λ©λ‹ˆλ‹€. SGDλ₯Ό κ΅¬ν˜„ν•˜λ €λ©΄, μ–΄λ–€ criterionμ΄λ‚˜ 신경망 κ΅¬μ‘°μ—μ„œλ„ gradient 즉, λ„ν•¨μˆ˜λ₯Ό ꡬ할 수 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

이λ₯Ό μœ„ν•΄ μ•žμ—μ„œ 배운 μˆ˜μΉ˜λ―ΈλΆ„μ„ μ‚¬μš©ν•  수 μžˆμ„ κ²ƒμž…λ‹ˆλ‹€. μœ„ κ²½μš°μ—λŠ” 신경망이 단일 κ³„μΈ΅μœΌλ‘œ λ˜μ–΄μžˆκ³  μž…λ ₯, 좜λ ₯ 차원도 μž‘μœΌλ―€λ‘œ 크게 λ¬Έμ œλ˜μ§„ μ•Šκ² μ§€λ§Œ 보톡 λ”₯λŸ¬λ‹ λͺ¨λΈλ“€μ€ λ‹€μΈ΅μœΌλ‘œ λ˜μ–΄μžˆμœΌλ©° μž…μΆœλ ₯ 차원도 큰데닀 성곡적인 ν›ˆλ ¨μ„ μœ„ν•΄μ„œλŠ” 수천, 수만 번 이상 ν›ˆλ ¨μ„ ν•΄μ•Όν•  수 μžˆμŠ΅λ‹ˆλ‹€. 그럴 경우, μˆ˜μΉ˜λ―ΈλΆ„μœΌλ‘œ μΈν•œ μ‚¬μ†Œν•œ μ˜€μ°¨κ°€ ν›ˆλ ¨μ„ κ±°λ“­ν•˜λ©° 눈덩이처럼 λΆˆμ–΄λ‚  수 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έλ ‡λ‹€λ©΄ μ•žμ—μ„œ 배운 κΈ°ν˜Έλ―ΈλΆ„μ€ μ–΄λ–¨κΉŒμš”? κΈ°ν˜Έλ―ΈλΆ„μ€ μ •ν™•ν•œ λ„ν•¨μˆ˜μ˜ ν˜•νƒœλ₯Ό 얻을 수 μžˆκΈ°μ— λΆ€λ™μ†Œμˆ˜μ  μ˜€μ°¨μ™Έμ—λŠ” λ³„λ„μ˜ μ˜€μ°¨κ°€ λˆ„μ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ•žμ„  κΈ€μ—μ„œλ„ μ§€μ ν–ˆλ‹€μ‹œν”Ό κΈ°ν˜Έλ―ΈλΆ„μ€ ν•œ 번 κ³„μ‚°ν• λ•Œλ§ˆλ‹€ 큰 계산 λΉ„μš©μ„ μš”κ΅¬ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 계산 λΉ„μš©μ€ 수천번 μ΄μƒμ˜ ν›ˆλ ¨μ„ κ±°μΉ˜λ©΄μ„œ κΈ°ν•˜κΈ‰μˆ˜μ μœΌλ‘œ μ¦κ°€ν•˜λ©°, μ΄λŠ” μ›ν•˜λŠ” μ‹œκ°„ 내에, ν˜Ήμ€ μ œν•œλœ λ©”λͺ¨λ¦¬ μƒν™©μ—μ„œ ν›ˆλ ¨μ„ μ–΄λ ΅κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ•žμ„œ 배운 두 가지 미뢄방법 λͺ¨λ‘ λΆ€μ ν•©ν•˜λ‹€λ©΄ λŒ€μ²΄ μ–΄λ–»κ²Œ κ΅¬ν˜„ν•΄μ•Όν• κΉŒμš”? λ‹€ν–‰νžˆ κ³Όν•™μžλ“€μ€ λ”₯λŸ¬λ‹μ΄ λ‚˜μ˜€κΈ° 이전에 이미 이λ₯Ό μœ„ν•œ 도ꡬλ₯Ό 발λͺ…ν•΄λ†“μ•˜μŠ΅λ‹ˆλ‹€. λ°”λ‘œ μžλ™λ―ΈλΆ„ (Automatic Differentiation) μž…λ‹ˆλ‹€. μžλ™λ―ΈλΆ„μ€ 계산방법에 따라 크게 두 κ°€μ§€λ‘œ λ‚˜λˆŒ 수 μžˆλŠ”λ°, μž…λ ₯ λ³€μˆ˜μ˜ 변화에 따라 각 ν•¨μˆ˜μ— μ „νŒŒλ˜λŠ” λ„ν•¨μˆ˜λ₯Ό κ³„μ‚°ν•˜λŠ” 방식을 μ •λ°©ν–₯ μžλ™λ―ΈλΆ„μ΄λΌ λΆ€λ₯΄λ©°, λ°˜λŒ€λ‘œ 좜λ ₯ λ³€μˆ˜μ˜ 변화에 따라 λ„ν•¨μˆ˜λ₯Ό κ³„μ‚°ν•˜λŠ” 방식을 μ—­λ°©ν–₯ μžλ™λ―ΈλΆ„μ΄λΌ λΆ€λ¦…λ‹ˆλ‹€. 각 방식은 μ„œλ‘œ λ‹€λ₯Έ μž₯단점을 κ°–κ³  μžˆμ–΄ κ²½μš°μ— 따라 μ„ νƒν•΄μ„œ μ‚¬μš©ν•  수 μžˆλŠ”λ°, 이번 κΈ€μ—μ„œλŠ” 특히 μ •λ°©ν–₯ μžλ™λ―ΈλΆ„μ— μ§‘μ€‘ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.


1. Truncated Taylor Series

μžλ™λ―ΈλΆ„μ΄λΌλŠ” κ±°μ°½ν•œ 이름이 λΆ™μ—ˆμ§€λ§Œ, μ •λ°©ν–₯ μžλ™λ―ΈλΆ„μ˜ κ°œλ…μ€ λŒ€ν•™κ΅ λ―Έμ λΆ„ν•™μ‹œκ°„μ— λ°°μš°λŠ” ν…ŒμΌλŸ¬ κΈ‰μˆ˜μ—μ„œ μœ λž˜ν•©λ‹ˆλ‹€. λ―ΈλΆ„κ°€λŠ₯ν•œ ν•¨μˆ˜ $f$의 νŠΉμ • 지점 $x_0$μ—μ„œμ˜ ν…ŒμΌλŸ¬ κΈ‰μˆ˜λŠ” λ‹€μŒκ³Ό 같이 μ „κ°œλ©λ‹ˆλ‹€.

$$ f(x) = f(x_0) + f’(x_0)(x - x_0) + \frac{f’’(x_0)}{2!}(x - x_0)^2 + \cdots $$

μ΄λ•Œ $x$ λŒ€μ‹  $x$에 μΆ©λΆ„νžˆ μž‘μ€ (infinitesimal) λ³€λŸ‰ $\epsilon$을 κ°€ν•˜κ³  이λ₯Ό $x$에 λŒ€ν•΄μ„œ ν…ŒμΌλŸ¬ κΈ‰μˆ˜λ₯Ό μ „κ°œν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

$$ f(x+ \epsilon) = f(x) + f’(x)\epsilon + \mathcal{O}(\epsilon^2) $$

$\epsilon$이 μΆ©λΆ„νžˆ μž‘λ‹€κ³  κ°€μ •ν–ˆμœΌλ―€λ‘œ ($|\epsilon| \ll 1$), $\epsilon^2$ μ΄μƒμ˜ 항듀은 λ¬΄μ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

$$ f(x + \epsilon) = f(x) + f’(x)\epsilon $$

μ΄λ ‡κ²Œ νŠΉμ • μ°¨μˆ˜κΉŒμ§€λ§Œ μž‘μ„±ν•˜λŠ” ν…ŒμΌλŸ¬ κΈ‰μˆ˜λ₯Ό Truncated Taylor Series라 λΆ€λ¦…λ‹ˆλ‹€. κΌ­ μΌμ°¨ν•­κΉŒμ§€λ§Œ 남겨놓을 ν•„μš”λŠ” μ—†μ§€λ§Œ, λŒ€λΆ€λΆ„μ˜ λ”₯λŸ¬λ‹ λ¬Έμ œλŠ” λ„ν•¨μˆ˜κΉŒμ§€μ˜ μ •λ³΄λ§Œ μš”κ΅¬ν•˜λ―€λ‘œ μ—¬κΈ°μ„œλŠ” μΌμ°¨ν•­κΉŒμ§€λ§Œ μž‘μ„±ν•˜κ² μŠ΅λ‹ˆλ‹€.

이제 $\epsilon$에 κ³„μˆ˜κ°€ κ³±ν•΄μ ΈμžˆλŠ” ν˜•νƒœλ₯Ό μƒκ°ν•΄λ΄…μ‹œλ‹€.

$$ f(x + \overline{x} \epsilon) = f(x) + f’(x)\overline{x}\epsilon $$

μœ„ 식을 보면 $\epsilon$이 마치 μΌμ’…μ˜ κΈ°μ €μ²˜λŸΌ μž‘μš©ν•œλ‹€λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. $x + \overline{x} \epsilon = x \mathbb{1} + \overline{x} \epsilon$ 으둜 보면 $\mathbb{1}$κ³Ό $\epsilon$이 기저인 κ³΅κ°„μ—μ„œ 수λ₯Ό λ‚˜νƒ€λ‚Έ κ²ƒμ²˜λŸΌ λ³΄μž…λ‹ˆλ‹€. λ”°λΌμ„œ 이λ₯Ό ν•˜λ‚˜μ˜ 수둜 μ·¨κΈ‰ν•˜κ³  λ‹€μŒκ³Ό 같이 κ°„λ‹¨νžˆ ν‘œν˜„ν•΄λ΄…μ‹œλ‹€.

$$ x \mathrel{\rhd} \overline{x} \equiv x + \overline{x}\epsilon $$

μ΄λŸ¬ν•œ 수λ₯Ό μ΄μ›μˆ˜ (Dual number) 라 λΆ€λ₯΄λ©° 무렀 1873년에 William Clifford에 μ˜ν•΄ μ°½μ•ˆλ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄μ›μˆ˜λŠ” 처음 λͺ©μ μ΄ μžλ™λ―ΈλΆ„μ€ μ•„λ‹ˆμ—ˆμ§€λ§Œ μ΅œκ·Όμ— μ™€μ„œλŠ” μžλ™λ―ΈλΆ„μ„ λ‚˜νƒ€λ‚΄λŠ” λŒ€ν‘œμ μΈ μˆ˜μ²΄κ³„λ‘œ μžλ¦¬μž‘μ•˜μŠ΅λ‹ˆλ‹€. 이제 이 수λ₯Ό μ΄μš©ν•˜μ—¬ λ‹€μ‹œ μΌμ°¨ν•­κΉŒμ§€μ˜ ν…ŒμΌλŸ¬ κΈ‰μˆ˜λ₯Ό μž‘μ„±ν•΄λ΄…μ‹œλ‹€.

$$ f(x \mathrel{\rhd} \overline{x}) = f(x) \mathrel{\rhd} f’(x)\overline{x} $$

이제 μ’€ 더 κ°„λ‹¨ν•΄μ‘Œμ§€λ§Œ ν•˜λ‚˜μ˜ λ¬Έμ œκ°€ λ‚¨μ•„μžˆμŠ΅λ‹ˆλ‹€. ν•¨μˆ˜ $f$의 μ •μ˜κ°€ λͺ…ν™•ν•˜μ§€ μ•Šλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. $x$λ₯Ό 1차원 μ‹€μˆ˜λ‘œ κ°€μ •ν•˜κ³  $x \mathrel{\rhd} \overline{x}$λ₯Ό 일차원 μ΄μ›μˆ˜($\mathbb{D}\mathbb{R}$)라 κ°€μ •ν•˜λ©΄ μ’Œλ³€μ—μ„œμ˜ ν•¨μˆ˜ $f$λŠ” $\mathbb{DR} \rightarrow \mathbb{DR}$ ν•¨μˆ˜μ΄μ§€λ§Œ μš°λ³€μ—μ„œμ˜ ν•¨μˆ˜ $f$λŠ” $\mathbb{R} \rightarrow \mathbb{R}$ μ΄λ―€λ‘œ μ •μ˜κ°€ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λŠ” λ‹€ν–‰νžˆ μƒˆλ‘œμš΄ μ—°μ‚°μžμΈ $\vec{\mathcal{J}}$을 λ„μž…ν•˜λ©΄ μ‰½κ²Œ ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

$$ \vec{\mathcal{J}}f(x \mathrel{\rhd}\overline{x}) = f(x) \mathrel{\rhd} f’(x)\overline{x} $$

μ΄λ•Œ μ—°μ‚°μž $\vec{\mathcal{J}}$λŠ” λ‹€μ–‘ν•œ κ΄€μ μ—μ„œ 해석될 수 μžˆλŠ”λ°, λͺ‡ κ°€μ§€λ§Œ μ•„λž˜μ— κΈ°μˆ ν•˜κ² μŠ΅λ‹ˆλ‹€.

  • $f$λΌλŠ” μ‹€μˆ˜κ³΅κ°„μ— νŽΌμ³μ§„ μž₯(field)을 μ΄μ›μˆ˜ κ³΅κ°„μ˜ μž₯으둜 λ³€ν™˜ν•œ μ—°μ‚°μžμ΄λ―€λ‘œ λ―ΈλΆ„κΈ°ν•˜ν•™μ—μ„œμ˜ Push-forward κ°œλ…μœΌλ‘œ 받아듀일 수 μžˆμŠ΅λ‹ˆλ‹€.

  • μ›λž˜ μ‹€μˆ˜ νƒ€μž…μ„ λ°›μ•„μ„œ μ‹€μˆ˜ νƒ€μž…μ„ λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜ $f$에 μž‘μš©ν•΄μ„œ μƒˆλ‘œμš΄ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄λƒˆμœΌλ―€λ‘œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œμ˜ lifting κ°œλ…μœΌλ‘œ 받아듀일 수 μžˆμŠ΅λ‹ˆλ‹€.

  • μ‹€μˆ˜ ν•¨μˆ˜λ‘œ μž‘μ„±λœ ν•¨μˆ˜ $f$에 μž‘μš©ν•˜μ—¬ μƒˆλ‘œμš΄ ν•¨μˆ˜ ν˜•νƒœλ₯Ό μž‘μ„±ν•˜λ―€λ‘œ Source code transformation, Operator overloading ν˜Ήμ€ Multiple dispatch ν˜•νƒœλ‘œ 이해할 수 μžˆμŠ΅λ‹ˆλ‹€.

이처럼 λ‹€μ–‘ν•œ λ°©μ‹μœΌλ‘œ 해석할 수 μžˆμ§€λ§Œ μœ„ μ‹μ˜ μ˜λ―ΈλŠ” ν•˜λ‚˜λ‘œ κ·€κ²°λ˜λŠ”λ°, λ°”λ‘œ “μ •λ°©ν–₯ μžλ™λ―ΈλΆ„"을 ν‘œν˜„ν•œ μ‹μ΄λΌλŠ” κ²ƒμž…λ‹ˆλ‹€. λ‹¨μˆœνžˆ μΌμ°¨ν•­κΉŒμ§€ ν‘œν˜„ν•œ ν…ŒμΌλŸ¬ κΈ‰μˆ˜κ°€ μ–΄λ–»κ²Œ 미뢄을 ν‘œν˜„ν•˜λŠ”μ§€ μ˜μ•„ν•˜μ‹€ν…Œλ‹ˆ ν•˜λ‚˜μ˜ μ˜ˆμ‹œλ₯Ό λ“€μ–΄λ΄…μ‹œλ‹€. μ˜ˆμ‹œλ‘œλŠ” μ•„μ£Ό κ°„λ‹¨ν•œ ν•¨μˆ˜μΈ $v = \sin u$λ₯Ό μ‚¬μš©ν•˜κ² μŠ΅λ‹ˆλ‹€.

1.1. μ˜ˆμ‹œ: Sin ν•¨μˆ˜ μžλ™λ―ΈλΆ„

$$ \begin{aligned} &\begin{aligned} v \mathrel{\rhd} \overline{v} &= \vec{\mathcal{J}}\sin (u \mathrel{\rhd} \overline{u}) \\ &= \sin u \mathrel{\rhd} (\cos u) \overline{u} \end{aligned} \\ \therefore ~ &v = \sin u,\quad \overline{v} = (\cos u) \overline{u} \end{aligned} $$

이둜써 $v = \sin u$의 λ„ν•¨μˆ˜λŠ” $\overline{v} = (\cos u) \overline{u}$μ΄λΌλŠ” κ²°κ³Όλ₯Ό μ–»μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μš°λ¦¬κ°€ ν”νžˆ μ‚¬μš©ν•˜λŠ” 연쇄 법칙(Chain rule)κ³Ό 일λ§₯μƒν†΅ν•˜λ―€λ‘œ μ˜¬λ°”λ₯Έ κ³„μ‚°μž„μ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ, μ–Έλœ» 보면 $\sin u$의 λ„ν•¨μˆ˜λ₯Ό $\cos u$둜 μ•Œλ € μ€€ λ‹€μŒ 계산을 ν•œ μ…ˆμ΄λ‹ˆ κΈ°ν˜Έλ―ΈλΆ„κ³Ό 무엇이 λ‹€λ₯Έμ§€ μ˜μ•„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 직접 μ½”λ“œλ₯Ό μž‘μ„±ν•΄λ³΄λ©΄ 이해할 수 μžˆμŠ΅λ‹ˆλ‹€.

#[derive(Debug, Copy, Clone)]
struct Dual {
    x: f64,
    dx: f64,
}

trait Sin {
    fn sin_(self) -> Self;
}

impl Sin for f64 {
    fn sin_(self) -> Self {
        self.sin()
    }
}

impl Sin for Dual {
    fn sin_(self) -> Self {
        Dual {
            x: self.x.sin(),
            dx: self.dx * self.x.cos(),
        }
    }
}

fn main() {
    let u = Dual { x: 1.0, dx: 1.0 }; // x at x=1
    let v = u.sin_();
    println!("v: {}, dv: {}", v.x, v.dx);
    // sin(1) = 0.8414..
    // sin'(x) = cos(x) = cos(1) = 0.5403..

    let w = v.sin_();
    println!("v: {}, dv: {}", v.x, v.dx);
    // sin(sin(1)) = 0.7456..
    // (sin(sin(x)))' = cos(sin(1)) * cos(1) = 0.3600..
}

Rustλ₯Ό μ΄μš©ν•˜μ—¬ κ°„λ‹¨νžˆ μ΄μ›μˆ˜ ꡬ쑰체λ₯Ό κ΅¬ν˜„ν•˜κ³  μ‹€μˆ˜μ™€ μ΄μ›μˆ˜ λͺ¨λ‘μ— μ μš©λ˜λŠ” Sinμ΄λΌλŠ” traitλ₯Ό μž‘μ„±ν•˜μ—¬ Method overloading을 μ΄μš©ν•˜μ—¬ μžλ™λ―ΈλΆ„μ„ κ΅¬ν˜„ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. mainν•¨μˆ˜μ˜ 첫번째 μ˜ˆμ‹œλ₯Ό 보면 $u = 1 \mathrel{\rhd} 1$둜 μ •μ˜λœ 것을 λ³Ό 수 μžˆλŠ”λ°, μ΄λŠ” 값이 1이고 λ„ν•¨μˆ˜κ°€ 1μ΄λΌλŠ” μ˜λ―Έμ΄λ―€λ‘œ $x$λ₯Ό ν‘œν˜„ν•œ κ²ƒμœΌλ‘œ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄μ›μˆ˜μ˜ sin_ν•¨μˆ˜ μžμ²΄μ— λ„ν•¨μˆ˜ 계산이 ν¬ν•¨λ˜μ–΄μžˆμœΌλ―€λ‘œ let v = u.sin_();λΌλŠ” 식 만으둜 이미 ν•¨μˆ«κ°’κ³Ό λ„ν•¨μˆ«κ°’μ΄ κ³„μ‚°λ˜κ³  μ΄λŠ” v.x와 v.dx둜 μ €μž₯λ©λ‹ˆλ‹€. μ΄λ ‡κ²Œ κ³„μ‚°λœ λ„ν•¨μˆ˜μ—λŠ” λΆ€λ™μ†Œμˆ˜μ  였차λ₯Ό μ œμ™Έν•œ λ‹€λ₯Έ μ˜€μ°¨λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©° 계산 λΉ„μš©λ„ λ‹¨μˆœνžˆ κ³±ν•˜κΈ° ν•œ 번과 코사인 ν•œλ²ˆμ„ κ³„μ‚°ν•œκ²Œ μ „λΆ€μž…λ‹ˆλ‹€.

두 번째 μ˜ˆμ‹œλ₯Ό 보면 μžλ™λ―ΈλΆ„μ˜ μ—„μ²­λ‚œ μ„±λŠ₯을 κ°€λŠ ν•  수 μžˆλŠ”λ°, μ•žμ„œ κ³„μ‚°ν•œ v에 ν•œλ²ˆ 더 sin_을 μ·¨ν•˜μ—¬ wλ₯Ό μ •μ˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 즉, $w = \sin(\sin(u))$인데, ν•¨μˆ˜κ°€ λ³΅μž‘ν•΄μ§„κ²ƒκ³Ό 관계없이 계산 μ½”λ“œλŠ” λ™μΌν•œ 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλ ‡κ²Œ κ³„μ‚°λœ κ²°κ³ΌλŠ” λ†€λžκ²Œλ„ ν•©μ„±ν•¨μˆ˜μ˜ κ°’κ³Ό λ„ν•¨μˆ˜λ₯Ό μ •ν™•νžˆ κ³„μ‚°ν•΄λ‚΄λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ μ •λ°©ν–₯ μžλ™λ―ΈλΆ„μ„ μ΄μš©ν•˜λ©΄ μ•„μ£Ό κ°„λ‹¨ν•œ λ„ν•¨μˆ˜ μ „νŒŒ κ·œμΉ™μ„ λͺ…μ‹œν•΄λ†“λŠ” κ²ƒλ§ŒμœΌλ‘œ λ³΅μž‘ν•˜κ²Œ ν•©μ„±λœ ν•¨μˆ˜λ₯Ό 맀우 적은 λΉ„μš©μœΌλ‘œ 계산할 수 μžˆλ‹€λŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.


2. μ •λ°©ν–₯ μžλ™λ―ΈλΆ„ κ΅¬ν˜„

μœ„μ²˜λŸΌ μžλ™λ―ΈλΆ„μ€ ν•¨μˆ˜μ˜ ν˜•νƒœμ™€ 상관없이 μž‘λ™ν–ˆλ˜ μˆ˜μΉ˜λ―ΈλΆ„κ³ΌλŠ” λ‹€λ₯΄κ²Œ 각 ν•¨μˆ˜μ˜ ν˜•νƒœλ§ˆλ‹€ λ„ν•¨μˆ˜μ˜ μ „νŒŒ κ·œμΉ™μ„ λͺ…μ‹œν•΄μ€˜μ•Όν•©λ‹ˆλ‹€. λ”°λΌμ„œ μ΄λ²ˆμ—λŠ” 사칙연산을 ν¬ν•¨ν•˜μ—¬ λŒ€ν‘œμ μΈ ν•¨μˆ˜ λͺ‡ 가지에 λŒ€ν•΄ μžλ™λ―ΈλΆ„μ„ κ΅¬ν˜„ν•΄λ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

2.1. 사칙연산

$w = u \pm v$둜 μ •μ˜λœ $w$에 λŒ€ν•΄μ„œ μ•„κΉŒμ™€ λ§ˆμ°¬κ°€μ§€λ‘œ μžλ™λ―ΈλΆ„μ„ μˆ˜ν–‰ν•΄λ΄…μ‹œλ‹€.

$$ \begin{aligned} &\begin{aligned} w \mathrel{\rhd} \overline{w} &= u \mathrel{\rhd} \overline{u} + v \mathrel{\rhd} \overline{v} \\ &= (u + v) \mathrel{\rhd} (\overline{u} + \overline{v}) \end{aligned} \\ \therefore ~ &w = u + v,\quad \overline{w} = \overline{u} + \overline{v} \end{aligned} $$

λ§ˆμ°¬κ°€μ§€λ‘œ μ΄λ²ˆμ—” $w = u \times v$에 μžλ™λ―ΈλΆ„μ„ μ μš©ν•΄λ΄…μ‹œλ‹€.

$$ \begin{aligned} &\begin{aligned} w \mathrel{\rhd} \overline{w} &= (u \mathrel{\rhd} \overline{u}) \times (v \mathrel{\rhd} \overline{v}) \\ &= (u + \overline{u}\epsilon) \times (v + \overline{v}\epsilon) \\ &= uv + (u \overline{v} + \overline{u}v)\epsilon \\ &= (u \times v) \mathrel{\rhd} (u\overline{v} + \overline{u}v) \end{aligned} \\ \therefore ~ &w = u \times v,\quad \overline{w} = u\overline{v} + \overline{u}v \end{aligned} $$

μœ„ 두 κ²°κ³ΌλŠ” 각각 μš°λ¦¬κ°€ 읡히 μ•Œκ³ μžˆλŠ” λ―ΈλΆ„μ˜ μ„ ν˜•μ„±κ³Ό λΌμ΄ν”„λ‹ˆμΈ  κ·œμΉ™κ³Ό μ •ν™•νžˆ λ“€μ–΄λ§žμŠ΅λ‹ˆλ‹€. 이λ₯Ό μœ„μ—μ„œ μ •μ˜ν•œ Dual ꡬ쑰체에 μ μš©ν•΄λ΄…μ‹œλ‹€.

use std::ops::{Add, Sub, Mul};

impl Add for Dual {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        Self {
            x: self.x + rhs.x,
            dx: self.dx + rhs.dx,
        }
    }
}

impl Sub for Dual {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self {
        Self {
            x: self.x - rhs.x,
            dx: self.dx - rhs.dx,
        }
    }
}

impl Mul for Dual {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self {
        Self {
            x: self.x * rhs.x,
            dx: self.x * rhs.dx + self.dx * rhs.x,
        }
    }
}

fn main() {
    let u = Dual { x: 1f64, dx: 1f64 }; // x at x=1
    let v = Dual { x: 2f64, dx: 4f64 }; // 2x^2 at x=1

    let w = u + v;
    println!("w: {}, dw: {}", w.x, w.dx);
    // w: 3, dw: 5

    let w = u * v;
    println!("w: {}, dw: {}", w.x, w.dx);
    // w = 2x^3 at x=1 = 2
    // dw = 6x^2 at x=1 = 6
}

Β 

2.2. μ§€μˆ˜, 둜그, λ‹€ν•­ ν•¨μˆ˜

λ‹€μŒμœΌλ‘œλŠ” μ§€μˆ˜ 둜그 ν•¨μˆ˜μ˜ λŒ€ν‘œμ μΈ ν•¨μˆ˜λ“€μΈ $y=e^x,\,y=\ln x$와 λ‹€ν•­ν•¨μˆ˜μΈ $y=x^n$에 λŒ€ν•΄μ„œ μžλ™λ―ΈλΆ„μ„ μˆ˜ν–‰ν•΄λ΄…μ‹œλ‹€.

  1. μ§€μˆ˜ν•¨μˆ˜ $$ \begin{aligned} &\begin{aligned} v \mathrel{\rhd} \overline{v} &= \exp(u \mathrel{\rhd} \overline{u}) \\ &= e^u \mathrel{\rhd} e^{u} \overline{u} \end{aligned} \\ \therefore ~ &v = e^u,\quad \overline{v} = e^{u} \overline{u} \end{aligned} $$

  2. λ‘œκ·Έν•¨μˆ˜ $$ \begin{aligned} &\begin{aligned} w \mathrel{\rhd} \overline{w} &= \ln(u \mathrel{\rhd} \overline{u}) \\ &= \ln u \mathrel{\rhd} \frac{\overline{u}}{u} \end{aligned} \\ \therefore ~ &w = \ln u,\quad \overline{w} = \frac{\overline{u}}{u} \end{aligned} $$

  3. λ‹€ν•­ν•¨μˆ˜ $$ \begin{aligned} &\begin{aligned} w \mathrel{\rhd} \overline{w} &= (u \mathrel{\rhd} \overline{u})^n \\ &= u^n \mathrel{\rhd} n u^{n-1} \overline{u} \end{aligned} \\ \therefore ~ &w = u^n,\quad \overline{w} = n u^{n-1} \overline{u} \end{aligned} $$

μ•žμ„œ κ΅¬ν˜„ν•œ sinν•¨μˆ˜μ™€ 같이 λ‹€λ₯Έ μ‚Όκ°ν•¨μˆ˜λ“€λ„ λͺ¨λ‘ κ΅¬ν˜„ν•˜μ—¬ ν•˜λ‚˜μ˜ trait을 μ„ μ–Έν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.


trait Ops {
    fn exp(self) -> Self;
    fn ln(self) -> Self;
    fn sin(self) -> Self;
    fn cos(self) -> Self;
    fn tan(self) -> Self;
    fn powi(self, n: i32) -> Self;
}

impl Ops for Dual {
    fn exp(self) -> Self {
        Self {
            x: self.x.exp(),
            dx: self.x * self.dx.exp(),
        }
    }

    fn ln(self) -> Self {
        Self {
            x: self.x.ln(),
            dx: self.dx / self.x,
        }
    }

    fn sin(self) -> Self {
        Self {
            x: self.x.sin(),
            dx: self.x.cos() * self.dx,
        }
    }

    fn cos(self) -> Self {
        Self {
            x: self.x.cos(),
            dx: -self.x.sin() * self.dx,
        }
    }

    fn tan(self) -> Self {
        let tan = self.x.tan();
        Self {
            x: tan,
            dx: self.dx * (tan * tan + 1.0),
        }
    }

    fn powi(self, n: i32) -> Self {
        Self {
            x: self.x.powi(n),
            dx: n as f64 * self.x.powi(n - 1) * self.dx,
        }
    }
}

Β 

2.3. μ‹œκ·Έλͺ¨μ΄λ“œ ν•¨μˆ˜

μœ„μ— μžˆλŠ” ν•¨μˆ˜λ“€μ— λͺ‡ 가지 μ—°μ‚° κ΅¬ν˜„μ„ λ”ν•˜λ©΄ λ³„λ„λ‘œ μ‹œκ·Έλͺ¨μ΄λ“œ ν•¨μˆ˜μ˜ μ „νŒŒλ°©λ²•μ„ κ΅¬ν˜„ν•˜μ§€ μ•Šλ”λΌλ„ μ‹œκ·Έλͺ¨μ΄λ“œ ν•¨μˆ˜μ— λŒ€ν•œ μžλ™λ―ΈλΆ„μ„ 계산할 수 μžˆμŠ΅λ‹ˆλ‹€.

trait Sigmoid: Sized 
    + Ops
    + Neg<Output=Self>
    + Add<f64, Output=Self> 
where
    f64: Div<Self, Output=Self> {
    fn sigmoid(self) -> Self {
        1f64 / ((-self).exp() + 1f64)
    }
}

impl Sigmoid for Dual {}

fn main() {
    let u = Dual { x: 1.0, dx: 1.0 }; // x at x=1
    let z = u.sigmoid();
    println!("z: {}, dz: {}", z.x, z.dx);
    // sigmoid(1), sigmoid'(x) = sigmoid(1) * (1 - sigmoid(1))
}

주의: μœ„μ— μƒκΈ°ν•œ μ½”λ“œμ™Έμ—λ„ f64μ™€μ˜ 연산이 μΆ”κ°€μ μœΌλ‘œ μ •μ˜λ˜μ–΄μ•Ό μž‘λ™ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€. μ‹€μ œλ‘œ 이 μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κ³  μ‹Άλ‹€λ©΄ λ‹€μŒ 링크λ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

github.com/Axect/dual


마치며

μ§€κΈˆκΉŒμ§€ μ΄μ›μˆ˜λ₯Ό μ΄μš©ν•œ μ •λ°©ν–₯ μžλ™λ―ΈλΆ„μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μ‰½κ²Œ μ„€λͺ…ν•˜κΈ° μœ„ν•΄μ„œ ꡉμž₯히 κ°„λ‹¨ν•œ κ²½μš°μ— λŒ€ν•΄μ„œλ§Œ λ‹€λ€˜λŠ”λ°, μ‹€μ œλ‘œ 잘 μž‘λ™λ˜λŠ” μžλ™λ―ΈλΆ„ 라이브러리λ₯Ό λ§Œλ“€κΈ° μœ„ν•΄μ„œλŠ” λ‹€μŒκ³Ό 같은 사항듀을 κ³ λ €ν•΄μ•Όν•©λ‹ˆλ‹€.

  • 고계 λ„ν•¨μˆ˜μ— λŒ€ν•œ μžλ™λ―ΈλΆ„

  • λ‹€λ³€μˆ˜ ν•¨μˆ˜μ— λŒ€ν•œ μžλ™λ―ΈλΆ„

  • ν–‰λ ¬κ³Ό 벑터 λ³€μˆ˜λ₯Ό ν¬ν•¨ν•œ ν•¨μˆ˜μ˜ μžλ™λ―ΈλΆ„

이 쀑 첫 2가지 사항에 λŒ€ν•΄μ„œλŠ” Rust μˆ˜μΉ˜κ³„μ‚° 라이브러리인 Peroxide에 잘 κ΅¬ν˜„λ˜μ–΄ μžˆμœΌλ―€λ‘œ μ°Έκ³ ν•˜μ‹œλ©΄ λ©λ‹ˆλ‹€. λ§ˆμ§€λ§‰ 사항에 λŒ€ν•΄μ„œλŠ” μ—­λ°©ν–₯ μžλ™λ―ΈλΆ„κ³Ό ν•¨κ»˜ λ‹€μŒ κΈ€μ—μ„œ 닀루도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.


μ°Έκ³ λ¬Έν—Œ