코드베이스는 여기에 정리해 두었다.

강화학습 워크플로우 배우기

환경이란?

환경은 4개의 질문데 답하는 시스템이다.

  • 지금 상태?
  • 지금 할 수 있는 일?
  • 방금 행동은 얼마나 좋았나?
  • 이번 판은 끝났나?

이 프로젝트에서 환경은 cartpole swingup 이다.

환경 안에는 아래의 요소가 들어있다.

  • 물리 상태
    • 위치, 속도, 각도, 가속도 등
  • 행동 입력
    • 에이전트가 카트에 어느 방향으로 얼마나 힘을 줄지
  • 보상 규칙
    • 지금 행동이 목표에 얼마나 도움이 됐는지 점수로 환산
  • 종료 조건
    • 한 에피소드를 언제 끝낼지

한 판(에피소드)은 보통 이렇게 흘러간다.

  1. 환경을 초기화 한다.
    • 어떤 초기 자세로 시작한다.
  2. 환경이 현재 상태를 준다.
    • 이것이 observation 이다.
    • 에이전트는 내부 물리 전체를 직접 보는게 아니라, 환경이 건네준 관측값만 본다.
  3. 에이전트가 행동을 고른다.
    • 이것이 action 이다.
    • 여기서는 카트를 얼마나 밀 것인가에 해당한다.
  4. 환경이 그 행동을 물리에 적용한다.
    • 카트가 움직이고, 막대가 흔들리며 다음 상태가 만들어진다.
  5. 환경이 계산해서 에이전트에게 보상을 준다.
    • 이것이 reward 이다.
    • 목표에 가까워졌으면 큰 점수, 아니면 작은 점수 또는 불리한 점수를 준다.
    • 에이전트는 결국 어떤 행동 패턴이 장기적으로 reward를 크게 만드는가를 배운다.
  6. 환경이 끝났는지 종료를 알려준다.
    • 아직 진행중이면 2번부터 반복한다.

위 반복 하나를 하나의 step 이라고 한다.

라이브러리끼리 어떻게 연결되는가?

이 프로젝트는 3개의 라이브러리가 붙어있다.

  1. dm_control
    • 물리 엔진과 태스크를 제공한다.
    • 카트와 막대의 물리 상태를 계산
    • 행동 넣었을 대 다음 상태 계산
    • reward 계산
    • epsiode 관련 timestep 반환

-> 세상이 어떻게 움직이는가를 담당한다.

하지만 자기 방식의 api를 사용하기 때문에 강화학습 라이브러리가 기대하는 Gymnasium 형식과는 다르다.

  1. Gymnasium
    • 환경 인터페이스의 표준이다.
    • 강화학습 코드 쪽에서는 보통 환경이 이런 형태여야 한다.
      • reset() 가능
      • step(action) 가능
      • observation_space, action_space 정의되어 있음

-> 환경은 이런 방식으로 말해야 한다는 약속이다.
물리를 계산하는 것이 아니라 표준 규약만 제공한다.

  1. 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 범위로 변환

이런 역할들을 한다.

학습 루프가 어떻게 도는가?

단순하게

  1. 환경 초기화
  2. 현재 상태 받기
  3. 행동 선택
  4. 환경에 행동 적용
  5. 다음 상태와 보상 받기
  6. 그 경험으로 정책 업데이트
  7. 반복

실제 프로젝트에서는 실험 관리가 필요하다.

  • seed 고정
  • 학습용/평가용 env 분리
  • 중간 평가
  • 모델 저장
  • 로그 기록

