[강화학습] Policy Gradient부터 Actor-Critic까지 정리

2026. 3. 10. 14:24·개념 정리 step2/강화 학습

강화학습에는 에이전트가 학습하는 크게 두 가지 접근 방식이 있습니다.

  1. 가치 기반 (Value-Based): 행동의 가치(Q값)를 계산해서 가장 높은 가치의 행동을 고르는 방식
  2. 정책 기반 (Policy-Based): 행동의 가치를 먼저 계산하는 것이 아니라, 상황(State)에 따라 어떤 행동을 할지 그 규칙(Policy) 자체를 직접 학습하는 방식

이번 포스팅에서는 정책 기반 강화학습의 이론적 배경이 되는 Policy Gradient Theorem부터, 이를 구현한 REINFORCE 알고리즘, 그리고 가치 기반과 정책 기반의 장점을 합친 Actor-Critic 구조까지 정리해 보겠습니다.


1. Policy Gradient Theorem 이란?

Policy Gradient Theorem은 정책 기반 강화학습에서 "에이전트의 정책을 어떻게 개선해야 하는가?"를 수학적으로 설명하는 정리입니다.

에이전트가 더 많은 보상을 얻으려면, 실제로 높은 보상을 얻은 행동을 선택할 확률은 높이고, 낮은 보상을 얻은 행동을 선택할 확률은 낮추는 방향으로 정책 파라미터($\theta$)를 조정해야 합니다. 이 정리는 환경의 모델(Dynamics)을 알 필요 없이, 경험으로부터 직접 정책을 학습할 수 있게 해주는 핵심 이론입니다.

1.1 Policy Gradient Theorem 수식의 이해

핵심 수식은 다음과 같습니다.

$$\nabla_\theta J(\theta) \propto \mathbb{E}_{\pi} \left[ \nabla_\theta \log \pi_\theta(a|s) Q^\pi(s,a) \right]$$

수식의 각 항목이 의미하는 바는 다음과 같습니다.

  • $\nabla_\theta J(\theta)$: 정책을 더 좋은 방향으로 바꾸기 위한 기울기(Gradient)입니다. "정책을 어느 방향으로 수정하면 보상이 커질까?"를 의미합니다.
  • $\pi_\theta(a|s)$: 현재 상태 $s$에서 행동 $a$를 선택할 확률입니다.
  • $\nabla_\theta \log \pi_\theta(a|s)$: 정책 파라미터를 조금 바꿨을 때 해당 행동의 선택 확률이 얼마나 변하는지를 나타내는 '정책의 민감도'입니다.
  • $Q^\pi(s,a)$: 상태 $s$에서 행동 $a$를 했을 때, 앞으로 받을 것으로 기대되는 총 보상(가치)입니다.

1.2 수식에 로그(log)가 들어간 이유

수학적으로 계산(미분)을 쉽게 만들기 위해서입니다. 특히 에피소드 동안 일어나는 행동 확률의 곱을 다룰 때, 로그를 취하면 덧셈으로 바뀌어 컴퓨터가 계산하기 훨씬 편해집니다. (이를 Log-derivative trick이라고 부릅니다.)


2. REINFORCE 알고리즘

REINFORCE 알고리즘은 Policy Gradient Theorem을 실제 코드로 구현한 가장 기본적인 몬테카를로(Monte Carlo) 기반 알고리즘입니다.

2.1 동작 과정

  1. 에이전트가 현재 정책에 따라 에피소드가 끝날 때까지 행동합니다.
  2. 에피소드가 끝나면, 각 시점(Step)에서 얻은 총 누적 보상인 Return ($G_t$)을 계산합니다.
  3. 계산된 Return을 이용해 정책을 업데이트합니다.

2.2 누적 보상 (Return, $G_t$)

특정 시점 $t$ 이후에 얻은 모든 보상의 합을 의미하며, 미래 보상에 대한 할인율(Gamma, $\gamma$)을 적용하여 계산합니다.

$$G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + \dots$$

2.3 업데이트 수식

REINFORCE의 파라미터 업데이트 수식은 다음과 같습니다.

