브라운 운동: 속도가 없는 길
격자 위를 100걸음 걸어 10칸 간 사람은 얼마나 빨리 움직인 것일까? 한 걸음에 한 칸이니 빠르기는 1이라고 해야 할까, 100걸음에 10칸이니 0.1이라고 해야 할까? 그리고 칸과 걸음을 한없이 잘게 만들면 이 길은 어떤 길이 되고, 한 식으로 어떻게 적을 수 있을까?
역사: 떨리는 꽃가루 알갱이
1827년 여름 식물학자 로버트 브라운은 물에 띄운 꽃가루에서 나온 작은 알갱이들이 쉬지 않고 떨리는 것을 현미경으로 보았다. 그는 생명 현상을 의심해 오래 말린 식물의 꽃가루, 유리와 쇳가루, 여러 시대의 돌가루, 심지어 스핑크스의 조각을 갈아 물에 띄워 보았지만 똑같은 떨림이 나타났다. 1905년 아인슈타인은 이 떨림을 물 분자의 충돌로 설명했다. 그는 알갱이마다 다른 알갱이와 상관없이 움직이고 한 알갱이의 움직임도 시간 구간마다 서로 상관없다고 가정해, 알갱이들의 분포가 확산 방정식을 따르고 한 방향으로 옮겨 간 거리의 제곱평균제곱근이 √(2Dt)임을 보였다.

한 사람의 빠르기
한 사람이 100만 걸음을 걷는 동안의 가로 위치를 Δ걸음 간격으로 재어, 한 구간에서 가로로 옮겨 간 거리의 절댓값을 Δ로 나눈 평균을 구하면 다음과 같다.
| 재는 간격 Δ (걸음) | 1 | 4 | 16 | 64 | 256 | 1024 |
|---|---|---|---|---|---|---|
| 가로 변위의 절댓값 ÷ Δ (평균) | 0.500 | 0.273 | 0.139 | 0.070 | 0.035 | 0.017 |
재는 간격을 네 배로 늘릴 때마다 빠르기가 절반이 된다. 한 구간에 옮겨 간 거리가 √Δ에 비례하므로 빠르기는 √Δ / Δ = 1/√Δ에 비례하는 것이다. 거꾸로 말하면 간격을 줄일수록 잰 빠르기는 한없이 커진다. 이 사람의 빠르기는 재는 간격에 따라 달라지므로 하나의 값으로 말할 수 없다.

