CS Repository/기초 강화학습

[강화학습] Target Network를 이용한 안정성 개선

조금씩 차근차근 2025. 9. 12. 21:34

지금까지 우리는 Gridworld 게임을 플레이하도록 심층 강화학습 알고리즘을 훈련하는 데 성공했다.
하지만 두 모드의 경우 가능한 4x4 게임판 구성이 아주 많지는 않으므로, 그냥 가능한 모든 게임판 구성을 암기했을 가능성도 있다.

 

 

따라서 게임을 이기려면 알고리즘은 게임 플레이 방법을 실제로 배워야 한다.

그러나 앞에서 본 잡음이 많은 손실 그래프가 말해 주듯이, 현재 우리의 심층 강화학습 모형은 Gridworld의 무작위 모드를 그리 잘 학습하지 못한다.

 

 

그럼 가치 갱신량들을 좀 더 고르게 만드는 또다른 기법을 살펴보자.

학습 불안정성

DQN 논문에서 딥마인드 팀은 개별 동작마다 QN의 매개변수들을 갱신하다보면 학습이 불안정해질 수 있음을 지적했다.
Girdworld 게임처럼 보상이 희소한 환경, 즉 게임에 승리하거나 패배해야 큰 보상이 주어지고 그 밖의 대부분의 동작에서는 보상이 없거나 아주작은 환경에서는 단계마다 신경망을 갱신하면 알고리즘이 이상하게 행동할 수 있다.


예를 들어 Gridworld의 한 에피소드의 한 상태에서 에이전트가 '위로'동작을 취해서 목표에 도달했다고 하자.
그러면 Q 신경망은 그 상태-동작 쌍에 대해 +1의 보상을 획득한 것을 암기할 것이다.
하지만 이는 순전히 "운"에 의한 보상으로, 다음번에는 그 상태에서 위로 가면 -1의 보상을 획득하게 되는 경우, 신경망은 위로 가는 결정이 나쁜 결정임을 배운다.
결국 신경망의 Q 가치 예측은 적절한 값으로 수렴하지 않고 계속 진동하는 상황이 벌어질 것이다.
이러한 학습 불안정성은 이전 글에서 이야기한 catastrophic forgetting과 유사하다.

 

 

 

이것이 단지 이론상의 문제는 아니다. 딥마인드 팀도 DQN을 훈련하면서 이런 문제를 겪었다.
그들이 고안한 해결책은 DQN의 복제본을 만들어서 두 신경망의 매개변수들을 따로 갱신한다는 것이었다.

원래의 Q 신경망을 복제한 신경망을 목표 신경망, Target Network 라고 부르고, Q-햇으로 표기한다.

훈련을 시작하기 전에는 Target Network가 원래의 Q Network와 동일하지만, 훈련이 시작되면 목표망의 매개변수들은 주 Q Network보다 몇단계 뒤처져서 갱신된다.

문제의 근원(이동하는 목표, moving target)

이를 수학적으로 좀 더 자세하게 풀어보자.

 

DQN의 TD 타깃은

처럼 Q값 자신으로 만든 값이다(부트스트래핑).
만약 타깃이 곧바로 현재 Q의 파라미터를 따라 계속 변하면, 손실의 최소화 대상이 계속 움직여 자기 꼬리를 쫓는 불안정한 동역학이 생기게 된다.
이게 학습 중 성능 진동의 핵심적인 원인이다.

Target Network의 적용

타깃 네트워크를 N 스텝마다 동기화(hard update) 하거나


처럼 폴리악 평균(soft update) 으로 천천히 섞으면, 타깃 Q가 느리게 변하는 신호가 된다.

 

그러면 현재 네트워크 Qθ​ 는 거의 고정된 목표 y 에 대해 거의 정적인 회귀 문제를 풀게 된다.
이 덕에 타겟은 모델 파라미터가 업데이트되는 속도보다 훨씬 천천히 변해서, 거의 고정된(supervised) 회귀 문제처럼 다룰 수 있게 된다.
마침내 네트워크의 갱신은 손실 표면이 부드러워지고, 급격한 갱신-역피드백 고리가 줄어들어 진동이 완화된다.

  • hard update: 수천 스텝(예: 1k–10k)마다 동기화 → 안정↑, 반응성↓
  • soft update: τ∈[10^{-3},10^{-2}] 정도 → 부드럽고 안정적
  • 너무 느리면 학습이 둔해지고, 너무 빠르면 다시 진동/발산 위험이 커진다.

