조금씩 더 나은 쪽으로 움직인다는 생각
지난 장에서 우리는 오차의 제곱합을 비용 함수로 만들고, 그 비용이 작아지는 값을 찾는 방법을 배웠다. 직선 맞추기처럼 운이 좋은 문제는 수식으로 한 번에 답을 구할 수 있었다. 하지만 로봇 문제는 늘 그렇게 친절하지 않다.
예를 들어 로봇의 위치를 여러 거리 센서로 추정한다고 해 보자. 센서가 알려 준 거리와 로봇 위치 후보에서 계산한 거리가 얼마나 다른지 비용으로 만들 수는 있다. 그런데 이 비용 함수가 복잡하면 "정답은 이 값입니다" 하고 깔끔하게 풀리지 않는다. 카메라 위치 보정, 로봇 팔 자세 추정, SLAM, 경로 최적화 같은 문제도 마찬가지다.
이때 로봇은 한 번에 정답을 맞히려 하지 않는다. 대신 현재 답에서 출발해 비용이 줄어드는 방향으로 조금 움직이고, 다시 비용이 줄어드는 방향으로 조금 움직인다. 마치 안개 낀 산에서 눈앞의 경사만 보고 한 발씩 내려가는 것과 같다.
이 방식이 바로 경사하강법이다.
경사하강법은 복잡한 로봇 최적화 문제를 조금씩 더 나은 해로 움직이게 하는 기본 엔진이다. 이름은 조금 딱딱하지만, 핵심은 단순하다.
"비용이 가장 빨리 증가하는 방향을 찾고, 그 반대 방향으로 간다."

