From 94505a89e6c1b9dafc6b5ab140d07bc50456f892 Mon Sep 17 00:00:00 2001 From: Josh Abramson Date: Wed, 20 Nov 2019 16:07:58 +0000 Subject: [PATCH] Update citation and add demo results for no-TVT with gamma<1. PiperOrigin-RevId: 281522361 --- tvt/README.md | 189 +++++ tvt/batch_env.py | 110 +++ tvt/dmlab/README.md | 66 ++ tvt/dmlab/active_visual_match.lua | 24 + tvt/dmlab/image_utils.lua | 42 + tvt/dmlab/key_to_door.lua | 20 + tvt/dmlab/key_to_door_bluekey.lua | 22 + tvt/dmlab/key_to_door_factory.lua | 459 +++++++++++ tvt/dmlab/key_to_door_to_match.lua | 28 + tvt/dmlab/latent_information_acquisition.lua | 23 + ...latent_information_acquisition_factory.lua | 418 ++++++++++ tvt/dmlab/passive_visual_match.lua | 24 + tvt/dmlab/two_keys_to_choose_factory.lua | 503 ++++++++++++ tvt/dmlab/two_negative_keys.lua | 19 + tvt/dmlab/visual_match_factory.lua | 776 ++++++++++++++++++ tvt/images/RMA_gamma1_KtD.png | Bin 0 -> 56626 bytes tvt/images/RMA_gamma1_im2r.png | Bin 0 -> 43368 bytes tvt/images/avm_notvt.png | Bin 0 -> 40233 bytes tvt/images/avm_tvt.png | Bin 0 -> 31895 bytes tvt/images/ktd_notvt.png | Bin 0 -> 35333 bytes tvt/images/ktd_tvt.png | Bin 0 -> 31503 bytes tvt/losses.py | 157 ++++ tvt/main.py | 258 ++++++ tvt/memory.py | 294 +++++++ tvt/nest_utils.py | 85 ++ tvt/pycolab/README.md | 31 + tvt/pycolab/active_visual_match.py | 162 ++++ tvt/pycolab/common.py | 325 ++++++++ tvt/pycolab/env.py | 105 +++ tvt/pycolab/game.py | 44 + tvt/pycolab/human_player.py | 67 ++ tvt/pycolab/key_to_door.py | 214 +++++ tvt/pycolab/objects.py | 123 +++ tvt/requirements.txt | 8 + tvt/rma.py | 584 +++++++++++++ tvt/run.sh | 20 + tvt/tvt_rewards.py | 247 ++++++ 37 files changed, 5447 insertions(+) create mode 100644 tvt/README.md create mode 100644 tvt/batch_env.py create mode 100644 tvt/dmlab/README.md create mode 100644 tvt/dmlab/active_visual_match.lua create mode 100644 tvt/dmlab/image_utils.lua create mode 100644 tvt/dmlab/key_to_door.lua create mode 100644 tvt/dmlab/key_to_door_bluekey.lua create mode 100644 tvt/dmlab/key_to_door_factory.lua create mode 100644 tvt/dmlab/key_to_door_to_match.lua create mode 100644 tvt/dmlab/latent_information_acquisition.lua create mode 100644 tvt/dmlab/latent_information_acquisition_factory.lua create mode 100644 tvt/dmlab/passive_visual_match.lua create mode 100644 tvt/dmlab/two_keys_to_choose_factory.lua create mode 100644 tvt/dmlab/two_negative_keys.lua create mode 100644 tvt/dmlab/visual_match_factory.lua create mode 100644 tvt/images/RMA_gamma1_KtD.png create mode 100644 tvt/images/RMA_gamma1_im2r.png create mode 100644 tvt/images/avm_notvt.png create mode 100644 tvt/images/avm_tvt.png create mode 100644 tvt/images/ktd_notvt.png create mode 100644 tvt/images/ktd_tvt.png create mode 100644 tvt/losses.py create mode 100644 tvt/main.py create mode 100644 tvt/memory.py create mode 100644 tvt/nest_utils.py create mode 100644 tvt/pycolab/README.md create mode 100644 tvt/pycolab/active_visual_match.py create mode 100644 tvt/pycolab/common.py create mode 100644 tvt/pycolab/env.py create mode 100644 tvt/pycolab/game.py create mode 100644 tvt/pycolab/human_player.py create mode 100644 tvt/pycolab/key_to_door.py create mode 100644 tvt/pycolab/objects.py create mode 100644 tvt/requirements.txt create mode 100644 tvt/rma.py create mode 100755 tvt/run.sh create mode 100644 tvt/tvt_rewards.py diff --git a/tvt/README.md b/tvt/README.md new file mode 100644 index 0000000..d454faa --- /dev/null +++ b/tvt/README.md @@ -0,0 +1,189 @@ +# TVT: Temporal Value Transport + +An open source implementation of agents, algorithm and environments related to +the paper [Optimizing Agent Behavior over Long Time Scales by Transporting Value](https://arxiv.org/abs/1810.06721). + +## Installation + +TVT package installation and training can run using: `tvt/run.sh`. This will use +all default flag values for the training script `tvt/main.py`. See the section +on running experiments below for launching with non-default flags. + +Note that the default installation uses tensorflow without gpu. Replace +`tensorflow` by `tensorflow-gpu` in `tvt/requirements.txt` to use tensorflow +with gpu. + +## Differences between this implementation and the paper + +In the paper agents were trained using a distributed A3C architecture with +384 actors. This implementation runs a batched A2C agent on a single gpu machine +with batch size 16. + +## Tasks + +### Pycolab tasks + +In order for this to train in a reasonable time on a single machine, we +provide 2D grid world versions of the paper tasks using Pycolab, to replace +the original DeepMind Lab 3D tasks. + +Further details of the tasks are given in the Pycolab directory README and users +can also play the tasks themselves, from the command line. + +Special thanks to Hamza Merzic for writing the two Pycolab task scripts. + +### DeepMind Lab tasks + +The DeepMind Lab tasks used in the paper are also provided as part of this +release. + +Further details of specific tasks are given in the DeepMind Lab directory +README. + +## Running experiments + +### Launching + +To start an experiment, run: + +``` +source tvt_venv/bin/activate +python3 -m tvt.main +``` + +This will launch a default setup that uses the RMA agent on the 'Key To Door' +Pycolab task. + +### Important flags +`tvt.main` accepts many flags. + +Note that all the default hyperparameters are tuned for the TVT-RMA agent to +solve both `key_to_door` and `active_visual_match` Pycolab tasks. + +#### Information logging: +`logging_frequency`: frequency of logging in console and tensorboard.
+`logdir`: Directory for tensorboard logging.
+ +#### Agent configuration: +`with_memory`: default True. Whether or not agent has external memory. If set to +False, then agent has only LSTM memory.
+`with_reconstruction`: default True. Whether or not agent reconstructs the +observation as described in Reconstructive Memory Agent (RMA) architecture.
+`gamma`: Agent discount factor.
+`entropy_cost`: Weight of the entropy loss.
+`image_cost_weight`: Weight of image reconstruction loss.
+`read_strength_cost`: Weight of the memory read strength. Used to regularize the +memory acess.
+`read_strength_tolerance`: The tolerance of hinge loss for the read strengths. +
+`do_tvt`: default True. Whether or not to apply the Temporal Value Transport +Algorithm (only works if the model has external memory).
+ +#### Optimization: +`batch_size`: Batch size for the batched A2C algorithm.
+`learning_rate`: Learning rate for Adam optimizer.
+`beta1`: Adam optimizer beta1.
+`beta2`: Adam optimizer beta2.
+`epsilon` Adam optimizer epsilon.
+`num_episodes` Number of episodes to train for. None means run forever.
+ +#### Pycolab-specific flags: +`pycolab_game`: Which game to run. One of 'key_to_door' or +'active_visual_match'. See pycolab/README for description.
+ +`pycolab_num_apples`: Number of apples to sample from.
+`pycolab_apple_reward_min`: The minimum apple reward.
+`pycolab_apple_reward_max`: The maximum apple reward.
+`pycolab_fix_apple_reward_in_episode` default True. This fixes the sampled apple +reward within an episode.
+`pycolab_final_reward`: Reward obtained at the last phase.
+`pycolab_crop`: default True. Whether to crop observations or not.
+ + +### Monitoring results + +Key outputs are logged to the command line and to tensorboard logs. +We can use [tensorboard](https://www.tensorflow.org/guide/summaries_and_tensorboard) +to track the learning progress if FLAGS.logdir is set.
+``` +tensorboard --logdir= +``` +
+Key values logged: +`reward`: The total rewards agent acquired in an episode.
+`last phase reward`: The critical reward acquired in the exploit phase, which +depends on the behavior in the exploring phase.
+`tvt reward`: The total fictitious rewards generated by the Temporal Value +Transport algorithm.
+`total loss`: The sum of all losses, including policy gradient loss, value +function loss, reconstruction loss, and memory read regularization loss. We also +log these losses separatedly. + +## Example results + +Here we show the example results of running the TVT agent (with the default +hyperparameters) and the best control RMA agent (with `do_tvt=False, gamma=1`). + +Since TVT is designed to reduce the variance in signal for learning rewards that +are temporally far from the actions or information that lead to those rewards, +in the paper we focus on the reward in the last phase of each task, which is +the only reward that depends on actions or information from much earlier in the +task than the time at which the reward is given. In the experiments here, the +best way to track if TVT is working is by monitoring the `last phase reward` +as this is the critical performance we are interested in - the agent with TVT +and the control agents are doing well in the apple collecting phase, which +contributes most of the episodic rewards, but not in the last phase. + +### Key-to-door +Across 10 replicas, we found that the TVT agents get to a score of 10, +meaning they reliably collected the key in the explore phase to open the door in +the exploit phase.
+# ![TVT_ktd](images/ktd_tvt.png) +For 10 replicas without TVT and with the same hyperparameters, we see consistent +low performance.
+# ![No_TVT_ktd](images/ktd_notvt.png) +For 5 replicas with gamma equal to 1, performance of the RMA agent without TVT +is improved, but is unstable and never goes above 7.
+# ![RMA with gamma 1_ktd](images/RMA_gamma1_KtD.png) + +### Active-visual-match +Across 10 replicas, we found that the TVT agents get to a score of 10, +meaning they reliably searched for the pixel and remembered its color in the +explore phase, and then touched the corresponding pixel in the exploit +phase.
+# ![TVT_vm](images/avm_tvt.png) +For 10 replicas without TVT and with the same hyperparamters, performance is +better than chance level but not at the maximum level, indicating that it is not +able to actively seek for information in the explore phase and instead must rely +on randomly encountering the information.
+# ![No_TVT_vm](images/avm_tvt.png) +For 5 replicas with gamma equal to 1, performance of the RMA agent without TVT +is considerably worse, suggesting the behavior learnt from later phases does not +result in undirected exploration in the first phase. +# ![RMA with gamma 1_vm](images/RMA_gamma1_im2r.png) + +## Citing this work + +If you use this code in your work, please cite the accompanying paper: + +``` +@article{ + author = {Chia{-}Chun Hung and + Timothy P. Lillicrap and + Josh Abramson and + Yan Wu and + Mehdi Mirza and + Federico Carnevale and + Arun Ahuja and + Greg Wayne}, + title = {Optimizing Agent Behavior over Long Time Scales by Transporting Value}, + journal = {Nat Commun}, + volume = {10}, + year = {2019}, + doi = {https://doi.org/10.1038/s41467-019-13073-w}, +} +``` + +## Disclaimer + +This is not an officially supported Google or DeepMind product. diff --git a/tvt/batch_env.py b/tvt/batch_env.py new file mode 100644 index 0000000..bdc9c1f --- /dev/null +++ b/tvt/batch_env.py @@ -0,0 +1,110 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Threaded batch environment wrapper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from concurrent import futures + +from six.moves import range +from six.moves import zip + +from tvt import nest_utils + + +class BatchEnv(object): + """Wrapper that steps multiple environments in separate threads. + + The threads are stepped in lock step, so all threads progress by one step + before any move to the next step. + """ + + def __init__(self, batch_size, env_builder, **env_kwargs): + self.batch_size = batch_size + self._envs = [env_builder(**env_kwargs) for _ in range(batch_size)] + self._num_actions = self._envs[0].num_actions + self._observation_shape = self._envs[0].observation_shape + self._episode_length = self._envs[0].episode_length + + self._executor = futures.ThreadPoolExecutor(max_workers=self.batch_size) + + def reset(self): + """Reset the entire batch of environments.""" + + def reset_environment(env): + return env.reset() + + try: + output_list = [] + for env in self._envs: + output_list.append(self._executor.submit(reset_environment, env)) + output_list = [env_output.result() for env_output in output_list] + except KeyboardInterrupt: + self._executor.shutdown(wait=True) + raise + + observations, rewards = nest_utils.nest_stack(output_list) + return observations, rewards + + def step(self, action_list): + """Step batch of envs. + + Args: + action_list: A list of actions, one per environment in the batch. Each one + should be a scalar int or a numpy scaler int. + + Returns: + A tuple (observations, rewards): + observations: A nest of observations, each one a numpy array where the + first dimension has size equal to the number of environments in the + batch. + rewards: An array of rewards with size equal to the number of + environments in the batch. + """ + + def step_environment(env, action): + return env.step(action) + + try: + output_list = [] + for env, action in zip(self._envs, action_list): + output_list.append(self._executor.submit(step_environment, env, action)) + output_list = [env_output.result() for env_output in output_list] + except KeyboardInterrupt: + self._executor.shutdown(wait=True) + raise + + observations, rewards = nest_utils.nest_stack(output_list) + return observations, rewards + + @property + def observation_shape(self): + """Observation shape per environment, i.e. with no batch dimension.""" + return self._observation_shape + + @property + def num_actions(self): + return self._num_actions + + @property + def episode_length(self): + return self._episode_length + + def last_phase_rewards(self): + return [env.last_phase_reward() for env in self._envs] diff --git a/tvt/dmlab/README.md b/tvt/dmlab/README.md new file mode 100644 index 0000000..2cb07d3 --- /dev/null +++ b/tvt/dmlab/README.md @@ -0,0 +1,66 @@ +# DM Lab Tasks + +## General Structure + +There are 7 [DM Lab](https://github.com/deepmind/lab) tasks presented here. +Each level is composed of 3 distinct phases (except `Key To Door To Match` +which has 5 phases). The first phase is the 'explore' phase, where the agent +should learn a piece of information or do something. For all tasks, the 2nd +phase is the 'distractor' phase, where the agent collects apples for rewards. +The 3rd phase is the 'exploit' phase, where the agent gets rewards based on the +knowledge acquired or actions performed in phase 1. + +## Specific Tasks + +### Passive Visual Match + +* Phase 1: A colour square right in front of the agent. +* Phase 2: Apples collection. +* Phase 3: Choose the colour square matched that in Phase 1 among 4 options. + +### Active Visual Match + +* Phase 1: A colour square randomly placed in a two-connected room. +* Phase 2: Apples collection. +* Phase 3: Choose the colour square matched that in Phase 1 among 4 options. + +### Key To Door + +* Phase 1: A key randomly placed in a two-connected room. +* Phase 2: Apples collection. +* Phase 3: A small room with a door. If agent has key, it can open the door to + get to the goal behind the door to get reward. + +### Key To Door Bluekey + +All the same as key_to_door above but the key has a blue colour instead of +black. + +### Two Negative Keys + +* Phase 1: A blue and a red key placed in a small room. The agent can only + pick up one of the key. +* Phase 2: Apples collection. +* Phase 3: A small room with a door. If agent has either key, it can open the + door to get reward. The reward depends on which key it got in Phase 1 + All the rewards are negative in this level. + +### Latent Information Acquisition + +* Phase 1: Thre randomly sampled objects are randomly placed in a small room. + When the agent touch each object, a red or green cue will appear, + indicating the reward it is associated in this episode. No rewards + are given in this phase. +* Phase 2: Apples collection. +* Phase 3: The same three objects in Phase 1 randomly placed again in the room. + The agent will get positive rewards if pick up the objects with green + cues in Phase 1, and get negative rewards for objects with red cues. + +### Key To Door To Match + +* Phase 1: A key is randomly placed in a room. Agent could pick it up. +* Phase 2: Apples collection. +* Phase 3: A colour square behind a door. If agent has key from Phase 1, it can + open the door to see the colour. +* Phase 4: Apples collection. +* Phase 5: Chose the colour square matched that in Phase 3 among 4 options. diff --git a/tvt/dmlab/active_visual_match.lua b/tvt/dmlab/active_visual_match.lua new file mode 100644 index 0000000..4c361e2 --- /dev/null +++ b/tvt/dmlab/active_visual_match.lua @@ -0,0 +1,24 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'visual_match_factory' + +return factory.createLevelApi{ + exploreMapMode = 'TWO_ROOMS', + episodeLengthSeconds = 40, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + + differentDistractRoomTexture = true, + differentRewardRoomTexture = true, + correctReward = 10, + incorrectReward = 1, +} diff --git a/tvt/dmlab/image_utils.lua b/tvt/dmlab/image_utils.lua new file mode 100644 index 0000000..c2211f0 --- /dev/null +++ b/tvt/dmlab/image_utils.lua @@ -0,0 +1,42 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local tensor = require 'dmlab.system.tensor' + +local utils = {} +utils.COLORS = { + {0, 0, 0}, + {0, 0, 170}, + {0, 170, 0}, + {0, 170, 170}, + {170, 0, 0}, + {170, 0, 170}, + {170, 85, 0}, + {170, 170, 170}, + {85, 85, 85}, + {85, 85, 255}, + {85, 255, 85}, + {85, 255, 255}, + {255, 85, 85}, + {255, 85, 255}, + {255, 255, 85}, + {255, 255, 255}, +} + +function utils:createByteImage(h, w, rgb) + return tensor.ByteTensor(h, w, 4):fill{rgb[1], rgb[2], rgb[3], 255} +end + +function utils:createTransparentImage(h, w) + return tensor.ByteTensor(h, w, 4):fill{127, 127, 127, 0} +end + +return utils diff --git a/tvt/dmlab/key_to_door.lua b/tvt/dmlab/key_to_door.lua new file mode 100644 index 0000000..af120cc --- /dev/null +++ b/tvt/dmlab/key_to_door.lua @@ -0,0 +1,20 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'key_to_door_factory' + +return factory.createLevelApi{ + episodeLengthSeconds = 37, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + differentDistractRoomTexture = true, + differentRewardRoomTexture = true, +} diff --git a/tvt/dmlab/key_to_door_bluekey.lua b/tvt/dmlab/key_to_door_bluekey.lua new file mode 100644 index 0000000..fe85183 --- /dev/null +++ b/tvt/dmlab/key_to_door_bluekey.lua @@ -0,0 +1,22 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'key_to_door_factory' + +return factory.createLevelApi{ + keyColor = {0, 0, 255}, + episodeLengthSeconds = 37, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + differentDistractRoomTexture = true, + differentRewardRoomTexture = true, +} + diff --git a/tvt/dmlab/key_to_door_factory.lua b/tvt/dmlab/key_to_door_factory.lua new file mode 100644 index 0000000..2df8d32 --- /dev/null +++ b/tvt/dmlab/key_to_door_factory.lua @@ -0,0 +1,459 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local make_map = require 'common.make_map' +local custom_observations = require 'decorators.custom_observations' +local debug_observations = require 'decorators.debug_observations' +local game = require 'dmlab.system.game' +local map_maker = require 'dmlab.system.map_maker' +local maze_generation = require 'dmlab.system.maze_generation' +local pickup_decorator = require 'decorators.human_recognisable_pickups' +local random = require 'common.random' +local setting_overrides = require 'decorators.setting_overrides' +local texture_sets = require 'themes.texture_sets' +local themes = require 'themes.themes' +local hrp = require 'common.human_recognisable_pickups' + +local DEFAULTS = { + EPISODE_LENGTH_SECONDS = 15, + EXPLORE_LENGTH_SECONDS = 5, + DISTRACTOR_LENGTH_SECONDS = 5, + REWARD_LENGTH_SECONDS = nil, + SHOW_KEY_COLOR_SQUARE_SECONDS = 1, + PROB_APPLE_IN_DISTRACTOR_MAP = 0.3, + APPLE_REWARD = 5, + APPLE_REWARD_PROB = 1.0, + APPLE_EXTRA_REWARD_RANGE = 0, + GOAL_REWARD = 10, + DISTRACTOR_ROOM_SIZE = {11, 11}, + DIFFERENT_DISTRACT_ROOM_TEXTURE = false, + DIFFERENT_REWARD_ROOM_TEXTURE = false, + KEY_COLOR = {0, 0, 0}, +} + +local APPLE_ID = 998 +local GOAL_ID = 999 +local KEY_SPAWN_ID = 1000 +local DOOR_ID = 1001 + +local KEY_CUE_RECTANGLE_WIDTH = 600 +local KEY_CUE_RECTANGLE_HEIGHT = 200 + +-- Table that maps from full decal name to decal index number. +local decalIndices = {} + +local EXPLORE_MAP = "exploreMap" +local DISTRACTOR_MAP = "distractorMap" +local REWARD_MAP = "rewardMap" + +-- Set texture set for all maps. +local textureSet = texture_sets.PACMAN +local secondTextureSet = texture_sets.TETRIS +local thirdTextureSet = texture_sets.TRON + +local REWARD_ROOM =[[ +*** +*P* +*H* +*G* +*** +]] + +local OPEN_TWO_ROOM = [[ +********* +********* +*PKK*KKK* +*KKKKKKK* +*KKK*KKK* +********* +]] +local N_KEY_POS_IN_TWO_ROOM = 18 -- # of K in OPEN_TWO_ROOM + +local function createDistractorMaze(opts) + -- Example room with height = 2, width = 3 + -- A are possible apple locations (everywhere) + -- ***** + -- *APA* + -- *AAA* + -- ***** + + local roomHeight = opts.roomSize[1] + local roomWidth = opts.roomSize[2] + centerWidth = 1 + math.ceil(roomWidth / 2) + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, -- +2 for the two side of walls + width = roomWidth + 2 + } + + -- Fill the room with 'A' for apples. updateSpawnVars decides where to put. + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, 'A') + end + end + -- Override one cell with 'P' for spawn point. + maze:setEntityCell(2, centerWidth, 'P') + return maze +end + +local function numPossibleAppleLocations(distractorRoomSize) + return distractorRoomSize[1] * distractorRoomSize[2] - 1 +end + +local factory = {} +game:console('cg_drawScriptRectanglesAlways 1') + +function factory.createLevelApi(kwargs) + kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or + DEFAULTS.EPISODE_LENGTH_SECONDS + kwargs.exploreLengthSeconds = kwargs.exploreLengthSeconds or + DEFAULTS.EXPLORE_LENGTH_SECONDS + kwargs.rewardLengthSeconds = kwargs.rewardLengthSeconds or + DEFAULTS.REWARD_LENGTH_SECONDS + kwargs.distractorLengthSeconds = kwargs.distractorLengthSeconds or + DEFAULTS.DISTRACTOR_LENGTH_SECONDS + kwargs.distractorRoomSize = kwargs.distractorRoomSize or + DEFAULTS.DISTRACTOR_ROOM_SIZE + + kwargs.appleReward = kwargs.appleReward or DEFAULTS.APPLE_REWARD + kwargs.appleRewardProb = kwargs.appleRewardProb or DEFAULTS.APPLE_REWARD_PROB + kwargs.probAppleInDistractorMap = kwargs.probAppleInDistractorMap or + DEFAULTS.PROB_APPLE_IN_DISTRACTOR_MAP + + kwargs.appleExtraRewardRange = + kwargs.appleExtraRewardRange or DEFAULTS.APPLE_EXTRA_REWARD_RANGE + + kwargs.differentDistractRoomTexture = kwargs.differentDistractRoomTexture or + DEFAULTS.DIFFERENT_DISTRACT_ROOM_TEXTURE + + kwargs.differentRewardRoomTexture = kwargs.differentRewardRoomTexture or + DEFAULTS.DIFFERENT_REWARD_ROOM_TEXTURE + + kwargs.showKeyColorSquareSeconds = kwargs.showKeyColorSquareSeconds or + DEFAULTS.SHOW_KEY_COLOR_SQUARE_SECONDS + kwargs.goalReward = kwargs.goalReward or DEFAULTS.GOAL_REWARD + kwargs.keyColor = kwargs.keyColor or DEFAULTS.KEY_COLOR + + local api = {} + + function api:init(params) + self:_createExploreMap() + self:_createDistractorMap() + self:_createRewardMap() + + local keyInfo = { + shape='key', + pattern='solid', + color1 = kwargs.keyColor, + color2 = kwargs.keyColor + } + self._keyObject = hrp.create(keyInfo) + self._keyCueRgba = { + kwargs.keyColor[1]/255, + kwargs.keyColor[2]/255, + kwargs.keyColor[3]/255, + 1 + } + end + + function api:_createRewardMap() + self._rewardMap = map_maker:mapFromTextLevel{ + mapName = REWARD_MAP, + entityLayer = REWARD_ROOM, + } + + -- Create map theme and override default wall decal placement. + local texture = textureSet + if kwargs.differentRewardRoomTexture then + texture = thirdTextureSet + end + local rewardMapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + floorModelFrequency = 0.0, + } + + self._rewardMap = map_maker:mapFromTextLevel{ + mapName = REWARD_MAP, + entityLayer = REWARD_ROOM, + theme = rewardMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createExploreMap() + exploreMapInfo = {map = OPEN_TWO_ROOM} + + -- Create map theme and override default wall decal placement. + local exploreMapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + floorModelFrequency = 0.0, + } + + self._exploreMap = map_maker:mapFromTextLevel{ + mapName = EXPLORE_MAP, + entityLayer = exploreMapInfo.map, + theme = exploreMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createDistractorMap() + -- Create maze to be converted into map. + local maze = createDistractorMaze{roomSize = kwargs.distractorRoomSize} + + -- Create map theme with no wall decals. + local texture = textureSet + if kwargs.differentDistractRoomTexture then + texture = secondTextureSet + end + local distractorMapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + floorModelFrequency = 0.0, + } + + self._distractorMap = map_maker:mapFromTextLevel{ + mapName = DISTRACTOR_MAP, + entityLayer = maze:entityLayer(), + theme = distractorMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:start(episode, seed) + random:seed(seed) + + self._map = nil + self._time = 0 + self._holdingKey = false + self._keyPosCount = 0 + self._collectedGoal = false + + if kwargs.distractorLengthSecondsRange then + self._distractorLen = random:uniformReal( + kwargs.distractorLengthSecondsRange[1], + kwargs.distractorLengthSecondsRange[2]) + else + self._distractorLen = kwargs.distractorLengthSeconds + end + + -- Sample the key position in phase 1. + self._keyPosition = random:uniformInt(1, N_KEY_POS_IN_TWO_ROOM) + + -- Default instruction channel to 0 (indicating the rewards in final phase.) + self.setInstruction(tostring(0)) + end + + function api:filledRectangles(args) + if self._showKeyCue then + return {{ + x = 12, + y = 12, + width = KEY_CUE_RECTANGLE_WIDTH, + height = KEY_CUE_RECTANGLE_HEIGHT, + rgba = self._keyCueRgba + }} + end + return {} + end + + function api:nextMap() + -- 1. Decide what is the next map. + if self._map == nil then + self._map = EXPLORE_MAP + elseif self._map == DISTRACTOR_MAP then + self._map = REWARD_MAP + elseif self._map == EXPLORE_MAP then + if self._distractorLen > 0.0 then + self._map = DISTRACTOR_MAP + else + self._map = REWARD_MAP + end + elseif self._map == REWARD_MAP then + -- Stay in distractor map till end of episode. + self._map = DISTRACTOR_MAP + self._collectedGoal = true + end + + -- 2. Set up timeout for the up-coming map. + if self._map == DISTRACTOR_MAP and self._collectedGoal then + if not self._timeOut then -- don't override any existing timeout + self._timeOut = self._time + 0.1 + end + elseif self._map == EXPLORE_MAP then + self._timeOut = self._time + kwargs.exploreLengthSeconds + elseif self._map == DISTRACTOR_MAP then + self._timeOut = self._time + self._distractorLen + elseif self._map == REWARD_MAP then + if kwargs.rewardLengthSeconds then + self._timeOut = self._time + kwargs.rewardLengthSeconds + else + self._timeOut = nil + end + end + + return self._map + end + + -- PICKUP functions ---------------------------------------------------------- + + function api:_makePickup(c) + if c == 'K' then + return 'key' + end + if c == 'G' then + return 'goal' + end + if c == 'A' then + return 'apple_reward' + end + end + + function api:pickup(spawnId) + if spawnId == GOAL_ID then + local goalReward = kwargs.goalReward + game:addScore(goalReward - 10) -- Offset the default +10 for goal. + self.setInstruction(tostring(goalReward)) + game:finishMap() + end + if spawnId == KEY_SPAWN_ID then + self._holdingKey = true + self._holdingKeyTime = self._time -- When the avatar got the key. + self._showKeyCue = true + end + + if spawnId == APPLE_ID then + if kwargs.appleRewardProb >= 1 or + random:uniformReal(0, 1) < kwargs.appleRewardProb then + -- The -1 is to offset the default 1 point for apple in dmlab + appleReward = kwargs.appleReward + + random:uniformInt(0, kwargs.appleExtraRewardRange) - 1 + game:addScore(appleReward) + else + -- The -1 is to offset the default 1 point for apple in dmlab + game:addScore(-1) + end + end + end + + -- TRIGGER functions --------------------------------------------------------- + + function api:canTrigger(teleportId, targetName) + if string.sub(targetName, 1, 4) == 'door' then + if self._holdingKey then + return true + else + return false + end + end + return true + end + + function api:trigger(teleportId, targetName) + if string.sub(targetName, 1, 4) == 'door' then + -- When door opend, stop showing key cue, and set holding key to false. + self._showKeyCue = false + self._holdingKey = false + return + end + end + + function api:hasEpisodeFinished(timeSeconds) + self._time = timeSeconds + + if self._map == REWARD_MAP or self._collectedGoal then + return self._timeOut and timeSeconds > self._timeOut + end + + -- Control the timing of showing key cue. + if self._holdingKey then + local showTime = self._time - self._holdingKeyTime + if showTime > kwargs.showKeyColorSquareSeconds then + self._showKeyCue = false + end + end + + if self._map == EXPLORE_MAP or self._map == DISTRACTOR_MAP then + if timeSeconds > self._timeOut then + game:finishMap() + end + return false + end + end + + -- END TRIGGER functions ----------------------------------------------------- + + function api:updateSpawnVars(spawnVars) + local classname = spawnVars.classname + if classname == "info_player_start" then + -- Spawn facing South. + spawnVars.angle = "-90" + spawnVars.randomAngleRange = "0" + elseif classname == "func_door" then + spawnVars.id = tostring(DOOR_ID) + spawnVars.wait = "1000000" -- Open the door for long time. + elseif classname == "goal" then + spawnVars.id = tostring(GOAL_ID) + elseif classname == "apple_reward" then + -- We respawn the avatar to distractor room after reaching goal + -- there will be no more apples in this case. + if self._collectedGoal == true then + return nil + end + local useApple = false + if kwargs.probAppleInDistractorMap > 0 then + useApple = random:uniformReal(0, 1) < kwargs.probAppleInDistractorMap + end + if useApple then + spawnVars.id = tostring(APPLE_ID) + else + return nil + end + elseif classname == "key" then + self._keyPosCount = self._keyPosCount + 1 + if self._keyPosition == self._keyPosCount then + spawnVars.id = tostring(KEY_SPAWN_ID) + spawnVars.classname = self._keyObject + else + return nil + end + end + return spawnVars + end + + custom_observations.decorate(api) + pickup_decorator.decorate(api) + setting_overrides.decorate{ + api = api, + apiParams = kwargs, + decorateWithTimeout = true + } + return api +end + +return factory diff --git a/tvt/dmlab/key_to_door_to_match.lua b/tvt/dmlab/key_to_door_to_match.lua new file mode 100644 index 0000000..b8c0ffc --- /dev/null +++ b/tvt/dmlab/key_to_door_to_match.lua @@ -0,0 +1,28 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'visual_match_factory' + +return factory.createLevelApi{ + exploreMapMode = 'KEY_TO_COLOR', + episodeLengthSeconds = 45, + secondOrderExploreLengthSeconds = 5, + preExploreDistractorLengthSeconds = 15, + exploreLengthSeconds = 5, + distractorLengthSeconds = 15, + + differentDistractRoomTexture = true, + differentRewardRoomTexture = true, + differentSecondOrderRoomTexture = true, + secondOrderExploreRoomSize = {4, 4}, + correctReward = 10, + incorrectReward = 1, +} diff --git a/tvt/dmlab/latent_information_acquisition.lua b/tvt/dmlab/latent_information_acquisition.lua new file mode 100644 index 0000000..b388c29 --- /dev/null +++ b/tvt/dmlab/latent_information_acquisition.lua @@ -0,0 +1,23 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'latent_information_acquisition_factory' + +return factory.createLevelApi{ + episodeLengthSeconds = 40, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + numObjects = 3, + probGoodObject = 0.5, + correctReward = 20, + incorrectReward = -10, + differentDistractRoomTexture = true, +} diff --git a/tvt/dmlab/latent_information_acquisition_factory.lua b/tvt/dmlab/latent_information_acquisition_factory.lua new file mode 100644 index 0000000..e87d7c6 --- /dev/null +++ b/tvt/dmlab/latent_information_acquisition_factory.lua @@ -0,0 +1,418 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local make_map = require 'common.make_map' +local custom_decals = require 'decorators.custom_decals_decoration' +local custom_entities = require 'common.custom_entities' +local custom_observations = require 'decorators.custom_observations' +local datasets_selector = require 'datasets.selector' +local game = require 'dmlab.system.game' +local maze_generation = require 'dmlab.system.maze_generation' +local pickup_decorator = require 'decorators.human_recognisable_pickups' +local random = require 'common.random' +local setting_overrides = require 'decorators.setting_overrides' +local texture_sets = require 'themes.texture_sets' +local themes = require 'themes.themes' +local hrp = require 'common.human_recognisable_pickups' + +local SHOW_COLOR_CUE_SECOND = 0.25 +local EPISODE_LENGTH_SECONDS = 30 +local EXPLORE_LENGTH_SECONDS = 10 +local DISTRACTOR_LENGTH_SECONDS = 10 +local NUM_OBJECTS = 3 +local PROB_GOOD_OBJECT = 0.5 +local GAURANTEE_GOOD_OBJECTS = 0 +local GAURANTEE_BAD_OBJECTS = 0 + +local PROB_APPLE_IN_DISTRACTOR_MAP = 0.3 +local APPLE_REWARD = 5 +local APPLE_EXTRA_REWARD_RANGE = 0 +local DISTRACTOR_ROOM_SIZE = {11, 11} +local APPLE_ID = 1000 +local CORRECT_REWARD = 2 +local INCORRECT_REWARD = -1 +local ROOM_SIZE = {3, 5} +local OBJECT_SCALE = 1.62 + +local EXPLORE_MAP = "exploreMap" +local DISTRACTOR_MAP = "distractorMap" +local EXPLOIT_MAP = "exploitMap" + + +local DIFFERENT_DISTRACT_ROOM_TEXTURE = false + +-- Set texture set for all maps. +local textureSet = texture_sets.TRON +local secondTextureSet = texture_sets.TETRIS + +-- Takes goal/location:i -> i +local function nameToLocationId(name) + return tonumber(name:match('^.+:(%d+)$')) +end + +-- Takes goal/location:i -> goal/pickup +local function nameToLocationClass(name) + return name:match('^(.+):%d+$') +end + +local factory = {} +game:console('cg_drawScriptRectanglesAlways 1') + +function factory.createLevelApi(kwargs) + kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or + EPISODE_LENGTH_SECONDS + kwargs.exploreLengthSeconds = kwargs.exploreLengthSeconds or + EXPLORE_LENGTH_SECONDS + if kwargs.distractorLengthSeconds == 0 then + kwargs.skipDistractor = true + else + kwargs.distractorLengthSeconds = kwargs.distractorLengthSeconds or + DISTRACTOR_LENGTH_SECONDS + end + kwargs.numObjects = kwargs.numObjects or NUM_OBJECTS + kwargs.probGoodObject = kwargs.probGoodObject or PROB_GOOD_OBJECT + kwargs.guaranteeGoodObjects = kwargs.guaranteeGoodObjects or + GAURANTEE_GOOD_OBJECTS + kwargs.guaranteeBadObjects = kwargs.guaranteeBadObjects or + GAURANTEE_BAD_OBJECTS + kwargs.correctReward = kwargs.correctReward or CORRECT_REWARD + kwargs.incorrectReward = kwargs.incorrectReward or INCORRECT_REWARD + kwargs.roomSize = kwargs.roomSize or ROOM_SIZE + kwargs.distractorRoomSize = kwargs.distractorRoomSize or DISTRACTOR_ROOM_SIZE + kwargs.probAppleInDistractorMap = kwargs.probAppleInDistractorMap or + PROB_APPLE_IN_DISTRACTOR_MAP + kwargs.differentDistractRoomTexture = kwargs.differentDistractRoomTexture or + DIFFERENT_DISTRACT_ROOM_TEXTURE + kwargs.appleReward = kwargs.appleReward or APPLE_REWARD + kwargs.appleExtraRewardRange = kwargs.appleExtraRewardRange or + APPLE_EXTRA_REWARD_RANGE + kwargs.objectScale = kwargs.objectScale or OBJECT_SCALE + + local api = {} + + function api:init(params) + self:_createExploreMap() + self:_createDistractorMap() + self:_createExploitMap() + end + + function api:pickup(spawnId) + if self._map == EXPLORE_MAP then + -- Setup to show color cue. + self._showObjectCue = true + self._cueColor = self._objects[spawnId].cueColor + self._cueStartTime = self._time + elseif self._map == EXPLOIT_MAP then + -- Give corresponding reward and termiante when all good objects collected + game:addScore(self._objects[spawnId].reward) + -- Update the instruction channel (to record final phase rewards.) + self._finalRewardMainTask = ( + self._finalRewardMainTask + self._objects[spawnId].reward) + self.setInstruction(tostring(self._finalRewardMainTask)) + end + + if spawnId == APPLE_ID then + -- note the -1 to offset default 1 point for apple in dmlab + appleReward = kwargs.appleReward + + random:uniformInt(0, kwargs.appleExtraRewardRange) - 1 + game:addScore(appleReward) + end + end + + function api:_createRoomCommon() + local roomHeight = kwargs.roomSize[1] + local roomWidth = kwargs.roomSize[2] + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, + width = roomWidth + 2 + } + + -- Set (2,2) as 'P' for the avatar location. + -- Set (i,j) as 'O' for possible object location if i%2 == 0 && j%2 == 0. + -- Otherwise, fill with '.' for empty location. + self._numLocations = 0 + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + if i == 2 and j == 2 then + maze:setEntityCell(i, j, 'P') + elseif i % 2 == 0 and j % 2 == 0 then + maze:setEntityCell(i, j, 'O') + self._numLocations = self._numLocations + 1 + else + maze:setEntityCell(i, j, '.') + end + end + end + + return maze + end + + function api:_createExploreMap() + maze = self:_createRoomCommon() + print('Generated explore maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + local mapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + } + + local counter = 1 + self._exploreMap = make_map.makeMap{ + mapName = EXPLORE_MAP, + mapEntityLayer = maze:entityLayer(), + theme = mapTheme, + callback = function (i, j, c, maker) + if c == 'O' then + pickup = 'location:' .. counter + counter = counter + 1 + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createDistractorMap() + -- Create map theme with no wall decals. + local distractorMapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + } + + -- Example room with height = 2, width = 3 + -- ***** + -- *APA* + -- *AAA* + -- ***** + local roomHeight = kwargs.distractorRoomSize[1] + local roomWidth = kwargs.distractorRoomSize[2] + centerWidth = 1 + math.ceil(roomWidth / 2) + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, + width = roomWidth + 2 + } + + -- Fill the room with 'A' for apples. updateSpawnVars decides which to use. + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, 'A') + end + end + -- Override one cell with 'P' for spawn point. + maze:setEntityCell(2, centerWidth, 'P') + + print('Generated distractor maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + local texture = textureSet + if kwargs.differentDistractRoomTexture then + texture = secondTextureSet + end + local mapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + } + self._distractMap = make_map.makeMap{ + mapName = DISTRACTOR_MAP, + mapEntityLayer = maze:entityLayer(), + theme = mapTheme, + } + end + + function api:_createExploitMap() + maze = self:_createRoomCommon() + print('Generated exploit maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + local mapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + } + + local counter = 1 + self.exploitMap = make_map.makeMap{ + mapName = EXPLOIT_MAP, + mapEntityLayer = maze:entityLayer(), + theme = mapTheme, + useSkybox = false, + callback = function (i, j, c, maker) + if c == 'O' then + pickup = 'location:' .. counter + counter = counter + 1 + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_generateRandomObjects() + -- 1. Generate a random list of positive/negative reward, `objectValence` + -- as function(numObjects, guaranteeGood, guaranteeBad, probGoodObject) + + local objectValence = {} + for i = 1, kwargs.numObjects do + if i <= kwargs.guaranteeGoodObjects then + objectValence[i] = 1 + elseif i<= kwargs.guaranteeGoodObjects + kwargs.guaranteeBadObjects then + objectValence[i] = -1 + else + if random:uniformReal(0, 1) < kwargs.probGoodObject then + objectValence[i] = 1 + else + objectValence[i] = -1 + end + end + end + random:shuffleInPlace(objectValence) + + -- 2. Generate random objects and link to the object valence above. + local objects = hrp.uniquelyShapedPickups(kwargs.numObjects) + for i = 1, kwargs.numObjects do + objects[i].scale= kwargs.objectScale + end + + self._objects = {} + for i, object in ipairs(objects) do + self._objects[i] = {} + self._objects[i].data = hrp.create(object) + if objectValence[i] == 1 then + self._objects[i].isGoodObject = true + self._objects[i].reward = kwargs.correctReward + self._objects[i].cueColor = {0, 1, 0, 1} -- green means good + else + self._objects[i].isGoodObject = false + self._objects[i].reward = kwargs.incorrectReward + self._objects[i].cueColor = {1, 0, 0, 1} -- red means bad + end + end + end + + function api:start(episode, seed) + random:seed(seed) + + -- Setup a random mapping from locationId to pickupId + -- There should be more locationId than pickupId + -- The location set with pickupId == 0 will have no object presented there. + self._mapLocationIdToPickupId = {} + for i = 1, self._numLocations do + if i <= kwargs.numObjects then + self._mapLocationIdToPickupId[i] = i + else + self._mapLocationIdToPickupId[i] = 0 + end + end + random:shuffleInPlace(self._mapLocationIdToPickupId) + + self:_generateRandomObjects() + self._map = nil + self._numTrials = 0 + self._timeOut = kwargs.exploreLengthSeconds + + -- Set the instruction channel to record the rewards in the final phase. + self._finalRewardMainTask = 0 + self.setInstruction("0") + end + + function api:nextMap() + if self._map == nil then -- Start of episode. + self._map = EXPLORE_MAP + elseif not kwargs.skipDistractor and self._map == EXPLORE_MAP then + -- Move from explore to distractor. + self._map = DISTRACTOR_MAP + self._timeOut = self._time + kwargs.distractorLengthSeconds + elseif (kwargs.skipDistractor and self._map == EXPLORE_MAP) + or self._map == DISTRACTOR_MAP then + -- Move from distractor or explore map to exploit map. + self._map = EXPLOIT_MAP + random:shuffleInPlace(self._mapLocationIdToPickupId) + self._timeOut = nil + end + + return self._map + end + + function api:hasEpisodeFinished(timeSeconds) + self._time = timeSeconds + if self._showObjectCue then + if self._time - self._cueStartTime > SHOW_COLOR_CUE_SECOND then + self._showObjectCue = false + end + end + + if self._map == EXPLORE_MAP or self._map == DISTRACTOR_MAP then + if timeSeconds > self._timeOut then + game:finishMap() + end + return false + end + end + + -- END TRIGGER functions ----------------------------------------------------- + function api:filledRectangles(args) + if self._map == EXPLORE_MAP and self._showObjectCue then + return {{ + x = 12, + y = 12, + width = 600, + height = 300, + rgba = self._cueColor, + }} + end + return {} + end + + function api:updateSpawnVars(spawnVars) + local classname = spawnVars.classname + if classname == "info_player_start" then + -- Spawn facing South. + spawnVars.angle = "-90" + spawnVars.randomAngleRange = "0" + elseif classname == "apple_reward" then + local useApple = false + if kwargs.probAppleInDistractorMap > 0 then + useApple = random:uniformReal(0, 1) < kwargs.probAppleInDistractorMap + spawnVars.id = tostring(APPLE_ID) + end + if not useApple then + return nil + end + else + -- Allocate objects onto the map by mapLocationIdToPickupId. + local locationClass = nameToLocationClass(classname) + if locationClass then + local locationId = nameToLocationId(classname) + id = self._mapLocationIdToPickupId[locationId] + if id == 0 then + return nil + else + spawnVars.classname = self._objects[id].data + spawnVars.id = tostring(id) + end + end + end + + return spawnVars + end + + custom_observations.decorate(api) + pickup_decorator.decorate(api) + setting_overrides.decorate{ + api = api, + apiParams = kwargs, + decorateWithTimeout = true + } + return api +end + +return factory diff --git a/tvt/dmlab/passive_visual_match.lua b/tvt/dmlab/passive_visual_match.lua new file mode 100644 index 0000000..fcc7617 --- /dev/null +++ b/tvt/dmlab/passive_visual_match.lua @@ -0,0 +1,24 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'visual_match_factory' + +return factory.createLevelApi{ + exploreMapMode = 'PASSIVE', + episodeLengthSeconds = 40, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + + differentDistractRoomTexture = true, + differentRewardRoomTexture = true, + correctReward = 10, + incorrectReward = 1, +} diff --git a/tvt/dmlab/two_keys_to_choose_factory.lua b/tvt/dmlab/two_keys_to_choose_factory.lua new file mode 100644 index 0000000..4efcbed --- /dev/null +++ b/tvt/dmlab/two_keys_to_choose_factory.lua @@ -0,0 +1,503 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local make_map = require 'common.make_map' +local custom_observations = require 'decorators.custom_observations' +local debug_observations = require 'decorators.debug_observations' +local game = require 'dmlab.system.game' +local image_utils = require 'image_utils' +local map_maker = require 'dmlab.system.map_maker' +local maze_generation = require 'dmlab.system.maze_generation' +local pickup_decorator = require 'decorators.human_recognisable_pickups' +local random = require 'common.random' +local setting_overrides = require 'decorators.setting_overrides' +local texture_sets = require 'themes.texture_sets' +local themes = require 'themes.themes' +local hrp = require 'common.human_recognisable_pickups' + +local EPISODE_LENGTH_SECONDS = 15 +local EXPLORE_LENGTH_SECONDS = 5 +local DISTRACTOR_LENGTH_SECONDS = 5 +local CUE_COLORS = {2, 5} -- Either red or blue cue. +local APPLE_ID = 998 +local GOAL_ID = 999 +local KEY_SPAWN_ID = 1000 +local BAD_KEY_SPAWN_ID = 1001 +local DOOR_ID = 1002 +local KEY_CUE_RECTANGLE_WIDTH = 600 +local KEY_CUE_RECTANGLE_HEIGHT = 200 +local SHOW_COLOR_SQUARE_SECONDS = 1 + +-- Table that maps from full decal name to decal index number. +local decalIndices = {} + +local EXPLORE_MAP = "exploreMap" +local DISTRACTOR_MAP = "distractorMap" +local REWARD_MAP = "rewardMap" +local COLORS = image_utils.COLORS + +local GOAL_WITH_GOOD_KEY_REWARD = -1 +local GOAL_WITH_BAD_KEY_REWARD = -10 + +local DISTRACTOR_ROOM_SIZE = {11, 11} +local EXPLORE_ROOM_SIZE = {4, 3} +local APPLE_REWARD = 5 +local PROB_APPLE_IN_DISTRACTOR_MAP = 0.3 +local DEFAULT_FINAL_REWARD = -20 +local APPLE_EXTRA_REWARD_RANGE = 0 +local DIFFERENT_DISTRACT_ROOM_TEXTURE = false + + +-- Set texture set for all maps. +local textureSet = texture_sets.TRON +local secondTextureSet = texture_sets.TETRIS + +local REWARD_ROOM =[[ +*** +*P* +*H* +*G* +*** +]] + +local function createDistractorMaze(opts) + -- Example room with height = 2, width = 3 + -- A are possible apple locations (everywhere) + -- ***** + -- *APA* + -- *AAA* + -- ***** + + local roomHeight = opts.roomSize[1] + local roomWidth = opts.roomSize[2] + centerWidth = 1 + math.ceil(roomWidth / 2) + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, -- +2 for the two side of walls + width = roomWidth + 2 + } + + -- Fill the room with 'A' for apples. updateSpawnVars decides which to use. + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, 'A') + end + end + -- Override one cell with 'P' for spawn point. + maze:setEntityCell(2, centerWidth, 'P') + + print('Generated distractor maze with entity layer:') + print(maze:entityLayer()) + io.flush() + return maze +end + +local function createExploreMaze(opts) + -- Procedurelly generate room like below: + -- xxxxxxx + -- x P x + -- x x + -- xK Kx + -- xxxxxxx + + local roomHeight = opts.roomSize[1] + local roomWidth = opts.roomSize[2] + centerWidth = 1 + math.ceil(roomWidth / 2) + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, + width = roomWidth + 2 + } + + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, '.') + end + end + + maze:setEntityCell(2, centerWidth, 'P') + maze:setEntityCell(roomHeight + 1, 2, 'K') + maze:setEntityCell(roomHeight + 1, roomWidth + 1, 'K') + + print('Generated 2nd order explore maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + return maze +end + +local factory = {} +game:console('cg_drawScriptRectanglesAlways 1') + +function factory.createLevelApi(kwargs) + kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or + EPISODE_LENGTH_SECONDS + kwargs.exploreLengthSeconds = kwargs.exploreLengthSeconds or + EXPLORE_LENGTH_SECONDS + kwargs.distractorLengthSeconds = kwargs.distractorLengthSeconds or + DISTRACTOR_LENGTH_SECONDS + kwargs.distractorRoomSize = kwargs.distractorRoomSize or DISTRACTOR_ROOM_SIZE + kwargs.probAppleInDistractorMap = kwargs.probAppleInDistractorMap or + PROB_APPLE_IN_DISTRACTOR_MAP + kwargs.exploreRoomSize = kwargs.exploreRoomSize or EXPLORE_ROOM_SIZE + kwargs.appleExtraRewardRange = + kwargs.appleExtraRewardRange or APPLE_EXTRA_REWARD_RANGE + kwargs.differentDistractRoomTexture = kwargs.differentDistractRoomTexture or + DIFFERENT_DISTRACT_ROOM_TEXTURE + kwargs.defaultFinalReward = kwargs.defaultFinalReward or DEFAULT_FINAL_REWARD + kwargs.goalWithGoodKeyReward = kwargs.goalWithGoodKeyReward or + GOAL_WITH_GOOD_KEY_REWARD + kwargs.goalWithBadKeyReward = kwargs.goalWithBadKeyReward or + GOAL_WITH_BAD_KEY_REWARD + kwargs.appleReward = kwargs.appleReward or APPLE_REWARD + + local api = {} + + function api:init(params) + self:_createSquareExploreMap() + self:_createDistractorMap() + self:_createRewardMap() + + + -- key 1 is a red key, good, leads to less negative reward. + local keyInfo = {shape='key', pattern='solid', + color1 = {255, 0, 0}, color2={0, 0, 0}} + self._keyObject = hrp.create(keyInfo) + self._keyCueRgba = {1, 0, 0, 1} + + -- key 2 is a blue key, bad, leads to more negative reward. + local keyInfo2 = {shape='key', pattern='solid', + color1 = {0, 0, 255}, color2={0, 0, 0}} + self._keyObject2 = hrp.create(keyInfo2) + self._keyCueRgba2 = {0, 0, 1, 1} + + self._keyCueRgbaNoKey = {0, 0, 0, 1} + end + + function api:_createRewardMap() + + self._rewardMap = map_maker:mapFromTextLevel{ + mapName = REWARD_MAP, + entityLayer = REWARD_ROOM, + } + + -- Create map theme and override default wall decal placement. + local rewardMapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + } + + self._rewardMap = map_maker:mapFromTextLevel{ + mapName = REWARD_MAP, + entityLayer = REWARD_ROOM, + theme = rewardMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createSquareExploreMap() + -- Create a maze to be converted into map. + local maze = createExploreMaze{ + roomSize = kwargs.exploreRoomSize + } + + -- Create a map theme without wall decal placement. + local exploreMapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 0.0, + } + + self._exploreMap = map_maker:mapFromTextLevel{ + mapName = EXPLORE_MAP, + entityLayer = maze:entityLayer(), + theme = exploreMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createDistractorMap() + + -- Create maze to be converted into map. + local maze = createDistractorMaze{ + roomSize = kwargs.distractorRoomSize, + } + + -- Create map theme with no wall decals. + local texture = textureSet + if kwargs.differentDistractRoomTexture then + texture = secondTextureSet + end + local distractorMapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + } + + self._exploreMap = map_maker:mapFromTextLevel{ + mapName = DISTRACTOR_MAP, + entityLayer = maze:entityLayer(), + theme = distractorMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:start(episode, seed) + random:seed(seed) + + self._map = nil + self._time = 0 + self._holdingKey = false + self._holdingBadKey = false + self._keyPosCount = 0 + + self._collectedGoal = false + self._showKeyCue = false + self._showNoKeyCue = false + self._finalReward = kwargs.defaultFinalReward + self._finalRewardAdded = false + + if kwargs.distractorLengthSecondsRange then + self._distractorLen = random:uniformReal( + kwargs.distractorLengthSecondsRange[1], + kwargs.distractorLengthSecondsRange[2]) + else + self._distractorLen = kwargs.distractorLengthSeconds + end + + if kwargs.exploreRoomSize then + local posIndex = {1, 2} -- only 2 possible key location + random:shuffleInPlace(posIndex) + self._keyPosition = posIndex[1] + self._keyPosition2 = posIndex[2] + end + + -- Set instruction channel output to defaultFinalReward. + -- Later this will be set to be the goal reward if collected. + self.setInstruction(tostring(kwargs.defaultFinalReward)) + end + + function api:filledRectangles(args) + if self._showKeyCue or self._showNoKeyCue then + local cueColor + if self._holdingKey then + cueColor = self._keyCueRgba + elseif self._holdingBadKey then + cueColor = self._keyCueRgba2 + elseif self._showNoKeyCue then + cueColor = self._keyCueRgbaNoKey + end + return {{ + x = 12, + y = 12, + width = KEY_CUE_RECTANGLE_WIDTH, + height = KEY_CUE_RECTANGLE_HEIGHT, + rgba = cueColor + }} + end + return {} + end + + function api:nextMap() + -- 1. Decide what is the next map. + if self._map == nil then + self._map = EXPLORE_MAP + elseif self._map == DISTRACTOR_MAP then + self._map = REWARD_MAP + elseif self._map == EXPLORE_MAP then + if self._distractorLen > 0.0 then + -- if not holding any key, show the no key cue + if not self._holdingKey and not self._holdingBadKey then + self._showNoKeyCue = true + self._NoKeyCueTime = self._time + end + self._map = DISTRACTOR_MAP + else + self._map = REWARD_MAP + end + elseif self._map == REWARD_MAP then + -- Stay in distractor map (no more apples) till the end of episode. + self._map = DISTRACTOR_MAP + self._collectedGoal = true + end + + -- 2. Set up timeout for the up-coming map. + if self._map == EXPLORE_MAP then + self._timeOut = self._time + kwargs.exploreLengthSeconds + elseif self._map == DISTRACTOR_MAP and not self._collectedGoal then + self._timeOut = self._time + self._distractorLen + elseif self._map == REWARD_MAP then + self._timeOut = nil + end + + return self._map + end + + -- PICKUP functions ---------------------------------------------------------- + + function api:_makePickup(c) + if c == 'K' then + return 'key' + elseif c == 'G' then + return 'goal' + elseif c == 'A' then + return 'apple_reward' + end + end + + function api:canPickup(spawnId) + -- Cannot pick up another key if avatar is already holding a key. + if spawnId == KEY_SPAWN_ID and self._holdingBadKey then + return false + end + if spawnId == BAD_KEY_SPAWN_ID and self._holdingKey then + return false + end + + return true + end + + function api:pickup(spawnId) + if spawnId == GOAL_ID then + local goalReward + if self._holdingKey then + goalReward = kwargs.goalWithGoodKeyReward + elseif self._holdingBadKey then + goalReward = kwargs.goalWithBadKeyReward + end + self.setInstruction(tostring(goalReward)) + game:addScore(-10) -- offset the default +10 for pick up goal. + self._finalReward = goalReward + game:finishMap() + end + if spawnId == KEY_SPAWN_ID then + self._holdingKey = true + self._holdingKeyTime = self._time + self._showKeyCue = true + end + if spawnId == BAD_KEY_SPAWN_ID then + self._holdingBadKey = true + self._holdingKeyTime = self._time + self._showKeyCue = true + end + + if spawnId == APPLE_ID then + -- note the -1 for the default 1 point for apple in dmlab + appleReward = kwargs.appleReward + + random:uniformInt(0, kwargs.appleExtraRewardRange) - 1 + game:addScore(appleReward) + end + end + + function api:hasEpisodeFinished(timeSeconds) + self._time = timeSeconds + + -- Give the final reward near the end of the episode. + if not self._finalRewardAdded and + timeSeconds > kwargs.episodeLengthSeconds - 0.1 then + game:addScore(self._finalReward) + self._finalRewardAdded = true + end + + if (self._holdingKey or self._holdingBadKey) and + self._time - self._holdingKeyTime > SHOW_COLOR_SQUARE_SECONDS then + self._showKeyCue = false + end + + if self._showNoKeyCue and + self._time - self._NoKeyCueTime > SHOW_COLOR_SQUARE_SECONDS then + self._showNoKeyCue = false + end + + if self._map == EXPLORE_MAP or self._map == DISTRACTOR_MAP or + self._map == SECOND_ORDER_EXPLORE_MAP then + if timeSeconds > self._timeOut then + game:finishMap() + end + return false + end + end + + function api:canTrigger(teleportId, targetName) + if string.sub(targetName, 1, 4) == 'door' then + -- open the door no matter which key the avatar holds. + if self._holdingKey or self._holdingBadKey then + return true + else + return false + end + end + return false + end + + function api:updateSpawnVars(spawnVars) + local classname = spawnVars.classname + if classname == "info_player_start" then + -- Spawn facing South. + spawnVars.angle = "-90" + spawnVars.randomAngleRange = "0" + elseif classname == "func_door" then + spawnVars.id = tostring(DOOR_ID) + spawnVars.wait = "1000000" -- Door open for a long time. + elseif classname == "goal" then + spawnVars.id = tostring(GOAL_ID) + elseif classname == "apple_reward" then + -- The avatar is spawned to distractor room after reaching goal + -- there should be no more apples in such case. + if self._collectedGoal then + return nil + end + local useApple = false + if kwargs.probAppleInDistractorMap > 0 then + useApple = random:uniformReal(0, 1) < kwargs.probAppleInDistractorMap + spawnVars.id = tostring(APPLE_ID) + end + if not useApple then + return nil + end + elseif classname == "key" then + self._keyPosCount = self._keyPosCount + 1 + if self._keyPosition == self._keyPosCount then + spawnVars.id = tostring(KEY_SPAWN_ID) + spawnVars.classname = self._keyObject + elseif self._keyPosition2 == self._keyPosCount then + spawnVars.id = tostring(BAD_KEY_SPAWN_ID) + spawnVars.classname = self._keyObject2 + else + return nil + end + end + return spawnVars + end + + custom_observations.decorate(api) + pickup_decorator.decorate(api) + setting_overrides.decorate{ + api = api, + apiParams = kwargs, + decorateWithTimeout = true + } + return api + +end + +return factory diff --git a/tvt/dmlab/two_negative_keys.lua b/tvt/dmlab/two_negative_keys.lua new file mode 100644 index 0000000..c220a28 --- /dev/null +++ b/tvt/dmlab/two_negative_keys.lua @@ -0,0 +1,19 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local factory = require 'two_keys_to_choose_factory' + +return factory.createLevelApi{ + episodeLengthSeconds = 37, + exploreLengthSeconds = 5, + distractorLengthSeconds = 30, + differentDistractRoomTexture = true, +} diff --git a/tvt/dmlab/visual_match_factory.lua b/tvt/dmlab/visual_match_factory.lua new file mode 100644 index 0000000..4faf9c2 --- /dev/null +++ b/tvt/dmlab/visual_match_factory.lua @@ -0,0 +1,776 @@ +-- Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- http://www.apache.org/licenses/LICENSE-2.0 +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- ============================================================================ +local make_map = require 'common.make_map' +local custom_decals = require 'decorators.custom_decals_decoration' +local custom_entities = require 'common.custom_entities' +local custom_observations = require 'decorators.custom_observations' +local datasets_selector = require 'datasets.selector' +local debug_observations = require 'decorators.debug_observations' +local game = require 'dmlab.system.game' +local image_utils = require 'image_utils' +local map_maker = require 'dmlab.system.map_maker' +local maze_generation = require 'dmlab.system.maze_generation' +local pickup_decorator = require 'decorators.human_recognisable_pickups' +local random = require 'common.random' +local setting_overrides = require 'decorators.setting_overrides' +local texture_sets = require 'themes.texture_sets' +local themes = require 'themes.themes' +local hrp = require 'common.human_recognisable_pickups' + +local DEFAULTS = { + EXPLORE_MAP_MODE = 'PASSIVE', + EPISODE_LENGTH_SECONDS = 30, + SECOND_ORDER_EXPLORE_LENGTH_SECONDS = 4, + EXPLORE_LENGTH_SECONDS = 10, + DISTRACTOR_LENGTH_SECONDS = 10, + PRE_EXPLORE_DISTRACTOR_LENGTH_SECONDS = 0, + NUM_IMAGES = 4, + CORRECT_REWARD = 10, + INCORRECT_REWARD = 1, + IMAGE_SCALE = 3.0, + IMAGE_ROOM_HEIGHT = 4, + SHOW_KEY_COLOR_SQUARE_SECONDS = 1, + DISTRACTOR_ROOM_SIZE = {11, 11}, + SECOND_ORDER_EXPLORE_ROOM_SIZE = {3, 3}, + PROB_APPLE_IN_DISTRACTOR_MAP = 0.3, + APPLE_REWARD = 5, + APPLE_REWARD_PROB = 1.0, + APPLE_EXTRA_REWARD_RANGE = 0, + DIFFERENT_DISTRACT_ROOM_TEXTURE = false, + DIFFERENT_REWARD_ROOM_TEXTURE = false, + DIFFERENT_SECOND_ORDER_ROOM_TEXTURE = false, +} + +local APPLE_ID = 999 +local KEY_OBJECT_SPAWN_ID = 1000 +local DOOR_ID = 1001 + +-- Table that maps from full decal name to decal index number. +local decalIndices = {} + +local SECOND_ORDER_EXPLORE_MAP = "secondOrderExploreMap" +local EXPLORE_MAP = "exploreMap" +local DISTRACTOR_MAP = "distractorMap" +local IMAGE_MAP = "imageMap" +local COLORS = image_utils.COLORS + +-- Set texture set for all maps. +local textureSet = texture_sets.PACMAN +local secondTextureSet = texture_sets.TETRIS +local thirdTextureSet = texture_sets.TRON +local fourthTextureSet = texture_sets.MINESWEEPER + +local SHORT_STRAIGHT_ROOM =[[ +*** +*P* +* * +* * +*** +]] + +local SHORT_STRAIGHT_ROOM_WITH_DOOR =[[ +*** +*P* +*H* +* * +*** +]] + +local TWO_ROOMS = [[ +********* +********* +* * * +* P * +* * * +********* +]] +-- There are 24 walls for hanging the colour square. +local TWO_ROOMS_VALID_PAINT_LOCATION = 24 + +local EXPLORE_TEXT_MAP_DICT = { + PASSIVE = { + map = SHORT_STRAIGHT_ROOM, + targetPic = {row=4, col=2, dir='S'}, + }, + TWO_ROOMS = { + map = TWO_ROOMS, + targetPic = {row=0, col=0, dir='S'}, + }, + KEY_TO_COLOR = { + map = SHORT_STRAIGHT_ROOM_WITH_DOOR, + targetPic = {row=4, col=2, dir='S'}, + } +} + +--[[ +Setup image room maze. + +Example 1: +numImages = 2 +imageRoomHeight = 3 + +***** +**P** +* * +* * +*T T* +***** + *t* + *** + +Example 2: +numImages = 4 +imageRoomHeight = 4 + +********* +****P**** +* * +* * +* * +*T T T T* +********* + *t* + *** +--]] +local function createImageMaze(opts) + local numImages = opts.numImages + local imageRoomHeight = opts.imageRoomHeight or 3 + local centerWidth = 1 + numImages + + local width = 2 * numImages + 1 + -- Set the height to imageRoomHeight + 3 for image room, 2 for finish area. + local height = (imageRoomHeight + 3) + 2 + + -- Initialize the maze. All cells start as '*' (wall). + local maze = maze_generation:mazeGeneration{ + width = width, + height = height, + } + maze:setEntityCell(2, centerWidth, 'P') -- Avatar start location. + + -- Fill image room with '.' (empty space). + local imageRoomHeightStart = 3 + local imageRoomHeightEnd = imageRoomHeightStart + imageRoomHeight - 1 + for i = imageRoomHeightStart, imageRoomHeightEnd do + for j = 2, width - 1 do + maze:setEntityCell(i, j, '.') + end + end + + -- Teleports in final row of image room. + for n = 1, numImages do + maze:setEntityCell(imageRoomHeightEnd, 2 * n, 'T') + end + -- Teleport target in finish box after hallway. + maze:setEntityCell(imageRoomHeightEnd + 2, centerWidth, 't') + + print('Generated image maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + return maze +end + +local function createSecondOrderExploreMaze(opts) + -- An open layout room of size = SECOND_ORDER_EXPLORE_ROOM_SIZE + -- the avatar is always at top-left corner, while the key is at other random + -- location. For example, a 3x3 room may be like this: + -- xxxxx + -- xPxxx + -- xKKKx + -- xKKKx + -- xxxxx + + roomHeight = opts.roomSize[1] + roomWidth = opts.roomSize[2] + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, -- +2 for the two side of walls + width = roomWidth + 2 + } + + -- Fill image room with 'K' (possible key locations). + for i = 3, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, 'K') + end + end + maze:setEntityCell(2, 2, 'P') -- Avatar start at top-left corner. + + print('Generated 2nd order explore maze with entity layer:') + print(maze:entityLayer()) + io.flush() + + return maze +end + +local function createDistractorMaze(opts) + -- Example room with height = 2, width = 3 + -- A are possible apple locations (everywhere) + -- ***** + -- *APA* + -- *AAA* + -- ***** + + local roomHeight = opts.roomSize[1] + local roomWidth = opts.roomSize[2] + local centerWidth = 1 + math.ceil(roomWidth / 2) + local maze = maze_generation:mazeGeneration{ + height = roomHeight + 2, -- +2 for the two side of walls + width = roomWidth + 2 + } + + -- Fill the room with 'A' for apples. updateSpawnVars decides which to use. + for i = 2, roomHeight + 1 do + for j = 2, roomWidth + 1 do + maze:setEntityCell(i, j, 'A') + end + end + -- Override one cell with 'P' for spawn point. + maze:setEntityCell(2, centerWidth, 'P') + + print('Generated distractor maze with entity layer:') + print(maze:entityLayer()) + io.flush() + return maze +end + +local factory = {} +game:console('cg_drawScriptRectanglesAlways 1') + +function factory.createLevelApi(kwargs) + + kwargs.episodeLengthSeconds = kwargs.episodeLengthSeconds or + DEFAULTS.EPISODE_LENGTH_SECONDS + kwargs.secondOrderExploreLengthSeconds = + kwargs.secondOrderExploreLengthSeconds or + DEFAULTS.SECOND_ORDER_EXPLORE_LENGTH_SECONDS + kwargs.secondOrderExploreRoomSize = kwargs.secondOrderExploreRoomSize or + DEFAULTS.SECOND_ORDER_EXPLORE_ROOM_SIZE + + kwargs.exploreLengthSeconds = kwargs.exploreLengthSeconds or + DEFAULTS.EXPLORE_LENGTH_SECONDS + + kwargs.preExploreDistractorLengthSeconds = + kwargs.preExploreDistractorLengthSeconds or + DEFAULTS.PRE_EXPLORE_DISTRACTOR_LENGTH_SECONDS + + kwargs.distractorLengthSeconds = kwargs.distractorLengthSeconds or + DEFAULTS.DISTRACTOR_LENGTH_SECONDS + + kwargs.numImages = kwargs.numImages or DEFAULTS.NUM_IMAGES + kwargs.correctReward = kwargs.correctReward or DEFAULTS.CORRECT_REWARD + kwargs.incorrectReward = kwargs.incorrectReward or DEFAULTS.INCORRECT_REWARD + + kwargs.appleReward = kwargs.appleReward or DEFAULTS.APPLE_REWARD + kwargs.appleRewardProb = kwargs.appleRewardProb or DEFAULTS.APPLE_REWARD_PROB + kwargs.appleExtraRewardRange = + kwargs.appleExtraRewardRange or DEFAULTS.APPLE_EXTRA_REWARD_RANGE + + kwargs.imageScale = kwargs.imageScale or DEFAULTS.IMAGE_SCALE + kwargs.imageRoomHeight = kwargs.imageRoomHeight or DEFAULTS.IMAGE_ROOM_HEIGHT + kwargs.distractorRoomSize = kwargs.distractorRoomSize or + DEFAULTS.DISTRACTOR_ROOM_SIZE + kwargs.probAppleInDistractorMap = kwargs.probAppleInDistractorMap or + DEFAULTS.PROB_APPLE_IN_DISTRACTOR_MAP + kwargs.differentDistractRoomTexture = kwargs.differentDistractRoomTexture or + DEFAULTS.DIFFERENT_DISTRACT_ROOM_TEXTURE + kwargs.differentRewardRoomTexture = kwargs.differentRewardRoomTexture or + DEFAULTS.DIFFERENT_REWARD_ROOM_TEXTURE + kwargs.differentSecondOrderRoomTexture = + kwargs.differentSecondOrderRoomTexture or + DEFAULTS.DIFFERENT_SECOND_ORDER_ROOM_TEXTURE + + kwargs.showKeyColorSquareSeconds = kwargs.showKeyColorSquareSeconds or + DEFAULTS.SHOW_KEY_COLOR_SQUARE_SECONDS + + assert(kwargs.numImages % 2 == 0, + 'numImages must be an even number if there is space between images.') + assert(kwargs.numImages <= #COLORS, + 'numImages must be <=' .. #COLORS .. ' for simple color images.') + + kwargs.exploreMapMode = kwargs.exploreMapMode or DEFAULTS.EXPLORE_MAP_MODE + + local api = {} + + function api:init(params) + self._isKeyToPaintingLevel = kwargs.exploreMapMode == 'KEY_TO_COLOR' + + self:_createExploreMap() + self:_createDistractorMap() + self:_createImageMap() + if self._isKeyToPaintingLevel then + self:_createSecondOrderKeyExploreMap() + end + + self._imageOrder = {} + for i = 1, kwargs.numImages do + self._imageOrder[i] = i + end + end + + function api:_createSecondOrderKeyExploreMap() + -- Create maze to be converted into map. + local maze = createSecondOrderExploreMaze{ + roomSize = kwargs.secondOrderExploreRoomSize + } + + -- Create map theme with no wall decals. + local texture = textureSet + if kwargs.differentSecondOrderRoomTexture then + texture = fourthTextureSet + end + + local keyExploreMapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + floorModelFrequency = 0.0, + } + + self._secondOrderExploreMap = map_maker:mapFromTextLevel{ + mapName = SECOND_ORDER_EXPLORE_MAP, + entityLayer = maze:entityLayer(), + theme = keyExploreMapTheme, + callback = function (i, j, c, maker) + local pickup = self:_makePickup(c) + if pickup then + return maker:makeEntity{i = i, j = j, classname = pickup} + end + end + } + end + + function api:_createExploreMap() + -- Create map theme and override default wall decal placement. + local exploreMapTheme = themes.fromTextureSet{ + textureSet = textureSet, + decalFrequency = 1.0, + floorModelFrequency = 0.0, + } + + local exploreMapInfo = EXPLORE_TEXT_MAP_DICT[kwargs.exploreMapMode] + local targetPic = exploreMapInfo.targetPic + local exploreMapEntityLayer = exploreMapInfo.map + + -- Note on decalIndex meaning: + -- decalIndex = 1 to numImages: the id for painting in the imageRoom + -- decalIndex = numImages + 1, the target image in exploreRoom + + local function _matchTextureLocation(loc, target) + if loc.i == target.row and loc.j == target.col and + loc.direction == target.dir then + return true + else + return false + end + end + + function exploreMapTheme:placeWallDecals(allWallLocations) + local wallDecals = {} + local numPossiblePaintLocation = 0 + for _, loc in pairs(allWallLocations) do + local decalIndex = nil + if kwargs.exploreMapMode ~= 'TWO_ROOMS' then + if _matchTextureLocation(loc, targetPic) then + decalIndex = kwargs.numImages + 1 + end + else + if loc.i > 2 then + numPossiblePaintLocation = numPossiblePaintLocation + 1 + decalIndex = numPossiblePaintLocation + end + end + + if decalIndex then + local decal = textureSet.wallDecals[decalIndex] + local actualDecal = { + tex = decal.tex .. '_alpha', + scale = kwargs.imageScale, + } + wallDecals[#wallDecals + 1] = { + index = loc.index, + decal = actualDecal, + } + local fullTextureName = "textures/" .. decal.tex + decalIndices[fullTextureName] = decalIndex + end + end + return wallDecals + end + + self._exploreMap = map_maker:mapFromTextLevel{ + mapName = EXPLORE_MAP, + entityLayer = exploreMapEntityLayer, + theme = exploreMapTheme, + } + end + + function api:_createDistractorMap() + + -- Create a maze to be converted into map. + local maze = createDistractorMaze{ + roomSize = kwargs.distractorRoomSize, + } + + -- Create a map theme with no wall decals. + local texture = textureSet + if kwargs.differentDistractRoomTexture then + texture = secondTextureSet + end + local mapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 0.0, + floorModelFrequency = 0.0, + } + + self._distractorMap = make_map.makeMap{ + mapName = DISTRACTOR_MAP, + mapEntityLayer = maze:entityLayer(), + theme = mapTheme, + } + end + + function api:_createImageMap() + -- Create a maze to be converted into map. + local imageMaze = createImageMaze{ + numImages = kwargs.numImages, + imageRoomHeight = kwargs.imageRoomHeight, + } + + local texture = textureSet + if kwargs.differentRewardRoomTexture then + texture = thirdTextureSet + end + -- Create map theme and override default wall decal placement. + local imageMapTheme = themes.fromTextureSet{ + textureSet = texture, + decalFrequency = 1.0, + floorModelFrequency = 0.0, + } + local paintingsRow = kwargs.imageRoomHeight + 2 + function imageMapTheme:placeWallDecals(allWallLocations) + local wallDecals = {} + local decalCount = 1 + for _, loc in pairs(allWallLocations) do + if loc.direction == "S" then + local decalIndex = nil + if loc.i == paintingsRow then + -- Only use even columns for paintings. + if loc.j % 2 == 0 then + decalIndex = decalCount -- Will be between 1 and numImages. + decalCount = decalCount + 1 + end + end + if decalIndex then + local decal = textureSet.wallDecals[decalIndex] + decal.scale = kwargs.imageScale + wallDecals[#wallDecals + 1] = { + index = loc.index, + decal = decal, + } + local fullTextureName = "textures/" .. decal.tex + decalIndices[fullTextureName] = decalIndex + end + end + end + return wallDecals + end + + self._imageMap = map_maker:mapFromTextLevel{ + mapName = IMAGE_MAP, + entityLayer = imageMaze:entityLayer(), + theme = imageMapTheme, + callback = function (i, j, c, maker) + if c == 'T' then + return custom_entities.makeTeleporter( + {imageMaze:toWorldPos(i + 1, j + 1)}, + 'teleporter') + end + if c == 't' then + return custom_entities.makeTeleporterTarget( + {imageMaze:toWorldPos(i + 1, j + 1)}, + 'teleporter') + end + end + } + end + + function api:_prepareKey(keyColor) + self._holdingKey = false + local keyInfo = {shape='key', pattern='solid', + color1 = {0, 0, 0}, color2={0, 0, 0}} + self._keyCueColorAlpha = {0, 0, 0, 1} + self._keyObject = hrp.create(keyInfo) + end + + function api:start(episode, seed) + random:seed(seed) + self._map = nil + self._time = 0 + self._targetIndex = 1 + self._images = {} + self._preExploreDistractorLen = kwargs.preExploreDistractorLengthSeconds + self._distractorLen = kwargs.distractorLengthSeconds + + local colorIndices = {} + for i = 1, #COLORS do + colorIndices[i] = i + end + random:shuffleInPlace(colorIndices) + for i = 1, kwargs.numImages do + local rgb = COLORS[colorIndices[i]] + self._images[i] = image_utils:createByteImage(3, 3, rgb) + end + + if kwargs.exploreMapMode == 'TWO_ROOMS' then + self._images[kwargs.numImages + 1] = + image_utils:createTransparentImage(3, 3) + local nPaintPos = TWO_ROOMS_VALID_PAINT_LOCATION + self._targetPaintLocation = random:uniformInt(1, nPaintPos) + end + + if self._isKeyToPaintingLevel then + self:_prepareKey() + -- Randomly sample the key location in secondOrderExploreMaze + local nPossibleKeyLocation = (kwargs.secondOrderExploreRoomSize[1] - 1) * + kwargs.secondOrderExploreRoomSize[2] + self._keyPosition = random:uniformInt(1, nPossibleKeyLocation) + end + + -- Set instruction channel output to 0. (to indicate final phase reward.) + self.setInstruction(tostring(0)) + end + + function api:filledRectangles(args) + if self._map == SECOND_ORDER_EXPLORE_MAP and self._showKeyCue then + return {{ + x = 12, + y = 12, + width = 600, + height = 200, + rgba = self._keyCueColorAlpha + }} + end + return {} + end + + function api:nextMap() + -- 1. Decide what is the next map. + if self._map == nil or self._map == IMAGE_MAP then + if self._isKeyToPaintingLevel then + self._map = SECOND_ORDER_EXPLORE_MAP + else + if self._preExploreDistractorLen > 0.0 then + self._notExploreYet = true + self._map = DISTRACTOR_MAP + else + self._map = EXPLORE_MAP + end + end + elseif self._map == SECOND_ORDER_EXPLORE_MAP then + self._notExploreYet = true + self._map = DISTRACTOR_MAP + elseif self._map == DISTRACTOR_MAP then + if self._notExploreYet then + self._notExploreYet = false + self._map = EXPLORE_MAP + else + self._map = IMAGE_MAP + end + elseif self._map == EXPLORE_MAP then + if self._distractorLen > 0.0 then + self._map = DISTRACTOR_MAP + else + self._map = IMAGE_MAP + end + end + + -- 2. Set up properly for the up-coming map. + if self._map == DISTRACTOR_MAP and self._notExploreYet then + self._timeOut = self._time + self._preExploreDistractorLen + elseif self._map == SECOND_ORDER_EXPLORE_MAP then + self._holdingKey = false + self._timeOut = self._time + kwargs.secondOrderExploreLengthSeconds + self._possibleKeyPosCount = 0 + elseif self._map == EXPLORE_MAP then + self._timeOut = self._time + kwargs.exploreLengthSeconds + elseif self._map == DISTRACTOR_MAP and not self._notExploreYet then + self._timeOut = self._time + self._distractorLen + elseif self._map == IMAGE_MAP then + self._timeOut = nil + self._teleportId = 0 + random:shuffleInPlace(self._imageOrder) + for i, shuffled_i in ipairs(self._imageOrder) do + if self._targetIndex == shuffled_i then + self._shuffledTargetIndex = i + end + end + end + + return self._map + end + + function api:replaceShader(textureName) + local index = decalIndices[textureName] + if index then + textureName = textureName .. '_alpha' + end + return textureName + end + + function api:loadTexture(textureName) + local fullTextureName = textureName .. "_nonsolid" + local index = decalIndices[fullTextureName] + + if index then + if self._map == EXPLORE_MAP and + kwargs.exploreMapMode == 'TWO_ROOMS' then + if index == self._targetPaintLocation then + return self._images[self._targetIndex] -- Set to arget color. + else + return self._images[kwargs.numImages + 1] -- Set to transparent. + end + end + + if index <= kwargs.numImages then + local shuffledIndex = self._imageOrder[index] + return self._images[shuffledIndex] + elseif index == kwargs.numImages + 1 then + return self._images[self._targetIndex] + end + end + end + + -- PICKUP functions ---------------------------------------------------------- + + function api:_makePickup(c) + if c == 'K' then + return 'key' + end + end + + function api:pickup(spawnId) + if spawnId == KEY_OBJECT_SPAWN_ID then + self._holdingKey = true + self._holdingKeyTime = self._time + self._showKeyCue = true + end + + if spawnId == APPLE_ID then + if kwargs.appleRewardProb >= 1 or + random:uniformReal(0, 1) < kwargs.appleRewardProb then + -- the -1 is for the default 1 point for apple in dmlab + appleReward = kwargs.appleReward + + random:uniformInt(0, kwargs.appleExtraRewardRange) - 1 + game:addScore(appleReward) + else + -- the -1 is to compensate the default 1 point for apple in dmlab + game:addScore(-1) + end + end + end + + -- TRIGGER functions --------------------------------------------------------- + + function api:canTrigger(teleportId, targetName) + if string.sub(targetName, 1, 4) == 'door' and not self._holdingKey then + return false + end + return true + end + + function api:trigger(teleportId, targetName) + if string.sub(targetName, 1, 4) == 'door' then + return + end + + -- Decide if the correct teleport is triggered. + local reward = 0 + if teleportId == self._shuffledTargetIndex then + self.setInstruction(tostring(kwargs.correctReward)) + reward = kwargs.correctReward + else + self.setInstruction(tostring(kwargs.incorrectReward)) + reward = kwargs.incorrectReward + end + + game:addScore(reward) + self._timeOut = self._time + 0.2 + end + + function api:hasEpisodeFinished(timeSeconds) + self._time = timeSeconds + + -- Decide the timing of showing the key cue. + if self._isKeyToPaintingLevel and self._holdingKey then + showTime = self._time - self._holdingKeyTime + if showTime > kwargs.showKeyColorSquareSeconds then + self._showKeyCue = false + end + end + + if self._map == EXPLORE_MAP or self._map == DISTRACTOR_MAP or + self._map == SECOND_ORDER_EXPLORE_MAP then + if timeSeconds > self._timeOut then + game:finishMap() + end + return false + else -- In the image room map, timeout only after been teleported. + return self._timeOut and timeSeconds > self._timeOut + end + end + + -- END TRIGGER functions ----------------------------------------------------- + + function api:updateSpawnVars(spawnVars) + local classname = spawnVars.classname + if classname == "info_player_start" then + -- Spawn facing South. + spawnVars.angle = "-90" + spawnVars.randomAngleRange = "0" + elseif classname == "trigger_teleport" then + self._teleportId = self._teleportId + 1 + spawnVars.id = tostring(self._teleportId) + elseif classname == "func_door" then + spawnVars.id = tostring(DOOR_ID) + spawnVars.wait = "1000000" -- Open the door for long time. + elseif classname == "apple_reward" then + local useApple = false + if kwargs.probAppleInDistractorMap > 0 then + useApple = random:uniformReal(0, 1) < kwargs.probAppleInDistractorMap + spawnVars.id = tostring(APPLE_ID) + end + if not useApple then + return nil + end + elseif classname == "key" then + self._possibleKeyPosCount = self._possibleKeyPosCount + 1 + if self._keyPosition == self._possibleKeyPosCount then + spawnVars.id = tostring(KEY_OBJECT_SPAWN_ID) + spawnVars.classname = self._keyObject + else + return nil + end + end + return spawnVars + end + + custom_observations.decorate(api) + pickup_decorator.decorate(api) + setting_overrides.decorate{ + api = api, + apiParams = kwargs, + decorateWithTimeout = true + } + return api +end + +return factory diff --git a/tvt/images/RMA_gamma1_KtD.png b/tvt/images/RMA_gamma1_KtD.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d7697214f569f4942273d4f14ebc9ec0e2b863 GIT binary patch literal 56626 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 literal 0 HcmV?d00001 diff --git a/tvt/images/avm_notvt.png b/tvt/images/avm_notvt.png new file mode 100644 index 0000000000000000000000000000000000000000..44b6b2a32c07e30ef6fad80fecd385f862a80aa4 GIT binary patch literal 40233 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 RXTUtdbt#$MH|A+eiPj>hJ(D{MKq|IWx_rv1V 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≠-7Y#IB$=Ah_uLsdiQ&9&v_X!l&1O9Tm~ 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