Target Network(목표망) 알고리즘

이제 타겟 네트워크의 알고리즘을 좀 더 자세히 살펴보자.

  1. Q 신경망을 초기화한다. 이 Q 신경망의 매개변수들을 θ_Q로 표기하겠다.
  2. Q 신경망을 매개변수들까지 그대로 복사해서 Target Network(목표망)를 만든다. 목표망의 매개변수들은 θ_τ로 표기한다. (지금은 θ_Q = θ_τ 이다)
  3. 엡실론 탐욕 전략에 따라 Q신경망의 Q Value를 이용해서 동작 a를 선택한다.
  4. 보상 r_t+1과 새 상태 s_t+1을 관측한다.
  5. 동작 a에 의해 이번 에피소드가 끝났다면(즉, 게임에 이겼거나 졌다면), 목표망의 Q 가치를 r_t+1로 설정하고, 그렇지 않으면 r_t+1 + gamma * max Q_theta_r (S_t+1)로 설정한다. (여기서 목표망이 실행된다.)
  6. 목표망의 Q 가치를 주 Q 신경망(Target Network이 아니다)으로 역전파한다.
  7. C회 반복마다 θ_τ = θ_Q로 설정한다(즉, 목표망의 매개변수들이 Q 신경망의 것과 같아지게 한다).

이를 다이어그램으로 표현하면 아래와 같다.

이상의 내용을 코드로 표현하면 다음과 같다.

from environment.GridWorld import *  
from matplotlib import pyplot as plt  
import torch  
import copy  

l1 = 64  
l2 = 150  
l3 = 100  
l4 = 4  


model = torch.nn.Sequential(  
    torch.nn.Linear(l1, l2),  
    torch.nn.ReLU(),  
    torch.nn.Linear(l2, l3),  
    torch.nn.ReLU(),  
    torch.nn.Linear(l3,l4)  
)  

model2 = copy.deepcopy(model) #A  
model2.load_state_dict(model.state_dict()) #B  

loss_fn = torch.nn.MSELoss()  
learning_rate = 1e-3  
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)  

gamma = 0.9  
epsilon = 0.3  
action_set = {  
    0: 'u',  
    1: 'd',  
    2: 'l',  
    3: 'r',  
}  

from collections import deque  

epochs = 5000  
losses = []  
mem_size = 1000  
batch_size = 200  
replay = deque(maxlen=mem_size)  
max_moves = 50  
h = 0  
sync_freq = 500  # A  
j = 0  
for i in range(epochs):  
    game = Gridworld(size=4, mode='random')  
    state1_ = game.board.render_np().reshape(1, 64) + np.random.rand(1, 64) / 100.0  
    state1 = torch.from_numpy(state1_).float()  
    status = 1  
    mov = 0  
    while (status == 1):  
        j += 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)  # H  
        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 = model2(state2_batch)  # B  

            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())  
            print(i, loss.item())  
            optimizer.zero_grad()  
            loss.backward()  
            losses.append(loss.item())  
            optimizer.step()  

            if j % sync_freq == 0:  # C  
                model2.load_state_dict(model.state_dict())  
        if reward != -1 or mov > max_moves:  
            status = 0  
            mov = 0  

losses = np.array(losses)

목표망은 주 Q 신경망의 복제본으로, 매개변수들이 가끔씩만 갱신된다.
PyTorch의 모든 모델 객체에는 매개변수들을 담은 딕셔너리 객체를 돌려주는 state_dict라는 메소드가 있다.

파이썬 내장 모듈 copy를 이용해서 주 Q 신경망(model)을 목표망(model2)로 복사하고, 목표망의 load_state_dict 메서드를 이용해서 주 Q 신경망의 매개변수들을 목표망에 복사한다.

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

 

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

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

www.yes24.com