비용 함수는 지형이다
경사하강법을 이해하려면 비용 함수를 숫자 공식이 아니라 지형으로 보는 것이 좋다. 어떤 위치 후보가 있을 때 비용이 크면 높은 산이고, 비용이 작으면 낮은 골짜기다. 우리가 찾고 싶은 답은 비용이 가장 낮은 곳이다.
먼저 1차원 예제로 시작하자. 로봇이 x축 위에서 목표 위치 5.0에 가까워지고 싶다고 하자. 현재 위치 후보를 x라고 할 때 비용을 다음처럼 만들 수 있다.
[ J(x) = frac{1}{2}(x - x_{goal})^2 ]
여기서 J는 비용 함수다. 앞에 붙은 1/2는 큰 의미가 있는 마법 숫자가 아니다. 나중에 미분했을 때 2가 사라져 식이 깔끔해지도록 붙인 것이다.
import numpy as np
import matplotlib.pyplot as plt
goal_x = 5.0
def cost_1d(x):
return 0.5 * (x - goal_x) ** 2
xs = np.linspace(-2, 10, 300)
ys = cost_1d(xs)
plt.figure(figsize=(8, 4))
plt.plot(xs, ys, label="cost")
plt.scatter([goal_x], [cost_1d(goal_x)], color="red", zorder=3, label="minimum")
plt.xlabel("x")
plt.ylabel("J(x)")
plt.title("1D cost function")
plt.grid(True)
plt.legend()
plt.show()그래프를 보면 목표 위치 x = 5에서 비용이 가장 작다. x가 목표보다 왼쪽에 있어도 비용이 커지고, 오른쪽에 있어도 비용이 커진다. 즉 비용 함수는 그릇처럼 생겼다. 우리는 이 그릇의 가장 낮은 지점으로 내려가고 싶다.
기울기는 가장 빠르게 올라가는 방향이다
이제 중요한 질문이 나온다.
현재 x = 0에 있다면 어느 방향으로 움직여야 비용이 줄어들까?
그래프만 보면 오른쪽으로 가야 한다. 반대로 x = 8에 있다면 왼쪽으로 가야 한다. 이 방향을 알려 주는 것이 기울기다.
1차원에서 비용 함수
[ J(x) = frac{1}{2}(x - x_{goal})^2 ]
의 기울기는 다음과 같다.
[ frac{dJ}{dx} = x - x_{goal} ]
Python으로 쓰면 아주 짧다.
def grad_1d(x):
return x - goal_x기울기의 부호를 살펴보자.
for x in [0.0, 3.0, 5.0, 8.0]:
print(f"x={x:4.1f}, cost={cost_1d(x):5.2f}, gradient={grad_1d(x):5.2f}")예상되는 결과는 다음과 같다.
x= 0.0, cost=12.50, gradient=-5.00
x= 3.0, cost= 2.00, gradient=-2.00
x= 5.0, cost= 0.00, gradient= 0.00
x= 8.0, cost= 4.50, gradient= 3.00x = 0에서는 기울기가 음수다. 1차원 그래프에서 기울기가 음수라는 말은 오른쪽으로 갈수록 비용이 낮아지고, 왼쪽으로 갈수록 비용이 높아진다는 뜻이다. 반대로 x = 8에서는 기울기가 양수다. 오른쪽으로 가면 비용이 더 커지고, 왼쪽으로 가면 비용이 작아진다.
여기서 한 가지 표현을 조심해야 한다. 기울기는 비용이 가장 빠르게 증가하는 방향을 가리킨다. 우리는 비용을 줄이고 싶으므로 기울기 방향으로 가면 안 된다. 기울기의 반대 방향으로 가야 한다.
sample_xs = np.array([0.0, 3.0, 8.0])
plt.figure(figsize=(8, 4))
plt.plot(xs, ys, label="cost")
for x in sample_xs:
y = cost_1d(x)
g = grad_1d(x)
descent_direction = -np.sign(g)
plt.scatter([x], [y], color="black")
plt.arrow(
x, y,
0.8 * descent_direction, 0,
head_width=0.5,
head_length=0.25,
color="red",
length_includes_head=True,
)
plt.text(x, y + 1.0, f"grad={g:.1f}", ha="center")
plt.scatter([goal_x], [0], color="blue", label="goal")
plt.xlabel("x")
plt.ylabel("J(x)")
plt.title("Red arrows point to the descent direction")
plt.grid(True)
plt.legend()
plt.show()빨간 화살표는 기울기의 반대 방향이다. 로봇 입장에서 말하면 "지금 위치 후보에서 비용이 줄어드는 쪽"이다.
경사하강법의 한 줄
경사하강법의 핵심 업데이트는 다음 한 줄로 표현된다.
[ theta_{new} = theta_{old} - alpha nabla J(theta_{old}) ]
여기서 θ는 우리가 찾고 싶은 값이다. 1차원 위치라면 x이고, 2차원 위치라면 [x, y]이며, 로봇 팔의 관절 각도라면 여러 개의 각도 묶음이 된다. ∇J는 비용 함수의 기울기이고, α는 학습률이다.
학습률은 한 번에 얼마나 움직일지를 정하는 값이다. 너무 작으면 조심스럽지만 느리다. 너무 크면 골짜기를 지나쳐 반대편 언덕으로 튀어 오를 수 있다.
먼저 1차원에서 구현해 보자.
def gradient_descent_1d(start_x, lr=0.2, steps=30):
x = float(start_x)
history = []
for step in range(steps):
c = cost_1d(x)
g = grad_1d(x)
history.append((x, c, g))
# 비용을 줄이기 위해 기울기의 반대 방향으로 이동한다.
x = x - lr * g
return history
history = gradient_descent_1d(start_x=-1.0, lr=0.2, steps=20)
for step, (x, c, g) in enumerate(history[:5]):
print(f"step={step:2d}, x={x:7.3f}, cost={c:8.4f}, grad={g:7.3f}")처음에는 x = -1에서 출발한다. 목표는 x = 5다. 기울기 x - goal_x는 음수이므로 x - lr * gradient를 계산하면 x는 오른쪽으로 움직인다. 한 번에 정답까지 순간이동하지 않고, 조금씩 가까워진다.
이 과정을 그림으로 보면 경사하강법의 느낌이 훨씬 선명해진다.
history = gradient_descent_1d(start_x=-1.0, lr=0.2, steps=20)
path_x = np.array([h[0] for h in history])
path_y = cost_1d(path_x)
plt.figure(figsize=(8, 4))
plt.plot(xs, ys, label="cost")
plt.plot(path_x, path_y, "o-", color="red", label="gradient descent path")
plt.scatter([goal_x], [0], color="blue", zorder=3, label="minimum")
plt.xlabel("x")
plt.ylabel("J(x)")
plt.title("Gradient descent in 1D")
plt.grid(True)
plt.legend()
plt.show()점들이 비용 함수의 곡선을 따라 아래쪽으로 내려간다. 실제 로봇이 곡선 위를 달리는 것은 아니다. 여기서 움직이는 것은 로봇의 물리적 위치가 아니라 "답 후보"다. 하지만 목표 추종 제어와 모양이 닮아 있다. 현재 값에서 목표와의 오차를 계산하고, 오차가 줄어드는 방향으로 조금 움직인다.
2차원 위치에서의 기울기
로봇은 보통 1차원 선 위에만 있지 않다. 2D 평면에서 움직인다. 이제 목표 위치를 [3, 2]라고 하자. 위치 후보 p = [x, y]가 있을 때 비용은 다음처럼 만들 수 있다.
[ J(x, y) = frac{1}{2}left((x - x_{goal})^2 + (y - y_{goal})^2right) ]
이 비용은 목표 위치와 현재 위치 후보 사이의 거리 제곱이다. 거리가 멀수록 비용이 커지고, 목표에 도착하면 비용은 0이 된다.
기울기는 각 방향에 대한 변화율을 모은 벡터다.
[ nabla J(x, y) = begin{bmatrix} frac{partial J}{partial x} \ frac{partial J}{partial y} end{bmatrix}
begin{bmatrix} x - x_{goal} \ y - y_{goal} end{bmatrix} ]
전문 용어로 ∂J/∂x는 편미분이라고 한다. 어렵게 느껴질 수 있지만 뜻은 단순하다. "y는 잠깐 고정하고 x만 조금 바꾸면 비용이 얼마나 변하는가?"를 나타낸다. ∂J/∂y도 같은 방식으로 y 방향 변화율을 나타낸다.
코드로 옮기면 다음과 같다.
goal = np.array([3.0, 2.0])
def cost_position(p):
error = p - goal
return 0.5 * np.dot(error, error)
def grad_position(p):
return p - goal이제 비용 지형을 등고선으로 그려 보자. 등고선은 같은 비용을 가진 점들을 연결한 선이다. 지도에서 같은 높이를 연결한 선을 생각하면 된다.
x_grid = np.linspace(-5, 7, 80)
y_grid = np.linspace(-5, 7, 80)
X, Y = np.meshgrid(x_grid, y_grid)
Z = 0.5 * ((X - goal[0]) 2 + (Y - goal[1]) 2)
# 기울기 벡터: 비용이 가장 빠르게 증가하는 방향
Gx = X - goal[0]
Gy = Y - goal[1]
plt.figure(figsize=(6, 6))
plt.contour(X, Y, Z, levels=20)
plt.quiver(
X[::8, ::8], Y[::8, ::8],
Gx[::8, ::8], Gy[::8, ::8],
color="gray", alpha=0.6
)
plt.scatter([goal[0]], [goal[1]], color="red", label="minimum")
plt.axis("equal")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Gradient vectors point uphill")
plt.grid(True)
plt.legend()
plt.show()회색 화살표는 기울기 벡터다. 화살표가 목표에서 바깥쪽으로 뻗어 나가는 것을 볼 수 있다. 왜냐하면 목표에서 멀어질수록 비용이 커지기 때문이다. 따라서 비용을 줄이려면 이 회색 화살표의 반대 방향으로 움직여야 한다. 즉 바깥에서 안쪽 골짜기로 내려와야 한다.
2차원 경사하강법 구현하기
이제 실제로 2차원 위치 후보를 최적화해 보자. 시작 위치 후보는 [-4, -3]이고, 목표는 [3, 2]다.
def gradient_descent_position(start, lr=0.2, steps=50):
p = np.array(start, dtype=float)
path = [p.copy()]
costs = [cost_position(p)]
for step in range(steps):
g = grad_position(p)
p = p - lr * g
path.append(p.copy())
costs.append(cost_position(p))
return np.array(path), np.array(costs)
start = np.array([-4.0, -3.0])
path, costs = gradient_descent_position(start, lr=0.2, steps=30)
print("final position:", path[-1])
print("final cost:", costs[-1])경사하강법은 매번 다음 계산을 반복한다.
g = p - goal
p = p - lr * g현재 위치 후보가 목표보다 왼쪽 아래에 있다면 p - goal은 왼쪽 아래를 향하는 벡터다. 그 반대 방향인 -g는 오른쪽 위, 즉 목표 방향이다. 그래서 위치 후보는 목표 쪽으로 이동한다.
등고선 위에 이동 경로를 그려 보자.
plt.figure(figsize=(6, 6))
plt.contour(X, Y, Z, levels=20)
plt.plot(path[:, 0], path[:, 1], "o-", color="red", label="descent path")
plt.scatter([start[0]], [start[1]], color="black", label="start")
plt.scatter([goal[0]], [goal[1]], color="blue", label="goal")
plt.axis("equal")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Gradient descent for 2D position")
plt.grid(True)
plt.legend()
plt.show()출발점에서 목표점으로 점들이 부드럽게 다가간다. 이 예제에서는 비용 함수가 아주 단순해서 길을 잃을 일이 없다. 하지만 원리는 복잡한 문제에서도 같다. 현재 해에서 비용의 기울기를 구하고, 그 반대 방향으로 해를 조금 바꾼다.
학습률이 너무 작거나 너무 크면 어떻게 될까
경사하강법에서 가장 먼저 만나는 조절 손잡이는 학습률 lr이다. 학습률은 수식에서 α로 쓴다.
[ p_{new} = p_{old} - alpha nabla J(p_{old}) ]
학습률이 작으면 안전하다. 하지만 답까지 가는 데 많은 반복이 필요하다. 학습률이 적당하면 빠르게 내려간다. 학습률이 너무 크면 목표를 지나쳐 반대편으로 튀고, 심하면 비용이 점점 커진다.
직접 실험해 보자.
learning_rates = [0.02, 0.2, 1.3, 2.1]
plt.figure(figsize=(8, 4))
for lr in learning_rates:
path_lr, costs_lr = gradient_descent_position(start, lr=lr, steps=25)
plt.plot(costs_lr, marker="o", label=f"lr={lr}")
plt.yscale("log")
plt.xlabel("step")
plt.ylabel("cost (log scale)")
plt.title("Effect of learning rate")
plt.grid(True)
plt.legend()
plt.show()lr=0.02는 비용이 줄어들지만 느리다. 작은 걸음으로 조심조심 내려가는 모습이다. lr=0.2는 안정적으로 빠르게 줄어든다. lr=1.3은 목표를 한 번 지나칠 수 있지만 그래도 다시 돌아오며 줄어든다. lr=2.1은 이 단순한 문제에서도 불안정해질 수 있다.
이번 비용 함수에서는 학습률이 2보다 커지면 발산하기 쉽다. 하지만 이 숫자가 모든 문제에 적용되는 규칙은 아니다. 비용 함수의 모양, 파라미터의 스케일, 데이터 크기에 따라 안정적인 학습률은 달라진다. 그래서 로봇 최적화에서는 학습률을 실험적으로 조정하거나, 더 똑똑한 최적화 알고리즘을 사용하기도 한다.
경로도 함께 보면 학습률 차이가 더 잘 보인다.
plt.figure(figsize=(7, 7))
plt.contour(X, Y, Z, levels=20)
for lr in learning_rates:
path_lr, costs_lr = gradient_descent_position(start, lr=lr, steps=12)
plt.plot(path_lr[:, 0], path_lr[:, 1], "o-", label=f"lr={lr}")
plt.scatter([goal[0]], [goal[1]], color="black", s=80, label="goal")
plt.axis("equal")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Paths with different learning rates")
plt.grid(True)
plt.legend()
plt.show()작은 학습률은 촘촘한 발자국을 남긴다. 적당한 학습률은 목표를 향해 시원하게 다가간다. 너무 큰 학습률은 골짜기 주변을 크게 흔들며 지나가거나 아예 멀어진다. 경사하강법은 단순하지만, 이 학습률 하나 때문에 성격이 크게 달라진다.
목표 위치에 가까워지는 최적 위치 찾기
이제 경사하강법을 조금 더 로봇다운 형태로 정리해 보자. 로봇이 목표 위치에 가까워지고 싶다. 목표 위치는 goal, 현재 위치 후보는 p다. 비용 함수는 목표와의 거리 제곱으로 둔다.
def optimize_position(start, goal, lr=0.2, max_steps=100, tol=1e-4):
p = np.array(start, dtype=float)
goal = np.array(goal, dtype=float)
history = []
for step in range(max_steps):
error = p - goal
cost = 0.5 * np.dot(error, error)
grad = error
grad_norm = np.linalg.norm(grad)
history.append({
"step": step,
"position": p.copy(),
"cost": cost,
"grad_norm": grad_norm,
})
if grad_norm < tol:
break
p = p - lr * grad
return p, history
best_position, history = optimize_position(
start=[-4.0, -3.0],
goal=[3.0, 2.0],
lr=0.2,
max_steps=100,
)
print("best position:", best_position)
print("steps:", len(history))
print("last cost:", history[-1]["cost"])여기서 tol은 멈춤 조건이다. 기울기의 크기가 충분히 작아지면 더 움직여도 큰 변화가 없다고 보고 반복을 멈춘다. 목표 위치에 거의 도착하면 오차가 작아지고, 오차가 작아지면 기울기도 작아진다.
기울기의 크기는 다음처럼 계산했다.
grad_norm = np.linalg.norm(grad)이 값은 "지금 비용 지형이 얼마나 가파른가?"를 나타낸다. 가파르면 아직 더 내려갈 곳이 많고, 거의 0이면 바닥에 가까운 상태다.
최적화 과정을 애니메이션처럼 보고 싶다면 간단히 프레임을 그릴 수 있다.
positions = np.array([h["position"] for h in history])
step_numbers = np.array([h["step"] for h in history])
plt.figure(figsize=(7, 7))
plt.contour(X, Y, Z, levels=20)
plt.plot(positions[:, 0], positions[:, 1], "o-", color="red")
for i in range(0, len(positions), max(1, len(positions) // 5)):
plt.text(positions[i, 0], positions[i, 1], str(step_numbers[i]))
plt.scatter([goal[0]], [goal[1]], color="blue", s=80, label="goal")
plt.axis("equal")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Optimization steps toward the target")
plt.grid(True)
plt.legend()
plt.show()이 예제에서 최적 위치는 당연히 목표 위치다. 너무 쉬워 보일 수 있다. 하지만 중요한 것은 답 자체가 아니라 구조다.
비용 함수를 만든다. 기울기를 구한다. 기울기의 반대 방향으로 파라미터를 업데이트한다. 비용이 충분히 작아질 때까지 반복한다.
이 네 단계는 앞으로 더 복잡한 로봇 문제에서도 계속 등장한다.
기울기를 직접 모를 때: 숫자로 미분하기
지금까지는 기울기를 손으로 쉽게 구할 수 있었다. 하지만 실제 로봇 문제에서는 비용 함수가 길고 복잡할 때가 많다. 카메라가 본 점과 지도 점의 오차, 로봇 팔 끝점의 위치 오차, 장애물과의 거리 비용이 섞이면 기울기를 손으로 쓰기 귀찮아진다.
그럴 때는 아주 작은 변화량을 넣어 비용이 얼마나 바뀌는지 보고 기울기를 근사할 수 있다. 이것을 수치 미분이라고 한다.
중심 차분 방식은 다음 아이디어를 사용한다.
[ frac{partial J}{partial x_i} approx frac{J(x_i + epsilon) - J(x_i - epsilon)}{2epsilon} ]
ε는 아주 작은 숫자다. Python으로 구현해 보자.
def numerical_gradient(f, p, eps=1e-5):
p = np.array(p, dtype=float)
grad = np.zeros_like(p)
for i in range(len(p)):
step = np.zeros_like(p)
step[i] = eps
cost_plus = f(p + step)
cost_minus = f(p - step)
grad[i] = (cost_plus - cost_minus) / (2 * eps)
return grad앞에서 만든 위치 비용 함수에 대해 손으로 구한 기울기와 수치 미분 기울기를 비교해 보자.
test_p = np.array([0.0, 0.0])
analytic_grad = grad_position(test_p)
numeric_grad = numerical_gradient(cost_position, test_p)
print("analytic gradient:", analytic_grad)
print("numeric gradient: ", numeric_grad)두 값은 거의 같게 나온다. 수치 미분은 편리하지만 비용 함수 계산을 여러 번 해야 하므로 느릴 수 있다. 파라미터가 100개라면 기울기 한 번을 구하기 위해 비용 함수를 200번 가까이 계산해야 한다. 그래서 큰 문제에서는 직접 기울기를 유도하거나, 자동 미분을 지원하는 도구를 쓰기도 한다.
그래도 수치 미분은 학습용으로 매우 좋다. 내가 만든 비용 함수가 예상대로 작동하는지 확인할 때도 유용하다.
수치 미분을 사용한 경사하강법은 다음처럼 작성할 수 있다.
def gradient_descent_numeric(f, start, lr=0.2, steps=50, tol=1e-4):
p = np.array(start, dtype=float)
path = [p.copy()]
costs = [f(p)]
for step in range(steps):
g = numerical_gradient(f, p)
if np.linalg.norm(g) < tol:
break
p = p - lr * g
path.append(p.copy())
costs.append(f(p))
return np.array(path), np.array(costs)
path_num, costs_num = gradient_descent_numeric(
cost_position,
start=[-4.0, -3.0],
lr=0.2,
steps=50,
)
print("final position with numeric gradient:", path_num[-1])이 코드는 기울기 공식을 몰라도 작동한다. 비용 함수 f만 있으면 된다. 이것이 최적화의 매력이다. 로봇에게 "무엇이 나쁜지"를 비용으로 알려 주면, 경사하강법은 그 나쁨을 줄이는 방향을 찾아 조금씩 움직인다.
최소제곱법과 경사하강법의 연결
지난 장에서 최소제곱법은 오차의 제곱합을 줄이는 방법이라고 했다. 경사하강법도 비용 함수를 줄이는 방법이다. 따라서 둘은 자연스럽게 만난다.
예를 들어 어떤 위치 후보 p와 목표 goal 사이의 오차를
[ e = p - goal ]
라고 하면 최소제곱 비용은
[ J(p) = frac{1}{2} e^T e ]
이다. 이것은 앞에서 쓴 거리 제곱 비용과 같다. 기울기는 p - goal이고, 경사하강법은 이 오차를 줄이는 방향으로 위치 후보를 업데이트한다.
더 복잡한 센서 문제에서는 오차가 단순히 p - goal이 아닐 수 있다. 예를 들어 거리 센서가 여러 기준점까지의 거리를 측정한다고 하자. 위치 후보 p에서 각 기준점까지의 예측 거리와 실제 측정 거리의 차이를 오차로 만들 수 있다.
landmarks = np.array([
[0.0, 0.0],
[6.0, 0.0],
[0.0, 5.0],
])
true_position = np.array([2.0, 1.5])
measured_distances = np.linalg.norm(landmarks - true_position, axis=1)
def distance_sensor_cost(p):
predicted = np.linalg.norm(landmarks - p, axis=1)
residuals = predicted - measured_distances
return 0.5 * np.sum(residuals ** 2)이 비용 함수는 위치 후보가 실제 위치와 가까울수록 작아진다. 하지만 거리에는 제곱근이 들어 있어서 앞의 단순한 목표 비용보다 기울기가 조금 귀찮다. 이럴 때 수치 미분 경사하강법으로도 위치를 추정할 수 있다.
path_est, costs_est = gradient_descent_numeric(
distance_sensor_cost,
start=[5.0, 4.0],
lr=0.1,
steps=100,
)
print("estimated position:", path_est[-1])
print("true position:", true_position)그리고 비용 지형 위에 추정 경로를 그려 볼 수 있다.
x_grid = np.linspace(-1, 7, 100)
y_grid = np.linspace(-1, 6, 100)
X, Y = np.meshgrid(x_grid, y_grid)
Z = np.zeros_like(X)
for i in range(X.shape[0]):
for j in range(X.shape[1]):
Z[i, j] = distance_sensor_cost(np.array([X[i, j], Y[i, j]]))
plt.figure(figsize=(7, 6))
plt.contour(X, Y, Z, levels=30)
plt.plot(path_est[:, 0], path_est[:, 1], "o-", color="red", label="gradient descent")
plt.scatter(landmarks[:, 0], landmarks[:, 1], marker="^", s=100, label="landmarks")
plt.scatter([true_position[0]], [true_position[1]], color="blue", s=80, label="true position")
plt.axis("equal")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Position estimation with distance sensor cost")
plt.grid(True)
plt.legend()
plt.show()이 예제는 작은 위치 추정 문제다. 실제 로봇에서는 측정값에 노이즈가 들어가고, 기준점이 많아지고, 로봇 자세까지 함께 추정해야 한다. 그래도 큰 흐름은 같다. 측정값과 예측값의 차이를 비용으로 만들고, 그 비용을 줄이는 방향으로 위치 후보를 업데이트한다.
경사하강법을 사용할 때 기억할 점
경사하강법은 강력하지만 만능은 아니다. 특히 로봇 문제에서는 다음 세 가지를 자주 만나게 된다.
비용 지형이 단순한 그릇 모양이 아닐 수 있다. 여러 골짜기가 있으면 시작점에 따라 다른 해에 도착할 수 있다.
파라미터의 스케일이 다르면 학습률 선택이 어려워진다. 예를 들어 위치는 미터 단위이고 각도는 라디안 단위라면 한 학습률로 모두 다루기 까다로울 수 있다.
센서 노이즈가 크면 비용 함수도 흔들린다. 그래서 필터링, 가중치, 튼튼한 비용 함수 같은 보조 장치가 필요해진다.
하지만 이런 어려움에도 경사하강법은 로봇 최적화의 출발점으로 매우 중요하다. 더 고급 알고리즘도 결국 "어느 방향으로 해를 바꿔야 비용이 줄어드는가?"라는 질문에서 출발한다.
로봇 관점에서 다시 보기
경사하강법을 로봇의 언어로 번역하면 다음과 같다.
현재 로봇은 어떤 해 후보를 가지고 있다. 그것은 위치일 수도 있고, 지도일 수도 있고, 로봇 팔의 관절 각도일 수도 있다. 로봇은 그 해가 얼마나 나쁜지 비용 함수로 계산한다. 그리고 그 비용이 가장 빠르게 증가하는 방향, 즉 기울기를 구한다. 비용을 줄이려면 그 반대 방향으로 해를 조금 수정한다. 이 일을 여러 번 반복하면 해는 점점 좋아진다.
이번 장에서 다룬 목표 위치 비용은 아주 단순했다. 하지만 다음 장에서 경로 최적화를 다루면 파라미터는 하나의 위치가 아니라 여러 개의 경로점이 된다. 비용 함수도 더 흥미로워진다. 경로가 짧을수록 좋고, 너무 꺾이지 않을수록 좋고, 장애물에서 멀수록 좋다. 그러면 경사하강법은 경로 전체를 조금씩 밀고 당기며 더 나은 길을 찾아간다.
즉 오늘 배운 한 줄,
[ theta_{new} = theta_{old} - alpha nabla J(theta_{old}) ]
은 다음 장에서 로봇의 길을 바꾸는 엔진이 된다. 비용을 만들 수 있다면, 로봇은 그 비용을 줄이는 방향으로 움직일 수 있다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.