$0k-XSJ)@>7LVNhGyXAypK89|E2R&cCyi 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 0.0: + # Neg log prob of obs image given Bernoulli(recon image) distribution. + negative_image_log_prob = tf.nn.sigmoid_cross_entropy_with_logits( + labels=targets.image, logits=recons.image) + nll_per_time = tf.reduce_sum(negative_image_log_prob, [-3, -2, -1]) + image_loss = image_cost * nll_per_time + image_loss = sum_time_average_batch(image_loss) + else: + image_loss = tf.constant(0.) + + if action_cost > 0.0 and recons.last_action is not tuple(): + # Labels have shape (T, B), logits have shape (T, B, num_actions). + action_loss = action_cost * tf.nn.sparse_softmax_cross_entropy_with_logits( + labels=targets.last_action, logits=recons.last_action) + action_loss = sum_time_average_batch(action_loss) + else: + action_loss = tf.constant(0.) + + if reward_cost > 0.0 and recons.last_reward is not tuple(): + # MSE loss for reward. + recon_last_reward = recons.last_reward + recon_last_reward = tf.squeeze(recon_last_reward, -1) + reward_loss = 0.5 * reward_cost * tf.square( + recon_last_reward - targets.last_reward) + reward_loss = sum_time_average_batch(reward_loss) + else: + reward_loss = tf.constant(0.) + + total_loss = image_loss + action_loss + reward_loss + + logged_values = dict( + recon_loss_image=image_loss, + recon_loss_action=action_loss, + recon_loss_reward=reward_loss, + total_reconstruction_loss=total_loss,) + + return total_loss, logged_values + + +def read_regularization_loss( + read_info, + strength_cost, + strength_tolerance, + strength_reg_mode, + key_norm_cost, + key_norm_tolerance): + """Computes the sum of read strength and read key regularization losses.""" + + if (strength_cost <= 0.) and (key_norm_cost <= 0.): + read_reg_loss = tf.constant(0.) + return read_reg_loss, dict(read_regularization_loss=read_reg_loss) + + if hasattr(read_info, 'read_strengths'): + read_strengths = read_info.read_strengths + read_keys = read_info.read_keys + else: + read_strengths = read_info.strengths + read_keys = read_info.keys + + if read_info == tuple(): + raise ValueError('Make sure read regularization costs are zero when ' + 'not outputting read info.') + + read_reg_loss = tf.constant(0.) + if strength_cost > 0.: + strength_hinged = tf.maximum(strength_tolerance, read_strengths) + if strength_reg_mode == 'L2': + strength_loss = 0.5 * tf.square(strength_hinged) + elif strength_reg_mode == 'L1': + # Read strengths are always positive. + strength_loss = strength_hinged + else: + raise ValueError( + 'Strength regularization mode "{}" is not supported.'.format( + strength_reg_mode)) + + # Sum across read heads to reduce from [T, B, n_reads] to [T, B]. + strength_loss = strength_cost * tf.reduce_sum(strength_loss, axis=2) + + if key_norm_cost > 0.: + key_norm_norms = tf.norm(read_keys, axis=-1) + key_norm_norms_hinged = tf.maximum(key_norm_tolerance, key_norm_norms) + key_norm_loss = 0.5 * tf.square(key_norm_norms_hinged) + + # Sum across read heads to reduce from [T, B, n_reads] to [T, B]. + key_norm_loss = key_norm_cost * tf.reduce_sum(key_norm_loss, axis=2) + + read_reg_loss += key_norm_cost * key_norm_loss + + if strength_cost > 0.: + strength_loss = sum_time_average_batch(strength_loss) + else: + strength_loss = tf.constant(0.) + + if key_norm_cost > 0.: + key_norm_loss = sum_time_average_batch(key_norm_loss) + else: + key_norm_loss = tf.constant(0.) + + read_reg_loss = strength_loss + key_norm_loss + + logged_values = dict( + read_reg_strength_loss=strength_loss, + read_reg_key_norm_loss=key_norm_loss, + total_read_reg_loss=read_reg_loss) + + return read_reg_loss, logged_values diff --git a/tvt/main.py b/tvt/main.py new file mode 100644 index 0000000..c81b123 --- /dev/null +++ b/tvt/main.py @@ -0,0 +1,258 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Batched synchronous actor/learner training.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time + +from absl import app +from absl import flags +from absl import logging +import numpy as np +from six.moves import range +from six.moves import zip +import tensorflow as tf + +from tvt import batch_env +from tvt import nest_utils +from tvt import rma +from tvt import tvt_rewards as tvt_module +from tvt.pycolab import env as pycolab_env +from tensorflow.contrib import framework as contrib_framework + +nest = contrib_framework.nest + +FLAGS = flags.FLAGS + +flags.DEFINE_integer('logging_frequency', 1, + 'Log training progress every logging_frequency episodes.') +flags.DEFINE_string('logdir', None, 'Directory for tensorboard logging.') + +flags.DEFINE_boolean('with_memory', True, + 'whether or not agent has external memory.') +flags.DEFINE_boolean('with_reconstruction', True, + 'whether or not agent reconstruct the observation.') +flags.DEFINE_float('gamma', 0.92, 'Agent discount factor') +flags.DEFINE_float('entropy_cost', 0.05, 'weight of the entropy loss') +flags.DEFINE_float('image_cost_weight', 50., 'image recon cost weight.') +flags.DEFINE_float('read_strength_cost', 5e-5, + 'Cost weight of the memory read strength.') +flags.DEFINE_float('read_strength_tolerance', 2., + 'The tolerance of hinge loss of the read_strength_cost.') +flags.DEFINE_boolean('do_tvt', True, 'whether or not do tvt') +flags.DEFINE_enum('pycolab_game', 'key_to_door', + ['key_to_door', 'active_visual_match'], + 'The name of the game in pycolab environment') +flags.DEFINE_integer('num_episodes', None, + 'Number of episodes to train for. None means run forever.') + +flags.DEFINE_integer('batch_size', 16, 'Batch size') + +flags.DEFINE_float('learning_rate', 2e-4, 'Adam optimizer learning rate') +flags.DEFINE_float('beta1', 0., 'Adam optimizer beta1') +flags.DEFINE_float('beta2', 0.95, 'Adam optimizer beta2') +flags.DEFINE_float('epsilon', 1e-6, 'Adam optimizer epsilon') + +# Pycolab-specific flags: +flags.DEFINE_integer('pycolab_num_apples', 10, + 'Number of apples to sample from the distractor grid.') +flags.DEFINE_float('pycolab_apple_reward_min', 1., + 'A reward range [min, max) to uniformly sample from.') +flags.DEFINE_float('pycolab_apple_reward_max', 10., + 'A reward range [min, max) to uniformly sample from.') +flags.DEFINE_boolean('pycolab_fix_apple_reward_in_episode', True, + 'Fix the sampled apple reward within an episode.') +flags.DEFINE_float('pycolab_final_reward', 10., + 'Reward obtained at the last phase.') +flags.DEFINE_boolean('pycolab_crop', True, + 'Whether to crop observations or not.') + + +def main(_): + + batch_size = FLAGS.batch_size + env_builder = pycolab_env.PycolabEnvironment + env_kwargs = { + 'game': FLAGS.pycolab_game, + 'num_apples': FLAGS.pycolab_num_apples, + 'apple_reward': [FLAGS.pycolab_apple_reward_min, + FLAGS.pycolab_apple_reward_max], + 'fix_apple_reward_in_episode': FLAGS.pycolab_fix_apple_reward_in_episode, + 'final_reward': FLAGS.pycolab_final_reward, + 'crop': FLAGS.pycolab_crop + } + env = batch_env.BatchEnv(batch_size, env_builder, **env_kwargs) + ep_length = env.episode_length + + agent = rma.Agent(batch_size=batch_size, + num_actions=env.num_actions, + observation_shape=env.observation_shape, + with_reconstructions=FLAGS.with_reconstruction, + gamma=FLAGS.gamma, + read_strength_cost=FLAGS.read_strength_cost, + read_strength_tolerance=FLAGS.read_strength_tolerance, + entropy_cost=FLAGS.entropy_cost, + with_memory=FLAGS.with_memory, + image_cost_weight=FLAGS.image_cost_weight) + + # Agent step placeholders and agent step. + batch_shape = (batch_size,) + observation_ph = tf.placeholder( + dtype=tf.uint8, shape=batch_shape + env.observation_shape, name='obs') + reward_ph = tf.placeholder( + dtype=tf.float32, shape=batch_shape, name='reward') + state_ph = nest.map_structure( + lambda s: tf.placeholder(dtype=s.dtype, shape=s.shape, name='state'), + agent.initial_state(batch_size=batch_size)) + step_outputs, state = agent.step(reward_ph, observation_ph, state_ph) + + # Update op placeholders and update op. + observations_ph = tf.placeholder( + dtype=tf.uint8, shape=(ep_length + 1, batch_size) + env.observation_shape, + name='observations') + rewards_ph = tf.placeholder( + dtype=tf.float32, shape=(ep_length + 1, batch_size), name='rewards') + actions_ph = tf.placeholder( + dtype=tf.int64, shape=(ep_length, batch_size), name='actions') + tvt_rewards_ph = tf.placeholder( + dtype=tf.float32, shape=(ep_length, batch_size), name='tvt_rewards') + + loss, loss_logs = agent.loss( + observations_ph, rewards_ph, actions_ph, tvt_rewards_ph) + + optimizer = tf.train.AdamOptimizer( + learning_rate=FLAGS.learning_rate, + beta1=FLAGS.beta1, + beta2=FLAGS.beta2, + epsilon=FLAGS.epsilon) + update_op = optimizer.minimize(loss) + initial_state = agent.initial_state(batch_size) + + if FLAGS.logdir: + if not tf.io.gfile.exists(FLAGS.logdir): + tf.io.gfile.makedirs(FLAGS.logdir) + summary_writer = tf.summary.FileWriter(FLAGS.logdir) + + # Do init + init_ops = (tf.global_variables_initializer(), + tf.local_variables_initializer()) + tf.get_default_graph().finalize() + + sess = tf.Session() + sess.run(init_ops) + + run = True + ep_num = 0 + prev_logging_time = time.time() + while run: + observation, reward = env.reset() + agent_state = sess.run(initial_state) + + # Initialise episode data stores. + observations = [observation] + rewards = [reward] + actions = [] + baselines = [] + read_infos = [] + + for _ in range(ep_length): + step_feed = {reward_ph: reward, observation_ph: observation} + for ph, ar in zip(nest.flatten(state_ph), nest.flatten(agent_state)): + step_feed[ph] = ar + step_output, agent_state = sess.run( + (step_outputs, state), feed_dict=step_feed) + action = step_output.action + baseline = step_output.baseline + read_info = step_output.read_info + + # Take step in environment, append results. + observation, reward = env.step(action) + + observations.append(observation) + rewards.append(reward) + actions.append(action) + baselines.append(baseline) + if read_info is not None: + read_infos.append(read_info) + + # Stack the lists of length ep_length so that each array (or each element + # of nest stucture for read_infos) has shape (ep_length, batch_size, ...). + observations = np.stack(observations) + rewards = np.array(rewards) + actions = np.array(actions) + baselines = np.array(baselines) + read_infos = nest_utils.nest_stack(read_infos) + + # Compute TVT rewards. + if FLAGS.do_tvt: + tvt_rewards = tvt_module.compute_tvt_rewards(read_infos, + baselines, + gamma=FLAGS.gamma) + else: + tvt_rewards = np.squeeze(np.zeros_like(baselines)) + + # Run update op. + loss_feed = {observations_ph: observations, + rewards_ph: rewards, + actions_ph: actions, + tvt_rewards_ph: tvt_rewards} + ep_loss, _, ep_loss_logs = sess.run([loss, update_op, loss_logs], + feed_dict=loss_feed) + + # Log episode results. + if ep_num % FLAGS.logging_frequency == 0: + steps_per_second = ( + FLAGS.logging_frequency * ep_length * batch_size / ( + time.time() - prev_logging_time)) + mean_reward = np.mean(np.sum(rewards, axis=0)) + mean_last_phase_reward = np.mean(env.last_phase_rewards()) + mean_tvt_reward = np.mean(np.sum(tvt_rewards, axis=0)) + + logging.info('Episode %d. SPS: %s', ep_num, steps_per_second) + logging.info('Episode %d. Mean episode reward: %f', ep_num, mean_reward) + logging.info('Episode %d. Last phase reward: %f', ep_num, + mean_last_phase_reward) + logging.info('Episode %d. Mean TVT episode reward: %f', ep_num, + mean_tvt_reward) + logging.info('Episode %d. Loss: %s', ep_num, ep_loss) + logging.info('Episode %d. Loss logs: %s', ep_num, ep_loss_logs) + + if FLAGS.logdir: + summary = tf.Summary() + summary.value.add(tag='reward', simple_value=mean_reward) + summary.value.add(tag='last phase reward', + simple_value=mean_last_phase_reward) + summary.value.add(tag='tvt reward', simple_value=mean_tvt_reward) + summary.value.add(tag='total loss', simple_value=ep_loss) + for k, v in ep_loss_logs.items(): + summary.value.add(tag='loss - {}'.format(k), simple_value=v) + # Tensorboard x-axis is total number of episodes run. + summary_writer.add_summary(summary, ep_num * batch_size) + summary_writer.flush() + + prev_logging_time = time.time() + + ep_num += 1 + if FLAGS.num_episodes and ep_num >= FLAGS.num_episodes: + run = False + + +if __name__ == '__main__': + app.run(main) diff --git a/tvt/memory.py b/tvt/memory.py new file mode 100644 index 0000000..56bc80a --- /dev/null +++ b/tvt/memory.py @@ -0,0 +1,294 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Memory Reader/Writer for RMA.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import sonnet as snt +import tensorflow as tf + +ReadInformation = collections.namedtuple( + 'ReadInformation', ('weights', 'indices', 'keys', 'strengths')) + + +class MemoryWriter(snt.RNNCore): + """Memory Writer Module.""" + + def __init__(self, mem_shape, name='memory_writer'): + """Initializes the `MemoryWriter`. + + Args: + mem_shape: The shape of the memory `(num_rows, memory_width)`. + name: The name to use for the Sonnet module. + """ + super(MemoryWriter, self).__init__(name=name) + self._mem_shape = mem_shape + + def _build(self, inputs, state): + """Inserts z into the argmin row of usage markers and updates all rows. + + Returns an operation that, when executed, correctly updates the internal + state and usage markers. + + Args: + inputs: A tuple consisting of: + * z, the value to write at this timestep + * mem_state, the state of the memory at this timestep before writing + state: The state is just the write_counter. + + Returns: + A tuple of the new memory state and a tuple containing the next state. + """ + z, mem_state = inputs + + # Stop gradient on writes to memory. + z = tf.stop_gradient(z) + + prev_write_counter = state + new_row_value = z + + # Find the index to insert the next row into. + num_mem_rows = self._mem_shape[0] + write_index = tf.cast(prev_write_counter, dtype=tf.int32) % num_mem_rows + one_hot_row = tf.one_hot(write_index, num_mem_rows) + write_counter = prev_write_counter + 1 + + # Insert state variable to new row. + # First you need to size it up to the full size. + insert_new_row = lambda mem, o_hot, z: mem - (o_hot * mem) + (o_hot * z) + new_mem = insert_new_row(mem_state, + tf.expand_dims(one_hot_row, axis=-1), + tf.expand_dims(new_row_value, axis=-2)) + + new_state = write_counter + + return new_mem, new_state + + @property + def state_size(self): + """Returns a description of the state size, without batch dimension.""" + return tf.TensorShape([]) + + @property + def output_size(self): + """Returns a description of the output size, without batch dimension.""" + return self._mem_shape + + +class MemoryReader(snt.AbstractModule): + """Memory Reader Module.""" + + def __init__(self, + memory_word_size, + num_read_heads, + top_k=0, + memory_size=None, + name='memory_reader'): + """Initializes the `MemoryReader`. + + Args: + memory_word_size: The dimension of the 1-D read keys this memory reader + should produce. Each row of the memory is of length `memory_word_size`. + num_read_heads: The number of reads to perform. + top_k: Softmax and summation when reading is only over top k most similar + entries in memory. top_k=0 (default) means dense reads, i.e. no top_k. + memory_size: Number of rows in memory. + name: The name for this Sonnet module. + """ + super(MemoryReader, self).__init__(name=name) + self._memory_word_size = memory_word_size + self._num_read_heads = num_read_heads + self._top_k = top_k + + # This is not an RNNCore but it is useful to expose the output size. + self._output_size = num_read_heads * memory_word_size + + num_read_weights = top_k if top_k > 0 else memory_size + self._read_info_size = ReadInformation( + weights=tf.TensorShape([num_read_heads, num_read_weights]), + indices=tf.TensorShape([num_read_heads, num_read_weights]), + keys=tf.TensorShape([num_read_heads, memory_word_size]), + strengths=tf.TensorShape([num_read_heads]), + ) + + with self._enter_variable_scope(): + # Transforms to value-based read for each read head. + output_dim = (memory_word_size + 1) * num_read_heads + self._keys_and_read_strengths_generator = snt.Linear(output_dim) + + def _build(self, inputs): + """Looks up rows in memory. + + In the args list, we have the following conventions: + B: batch size + M: number of slots in a row of the memory matrix + R: number of rows in the memory matrix + H: number of read heads in the memory controller + + Args: + inputs: A tuple of + * read_inputs, a tensor of shape [B, ...] that will be flattened and + passed through a linear layer to get read keys/read_strengths for + each head. + * mem_state, the primary memory tensor. Of shape [B, R, M]. + + Returns: + The read from the memory (concatenated across read heads) and read + information. + """ + # Assert input shapes are compatible and separate inputs. + _assert_compatible_memory_reader_input(inputs) + read_inputs, mem_state = inputs + + # Determine the read weightings for each key. + flat_outputs = self._keys_and_read_strengths_generator( + snt.BatchFlatten()(read_inputs)) + + # Separate the read_strengths from the rest of the weightings. + h = self._num_read_heads + flat_keys = flat_outputs[:, :-h] + read_strengths = tf.nn.softplus(flat_outputs[:, -h:]) + + # Reshape the weights. + read_shape = (self._num_read_heads, self._memory_word_size) + read_keys = snt.BatchReshape(read_shape)(flat_keys) + + # Read from memory. + memory_reads, read_weights, read_indices, read_strengths = ( + read_from_memory(read_keys, read_strengths, mem_state, self._top_k)) + concatenated_reads = snt.BatchFlatten()(memory_reads) + + return concatenated_reads, ReadInformation( + weights=read_weights, + indices=read_indices, + keys=read_keys, + strengths=read_strengths) + + @property + def output_size(self): + """Returns a description of the output size, without batch dimension.""" + return self._output_size, self._read_info_size + + +def read_from_memory(read_keys, read_strengths, mem_state, top_k): + """Function for cosine similarity content based reading from memory matrix. + + In the args list, we have the following conventions: + B: batch size + M: number of slots in a row of the memory matrix + R: number of rows in the memory matrix + H: number of read heads (of the controller or the policy) + K: top_k if top_k>0 + + Args: + read_keys: the read keys of shape [B, H, M]. + read_strengths: the coefficients used to compute the normalised weighting + vector of shape [B, H]. + mem_state: the primary memory tensor. Of shape [B, R, M]. + top_k: only use top k read matches, other reads do not go into softmax and + are zeroed out in the output. top_k=0 (default) means use dense reads. + + Returns: + The memory reads [B, H, M], read weights [B, H, top k], read indices + [B, H, top k], and read strengths [B, H, 1]. + """ + _assert_compatible_read_from_memory_inputs(read_keys, read_strengths, + mem_state) + batch_size = read_keys.shape[0] + num_read_heads = read_keys.shape[1] + + with tf.name_scope('memory_reading'): + # Scale such that all rows are L2-unit vectors, for memory and read query. + scaled_read_keys = tf.math.l2_normalize(read_keys, axis=-1) # [B, H, M] + scaled_mem = tf.math.l2_normalize(mem_state, axis=-1) # [B, R, M] + + # The cosine distance is then their dot product. + # Find the cosine distance between each read head and each row of memory. + cosine_distances = tf.matmul( + scaled_read_keys, scaled_mem, transpose_b=True) # [B, H, R] + + # The rank must match cosine_distances for broadcasting to work. + read_strengths = tf.expand_dims(read_strengths, axis=-1) # [B, H, 1] + weighted_distances = read_strengths * cosine_distances # [B, H, R] + + if top_k: + # Get top k indices (row indices with top k largest weighted distances). + top_k_output = tf.nn.top_k(weighted_distances, top_k, sorted=False) + read_indices = top_k_output.indices # [B, H, K] + + # Create a sub-memory for each read head with only the top k rows. + # Each batch_gather is [B, K, M] and the list stacks to [B, H, K, M]. + topk_mem_per_head = [tf.batch_gather(mem_state, ri_this_head) + for ri_this_head in tf.unstack(read_indices, axis=1)] + topk_mem = tf.stack(topk_mem_per_head, axis=1) # [B, H, K, M] + topk_scaled_mem = tf.math.l2_normalize(topk_mem, axis=-1) # [B, H, K, M] + + # Calculate read weights for each head's top k sub-memory. + expanded_scaled_read_keys = tf.expand_dims( + scaled_read_keys, axis=2) # [B, H, 1, M] + topk_cosine_distances = tf.reduce_sum( + expanded_scaled_read_keys * topk_scaled_mem, axis=-1) # [B, H, K] + topk_weighted_distances = ( + read_strengths * topk_cosine_distances) # [B, H, K] + read_weights = tf.nn.softmax( + topk_weighted_distances, axis=-1) # [B, H, K] + + # For each head, read using the sub-memories and corresponding weights. + expanded_weights = tf.expand_dims(read_weights, axis=-1) # [B, H, K, 1] + memory_reads = tf.reduce_sum( + expanded_weights * topk_mem, axis=2) # [B, H, M] + else: + read_weights = tf.nn.softmax(weighted_distances, axis=-1) + + num_rows_memory = mem_state.shape[1] + all_indices = tf.range(num_rows_memory, dtype=tf.int32) + all_indices = tf.reshape(all_indices, [1, 1, num_rows_memory]) + read_indices = tf.tile(all_indices, [batch_size, num_read_heads, 1]) + + # This is the actual memory access. + # Note that matmul automatically batch applies for us. + memory_reads = tf.matmul(read_weights, mem_state) + + read_keys.shape.assert_is_compatible_with(memory_reads.shape) + + read_strengths = tf.squeeze(read_strengths, axis=-1) # [B, H, 1] -> [B, H] + + return memory_reads, read_weights, read_indices, read_strengths + + +def _assert_compatible_read_from_memory_inputs(read_keys, read_strengths, + mem_state): + read_keys.shape.assert_has_rank(3) + b_shape, h_shape, m_shape = read_keys.shape + mem_state.shape.assert_has_rank(3) + r_shape = mem_state.shape[1] + + read_strengths.shape.assert_is_compatible_with( + tf.TensorShape([b_shape, h_shape])) + mem_state.shape.assert_is_compatible_with( + tf.TensorShape([b_shape, r_shape, m_shape])) + + +def _assert_compatible_memory_reader_input(input_tensors): + """Asserts MemoryReader's _build has been given the correct shapes.""" + assert len(input_tensors) == 2 + _, mem_state = input_tensors + mem_state.shape.assert_has_rank(3) diff --git a/tvt/nest_utils.py b/tvt/nest_utils.py new file mode 100644 index 0000000..d95a116 --- /dev/null +++ b/tvt/nest_utils.py @@ -0,0 +1,85 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""nest utils.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from six.moves import range +from tensorflow.contrib import framework as contrib_framework + +nest = contrib_framework.nest + + +def _nest_apply_over_list(list_of_nests, fn): + """Equivalent to fn, but works on list-of-nests. + + Transforms a list-of-nests to a nest-of-lists, then applies `fn` + to each of the inner lists. + + It is assumed that all nests have the same structure. Elements of the nest may + be None, in which case they are ignored, i.e. they do not form part of the + stack. This is useful when stacking agent states where parts of the state nest + have been filtered. + + Args: + list_of_nests: A Python list of nests. + fn: the function applied on the list of leaves. + + Returns: + A nest-of-arrays, where the arrays are formed by `fn`ing a list. + """ + list_of_flat_nests = [nest.flatten(n) for n in list_of_nests] + flat_nest_of_stacks = [] + for position in range(len(list_of_flat_nests[0])): + new_list = [flat_nest[position] for flat_nest in list_of_flat_nests] + new_list = [x for x in new_list if x is not None] + flat_nest_of_stacks.append(fn(new_list)) + return nest.pack_sequence_as( + structure=list_of_nests[0], flat_sequence=flat_nest_of_stacks) + + +def _take_indices(inputs, indices): + return nest.map_structure(lambda t: np.take(t, indices, axis=0), inputs) + + +def nest_stack(list_of_nests, axis=0): + """Equivalent to np.stack, but works on list-of-nests. + + Transforms a list-of-nests to a nest-of-lists, then applies `np.stack` + to each of the inner lists. + + It is assumed that all nests have the same structure. Elements of the nest may + be None, in which case they are ignored, i.e. they do not form part of the + stack. This is useful when stacking agent states where parts of the state nest + have been filtered. + + Args: + list_of_nests: A Python list of nests. + axis: Optional, the `axis` argument for `np.stack`. + + Returns: + A nest-of-arrays, where the arrays are formed by `np.stack`ing a list. + """ + return _nest_apply_over_list(list_of_nests, lambda l: np.stack(l, axis=axis)) + + +def nest_unstack(batched_inputs, batch_size): + """Splits a sequence of numpy arrays along 0th dimension.""" + return [_take_indices(batched_inputs, idx) for idx in range(batch_size)] diff --git a/tvt/pycolab/README.md b/tvt/pycolab/README.md new file mode 100644 index 0000000..eb6bd31 --- /dev/null +++ b/tvt/pycolab/README.md @@ -0,0 +1,31 @@ +# Pycolab Tasks + +## Playing the Pycolab Tasks + +We provide a script to allow human play of the Pycolab tasks. To play, run e.g. + +`python3 pycolab/human_player.py -- --game=key_to_door` + +## The Pycolab Tasks + +There are 2 [Pycolab](https://github.com/deepmind/pycolab) tasks presented here. +Each level is composed of 3 distinct phases. The first phase is the 'explore' +phase, where the agent should learn a piece of information or do something. For +both tasks, the 2nd phase is the 'distractor' phase, where the agent collects +apples for rewards. The 3rd phase is the 'exploit' phase, where the agent gets +rewards based on the knowledge acquired or actions performed in phase 1. + +Special thanks to Hamza Merzic for writing these task scripts. + +### Active Visual Match + +* Phase 1: A colour square randomly placed in a two-connected room. +* Phase 2: Apples collection. +* Phase 3: Choose the colour square matched that in Phase 1 among 4 options. + +### Key To Door + +* Phase 1: A key randomly placed in a two-connected room. +* Phase 2: Apples collection. +* Phase 3: A small room with a door. If agent has key, it can open the door to + get to the goal behind the door to get reward. diff --git a/tvt/pycolab/active_visual_match.py b/tvt/pycolab/active_visual_match.py new file mode 100644 index 0000000..bfe4d7f --- /dev/null +++ b/tvt/pycolab/active_visual_match.py @@ -0,0 +1,162 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Active visual match task. + +The game is split up into three phases: +1. (exploration phase) player is in one room and there's a colour in the other, +2. (distractor phase) player is collecting apples, +3. (reward phase) player sees three doors of different colours and has to select + the one of the same color as the colour in the first phase. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from pycolab import ascii_art +from pycolab import storytelling + +from tvt.pycolab import common +from tvt.pycolab import game +from tvt.pycolab import objects + + +SYMBOLS_TO_SHUFFLE = ['b', 'c', 'e'] + +EXPLORE_GRID = [ + ' ppppppp ', + ' p p ', + ' p p ', + ' pp pp ', + ' p+++++p ', + ' p+++++p ', + ' ppppppp ' +] + +REWARD_GRID = [ + '###########', + '# b c e #', + '# #', + '# #', + '#### ####', + ' # + # ', + ' ##### ' +] + + +class Game(game.AbstractGame): + """Image Match Passive Game.""" + + def __init__(self, + rng, + num_apples=10, + apple_reward=(1, 10), + fix_apple_reward_in_episode=True, + final_reward=10., + max_frames=common.DEFAULT_MAX_FRAMES_PER_PHASE): + self._rng = rng + self._num_apples = num_apples + self._apple_reward = apple_reward + self._fix_apple_reward_in_episode = fix_apple_reward_in_episode + self._final_reward = final_reward + self._max_frames = max_frames + self._episode_length = sum(self._max_frames.values()) + self._num_actions = common.NUM_ACTIONS + self._colours = common.FIXED_COLOURS.copy() + self._colours.update( + common.get_shuffled_symbol_colour_map(rng, SYMBOLS_TO_SHUFFLE)) + + self._extra_observation_fields = ['chapter_reward_as_string'] + + @property + def extra_observation_fields(self): + """The field names of extra observations.""" + return self._extra_observation_fields + + @property + def num_actions(self): + """Number of possible actions in the game.""" + return self._num_actions + + @property + def episode_length(self): + return self._episode_length + + @property + def colours(self): + """Symbol to colour map for key to door.""" + return self._colours + + def _make_explore_phase(self, target_char): + # Keep only one coloured position and one player position. + grid = common.keep_n_characters_in_grid(EXPLORE_GRID, 'p', 1, common.BORDER) + grid = common.keep_n_characters_in_grid(grid, 'p', 0, target_char) + grid = common.keep_n_characters_in_grid(grid, common.PLAYER, 1) + + return ascii_art.ascii_art_to_game( + grid, + what_lies_beneath=' ', + sprites={ + common.PLAYER: + ascii_art.Partial( + common.PlayerSprite, + impassable=common.BORDER + target_char), + target_char: + objects.ObjectSprite, + common.TIMER: + ascii_art.Partial(common.TimerSprite, + self._max_frames['explore']), + }, + update_schedule=[common.PLAYER, target_char, common.TIMER], + z_order=[target_char, common.PLAYER, common.TIMER], + ) + + def _make_distractor_phase(self): + return common.distractor_phase( + player_sprite=common.PlayerSprite, + num_apples=self._num_apples, + max_frames=self._max_frames['distractor'], + apple_reward=self._apple_reward, + fix_apple_reward_in_episode=self._fix_apple_reward_in_episode) + + def _make_reward_phase(self, target_char): + return ascii_art.ascii_art_to_game( + REWARD_GRID, + what_lies_beneath=' ', + sprites={ + common.PLAYER: common.PlayerSprite, + 'b': objects.ObjectSprite, + 'c': objects.ObjectSprite, + 'e': objects.ObjectSprite, + common.TIMER: ascii_art.Partial(common.TimerSprite, + self._max_frames['reward'], + track_chapter_reward=True), + target_char: ascii_art.Partial(objects.ObjectSprite, + reward=self._final_reward), + }, + update_schedule=[common.PLAYER, 'b', 'c', 'e', common.TIMER], + z_order=[common.PLAYER, 'b', 'c', 'e', common.TIMER], + ) + + def make_episode(self): + """Factory method for generating new episodes of the game.""" + target_char = self._rng.choice(SYMBOLS_TO_SHUFFLE) + return storytelling.Story([ + lambda: self._make_explore_phase(target_char), + self._make_distractor_phase, + lambda: self._make_reward_phase(target_char), + ], croppers=common.get_cropper()) diff --git a/tvt/pycolab/common.py b/tvt/pycolab/common.py new file mode 100644 index 0000000..1cca6a2 --- /dev/null +++ b/tvt/pycolab/common.py @@ -0,0 +1,325 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Common utilities for Pycolab games.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import colorsys +import numpy as np +from pycolab import ascii_art +from pycolab import cropping +from pycolab import things as plab_things +from pycolab.prefab_parts import sprites as prefab_sprites +from six.moves import zip +from tensorflow.contrib import framework as contrib_framework + +nest = contrib_framework.nest + +# Actions. +# Those with a negative ID are not allowed for the agent. +ACTION_QUIT = -2 +ACTION_DELAY = -1 +ACTION_NORTH = 0 +ACTION_SOUTH = 1 +ACTION_WEST = 2 +ACTION_EAST = 3 + +NUM_ACTIONS = 4 +DEFAULT_MAX_FRAMES_PER_PHASE = { + 'explore': 15, + 'distractor': 90, + 'reward': 15 +} + +# Reserved symbols. +PLAYER = '+' +BORDER = '#' +BACKGROUND = ' ' +KEY = 'k' +DOOR = 'd' +APPLE = 'a' +TIMER = 't' +INDICATOR = 'i' + +FIXED_COLOURS = { + PLAYER: (898, 584, 430), + BORDER: (100, 100, 100), + BACKGROUND: (800, 800, 800), + KEY: (627, 321, 176), + DOOR: (529, 808, 922), + APPLE: (550, 700, 0), +} + +APPLE_DISTRACTOR_GRID = [ + '###########', + '#a a a a a#', + '# a a a a #', + '#a a a a a#', + '# a a a a #', + '#a a + a a#', + '###########' +] +DEFAULT_APPLE_RESPAWN_TIME = 20 +DEFAULT_APPLE_REWARD = 1. + + +def get_shuffled_symbol_colour_map(rng_or_seed, symbols, + num_potential_colours=None): + """Get a randomized mapping between symbols and colours. + + Args: + rng_or_seed: A random state or random seed. + symbols: List of symbols. + num_potential_colours: Number of equally spaced colours to choose from. + Defaults to number of symbols. Colours are generated deterministically. + + Returns: + Randomized mapping between symbols and colours. + """ + num_symbols = len(symbols) + num_potential_colours = num_potential_colours or num_symbols + if isinstance(rng_or_seed, np.random.RandomState): + rng = rng_or_seed + else: + rng = np.random.RandomState(rng_or_seed) + + # Generate a range of colours. + step = 1. / num_potential_colours + hues = np.arange(0, num_potential_colours) * step + potential_colours = [colorsys.hsv_to_rgb(h, 1.0, 1.0) for h in hues] + + # Randomly draw num_symbols colours without replacement. + rng.shuffle(potential_colours) + colours = potential_colours[:num_symbols] + + symbol_to_colour_map = dict(list(zip(symbols, colours))) + + # Multiply each colour value by 1000. + return nest.map_structure(lambda c: int(c * 1000), symbol_to_colour_map) + + +def get_cropper(): + return cropping.ScrollingCropper( + rows=5, + cols=5, + to_track=PLAYER, + pad_char=BACKGROUND, + scroll_margins=(2, 2)) + + +def distractor_phase(player_sprite, num_apples, max_frames, + apple_reward=DEFAULT_APPLE_REWARD, + fix_apple_reward_in_episode=False, + respawn_every=DEFAULT_APPLE_RESPAWN_TIME): + """Distractor phase engine factory. + + Args: + player_sprite: Player sprite class. + num_apples: Number of apples to sample from the apple distractor grid. + max_frames: Maximum duration of the distractor phase in frames. + apple_reward: Can either be a scalar specifying the reward or a reward range + [min, max), given as a list or tuple, to uniformly sample from. + fix_apple_reward_in_episode: The apple reward is constant throughout each + episode. + respawn_every: respawn frequency of apples. + + Returns: + Distractor phase engine. + """ + distractor_grid = keep_n_characters_in_grid(APPLE_DISTRACTOR_GRID, APPLE, + num_apples) + + engine = ascii_art.ascii_art_to_game( + distractor_grid, + what_lies_beneath=BACKGROUND, + sprites={ + PLAYER: player_sprite, + TIMER: ascii_art.Partial(TimerSprite, max_frames), + }, + drapes={ + APPLE: ascii_art.Partial( + AppleDrape, + reward=apple_reward, + fix_apple_reward_in_episode=fix_apple_reward_in_episode, + respawn_every=respawn_every) + }, + update_schedule=[PLAYER, APPLE, TIMER], + z_order=[APPLE, PLAYER, TIMER], + ) + + return engine + + +def replace_grid_symbols(grid, old_to_new_map): + """Replaces symbols in the grid. + + If mapping is not defined the symbol is not updated. + + Args: + grid: Represented as a list of strings. + old_to_new_map: Mapping between symbols. + + Returns: + Updated grid. + """ + def symbol_map(x): + if x in old_to_new_map: + return old_to_new_map[x] + return x + new_grid = [] + for row in grid: + new_grid.append(''.join(symbol_map(i) for i in row)) + return new_grid + + +def keep_n_characters_in_grid(grid, character, n, backdrop_char=BACKGROUND): + """Keeps only a sample of characters `character` in the grid.""" + np_grid = np.array([list(i) for i in grid]) + char_positions = np.argwhere(np_grid == character) + + # Randomly select parts to remove. + num_empty_positions = char_positions.shape[0] - n + if num_empty_positions < 0: + raise ValueError('Not enough characters `{}` in grid.'.format(character)) + empty_pos = np.random.permutation(char_positions)[:num_empty_positions] + + # Remove characters. + grid = [list(row) for row in grid] + for (i, j) in empty_pos: + grid[i][j] = backdrop_char + + return [''.join(row) for row in grid] + + +class PlayerSprite(prefab_sprites.MazeWalker): + """Sprite for the actor.""" + + def __init__(self, corner, position, character, impassable=BORDER): + super(PlayerSprite, self).__init__( + corner, position, character, impassable=impassable, + confined_to_board=True) + + def update(self, actions, board, layers, backdrop, things, the_plot): + + the_plot.add_reward(0.) + + if actions == ACTION_QUIT: + the_plot.next_chapter = None + the_plot.terminate_episode() + + if actions == ACTION_WEST: + self._west(board, the_plot) + elif actions == ACTION_EAST: + self._east(board, the_plot) + elif actions == ACTION_NORTH: + self._north(board, the_plot) + elif actions == ACTION_SOUTH: + self._south(board, the_plot) + + +class AppleDrape(plab_things.Drape): + """Drape for the apples used in the distractor phase.""" + + def __init__(self, + curtain, + character, + respawn_every, + reward, + fix_apple_reward_in_episode): + """Constructor. + + Args: + curtain: Array specifying locations of apples. Obtained from ascii grid. + character: Character representing the drape. + respawn_every: respawn frequency of apples. + reward: Can either be a scalar specifying the reward or a reward range + [min, max), given as a list or tuple, to uniformly sample from. + fix_apple_reward_in_episode: If set to True, then only sample the apple's + reward once in the episode and then fix the value. + """ + super(AppleDrape, self).__init__(curtain, character) + self._respawn_every = respawn_every + if not isinstance(reward, (list, tuple)): + # Assuming scalar. + self._reward = [reward, reward] + else: + if len(reward) != 2: + raise ValueError('Reward must be a scalar or a two element list/tuple.') + self._reward = reward + self._fix_apple_reward_in_episode = fix_apple_reward_in_episode + + # Grid specifying for each apple the last frame it was picked up. + # Initialized to inifinity for cells with apples and -1 for cells without. + self._last_pickup = np.where(curtain, + np.inf * np.ones_like(curtain), + -1. * np.ones_like(curtain)) + + def update(self, actions, board, layers, backdrop, things, the_plot): + player_position = things[PLAYER].position + # decide the apple_reward + if (self._fix_apple_reward_in_episode and + not the_plot.get('sampled_apple_reward', None)): + the_plot['sampled_apple_reward'] = np.random.choice((self._reward[0], + self._reward[1])) + + if self.curtain[player_position]: + self._last_pickup[player_position] = the_plot.frame + self.curtain[player_position] = False + if not self._fix_apple_reward_in_episode: + the_plot.add_reward(np.random.uniform(*self._reward)) + else: + the_plot.add_reward(the_plot['sampled_apple_reward']) + + if self._respawn_every: + respawn_cond = the_plot.frame > self._last_pickup + self._respawn_every + respawn_cond &= self._last_pickup >= 0 + self.curtain[respawn_cond] = True + + +class TimerSprite(plab_things.Sprite): + """Sprite for the timer. + + The timer is in charge of stopping the current chapter. Timer sprite should be + placed last in the update order to make sure everything is updated before the + chapter terminates. + """ + + def __init__(self, corner, position, character, max_frames, + track_chapter_reward=False): + super(TimerSprite, self).__init__(corner, position, character) + if not isinstance(max_frames, int): + raise ValueError('max_frames must be of type integer.') + self._max_frames = max_frames + self._visible = False + self._track_chapter_reward = track_chapter_reward + self._total_chapter_reward = 0. + + def update(self, actions, board, layers, backdrop, things, the_plot): + directives = the_plot._get_engine_directives() # pylint: disable=protected-access + + if self._track_chapter_reward: + self._total_chapter_reward += directives.summed_reward or 0. + + # Every chapter starts at frame = 0. + if the_plot.frame >= self._max_frames or directives.game_over: + # Calculate the reward obtained in this phase and send it through the + # extra observations channel. + if self._track_chapter_reward: + the_plot['chapter_reward'] = self._total_chapter_reward + the_plot.terminate_episode() diff --git a/tvt/pycolab/env.py b/tvt/pycolab/env.py new file mode 100644 index 0000000..9309b3b --- /dev/null +++ b/tvt/pycolab/env.py @@ -0,0 +1,105 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Pycolab env.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from pycolab import rendering + +from tvt.pycolab import active_visual_match +from tvt.pycolab import key_to_door +from tensorflow.contrib import framework as contrib_framework + +nest = contrib_framework.nest + + +class PycolabEnvironment(object): + """A simple environment adapter for pycolab games.""" + + def __init__(self, game, + num_apples=10, + apple_reward=1., + fix_apple_reward_in_episode=False, + final_reward=10., + crop=True, + default_reward=0): + """Construct a `environment.Base` adapter that wraps a pycolab game.""" + rng = np.random.RandomState() + if game == 'key_to_door': + self._game = key_to_door.Game(rng, + num_apples, + apple_reward, + fix_apple_reward_in_episode, + final_reward, + crop) + elif game == 'active_visual_match': + self._game = active_visual_match.Game(rng, + num_apples, + apple_reward, + fix_apple_reward_in_episode, + final_reward) + else: + raise ValueError('Unsupported game "%s".' % game) + self._default_reward = default_reward + + self._num_actions = self._game.num_actions + + # Agents expect HWC uint8 observations, Pycolab uses CHW float observations. + colours = nest.map_structure(lambda c: float(c) * 255 / 1000, + self._game.colours) + self._rgb_converter = rendering.ObservationToArray( + value_mapping=colours, permute=(1, 2, 0), dtype=np.uint8) + + episode = self._game.make_episode() + observation, _, _ = episode.its_showtime() + self._image_shape = self._rgb_converter(observation).shape + + def _process_outputs(self, observation, reward): + if reward is None: + reward = self._default_reward + image = self._rgb_converter(observation) + return image, reward + + def reset(self): + """Start a new episode.""" + self._episode = self._game.make_episode() + observation, reward, _ = self._episode.its_showtime() + return self._process_outputs(observation, reward) + + def step(self, action): + """Take step in episode.""" + observation, reward, _ = self._episode.play(action) + return self._process_outputs(observation, reward) + + @property + def num_actions(self): + return self._num_actions + + @property + def observation_shape(self): + return self._image_shape + + @property + def episode_length(self): + return self._game.episode_length + + def last_phase_reward(self): + # In Pycolab games here we only track chapter_reward for final chapter. + return float(self._episode.the_plot['chapter_reward']) diff --git a/tvt/pycolab/game.py b/tvt/pycolab/game.py new file mode 100644 index 0000000..f1617ae --- /dev/null +++ b/tvt/pycolab/game.py @@ -0,0 +1,44 @@ +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Pycolab Game interface.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class AbstractGame(object): + """Abstract base class for Pycolab games.""" + + @abc.abstractmethod + def __init__(self, rng, **settings): + """Initialize the game.""" + + @abc.abstractproperty + def num_actions(self): + """Number of possible actions in the game.""" + + @abc.abstractproperty + def colours(self): + """Symbol to colour map for the game.""" + + @abc.abstractmethod + def make_episode(self): + """Factory method for generating new episodes of the game.""" diff --git a/tvt/pycolab/human_player.py b/tvt/pycolab/human_player.py new file mode 100644 index 0000000..a8a147e --- /dev/null +++ b/tvt/pycolab/human_player.py @@ -0,0 +1,67 @@ +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Pycolab human player.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import curses + +from absl import app +from absl import flags +import numpy as np +from pycolab import human_ui + +from tvt.pycolab import active_visual_match +from tvt.pycolab import common +from tvt.pycolab import key_to_door + +FLAGS = flags.FLAGS + +flags.DEFINE_enum('game', 'key_to_door', + ['key_to_door', 'active_visual_match'], + 'The name of the game') + + +def main(unused_argv): + + rng = np.random.RandomState() + + if FLAGS.game == 'key_to_door': + game = key_to_door.Game(rng) + elif FLAGS.game == 'active_visual_match': + game = active_visual_match.Game(rng) + else: + raise ValueError('Unsupported game "%s".' % FLAGS.game) + episode = game.make_episode() + + ui = human_ui.CursesUi( + keys_to_actions={ + curses.KEY_UP: common.ACTION_NORTH, + curses.KEY_DOWN: common.ACTION_SOUTH, + curses.KEY_LEFT: common.ACTION_WEST, + curses.KEY_RIGHT: common.ACTION_EAST, + -1: common.ACTION_DELAY, + 'q': common.ACTION_QUIT, + 'Q': common.ACTION_QUIT}, + delay=-1, + colour_fg=game.colours + ) + ui.play(episode) + +if __name__ == '__main__': + app.run(main) diff --git a/tvt/pycolab/key_to_door.py b/tvt/pycolab/key_to_door.py new file mode 100644 index 0000000..dc5cb33 --- /dev/null +++ b/tvt/pycolab/key_to_door.py @@ -0,0 +1,214 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Key to door task. + +The game is split up into three phases: +1. (exploration phase) player can collect a key, +2. (distractor phase) player is collecting apples, +3. (reward phase) player can open the door and get the reward if the key is + previously collected. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from pycolab import ascii_art +from pycolab import storytelling +from pycolab import things as plab_things + +from tvt.pycolab import common +from tvt.pycolab import game +from tvt.pycolab import objects + + +COLOURS = { + 'i': (1000, 1000, 1000), # Indicator. +} + +EXPLORE_GRID = [ + ' ####### ', + ' #kkkkk# ', + ' #kkkkk# ', + ' ## ## ', + ' #+++++# ', + ' #+++++# ', + ' ####### ' +] + +REWARD_GRID = [ + ' ', + ' ##d## ', + ' # # ', + ' # + # ', + ' # # ', + ' ##### ', + ' ', +] + + +class KeySprite(plab_things.Sprite): + """Sprite for the key.""" + + def update(self, actions, board, layers, backdrop, things, the_plot): + player_position = things[common.PLAYER].position + pick_up = self.position == player_position + + if self.visible and pick_up: + # Pass information to all phases. + the_plot['has_key'] = True + self._visible = False + + +class DoorSprite(plab_things.Sprite): + """Sprite for the door.""" + + def __init__(self, corner, position, character, pickup_reward): + super(DoorSprite, self).__init__(corner, position, character) + self._pickup_reward = pickup_reward + + def update(self, actions, board, layers, backdrop, things, the_plot): + player_position = things[common.PLAYER].position + pick_up = self.position == player_position + + if pick_up and the_plot.get('has_key'): + the_plot.add_reward(self._pickup_reward) + # The key is lost after the first time opening the door + # to ensure only one reward per episode. + the_plot['has_key'] = False + + +class PlayerSprite(common.PlayerSprite): + """Sprite for the actor.""" + + def __init__(self, corner, position, character): + super(PlayerSprite, self).__init__( + corner, position, character, + impassable=common.BORDER + common.INDICATOR + common.DOOR) + + def update(self, actions, board, layers, backdrop, things, the_plot): + + # Allow moving through the door if key is previously collected. + if common.DOOR in self.impassable and the_plot.get('has_key'): + self._impassable.remove(common.DOOR) + + super(PlayerSprite, self).update(actions, board, layers, backdrop, things, + the_plot) + + +class Game(game.AbstractGame): + """Key To Door Game.""" + + def __init__(self, + rng, + num_apples=10, + apple_reward=(1, 10), + fix_apple_reward_in_episode=True, + final_reward=10., + crop=True, + max_frames=common.DEFAULT_MAX_FRAMES_PER_PHASE): + del rng # Each episode is identical and colours are not randomised. + self._num_apples = num_apples + self._apple_reward = apple_reward + self._fix_apple_reward_in_episode = fix_apple_reward_in_episode + self._final_reward = final_reward + self._crop = crop + self._max_frames = max_frames + self._episode_length = sum(self._max_frames.values()) + self._num_actions = common.NUM_ACTIONS + self._colours = common.FIXED_COLOURS.copy() + self._colours.update(COLOURS) + self._extra_observation_fields = ['chapter_reward_as_string'] + + @property + def extra_observation_fields(self): + """The field names of extra observations.""" + return self._extra_observation_fields + + @property + def num_actions(self): + """Number of possible actions in the game.""" + return self._num_actions + + @property + def episode_length(self): + return self._episode_length + + @property + def colours(self): + """Symbol to colour map for key to door.""" + return self._colours + + def _make_explore_phase(self): + # Keep only one key and one player position. + explore_grid = common.keep_n_characters_in_grid( + EXPLORE_GRID, common.KEY, 1) + explore_grid = common.keep_n_characters_in_grid( + explore_grid, common.PLAYER, 1) + return ascii_art.ascii_art_to_game( + art=explore_grid, + what_lies_beneath=' ', + sprites={ + common.PLAYER: PlayerSprite, + common.KEY: KeySprite, + common.INDICATOR: ascii_art.Partial(objects.IndicatorObjectSprite, + char_to_track=common.KEY, + override_position=(0, 5)), + common.TIMER: ascii_art.Partial(common.TimerSprite, + self._max_frames['explore']), + }, + update_schedule=[ + common.PLAYER, common.KEY, common.INDICATOR, common.TIMER], + z_order=[common.KEY, common.INDICATOR, common.PLAYER, common.TIMER], + ) + + def _make_distractor_phase(self): + return common.distractor_phase( + player_sprite=PlayerSprite, + num_apples=self._num_apples, + max_frames=self._max_frames['distractor'], + apple_reward=self._apple_reward, + fix_apple_reward_in_episode=self._fix_apple_reward_in_episode) + + def _make_reward_phase(self): + return ascii_art.ascii_art_to_game( + art=REWARD_GRID, + what_lies_beneath=' ', + sprites={ + common.PLAYER: PlayerSprite, + common.DOOR: ascii_art.Partial(DoorSprite, + pickup_reward=self._final_reward), + common.TIMER: ascii_art.Partial(common.TimerSprite, + self._max_frames['reward'], + track_chapter_reward=True), + }, + update_schedule=[common.PLAYER, common.DOOR, common.TIMER], + z_order=[common.PLAYER, common.DOOR, common.TIMER], + ) + + def make_episode(self): + """Factory method for generating new episodes of the game.""" + if self._crop: + croppers = common.get_cropper() + else: + croppers = None + + return storytelling.Story([ + self._make_explore_phase, + self._make_distractor_phase, + self._make_reward_phase, + ], croppers=croppers) diff --git a/tvt/pycolab/objects.py b/tvt/pycolab/objects.py new file mode 100644 index 0000000..0c261da --- /dev/null +++ b/tvt/pycolab/objects.py @@ -0,0 +1,123 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Pycolab sprites.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from pycolab import things as plab_things +from pycolab.prefab_parts import sprites as prefab_sprites +import six +from tvt.pycolab import common + + +class PlayerSprite(prefab_sprites.MazeWalker): + """Sprite representing the agent.""" + + def __init__(self, corner, position, character, + max_steps_per_act, moving_player): + + """Indicates to the superclass that we can't walk off the board.""" + super(PlayerSprite, self).__init__( + corner, position, character, impassable=[common.BORDER], + confined_to_board=True) + + self._moving_player = moving_player + self._max_steps_per_act = max_steps_per_act + self._num_steps = 0 + + def update(self, actions, board, layers, backdrop, things, the_plot): + del backdrop # Unused. + + if actions is not None: + assert actions in common.ACTIONS + + the_plot.log("Step {} | Action {}".format(self._num_steps, actions)) + the_plot.add_reward(0.0) + self._num_steps += 1 + + if actions == common.ACTION_QUIT: + the_plot.terminate_episode() + + if self._moving_player: + if actions == common.ACTION_WEST: + self._west(board, the_plot) + elif actions == common.ACTION_EAST: + self._east(board, the_plot) + elif actions == common.ACTION_NORTH: + self._north(board, the_plot) + elif actions == common.ACTION_SOUTH: + self._south(board, the_plot) + + if self._max_steps_per_act == self._num_steps: + the_plot.terminate_episode() + + +class ObjectSprite(plab_things.Sprite): + """Sprite for a generic object which can be collectable.""" + + def __init__(self, corner, position, character, reward=0., collectable=True, + terminate=True): + super(ObjectSprite, self).__init__(corner, position, character) + self._reward = reward # Reward on pickup. + self._collectable = collectable + + def set_visibility(self, visible): + self._visible = visible + + def update(self, actions, board, layers, backdrop, things, the_plot): + player_position = things[common.PLAYER].position + pick_up = self.position == player_position + + if pick_up and self.visible: + the_plot.add_reward(self._reward) + if self._collectable: + self.set_visibility(False) + # set all other objects to be invisible + for v in six.itervalues(things): + if isinstance(v, ObjectSprite): + v.set_visibility(False) + + +class IndicatorObjectSprite(plab_things.Sprite): + """Sprite for the indicator object. + + The indicator object is an object that spawns at a designated position once + the player picks up an object defined by the `char_to_track` argument. + The indicator object is spawned for just a single frame. + """ + + def __init__(self, corner, position, character, char_to_track, + override_position=None): + super(IndicatorObjectSprite, self).__init__(corner, position, character) + if override_position is not None: + self._position = override_position + self._char_to_track = char_to_track + self._visible = False + self._pickup_frame = None + + def update(self, actions, board, layers, backdrop, things, the_plot): + player_position = things[common.PLAYER].position + pick_up = things[self._char_to_track].position == player_position + + if self._pickup_frame: + self._visible = False + + if pick_up and not self._pickup_frame: + self._visible = True + self._pickup_frame = the_plot.frame diff --git a/tvt/requirements.txt b/tvt/requirements.txt new file mode 100644 index 0000000..62481c7 --- /dev/null +++ b/tvt/requirements.txt @@ -0,0 +1,8 @@ +absl-py +dm-sonnet==1.34 +numpy +pycolab +six +trfl +tensorflow==1.13.2 +tensorflow-probability==0.6.0 diff --git a/tvt/rma.py b/tvt/rma.py new file mode 100644 index 0000000..dccbd32 --- /dev/null +++ b/tvt/rma.py @@ -0,0 +1,584 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""RMA agent.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import numpy as np +from six.moves import range +from six.moves import zip +import sonnet as snt +import tensorflow as tf +import trfl + +from tvt import losses +from tvt import memory as memory_module +from tensorflow.contrib import framework as contrib_framework + +nest = contrib_framework.nest + +PolicyOutputs = collections.namedtuple( + 'PolicyOutputs', ['policy', 'action', 'baseline']) + +StepOutput = collections.namedtuple( + 'StepOutput', ['action', 'baseline', 'read_info']) + +AgentState = collections.namedtuple( + 'AgentState', ['core_state', 'prev_action']) + +Observation = collections.namedtuple( + 'Observation', ['image', 'last_action', 'last_reward']) + +RNNStateNoMem = collections.namedtuple( + 'RNNStateNoMem', ['controller_outputs', 'h_controller']) + +RNNState = collections.namedtuple( + 'RNNState', + list(RNNStateNoMem._fields) + ['memory', 'mem_reads', 'h_mem_writer']) + +CoreOutputs = collections.namedtuple( + 'CoreOutputs', ['action', 'policy', 'baseline', 'z', 'read_info']) + + +def rnn_inputs_to_static_rnn_inputs(inputs): + """Converts time major tensors to timestep lists.""" + # Inputs to core build method are expected to be a tensor or tuple of tensors. + if isinstance(inputs, tuple): + num_timesteps = inputs[0].shape.as_list()[0] + converted_inputs = [tf.unstack(input_, num_timesteps) for input_ in inputs] + return list(zip(*converted_inputs)) + else: + return tf.unstack(inputs) + + +def static_rnn_outputs_to_core_outputs(outputs): + """Convert from length T list of nests to nest of tensors with first dim T.""" + list_of_flats = [nest.flatten(n) for n in outputs] + new_outputs = list() + for i in range(len(list_of_flats[0])): + new_outputs.append(tf.stack([flat_nest[i] for flat_nest in list_of_flats])) + return nest.pack_sequence_as(structure=outputs[0], flat_sequence=new_outputs) + + +def unroll(core, initial_state, inputs, dtype=tf.float32): + """Perform a static unroll of the core.""" + static_rnn_inputs = rnn_inputs_to_static_rnn_inputs(inputs) + static_outputs, _ = tf.nn.static_rnn( + core, + inputs=static_rnn_inputs, + initial_state=initial_state, + dtype=dtype) + core_outputs = static_rnn_outputs_to_core_outputs(static_outputs) + return core_outputs + + +class ImageEncoderDecoder(snt.AbstractModule): + """Image Encoder/Decoder module.""" + + def __init__( + self, + image_code_size, + name='image_encoder_decoder'): + """Initialize the image encoder/decoder.""" + super(ImageEncoderDecoder, self).__init__(name=name) + + # This is set by a call to `encode`. `decode` will fail before this is set. + self._convnet_output_shape = None + + with self._enter_variable_scope(): + self._convnet = snt.nets.ConvNet2D( + output_channels=(16, 32), + kernel_shapes=(3, 3), + strides=(1, 1), + paddings=('SAME',)) + self._post_convnet_layer = snt.Linear(image_code_size, name='final_layer') + + @snt.reuse_variables + def encode(self, image): + """Encode the image observation.""" + convnet_output = self._convnet(image) + + # Store unflattened convnet output shape for use in decoder. + self._convnet_output_shape = convnet_output.shape[1:] + + # Flatten convnet outputs and pass through final layer to get image code. + return self._post_convnet_layer(snt.BatchFlatten()(convnet_output)) + + @snt.reuse_variables + def decode(self, code): + """Decode the image observation from a latent code.""" + if self._convnet_output_shape is None: + raise ValueError('Must call `encode` before `decode`.') + transpose_convnet_in_flat = snt.Linear( + self._convnet_output_shape.num_elements(), + name='decode_initial_linear')( + code) + transpose_convnet_in_flat = tf.nn.relu(transpose_convnet_in_flat) + transpose_convnet_in = snt.BatchReshape( + self._convnet_output_shape.as_list())(transpose_convnet_in_flat) + return self._convnet.transpose(None)(transpose_convnet_in) + + def _build(self, *args): # Unused. Use encode/decode instead. + raise NotImplementedError('Use encode/decode methods instead of __call__.') + + +class Policy(snt.AbstractModule): + """A policy module possibly containing a read-only DNC.""" + + def __init__(self, + num_actions, + num_policy_hiddens=(), + num_baseline_hiddens=(), + activation=tf.nn.tanh, + policy_clip_abs_value=10.0, + name='Policy'): + """Construct a policy module possibly containing a read-only DNC. + + Args: + num_actions: Number of discrete actions to choose from. + num_policy_hiddens: Tuple or List, sizes of policy MLP hidden layers. + num_baseline_hiddens: Tuple or List, sizes of baseline MLP hidden layers. + An empty tuple/list results in a linear layer instead of an MLP. + activation: Callable, e.g. tf.nn.tanh. + policy_clip_abs_value: float, Policy gradient clip value. + name: A string, the module's name + """ + super(Policy, self).__init__(name=name) + + self._num_actions = num_actions + self._policy_layers = tuple(num_policy_hiddens) + (num_actions,) + self._baseline_layers = tuple(num_baseline_hiddens) + (1,) + self._policy_clip_abs_value = policy_clip_abs_value + self._activation = activation + + def _build(self, inputs): + (shared_inputs, extra_policy_inputs) = inputs + policy_in = tf.concat([shared_inputs, extra_policy_inputs], axis=1) + + policy = snt.nets.MLP( + output_sizes=self._policy_layers, + activation=self._activation, + name='policy_mlp')( + policy_in) + + # Sample an action from the policy logits. + action = tf.multinomial(policy, num_samples=1, output_dtype=tf.int32) + action = tf.squeeze(action, 1) # [B, 1] -> [B] + + if self._policy_clip_abs_value > 0: + policy = snt.clip_gradient( + net=policy, + clip_value_min=-self._policy_clip_abs_value, + clip_value_max=self._policy_clip_abs_value) + + baseline_in = tf.concat([shared_inputs, tf.stop_gradient(policy)], axis=1) + baseline = snt.nets.MLP( + self._baseline_layers, + activation=self._activation, + name='baseline_mlp')( + baseline_in) + baseline = tf.squeeze(baseline, axis=-1) # [B, 1] -> [B] + + if self._policy_clip_abs_value > 0: + baseline = snt.clip_gradient( + net=baseline, + clip_value_min=-self._policy_clip_abs_value, + clip_value_max=self._policy_clip_abs_value) + + outputs = PolicyOutputs( + policy=policy, + action=action, + baseline=baseline) + + return outputs + + +class _RMACore(snt.RNNCore): + """RMA RNN Core.""" + + def __init__(self, + num_actions, + with_memory=True, + name='rma_core'): + super(_RMACore, self).__init__(name=name) + + # MLP activation as callable. + mlp_activation = tf.nn.tanh + + # Size of latent code written to memory (if using it) and used to + # reconstruct from (if including reconstructions). + num_latents = 200 + + # Value function decode settings. + baseline_mlp_num_hiddens = (200,) + + # Policy settings. + num_policy_hiddens = (200,) # Only used for non-recurrent core. + + # Controller settings. + control_hidden_size = 256 + control_num_layers = 2 + + # Memory settings (only used if with_memory=True). + memory_size = 1000 + memory_num_reads = 3 + memory_top_k = 50 + + self._with_memory = with_memory + + with self._enter_variable_scope(): + # Construct the features -> latent encoder. + self._z_encoder_mlp = snt.nets.MLP( + output_sizes=(2 * num_latents, num_latents), + activation=mlp_activation, + activate_final=False, + name='z_encoder_mlp') + + # Construct controller. + rnn_cores = [snt.LSTM(control_hidden_size) + for _ in range(control_num_layers)] + self._controller = snt.DeepRNN( + rnn_cores, skip_connections=True, name='controller') + + # Construct memory. + if self._with_memory: + memory_dim = num_latents # Each write to memory is of size memory_dim. + self._mem_shape = (memory_size, memory_dim) + self._memory_reader = memory_module.MemoryReader( + memory_word_size=memory_dim, + num_read_heads=memory_num_reads, + top_k=memory_top_k, + memory_size=memory_size) + self._memory_writer = memory_module.MemoryWriter( + mem_shape=self._mem_shape) + + # Construct policy, starting with policy_core and policy_action_head. + # `extra_inputs` in this case will be mem_out from current time step (note + # that mem_out is just the controller output if with_memory=False). + self._policy = Policy( + num_policy_hiddens=num_policy_hiddens, + num_actions=num_actions, + num_baseline_hiddens=baseline_mlp_num_hiddens, + activation=mlp_activation, + policy_clip_abs_value=10.0,) + + # Set state_size and output_size. + controller_out_size = self._controller.output_size + controller_state_size = self._controller.state_size + self._state_size = RNNStateNoMem(controller_outputs=controller_out_size, + h_controller=controller_state_size) + read_info_size = () + if self._with_memory: + mem_reads_size, read_info_size = self._memory_reader.output_size + mem_writer_state_size = self._memory_writer.state_size + self._state_size = RNNState(memory=tf.TensorShape(self._mem_shape), + mem_reads=mem_reads_size, + h_mem_writer=mem_writer_state_size, + **self._state_size._asdict()) + + z_size = num_latents + self._output_size = CoreOutputs( + action=tf.TensorShape([]), # Scalar tensor shapes must be explicit. + policy=num_actions, + baseline=tf.TensorShape([]), # Scalar tensor shapes must be explicit. + z=z_size, + read_info=read_info_size) + + def _build(self, inputs, h_prev): + features = inputs + + z_net_inputs = [features, h_prev.controller_outputs] + if self._with_memory: + z_net_inputs.append(h_prev.mem_reads) + z_net_inputs_concat = tf.concat(z_net_inputs, axis=1) + z = self._z_encoder_mlp(z_net_inputs_concat) + + controller_out, h_controller = self._controller(z, h_prev.h_controller) + + read_info = () + if self._with_memory: + # Perform a memory read/write step before generating the policy_modules. + mem_reads, read_info = self._memory_reader((controller_out, + h_prev.memory)) + memory, h_mem_writer = self._memory_writer((z, h_prev.memory), + h_prev.h_mem_writer) + policy_extra_input = tf.concat([controller_out, mem_reads], axis=1) + else: + policy_extra_input = controller_out + + # Get policy, action and (possible empty) baseline from policy module. + policy_inputs = (z, policy_extra_input) + policy_outputs = self._policy(policy_inputs) + core_outputs = CoreOutputs( + z=z, + read_info=read_info, + **policy_outputs._asdict()) + + h_next = RNNStateNoMem(controller_outputs=controller_out, + h_controller=h_controller) + if self._with_memory: + h_next = RNNState(memory=memory, + mem_reads=mem_reads, + h_mem_writer=h_mem_writer, + **h_next._asdict()) + + return core_outputs, h_next + + def initial_state(self, batch_size): + """Use initial state for RNN modules, otherwise use zero state.""" + zero_state = self.zero_state(batch_size, dtype=tf.float32) + controller_out = zero_state.controller_outputs + h_controller = self._controller.initial_state(batch_size) + + state = RNNStateNoMem(controller_outputs=controller_out, + h_controller=h_controller) + if self._with_memory: + memory = zero_state.memory + mem_reads = zero_state.mem_reads + h_mem_writer = self._memory_writer.initial_state(batch_size) + state = RNNState(memory=memory, + mem_reads=mem_reads, + h_mem_writer=h_mem_writer, + **state._asdict()) + return state + + @property + def state_size(self): + return self._state_size + + @property + def output_size(self): + return self._output_size + + +class Agent(snt.AbstractModule): + """Myriad RMA agent. + + `latents` here refers to a purely deterministic encoding of the inputs, rather + than VAE-like latents in e.g. the MERLIN agent. + """ + + def __init__(self, + batch_size, + with_reconstructions=True, + with_memory=True, + image_code_size=500, + image_cost_weight=50., + num_actions=None, + observation_shape=None, + entropy_cost=0.01, + return_cost_weight=0.4, + gamma=0.96, + read_strength_cost=5e-5, + read_strength_tolerance=2., + name='rma_agent'): + super(Agent, self).__init__(name=name) + + self._batch_size = batch_size + self._with_reconstructions = with_reconstructions + self._image_cost_weight = image_cost_weight + self._image_code_size = image_code_size + self._entropy_cost = entropy_cost + self._return_cost_weight = return_cost_weight + self._gamma = gamma + self._read_strength_cost = read_strength_cost + self._read_strength_tolerance = read_strength_tolerance + self._num_actions = num_actions + self._name = name + self._logged_values = {} + + # Store total number of pixels across channels (for image loss scaling). + self._total_num_pixels = np.prod(observation_shape) + + with self._enter_variable_scope(): + + # Construct image encoder/decoder. + self._image_encoder_decoder = ImageEncoderDecoder( + image_code_size=image_code_size) + + self._core = _RMACore( + num_actions=self._num_actions, + with_memory=with_memory) + + def initial_state(self, batch_size): + with tf.name_scope(self._name + '/initial_state'): + return AgentState( + core_state=self._core.initial_state(batch_size), + prev_action=tf.zeros(shape=(batch_size,), dtype=tf.int32)) + + def _prepare_observations(self, observation, last_reward, last_action): + image = observation + + # Make sure the entries are in [0, 1) range. + if image.dtype.is_integer: + image = tf.cast(image, tf.float32) / 255. + + if last_reward is None: + # For some envs, in the first timestep the last_reward can be None. + batch_size = observation.shape[0] + last_reward = tf.zeros((batch_size,), dtype=tf.float32) + + return Observation( + image=image, + last_action=last_action, + last_reward=last_reward) + + @snt.reuse_variables + def _encode(self, observation, last_reward, last_action): + inputs = self._prepare_observations(observation, last_reward, last_action) + + # Encode image observation. + obs_code = self._image_encoder_decoder.encode(inputs.image) + + # Encode last action. + action_code = tf.one_hot(inputs.last_action, self._num_actions) + + # Encode last reward. + reward_code = tf.expand_dims(inputs.last_reward, -1) + + features = tf.concat([obs_code, action_code, reward_code], axis=1) + + return inputs, features + + @snt.reuse_variables + def _decode(self, z): + # Decode image. + image_recon = self._image_encoder_decoder.decode(z) + + # Decode action. + action_recon = snt.Linear(self._num_actions, name='action_recon_linear')(z) + + # Decode reward. + reward_recon = snt.Linear(1, name='reward_recon_linear')(z) + + # Full reconstructions. + recons = Observation( + image=image_recon, + last_reward=reward_recon, + last_action=action_recon) + + return recons + + def step(self, reward, observation, prev_state): + with tf.name_scope(self._name + '/step'): + _, features = self._encode(observation, reward, prev_state.prev_action) + + core_outputs, next_core_state = self._core( + features, prev_state.core_state) + + action = core_outputs.action + + step_output = StepOutput( + action=action, + baseline=core_outputs.baseline, + read_info=core_outputs.read_info) + agent_state = AgentState( + core_state=next_core_state, + prev_action=action) + return step_output, agent_state + + @snt.reuse_variables + def loss(self, observations, rewards, actions, additional_rewards=None): + """Compute the loss.""" + dummy_zeroth_step_actions = tf.zeros_like(actions[:1]) + all_actions = tf.concat([dummy_zeroth_step_actions, actions], axis=0) + inputs, features = snt.BatchApply(self._encode)( + observations, rewards, all_actions) + + rewards = rewards[1:] # Zeroth step reward not correlated to actions. + if additional_rewards is not None: + # Additional rewards are not passed to the encoder (above) in order to be + # consistent with the step, nor to the recon loss so that recons are + # consistent with the observations. Thus, additional rewards only affect + # the returns used to learn the value function. + rewards += additional_rewards + + initial_state = self._core.initial_state(self._batch_size) + + rnn_inputs = features + core_outputs = unroll(self._core, initial_state, rnn_inputs) + + # Remove final timestep of outputs. + core_outputs = nest.map_structure(lambda t: t[:-1], core_outputs) + + if self._with_reconstructions: + recons = snt.BatchApply(self._decode)(core_outputs.z) + recon_targets = nest.map_structure(lambda t: t[:-1], inputs) + recon_loss, recon_logged_values = losses.reconstruction_losses( + recons=recons, + targets=recon_targets, + image_cost=self._image_cost_weight / self._total_num_pixels, + action_cost=1., + reward_cost=1.) + else: + recon_loss = tf.constant(0.0) + recon_logged_values = dict() + + if core_outputs.read_info is not tuple(): + read_reg_loss, read_reg_logged_values = ( + losses.read_regularization_loss( + read_info=core_outputs.read_info, + strength_cost=self._read_strength_cost, + strength_tolerance=self._read_strength_tolerance, + strength_reg_mode='L1', + key_norm_cost=0., + key_norm_tolerance=1.)) + else: + read_reg_loss = tf.constant(0.0) + read_reg_logged_values = dict() + + # Bootstrap value is at end of episode so is zero. + bootstrap_value = tf.zeros(shape=(self._batch_size,), dtype=tf.float32) + + discounts = self._gamma * tf.ones_like(rewards) + + a2c_loss, a2c_loss_extra = trfl.sequence_advantage_actor_critic_loss( + policy_logits=core_outputs.policy, + baseline_values=core_outputs.baseline, + actions=actions, + rewards=rewards, + pcontinues=discounts, + bootstrap_value=bootstrap_value, + lambda_=self._gamma, + entropy_cost=self._entropy_cost, + baseline_cost=self._return_cost_weight, + name='SequenceA2CLoss') + + a2c_loss = tf.reduce_mean(a2c_loss) # Average over batch. + + total_loss = a2c_loss + recon_loss + read_reg_loss + + a2c_loss_logged_values = dict( + pg_loss=tf.reduce_mean(a2c_loss_extra.policy_gradient_loss), + baseline_loss=tf.reduce_mean(a2c_loss_extra.baseline_loss), + entropy_loss=tf.reduce_mean(a2c_loss_extra.entropy_loss)) + agent_loss_log = losses.combine_logged_values( + a2c_loss_logged_values, + recon_logged_values, + read_reg_logged_values) + agent_loss_log['total_loss'] = total_loss + + return total_loss, agent_loss_log + + def _build(self, *args): # Unused. + # pylint: disable=no-value-for-parameter + return self.step(*args) + # pylint: enable=no-value-for-parameter diff --git a/tvt/run.sh b/tvt/run.sh new file mode 100755 index 0000000..bcee93e --- /dev/null +++ b/tvt/run.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Copyright 2019 Deepmind Technologies Limited. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +python3 -m venv tvt_venv +source tvt_venv/bin/activate +pip install -r tvt/requirements.txt + +python3 -m tvt.main diff --git a/tvt/tvt_rewards.py b/tvt/tvt_rewards.py new file mode 100644 index 0000000..93b1db7 --- /dev/null +++ b/tvt/tvt_rewards.py @@ -0,0 +1,247 @@ +# Lint as: python2, python3 +# pylint: disable=g-bad-file-header +# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Temporal Value Transport implementation.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from concurrent import futures +import numpy as np +from six.moves import range +from six.moves import zip + + +def _unstack(array, axis): + """Opposite of np.stack.""" + split_array = np.split(array, array.shape[axis], axis=axis) + return [np.squeeze(a, axis=axis) for a in split_array] + + +def _top_k_args(array, k): + """Return top k arguments or all arguments if array size is less than k.""" + if len(array) <= k: + return np.arange(len(array)) + return np.argpartition(array, kth=-k)[-k:] + + +def _threshold_read_event_times(read_strengths, threshold): + """Return the times of max read strengths within one threshold read event.""" + chosen_times = [] + over_threshold = False + max_read_strength = 0. + # Wait until the threshold is crossed then keep track of max read strength and + # time of max read strength until the read strengths go back under the + # threshold, then add that max read strength time to the chosen times. Wait + # until threshold is crossed again and then repeat the process. + for time, strength in enumerate(read_strengths): + if strength > threshold: + over_threshold = True + if strength > max_read_strength: + max_read_strength = strength + max_read_strength_time = time + else: + # If coming back under threshold, add the time of the last max read. + if over_threshold: + chosen_times.append(max_read_strength_time) + max_read_strength = 0. + over_threshold = False + # Add max read strength time if episode finishes before going under threshold. + if over_threshold: + chosen_times.append(max_read_strength_time) + return np.array(chosen_times) + + +def _tvt_rewards_single_head(read_weights, read_strengths, read_times, + baselines, alpha, top_k_t1, + read_strength_threshold, no_transport_period): + """Compute TVT rewards for a single read head, no batch dimension. + + This performs the updates for one read head. + `t1` and `t2` refer to times to where and from where the value is being + transported, respectively. I.e. the rewards at `t1` times are being modified + based on values at times `t2`. + + Args: + read_weights: shape (ep_length, top_k). + read_strengths: shape (ep_length,). + read_times: shape (ep_length, top_k). + baselines: shape (ep_length,). + alpha: The multiplier for the temporal value transport rewards. + top_k_t1: For each read event time, this determines how many time points + to send tvt reward to. + read_strength_threshold: Read strengths below this value are ignored. + no_transport_period: Length of no_transport_period. + + Returns: + An array of TVT rewards with shape (ep_length,). + """ + tvt_rewards = np.zeros_like(baselines) + + # Mask read_weights for reads that read back to times within + # no_transport_period of current time. + ep_length = read_times.shape[0] + times = np.arange(ep_length) + # Expand dims for correct broadcasting when subtracting read_times. + times = np.expand_dims(times, -1) + read_past_no_transport_period = (times - read_times) > no_transport_period + read_weights_masked = np.where(read_past_no_transport_period, + read_weights, + np.zeros_like(read_weights)) + + # Find t2 times with maximum read weights. Ignore t2 times whose maximum + # read weights fall inside the no_transport_period. + max_read_weight_args = np.argmax(read_weights, axis=1) # (ep_length,) + times = np.arange(ep_length) + max_read_weight_times = read_times[times, + max_read_weight_args] # (ep_length,) + read_strengths_cut = np.where( + times - max_read_weight_times > no_transport_period, + read_strengths, + np.zeros_like(read_strengths)) + + # Filter t2 candidates to perform value transport on local maximums + # above a threshold. + t2_times_with_largest_reads = _threshold_read_event_times( + read_strengths_cut, read_strength_threshold) + + # Loop through all t2 candidates and transport value to top_k_t1 read times. + for t2 in t2_times_with_largest_reads: + try: + baseline_value_when_reading = baselines[t2] + except IndexError: + raise RuntimeError("Attempting to access baselines array with length {}" + " at index {}. Make sure output_baseline is set in" + " the agent config.".format(len(baselines), t2)) + read_times_from_t2 = read_times[t2] + read_weights_from_t2 = read_weights_masked[t2] + + # Find the top_k_t1 read times for this t2 and their corresponding read + # weights. The call to _top_k_args() here gives the array indices for the + # times and weights of the top_k_t1 reads from t2. + top_t1_indices = _top_k_args(read_weights_from_t2, top_k_t1) + top_t1_read_times = np.take(read_times_from_t2, top_t1_indices) + top_t1_read_weights = np.take(read_weights_from_t2, top_t1_indices) + + # For each of the top_k_t1 read times t and corresponding read weight w, + # find the trajectory that contains step_num (t + shift) and modify the + # reward at step_num (t + shift) using w and the baseline value at t2. + # We ignore any read times t >= t2. These can emerge because if nothing + # in memory matches positively with the read query, the top reads may be + # in the empty region of the memory. + for step_num, read_weight in zip(top_t1_read_times, top_t1_read_weights): + if step_num >= t2: + # Skip this step_num as it is not really a memory time. + continue + + # Compute the tvt reward and add it on. + tvt_reward = alpha * read_weight * baseline_value_when_reading + tvt_rewards[step_num] += tvt_reward + + return tvt_rewards + + +def _compute_tvt_rewards_from_read_info( + read_weights, read_strengths, read_times, baselines, gamma, + alpha=0.9, top_k_t1=50, + read_strength_threshold=2., + no_transport_period_when_gamma_1=25): + """Compute TVT rewards given supplied read information, no batch dimension. + + Args: + read_weights: shape (ep_length, num_read_heads, top_k). + read_strengths: shape (ep_length, num_read_heads). + read_times: shape (ep_length, num_read_heads, top_k). + baselines: shape (ep_length,). + gamma: Scalar discount factor used to calculate the no_transport_period. + alpha: The multiplier for the temporal value transport rewards. + top_k_t1: For each read event time, this determines how many time points + to send tvt reward to. + read_strength_threshold: Read strengths below this value are ignored. + no_transport_period_when_gamma_1: no transport period when gamma == 1. + + Returns: + An array of TVT rewards with shape (ep_length,). + """ + + if gamma < 1: + no_transport_period = int(1 / (1 - gamma)) + else: + if no_transport_period_when_gamma_1 is None: + raise ValueError("No transport period must be defined when gamma == 1.") + no_transport_period = no_transport_period_when_gamma_1 + + # Split read infos by read head. + num_read_heads = read_weights.shape[1] + read_weights = _unstack(read_weights, axis=1) + read_strengths = _unstack(read_strengths, axis=1) + read_times = _unstack(read_times, axis=1) + + # Calcuate TVT rewards for each read head separately and add to total. + tvt_rewards = np.zeros_like(baselines) + for i in range(num_read_heads): + tvt_rewards += _tvt_rewards_single_head( + read_weights[i], read_strengths[i], read_times[i], + baselines, alpha, top_k_t1, read_strength_threshold, + no_transport_period) + + return tvt_rewards + + +def compute_tvt_rewards(read_infos, baselines, gamma=.96): + """Compute TVT rewards from EpisodeOutputs. + + Args: + read_infos: A memory_reader.ReadInformation namedtuple, where each element + has shape (ep_length, batch_size, num_read_heads, ...). + baselines: A numpy float array with shape (ep_length, batch_size). + gamma: Discount factor. + + Returns: + An array of TVT rewards with shape (ep_length,). + """ + if not read_infos: + return np.zeros_like(baselines) + + # TVT reward computation is without batch dimension. so we need to process + # read_infos and baselines into batchwise components. + batch_size = baselines.shape[1] + + # Split each element of read info on batch dim. + read_weights = _unstack(read_infos.weights, axis=1) + read_strengths = _unstack(read_infos.strengths, axis=1) + read_indices = _unstack(read_infos.indices, axis=1) + # Split baselines on batch dim. + baselines = _unstack(baselines, axis=1) + + # Comute TVT rewards for each element in the batch (threading over batch). + tvt_rewards = [] + with futures.ThreadPoolExecutor(max_workers=batch_size) as executor: + for i in range(batch_size): + tvt_rewards.append( + executor.submit( + _compute_tvt_rewards_from_read_info, + read_weights[i], + read_strengths[i], + read_indices[i], + baselines[i], + gamma) + ) + tvt_rewards = [f.result() for f in tvt_rewards] + + # Process TVT rewards back into an array of shape (ep_length, batch_size). + return np.stack(tvt_rewards, axis=1)