인공지능/강화학습

강화학습 9장 - 정책 기반 에이전트

이게될까 2024. 6. 8. 14:10
728x90
728x90

가치 기반 에이전트가 액션을 선택하는 방식은 결정론적이다. == 모든 state의 각 상태에서 선택하는 action이 변하지 않는다.

정책 기반 에이전트는 가치 기반 에이전트에 비해 좀 더 유연하다.

action 공간이 연속적인 경우 가치 기반 에이전트는 q(s,a)에서 최대가 되는 a를 잘 찾기 힘들다.
정책 기반 에이전트는 파이가 있기 때문에 액션을 바로 뽑을 수 있다.

정책 기반 에이전트는 확률적 정책(state에서 action을 고를 확률)이다.

Policy Gradient

여기선 최대화를 해야 하기 때문에 +를 사용한다.
어떤 행동이 가장 좋은지에 대해 기준이 필요하다 => 기준 == 리턴 (정답은 아니므로 비지도 학습) => 정책함수 학습 가능

1-step MDP

딱 한스텝만 진행하는 MDO이다.
모델 프리상황에서는 r과 P를 알 수 없다.

샘플 기반 방법론으로 변경한다.

MDP에서는 한 스탭만 밟고 종료되는 것이 아니라 이후에 여러 스탭이 있기 때문에 이후의 받을 보상까지 Q로 포함한다.
목적함수에 대한 gradient를 정책이 경험한 데이터를 기반으로 계산할 수 있도록 해주는 방법이 Policy Gradient이다. 

그저 리턴이 제일 커지도록 정책을 수정하는 일을 한다.

더보기

네, Policy Gradient (PG) 알고리즘의 기본 목표는 리턴(return, 누적 보상)을 최대화하는 정책을 찾는 것입니다. 이를 위해 PG 알고리즘은 정책의 파라미터를 직접 최적화하는 접근 방식을 취합니다. 구체적으로, 에이전트가 환경과 상호작용하면서 주어진 상태에서 어떤 행동을 선택할지 결정하는 확률적 정책을 학습하고, 이 정책을 지속적으로 개선해나갑니다.

Policy Gradient의 주요 목표

  1. 리턴의 최대화: 주어진 에피소드에서 받는 누적 보상을 최대화하는 정책을 찾는 것이 목표입니다. 리턴은 주로 에피소드 내에서 얻은 보상의 합으로 정의됩니다.
  2. 정책의 직접 최적화: PG 알고리즘은 정책 (\pi_\theta)를 파라미터 (\theta)로 표현하고, 이 파라미터를 최적화합니다. 이는 상태에서 행동을 선택하는 확률을 직접 모델링하여 에이전트가 학습하도록 합니다.
  3. 확률적 정책: 정책이 확률적으로 표현되기 때문에, 에이전트는 같은 상태에서도 다양한 행동을 선택할 수 있습니다. 이는 탐험(exploration)과 이용(exploitation)의 균형을 유지하는 데 도움이 됩니다.

리턴을 최대화하는 방법

PG 알고리즘은 리턴을 최대화하기 위해 다음과 같은 절차를 따릅니다:

  1. 정책의 정의: 정책 (\pi_\theta(a|s))는 상태 (s)에서 행동 (a)를 선택할 확률을 정의합니다. 이는 보통 신경망과 같은 함수 근사기로 표현됩니다.
  2. 리턴의 기댓값 최대화: 리턴 (R)의 기댓값을 최대화하는 것이 목표입니다. 이를 위해 다음과 같은 목표 함수를 정의합니다:
  3. 경사 상승 방법: 정책의 파라미터 (\theta)를 업데이트하기 위해 목표 함수 (J(\theta))의 경사를 계산합니다. 이는 REINFORCE 알고리즘을 통해 다음과 같이 표현됩니다:
  4. 정책 업데이트: 계산된 경사를 이용해 정책 파라미터 (\theta)를 업데이트합니다. 이는 학습률 (\alpha)를 곱한 경사를 정책 파라미터에 더하는 방식으로 이루어집니다:

추가적인 개선 사항

  • 베이스라인 사용: 보상의 분산을 줄이기 위해 베이스라인 b(s)를 도입하여 업데이트 식을 개선합니다. 이는 보통 상태 가치 함수 V(s)로 정의됩니다.
  • Actor-Critic 방법: Actor-Critic 방법은 정책 Actor과 가치 함수 Critic를 동시에 학습하여 보다 안정적이고 효율적인 학습을 도모합니다. Critic은 상태의 가치를 추정하고, Actor는 이 정보를 사용해 정책을 업데이트합니다.

