CS Repository/기초 강화학습

[강화학습] Experience Replay, ER - Catastrophic Forgetting의 해소

조금씩 차근차근 2025. 9. 12. 20:03

본 내용은 심층강화학습 인 액션 도서를 참고하여 작성되었습니다.

 

심층 강화학습 인 액션 - 예스24

프로젝트로 배우는 심층 강화학습의 이론과 실제!이 책 『심층 강화학습 인 액션』은 환경이 제공하는 직접적인 피드백에 기반해서 환경에 적응하고 자신을 개선해 나가는 에이전트의 구현 방

www.yes24.com

 

우리는 이전 글에서 Gridworld를 학습시켰고, 무사히 학습에 성공한?것을 알 수 있었다.
하지만 이것은 게임의 '정적' 모드, 즉 가장 쉬운 버전이었다.
mode='random'으로 다시 실험해보면 실망스러운 결과가 나온다.

실망스럽지만 흥미로운 결과이다.

이 결과를 자세히 살펴보면, 단순히 정적 모드에서의 움직임을 암기했다고 보는게 정확한 수준으로 그대로 움직인다.

 

 

학습 또한 'random' 모드로 수행하면, 손실함수 그래프는 다음과 같이 전혀 수렴하지 않는다.

Catastrophic Forgetting 방지 - Experience Replay(ER)

첫 시도에서 아주 그럴듯한 결과를 얻었지만, 알고 보니 정적 모드에서 훈련한 신경망은 그냥 특정 게임 플레이를 암기할 뿐이었다.
그리고 무작위 모드에서 훈련하면 신경망은 거의 아무것도 배우지 못했다.

 

우리가 원하는 것은 게임판의 초기 구성이 어떻든 플레이어가 구덩이에 빠지는 일 없이 벽을 피해 최단 경로를 따라 목표로 이동하는 것이다.
이를 위해서는 게임 환경을 좀 더 정교하게 표현할 수 있어야 한다.

catastrophic forgetting

앞에서 무작위 모드에서 모델 훈련을 수행했을 때 우리가 겪은 문제는 소위 파국적 망각(catastrophic forgetting) 문제에 해당한다.

 

사실 이 문제는 온라인학습 방식의 경사 하강법 기반 훈련 방법들에서 아주 중요한 문제이다.

여기서 온라인 학습이란, 한 에피소드를 끝낼 때마다 바로 역전파를 수행하는 방식을 의미한다.

 

 

지금부터 이 문제의 원인에 대해 예시를 들어 설명하도록 하겠다.

다음과 같이 환경이 주어진다고 가정해보자.
입실론-그리디 전략 사용 시 에이전트는 일정 확률로 무작위 동작을 선택한다.
운 좋게 왼쪽 이동이 선택된다면 에이전트는 바로 목표에 보달하고, 높은 승리 보상 덕분에, 역전파 과정을 통해 신경망은 이 특정한 상태-동작 쌍을 높은 가치와 연관시키게 된다.

 

다음으로, 두번째 훈련 주기에서 다음 환경이 주어진다고 하자.


이번에도 플레이어는 폭탄과 목표 사이에 있지만, 이번엔 그 위치가 서로 반대이다.
우리의 단순한 학습 알고리즘이 보기에 이 상황은 이전 에피소드와 아주 비슷하다.
에이전트는 이전에 왼쪽으로 이동해서 높은 긍정적 보상을 얻었으므로, 이번에도 왼쪽 이동을 선택한다.

 

 

그러나 보상은 -1이다.

 


이에 에이전트는 "어라? 이전 경험에 따르면 왼쪽 이동이 최선의 선택인데?" 라고 생각하지만, 다시 역전파를 수행해서 이 상태-동작 쌍의 가치를 갱신하게 된다.
이때 이 상태-동작 쌍은 이전에 배운 상태-동작 쌍과 아주 비슷하기 때문에, 이 전에 배운 가중치들이 새 가중치로 대체된다.

 

즉, 모델은 이전에 배운 것들은 모두 잊는다.

 

 

