gym_wikinav/envs/wikinav_env/environment.py (84 lines of code) (raw):

from io import StringIO import sys import gym from gym import error, spaces, utils from gym.utils import seeding from gym_wikinav.envs.wikinav_env import web_graph class WikiNavEnv(gym.Env): metadata = {"render.modes": ["human", "ansi"]} def __init__(self, beam_size=32, graph=None, goal_reward=10.0): """ Args: beam_size: Number of candidates to present as actions at each timestep graph: """ super(WikiNavEnv, self).__init__() if graph is None: graph = web_graph.EmbeddedWikispeediaGraph.get_default_graph() self.graph = graph # TODO verify beam size self.beam_size = beam_size self.goal_reward = goal_reward self.path_length = self.graph.path_length self.navigator = web_graph.Navigator(self.graph, self.beam_size, self.path_length) self._action_space = spaces.Discrete(self.beam_size) self._just_reset = False @property def action_space(self): return self._action_space @property def observation_space(self): # abstract raise NotImplementedError @property def cur_article_id(self): return self.navigator.cur_article_id @property def gold_path_length(self): return self.navigator.gold_path_length def get_article_for_action(self, action): return self.navigator.get_article_for_action(action) def _step(self, action): reward = self._reward(action) self.navigator.step(action) obs = self._observe() done = self.navigator.done info = {} return obs, reward, done, info def _reset(self): self.navigator.reset() self._just_reset = True obs = self._observe() self._just_reset = False return obs def _observe(self): # abstract raise NotImplementedError def _reward(self, action): """ Compute single-timestep reward after having taken the action specified by `action`. """ # abstract raise NotImplementedError def _render(self, mode="human", close=False): if close: return outfile = StringIO() if mode == "ansi" else sys.stdout cur_page = self.graph.get_article_title(self.cur_article_id) outfile.write("%s\n" % cur_page) return outfile class EmbeddingWikiNavEnv(WikiNavEnv): """ WikiNavEnv which represents articles with embeddings. """ def __init__(self, *args, **kwargs): super(EmbeddingWikiNavEnv, self).__init__(*args, **kwargs) self.embedding_dim = self.graph.embedding_dim self._query_embedding = None @property def observation_space(self): # 2 embeddings (query and current page) plus the embeddings of # articles on the beam return spaces.Box(low=-np.inf, high=np.inf, shape=(2 + self.beam_size, self.embedding_dim)) def _observe(self): if self._just_reset: self._query_embedding = \ self.graph.get_query_embeddings([self.navigator._path])[0] current_page_embedding = \ self.graph.get_article_embeddings([self.cur_article_id])[0] beam_embeddings = self.graph.get_article_embeddings(self.navigator.beam) return self._query_embedding, current_page_embedding, beam_embeddings def _reward(self, idx): if idx == self.graph.stop_sentinel: if self.navigator.on_target or self.navigator.done: # Return goal reward when first stopping on target and also at # every subsequent timestep. return self.goal_reward else: # Penalize for stopping on wrong page. return -self.goal_reward next_page = self.navigator.get_article_for_action(idx) overlap = self.graph.get_relative_word_overlap(next_page, self.navigator.target_id) return overlap * self.goal_reward