코드로 확인하기
격자 걷기(100만 명이 동전 두 개로 걸으며 99·100·400걸음 뒤 거리 제곱의 평균과 원점·(10, 0)에 선 비율을 재고, 한가운데 칸의 1을 이웃 넷의 평균으로 되풀이해 바꾼 값과 비교)와 빠르기 측정을 그대로 돌리는 코드다. 이어서 한 사람의 길을 간격을 바꿔 가며 재어 빠르기가 간격의 제곱근에 반비례하는지 본다.
import numpy as np
rng = np.random.default_rng(0)
M = 1_000_000 # 걷는 사람 100만 명, 모두 원점에서 출발
steps = np.array([(1, 0), (-1, 0), (0, 1), (0, -1)], dtype=np.int16)
pos = np.zeros((M, 2), dtype=np.int16)
L = 241; c = L // 2 # 비교용: 한 칸의 1을 이웃 넷의 평균으로 되풀이
u = np.zeros((L, L)); u[c, c] = 1.0
def neighbor_avg(u):
return (np.roll(u, 1, 0) + np.roll(u, -1, 0) + np.roll(u, 1, 1) + np.roll(u, -1, 1)) / 4
for n in range(1, 401):
pos += steps[rng.integers(0, 4, M)] # 사람마다 동전 두 개로 네 방향 중 하나를 골라 한 칸
u = neighbor_avg(u)
if n in (99, 100, 400):
r2 = (pos.astype(np.int64)**2).sum(1).mean()
at0 = np.mean((pos[:, 0] == 0) & (pos[:, 1] == 0))
at10 = np.mean((pos[:, 0] == 10) & (pos[:, 1] == 0))
print(f"n = {n}: 거리 제곱의 평균 {r2:.2f} | 원점에 선 비율 {at0:.5f} (이웃 평균 {u[c, c]:.5f}) | "
f"(10, 0)에 선 비율 {at10:.5f} (이웃 평균 {u[c + 10, c]:.5f})")
# 한 사람의 길: 100만 걸음 동안의 가로 위치를 Δ걸음 간격으로 재어 「빠르기」를 구한다
x = np.cumsum(steps[rng.integers(0, 4, 1_000_000)][:, 0])
for d in (1, 4, 16, 64, 256, 1024):
print(f"Δ = {d:4d}걸음: 평균 |가로 변위| / Δ = {np.abs(x[d::d] - x[:-d:d]).mean() / d:.4f}")
# n = 99: 거리 제곱의 평균 99.07 | 원점에 선 비율 0.00000 (이웃 평균 0.00000) | (10, 0)에 선 비율 0.00000 (이웃 평균 0.00000)
# n = 100: 거리 제곱의 평균 100.10 | 원점에 선 비율 0.00622 (이웃 평균 0.00633) | (10, 0)에 선 비율 0.00228 (이웃 평균 0.00235)
# n = 400: 거리 제곱의 평균 400.00 | 원점에 선 비율 0.00161 (이웃 평균 0.00159) | (10, 0)에 선 비율 0.00117 (이웃 평균 0.00124)
# Δ = 1걸음: 평균 |가로 변위| / Δ = 0.5004
# Δ = 4걸음: 평균 |가로 변위| / Δ = 0.2730
# Δ = 16걸음: 평균 |가로 변위| / Δ = 0.1391
# Δ = 64걸음: 평균 |가로 변위| / Δ = 0.0704
# Δ = 256걸음: 평균 |가로 변위| / Δ = 0.0350
# Δ = 1024걸음: 평균 |가로 변위| / Δ = 0.0169
사람들의 비율은 이웃 평균의 값과 표본 오차 안에서 같고, 99걸음 뒤에는 원점과 (10, 0)이 모두 비어 있다. 한 사람의 빠르기는 재는 간격을 네 배로 늘릴 때마다 절반이 된다.
연속한 극한: 브라운 운동
격자 걷기에서 본 「속도가 없다」는 성질은 칸과 걸음을 한없이 잘게 만들면 더 분명해진다. 칸 간격 a와 한 걸음의 시간 τ를 줄이면서 확산 계수 D = a²/(4τ)를 고정하면, 사람들의 분포는 점점 매끄러워져 열 방정식 ∂ρ/∂t = D∇²ρ를 따르지만 한 사람의 길은 매끄러워지지 않는다. 시간 Δt 동안 한 축으로 옮겨 가는 거리는 √(2DΔt) 정도인데, Δt가 작을 때 √Δt는 Δt보다 훨씬 크다. 그래서 길은 끊어지지 않고 이어져 있지만 어느 곳에서도 속도, 곧 Δx/Δt의 극한이 없다.
우유 한 방울을 물에 묽게 풀어 현미경으로 보면 지름 1 μm쯤 되는 지방 알갱이들이 쉬지 않고 떨리는 것이 보인다. 20°C 물속의 지름 1 μm 알갱이는 확산 계수가 약 4.3 × 10⁻¹³ m²/s여서 한 축으로 1초에 약 0.93 μm, 1분에 약 7.2 μm 옮겨 간다. 1분에 옮겨 간 거리가 1초의 60배가 아니라 √60 ≈ 7.7배다.

