상태: 초안 (2026-10-18), 실측. 숫자는 노트북(Apple M1 Max, 10코어, macOS 26.5)의 Python 3.9.6·numpy 2.0.2 로 직접 계산한 시뮬레이션 값입니다. 학습 한 번은 프로세스 하나(numpy 스레드 1개)이고, 학습 8개를 동시에 돌려 한 번에 5~7분 걸렸습니다. 원자료와 코드: 작업 디렉터리
measure/robot-arm-basics/rl/의ppo.py,ttenv.py,exp_rl.py, 결과out/rl/ppo_*_s{0,1,2}.json. 실제 로봇 실험은 없습니다(하드웨어 미실측). 참고 자료는 2026-10-18 에 열람했습니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.
목차
1. 직관: 잘된 쪽으로 조금씩, 그러나 너무 많이는 말고
공을 100번 쳤다고 합시다. 평소보다 잘 들어간 스윙이 있었고 평소보다 못한 스윙이 있었습니다. 다음에는 잘된 스윙과 비슷하게, 못한 스윙과는 다르게 치면 됩니다. 이것이 정책 경사(policy gradient)의 전부입니다. 정책이 그 행동을 고를 확률을, 결과가 평소보다 좋았던 만큼 올립니다.
문제는 「얼마나」입니다. 100번의 경험은 잡음이 많습니다. 한 번 잘 들어간 스윙을 너무 믿고 정책을 크게 바꾸면, 새 정책은 이전 경험이 전혀 말해 주지 않는 영역으로 가 버리고 거기서 무너집니다. PPO(proximal policy optimization, 근접 정책 최적화)는 새 정책이 옛 정책에서 너무 멀어지면 더 이상 밀지 않는 장치를 목적 함수 안에 넣었습니다. 덕분에 같은 경험으로 여러 번 갱신할 수 있습니다. Schulman 외 2017 의 초록도 미니배치 갱신을 여러 에폭 할 수 있다는 점을 새 목적 함수의 장점으로 듭니다.
2. 그림: 잘린 목적 함수
- 가로축은 새 정책과 옛 정책이 같은 행동을 고를 확률의 비율 r 입니다. r = 1 이면 정책이 바뀌지 않은 것입니다.
- 이점 A 가 양수(평소보다 좋았던 행동)면 r 을 키울수록 목적이 커지지만, 1 + ε 를 넘으면 평평해져 더 키울 이유가 사라집니다.
- A 가 음수면 반대로 r 을 줄이되 1 − ε 아래로는 밀지 않습니다.
- 평평한 구간에서는 기울기가 0 이므로, 그 표본은 갱신에 아무 힘도 보태지 않습니다. 이 글의 실측에서 학습 후반 표본의 약 5% 가 이 상태였습니다.
3. 계산
3.1 정책 경사
정책의 성능 J 를 정책 변수 θ 로 미분하면 다음과 같은 꼴이 됩니다.
∇J(θ) = E[ ∇ log π_θ(a | s) · A(s, a) ]
가우시안 정책(평균 μ_θ(s), 표준편차 σ)에서는 로그 확률의 미분이 간단합니다.
log π(a|s) = −½ ((a − μ)/σ)² − log σ − ½ log 2π
∂/∂μ = (a − μ) / σ²
∂/∂ log σ = ((a − μ)/σ)² − 1
평균보다 큰 행동이 좋았다면(A > 0, a > μ) 평균을 그쪽으로 옮기고, 평균에서 멀리 떨어진 행동이 좋았다면 σ 를 키워 더 넓게 탐색합니다.
3.2 이점 추정과 GAE
이점 A = Q − V 를 정확히 알 수는 없으므로 추정합니다. 가치 함수 V 를 따로 학습해 두면 한 걸음 시간차 오차(TD error)를 만들 수 있습니다.
δ_t = r_t + γ V(s_{t+1}) − V(s_t)
GAE: Â_t = δ_t + (γλ) δ_{t+1} + (γλ)² δ_{t+2} + …
λ = 0: Â_t = δ_t 한 걸음만 보고 나머지는 V 를 믿는다(분산 작음, V 가 틀리면 치우침)
λ = 1: Â_t = G_t − V(s_t) 끝까지 실제 보상을 더한다(치우침 없음, 분산 큼)
일반화 이점 추정(generalized advantage estimation, GAE)은 λ 로 그 사이를 고릅니다. Schulman 외 2015 의 초록은 가치 함수로 분산을 크게 줄이는 대신 약간의 치우침을 받아들인다고 설명합니다. 탁구처럼 보상이 100걸음 뒤에 한 번 오는 문제에서 λ = 0 은 학습 초기의 엉터리 V 를 100번 곱씹는 셈이라 위험합니다. 4절에서 잽니다.
3.3 PPO 손실
r_t(θ) = π_θ(a_t | s_t) / π_old(a_t | s_t)
L_clip = E[ min( r_t Â_t , clip(r_t, 1 − ε, 1 + ε) Â_t ) ] (키운다)
L_value = E[ (V(s_t) − (Â_t + V_old(s_t)))² ] (줄인다)
L_ent = E[ 정책의 엔트로피 ] (c_ent 만큼 키운다)
엔트로피 보너스는 σ 가 너무 빨리 줄어 탐색을 멈추는 것을 막습니다.
3.4 구현
자동 미분 없이 numpy 로 손으로 역전파를 썼습니다. 핵심은 잘린 목적의 기울기가 「min 이 자르지 않은 항을 고른 표본」에서만 흐른다는 점입니다.
ratio = np.exp(logp_new - logp_old)
unc, clp = ratio * A, np.clip(ratio, 1 - eps, 1 + eps) * A
w = np.where(unc <= clp, A * ratio, 0.0) # 잘린 표본은 기울기 0
z = (a - mu) / std
d_mu = -(w[:, None] * z / std) / B # 손실(−L_clip)의 평균에 대한 기울기
d_log_std = -(w[:, None] * (z**2 - 1)).sum(0) / B - c_ent
설정은 다음과 같습니다. 정책·가치 신경망은 각각 은닉층 64-64(tanh), 학습률 3×10⁻⁴(Adam), 반복마다 환경 64개로 에피소드 64개를 모은 뒤 10 에폭·미니배치 512 로 갱신, ε = 0.2, γ = 0.99, λ = 0.95, 기울기 노름 0.5 로 자르기, 처음 σ = 0.61 입니다. 300번 반복하면 에피소드 19,200개, 환경 걸음 약 140만 개입니다. 평가는 20번 반복마다 평가 공 1,000개에 확률 없이 평균 행동만 써서 합니다.
4. 실측
4.1 학습 곡선
| 설정(시드 3개) | 마지막 평가 성공률 중앙(최소~최대) | 마지막 KL(옛→새) | 마지막 σ |
|---|---|---|---|
| PPO 기본(ε 0.2, λ 0.95) | 82.9% (76.8~90.9) | 0.007~0.010 | 0.10~0.12 |
| + 엔트로피 0.01 | 87.7% (87.5~87.8) | 0.004~0.008 | 0.18~0.21 |
| GAE λ = 1 | 42.6% (41.9~84.2) | 0.003~0.009 | 0.13~0.15 |
| GAE λ = 0 | 0.0% (0.0~0.4) | 0.018~0.023 | 0.09~0.14 |
| 자르기 없음(같은 10 에폭) | 0.0% (0.0~0.7) | 9.4~25.1 | 0.06~0.10 |
기준선은 무작위 1.5%, 회전 모르는 고전 계획기 40%, 하이브리드 행동 공간의 상한 85% 였습니다(11편).
4.2 읽은 것
- 자르기가 학습을 살립니다. 자르지 않고 같은 데이터로 10 에폭을 돌리면 갱신 한 번에 정책이 옛 정책에서 KL 9~25 만큼 멀어졌습니다(PPO 기본은 0.01 안팎). 한 시드는 처음 60번 반복 동안 36% 까지 올라갔다가 무너졌고, 두 시드는 처음부터 0% 근처였습니다. 같은 데이터를 여러 번 쓰는 것 자체가 위험하고, 자르기가 그 위험을 막는다는 것을 보여 줍니다.
- λ = 0 은 이 문제에서 실패했습니다. 보상이 에피소드 끝에 한 번 오므로 한 걸음 TD 오차는 거의 0 이고, 초기의 엉터리 가치 함수가 신호를 전부 대신합니다. λ = 1 은 한 시드에서 84% 에 닿았지만 두 시드는 43% 근처에 머물렀습니다. 분산이 커서 시드 운에 많이 기댑니다. 중간값 λ = 0.95 가 세 시드 모두 77% 를 넘었습니다.
- 엔트로피 보너스는 이 문제에서 이득이었습니다. 중앙값이 83% 에서 88% 로 올랐고, 시드 사이 범위가 14%p 에서 0.3%p 로 줄었습니다. σ 가 0.1 까지 줄어든 기본 설정은 일찍 탐색을 멈춘 시드(77%)가 있었습니다. 단, 시드 3개라 이 차이를 일반화하기는 어렵습니다(15편).
- 학습된 정책은 관절 한계에 부딪혔습니다. 기본 설정의 마지막 평가에서 관절 한계에 한 번 이상 닿은 에피소드가 시드에 따라 4~79% 였습니다. 보상이 한계를 말하지 않았기 때문입니다. 13편에서 벌점을 넣어 봅니다.
- 같은 정책의 학습용 공 1,000개 성공률은 평가용과 1%p 안에서 같았습니다(예: 84.1% 대 82.9%). 공 분포가 같아서 과적합이 거의 없었습니다. 15편에서 평가 방법을 다룹니다.
4.3 실제 탁구 로봇은 무엇을 썼나
Büchler 외(arXiv 2006.05935 v1)는 공압 근육 팔을 PPO 로 100 Hz 에서 학습했습니다(10편). 반대로 D'Ambrosio 외 2024 는 저수준 기술을 PPO 가 아니라 진화 전략 계열의 블랙박스 최적화(BGS)로 학습했고, PPO·SAC 보다 동작이 매끄러웠다는 것을 이유로 들었습니다(arXiv:2408.03906 II-C 절). 이 글의 기본 PPO 가 관절 한계를 치며 덜컥거리는 정책을 만든 것과 같은 걱정입니다. 「어떤 알고리즘이 더 좋다」가 아니라 「무엇을 원하는가(성공률, 매끄러움, 표본 수)」에 따라 고릅니다.
5. 한계
- 시드 3개, 학습 300번 반복입니다. 기본 설정의 두 시드는 300번에서도 아직 오르고 있었습니다. 더 오래 돌린 결과는 미실측입니다.
- 관측 정규화, 학습률 감쇠, 가치 손실 자르기 같은 흔한 구현 요령을 넣지 않았습니다. 이득 설정(ε, λ, 엔트로피 계수)은 몇 값만 비교했습니다.
- PPO 원논문 본문의 실험 표(ε 값별 비교, MuJoCo 설정)는 PDF 를 텍스트로 열지 못해 확인하지 못했습니다(미확인). 이 글의 ε = 0.2 는 흔히 쓰는 값으로 고른 것입니다.
6. 정리
- 정책 경사는 「평소보다 좋았던 행동」의 확률을 올립니다. 「평소」를 재는 것이 가치 함수이고, 그 차이가 이점입니다.
- GAE 의 λ 는 치우침과 분산을 맞바꿉니다. 보상이 늦게 오는 탁구에서는 λ = 0 이 실패했고 0.95 가 가장 안정적이었습니다.
- PPO 는 비율을 잘라 같은 데이터를 여러 번 쓰면서도 무너지지 않게 합니다. 자르지 않으면 갱신 한 번의 KL 이 기본 설정의 약 1,000~2,500배로 뛰며 무너졌습니다.
- 장난감 진자 라켓에서 PPO 는 공 1만 9천 개로 83~88% 에 닿았습니다. 같은 공에서 회전 모르는 고전 계획기는 40% 였습니다. 다만 그 정책은 관절 한계를 마구 쳤습니다.
티티 한마디. 한 번 잘 친 스윙에 너무 신나서 확 바꾸면 다음 공을 다 놓쳐요. PPO 는 「조금만 바꾸기」를 규칙으로 정해 둔 거예요!
참고
모든 자료는 2026-10-18 에 열람했습니다.
- Schulman, Wolski, Dhariwal, Radford, Klimov, Proximal Policy Optimization Algorithms, arXiv:1707.06347, 초록.
- Schulman, Moritz, Levine, Jordan, Abbeel, High-Dimensional Continuous Control Using Generalized Advantage Estimation, arXiv:1506.02438, 초록.
- Büchler 외, arXiv:2006.05935 v1.
- D'Ambrosio 외, arXiv:2408.03906, II-C 절(학습 알고리즘).