이상이 바로 파국적 망각이다.
이처럼 서로 아주 비슷한(그러나 가치는 다른) 상태-동작 쌍들이 서로의 경험을 상쇄하면 신경망은 아무것도 제대로 배우지 못한다.
일반적으로 지도학습에서는 온라인 학습 접근 방식 대신 훈련 데이터의 한 부분집합으로 신경망을 실행하고 그 부분집합 전체에 대한 합 또는 평균 기울기를 계산한 후에야 가중치들을 갱신하는 batch 접근 방식을 사용하기 때문에 이런 문제가 잘 생기지 않는다.
그처럼 일정 단위의 데이터에 대한 평균을 학습 시 사용하면 학습이 안정해진다.

Experience Replay

모든 것이 고정된 Gridworld게임의 정적 모드에서는 파국적 망각을 걱정할 필요가 없다.

실제로 모델은 정적 모드에서 승리하는 방법을 잘 학습했다.

 

그러나 무작위 모드에서는 파국적 망각 때문에 모델이 아무것도 학습하지 못했다.
이에 대한 해결책으로 경험 재현(Experience Replay)이라는 기법이 있다.
기본적으로 경험 재현은 온라인 학습에 배치 훈련 방식을 도입하는 것인데, 구현하기도 그리 어렵지 않다.

 

다음은 경험 재현 기법의 작동 방식이다.

  1. 상태 s에서 동작 a를 취하고 새 상태 s_t+1 과 보상 r_t+1을 관측한다.
  2. 이 '경험'을 하나의 튜플(s, a, s_t+1, r_t+1)로 묶어서 목록에 추가한다.
  3. 미리 정해 둔 길이(개발자가 정하기 나름이다)의 목록이 다 찰 때까지 단계 1과 2를 반복한다.
  4. 경험 목록이 다 차면, 일부 경험들을 무작위로 선택해서 하나의 부분집합을 만든다.
    (이 부분집합의 크기 역시 개발자가 정하기 나름이다.)
  5. 그 부분집합의 경험들로 가치들을 계산해서 개별 배열(Y라고 하자)에 저장하고, 경험들의 상태 s들을 또 다른 배열(X라고 하자)에 저장한다.
  6. 이제 X와 Y를 하나의 미니배치(mini-batch)로 두어서 배치 훈련을 실행한다.
    (여기까지가 하나의 훈련 주기이다. 훈련을 반복해서 경험 재현 목록이 꽉 차면, 그때부터는 목록의 기존 경험들이 새 경험들로 대체된다.)

epoch = 5000  
losses = []  
mem_size = 1000  
batch_size = 200  
replay = deque(maxlen=mem_size)  
max_moves = 50  
h = 0  
for e in range(epoch):  
    game = Gridworld(size=4, mode='random')  
    #  
    state_ = game.board.render_np().reshape(1, 64) + np.random.rand(1, 64)/100.0  
    state1 = torch.from_numpy(state_).float()  
    status = 1  
    mov = 0  
    while (status == 1):  
        mov += 1  
        qval = model(state1)  
        qval_ = qval.data.numpy()  
        if(random.random() < epsilon):  
            action_ = np.random.randint(0, 4)  
        else:  
            action_ = np.argmax(qval_)  

        action = action_set[action_]  
        game.makeMove(action)  
        state2_ = game.board.render_np().reshape(1, 64) + np.random.rand(1, 64)/100.0  
        state2 = torch.from_numpy(state2_).float()  
        reward = game.reward()  
        done = True if reward > 0 else False  
        exp = (state1, action_, reward, state2, done)  
        replay.append(exp)  
        state1 = state2  

        if len(replay) > batch_size:  
            minibatch = random.sample(replay, batch_size)  
            state1_batch = torch.cat([s1 for (s1, a, r, s2, d) in minibatch])  
            action_batch = torch.Tensor([a for (s1, a, r, s2, d) in minibatch])  
            reward_batch = torch.Tensor([r for (s1, a, r, s2, d) in minibatch])  
            state2_batch = torch.cat([s2 for (s1, a, r, s2, d) in minibatch])  
            done_batch = torch.Tensor([d for (s1, a, r, s2, d) in minibatch])  

            Q1 = model(state1_batch)  
            with torch.no_grad():  
                Q2 = model(state2_batch)  

            # torch.max() -> (values, indices) 튜플을 반환한다.  
            # max Q2 값이 다음 상태에서의 최대 Q 값을 의미한다.  
            Y = reward_batch + gamma * ((1 - done_batch) * torch.max(Q2,dim=1)[0])  

            X = Q1.gather(dim=1, index = action_batch.long().unsqueeze(dim=1)).squeeze()  

            loss = loss_fn(X, Y.detach())  # 모양 (1,4) vs (1,4)  
            optimizer.zero_grad()  
            loss.backward()  
            losses.append(loss.item())  

            optimizer.step()  
        if reward != -1 or mov > max_moves:  
            status = 0  
            mov = 0