정리하면, 동전으로 걷는 사람 하나의 길과 그런 사람 여럿의 분포는 같은 과정을 두 쪽에서 적은 것이다. 한 사람의 길은 무작위하고 거칠어서 속도가 없고, 그 대신 변위의 분산이 시간에 비례해 쌓인다. 여러 사람의 분포는 결정론적이고 매끄러워서 이웃 평균, 곧 열 방정식을 따른다.
극한의 길을 식으로 적어 보자. 동전의 규칙에서 출발하면, 한 축에서 한 걸음의 분산이 a²/2이고 한 걸음에 시간 τ가 걸리므로, 단위 시간에 쌓이는 분산은 a²/(2τ) = 2D다. 극한의 길은 잘게 나눈 시간 Δt마다 평균 0, 분산 2DΔt인 독립 변위를 더해 간다. 걸음이 많이 모이면 변위의 분포는 동전 대신 무엇을 썼든 정규분포가 되므로, 처음부터 정규분포로 적어도 된다.
이런 길을 브라운 운동 (시간 구간마다 평균 0, 한 축 분산이 2D × 구간 길이인 독립 정규 변위를 더해 가는 연속한 길, Brownian motion)이라 한다. 식에서 Δt를 반으로 나눠 두 번 더해도 분산이 DΔt + DΔt로 똑같이 2DΔt이므로, 시간을 어떻게 나누든 같은 길이 나온다. 잡음 앞에 √Δt가 붙는 이유가 이것이다. 잡음 앞에 Δt를 붙이면 반으로 나눈 두 걸음의 분산 합이 원래의 절반이 되어, 시간을 잘게 나눌수록 잡음이 사라져 버린다. 분산 2D = 1인 경우, 곧 한 축에서 시간 t 동안의 분산이 t인 것을 표준 브라운 운동 B(t)라 하고, 짧은 시간 dt 동안의 변화 dB를 √dt × 표준정규 잡음으로 읽는다. 수학 책은 이것을 위너 과정이라 부르며 W(t)로 적는 일이 많은데, 이 책에서 W는 경우의 수에 쓰므로 글자만 B로 바꿨다. 가리키는 것은 같다.
ML에서: 잡음을 나눠 더해도 같은 분포
분산 폭발형 확산 모델은 이미지에 표준편차가 커져 가는 정규 잡음을 더해 가는데, 이것은 픽셀마다 브라운 운동을 돌리는 것과 같다. 그래서 잡음을 나눠 더할 때는 표준편차가 아니라 분산이 더해진다. 표준편차 0.3인 잡음을 두 번 더한 이미지는 표준편차 0.6이 아니라 √(0.3² + 0.3²) ≈ 0.424인 잡음을 한 번 더한 것과 같은 분포를 따른다. 그렇다면 잡음 수준이 여러 단계인 학습 데이터를 만들 때, 코드는 그 단계를 하나씩 차례로 밟아야 할까? 아래 문제에서 따져 보자.
문제 3. 코딩 진도는 얼마나 빠른가
민준이 과제 코드를 쓰다 지우기를 되풀이한다. 1분마다 코드 줄 수가 평균 0, 표준편차 3줄만큼 바뀌고, 1분마다의 변화는 서로 독립이다. 조교는 줄 수 변화의 제곱평균제곱근을 재는 간격으로 나눈 값을 「진도」(줄/분)로 적는다. (가) 1분 간격으로 재면 진도는? (나) 1시간 간격, 하루(8시간) 간격으로 재면?

제 얘기네요. (가)는 3줄/분이요. 1시간이면 180줄이 바뀌니까 진도는 똑같이 3줄/분이고요.

1분 변화들이 독립이고 평균이 0이면 더해지는 건 분산이야. 1시간 동안의 분산은 60 × 3²이니까 표준편차는 3√60 = 23.2줄이야. 60분으로 나누면 0.39줄/분.

하루면 3√480 = 65.7줄, 0.14줄/분이에요. 재는 간격을 늘릴수록 제 진도가 느려지네요?

거꾸로 1초 간격으로 재면요? 1초 변화도 서로 독립이라고 해 봐요.

1초 변화의 표준편차가 3/√60이니까 진도는 23.2줄/분이에요. 간격을 줄일수록 커지고 늘릴수록 작아져서, 진도라는 하나의 값이 없어요.

