이번 단계 목표: 11번의 gaitpush 정책에 남은 두 문제 — 왼 무릎 뻗정다리(절뚝임)와 무릎 토크 97% 포화 — 를 토크 벌점 + 좌우 대칭 보상으로 잡습니다. 시리즈에서 반복된 “채점 안 하면 엉뚱한 데로 샌다”의 마지막 교정입니다.
발단: 재검증이 찾아낸 두 구멍
11번에서 걸음 품질과 강건함을 다 잡았다고 했지만, 재검증 계측이 두 가지를 더 찾아냈습니다.
구멍 1 — 절뚝임. 왼 무릎이 −6°에 고정된 뻗정다리, 오른 무릎만 −2~44°로 일하는 비대칭 걸음. 채점표에 “좌우 대칭”이 없었으니까요.
구멍 2 — 토크 포화 97%. 실제로 걸린 토크를 재 보니 무릎이 시간의 97% 동안 실물 한계(139Nm)에 붙어 있었습니다. Genesis는 URDF 토크 한계로 자동 클램프하는데, 정책이 PD 목표를 실제 각도에서 일부러 멀리 던져 이 클램프를 ‘최대 힘 페달’처럼 밟고 있었던 겁니다. 채점표에 토크가 없었으니, 힘은 공짜였습니다. (06번의 셔플 정책도 발목 토크를 31% 포화시키고 있었습니다 — 오래된 버릇이었던 거죠.)
실물 모터는 피크 토크를 상시 낼 수 없으니, 이대로는 아무리 예쁘게 걸어도 실물 로봇으로 옮길 수가 없습니다.
처방: 채점표에 두 항목 추가
# --task sym = gaitpush의 14개 항목 + 아래 둘
"torques": -1e-5, # 낸 토크의 제곱에 벌점 — 힘은 공짜가 아니다
"gait_symmetry": -1.0, # 좌우 비대칭 벌점 — 절뚝이지 마라
토크 벌점은 간단합니다. 매 스텝, 모터가 낸 토크의 제곱합에 벌점 — 139Nm에 상시 기대면 비싸고, 30~50Nm로 걸으면 쌉니다. 힘의 ‘가격표’를 붙인 것.
대칭 벌점이 이번 단계의 재미있는 부분입니다. 걷기에서 좌우 대칭이란 “왼다리와 오른다리가 같은 값”이 아니라(그건 두 발 모아 뛰기죠), “왼다리의 지금 궤적 = 반 박자(0.4초) 전 오른다리 궤적의 거울상”입니다. 그래서:
- 관절각 이력을 반 박자만큼 링 버퍼에 저장해 두고,
- 매 스텝
왼다리(지금)와오른다리(반 박자 전)를 비교합니다. - ‘거울상’이므로 pitch류(굽힘)는 같은 부호, roll/yaw류(벌림·비틀림)는 반대 부호로 뒤집어 비교합니다.
cd scripts/train
python g1_train.py --task sym --max_iterations 3000 # 학습 (GPU)
python g1_eval.py --task sym --ckpt 2999 --record # 조종 시나리오
python g1_eval.py --task push -e g1-sym --ckpt 2999 # 밀치기 재시험
python g1_gait_metrics.py -e g1-sym --ckpt 2999 --vx 0.4 # 걸음 품질 재측정
결과
학습 41분(3000 iteration), 보상 25.6 수렴. 재시험 결과, 노린 두 구멍은 둘 다 막혔습니다:
| 지표 | 11 gaitpush | 12 sym | 판정 |
|---|---|---|---|
| 무릎 가동범위 (왼/오른) | −6~−6° / −2~44° | −5~40° / −5~39° | 절뚝임 해소 — 완전 대칭 |
| 무릎 토크 | 최대 139Nm, 포화 97% | 최대 32Nm, 포화 0% | 실물 한계의 23%만 사용 |
| 발목 토크 포화 | 0% | 1% | (순간 접촉 수준) |
| 걸음 빈도/보폭/발 간격 | 1.25Hz / 32cm / 15cm | 1.25Hz / 30cm / 16cm | 걸음 품질 유지 |
| 발 들림 (왼/오른) | 2.0 / 4.0cm | 3.5 / 4.4cm | 좌우 격차도 축소 |
| 밀치기 4회 | 4/4 회복 | 4/4 회복 (기울기 6~15°) | 강건함 유지 |
| 회전 추종 (좌/우) | +112° / −113° (목표 ±115°) | +92° / −97° | 저하 (약 20% 덜 돎) |
무릎이 좌우 똑같이 40°까지 굽고, 토크는 한계의 4분의 1만 씁니다. 걸음·강건함도 그대로. 그런데 회전이 둔해졌습니다. 왜일까요?
회전은 본질적으로 비대칭 동작이기 때문입니다. 도는 동안 안쪽 다리와 바깥쪽 다리는 다른 궤적을 그려야 하는데, 우리 대칭 벌점은 그 차이에도 벌점을 매깁니다. 절뚝임을 잡으려고 넣은 채점 항목이 회전 능력의 일부를 대가로 가져간 것 — 이번 시리즈에서 네 번째로 만나는 트레이드오프입니다. (개선 후보: 회전 명령이 클 때는 대칭 벌점을 줄이는 ‘명령 조건부 대칭’. 이 정도 오차는 상위 제어기가 명령을 더 주는 것으로도 보상할 수 있어, 여기서는 기록만 하고 멈춥니다.)

전체 영상:
(조종),
(밀치기)
회고 — 막혔던 점 & 배운 것
- 채점표는 ‘완성’되지 않는다, ‘수렴’할 뿐. 셔플 → 절뚝임+토크 페달 → 회전 둔화. 구멍을 막을 때마다 최적화는 다음으로 싼 자유도를 찾아냈고, 우리는 채점 항목을 8개에서 16개까지 늘렸습니다. 매번 손해는 점점 작아졌습니다(걸음 전체가 가짜 → 한쪽 무릎 → 회전 20%). 보상 설계란 완벽한 명세를 한 번에 쓰는 일이 아니라, 측정→발견→보강의 순환을 손해가 감당할 만해질 때까지 돌리는 일이었습니다.
- 대칭은 시간을 접어야 정의된다. “왼다리 = 오른다리”가 아니라 “왼다리(지금) = 오른다리 (반 박자 전)의 거울상”이라는 정의에 도달하는 게 이번 구현의 핵심이었습니다. 링 버퍼 하나로 끝나는 코드지만, 정의를 잘못 잡으면(순간 대칭) 두 발 모아 뛰기를 가르치게 됩니다.
- 벌점끼리도 싸운다. 대칭 벌점 vs 회전 추종 보상의 충돌은 예상 못 했습니다. 항목을 추가할 때 “이 항목이 막는 것”뿐 아니라 “이 항목이 함께 막아버리는 것”도 물어야 합니다.
- 토크 벌점은 싸게 먹혔다. −1e-5라는 작은 가격표만으로 무릎 토크가 139→32Nm로 내려왔고 걸음 품질은 그대로였습니다. 애초에 139Nm가 ‘필요’했던 게 아니라 ‘공짜라서’ 썼을 뿐이라는 게 증명된 셈입니다.
다음 단계
남은 오차 둘(회전 저하, 발 들림 4cm)을 청소하러 갑니다 — 그중 하나는 뜻밖에도 우리 계산 실수였습니다 → 13. 회전 게이트와 진짜 8cm
이 시리즈의 전체 글 목록 → 시리즈 목차