{ "cells": [ { "cell_type": "markdown", "id": "6c6737c2", "metadata": {}, "source": [ "# Saliency Generation with Deep Reinforcement Learning " ] }, { "attachments": {}, "cell_type": "markdown", "id": "43a651a8", "metadata": {}, "source": [ "## Introduction\n", "\n", "This notebook shows usage of the xaitk-saliency API to gain insight into the behavior of a trained deep reinforcement learning agent in an Atari 2600 environment.\n", "\n", "This example is based on [this paper](https://arxiv.org/abs/1711.00138) and the corresponding code in this [Github repository](https://github.com/greydanus/visualize_atari). The authors use the Asynchronous Advantage Actor Critic (A3C) algorithm with a LSTM-CNN policy network to train several agents for automated gameplay in different Atari 2600 games. They also implement a method for generating saliency maps using image perturbation. Here, we will try to reproduce their results using the xaitk-saliency API, focusing on the Breakout-v0 environment.\n", "\n", "### Table of Contents\n", "* [Set Up Environment](#Set-Up-Environment)\n", "* [Create Atari Environment](#Create-Atari-Environment)\n", " * [Define Policy Network](#Define-Policy-Network)\n", " * [Download Pretrained Model](#Download-Pretrained-Model)\n", " * [Define Rollout Function](#Define-Rollout-Function)\n", " * [Play Breakout](#Play-Breakout)\n", "* [Defining the Application](#Defining-the-Application)\n", " * [Perturbation and Saliency Implementations](#Perturbation-and-Saliency-Implementations)\n", " * [Calling the Application](#Calling-the-Application)\n", "\n", "#### References\n", "1. Greydanus, Samuel, et al. \"Visualizing and Understanding Atari Agents.\" International Conference on Machine Learning. PMLR, 2018.\n", "\n", "
\n", "\n", "To run this notebook in Colab, use the link below:\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/XAITK/xaitk-saliency/blob/master/docs/examples/atari_deepRL_saliency.ipynb)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "700ced8f", "metadata": {}, "source": [ "# Set Up Environment\n", "**Note for Colab users**: after setting up the environment, you may need to \"Restart Runtime\" in order to resolve package version conflicts (see the [README](https://github.com/XAITK/xaitk-saliency/blob/master/docs/examples/README.md#run-the-notebooks-from-colab) for more info)." ] }, { "cell_type": "code", "execution_count": null, "id": "411c7c91", "metadata": {}, "outputs": [], "source": [ "import sys # noqa\n", "\n", "!{sys.executable} -m pip install -qU pip\n", "!{sys.executable} -m pip install -q xaitk-saliency\n", "!{sys.executable} -m pip install -q \"torch>=1.9.0\"\n", "!{sys.executable} -m pip install -q \"torchvision>=0.10.0\"\n", "!{sys.executable} -m pip install -q \"gym[atari,accept-rom-license]==0.26.2\"\n", "# Install the libGL-less version of OpenCV.\n", "!{sys.executable} -m pip uninstall -qy \"opencv-python\" \"opencv-python-headless\" # make sure they're both gone.\n", "!{sys.executable} -m pip install -q \"opencv-python-headless\"\n", "\n", "# Force reinstallation to resolve incompatibilities between dependencies\n", "!{sys.executable} -m pip install -q --force-reinstall --no-cache-dir \\\n", " \"numpy<2.0\" \"packaging<24.0\" \\\n", " opencv-python-headless scikit-image" ] }, { "attachments": {}, "cell_type": "markdown", "id": "d7da5fd3", "metadata": {}, "source": [ "# Create Atari Environment\n", "\n", "Here we create the Breakout environment for our agent using Gym.\n", "\n", "Our agent has 4 different actions to choose from:\n", "
    1. Do nothing\n", "
    2. Fire\n", "
    3. Move right\n", "
    4. Move left" ] }, { "cell_type": "code", "execution_count": 2, "id": "acf34caa", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Action space: ['NOOP', 'FIRE', 'RIGHT', 'LEFT']\n" ] } ], "source": [ "import gym\n", "\n", "env_name = \"ALE/Breakout-v5\"\n", "env = gym.make(env_name)\n", "# Set seed for reproducibility\n", "env.seed(1)\n", "\n", "action_space = env.unwrapped.get_action_meanings()\n", "print(f\"Action space: {action_space}\")" ] }, { "attachments": {}, "cell_type": "markdown", "id": "06b5ce04", "metadata": {}, "source": [ "## Define Policy Network\n", "\n", "This policy network implementation is taken directly from the author's original implementation. It consists of four convolutional layers, an LSTM layer, and two separate fully-connected layers for the value and policy function predictions." ] }, { "cell_type": "code", "execution_count": null, "id": "df77bc74", "metadata": {}, "outputs": [], "source": [ "import glob\n", "from typing import Any\n", "\n", "import numpy as np\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as functional\n", "\n", "\n", "class NNPolicy(torch.nn.Module):\n", " \"\"\"an actor-critic neural network.\"\"\"\n", "\n", " def __init__(self, channels: int, num_actions: int) -> None:\n", " \"\"\"Initialize model.\"\"\"\n", " super().__init__()\n", " self.conv1 = nn.Conv2d(channels, 32, 3, stride=2, padding=1)\n", " self.conv2 = nn.Conv2d(32, 32, 3, stride=2, padding=1)\n", " self.conv3 = nn.Conv2d(32, 32, 3, stride=2, padding=1)\n", " self.conv4 = nn.Conv2d(32, 32, 3, stride=2, padding=1)\n", " self.lstm = nn.LSTMCell(32 * 5 * 5, 256)\n", " self.critic_linear, self.actor_linear = nn.Linear(256, 1), nn.Linear(256, num_actions)\n", "\n", " def forward(\n", " self,\n", " inputs: tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]],\n", " ) -> tuple[torch.Tensor, torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:\n", " \"\"\"Move game forward ones step.\"\"\"\n", " inputs, (hx, cx) = inputs\n", " x = functional.elu(self.conv1(inputs))\n", " x = functional.elu(self.conv2(x))\n", " x = functional.elu(self.conv3(x))\n", " x = functional.elu(self.conv4(x))\n", " x = x.view(-1, 32 * 5 * 5)\n", " hx, cx = self.lstm(x, (hx, cx))\n", " return self.critic_linear(hx), self.actor_linear(hx), (hx, cx)\n", "\n", " def try_load(self, save_dir: str, checkpoint: str = \"*.tar\") -> int:\n", " \"\"\".\"\"\"\n", " paths = glob.glob(save_dir + checkpoint)\n", " step = 0\n", " if len(paths) > 0:\n", " ckpts = [int(s.split(\".\")[-2]) for s in paths]\n", " ix = np.argmax(ckpts)\n", " step = ckpts[ix]\n", " self.load_state_dict(torch.load(paths[ix]))\n", " print(\"\\tno saved models\") if step == 0 else print(f\"\\tloaded model: {paths[ix]}\")\n", " return step" ] }, { "attachments": {}, "cell_type": "markdown", "id": "a27afa00", "metadata": {}, "source": [ "## Download Pretrained Model\n", "\n", "The authors provide pretrained agents [here](https://goo.gl/fqwJDB) for the different environments they used (both the \"strong\" and \"overfit\" variants, see the original paper for details). We will use the \"strong\" Breakout agent for our purposes.\n", "\n", "We provide the Breakout model checkpoint here for easy download, although we refer the reader to the link above which contains a zip file of all the pretrained agents, including both \"strong\" and \"overfit\" agents." ] }, { "cell_type": "code", "execution_count": 4, "id": "737cd8c7", "metadata": {}, "outputs": [], "source": [ "import os\n", "import urllib.request\n", "\n", "root_dir = \"data/pretrained_agents/breakout-v0/\"\n", "os.makedirs(root_dir, exist_ok=True)\n", "model_checkpoint = os.path.join(root_dir, \"strong.40.tar\")\n", "\n", "_ = urllib.request.urlretrieve(\n", " \"https://data.kitware.com/api/v1/item/62325a1d4acac99f426f21e9/download\",\n", " model_checkpoint,\n", ")" ] }, { "attachments": {}, "cell_type": "markdown", "id": "8126ed86", "metadata": {}, "source": [ "## Load Pretrained Model\n", "\n", "Here we load the downloaded model into an instance of the policy function class." ] }, { "cell_type": "code", "execution_count": 5, "id": "abbf6ee3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\tloaded model: data/pretrained_agents/breakout-v0/strong.40.tar\n" ] } ], "source": [ "model = NNPolicy(channels=1, num_actions=env.action_space.n)\n", "model.try_load(root_dir, checkpoint=\"*.tar\")\n", "# Seed seed for reproducibility\n", "_ = torch.manual_seed(1)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "ca132d18", "metadata": {}, "source": [ "## Define Rollout Function\n", "\n", "This function carries out the pretrained agent's policy for a defined number of frames in our Breakout environment. At each step, the current game frame is run through our policy model to get the predicted best action and the agent takes that action. The state of the game is stored after after each step." ] }, { "cell_type": "code", "execution_count": null, "id": "3e447c5a", "metadata": {}, "outputs": [], "source": [ "import cv2\n", "\n", "\n", "def prepro(img: np.ndarray) -> np.ndarray:\n", " \"\"\"Standard frame pre-processing based on Greydanus et al., '18'.\"\"\"\n", " return cv2.resize(src=img[35:195].mean(2), dsize=(80, 80)).astype(np.float32) / 255.0\n", "\n", "\n", "def rollout(model: torch.nn.Module, env: gym.core.Env, max_ep_len: int) -> dict[str, Any]:\n", " \"\"\"Plays the game and return the game history.\"\"\"\n", " history = {\"ins\": [], \"logits\": [], \"values\": [], \"outs\": [], \"hx\": [], \"cx\": []}\n", "\n", " state = torch.Tensor(prepro(env.reset()[0])) # get first state\n", " episode_length, epr, _eploss, done = 0, 0, 0, False # bookkeeping\n", " hx, cx = torch.zeros(1, 256), torch.zeros(1, 256)\n", "\n", " # iterate through each frame in episode\n", " while not done and episode_length <= max_ep_len:\n", " episode_length += 1\n", "\n", " # get game state\n", " model_inp = (state.view(1, 1, 80, 80), (hx, cx))\n", "\n", " # run through model\n", " value, logit, (hx, cx) = model(model_inp)\n", " hx, cx = hx.data, cx.data\n", "\n", " # action probabilities\n", " prob = functional.softmax(logit)\n", "\n", " # best action\n", " action = prob.max(1)[1].data\n", "\n", " # take best action\n", " obs, reward, done, _expert_policy, _ = env.step(action.numpy()[0])\n", "\n", " # update reward\n", " state = torch.Tensor(prepro(obs))\n", " epr += reward\n", "\n", " # save state\n", " history[\"ins\"].append(obs) # game state after taking action\n", " history[\"hx\"].append(hx.squeeze(0).data.numpy()) # LSTM hx output\n", " history[\"cx\"].append(cx.squeeze(0).data.numpy()) # LSTM cx output\n", " history[\"logits\"].append(logit.data.numpy()[0]) # actor output\n", " history[\"values\"].append(value.data.numpy()[0]) # critic output\n", " history[\"outs\"].append(prob.data.numpy()[0]) # action probabilities\n", " print(f\"\\tstep # {episode_length}, reward {epr:.0f}\", end=\"\\r\")\n", "\n", " return history" ] }, { "attachments": {}, "cell_type": "markdown", "id": "09c80601", "metadata": {}, "source": [ "## Play Breakout\n", "\n", "Our pretrained agent will now play the game for up to 3,000 frames. We will create a short video clip from a slice of the game state so we can see the agent in action. The agent shows a \"tunneling\" behavior, which was also noted by the original authors in the paper referenced above." ] }, { "cell_type": "code", "execution_count": 7, "id": "a5e46ac2", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Rolling out policy...\n", "\tstep # 2179, reward 258\n", "Creating video...\n", "Done\n" ] } ], "source": [ "import warnings\n", "\n", "import matplotlib.pyplot as plt\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "\n", "# Play game\n", "print(\"Rolling out policy...\")\n", "history = rollout(model, env, max_ep_len=int(3e3))\n", "\n", "# Create video from frames\n", "print(\"\\nCreating video...\")\n", "fps = 30\n", "w = history[\"ins\"][0].shape[1]\n", "h = history[\"ins\"][0].shape[0]\n", "out = cv2.VideoWriter(\"data/breakout.mp4\", cv2.VideoWriter_fourcc(*\"vp09\"), fps, (w, h))\n", "\n", "start_frame = 638\n", "end_frame = 1238\n", "for i in range(start_frame, end_frame + 1):\n", " frame = cv2.cvtColor(history[\"ins\"][i], cv2.COLOR_RGB2BGR) # Convert to BRG for cv2 standards\n", " out.write(frame)\n", "out.release()\n", "\n", "print(\"Done\")" ] }, { "attachments": {}, "cell_type": "markdown", "id": "3421880e", "metadata": {}, "source": [ "Here we show a single frame from the game." ] }, { "cell_type": "code", "execution_count": 8, "id": "21d701ca", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "f = plt.figure(figsize=[8, 12])\n", "frame_ix = 1070\n", "plt.imshow(history[\"ins\"][frame_ix])\n", "for a in f.axes:\n", " a.get_xaxis().set_visible(False)\n", " a.get_yaxis().set_visible(False)\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "92ac87ff", "metadata": {}, "source": [ "Here we show a recorded video from the game. This video is saved as `breakout.mp4` in the current directory.\n", "\n", "Note that this file will not exist, and the video will not be visible, until the notebook is run." ] }, { "cell_type": "code", "execution_count": 9, "id": "188c9b17", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "
\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%HTML\n", "
\n", "
" ] }, { "attachments": {}, "cell_type": "markdown", "id": "928053a1", "metadata": {}, "source": [ "# Defining the Application\n", "\n", "Our saliency application has four parameters:\n", "
    `start_frame` - the first frame to perform saliency generation for\n", "
    `end_frame` - the last frame to perform saliency generation for\n", "
    `perturber` - the PerturbImage implementation to use\n", "
    `saliency_gen` - the GenerateClassifierConficdenceSaliency implementation to use\n", "\n", "The application will create saliency maps for both the actor (policy function) and the critic (value function) for each frame from `start_frame` to `end_frame` using the image perturber and saliency generator that you pass it. Salient parts of each frame will be highlighted in blue for the actor and in red for the critic and a video will be created from this set of highlighted game frames. This gives a single representation for each frame of where both models are looking to make their predictions.\n", "\n", "To speed up this process, the application utilizes multiple processing threads, one for each frame. This will use a good amount of system memory so be cautious." ] }, { "cell_type": "code", "execution_count": null, "id": "0ca48352", "metadata": {}, "outputs": [], "source": [ "import concurrent.futures\n", "import multiprocessing\n", "import threading\n", "\n", "from scipy.ndimage.filters import gaussian_filter\n", "\n", "from xaitk_saliency import GenerateClassifierConfidenceSaliency, PerturbImage\n", "from xaitk_saliency.utils.masking import occlude_image_batch\n", "\n", "actor_sal_maps = []\n", "critic_sal_maps = []\n", "\n", "print_lock = threading.Lock() # Lock to control thread printing\n", "\n", "\n", "def print_helper(to_print: str, verbose: int) -> None:\n", " \"\"\".\"\"\"\n", " if verbose:\n", " with print_lock:\n", " print(to_print)\n", "\n", "\n", "def gen_sal_maps(\n", " img_idx: int,\n", " sal_idx: int,\n", " perturber: PerturbImage,\n", " saliency_gen: GenerateClassifierConfidenceSaliency,\n", " verbose: int = 0,\n", ") -> None:\n", " \"\"\"Generates actor and critic saliency maps on a per-frame basis given an image\n", " perturber and a saliency map generation implementation.\n", "\n", " img_idx: index of input video frame\n", " sal_idx: index into corresponding global saliency map arrays\n", " perturber: PerturbImage implementation\n", " saliency_gen: GenerateClassifierConfidenceSaliency implementation\n", " verbose: whether or not to print debug statements (default: 0)\n", " \"\"\"\n", " global actor_sal_maps\n", " global critic_sal_maps\n", "\n", " # Score reference frame\n", " print_helper(f\"[{img_idx}]Scoring frame\", verbose)\n", "\n", " ref_img = history[\"ins\"][img_idx]\n", " ref_img_proc = prepro(ref_img)\n", " hx = torch.tensor(history[\"hx\"][img_idx - 1]).view(1, -1)\n", " cx = torch.tensor(history[\"cx\"][img_idx - 1]).view(1, -1)\n", "\n", " ref_img_tensor = torch.tensor(ref_img_proc.reshape(1, 1, 80, 80))\n", " model_inp = (ref_img_tensor, (hx, cx))\n", " ref_value, ref_logit, _ = model(model_inp)\n", " ref_value = ref_value.detach().numpy()[0]\n", " ref_logit = ref_logit.detach().numpy()[0]\n", "\n", " # Get image perturbations\n", " print_helper(f\"[{img_idx}]Perturbing image\", verbose)\n", "\n", " pert_masks = perturber(ref_img_proc)\n", " blurred_img = gaussian_filter(ref_img_proc, sigma=3)\n", " pert_imgs = occlude_image_batch(ref_img_proc, pert_masks, fill=blurred_img)\n", "\n", " pert_values = []\n", " pert_logits = []\n", "\n", " # Score perturbed frames\n", " print_helper(f\"[{img_idx}]Scoring perturbations\", verbose)\n", "\n", " for pert_img in pert_imgs:\n", " pert_img_tensor = torch.tensor(pert_img.reshape(1, 1, 80, 80))\n", " model_inp = (pert_img_tensor, (hx, cx))\n", " pert_value, pert_logit, _ = model(model_inp)\n", " pert_values.append(pert_value.detach().numpy()[0])\n", " pert_logits.append(pert_logit.detach().numpy()[0])\n", "\n", " # Generate actor saliency maps\n", " print_helper(f\"[{img_idx}]Generating actor saliency maps\", verbose)\n", "\n", " actor_sal_maps[sal_idx] = saliency_gen(ref_logit, pert_logits, pert_masks)\n", "\n", " # Generate critic saliency maps\n", " print_helper(f\"[{img_idx}]Generating critic saliency maps\", verbose)\n", "\n", " critic_sal_maps[sal_idx] = saliency_gen(ref_value, pert_values, pert_masks)\n", "\n", "\n", "def visualize_saliency(\n", " frame: np.ndarray,\n", " actor_sal_map: np.ndarray,\n", " critic_sal_map: np.ndarray,\n", ") -> np.ndarray:\n", " \"\"\"Creates visualization of saliency by alpha-blending the provided frame and\n", " saliency maps. The actor and critic saliency maps are scaled to [0,255] to\n", " match the range of the frame and then blended as the blue and red channels\n", " respectively.\n", " \"\"\"\n", " # Scale saliency maps to [0, 255] in respective color channel\n", " sal_colors = np.zeros((210, 160, 3), dtype=np.uint8)\n", " sal_colors[35:195, :, 2] = (255.0 * cv2.resize(src=actor_sal_map, dsize=(160, 160))).astype(\"uint8\")\n", " sal_colors[35:195, :, 0] = (255.0 * cv2.resize(src=critic_sal_map, dsize=(160, 160))).astype(\"uint8\")\n", "\n", " return cv2.addWeighted(frame, 0.4, sal_colors, 0.6, 0)\n", "\n", "\n", "def app(\n", " start_frame: int,\n", " end_frame: int,\n", " perturber: PerturbImage,\n", " saliency_gen: GenerateClassifierConfidenceSaliency,\n", ") -> None:\n", " \"\"\"App for running breakout and peforming saliency generation.\"\"\"\n", " global actor_sal_maps\n", " global critic_sal_maps\n", "\n", " # Initialize map arrays to correct size\n", " actor_sal_maps = [None] * (end_frame - start_frame + 1)\n", " critic_sal_maps = [None] * (end_frame - start_frame + 1)\n", "\n", " # Use threaded computation\n", " def call_wrapper(idx: int) -> None:\n", " \"\"\"Wrapper for gen_sal_maps.\"\"\"\n", " return gen_sal_maps(idx, idx - start_frame, perturber, saliency_gen)\n", "\n", " tpe = concurrent.futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count())\n", " list(tpe.map(call_wrapper, range(start_frame, end_frame + 1)))\n", " tpe.shutdown(wait=True)\n", "\n", " # Write out videos\n", " print(\"Creating video\")\n", " fps = 30\n", " vid_writer = cv2.VideoWriter(\"data/breakout_saliency.mp4\", cv2.VideoWriter_fourcc(*\"vp09\"), fps, (w, h))\n", "\n", " for img_idx in range(start_frame, end_frame + 1):\n", " sal_idx = img_idx - start_frame\n", "\n", " frame = visualize_saliency(history[\"ins\"][img_idx], actor_sal_maps[sal_idx], critic_sal_maps[sal_idx])\n", "\n", " frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) # Convert to BGR to meet cv2 convention\n", "\n", " vid_writer.write(frame)\n", "\n", " vid_writer.release()\n", "\n", " print(\"Done.\")" ] }, { "attachments": {}, "cell_type": "markdown", "id": "180dfd54", "metadata": {}, "source": [ "## Perturbation and Saliency Implementations\n", "\n", "For this example we will use the `SlidingRadial` perturbation implementation with blurring using sigma values of (5,5) and stride of (5,5). Our filled image here will be a blurred version of the original image. We try to use the same parameters as in the original implementation.\n", "\n", "The `SquaredDifferenceScoring` heatmap generation implementation is used here, following the authors' approach in the paper. This will give an absolute sense of saliency for both the actor and critic models without distinguishing positive vs. negative." ] }, { "cell_type": "code", "execution_count": 11, "id": "954163d1", "metadata": {}, "outputs": [], "source": [ "from xaitk_saliency.impls.gen_classifier_conf_sal.squared_difference_scoring import SquaredDifferenceScoring\n", "from xaitk_saliency.impls.perturb_image.sliding_radial import SlidingRadial\n", "\n", "window_perturber = SlidingRadial(radius=(1, 1), stride=(5, 5), sigma=(5, 5))\n", "sal_gen = SquaredDifferenceScoring()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "16e93ceb", "metadata": {}, "source": [ "## Calling the Application\n", "\n", "An arbitrary set of frames is chosen for saliency generation using our application and the resulting video is displayed below.\n", "\n", "From the video it is apparent that the area above the paddle and around the ball seem to be very important to both the value and policy functions. We can also see that the value function is heavily affected by holes in the blocks. This suggests that the agent has learned to create these holes so that it can get the ball behind the blocks to earn a high reward quickly." ] }, { "cell_type": "code", "execution_count": 12, "id": "bd9bdbc4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Creating video\n", "Done.\n" ] } ], "source": [ "app(start_frame=638, end_frame=1238, perturber=window_perturber, saliency_gen=sal_gen)" ] }, { "attachments": {}, "cell_type": "markdown", "id": "32b2698d", "metadata": {}, "source": [ "Here we show saliency (actor in blue, critic in red) overlaid on a single frame from the game." ] }, { "cell_type": "code", "execution_count": 13, "id": "d8c8459d", "metadata": {}, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "f = plt.figure(figsize=[8, 12])\n", "frame_ix = 1070\n", "\n", "img = history[\"ins\"][frame_ix]\n", "\n", "viz = visualize_saliency(img, actor_sal_maps[frame_ix - 1020], critic_sal_maps[frame_ix - 1020])\n", "\n", "plt.imshow(viz)\n", "\n", "for a in f.axes:\n", " a.get_xaxis().set_visible(False)\n", " a.get_yaxis().set_visible(False)\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "985b82c8", "metadata": {}, "source": [ "Finally, we show a recorded video from the game with overlaid saliency (actor in blue, critic in red). This video is saved as `breakout_saliency.mp4` in the current directory.\n", "\n", "Note that this file will not exist, and the video will not be visible, until the notebook is run." ] }, { "cell_type": "code", "execution_count": 14, "id": "27a20662", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "
\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "%%HTML\n", "
\n", "\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 5 }