[기초] 강화학습으로 Cart-pole 제어하기
코드베이스는 여기에 정리해 두었다.
강화학습 워크플로우 배우기
환경이란?
환경은 4개의 질문데 답하는 시스템이다.
- 지금 상태?
- 지금 할 수 있는 일?
- 방금 행동은 얼마나 좋았나?
- 이번 판은 끝났나?
이 프로젝트에서 환경은 cartpole swingup 이다.
환경 안에는 아래의 요소가 들어있다.
- 물리 상태
- 위치, 속도, 각도, 가속도 등
- 행동 입력
- 에이전트가 카트에 어느 방향으로 얼마나 힘을 줄지
- 보상 규칙
- 지금 행동이 목표에 얼마나 도움이 됐는지 점수로 환산
- 종료 조건
- 한 에피소드를 언제 끝낼지
한 판(에피소드)은 보통 이렇게 흘러간다.
- 환경을 초기화 한다.
- 어떤 초기 자세로 시작한다.
- 환경이 현재 상태를 준다.
- 이것이
observation이다. - 에이전트는 내부 물리 전체를 직접 보는게 아니라, 환경이 건네준 관측값만 본다.
- 이것이
- 에이전트가 행동을 고른다.
- 이것이
action이다. - 여기서는 카트를 얼마나 밀 것인가에 해당한다.
- 이것이
- 환경이 그 행동을 물리에 적용한다.
- 카트가 움직이고, 막대가 흔들리며 다음 상태가 만들어진다.
- 환경이 계산해서 에이전트에게 보상을 준다.
- 이것이
reward이다. - 목표에 가까워졌으면 큰 점수, 아니면 작은 점수 또는 불리한 점수를 준다.
- 에이전트는 결국 어떤 행동 패턴이 장기적으로 reward를 크게 만드는가를 배운다.
- 이것이
- 환경이 끝났는지 종료를 알려준다.
- 아직 진행중이면 2번부터 반복한다.
위 반복 하나를 하나의 step 이라고 한다.
라이브러리끼리 어떻게 연결되는가?
이 프로젝트는 3개의 라이브러리가 붙어있다.
- dm_control
- 물리 엔진과 태스크를 제공한다.
- 카트와 막대의 물리 상태를 계산
- 행동 넣었을 대 다음 상태 계산
- reward 계산
- epsiode 관련 timestep 반환
-> 세상이 어떻게 움직이는가를 담당한다.
하지만 자기 방식의 api를 사용하기 때문에 강화학습 라이브러리가 기대하는 Gymnasium 형식과는 다르다.
- Gymnasium
- 환경 인터페이스의 표준이다.
- 강화학습 코드 쪽에서는 보통 환경이 이런 형태여야 한다.
- reset() 가능
- step(action) 가능
- observation_space, action_space 정의되어 있음
-> 환경은 이런 방식으로 말해야 한다는 약속이다.
물리를 계산하는 것이 아니라 표준 규약만 제공한다.
- Stable-Baselines3
- 학습 알고리즘 구현체이다.
- 이 프로젝트에서는
SAC를 사용한다. - observation을 입력으로 받는다.
- action을 선택한다.
- 환경과 상호작용하며 데이터를 모은다.
- 그 데이터로 정책을 업데이트 한다.
-> 어떻게 배울 것인가를 담당한다.
dm_control과 SB3 사이의 브리지 역할을 해주기 위해서 wrapper 라고 하는 어댑터 코드(wrapper)를 작성해야한다.
시뮬레이터 + 표준 인터페이스 + 학습기 구조인 것이다.
환경을 만드는 코드를 한 곳에서 관리하는 이유는?
환경을 생성하는 과정을 한 코드에서 관리하는 이유는
- 학습용 환경
- 평가용 환경
- render용 환경
이 환경들에 같은 규칙이 적용되어야 하기 때문이다.
나중에 seed나 설정이 미묘하게 달라지는 문제를 막을 수 있다.
observation/action space가 정의 되어야 하는 이유는?
SB3가 환경이 어떤 입력과 출력을 갖는지 알아야하기 때문이다.
observation이나 action이 몇 차원인지, 연속값인지 이산값인지, 최소/최대 범위와 같은 정보들이 필요하다.
action을 왜 [-1,1] 범위로 맞췄나?
이 프로젝트에서는 action이 [-1,1] 범위인데, 환경의 실제 액추에이터 범위와 학습 알고리즘이 다루기 편한 범위가 꼭 같지 않을수도 있다.
그래서 wrapper가 중간에서
- 에이전트는 [-1,1] 범위의 action만 출력
- wrapper가 그걸 실제 dm_control action 범위로 변환
이런 역할들을 한다.
학습 루프가 어떻게 도는가?
단순하게
- 환경 초기화
- 현재 상태 받기
- 행동 선택
- 환경에 행동 적용
- 다음 상태와 보상 받기
- 그 경험으로 정책 업데이트
- 반복
실제 프로젝트에서는 실험 관리가 필요하다.
- seed 고정
- 학습용/평가용 env 분리
- 중간 평가
- 모델 저장
- 로그 기록
이 프로젝트의 코드 기준으로 진행 순서를 설명하자면
- 설정 읽기
- configs/sac_cartpole_swingup.yaml 에서 설정을 읽는다.
- 설정에는 이런 값이 있다.
- seed
- 총 학습 timestep 수
- learning rate
- batch size
- 평가 주기
- 저장 주기
이 파일은 한마디로 실험 조건표이다.
- seed 고정
- utils/seed.py 로 시드를 고정한다.
- 초기 상태가 달라지거나
- action 샘플링이 달라지거나
- 학습 결과가 달라지는 것을 막을 수 있다.
- seed를 고정하면 완전히 같지는 않아도 비슷한 조건으로 다시 실험 할 수 있다.
- utils/seed.py 로 시드를 고정한다.
- 환경 만들기
- utils/make_env.py 로 환경을 만든다.
- 학습용 환경
- 에이전트가 계속 경험을 쌓는 곳
- 평가용 환경
- 지금 실력이 어느정도인지 체크하는 곳
- 학습용 환경
- utils/make_env.py 로 환경을 만든다.
- 환경 점검
- check_env와 random rollout을 한다.
- 알고리즘 문제
- 환경 버그
- 보통 두 가지 원인으로 RL이 실패하기 때문이다.
- 환경이 Gymnasuim 규약을 지켰는지 확인한다.
- check_env와 random rollout을 한다.
- 학습 루프 시작
- 환경이 reset() 되면
- observation 값이 나온다.
- 에이전트가 action을 고름
- 학습 초반에는 행동이 미숙해서 막대를 잘 세우지 못할 것이다.
- 환경이 그 action을 적용
- action을 받아 실제 물리를 한 step 진행한다.
- 결과로
- 다음 ovservation
- reward
- terminated
- truncated
- 위 항목을 돌려준다.
- 에이전트 너가 방금 이렇게 행동 했더니 결과가 이렇다 하고 알려주는 단계이다.
- 경험을 저장
- 이 한 번의 환경과 에이전트의 상호작용이 학습 데이터가 된다.
- 강화학습에는 보통 이런 형태의 경험이 쌓인다.
- 현재 상태
- 행동
- 보상
- 다음 상태
- 종료 여부
- 이걸 transition 이라고 하고 SAC는 이런 transition들을 replay buffer에 쌓아두고 학습에 사용한다.
- 모델 업데이트
- 충분한 데이터가 쌓이면, SAC가 그 데이터를 꺼내서 정책을 업데이트한다.
- 쌓인 경험을 사용해 반복적으로 업데이트 한다.
- 흐름
- 환경에서 경험 수집
- 버퍼에 저장
- 버퍼에서 샘플 뽑기
- 신경망 업데이트
- 에피소드 종료 여부 확인
- terminated나 truncated가 True면 한 판이 끝난다.
- 그러면 다시 reset() 해서 새 판을 시작한다.
- 반복한다.
한 스텝 단위 말고 큰 흐름을 보면
- 환경에서 데이터를 모은다.
- 데이터로 정책을 학습한다.
- 조금 더 나은 정책으로 다시 데이터를 모은다.
- 다시 학습한다.
이런 선순환이 계속 일어나는게 강화학습이다.
중간 평가를 왜 하는가?
- utils/callbacks.py 에서 학습이 잘 되는지 중간중간 확인을 한다.
- 중간 평가를 통해 알 수 있는 건
- reward가 올라가고 있는지
- 이번 모델이 이전보다 좋은지
- best model을 따로 저장해야 하는지
강화학습은 중간에 성능이 흔들릴 수 있어서, 마지막 모델이 항상 최고 모델은 아니다.
따라서 마지막 모델과 최고의 모델을 둘 다 저장하는게 의미가 있다.
왜 로그를 남기는가?
로그로는 이런 것들을 확인한다.
- 평균 episode reward
- 평가 reward
- 학습 진행도
- TensorBoard 곡선
지금 뭐가 일어나고 있는지를 나중에 볼 수 있게 하는 장치다.
학습 결과를 읽는 방법
강화학습은 겉으로는 돌아가도 실제도는 이상하게 배웠을 수 있다.
예를 들어
- reward는 조금 오르는데 실제 행동은 불안정함
- 몇 번은 잘 되는데 평균적으로는 형편없음
- 마지막 모델은 안 좋은데 중간 모델은 좋았음
- 숫자는 괜찮은데 실제 목표 행동과 다름
따라서 학습 결과는 숫자와 영상을 둘 다 봐야한다.
이 프로젝트에서는 evaluate.py와 play.py가 그 역할을 한다.
왜 학습 결과를 따로 평가해야 하나?
학습중에도 reward 로그가 찍히긴 하지만 학습중 로그는 항상 신뢰할 수 없기 때문이다.
그래서 보통은 학습과 별도로 저장된 모델을 다시 불러와서 지금 이 정책을 시험 삼아 여러 판 돌리면 얼마나 잘하는지 측정한다.
숫자로 볼 때 무엇을 봐야하나?
여러 episode를 돌린 뒤 통계를 낸다.
주요하게 볼 값은
- Mean episode reward
- 평균적으로 얼마나 reward를 얻는지
- 높아지면 대체로 성능이 좋아졌다고 볼 수 있다.
- Std episode reward
- 표준편차(성능의 들쭉날쭉함)
- 높다는 것은 어떤 판은 잘 하고 어떤 판은 못했다는 의미이다.
- Min episode reward
- 최악의 에피소드에서 조차 괜찮은지 판단할 때 사용한다.
- Max episode reward
- 최고점도 중요하지만 최저점도 같이 봐야한다.
- Success rate
- 성공 기준이 있는 경우 얼마나 성공했는지 평균 reward 보다 직관적으로 알 수 있다.
영상은 왜 같이 봐야하는가?
숫자는 요약값이라 실제 행동 패턴을 숨길 수 있다.
그래서 영상에서 아래의 요소들을 확인해야한다.
- 막대를 실제로 위로 올리는가
- 올린 뒤 안정적으로 유지하는가
- 카트 움직임이 지나치게 거칠지 않은가
- 목표 행동인가 꼼수인가
강화학습에서는 reward hacking이라는 꼼수가 있을 수 있어서 사람이 보기에는 행동이 이상할 수 있다.
좋은 결과의 기준
- 평균 reward가 일정 수준 이상
- 표준 편차가 낮음
- 성공률이 높음
- 영상에서도 자연스러움
best model과 latesdt model을 나눠 저장하는 이유?
학습이 항상 단조롭게 좋아지지 않기 때문이다.
예를 들어, 3k step에서는 잘하다가 5k step에서 오히려 나빠질 수 있다.
결과 체크리스트
[ ] 평균 성능
[ ] 변동성(표준 편차)
[ ] 성공률
[ ] 실제 영상
[ ] best vs latest 비교
앞으로 어디를 바꿔가며 실험해볼 수 있을까?
이제 뭐를 바꿔보면 학습 결과가 달라지는지 생각해볼 단계이다.
강화학습은 한 번 돌리고 끝나는 게 아니라, 문제 정의와 학습 조건을 조금씩 바꾸면서 관찰하는 과정이다.
이 프로젝트 기준으로는 5가지를 바꿔볼 수 있다.
- observation
- 모든 관측이 필요할까?
- 어떤 정보가 빠지면 학습이 어려워질까?
- 어떤 정보를 주면 더 쉽게 배울까?
- 이를 통해 에이전트가 문제를 풀기위해 어떤 정보가 필요한지를 배울 수 있다.
- 현실 문제에서는 센서가 불완전한 경우가 많기 때문에 정보의 선정이 중요하다.
- action
- action의 범위를 바꾸면?
- 연속 action과 이산 action을 바꾸면?
- 행동 공간이 학습 난이도에 얼마나 영향을 주는가를 알 수 있다.
- reward
- 막대가 위에 가까울수록 보상을 더 키우면?
- 중심 근처에 카트가 있을수록 추가 보상
- action이 너무 크면 패널티
- 성공 후 유지 시간에 보상
- reward를 어떻게 주느냐에 따라 에이전트가 완전히 다른 전략을 배울 수 있다.
- 점수만 잘 따는 이상한 행동을 배울 수 있으니 주의하자.
- episode/환경 조건
- episode가 너무 짧으면 학습 전에 끝나지는 않을까?
- 너무 길면 비효율적이지 않을까?
- 노이즈 추가
- 초기 상태 랜덤화
- 관측 지연 추가
- Robust 제어 분야에서 중요해진다.
- 알고리즘 및 학습 파라미터
- 지금은 SAC를 사용하지만 PPO를 사용하면?
- config에서
- learning_rate
- batch_size
- gamma
- tau
- total_timesteps
- 이 값들을 바꾸면 학습 안정성이나 속도가 달라질 수 있다.
실험을 하는 과정
많이 바꾸는 것보다는 무엇을 왜 바꿨는지 기록하면서 비교하는 것이 중요하다.
-
기준 실험 하나를 만든다.
-
변수 하나만 바꾼다.
-
다시 학습 시킨다.
-
평가와 gif를 비교한다.
-
어떤 변화가 있었는지 메모한다.
기준 : 현재 config
실험1 : max_episode_steps만 증가
실험2 : total_timesteps만 증가
이런 식으로 설계해야 원인이 무엇인지 보이게 된다.
여러개를 한 번에 바꾸면 결과가 달라져도 이유를 알 수 없다.
중요한 RL 실험 본질
- 어떤 정보를 보여줄지 정하기
- 어떤 행동을 허용할지 정하기
- 무엇을 보상할지 정하기
- 얼마나 오래 학습시킬지 정하기
- 결과를 다시 보고 수정 반복하기