요약

Policy Gradient 알고리즘은 리턴을 최대화하는 정책을 찾기 위해 정책의 파라미터를 직접 최적화합니다. 이를 위해 상태에서 행동을 선택할 확률을 모델링하고, 이 확률을 지속적으로 조정하여 더 높은 보상을 얻는 방향으로 정책을 개선해나갑니다. 다양한 개선 방법과 변형이 존재하지만, 기본적인 목표는 항상 리턴을 최대화하는 최적의 정책을 찾는 것입니다.

 

 

Policy Gradient에서 V나 Q 함수를 테이블 형태로 사용하는 경우도 있지만, 반드시 테이블 형태로만 사용해야 하는 것은 아닙니다. 뉴럴 네트워크를 사용하여 (V)나 (Q) 함수를 근사할 수도 있습니다. 중요한 점은 정책을 최적화하기 위해 사용되는 방법입니다.

Policy Gradient와 Actor-Critic의 차이

  1. Policy Gradient (PG)
    • 기본 형태: 기본적인 Policy Gradient 방법은 정책을 직접 학습하는 방법입니다. 예를 들어 REINFORCE 알고리즘이 대표적입니다. 이 경우, 가치 함수 (V)나 행동 가치 함수 (Q)를 명시적으로 사용하지 않을 수 있습니다.
    • 뉴럴 네트워크 사용 여부: 정책을 표현하기 위해 뉴럴 네트워크를 사용할 수 있습니다. 즉, 정책 (\pi_\theta)를 파라미터화하는데 뉴럴 네트워크가 사용될 수 있습니다.
    • 가치 함수 사용: 만약 가치 함수를 사용하여 정책을 더 효율적으로 학습하고 싶다면, 이 가치 함수도 뉴럴 네트워크로 근사할 수 있습니다.
  2. Actor-Critic
    • Actor-Critic의 구성: Actor-Critic 방법은 Policy Gradient와 가치 기반 방법을 결합한 것입니다. Actor는 정책 (\pi_\theta)를 학습하고, Critic은 상태 가치 함수 (V)나 행동 가치 함수 (Q)를 학습합니다.
    • 뉴럴 네트워크 사용: Actor-Critic 방법에서는 보통 Actor와 Critic 모두 뉴럴 네트워크를 사용하여 근사합니다. Actor는 정책을, Critic은 가치 함수를 근사하는 데 사용됩니다.
    • 학습 과정: Critic이 상태 가치 함수 V(s)나 행동 가치 함수 Q(s,a)를 추정하여 Actor의 정책을 업데이트하는 데 사용됩니다. 이로 인해 더 안정적이고 효율적인 학습이 가능합니다.

테이블 형태와 뉴럴 네트워크

  • 테이블 형태: 작은 상태 공간에서는 (V)나 (Q) 함수를 테이블 형태로 사용할 수 있습니다. 이는 각 상태나 상태-행동 쌍에 대한 값을 테이블에 저장하고, 이를 이용하여 정책을 업데이트합니다.
  • 뉴럴 네트워크 형태: 큰 상태 공간에서는 테이블 형태로는 불가능할 수 있습니다. 이 경우 뉴럴 네트워크를 사용하여 가치 함수나 정책을 근사합니다. 이는 Actor-Critic 뿐만 아니라 단순한 Policy Gradient 방법에서도 사용될 수 있습니다.

요약

Policy Gradient 방법에서는 (V)나 (Q) 함수를 테이블 형태로 사용하거나, 뉴럴 네트워크를 사용하여 근사할 수 있습니다. 반드시 테이블 형태로만 사용해야 하는 것은 아닙니다. Actor-Critic 방법은 정책과 가치 함수를 동시에 학습하는 방법으로, 이 과정에서 뉴럴 네트워크를 사용하는 것이 일반적입니다. Policy Gradient 방법에서도 큰 상태 공간을 다루기 위해 뉴럴 네트워크를 사용할 수 있습니다.

 

Reinforce 알고리즘

log는 숫자의 크기가 크지 않기 때문에 Gt의 영향이 제일 쌔다. -> 리턴을 많이 받는 방향으로 업데이트된다.

 

 

 

s가 너무 좋은것이니 s에 대한 가치를 빼서 action의 역할을 강조하자!

분산을 줄이는 효과가 있다.

 