이 프로젝트의 코드 기준으로 진행 순서를 설명하자면

  1. 설정 읽기
    • configs/sac_cartpole_swingup.yaml 에서 설정을 읽는다.
    • 설정에는 이런 값이 있다.
      • seed
      • 총 학습 timestep 수
      • learning rate
      • batch size
      • 평가 주기
      • 저장 주기

    이 파일은 한마디로 실험 조건표이다.

  2. seed 고정
    • utils/seed.py 로 시드를 고정한다.
      • 초기 상태가 달라지거나
      • action 샘플링이 달라지거나
      • 학습 결과가 달라지는 것을 막을 수 있다.
    • seed를 고정하면 완전히 같지는 않아도 비슷한 조건으로 다시 실험 할 수 있다.
  3. 환경 만들기
    • utils/make_env.py 로 환경을 만든다.
      • 학습용 환경
        • 에이전트가 계속 경험을 쌓는 곳
      • 평가용 환경
        • 지금 실력이 어느정도인지 체크하는 곳
  4. 환경 점검
    • check_env와 random rollout을 한다.
      • 알고리즘 문제
      • 환경 버그
      • 보통 두 가지 원인으로 RL이 실패하기 때문이다.
    • 환경이 Gymnasuim 규약을 지켰는지 확인한다.
  5. 학습 루프 시작
    • 환경이 reset() 되면
    • observation 값이 나온다.
  6. 에이전트가 action을 고름
    • 학습 초반에는 행동이 미숙해서 막대를 잘 세우지 못할 것이다.
  7. 환경이 그 action을 적용
    • action을 받아 실제 물리를 한 step 진행한다.
    • 결과로
      • 다음 ovservation
      • reward
      • terminated
      • truncated
      • 위 항목을 돌려준다.
    • 에이전트 너가 방금 이렇게 행동 했더니 결과가 이렇다 하고 알려주는 단계이다.
  8. 경험을 저장
    • 이 한 번의 환경과 에이전트의 상호작용이 학습 데이터가 된다.
    • 강화학습에는 보통 이런 형태의 경험이 쌓인다.
      • 현재 상태
      • 행동
      • 보상
      • 다음 상태
      • 종료 여부
    • 이걸 transition 이라고 하고 SAC는 이런 transition들을 replay buffer에 쌓아두고 학습에 사용한다.
  9. 모델 업데이트
    • 충분한 데이터가 쌓이면, SAC가 그 데이터를 꺼내서 정책을 업데이트한다.
    • 쌓인 경험을 사용해 반복적으로 업데이트 한다.
    • 흐름
      • 환경에서 경험 수집
      • 버퍼에 저장
      • 버퍼에서 샘플 뽑기
      • 신경망 업데이트
  10. 에피소드 종료 여부 확인
    • terminated나 truncated가 True면 한 판이 끝난다.
    • 그러면 다시 reset() 해서 새 판을 시작한다.
    • 반복한다.

한 스텝 단위 말고 큰 흐름을 보면

  1. 환경에서 데이터를 모은다.
  2. 데이터로 정책을 학습한다.
  3. 조금 더 나은 정책으로 다시 데이터를 모은다.
  4. 다시 학습한다.

이런 선순환이 계속 일어나는게 강화학습이다.

중간 평가를 왜 하는가?

  • 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
      • 이 값들을 바꾸면 학습 안정성이나 속도가 달라질 수 있다.

실험을 하는 과정

많이 바꾸는 것보다는 무엇을 왜 바꿨는지 기록하면서 비교하는 것이 중요하다.

  1. 기준 실험 하나를 만든다.

  2. 변수 하나만 바꾼다.

  3. 다시 학습 시킨다.

  4. 평가와 gif를 비교한다.

  5. 어떤 변화가 있었는지 메모한다.

기준 : 현재 config

실험1 : max_episode_steps만 증가

실험2 : total_timesteps만 증가

이런 식으로 설계해야 원인이 무엇인지 보이게 된다.
여러개를 한 번에 바꾸면 결과가 달라져도 이유를 알 수 없다.

중요한 RL 실험 본질

  • 어떤 정보를 보여줄지 정하기
  • 어떤 행동을 허용할지 정하기
  • 무엇을 보상할지 정하기
  • 얼마나 오래 학습시킬지 정하기
  • 결과를 다시 보고 수정 반복하기

Updated: