-
Notifications
You must be signed in to change notification settings - Fork 0
/
agent_linear.py
200 lines (152 loc) · 6.02 KB
/
agent_linear.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
194
195
196
197
198
199
"""Linear 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 = 5
NUM_EPOCHS = 600
NUM_EPIS_TRAIN = 25 # number of episodes for training at each epoch
NUM_EPIS_TEST = 50 # number of episodes for testing
ALPHA = 0.01 # learning rate for training
ACTIONS = framework.get_actions()
OBJECTS = framework.get_objects()
NUM_ACTIONS = len(ACTIONS)
NUM_OBJECTS = len(OBJECTS)
def tuple2index(action_index, object_index):
"""Converts a tuple (a,b) to an index c"""
return action_index * NUM_OBJECTS + object_index
def index2tuple(index):
"""Converts an index c to a tuple (a,b)"""
return index // NUM_OBJECTS, index % NUM_OBJECTS
# pragma: coderesponse template name="linear_epsilon_greedy"
def epsilon_greedy(state_vector, theta, epsilon):
"""Returns an action selected by an epsilon-greedy exploration policy
Args:
state_vector (np.ndarray): extracted vector representation
theta (np.ndarray): current weight matrix
epsilon (float): the probability of choosing a random command
Returns:
(int, int): the indices describing the action/object to take
"""
if np.random.random() < epsilon:
action_index, object_index = np.random.choice(NUM_ACTIONS), np.random.choice(NUM_OBJECTS)
else:
action_index, object_index = \
index2tuple(np.argmax(theta @ state_vector))
return action_index, object_index
# pragma: coderesponse end
# pragma: coderesponse template
def linear_q_learning(theta, current_state_vector, action_index, object_index,
reward, next_state_vector, terminal):
"""Update theta for a given transition
Args:
theta (np.ndarray): current weight matrix
current_state_vector (np.ndarray): vector representation of 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_vector (np.ndarray): vector representation of next state
terminal (bool): True if this episode is over
Returns:
None
"""
theta[tuple2index(action_index, object_index)] += ALPHA * \
(reward + GAMMA * np.max(theta @ next_state_vector) * \
(not terminal) - (theta @ current_state_vector)[tuple2index(action_index, object_index)]) * \
current_state_vector
# pragma: coderesponse end
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
current_state = current_room_desc + current_quest_desc
current_state_vector = utils.extract_bow_feature_vector(
current_state, dictionary)
next_action, next_object = epsilon_greedy(
current_state_vector,
theta,
epsilon)
next_room_desc, next_quest_desc, reward, terminal = framework.step_game(
current_room_desc,
current_quest_desc,
next_action,
next_object)
next_state = next_room_desc + next_quest_desc
next_state_vector = utils.extract_bow_feature_vector(
next_state, dictionary)
if for_training:
# update Q-function.
linear_q_learning(
theta,
current_state_vector,
next_action,
next_object,
reward,
next_state_vector,
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
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 theta
theta = np.zeros([action_dim, state_dim])
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__':
state_texts = utils.load_data('game.tsv')
dictionary = utils.bag_of_words(state_texts)
state_dim = len(dictionary)
action_dim = NUM_ACTIONS * NUM_OBJECTS
# 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(('Linear: nRuns=%d, Epilon=%.2f, Epi=%d, alpha=%.4f' %
(NUM_RUNS, TRAINING_EP, NUM_EPIS_TRAIN, ALPHA)))