Q는 애초에 V로 표현 가능하다. -> Q-V는 V만의 식으로 표현 가능하다.

 

 

더보기

9. 정책 기반 에이전트

9.1 Policy Gradient

정책 기반 에이전트의 특성:

  • 가치 기반 에이전트는 결정론적(deterministic)으로 행동을 선택하는 반면, 정책 기반 에이전트는 확률적 정책(stochastic policy)을 사용합니다.
  • 정책 기반 에이전트는 가치 기반 에이전트에 비해 더 유연하며, 연속적 액션 공간에서도 잘 작동합니다.
  • 가치 기반 에이전트는 모든 가능성을 평가해야 하지만, 정책 기반 에이전트는 정책을 통해 바로 액션을 선택합니다.

목적 함수 정하기:

1-Step MDP:

  • 한 스텝만 진행하고 바로 에피소드가 끝나는 MDP.
  • 샘플 기반 방법론을 사용하여 그래디언트를 계산합니다.

9.2 REINFORCE 알고리즘

이론적 배경:

  • Policy gradient 알고리즘 중 가장 기본적이고 고전적인 알고리즘입니다.
  • Policy gradient theorem에서 유도된 수식을 사용합니다.

REINFORCE pseudo code:

  • 정책 파라미터 𝜃를 랜덤으로 초기화.
  • 에이전트의 상태를 초기화하고 에피소드 끝까지 진행하여 데이터를 수집.
  • 수집된 데이터로부터 그래디언트를 계산하고 파라미터를 업데이트.

REINFORCE 알고리즘 구현:

  • PyTorch를 사용하여 정책 네트워크를 정의하고 학습합니다.
  • gym 환경을 사용하여 에이전트의 행동을 시뮬레이션합니다.
import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

# Hyperparameters
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()
        for r, prob in self.data[::-1]:
            R = r + gamma * R
            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("# of episode :{}, avg score : {}".format(n_epi, score / print_interval))
            score = 0.0

    env.close()

if __name__ == '__main__':
    main()

9.3 액터-크리틱

Q 액터-크리틱:

  • 정책 네트워크와 밸류 네트워크를 함께 학습합니다.
  • 액터는 행동을 선택하고, 크리틱은 선택된 행동의 가치를 평가합니다.

Q Actor-Critic pseudo code:

  • 정책 파라미터 𝜃와 액션-벨류 네트워크 파라미터 𝑤를 초기화.
  • 상태 𝑠를 초기화하고 액션 𝑎를 샘플링.
  • 행동을 실행하여 보상과 다음 상태를 얻고, 파라미터를 업데이트.

어드밴티지 액터-크리틱:

TD Actor-Critic:

TD Actor-Critic 구현:

  • PyTorch를 사용하여 액터-크리틱 네트워크를 정의하고 학습합니다.
import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

