본문 바로가기

A3C – 여러 에이전트가 함께 학습하는 Actor-Critic

@eunyoung-study2026. 3. 16. 22:19

1. A3C 한 줄 요약

여러 개의 Actor-Critic 에이전트가 각자 환경을 돌면서 동시에 경험을 모으고,
그 경험으로 하나의 전역 네트워크(Global Network)를 비동기적으로 업데이트하는 알고리즘.

  • Actor: 어떤 행동을 할지 결정하는 정책 \(\pi(a|s)\) 를 학습
  • Critic: 상태의 가치 \(V(s)\) 를 학습
  • Advantage: “실제로 얻은 결과가, 지금 상태 가치 예측보다 얼마나 좋았는지/나빴는지”를 나타내는 값

여기에 여러 worker 프로세스가 병렬로 환경을 탐험하는 아이디어가 결합된 것이 A3C입니다.


2. 왜 A3C가 필요했을까?

초기 강화학습(DQN 등)은 보통 하나의 환경에서 하나의 에이전트만 돌렸습니다.
이 방식에는 두 가지 큰 문제가 있습니다.

2.1 문제 1 – 데이터가 너무 비슷하다 (상관성 문제)

  • 한 환경에서 같은 정책으로만 학습하면,
    • 연속된 상태들이 아주 비슷한 궤적을 가진 데이터만 계속 쌓입니다.
  • 이렇게 강하게 상관된 데이터로 학습하면,
    • 신경망이 특정 궤적에 과하게 맞춰지고
    • 일반화가 잘 안 되고, 학습이 불안정해질 수 있습니다.

2.2 문제 2 – 경험 수집 속도가 느리다

  • 환경이 하나뿐이면,
    • 한 번에 하나의 에피소드만 실행할 수 있습니다.
  • 특히 시뮬레이션이 느린 환경에서는,
    • 충분한 경험을 쌓는 데 시간이 오래 걸립니다.

요약하면, 데이터가 느리고, 편향되고, 서로 너무 비슷하다는 문제가 있었습니다.


3. A3C의 핵심 아이디어

노트북에서는 A3C를 세 가지 아이디어의 결합으로 설명합니다.

  • Actor-Critic 구조
  • Advantage 기반 정책 업데이트
  • 비동기 병렬 학습

3.1 여러 에이전트를 동시에 실행

텍스트로 표현하면 대략 이런 구조입니다.

Worker 1  →  환경 1
Worker 2  →  환경 2
Worker 3  →  환경 3
Worker 4  →  환경 4
...
  • 각 worker는 독립적인 환경을 가지고,
    • 자기 환경에서 상태를 보고 행동을 선택하고
    • 보상을 받으면서 자기만의 궤적을 쌓습니다.
  • 이렇게 하면
    • 동시에 여러 에피소드를 진행할 수 있어서 경험 수집 속도가 빨라지고,
    • 서로 다른 궤적이 섞여서 데이터 다양성도 좋아집니다.

3.2 Global Network + Local Network 구조

전체 구조를 단순화하면 다음과 같습니다.

       Global Network
        (Actor + Critic)
              ↑
      ┌───────┼────────┐
      │       │        │
  Worker 1 Worker 2 Worker 3
   (로컬)   (로컬)    (로컬)
  • Global Network
    • 전역에서 하나만 존재하는 신경망
    • 모든 worker가 이 모델을 기준으로 학습
  • Local Network (worker별)
    • 각 worker가 Global Network를 복사해서 시작
    • 자기 환경에서 rollout을 하고, 그 데이터로 gradient를 계산
    • 계산된 gradient를 다시 Global Network에 비동기적으로 반영

이 과정을 모든 worker가 동시에 반복하면서,
Global Network가 점점 좋아지게 됩니다.


4. Advantage란 무엇인가?

노트북에서는 먼저 Advantage를 이렇게 설명합니다.

Advantage는 현재 행동이 예상보다 얼마나 더 좋았는지를 나타내는 값입니다.

일반적인 형태는 다음과 같습니다.

