멀티 암드 밴딧
카지노에 놀러간 여러분의 앞에는 $n$개의 서로 다른 슬롯머신이 있습니다. 레버를 당기면 랜덤한 확률로 상금(보상)을 탈 수 있는데, 각각의 슬롯머신이 상금을 주는 확률과 상금의 크기가 서로 다릅니다. 여러분은 오늘 하루동안 이 슬롯머신이 상금을 주는 확률과 상금의 크기가 서로 다릅니다. 여러분은 오늘 하루동안 이 슬롯머신을 당겨서 최대한의 보상을 받고자 합니다.
에이전트는 한정된 플레이 횟수 속에서 자신이 알고있는 가장 좋은 슬롯머신을 계속 플레이해야할까? 아니면 더 좋은 슬롯머신이 있는지 미지의 환경을 탐색해야할까?
탐색과 활용
탐색(Exploration)
아직 잘 모르는 슬롯머신을 선택해 더 많은 정보를 얻는 과정이다. 더 좋은 슬롯머신을 발견할 가능성이 있다.
활용(Exploitation)
현재까지 알려진 정보를 바탕으로, 가장 좋은 보상을 줄것으로 보이는 슬롯머신을 선택하는 과정이다.
보상 추정하기
$1$번부터 $n$번까지의 슬롯머신 $i$에 대해 각각 $n$번씩 선택했다 해보자, i번 슬롯머신을 $n$번 당겨 $n$번의 보상을 받았을때 이 $i$번 슬롯머신 가치의 추정치는 보상의 평균이다.
환경 구성하기
import gymnasium as gym
import numpy as np
#라이브러리 불러오기
class BanditEnv(gym.Env):
def __init__(self, num_bandits):
self.num_bandits = num_bandits # 슬롯머신의 갯수
self.action_space = list(range(num_bandits)) # 행동은 가능한 모든 슬롯머신
self.observation_space = [0] # 상태는 의미 없음
def reset(self):
# 슬롯머신의 평균 보상의 크기를 미리 결정하는 코드
# 각 슬롯머신에 대해, 랜덤한 평균 보상의 크기를 결정해 줌.
# self.mean = np.random.normal(size=self.num_bandits) * 10
# 아래는 항상 같은 크기의 보상을 정해둔 코드의 예시
# self.mean = [9, 8, 7.5, 7, 8.5, 7, 6, 7.5, 8, 8.5]
self.mean=np.random.normal(size=self.num_bandits) * 10
return 0 # 상태 전환 없음. 의미 없음.
def step(self, action):
state = 0
mean = self.mean[action] # 정해둔 평균값에서 선택한 슬롯머신에 대한 값 확인
reward = mean + np.random.normal() # 보상은 그 슬롯머신의 평균값 + 랜덤한 노이즈
done = False
return state, reward, done, {}
슬롯 머신의 환경을 구현하는 코드이다. 클래스 BanditEnv
를 만들고 num_bandits
와 같은 데이터를 저장해주는데, 여기서 슬롯머신 문제의 특성상 상태와같은것은 필요 없기에 적절히 설정만 해둔다.
환경의 step
부분은 먼저 정해둔 각 슬롯머신의 평균값에서 랜덤한 노이즈를 더해 구현한다.
직접 구현해보기
class MyPolicy:
def __init__(self, num_bandits):
self.num_bandits = num_bandits # 슬롯머신 개수 저장
# 아래에 더 저장하고싶은정보 저장가능
self.bandits_data=[0 for i in range(num_bandits)]
def __call__(self, state, reward):
global howMuch
action = 0
if np.random.random(1)[0] <= howMuch:
action=np.random.randint(0, self.num_bandits)
# print("random select")
isRandom=True
else:
action=self.bandits_data.index(max(self.bandits_data))
# print("best select")
isRandom=False
self.bandits_data[action]=(self.bandits_data[action] + reward)/2
# print(" ".join(map(str, self.bandits_data)))
return action, isRandom
직접 문제 해결을 시도한 코드이다. 탐색 과정을 확률을 이용해 강제로 진행하는 방식으로 진행하였고, 외부에서 지정한 확률에 따라 랜덤한 슬롯머신을 당기거나, 평균값이 최대인 슬롯머신의 레버를 당긴다.
bandits_data
리스트는 평균값을 저장하는데, bandits_data[action]=(self.bandits_data[action] + reward)/2
에서 기존 평균값과 새로운 보상값을 평균내었다. 다만 이 방법은 정확한 평균값 수정이 아니었다.
env=BanditEnv(10)
state= env.reset()
agent=MyPolicy(10)
reward=0
n=20
howMuch=0.3
total_reward = []
len1, len2=[],[]
random_reward=[]
graph1=[]
graph2=[]
total_reward=0
# howMuch=0.01*i
for t in range(n):
action, isRandom = agent(state, reward)
# action = 1
# print("Chose action:", action)
state, reward, done, _= env.step(action)
# print("Reward:", reward)
if isRandom:
random_reward.append(reward)
len1.append(t)
else:
total_reward+=reward
len2.append(t)
# print("="*20)
total_reward+=reward
print(env.mean[action])
print("최고의 슬롯머신의 보상 평균", max(env.mean))
print(total_reward)
9 최고의 슬롯머신의 보상 평균 9 131.73648839473503
직접 구현하려는 시도에서는 위와같이 운좋게 성공하는 경우가 있긴 했으나, 대다수 실패했다.
직접 만든 코드의 시각화인데, 정상적으로 진행되지 않은걸 볼수있다.
제대로된 구현
class MyPolicy:
def __init__(self, num_bandits):
self.num_bandits = num_bandits # 슬롯머신 개수 저장
initial_q=100
self.q = [initial_q for machine in range(num_bandits)]
self.n = [0 for machine in range(num_bandits)]
def __call__(self, state):
action = np.argmax(self.q)
return action
제대로된 구현에서는 비정상적으로 높은 값에서 뽑은 값과 평균내며, 확률 없이도 전체 탐색이 가능하도록 만들어져있다.
ε-greedy 알고리즘
env = BanditEnv(10)
state = env.reset()
agent = MyPolicy(10)
# 그동안의 선택 정보를 저장
history = [100 for i in range(env.num_bandits)]
num=[1 for i in range(env.num_bandits)]
total_reward = 0
for t in range(2000):
if np.random.random(1)[0]<=0.01:
action = action=np.random.randint(0, env.num_bandits)
else:
action = agent(state)
# action = np.random.choice(env.num_bandits)
state, reward, done, _ = env.step(action)
# 기록 저장
agent.n[action]+=1
agent.q[action]+= (reward - agent.q[action])/ agent.n[action]
total_reward+=reward
# agent.q = ???
# print(f"Action: {action}")
# print(f"Reward: {reward}")
total_reward += reward
print(f"Total Reward: {total_reward}")
ε-그리디 알고리즘은 기존 그리디 알고리즘이 탐색을 정상적으로 진행하지 않는다는 점을 보완하여, 특정한 확률에 따라 강제적으로 탐색을 실행하도록 만들어진 알고리즘이다. 이 코드에서는 단순하게 특정 확률로 다른 에이전트를 실행하는 방식으로 구현했지만, 에이전트 클래스 내부에서 확률에 따라 달라지게 만들수도 있다.
과제
- 정리하기
- 도식화한 내용 코드짜오기
- 배운 내용 교재 읽어보기
- 사전과제 안한 것 수행
- 강화학습으로 풀 수 있는 주제 고민해보고 코드로 구현가능하면 해보기