-
Notifications
You must be signed in to change notification settings - Fork 0
/
agent_tabular_ql.py
193 lines (148 loc) · 6.03 KB
/
agent_tabular_ql.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"""Tabular QL agent"""
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import framework
import utils
DEBUG = False
GAMMA = 0.5 # discounted factor
TRAINING_EP = 0.5 # epsilon-greedy parameter for training
TESTING_EP = 0.05 # epsilon-greedy parameter for testing
NUM_RUNS = 10
NUM_EPOCHS = 200
NUM_EPIS_TRAIN = 25 # number of episodes for training at each epoch
NUM_EPIS_TEST = 50 # number of episodes for testing
ALPHA = 1. # learning rate for training
ACTIONS = framework.get_actions()
OBJECTS = framework.get_objects()
NUM_ACTIONS = len(ACTIONS)
NUM_OBJECTS = len(OBJECTS)
# pragma: coderesponse template
def epsilon_greedy(state_1, state_2, q_func, epsilon):
"""Returns an action selected by an epsilon-Greedy exploration policy
Args:
state_1, state_2 (int, int): two indices describing the current state
q_func (np.ndarray): current Q-function
epsilon (float): the probability of choosing a random command
Returns:
(int, int): the indices describing the action/object to take
"""
ACTIONS_OBJECTS_DIM = NUM_ACTIONS, NUM_OBJECTS
if np.random.random() < epsilon:
action_index, object_index = np.random.choice(NUM_ACTIONS), np.random.choice(NUM_OBJECTS)
else:
action_index, object_index = \
np.unravel_index(np.argmax(q_func[state_1, state_2]), ACTIONS_OBJECTS_DIM)[0], \
np.unravel_index(np.argmax(q_func[state_1, state_2]), ACTIONS_OBJECTS_DIM)[1]
return action_index, object_index
# pragma: coderesponse end
# pragma: coderesponse template
def tabular_q_learning(q_func, current_state_1, current_state_2, action_index,
object_index, reward, next_state_1, next_state_2,
terminal):
"""Update q_func for a given transition
Args:
q_func (np.ndarray): current Q-function
current_state_1, current_state_2 (int, int): two indices describing the current state
action_index (int): index of the current action
object_index (int): index of the current object
reward (float): the immediate reward the agent receives from playing current command
next_state_1, next_state_2 (int, int): two indices describing the next state
terminal (bool): True if this episode is over
Returns:
None
"""
q_func[current_state_1, current_state_2, action_index, object_index] = \
(1 - ALPHA) * q_func[current_state_1, current_state_2, action_index, object_index] + \
ALPHA * (reward + GAMMA * np.max(q_func[next_state_1, next_state_2]) * (not terminal))
# pragma: coderesponse end
# pragma: coderesponse template
def run_episode(for_training):
""" Runs one episode
If for training, update Q function
If for testing, computes and return cumulative discounted reward
Args:
for_training (bool): True if for training
Returns:
None
"""
epsilon = TRAINING_EP if for_training else TESTING_EP
epi_reward = 0
epi_step_count = 0
# initialize for each episode
(current_room_desc, current_quest_desc, terminal) = framework.newGame()
while not terminal:
# Choose next action and execute
next_action, next_object = epsilon_greedy(
dict_room_desc[current_room_desc],
dict_quest_desc[current_quest_desc],
q_func,
epsilon)
next_room_desc, next_quest_desc, reward, terminal = framework.step_game(
current_room_desc,
current_quest_desc,
next_action,
next_object)
if for_training:
# update Q-function.
tabular_q_learning(
q_func,
dict_room_desc[current_room_desc],
dict_quest_desc[current_quest_desc],
next_action,
next_object,
reward,
dict_room_desc[next_room_desc],
dict_quest_desc[next_quest_desc],
terminal)
else:
# update reward
epi_reward += (GAMMA ** epi_step_count) * reward
# prepare next step
epi_step_count += 1
current_room_desc = next_room_desc
current_quest_desc = next_quest_desc
if not for_training:
return epi_reward
# pragma: coderesponse end
def run_epoch():
"""Runs one epoch and returns reward averaged over test episodes"""
rewards = []
for _ in range(NUM_EPIS_TRAIN):
run_episode(for_training=True)
for _ in range(NUM_EPIS_TEST):
rewards.append(run_episode(for_training=False))
return np.mean(np.array(rewards))
def run():
"""Returns array of test reward per epoch for one run"""
global q_func
q_func = np.zeros((NUM_ROOM_DESC, NUM_QUESTS, NUM_ACTIONS, NUM_OBJECTS))
single_run_epoch_rewards_test = []
pbar = tqdm(range(NUM_EPOCHS), ncols=100)
for _ in pbar:
single_run_epoch_rewards_test.append(run_epoch())
pbar.set_description(
"Avg reward: {:0.6f} | Ewma reward: {:0.6f}".format(
np.mean(single_run_epoch_rewards_test),
utils.ewma(single_run_epoch_rewards_test)))
return single_run_epoch_rewards_test
if __name__ == '__main__':
# Data loading and build the dictionaries that use unique index for each state
(dict_room_desc, dict_quest_desc) = framework.make_all_states_index()
NUM_ROOM_DESC = len(dict_room_desc)
NUM_QUESTS = len(dict_quest_desc)
# set up the game
framework.load_game_data()
epoch_rewards_test = [] # shape NUM_RUNS * NUM_EPOCHS
for _ in range(NUM_RUNS):
epoch_rewards_test.append(run())
epoch_rewards_test = np.array(epoch_rewards_test)
x = np.arange(NUM_EPOCHS)
fig, axis = plt.subplots()
axis.plot(x, np.mean(epoch_rewards_test,
axis=0)) # plot reward per epoch averaged per run
axis.set_xlabel('Epochs')
axis.set_ylabel('reward')
axis.set_title(('Tablular: nRuns=%d, Epilon=%.2f, Epi=%d, alpha=%.4f' %
(NUM_RUNS, TRAINING_EP, NUM_EPIS_TRAIN, ALPHA)))
plt.show()