\[
A(s, a) = \text{Target} - V(s)
\]

  • Target: 실제로 경험해 본 “새로운 평가”
    • 예: \(r + \gamma V(s')\) 같이 보상 + 다음 상태 가치를 반영한 값
  • \(V(s)\): Critic이 원래 생각하던 현재 상태의 가치

즉,

Advantage = (실제로 겪어 보니 이 정도는 받겠네) − (원래 내가 생각했던 가치)
  • Advantage > 0
    • 실제 결과가 예상보다 좋았음
    • “생각보다 괜찮은 행동이었다” → 이 행동의 확률을 올려야 합니다.
  • Advantage < 0
    • 실제 결과가 예상보다 나빴음
    • “생각보다 별로인 행동이었다” → 이 행동의 확률을 줄여야 합니다.

Actor는 이 Advantage를 이용해서
좋은 행동의 확률은 증가, 나쁜 행동의 확률은 감소하도록 학습합니다.


5. A3C 학습 흐름 정리

노트북의 설명을 코드와 함께 정리하면, 각 worker는 다음을 반복합니다.

1단계 – 글로벌 모델 복사 (Local 초기화)

local_model = ActorCritic()
local_model.load_state_dict(global_model.state_dict())
  • Global Network의 파라미터를 복사해서 Local Network를 만듭니다.
  • 각 worker는 자기만의 local_model로 환경을 탐험합니다.

2단계 – 환경에서 rollout 수행

s_lst, a_lst, r_lst = [], [], []

for _ in range(update_interval):
    prob = local_model.pi(torch.from_numpy(s).float())
    m = Categorical(prob)
    a = m.sample().item()

    s_prime, r, terminated, truncated, info = env.step(a)
    done = terminated or truncated

    s_lst.append(s)
    a_lst.append([a])
    r_lst.append(r / 100.0)

    s = s_prime
    if done:
        break
  • 현재 상태 s에서 local_model.pi정책 확률 분포를 얻고,
    • Categorical 분포에서 행동 a를 샘플링합니다.
  • 환경에서 한 스텝 진행 → s_prime, r, done 획득
  • 상태/행동/보상을 리스트에 쌓으면서,
    • 일정 step(update_interval)이 지나거나
    • 에피소드가 끝날 때까지 반복합니다.

3단계 – TD 타깃과 Advantage 계산

s_final = torch.tensor(s_prime, dtype=torch.float)
R = 0.0 if done else local_model.v(s_final).item()

td_target_lst = []
for reward in r_lst[::-1]:
    R = gamma * R + reward
    td_target_lst.append([R])
td_target_lst.reverse()

s_batch = torch.tensor(s_lst, dtype=torch.float)
a_batch = torch.tensor(a_lst)
td_target = torch.tensor(td_target_lst, dtype=torch.float)

advantage = td_target - local_model.v(s_batch)
  • 에피소드가 끝나지 않았다면 마지막 상태 가치 \(V(s')\)를 부트스트랩으로 사용하고,
  • r_lst를 뒤에서부터 순회하며
    • \(R = r_t + \gamma R\) 형태로 TD 타깃을 만듭니다.
  • 그 후,

\[
\text{Advantage} = \text{TD Target} - V(s)
\]

을 계산해 Advantage를 얻습니다.

4단계 – Actor & Critic 손실 계산

pi = local_model.pi(s_batch, softmax_dim=1)
pi_a = pi.gather(1, a_batch)

value_loss = F.smooth_l1_loss(local_model.v(s_batch), td_target.detach())
policy_loss = -torch.log(pi_a) * advantage.detach()
loss = policy_loss + value_loss
  • pi: 각 상태에서 [왼쪽, 오른쪽] 확률
  • pi_a = pi.gather(1, a_batch):
    • 실제로 선택한 행동의 확률 \(\pi(a|s)\)만 뽑아온 것
  • 정책 손실(policy_loss):
    • -log(pi_a) * advantage
    • Advantage > 0 → 해당 행동의 확률을 키우는 방향
    • Advantage < 0 → 해당 행동의 확률을 줄이는 방향
  • 가치 손실(value_loss):
    • SmoothL1Loss(V(s), TD Target)
    • Critic이 V(s)를 좀 더 정확하게 맞추도록 학습

5단계 – Global Network 갱신 & Local 동기화

optimizer.zero_grad()
loss.mean().backward()

for global_param, local_param in zip(global_model.parameters(), local_model.parameters()):
    global_param._grad = local_param.grad

optimizer.step()
local_model.load_state_dict(global_model.state_dict())
  • local_model에서 backward를 한 뒤,
    • Local Network의 gradient를 Global Network 파라미터에 복사해서
    • optimizer.step()으로 Global Network를 업데이트합니다.
  • 그 다음,
    • 다시 Global Network 파라미터를 local_model에 복사해
    • Local을 최신 상태로 맞춰 줍니다.

이 과정을 여러 worker가 동시에 돌리기 때문에
Global Network는 다양한 궤적에서 나온 gradient를 계속 받아 학습하게 됩니다.


6. 테스트(평가) 루프

노트북에서는 별도의 test 프로세스를 두고,
현재 Global Network가 얼마나 잘하는지 주기적으로 평가합니다.

def test(global_model):
    env = gym.make("CartPole-v1")
    score = 0.0
    print_interval = 5

    for n_epi in range(max_test_ep):
        done = False
        s, _ = env.reset()

        while not done:
            prob = global_model.pi(torch.from_numpy(s).float())
            a = Categorical(prob).sample().item()

            s_prime, r, terminated, truncated, info = env.step(a)
            done = terminated or truncated

            s = s_prime
            score += r

        if n_epi % print_interval == 0 and n_epi != 0:
            print(f"[TEST] episode={n_epi}, avg score={score / print_interval:.1f}", flush=True)
            score = 0.0
  • 학습 중인 Global Network를 그대로 사용해서
    • CartPole 에피소드를 여러 번 돌려 보고
    • 일정 에피소드마다 평균 점수를 출력합니다.
  • 이렇게 하면 A3C가 학습되면서 점수가 어떻게 올라가는지 확인할 수 있습니다.

7. A3C vs A2C – 왜 요즘은 A2C/PPO를 더 많이 쓸까?

노트북 마지막 부분에서는 A3C와 A2C의 차이와,
왜 A2C/PPO 같은 동기식 방법이 더 많이 쓰이는지 설명합니다.

7.1 A3C의 문제점

  1. Gradient 충돌 (Gradient Interference)
    • 여러 worker가 동시에 Global Network를 업데이트하면,
      • worker 1이 업데이트한 직후,
      • worker 2가 완전히 다른 gradient로 덮어쓰는 일이 발생합니다.
    • 이 때문에 학습이 불안정해질 수 있습니다.
  2. 학습 순서가 계속 바뀜
    • 비동기(asynchronous) 방식이라
    • 어떤 worker의 업데이트가 먼저 반영될지 매번 달라집니다.
    • 재현성이 낮고, 디버깅이 어렵습니다.
  3. GPU 활용이 어렵다
    • A3C는 CPU 멀티프로세싱 중심 구조입니다.
    • 파라미터를 자주 동기화하고, 작은 batch를 여러 번 보내기 때문에
      GPU에서 큰 batch로 처리하기에 비효율적입니다.

7.2 A2C(Advantage Actor-Critic)의 장점

A2C는 A3C의 동기식(synchronous) 버전으로 볼 수 있습니다.

  • 여러 환경(worker)에서 병렬로 데이터를 모으되,
    • 한 번에 모아서(batch) 업데이트합니다.

장점은 다음과 같습니다.

  1. 학습 안정성
    • 한 번에 모은 batch에 대해 한 번의 gradient로 업데이트하므로,
    • gradient들이 서로 충돌하는 문제가 줄어듭니다.
  2. GPU 사용 용이
    • 여러 환경에서 모은 데이터를 큰 batch로 한 번에 처리할 수 있어
    • GPU를 효율적으로 사용할 수 있습니다.
  3. 구현이 단순
    • 비동기 처리/락(lock) 관리가 필요 없습니다.
  4. 재현성 증가
    • 동기 방식이라 업데이트 순서가 일정해지고,
    • 실험을 다시 했을 때 결과가 더 잘 재현됩니다.

이런 이유로, 실제 구현에서는 A3C보다
A2C, PPO 같은 동기식 Actor-Critic 계열이 더 널리 쓰이고 있습니다.

더보기

[ 오늘의 정리 ] – A3C의 포인트

  • A3C(Asynchronous Advantage Actor-Critic) 
    • Actor-Critic + Advantage + 비동기 멀티프로세싱을 결합한 알고리즘입니다.
  • 여러 worker가 각기 다른 환경에서 동시에 데이터를 수집하고,
    • 비동기적으로 Global Network를 업데이트함으로써
    • 데이터 다양성과 수집 속도를 높였습니다.
  • Advantage를 사용해
    • “예상보다 얼마나 좋은 행동이었는지”에 따라
    • 정책을 더 안정적으로 업데이트합니다.
  • 다만 비동기 특성 때문에
    • gradient 충돌, 재현성, GPU 활용 측면에서 한계가 있어,
    • 이후에는 A2C, PPO 같은 동기식 Actor-Critic 계열이 더 많이 사용됩니다.

CartPole 예제를 통해 A3C의 전체 흐름을 코드를 따라가 보면,

“여러 Actor-Critic 에이전트가 동시에 환경을 돌아다니면서,
하나의 공통 정책과 가치 함수를 함께 키워 나가는 구조”

라는 A3C의 큰 그림을 자연스럽게 이해할 수 있습니다.

eunyoung-study
@eunyoung-study :: 은영의 이해 노트

개념을 이해하고, 논문을 풀어보고, 코드로 확인하는 기록 ! 오늘도 파이팅 😉

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차