# Hyperparameters
learning_rate = 0.0002
gamma = 0.98
n_rollout = 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)
        self.fc_v = nn.Linear(256, 1)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)

    def pi(self, x, softmax_dim=0):
        x = F.relu(self.fc1(x))
        x = self.fc_pi(x)
        prob = F.softmax(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, a_batch, r_batch, s_prime_batch, done_batch = torch.tensor(s_lst, dtype=torch.float), \
                                                               torch.tensor(a_lst), torch.tensor(r_lst, dtype=torch.float), \
                                                               torch.tensor(s_prime_lst, dtype=torch.float), \
                                                               torch.tensor(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 = r + gamma * self.v(s_prime) * done
        delta = td_target - self.v(s)

        pi = self.pi(s, softmax_dim=1)
        pi_a = pi.gather(1, a)
        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("# of episode :{}, avg score : {:.1f}".format(n_epi, score / print_interval))
            score = 0.0

    env.close()

if __name__ == '__main__':
    main()

이 요약을 통해 정책 기반 에이전트와 REINFORCE 알고리즘, 액터-크리틱 알고리즘의 개념과 구현 방법을 이해할 수 있습니다. 정책 기반 접근 방식은 가치 기반 접근 방식보다 유연하며, 다양한 상황에서 더 효율적으로 작동할 수 있습니다. REINFORCE와 액터-크리틱 알고리즘은 이러한 정책 기반 접근 방식의 대표적인 예로, 강화학습에서 자주 사용되는 기법들입니다.

 

 

O, X 문제 (10문제)

  1. 정책 기반 에이전트는 확률적 정책을 사용하여 액션을 선택한다. (O)
    • 해설: 정책 기반 에이전트는 확률적 정책을 사용하여 액션을 선택합니다.
    • 가치 기반 에이전트가 결정론적으로 액션을 선택 
  2. 정책 기반 에이전트는 가치 기반 에이전트보다 유연성이 떨어진다. (X)
    • 해설: 정책 기반 에이전트는 가치 기반 에이전트보다 유연성이 높습니다.
  3. REINFORCE 알고리즘은 정책 그래디언트 알고리즘의 일종이다. (O)
    • 해설: REINFORCE는 정책 그래디언트 알고리즘의 기본적인 형태입니다.
  4. Q 액터-크리틱 방법은 정책 네트워크와 밸류 네트워크를 함께 학습시키는 방법이다. (O)
    • 해설: Q 액터-크리틱은 정책 네트워크와 밸류 네트워크를 함께 학습시킵니다.
  5. 어드밴티지 액터-크리틱에서 어드밴티지 함수는 ( A(s, a) = Q(s, a) - V(s) )로 정의된다. (O)
    • 해설: 어드밴티지 함수는 Q 함수에서 V 함수를 뺀 값입니다.
  6. 정책 네트워크는 상태와 행동의 쌍을 입력으로 받는다. (X)
    • 해설: 정책 네트워크는 상태를 입력으로 받고, 행동의 확률을 출력합니다.
  7. REINFORCE 알고리즘은 온-폴리시(on-policy) 방법이다. (O)
    • 해설: REINFORCE는 현재 정책을 기반으로 학습하는 온-폴리시 방법입니다.

빈칸 문제 (10문제)

  1. 정책 기반 에이전트는 __ 정책을 사용하여 액션을 선택한다. (확률적)
    • 해설: 정책 기반 에이전트는 확률적 정책을 사용하여 액션을 선택합니다.
  2. 정책을 평가하는 함수는 __이다. (J(\theta))
    • 해설: 정책을 평가하는 함수는 J(\theta)입니다.
  3. Q 액터-크리틱 방법은 __ 네트워크와 밸류 네트워크를 함께 학습시키는 방법이다. (정책)
    • 해설: Q 액터-크리틱은 정책 네트워크와 밸류 네트워크를 함께 학습시킵니다.
  4. 정책 네트워크는 상태를 입력으로 받고 __를 출력한다. (행동의 확률)
    • 해설: 정책 네트워크는 상태를 입력으로 받고, 행동의 확률을 출력합니다.
  5. REINFORCE 알고리즘은 __ 방법이다. (온-폴리시)
    • 해설: REINFORCE는 현재 정책을 기반으로 학습하는 온-폴리시 방법입니다.

서술형 문제 (10문제)

  1. 정책 기반 에이전트의 특성과 가치 기반 에이전트와의 차이점을 설명하시오.
    • 정답: 정책 기반 에이전트는 확률적 정책을 사용하여 액션을 선택하며, 액션 공간이 연속적인 경우에도 잘 작동합니다. 반면, 가치 기반 에이전트는 결정론적 액션 선택을 하며, 모든 상태에 대해 Q 값을 계산해야 합니다. 정책 기반 에이전트는 더 유연하고, 환경의 변화에 더 잘 적응할 수 있습니다.
    • 해설: 정책 기반 에이전트는 확률적 정책과 연속적 액션 공간에서의 유연성 때문에 가치 기반 에이전트와 차별화됩니다.
  2. REINFORCE 알고리즘의 주요 개념과 그 작동 방식을 설명하시오.
    • 정답: REINFORCE는 정책 그래디언트 알고리즘의 일종으로, 에피소드마다 누적 보상 (G_t)를 계산하고, 이를 기반으로 정책 네트워크의 파라미터를 업데이트합니다. 구체적으로는 (\theta \leftarrow \theta + \alpha \nabla_\theta \log \pi_\theta(s, a) G_t)로 업데이트합니다. 이 과정에서 좋은 행동은 강화되고, 나쁜 행동은 억제됩니다.
    • 해설: REINFORCE는 정책 그래디언트를 이용해 정책 네트워크를 업데이트하는 방법으로, 누적 보상을 사용하여 학습합니다.
  3. 액터-크리틱 방법의 개념과 그 장점을 설명하시오.
    • 정답: 액터-크리틱 방법은 정책 네트워크(액터)와 가치 네트워크(크리틱)를 동시에 학습시키는 방법입니다. 액터는 액션을 선택하고, 크리틱은 선택된 액션의 가치를 평가합니다. 이 방법은 정책과 가치를 동시에 학습할 수 있어, 학습 효율성과 성능이 높아집니다.
    • 해설: 액터-크리틱은 정책과 가치를 동시에 학습하여 효율성과 성능을 높이는 방법입니다.
  4. 어드밴티지 액터-크리틱에서 어드밴티지 함수의 역할을 설명하시오.
    • 정답: 어드밴티지 함수 (A(s, a) = Q(s, a) - V(s))는 특정 액션이 얼마나 더 좋은지를 나타내며, 정책 그래디언트를 계산할 때 사용됩니다. 이는 불필요한 분산을 줄이고, 학습 속도를 높이는 데 기여합니다.
    • 해설: 어드밴티지 함수는 액션의 상대적인 가치를 평가하여 정책 그래디언트의 분산을 줄입니다.
  5. TD 액터-크리틱에서 TD 에러의 역할과 그 계산 방법을 설명하시오.
    • 해설: TD 에러는 상태 가치의 차이를 측정하여 네트워크를 업데이트하는 데 사용됩니다.
  6. 정책 그래디언트의 목적 함수와 그 최적화 방법을 설명하시오.
    • 정답:
    • 해설: 정책 그래디언트는 보상의 기댓값을 최대화하기 위해 그래디언트 상승 방법을 사용합니다.
  7. REINFORCE 알고리즘의 pseudo code를 설명하시오.
    • 정답: REINFORCE 알고리즘은 다음과 같습니다:
    • 해설: REINFORCE는 누적 보상을 기반으로 정책 네트워크를 업데이트합니다.
  8. 액터-크리틱 방법의 pseudo code를 설명하시오.
    • 해설: 액터-크리틱은 정책 네트워크와 가치 네트워크를 동시에 업데이트합니다.
  9. 어드밴티지 액터-크리틱에서 어드밴티지 함수를 사용하는 이유를 설명하시오.
    • 정답: 어드밴티지 함수는 액션의 상대적인 가치를 평가하여, 정책 그래디언트의 분산을 줄이고, 학습의 안정성과 속도를 높이기 위해 사용됩니다.
    • 해설: 어드밴티지 함수는 불필요한 분산을 줄여 학습 효율을 높입니다.
  10. 정책 기반 학습과 가치 기반 학습의 차이점을 설명하시오.
    • 해설: 정책 기반 학습은 연속적 액션 공간에서, 가치 기반 학습은 이산적 액션 공간에서 각각 강점을 가집니다.

단답형 문제 (10문제)

  1. 정책 기반 에이전트는 어떤 정책을 사용하여 액션을 선택하는가?
    • 정답: 확률적 정책
    • 해설: 정책 기반 에이전트는 확률적 정책을 사용합니다.
  2. 정책 네트워크의 파라미터는 무엇으로 나타내는가?
    • 정답: (\theta)
    • 해설: 정책 네트워크의 파라미터는 (\theta)로 나타냅니다.
  3. 정책을 평가하는 함수는 무엇인가?
    • 정답: J(\theta)
    • 해설: 정책을 평가하는 함수는 J(\theta)입니다.
  4. REINFORCE 알고리즘에서 누적 보상을 나타내는 기호는 무엇인가?
    • 정답: ( G_t )
    • 해설: ( G_t )는 누적 보상을 나타냅니다.
  5. Q 액터-크리틱 방법은 어떤 네트워크를 함께 학습시키는가?
    • 정답: 정책 네트워크와 밸류 네트워크
    • 해설: Q 액터-크리틱은 정책 네트워크와 밸류 네트워크를 함께 학습시킵니다.
  6. 어드밴티지 함수는 어떻게 정의되는가?
    • 정답: A(s, a) = Q(s, a) - V(s)
    • 해설: 어드밴티지 함수는 Q 함수에서 V 함수를 뺀 값입니다.
  7. TD 에러는 어떻게 계산되는가?
  8. 정책 네트워크는 무엇을 입력으로 받고 무엇을 출력하는가?
    • 정답: 상태를 입력으로 받고, 행동의 확률을 출력
    • 해설: 정책 네트워크는 상태를 입력으로 받고, 행동의 확률을 출력합니다.
  9. REINFORCE 알고리즘은 어떤 방법인가?
    • 정답: 온-폴리시
    • 해설: REINFORCE는 온-폴리시 방법입니다.
  10. 일반적인 MDP에서 정책 그래디언트는 어떻게 계산되는가?
728x90