13wk-2: 강화학습 (1) – Bandit

Author

최규빈

Published

May 29, 2024

1. 강의영상

2. Imports

import numpy as np

3. 강화학습 Intro

- 강화학습(대충설명): 어떠한 “(게임)환경”이 있을때 거기서 “뭘 할지”를 학습하는 과업

- 딥마인드: breakout \(\to\) 알파고

- 강화학습 미래? (이거 잘하면 먹고 살 수 있을까?)

4. Game1: Bandit 게임

A. 게임설명 및 원시코드

- 문제설명: 두 개의 버튼이 있다. 버튼0을 누르면 1의 보상을, 버튼1을 누르면 10의 보상을 준다고 가정

- 처음에 어떤 행동을 해야 하는가?

  • 처음에는 아는게 없음
  • 일단 “아무거나” 눌러보자.

- 버튼을 아무거나 누르는 코드를 작성해보자.

action_space = ['버튼0','버튼1']
action = np.random.choice(action_space,p=[0.5,0.5])
action
'버튼0'

action_spaceaction 이라는 용어를 기억할 것

- 버튼을 누른 행위에 따른 보상을 구현하자.

reward = 1 if action == "버튼0" else 10 
reward
1

reward라는 용어를 기억할 것

- 아무버튼이나 10번정도 눌러보면서 데이터를 쌓아보자.

action_space = ['버튼0','버튼1']
for _ in range(10):
    action = np.random.choice(action_space)
    reward = 1 if action == "버튼0" else 10
    print(action,reward)
버튼1 10
버튼1 10
버튼1 10
버튼0 1
버튼1 10
버튼1 10
버튼0 1
버튼0 1
버튼1 10
버튼0 1

- 깨달았음: 버튼0을 누르면 1점을 받고, 버튼1을 누르면 10점을 받는 “환경(environment)”이구나? \(\to\) 버튼1을 누르는 “동작(=action)”을 해야하는 상황이구나?

  • 여기에서 \(\to\)의 과정을 체계화 시킨 학문이 강화학습

environment라는 용어를 기억할 것

action_space = ['버튼0','버튼1']
for _ in range(10):
    action = '버튼1'
    reward = 1 if action == "버튼0" else 10
    print(action,reward)
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
버튼1 10
  • 게임 클리어

- 강화학습: 환경(environment)을 이해 \(\to\) 에이전트(agent)가 행동(action)을 결정

agent라는 용어를 기억할 것

위의 과정이 잘 되었다는 의미로 사용하는 문장들

  • 강화학습이 성공적으로 잘 되었다.
  • 에이전트가 환경의 과제를 완료했다.
  • 에이전트가 환경에서 성공적으로 학습했다.
  • 에이전트가 올바른 행동을 학습했다.
  • 게임 클리어 (비공식)