$$\theta \leftarrow \theta + \alpha \nabla_\theta \log \pi_\theta(a_t|s_t) G_t$$
  • 보상($G_t$)이 큰 행동은 확률을 높이고, 보상이 작은 행동은 확률을 낮추게 됩니다.

2.4 PyTorch로 구현한 REINFORCE (CartPole-v1)

import gymnasium as gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

learning_rate = 0.0002
gamma = 0.98

class Policy(nn.Module):
    def __init__(self):
        super(Policy, self).__init__()
        self.data = []
        self.fc1 = nn.Linear(4, 128)
        self.fc2 = nn.Linear(128, 2)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.softmax(self.fc2(x), dim=0) # 행동 확률 출력
        return x

    def put_data(self, item):
        self.data.append(item)

    def train_net(self):
        R = 0
        self.optimizer.zero_grad()
        
        # 마지막 스텝부터 거꾸로 계산하여 Return(G_t)을 효율적으로 구함
        for r, prob in self.data[::-1]:
            R = r + gamma * R
            # loss = -log(prob) * Return (음수를 붙여 Gradient Ascent 효과)
            loss = -torch.log(prob) * R
            loss.backward()
            
        self.optimizer.step()
        self.data = []

def main():
    env = gym.make('CartPole-v1')
    pi = Policy()
    score = 0.0
    print_interval = 20

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

        while not done:
            prob = pi(torch.from_numpy(s).float())
            m = Categorical(prob)
            a = m.sample() # 확률 분포 기반 행동 샘플링
            s_prime, r, done, truncated, info = env.step(a.item())
            
            pi.put_data((r, prob[a])) # 보상과 선택한 행동의 확률 저장
            s = s_prime
            score += r
            
        pi.train_net() # 에피소드 종료 후 정책 업데이트
        
        if n_epi % print_interval == 0 and n_epi != 0:
            print(f"# of episode: {n_epi}, avg score: {score/print_interval:.1f}")
            score = 0.0
            
    env.close()

if __name__ == '__main__':
    main()

3. Actor-Critic (액터-크리틱)

REINFORCE 알고리즘은 에피소드가 끝날 때까지 기다려야 하고, 전체 Return을 사용하기 때문에 분산(Variance)이 커서 학습이 불안정하다는 단점이 있습니다. 이를 해결하기 위해 등장한 것이 Actor-Critic입니다.

  • Actor (배우): "무엇을 할까?"를 결정합니다. 정책 $\pi(a|s)$를 학습합니다.
  • Critic (평가자): "방금 한 선택이 얼마나 좋았을까?"를 평가합니다. 가치 함수 $V(s)$ 또는 $Q(s, a)$를 학습합니다.

3.1 Critic이 필요한 이유와 TD Error

Actor 혼자서는 행동의 좋고 나쁨을 정확히 판단하기 어렵습니다. 이때 Critic이 **TD Error (Temporal Difference Error)**를 이용해 피드백을 줍니다.