조교님이 제 코딩 진도를 1분마다 보면 엄청 빨리 타이핑하는 걸로, 하루에 한 번 보면 거의 제자리인 걸로 보이겠네요. 썼다 지웠다 하니까요.
문제 4. 현미경으로 잰 속도
20°C 물속에 지름 1 μm인 알갱이가 떠 있다. 확산 계수는 D ≈ 4.3 × 10⁻¹³ m²/s다. 현미경 영상에서 Δt 간격으로 위치를 찍어, 한 축으로 옮겨 간 거리의 제곱평균제곱근을 Δt로 나눈 값을 「속도」로 잰다. (가) Δt = 1초, 0.01초, 10⁻⁴초에서 잰 속도는? (나) 카메라를 더 빠른 것으로 바꿀수록 잰 속도는 끝없이 커지는가?

옮겨 간 거리가 √(2DΔt)니까 Δt로 나누면 √(2D/Δt)예요. 1초면 0.93 μm/s, 0.01초면 9.3 μm/s, 10⁻⁴초면 93 μm/s요. 카메라를 좋은 걸로 바꿀수록 알갱이가 빨라지네요? 초고속 카메라를 사면 무한대가 나오겠는데요.

동전으로 걷는 사람이랑 같아. 재는 간격을 네 배로 늘리면 빠르기가 절반이었잖아. 속도가 정해지지 않는 길이야.

아까 코딩 진도랑 같은 셈이네요.

(나)는요? 알갱이가 정말 끝없이 빨라질 수 있을까요?

식으로는 그렇지만… 알갱이는 질량이 있는 물체잖아요. 끝없이 빠를 수는 없을 것 같은데요.

좋은 의심이에요. 브라운 운동이라는 식은 아주 짧은 시간에서 어딘가 맞지 않게 되는데, 그 경계를 찾으려면 알갱이에 걸리는 힘을 따져야 해요. 힘을 넣은 식을 세운 뒤에 다시 돌아와요.
문제 5. 잡음을 한 번에 더할 수 있을까
분산 폭발형 확산 모델에서 깨끗한 이미지에 표준편차 0.1, 0.2, 0.2, 0.4인 정규 잡음을 차례로, 서로 독립으로 더해 네 단계의 잡음 이미지를 만든다. (가) 학습에 넷째 단계의 이미지만 필요하다면, 네 번 더하는 대신 표준편차 얼마인 잡음을 한 번 더하면 같은 분포를 얻는가? 학습 코드는 단계를 하나씩 밟아야 하는가? (나) 코드가 실수로 네 단계 모두 같은 잡음 이미지 z 하나를 크기만 바꿔 더했다면?

0.1 + 0.2 + 0.2 + 0.4 = 0.9요. 표준편차 0.9인 잡음을 한 번 더하면 되죠.

표준편차 0.3인 잡음을 두 번 더한 이미지는 표준편차가 얼마였죠?

0.6이 아니라 0.424였어요. 분산이 더해지니까… √(0.01 + 0.04 + 0.04 + 0.16) = √0.25 = 0.5요.

그럼 학습 코드는 네 단계를 차례로 밟아야 할까요?

넷째 단계의 분포는 표준편차 0.5인 잡음 하나로 정해지니까, 원하는 단계의 표준편차만 알면 한 번에 만들 수 있어요. 중간 단계를 거칠 필요가 없네요.

그래서 학습 코드가 단계 번호를 무작위로 하나 뽑고 잡음을 딱 한 번만 더하는 거였구나.

(나)는요?

같은 z를 0.1, 0.2, 0.2, 0.4배 해서 더하면 0.9z잖아. 이번엔 표준편차가 정말 0.9야.

제 처음 덧셈이 맞는 경우도 있네요. 분산이 더해지는 건 잡음이 서로 독립일 때고요.

그래요. 표준편차가 더해지느냐 분산이 더해지느냐는 독립이냐 아니냐가 갈라요. 씨앗을 고정해 같은 잡음을 되풀이해 쓰는 코드가 이런 실수를 해요.