- 게임이 클리어 되었다는 것을 의미하는 지표를 정하고 싶다.

  • 첫 생각: 버튼1을 누르는 순간 게임클리어로 보면 되지 않나?
  • 두번째 생각: 아니지? 우연히 누를수도 있잖아?
  • 게임클리어조건: (1) 20번은 그냥 진행 (2) 최근 20번의 보상의 평균이 9.5점 이상이면 게임이 클리어 되었다고 생각하자.1
  • 1 버튼1을 눌러야 하는건 맞지만 몇번의 실수는 눈감아 주자는 의미

  • - 원시코드1: 환경을 이해하지 못한 에이전트 – 게임을 클리어할 수 없다.

    action_space = [0,1]
    actions = []
    rewards = []
    for t in range(1,51):
        action = np.random.choice(action_space)
        reward = 1 if action == 0 else 10
        actions.append(action)
        rewards.append(reward)
        #--#
        print(
            f"시도:{t}\t"
            f"행동:{action}\t"
            f"보상:{reward}\t"
            f"최근20번보상평균:{np.mean(rewards[-20:]):.4f}\t"
        )
        if t<20:
            pass 
        elif t==20:
            print("--")
        else: 
            if np.mean(rewards[-20:]) > 9.5:
                print("Game Clear")
                break
    시도:1    행동:0    보상:1    최근20번보상평균:1.0000    
    시도:2    행동:0    보상:1    최근20번보상평균:1.0000    
    시도:3    행동:0    보상:1    최근20번보상평균:1.0000    
    시도:4    행동:1    보상:10   최근20번보상평균:3.2500    
    시도:5    행동:0    보상:1    최근20번보상평균:2.8000    
    시도:6    행동:1    보상:10   최근20번보상평균:4.0000    
    시도:7    행동:0    보상:1    최근20번보상평균:3.5714    
    시도:8    행동:1    보상:10   최근20번보상평균:4.3750    
    시도:9    행동:0    보상:1    최근20번보상평균:4.0000    
    시도:10   행동:0    보상:1    최근20번보상평균:3.7000    
    시도:11   행동:1    보상:10   최근20번보상평균:4.2727    
    시도:12   행동:0    보상:1    최근20번보상평균:4.0000    
    시도:13   행동:1    보상:10   최근20번보상평균:4.4615    
    시도:14   행동:1    보상:10   최근20번보상평균:4.8571    
    시도:15   행동:1    보상:10   최근20번보상평균:5.2000    
    시도:16   행동:0    보상:1    최근20번보상평균:4.9375    
    시도:17   행동:0    보상:1    최근20번보상평균:4.7059    
    시도:18   행동:0    보상:1    최근20번보상평균:4.5000    
    시도:19   행동:1    보상:10   최근20번보상평균:4.7895    
    시도:20   행동:0    보상:1    최근20번보상평균:4.6000    
    --
    시도:21   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:22   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:23   행동:1    보상:10   최근20번보상평균:5.5000    
    시도:24   행동:1    보상:10   최근20번보상평균:5.5000    
    시도:25   행동:0    보상:1    최근20번보상평균:5.5000    
    시도:26   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:27   행동:1    보상:10   최근20번보상평균:5.5000    
    시도:28   행동:1    보상:10   최근20번보상평균:5.5000    
    시도:29   행동:1    보상:10   최근20번보상평균:5.9500    
    시도:30   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:31   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:32   행동:1    보상:10   최근20번보상평균:6.8500    
    시도:33   행동:1    보상:10   최근20번보상평균:6.8500    
    시도:34   행동:1    보상:10   최근20번보상평균:6.8500    
    시도:35   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:36   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:37   행동:1    보상:10   최근20번보상평균:6.8500    
    시도:38   행동:1    보상:10   최근20번보상평균:7.3000    
    시도:39   행동:1    보상:10   최근20번보상평균:7.3000    
    시도:40   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:41   행동:0    보상:1    최근20번보상평균:7.3000    
    시도:42   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:43   행동:0    보상:1    최근20번보상평균:7.3000    
    시도:44   행동:0    보상:1    최근20번보상평균:6.8500    
    시도:45   행동:0    보상:1    최근20번보상평균:6.8500    
    시도:46   행동:1    보상:10   최근20번보상평균:7.3000    
    시도:47   행동:1    보상:10   최근20번보상평균:7.3000    
    시도:48   행동:0    보상:1    최근20번보상평균:6.8500    
    시도:49   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:50   행동:1    보상:10   최근20번보상평균:6.4000    

    - 원시코드2: 환경을 깨달은 에이전트 – 게임클리어

    action_space = [0,1]
    actions = []
    rewards = []
    for t in range(1,51):
        action = 1
        reward = 1 if action == 0 else 10
        actions.append(action)
        rewards.append(reward)
        #--#
        print(
            f"시도:{t}\t"
            f"행동:{action}\t"
            f"보상:{reward}\t"
            f"최근20번보상평균:{np.mean(rewards[-20:]):.4f}\t"
        )
        if t<20:
            pass 
        elif t==20:
            print("--")
        else: 
            if np.mean(rewards[-20:]) > 9.5:
                print("Game Clear")
                break
    시도:1    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:2    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:3    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:4    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:5    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:6    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:7    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:8    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:9    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:10   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:11   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:12   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:13   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:14   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:15   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:16   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:17   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:18   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:19   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:20   행동:1    보상:10   최근20번보상평균:10.0000   
    --
    시도:21   행동:1    보상:10   최근20번보상평균:10.0000   
    Game Clear

    B. 수정1: Env 구현

    - Bandit 클래스 선언 + .step() 구현

    class Bandit:
        def step(self,agent_action):
            reward = 1 if agent_action == 0 else 10
            return reward
    env = Bandit()
    action_space = [0,1]
    actions = []
    rewards = []
    for t in range(1,51):
        action = np.random.choice(action_space)
        reward = env.step(action)
        actions.append(action)
        rewards.append(reward)
        #--#
        print(
            f"시도:{t}\t"
            f"행동:{action}\t"
            f"보상:{reward}\t"
            f"최근20번보상평균:{np.mean(rewards[-20:]):.4f}\t"
        )
        if t<20:
            pass 
        elif t==20:
            print("--")
        else: 
            if np.mean(rewards[-20:]) > 9.5:
                print("Game Clear")
                break
    시도:1    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:2    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:3    행동:0    보상:1    최근20번보상평균:7.0000    
    시도:4    행동:0    보상:1    최근20번보상평균:5.5000    
    시도:5    행동:0    보상:1    최근20번보상평균:4.6000    
    시도:6    행동:1    보상:10   최근20번보상평균:5.5000    
    시도:7    행동:0    보상:1    최근20번보상평균:4.8571    
    시도:8    행동:1    보상:10   최근20번보상평균:5.5000    
    시도:9    행동:1    보상:10   최근20번보상평균:6.0000    
    시도:10   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:11   행동:1    보상:10   최근20번보상평균:6.7273    
    시도:12   행동:0    보상:1    최근20번보상평균:6.2500    
    시도:13   행동:1    보상:10   최근20번보상평균:6.5385    
    시도:14   행동:1    보상:10   최근20번보상평균:6.7857    
    시도:15   행동:1    보상:10   최근20번보상평균:7.0000    
    시도:16   행동:1    보상:10   최근20번보상평균:7.1875    
    시도:17   행동:0    보상:1    최근20번보상평균:6.8235    
    시도:18   행동:1    보상:10   최근20번보상평균:7.0000    
    시도:19   행동:0    보상:1    최근20번보상평균:6.6842    
    시도:20   행동:1    보상:10   최근20번보상평균:6.8500    
    --
    시도:21   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:22   행동:0    보상:1    최근20번보상평균:5.9500    
    시도:23   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:24   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:25   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:26   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:27   행동:0    보상:1    최근20번보상평균:6.4000    
    시도:28   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:29   행동:1    보상:10   최근20번보상평균:6.4000    
    시도:30   행동:0    보상:1    최근20번보상평균:5.9500    
    시도:31   행동:1    보상:10   최근20번보상평균:5.9500    
    시도:32   행동:0    보상:1    최근20번보상평균:5.9500    
    시도:33   행동:0    보상:1    최근20번보상평균:5.5000    
    시도:34   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:35   행동:0    보상:1    최근20번보상평균:4.6000    
    시도:36   행동:0    보상:1    최근20번보상평균:4.1500    
    시도:37   행동:1    보상:10   최근20번보상평균:4.6000    
    시도:38   행동:1    보상:10   최근20번보상평균:4.6000    
    시도:39   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:40   행동:0    보상:1    최근20번보상평균:4.6000    
    시도:41   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:42   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:43   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:44   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:45   행동:0    보상:1    최근20번보상평균:5.0500    
    시도:46   행동:0    보상:1    최근20번보상평균:4.6000    
    시도:47   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:48   행동:1    보상:10   최근20번보상평균:5.0500    
    시도:49   행동:0    보상:1    최근20번보상평균:4.6000    
    시도:50   행동:0    보상:1    최근20번보상평균:4.6000    

    C. 수정2: Agent 구현 (인간지능)

    - Agent 클래스 설계

    • 액션을 하고, 본인의 행동과 환경에서 받은 reward를 기억
    • .act()함수와 .save_experience()함수 구현
    class Agent:
        def __init__(self):
            self.action_space = [0,1]
            self.action = None 
            self.reward = None
            self.actions = []
            self.rewards = [] 
        def act(self):
            prob = [0.5, 0.5]
            self.action = 1 #np.random.choice(self.action_space,p=prob)
        def save_experience(self):
            self.actions.append(self.action)
            self.rewards.append(self.reward)

    — 대충 아래와 같은 느낌으로 코드가 돌아가요 —

    시점0: init

    agent = Agent()
    env = Bandit()
    agent.action, agent.reward, agent.actions, agent.rewards
    (None, None, [], [])

    시점1: agent 가 acition을 선택

    agent.act()
    agent.action, agent.reward, agent.actions, agent.rewards
    (1, None, [], [])

    시점2: env가 agent에게 보상을 줌

    agent.reward = env.step(agent.action)
    agent.action, agent.reward, agent.actions, agent.rewards
    (1, 10, [], [])

    시점3: 경험을 저장

    agent.save_experience()
    agent.action, agent.reward, agent.actions, agent.rewards
    (1, 10, [1], [10])

    – 전체코드 –

    env = Bandit()
    agent = Agent()
    for t in range(1,51):
        agent.act()
        agent.reward = env.step(agent.action)
        agent.save_experience()
        #--#
        print(
            f"시도:{t}\t"
            f"행동:{agent.action}\t"
            f"보상:{agent.reward}\t"
            f"최근20번보상평균:{np.mean(agent.rewards[-20:]):.4f}\t"
        )
        if t<20:
            pass 
        elif t==20:
            print("--")
        else: 
            if np.mean(agent.rewards[-20:]) > 9.5:
                print("Game Clear")
                break    
    시도:1    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:2    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:3    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:4    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:5    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:6    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:7    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:8    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:9    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:10   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:11   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:12   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:13   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:14   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:15   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:16   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:17   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:18   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:19   행동:1    보상:10   최근20번보상평균:10.0000   
    시도:20   행동:1    보상:10   최근20번보상평균:10.0000   
    --
    시도:21   행동:1    보상:10   최근20번보상평균:10.0000   
    Game Clear

    D. 수정3: Agent 구현 (인공지능)

    - 지금까지 풀이의 한계

    • 사실 강화학습은 “환경을 이해 \(\to\) 행동을 결정” 의 과정에서 “\(\to\)”의 과정을 수식화 한 것이다.
    • 그런데 지금까지 했던 코드는 환경(environment)를 이해하는 순간 에이전트(agent)가 최적의 행동(action)2“직관적으로” 결정하였으므로 기계가 스스로 학습을 했다고 볼 수 없다.
  • 2 버튼1을 누른다

  • - 에이전트가 데이터를 보고 스스로 학습할 수 있도록 설계 – 부제: agent.learn()을 설계하자.

    1. 데이터를 모아서 q_table 를 만든다. q_table은 아래와 같은 내용을 포함한다.
    행동 보상(추정값)
    버튼0 (\(=a_0\)) 1 (\(=q_0\))
    버튼1 (\(=a_1\)) 10 (\(=q_1\))
    1. q_table을 바탕으로 적절한 정책(=policy)을 설정한다.
    • 이 예제에서는 버튼0과 버튼1을 각각 \(\big(\frac{q_0}{q_0+q_1},\frac{q_1}{q_0+q_1}\big)\) 의 확률로 선택하는 “정책”을 이용하면 충분할 듯
    • 처음부터 이러한 확률로 선택하기보다 처음 20번정도는 데이터를 쌓기 위해서 행동을 랜덤하게 선택하고, 그 이후에 위에서 제시한 확률값으로 행동을 선택하는게 합리적인듯.

    여기에서 q_table, policy라는 용어를 기억하세요.

    - q_table을 계산하는 코드 예시

    agent.actions = [0, 1, 1,  0, 1,   0, 0] 
    agent.rewards = [1, 9, 10, 1, 9.5, 1, 1.2] 
    actions = np.array(agent.actions)
    rewards = np.array(agent.rewards)
    q0,q1 = rewards[actions==0].mean(), rewards[actions==1].mean()
    q_table = np.array([q0,q1])
    q_table
    array([1.05, 9.5 ])
    q_table/ q_table.sum()
    array([0.09952607, 0.90047393])
    prob = q_table/ q_table.sum()
    agent.action = np.random.choice(agent.action_space,p = prob )
    agent.action
    1

    - 최종코드정리

    class Agent:
        def __init__(self):
            self.action_space = [0,1]
            self.action = None 
            self.reward = None
            self.actions = []
            self.rewards = []
            self.q_table = np.array([0.001,0.001])
            self.n_experience = 0 
        def act(self):
            if self.n_experience <= 20:
                self.action = np.random.choice(self.action_space)
            else: 
                prob = self.q_table/ self.q_table.sum()            
                self.action = np.random.choice(self.action_space,p = prob )
        def save_experience(self):
            self.actions.append(self.action)
            self.rewards.append(self.reward)
            self.n_experience = self.n_experience + 1 
        def learn(self):
            if self.n_experience < 20:
                pass 
            else: 
                actions = np.array(self.actions)
                rewards = np.array(self.rewards)      
                q0,q1 = rewards[actions==0].mean(), rewards[actions==1].mean()
                self.q_table = np.array([q0,q1])
    env = Bandit()
    agent = Agent()
    for t in range(1,51):
        # step1: 행동
        agent.act()
        # step2: 보상
        agent.reward = env.step(agent.action)
        # step3: 저장 & 학습
        agent.save_experience()
        agent.learn()    
        #--#
        print(
            f"시도:{t}\t"
            f"행동:{agent.action}\t"
            f"보상:{agent.reward}\t"
            f"최근20번보상평균:{np.mean(agent.rewards[-20:]):.4f}\t"
        )
        if t<20:
            pass 
        elif t==20:
            print("--")
        else: 
            if np.mean(agent.rewards[-20:]) > 9.5:
                print("Game Clear")
                break    
    시도:1    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:2    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:3    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:4    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:5    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:6    행동:1    보상:10   최근20번보상평균:10.0000   
    시도:7    행동:0    보상:1    최근20번보상평균:8.7143    
    시도:8    행동:1    보상:10   최근20번보상평균:8.8750    
    시도:9    행동:1    보상:10   최근20번보상평균:9.0000    
    시도:10   행동:1    보상:10   최근20번보상평균:9.1000    
    시도:11   행동:1    보상:10   최근20번보상평균:9.1818    
    시도:12   행동:1    보상:10   최근20번보상평균:9.2500    
    시도:13   행동:0    보상:1    최근20번보상평균:8.6154    
    시도:14   행동:1    보상:10   최근20번보상평균:8.7143    
    시도:15   행동:0    보상:1    최근20번보상평균:8.2000    
    시도:16   행동:1    보상:10   최근20번보상평균:8.3125    
    시도:17   행동:1    보상:10   최근20번보상평균:8.4118    
    시도:18   행동:1    보상:10   최근20번보상평균:8.5000    
    시도:19   행동:0    보상:1    최근20번보상평균:8.1053    
    시도:20   행동:1    보상:10   최근20번보상평균:8.2000    
    --
    시도:21   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:22   행동:0    보상:1    최근20번보상평균:7.7500    
    시도:23   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:24   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:25   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:26   행동:1    보상:10   최근20번보상평균:7.7500    
    시도:27   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:28   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:29   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:30   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:31   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:32   행동:1    보상:10   최근20번보상평균:8.2000    
    시도:33   행동:1    보상:10   최근20번보상평균:8.6500    
    시도:34   행동:1    보상:10   최근20번보상평균:8.6500    
    시도:35   행동:1    보상:10   최근20번보상평균:9.1000    
    시도:36   행동:1    보상:10   최근20번보상평균:9.1000    
    시도:37   행동:1    보상:10   최근20번보상평균:9.1000    
    시도:38   행동:1    보상:10   최근20번보상평균:9.1000    
    시도:39   행동:1    보상:10   최근20번보상평균:9.5500    
    Game Clear