$$\delta = r + \gamma V(s') - V(s)$$
  • $V(s)$: 예상했던 현재 상태의 가치
  • $r + \gamma V(s')$: 실제로 경험한 보상과 다음 상태의 가치
  • $\delta > 0$: 예상보다 결과가 좋음 $\rightarrow$ Actor는 해당 행동의 확률을 높임
  • $\delta < 0$: 예상보다 결과가 나쁨 $\rightarrow$ Actor는 해당 행동의 확률을 낮춤

3.2 REINFORCE vs Actor-Critic

특징 REINFORCE Actor-Critic
업데이트 시점 에피소드가 끝난 후 한 번에 매 스텝(Step)마다 가능
기준 값 에피소드 전체 누적 보상 (Return) Critic의 평가 (TD Error 등)
학습 안정성 분산이 커서 불안정함 Critic 덕분에 상대적으로 안정적임

4. 다양한 Actor-Critic의 발전 형태

Actor-Critic 구조에서 Critic이 어떤 값을 평가 기준으로 삼느냐에 따라 여러 알고리즘으로 나뉩니다.

4.1 Q Actor-Critic

Critic이 상태 가치 $V(s)$ 대신 행동 가치 함수 $Q(s,a)$를 학습하는 방식입니다. Actor는 이 $Q$값을 바탕으로 행동 확률을 조정합니다.

4.2 Advantage Actor-Critic (A2C)

단순히 가치를 평가하는 것을 넘어, "평균적인 행동보다 얼마나 더 좋은가?"를 나타내는 Advantage $A(s,a)$를 사용합니다.

$$A(s,a) = Q(s,a) - V(s)$$

이 방식은 학습의 분산을 획기적으로 줄여주어 매우 안정적인 학습을 돕습니다.

4.3 TD Actor-Critic

Critic이 매 스텝마다 발생하는 TD Error 자체를 어드밴티지로 사용하여 정책을 업데이트하는 방식입니다. 에피소드 종료를 기다릴 필요가 없어 학습 속도가 빠릅니다.


5. PyTorch로 구현한 TD Actor-Critic (CartPole-v1)

하나의 신경망 안에 Actor(정책 출력)와 Critic(가치 출력)을 동시에 구성한 코드입니다.

import gymnasium as gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
from torch.distributions import Categorical

learning_rate = 0.0002
gamma = 0.98
n_rollout = 10 # 10 스텝마다 한 번씩 학습

class ActorCritic(nn.Module):
    def __init__(self):
        super(ActorCritic, self).__init__()
        self.data = []
        self.fc1 = nn.Linear(4, 256)
        self.fc_pi = nn.Linear(256, 2) # Actor: 정책(행동 확률) 출력층
        self.fc_v = nn.Linear(256, 1)  # Critic: 가치(V(s)) 출력층
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)

    def pi(self, x, softmax_dim=0):
        x = F.relu(self.fc1(x))
        prob = F.softmax(self.fc_pi(x), dim=softmax_dim)
        return prob

    def v(self, x):
        x = F.relu(self.fc1(x))
        v = self.fc_v(x)
        return v

    def put_data(self, transition):
        self.data.append(transition)

    def make_batch(self):
        s_lst, a_lst, r_lst, s_prime_lst, done_lst = [], [], [], [], []
        for transition in self.data:
            s, a, r, s_prime, done = transition
            s_lst.append(s)
            a_lst.append([a])
            r_lst.append([r / 100.0]) # 보상 스케일링
            s_prime_lst.append(s_prime)
            done_mask = 0.0 if done else 1.0
            done_lst.append([done_mask])

        # 텐서 변환
        s_batch = torch.tensor(np.array(s_lst), dtype=torch.float)
        a_batch = torch.tensor(np.array(a_lst), dtype=torch.long)
        r_batch = torch.tensor(np.array(r_lst), dtype=torch.float)
        s_prime_batch = torch.tensor(np.array(s_prime_lst), dtype=torch.float)
        done_batch = torch.tensor(np.array(done_lst), dtype=torch.float)
        
        self.data = []
        return s_batch, a_batch, r_batch, s_prime_batch, done_batch

    def train_net(self):
        s, a, r, s_prime, done = self.make_batch()
        
        # TD Target 계산
        td_target = r + gamma * self.v(s_prime) * done
        # TD Error 계산 (실제 타깃 - 예측 가치)
        delta = td_target - self.v(s)

        pi = self.pi(s, softmax_dim=1)
        pi_a = pi.gather(1, a) # 실제 선택한 행동의 확률
        
        # Actor Loss: -log(pi) * TD Error
        # Critic Loss: Smooth L1 Loss (예측 가치와 TD 타깃의 차이)
        loss = -torch.log(pi_a) * delta.detach() + F.smooth_l1_loss(self.v(s), td_target.detach())

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

def main():
    env = gym.make('CartPole-v1')
    model = ActorCritic()
    print_interval = 20
    score = 0.0

    for n_epi in range(10000):
        done = False
        s, _ = env.reset()
        
        while not done:
            for t in range(n_rollout):
                prob = model.pi(torch.from_numpy(s).float())
                m = Categorical(prob)
                a = m.sample().item()
                s_prime, r, done, truncated, info = env.step(a)
                
                model.put_data((s, a, r, s_prime, done))
                s = s_prime
                score += r
                
                if done:
                    break
                    
            model.train_net() # 지정된 스텝마다 네트워크 업데이트

        if n_epi % print_interval == 0 and n_epi != 0:
            print(f"# of episode: {n_epi}, avg score: {score/print_interval:.1f}")
            score = 0.0
            
    env.close()

if __name__ == '__main__':
    main()

'개념 정리 step2 > 강화 학습' 카테고리의 다른 글

[강화학습] "PPO" 알고리즘 핵심 이론 및 최신 RL 트렌드 정리  (0) 2026.03.15
[강화학습] A3C, A2C, ACER 알고리즘 핵심 정리 및 구현  (0) 2026.03.14
[강화학습] Q-learning의 개념부터 Gym을 활용한 DQN 구현까지  (0) 2026.03.04
[강화학습] Deep Reinforcement Learning 개념  (0) 2026.03.03
[강화학습] TD Learning (시간차 학습) 개념, 랜덤 벽 GridWorld 실습  (0) 2026.03.01
'개념 정리 step2/강화 학습' 카테고리의 다른 글
  • [강화학습] "PPO" 알고리즘 핵심 이론 및 최신 RL 트렌드 정리
  • [강화학습] A3C, A2C, ACER 알고리즘 핵심 정리 및 구현
  • [강화학습] Q-learning의 개념부터 Gym을 활용한 DQN 구현까지
  • [강화학습] Deep Reinforcement Learning 개념
고니3000원
고니3000원
공부 내용 정리, 자기발전 블로그 입니다. 기존 네이버 블로그에서 티스토리로 이전했습니다. https://blog.naver.com/pak1010pak
  • 고니3000원
    곤이의 공부 블로그
    고니3000원
  • 전체
    오늘
    어제
    • 분류 전체보기 (178) N
      • 1. AI 논문 + 모델 분석 (20)
        • AI 논문 분석 (13)
        • AI 모델 분석 (7)
      • 2. 자료구조와 알고리즘 (16)
        • 2-1 자료구조와 알고리즘 (13)
        • 2-2 강화학습 알고리즘 (3)
      • 3. 자습 & 메모(실전, 실습, 프로젝트) (25)
        • 3-1 문제 해석 (4)
        • 3-2 메모(실전, 프로젝트) (14)
        • 3-3 배포 실전 공부 (7)
      • 4. [팀] 프로젝트 및 공모전 (14)
        • 4-1 팀 프로젝트(메모, 공부) (1)
        • 4-2 Meat-A-Eye (6)
        • 4-3 RL-Tycoon-Agent (3)
        • 4-4 구조물 안정성 물리 추론 AI 경진대회(D.. (4)
      • 5. [개인] 프로젝트 및 공모전 (0)
        • 4-1 귀멸의칼날디펜스(자바스크립트 활용) (5)
        • 4-2 바탕화면 AI 펫 프로그램 (4)
        • 4-3 개인 프로젝트(기타) (3)
      • 개념 정리 step1 (32)
        • Python 기초 (7)
        • DBMS (1)
        • HTML | CSS (3)
        • Git | GitHub (1)
        • JavaScript (5)
        • Node.js (5)
        • React (1)
        • 데이터 분석 (6)
        • Python Engineering (3)
      • 개념 정리 step2 (57) N
        • Machine | Deep Learning (15)
        • 멀티모달(Multi-modal) (23)
        • 강화 학습 (10)
        • AI Agent (9) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 네이버 곤이의 블로그(Naver->Tistory)
    • Github
  • 공지사항

  • 인기 글

  • 태그

    transformer
    Python
    논문 리뷰
    알고리즘
    paddleocr
    학습
    pandas
    강화학습
    Vision
    파인튜닝
    자바스크립트
    OCR
    EfficientNet
    공모전
    구현
    강화 학습
    OCR학습
    파이썬
    자료구조
    Ai
    Grad-CAM
    bottleneck
    프로젝트
    데이터분석
    html
    javascript
    Attention Is All You Need
    github
    ViT
    귀칼
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고니3000원
[강화학습] Policy Gradient부터 Actor-Critic까지 정리
상단으로

티스토리툴바