이전의 훈련 방식과 Experience Replay 훈련의 주된 차이점은, ER에서는 ER 목록이 꽉 차면 비로소 데이터를 여러 미니 배치로 만들어서 훈련을 진행한다는 것이다.

 

 

ER 목록이 꽉 차면 일부 경험을 무작위로 선택해서 하나의 부분집합을 얻고, 그 경험들의 성분들을 분리해서 종류별 미니 배치 (state1_batch, reward_batch, action_batch, state2_batch)를 만든다.


예를 들어,

  • state1_batch = 100x64(여기서 100은 batch_size) 텐서
  • reward_batch = 100차원 벡터

이다.
훈련 방법 자체는 이전의 온라인 학습 기반 훈련과 동일하되, 미니배치들을 사용한다는 점이 다르다.

X = Q1.gather(dim=1, index = action_batch.long().unsqueeze(dim=1)).squeeze()

텐서 Q1에 대한 gather 메서드는 그 텐서의 한 부분집합을 추출하는데, 동작 번호들을 index로지정했으므로 앞에서 선택된 부분집합의 동작들에 대한 Q Value만 수집된다.
결과적으로 이 메서드는 하나의 100차원 벡터를 산출한다.

Y = reward_batch + gamma * ((1 - done_batch) * torch.max(Q2,dim=1)[0])

done_batch는 게임 종료를 뜻하는 변수로, 만일 현재 동작에 의해 게임이 끝났다면 done_batch는 1로 평가되어, 결과적으로는 우항이 사라지게 된다.
따라서 reward 자체가 목표 수익이 된다.

 

또한 [0] 을 가져오는 이유는 기본적으로 torch.max(Q2, dim=1) 메소드는 (values, indices) 튜플을 돌려준다.
따라서 그 결과는 다음과 같이 구성되어 있다.

  • values(0번): 각 행(배치마다)에서 가장 큰 Q값
  • indices(1번): 그 최대값이 나온 액션의 인덱스

즉 위 코드는, 가장 큰 Q value 를 가져오는 코드이다.

잡음 크기 조절


이전에는 잡음을 /10 으로 나누어 주었다.
이는 정적 환경에서 환경의 대칭성을 깨주기 위해 다소 크게 잡음을 넣어준 것이었다.

이번에는 충분히 환경이 불규칙적이기 때문에, 잡음의 크기를 다음과 같이 다소 낮춰주었다.


이와 같이 학습을 진행해준 결과, 다음과 같은 결과를 획득했다.

 

 

못이길 게임이 없을 것 같은데 왜 승률이 100%가 나오지 않는걸까?

 

 

게임판을 무작위로 초기화하다 보면 도저히 이길 수 없는 게임이 만들어질 수도 있으므로, 어떻게 해도 승률이 100%에 도달하지는 못할 여지가 있다.
예를 들어 목표 타일이 격자의 한 귀퉁이에 있고, 그 목표 타일을 구덩이와 벽이 가로막고 있으면 에이전트는 게임에 승리할 수 없다.

 

 

또한, 위 손실 값 그래프를 보면 손실 값이 대체로 줄어들긴 하지만 여전히 들쭉날쭉함을 보여준다.
지도학습에서는 이런 형태의 손실 그래프를 보기 힘들지만, 단순한 형태의 심층 강화학습 응용에서는 흔히 볼 수 있다.
파국적 망각을 줄여주는 경험 재현 메커니즘이 훈련의 안정화에 도움이 되나, 파국적 망각이 이런 불안정성의 유일한 원인은 아니다.