{ "cells": [ { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "c3a8b2eedba23933bc9763fd7a7f93f8", "grade": false, "grade_id": "cell-b37957442e1fbd8b", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "# Assignment 2 - Semi-gradient TD with a Neural Network\n", "\n", "Welcome to Course 3 Programming Assignment 2. In the previous assignment, you implemented semi-gradient TD with State Aggregation for solving a **policy evaluation task**. In this assignment, you will implement **semi-gradient TD with a simple Neural Network** and use it for the same policy evaluation problem. \n", "\n", "You will implement an agent to evaluate a fixed policy on the 500-State Randomwalk. As you may remember from the previous assignment, the 500-state Randomwalk includes 500 states. Each episode begins with the agent at the center and terminates when the agent goes far left beyond state 1 or far right beyond state 500. At each time step, the agent selects to move either left or right with equal probability. The environment determines how much the agent moves in the selected direction.\n", "\n", "**In this assignment, you will:**\n", "- Implement stochastic gradient descent method for state-value prediction.\n", "- Implement semi-gradient TD with a neural network as the function approximator and Adam algorithm.\n", "- Compare performance of semi-gradient TD with a neural network and semi-gradient TD with tile-coding.\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "2e46d3cb20b9eaffc48288df9563d1ff", "grade": false, "grade_id": "cell-9b62cd317dfd157f", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## Packages\n", "We import the following libraries that are required for this assignment:\n", "\n", "- [numpy](www.numpy.org) : Fundamental package for scientific computing with Python.\n", "- [matplotlib](http://matplotlib.org) : Library for plotting graphs in Python.\n", "- [RL-Glue](http://www.jmlr.org/papers/v10/tanner09a.html) : Library for reinforcement learning experiments.\n", "- [tqdm](https://tqdm.github.io/) : A package to display progress bar when running experiments.\n", "- BaseOptimizer : An abstract class that specifies the optimizer API for Agent.\n", "- plot_script : Custom script to plot results.\n", "- RandomWalkEnvironment : The Randomwalk environment script from Course 3 Assignment 1." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "361a4918a4d8258187005559ca15798b", "grade": false, "grade_id": "cell-b48b54531e30224f", "locked": true, "schema_version": 1, "solution": false } }, "outputs": [], "source": [ "# Do not modify this cell!\n", "\n", "# Import necessary libraries\n", "# DO NOT IMPORT OTHER LIBRARIES - This will break the autograder.\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "%matplotlib inline\n", "\n", "import os, shutil\n", "from tqdm import tqdm\n", "\n", "from rl_glue import RLGlue\n", "from environment import BaseEnvironment\n", "from agent import BaseAgent\n", "from optimizer import BaseOptimizer\n", "import plot_script\n", "from randomwalk_environment import RandomWalkEnvironment" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "77b652f0dd2324ab9327d0b5c583edf2", "grade": false, "grade_id": "cell-de641a52a225de33", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## Section 1: Create semi-gradient TD with a Neural Network\n", "In this section, you will implement an Agent that learns with semi-gradient TD with a neural network. You will use a neural network with one hidden layer. The input of the neural network is the one-hot encoding of the state number. We use the one-hot encoding of the state number instead of the state number itself because we do not want to build the prior knowledge that integer number inputs close to each other have similar values. The hidden layer contains 100 rectifier linear units (ReLUs) which pass their input if it is bigger than one and return 0 otherwise. ReLU gates are commonly used in neural networks due to their nice properties such as the sparsity of the activation and having non-vanishing gradients. The output of the neural network is the estimated state value. It is a linear function of the hidden units as is commonly the case when estimating the value of a continuous target using neural networks.\n", "\n", "The neural network looks like this:\n", "![](nn_structure.png)\n", "\n", "\n", "For a given input, $s$, value of $s$ is computed by:\n", "$$\n", "\\begin{align} \n", "\\psi &= sW^{[0]} + b^{[0]} \\\\\n", "x &= \\textit{max}(0, \\psi) \\\\\n", "v &= xW^{[1]} + b^{[1]}\n", "\\end{align} \n", "$$\n", "\n", "where $W^{[0]}$, $b^{[0]}$, $W^{[1]}$, $b^{[1]}$ are the parameters of the network and will be learned when training the agent." ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "4ba60f0d7c91468d560247475385d931", "grade": false, "grade_id": "cell-5f7ab00aa29454a6", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## 1-1: Implement helper methods\n", "\n", "Before implementing the agent, you first implement some helper functions which you will later use in agent's main methods. \n", "\n", "### Implement `get_value()`\n", "First, you will implement get_value() method which feeds an input $s$ into the neural network and returns the output of the network $v$ according to the equations above. To implement get_value(), take into account the following notes:\n", "\n", "- `get_value()` gets the one-hot encoded state number denoted by s as an input. \n", "- `get_value()` receives the weights of the neural network as input, denoted by weights and structured as an array of dictionaries. Each dictionary corresponds to weights from one layer of the neural network to the next. Each dictionary includes $W$ and $b$. The shape of the elements in weights are as follows:\n", " - weights[0][\"W\"]: num_states $\\times$ num_hidden_units\n", " - weights[0][\"b\"]: 1 $\\times$ num_hidden_units\n", " - weights[1][\"W\"]: num_hidden_units $\\times$ 1\n", " - weights[1][\"b\"]: 1 $\\times$ 1\n", "\n", "- The input of the neural network is a sparse vector. To make computation faster, we take advantage of input sparsity. To do so, we provided a helper method `my_matmul()`. **Make sure that you use `my_matmul()` for all matrix multiplications except for element-wise multiplications in this notebook.**\n", "- The max operator used for computing $x$ is element-wise. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "b4f8b88337142352f0977c64e53467ab", "grade": false, "grade_id": "cell-5a1a1bbc13b00f36", "locked": true, "schema_version": 1, "solution": false } }, "outputs": [], "source": [ "def my_matmul(x1, x2):\n", " \"\"\"\n", " Given matrices x1 and x2, return the multiplication of them\n", " \"\"\"\n", " \n", " result = np.zeros((x1.shape[0], x2.shape[1]))\n", " x1_non_zero_indices = x1.nonzero()\n", " if x1.shape[0] == 1 and len(x1_non_zero_indices[1]) == 1:\n", " result = x2[x1_non_zero_indices[1], :]\n", " elif x1.shape[1] == 1 and len(x1_non_zero_indices[0]) == 1:\n", " result[x1_non_zero_indices[0], :] = x2 * x1[x1_non_zero_indices[0], 0]\n", " else:\n", " result = np.matmul(x1, x2)\n", " return result" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "deletable": false, "nbgrader": { "checksum": "18603564b0ad43c6dd7b6075a9a79bfc", "grade": false, "grade_id": "cell-f6e1321ef3928d09", "locked": false, "schema_version": 1, "solution": true } }, "outputs": [], "source": [ "#GRADED FUNCTION: [get_value]\n", "\n", "def get_value(s, weights):\n", " \"\"\"\n", " Compute value of input s given the weights of a neural network\n", " \"\"\"\n", " ### Compute the ouput of the neural network, v, for input s (3 lines)\n", " \n", " ### START CODE HERE ###\n", " psi = my_matmul(s, weights[0][\"W\"]) + weights[0][\"b\"]\n", " x = np.maximum(0, psi)\n", " v = my_matmul(x, weights[1][\"W\"]) + weights[1][\"b\"]\n", " ### END CODE HERE ###\n", " return v" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "feeb6580e587e701ca835133a3780004", "grade": false, "grade_id": "cell-d422d7296850b470", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `get_value()` function:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "ba83e4def1dfa6f3eacc1935bc531aee", "grade": true, "grade_id": "cell-e0dacb873e8f91f4", "locked": true, "points": 10, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Estimated value: [[-0.21915705]]\n", "Passed the assert!\n" ] } ], "source": [ "## Test Code for get_value() ## \n", "\n", "# Suppose num_states = 5, num_hidden_layer = 1, and num_hidden_units = 10 \n", "num_hidden_layer = 1\n", "s = np.array([[0, 0, 0, 1, 0]])\n", "\n", "weights_data = np.load(\"asserts/get_value_weights.npz\")\n", "weights = [dict() for i in range(num_hidden_layer+1)]\n", "weights[0][\"W\"] = weights_data[\"W0\"]\n", "weights[0][\"b\"] = weights_data[\"b0\"]\n", "weights[1][\"W\"] = weights_data[\"W1\"]\n", "weights[1][\"b\"] = weights_data[\"b1\"]\n", "\n", "estimated_value = get_value(s, weights)\n", "print (\"Estimated value: {}\".format(estimated_value))\n", "assert(np.allclose(estimated_value, np.array([[-0.21915705]])))\n", "\n", "print (\"Passed the assert!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "ae6d398c5d6051d7a6510c6bb16cbad9", "grade": false, "grade_id": "cell-f34843c42f4bbc15", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " Estimated value: [[-0.21915705]]" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "2833eef7385a16de825b9e20120186a5", "grade": false, "grade_id": "cell-c99e57755233d068", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "### Implement `get_gradient()`\n", "You will also implement `get_gradient()` method which computes the gradient of the value function for a given input, using backpropagation. You will later use this function to update the value function. \n", "\n", "As you know, we compute the value of a state $s$ according to: \n", "$$\n", "\\begin{align} \n", "\\psi &= sW^{[0]} + b^{[0]} \\\\\n", "x &= \\textit{max}(0, \\psi) \\\\\n", "v &= xW^{[1]} + b^{[1]}\n", "\\end{align} \n", "$$\n", "\n", "To update the weights of the neural network ($W^{[0]}$, $b^{[0]}$, $W^{[1]}$, $b^{[1]}$), we compute the gradient of $v$ with respect to the weights according to:\n", "\n", "$$\n", "\\begin{align} \n", "\\frac{\\partial v}{\\partial W^{[0]}} &= s^T(W^{[1]T} \\odot I_{x>0}) \\\\\n", "\\frac{\\partial v}{\\partial b^{[0]}} &= W^{[1]T} \\odot I_{x>0} \\\\\n", "\\frac{\\partial v}{\\partial W^{[1]}} &= x^T \\\\\n", "\\frac{\\partial v}{\\partial b^{[1]}} &= 1\n", "\\end{align}\n", "$$\n", "where $\\odot$ denotes element-wise matrix multiplication and $I_{x>0}$ is the gradient of the ReLU activation function which is an indicator whose $i$th element is 1 if $x[i]>0$ and 0 otherwise." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "deletable": false, "nbgrader": { "checksum": "576b7d14329dfdb7b8697e48f4096105", "grade": false, "grade_id": "cell-940eb29a426e021b", "locked": false, "schema_version": 1, "solution": true } }, "outputs": [], "source": [ "#GRADED FUNCTION: [get_gradient]\n", "\n", "def get_gradient(s, weights):\n", " \"\"\"\n", " Given inputs s and weights, return the gradient of v with respect to the weights\n", " \"\"\"\n", "\n", " ### Compute the gradient of the value function with respect to W0, b0, W1, b1 for input s (6~8 lines)\n", " # grads[0][\"W\"] = ?\n", " # grads[0][\"b\"] = ?\n", " # grads[1][\"W\"] = ?\n", " # grads[1][\"b\"] = ?\n", " # Note that grads[0][\"W\"], grads[0][\"b\"], grads[1][\"W\"], and grads[1][\"b\"] should have the same shape as \n", " # weights[0][\"W\"], weights[0][\"b\"], weights[1][\"W\"], and weights[1][\"b\"] respectively\n", " # Note that to compute the gradients, you need to compute the activation of the hidden layer (x)\n", "\n", " grads = [dict() for i in range(len(weights))]\n", "\n", " ### START CODE HERE ###\n", " psi = my_matmul(s, weights[0][\"W\"]) + weights[0][\"b\"]\n", " x = np.maximum(0, psi)\n", " I = (x > 0) * 1\n", " grads[0][\"W\"] = my_matmul(s.T, (weights[1][\"W\"].T * I))\n", " grads[0][\"b\"] = weights[1][\"W\"].T * I\n", " grads[1][\"W\"] = x.T\n", " grads[1][\"b\"] = 1\n", " ### END CODE HERE ###\n", "\n", " return grads\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "d28c022a9fef03b20db192090abc644b", "grade": false, "grade_id": "cell-c6eb98da1041cedf", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `get_gradient()` function:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "cd8e33658e276ea4cafb6d9d0803df73", "grade": true, "grade_id": "cell-217e4e4ad7daf7d6", "locked": true, "points": 10, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "grads[0][\"W\"]\n", " [[0. 0. ]\n", " [0. 0. ]\n", " [0. 0. ]\n", " [0.76103773 0.12167502]\n", " [0. 0. ]] \n", "\n", "grads[0][\"b\"]\n", " [[0.76103773 0.12167502]] \n", "\n", "grads[1][\"W\"]\n", " [[0.69198983]\n", " [0.82403662]] \n", "\n", "grads[1][\"b\"]\n", " 1 \n", "\n", "Passed the asserts!\n" ] } ], "source": [ "## Test Code for get_gradient() ## \n", "\n", "# Suppose num_states = 5, num_hidden_layer = 1, and num_hidden_units = 2 \n", "num_hidden_layer = 1\n", "s = np.array([[0, 0, 0, 1, 0]])\n", "\n", "weights_data = np.load(\"asserts/get_gradient_weights.npz\")\n", "weights = [dict() for i in range(num_hidden_layer+1)]\n", "weights[0][\"W\"] = weights_data[\"W0\"]\n", "weights[0][\"b\"] = weights_data[\"b0\"]\n", "weights[1][\"W\"] = weights_data[\"W1\"]\n", "weights[1][\"b\"] = weights_data[\"b1\"]\n", "\n", "grads = get_gradient(s, weights)\n", "\n", "grads_answer = np.load(\"asserts/get_gradient_grads.npz\")\n", "\n", "print(\"grads[0][\\\"W\\\"]\\n\", grads[0][\"W\"], \"\\n\")\n", "print(\"grads[0][\\\"b\\\"]\\n\", grads[0][\"b\"], \"\\n\")\n", "print(\"grads[1][\\\"W\\\"]\\n\", grads[1][\"W\"], \"\\n\")\n", "print(\"grads[1][\\\"b\\\"]\\n\", grads[1][\"b\"], \"\\n\")\n", "\n", "assert(np.allclose(grads[0][\"W\"], grads_answer[\"W0\"]))\n", "assert(np.allclose(grads[0][\"b\"], grads_answer[\"b0\"]))\n", "assert(np.allclose(grads[1][\"W\"], grads_answer[\"W1\"]))\n", "assert(np.allclose(grads[1][\"b\"], grads_answer[\"b1\"]))\n", "\n", "print(\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "7e660355c7662a6d5fde8a39a82ae258", "grade": false, "grade_id": "cell-d14e4ea8eb9e5c71", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " grads[0][\"W\"]\n", " [[0. 0. ]\n", " [0. 0. ]\n", " [0. 0. ]\n", " [0.76103773 0.12167502]\n", " [0. 0. ]] \n", "\n", " grads[0][\"b\"]\n", " [[0.76103773 0.12167502]] \n", "\n", " grads[1][\"W\"]\n", " [[0.69198983]\n", " [0.82403662]] \n", "\n", " grads[1][\"b\"]\n", " [[1.]] " ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "ff5c416e4be90cb754c2163fe04cc495", "grade": false, "grade_id": "cell-3976c881b8145ebd", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "### Implement stochastic gradient descent method for state-value prediction\n", "In this section, you will implement stochastic gradient descent (SGD) method for state_value prediction. Here is the basic SGD update for state-value prediction with TD:\n", "\n", "$$\\mathbf{w_{t+1}} = \\mathbf{w_{t}} + \\alpha \\delta_t \\nabla \\hat{v}(S_t,\\mathbf{w_{t}})$$\n", "\n", "At each time step, we update the weights in the direction $g_t = \\delta_t \\nabla \\hat{v}(S_t,\\mathbf{w_t})$ using a fixed step-size $\\alpha$. $\\delta_t = R_{t+1} + \\gamma \\hat{v}(S_{t+1},\\mathbf{w_{t}}) - \\hat{v}(S_t,\\mathbf{w_t})$ is the TD-error. $\\nabla \\hat{v}(S_t,\\mathbf{w_{t}})$ is the gradient of the value function with respect to the weights.\n", "\n", "The following cell includes the SGD class. You will complete the `update_weight()` method of SGD assuming that the weights and update g are provided.\n", "\n", "**As you know, in this assignment, we structured the weights as an array of dictionaries. Note that the updates $g_t$, in the case of TD, is $\\delta_t \\nabla \\hat{v}(S_t,\\mathbf{w_t})$. As a result, $g_t$ has the same structure as $\\nabla \\hat{v}(S_t,\\mathbf{w_t})$ which is also an array of dictionaries.**" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "deletable": false, "nbgrader": { "checksum": "1edadbb4d5dc91d41ffe1a25a20d14d6", "grade": false, "grade_id": "cell-ec1ae0c2c9d60a19", "locked": false, "schema_version": 1, "solution": true } }, "outputs": [], "source": [ "#GRADED FUNCTION: [SGD]\n", "\n", "class SGD(BaseOptimizer):\n", " def __init__(self):\n", " pass\n", " \n", " def optimizer_init(self, optimizer_info):\n", " \"\"\"Setup for the optimizer.\n", "\n", " Set parameters needed to setup the stochastic gradient descent method.\n", "\n", " Assume optimizer_info dict contains:\n", " {\n", " step_size: float\n", " }\n", " \"\"\"\n", " self.step_size = optimizer_info.get(\"step_size\")\n", " \n", " def update_weights(self, weights, g):\n", " \"\"\"\n", " Given weights and update g, return updated weights\n", " \"\"\"\n", " for i in range(len(weights)):\n", " for param in weights[i].keys():\n", " \n", " ### update weights (1 line)\n", " # weights[i][param] = None\n", " \n", " ### START CODE HERE ###\n", " weights[i][param] += self.step_size * g[i][param]\n", " ### END CODE HERE ###\n", " \n", " return weights" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "02c0dea41c93d0f3735de4850fb20778", "grade": false, "grade_id": "cell-06b56bd3ac6717af", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `update_weights()` function:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "3209c121f74aeb7c51a10676e3b3b5dd", "grade": true, "grade_id": "cell-dcf2cc6e7528b324", "locked": true, "points": 10, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "updated_weights[0][\"W\"]\n", " [[ 1.17899492 0.53656321]\n", " [ 0.58008221 1.47666572]\n", " [ 1.01909411 -1.10248056]\n", " [ 0.72490408 0.06828853]\n", " [-0.20609725 0.69034095]] \n", "\n", "updated_weights[0][\"b\"]\n", " [[-0.18484533 0.92844539]] \n", "\n", "updated_weights[1][\"W\"]\n", " [[0.70488257]\n", " [0.58150878]] \n", "\n", "updated_weights[1][\"b\"]\n", " [[0.88467086]] \n", "\n", "Passed the asserts!\n" ] } ], "source": [ "# Do not modify this cell!\n", "\n", "## Test Code for update_weights() ## \n", "\n", "# Suppose num_states = 5, num_hidden_layer = 1, and num_hidden_units = 2 \n", "num_hidden_layer = 1\n", "\n", "weights_data = np.load(\"asserts/update_weights_weights.npz\")\n", "weights = [dict() for i in range(num_hidden_layer+1)]\n", "weights[0][\"W\"] = weights_data[\"W0\"]\n", "weights[0][\"b\"] = weights_data[\"b0\"]\n", "weights[1][\"W\"] = weights_data[\"W1\"]\n", "weights[1][\"b\"] = weights_data[\"b1\"]\n", "\n", "g_data = np.load(\"asserts/update_weights_g.npz\")\n", "g = [dict() for i in range(num_hidden_layer+1)]\n", "g[0][\"W\"] = g_data[\"W0\"]\n", "g[0][\"b\"] = g_data[\"b0\"]\n", "g[1][\"W\"] = g_data[\"W1\"]\n", "g[1][\"b\"] = g_data[\"b1\"]\n", "\n", "test_sgd = SGD()\n", "optimizer_info = {\"step_size\": 0.3}\n", "test_sgd.optimizer_init(optimizer_info)\n", "updated_weights = test_sgd.update_weights(weights, g)\n", "\n", "# updated weights asserts\n", "updated_weights_answer = np.load(\"asserts/update_weights_updated_weights.npz\")\n", "\n", "print(\"updated_weights[0][\\\"W\\\"]\\n\", updated_weights[0][\"W\"], \"\\n\")\n", "print(\"updated_weights[0][\\\"b\\\"]\\n\", updated_weights[0][\"b\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"W\\\"]\\n\", updated_weights[1][\"W\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"b\\\"]\\n\", updated_weights[1][\"b\"], \"\\n\")\n", "\n", "assert(np.allclose(updated_weights[0][\"W\"], updated_weights_answer[\"W0\"]))\n", "assert(np.allclose(updated_weights[0][\"b\"], updated_weights_answer[\"b0\"]))\n", "assert(np.allclose(updated_weights[1][\"W\"], updated_weights_answer[\"W1\"]))\n", "assert(np.allclose(updated_weights[1][\"b\"], updated_weights_answer[\"b1\"]))\n", "\n", "print(\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "5da852f49bbf6f5ae1aa046063066bf3", "grade": false, "grade_id": "cell-59a172bdd7aaf622", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " updated_weights[0][\"W\"]\n", " [[ 1.17899492 0.53656321]\n", " [ 0.58008221 1.47666572]\n", " [ 1.01909411 -1.10248056]\n", " [ 0.72490408 0.06828853]\n", " [-0.20609725 0.69034095]] \n", "\n", " updated_weights[0][\"b\"]\n", " [[-0.18484533 0.92844539]] \n", "\n", " updated_weights[1][\"W\"]\n", " [[0.70488257]\n", " [0.58150878]] \n", "\n", " updated_weights[1][\"b\"]\n", " [[0.88467086]] " ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "9f54dedb2d48647d810a9bdd8a1788da", "grade": false, "grade_id": "cell-29620a7c315e087d", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "### Adam Algorithm\n", "In this assignment, instead of using SGD for updating the weights, we use a more advanced algorithm called Adam. The Adam algorithm improves the SGD update with two concepts: adaptive vector step-sizes and momentum. It keeps estimates of the mean and second moment of the updates, denoted by $\\mathbf{m}$ and $\\mathbf{v}$ respectively:\n", "$$\\mathbf{m_t} = \\beta_m \\mathbf{m_{t-1}} + (1 - \\beta_m)g_t \\\\\n", "\\mathbf{v_t} = \\beta_v \\mathbf{v_{t-1}} + (1 - \\beta_v)g^2_t\n", "$$\n", "\n", "Given that $\\mathbf{m}$ and $\\mathbf{v}$ are initialized to zero, they are biased toward zero. To get unbiased estimates of the mean and second moment, Adam defines $\\mathbf{\\hat{m}}$ and $\\mathbf{\\hat{v}}$ as:\n", "$$ \\mathbf{\\hat{m_t}} = \\frac{\\mathbf{m_t}}{1 - \\beta_m^t} \\\\\n", "\\mathbf{\\hat{v_t}} = \\frac{\\mathbf{v_t}}{1 - \\beta_v^t}\n", "$$\n", "\n", "The weights are then updated as follows:\n", "$$ \\mathbf{w_t} = \\mathbf{w_{t-1}} + \\frac{\\alpha}{\\sqrt{\\mathbf{\\hat{v_t}}}+\\epsilon} \\mathbf{\\hat{m_t}}\n", "$$\n", "\n", "When implementing the agent you will use the Adam algorithm instead of SGD because it is more efficient. We have already provided you the implementation of the Adam algorithm in the cell below. You will use it when implementing your agent. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "bc01d14ea352fe85e325285f778a05ea", "grade": false, "grade_id": "cell-201cc0242fbec19f", "locked": true, "schema_version": 1, "solution": false } }, "outputs": [], "source": [ "class Adam(BaseOptimizer):\n", " def __init__(self):\n", " pass\n", " \n", " def optimizer_init(self, optimizer_info):\n", " \"\"\"Setup for the optimizer.\n", "\n", " Set parameters needed to setup the Adam algorithm.\n", "\n", " Assume optimizer_info dict contains:\n", " {\n", " num_states: integer,\n", " num_hidden_layer: integer,\n", " num_hidden_units: integer,\n", " step_size: float, \n", " self.beta_m: float\n", " self.beta_v: float\n", " self.epsilon: float\n", " }\n", " \"\"\"\n", " \n", " self.num_states = optimizer_info.get(\"num_states\")\n", " self.num_hidden_layer = optimizer_info.get(\"num_hidden_layer\")\n", " self.num_hidden_units = optimizer_info.get(\"num_hidden_units\")\n", "\n", " # Specify Adam algorithm's hyper parameters\n", " self.step_size = optimizer_info.get(\"step_size\")\n", " self.beta_m = optimizer_info.get(\"beta_m\")\n", " self.beta_v = optimizer_info.get(\"beta_v\")\n", " self.epsilon = optimizer_info.get(\"epsilon\")\n", "\n", " self.layer_size = np.array([self.num_states, self.num_hidden_units, 1])\n", "\n", " # Initialize Adam algorithm's m and v\n", " self.m = [dict() for i in range(self.num_hidden_layer+1)]\n", " self.v = [dict() for i in range(self.num_hidden_layer+1)]\n", "\n", " for i in range(self.num_hidden_layer+1):\n", "\n", " # Initialize self.m[i][\"W\"], self.m[i][\"b\"], self.v[i][\"W\"], self.v[i][\"b\"] to zero\n", " self.m[i][\"W\"] = np.zeros((self.layer_size[i], self.layer_size[i+1]))\n", " self.m[i][\"b\"] = np.zeros((1, self.layer_size[i+1]))\n", " self.v[i][\"W\"] = np.zeros((self.layer_size[i], self.layer_size[i+1]))\n", " self.v[i][\"b\"] = np.zeros((1, self.layer_size[i+1]))\n", "\n", " # Initialize beta_m_product and beta_v_product to be later used for computing m_hat and v_hat\n", " self.beta_m_product = self.beta_m\n", " self.beta_v_product = self.beta_v\n", "\n", " def update_weights(self, weights, g):\n", " \"\"\"\n", " Given weights and update g, return updated weights\n", " \"\"\"\n", " \n", " for i in range(len(weights)):\n", " for param in weights[i].keys():\n", "\n", " ### update self.m and self.v\n", " self.m[i][param] = self.beta_m * self.m[i][param] + (1 - self.beta_m) * g[i][param]\n", " self.v[i][param] = self.beta_v * self.v[i][param] + (1 - self.beta_v) * (g[i][param] * g[i][param])\n", "\n", " ### compute m_hat and v_hat\n", " m_hat = self.m[i][param] / (1 - self.beta_m_product)\n", " v_hat = self.v[i][param] / (1 - self.beta_v_product)\n", "\n", " ### update weights\n", " weights[i][param] += self.step_size * m_hat / (np.sqrt(v_hat) + self.epsilon)\n", " \n", " ### update self.beta_m_product and self.beta_v_product\n", " self.beta_m_product *= self.beta_m\n", " self.beta_v_product *= self.beta_v\n", " \n", " return weights\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "6b51319a92f51a4bea52e1916eae6c61", "grade": false, "grade_id": "cell-93e3b1233b92a93f", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## 1-2: Implement Agent Methods\n", "In this section, you will implement `agent_init()`, `agent_start()`, `agent_step()`, and `agent_end()`.\n", "\n", "In `agent_init()`, you will:\n", " \n", " - specify the neural network structure by filling self.layer_size with the size of the input layer, hidden layer, and output layer.\n", " - initialize the network's parameters. We show the parameters as an array of dictionaries, self.weights, where each dictionary corresponds to weights from one layer to the next. Each dictionary includes $W$ and $b$. To initialize the parameters, you will use a normal distribution with mean 0 and standard deviation $\\sqrt{\\frac{2}{\\text{# input of each node}}}$. This initialization heuristic is commonly used when using ReLU gates and helps keep the output of a neuron from getting too big or too small. To initialize the network's parameters, use **self.rand_generator.normal()** which draws random samples from a normal distribution. The parameters of self.rand_generator.normal are mean of the distribution, standard deviation of the distribution, and output shape in the form of tuple of integers.\n", "\n", "\n", "In `agent_start()`, you will:\n", " - specify self.last_state and self.last_action.\n", " \n", "In `agent_step()` and `agent_end()`, you will:\n", " - compute the TD error using $v(S_t)$ and $v(S_{t+1})$. To compute the value function for $S_t$ and $S_{t+1}$, you will get their one-hot encoding using `one_hot()` method that we provided below. You feed the one-hot encoded state number to the neural networks using `get_value()` method that you implemented above. Note that `one_hot()` method returns the one-hot encoding of a state as a numpy array of shape (1, num_states).\n", " - retrieve the gradients using `get_gradient()` function that you implemented.\n", " - use Adam_algorithm that we provided to update the neural network's parameters, self.weights.\n", " - use `agent_policy()` method to select actions with. (only in `agent_step()`)\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "383049ffcbcce7abc10d684ac0c1f3d5", "grade": false, "grade_id": "cell-b2e6a488bd1b6961", "locked": true, "schema_version": 1, "solution": false } }, "outputs": [], "source": [ "def one_hot(state, num_states):\n", " \"\"\"\n", " Given num_state and a state, return the one-hot encoding of the state\n", " \"\"\"\n", " # Create the one-hot encoding of state\n", " # one_hot_vector is a numpy array of shape (1, num_states)\n", " \n", " one_hot_vector = np.zeros((1, num_states))\n", " one_hot_vector[0, int((state - 1))] = 1\n", " \n", " return one_hot_vector" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "deletable": false, "nbgrader": { "checksum": "00a274397e3680c8f750785edc7a5f38", "grade": false, "grade_id": "cell-bcf71c78d138fd44", "locked": false, "schema_version": 1, "solution": true } }, "outputs": [], "source": [ "#GRADED FUNCTION: [Agent]\n", "\n", "class TDAgent(BaseAgent):\n", " def __init__(self):\n", " self.name = \"td_agent\"\n", " pass\n", "\n", " def agent_init(self, agent_info={}):\n", " \"\"\"Setup for the agent called when the experiment first starts.\n", "\n", " Set parameters needed to setup the semi-gradient TD with a Neural Network.\n", "\n", " Assume agent_info dict contains:\n", " {\n", " num_states: integer,\n", " num_hidden_layer: integer,\n", " num_hidden_units: integer,\n", " step_size: float, \n", " discount_factor: float,\n", " self.beta_m: float\n", " self.beta_v: float\n", " self.epsilon: float\n", " seed: int\n", " }\n", " \"\"\"\n", " \n", " # Set random seed for weights initialization for each run\n", " self.rand_generator = np.random.RandomState(agent_info.get(\"seed\")) \n", " \n", " # Set random seed for policy for each run\n", " self.policy_rand_generator = np.random.RandomState(agent_info.get(\"seed\"))\n", "\n", " # Set attributes according to agent_info\n", " self.num_states = agent_info.get(\"num_states\")\n", " self.num_hidden_layer = agent_info.get(\"num_hidden_layer\")\n", " self.num_hidden_units = agent_info.get(\"num_hidden_units\")\n", " self.discount_factor = agent_info.get(\"discount_factor\")\n", "\n", " ### Define the neural network's structure (1 line)\n", " # Specify self.layer_size which shows the number of nodes in each layer\n", " # self.layer_size = np.array([None, None, None])\n", " # Hint: Checkout the NN diagram at the beginning of the notebook\n", "\n", " ### START CODE HERE ###\n", " self.layer_size = np.array([self.num_states, self.num_hidden_units, self.num_hidden_layer])\n", " ### END CODE HERE ###\n", "\n", " # Initialize the neural network's parameter (2 lines)\n", " self.weights = [dict() for i in range(self.num_hidden_layer+1)]\n", " for i in range(self.num_hidden_layer+1):\n", "\n", " ### Initialize self.weights[i][\"W\"] and self.weights[i][\"b\"] using self.rand_generator.normal()\n", " # Note that The parameters of self.rand_generator.normal are mean of the distribution, \n", " # standard deviation of the distribution, and output shape in the form of tuple of integers.\n", " # To specify output shape, use self.layer_size.\n", "\n", " ### START CODE HERE ###\n", " input_size, output_size = self.layer_size[i], self.layer_size[i+1]\n", " self.weights[i][\"W\"] = self.rand_generator.normal(0, np.sqrt(2/input_size), (input_size, output_size))\n", " self.weights[i][\"b\"] = self.rand_generator.normal(0, np.sqrt(2/input_size), (1, output_size))\n", " ### END CODE HERE ###\n", " \n", " # Specify the optimizer\n", " self.optimizer = Adam()\n", " optimizer_info = {\"num_states\": agent_info[\"num_states\"],\n", " \"num_hidden_layer\": agent_info[\"num_hidden_layer\"],\n", " \"num_hidden_units\": agent_info[\"num_hidden_units\"],\n", " \"step_size\": agent_info[\"step_size\"],\n", " \"beta_m\": agent_info[\"beta_m\"],\n", " \"beta_v\": agent_info[\"beta_v\"],\n", " \"epsilon\": agent_info[\"epsilon\"]}\n", " self.optimizer.optimizer_init(optimizer_info)\n", " \n", " self.last_state = None\n", " self.last_action = None\n", "\n", " def agent_policy(self, state):\n", "\n", " ### Set chosen_action as 0 or 1 with equal probability. \n", " chosen_action = self.policy_rand_generator.choice([0,1]) \n", " return chosen_action\n", "\n", " def agent_start(self, state):\n", " \"\"\"The first method called when the experiment starts, called after\n", " the environment starts.\n", " Args:\n", " state (Numpy array): the state from the\n", " environment's evn_start function.\n", " Returns:\n", " The first action the agent takes.\n", " \"\"\"\n", " ### select action given state (using self.agent_policy()), and save current state and action (2 lines)\n", " # self.last_state = ?\n", " # self.last_action = ?\n", "\n", " ### START CODE HERE ###\n", " action = self.agent_policy(state)\n", " self.last_state = state\n", " self.last_action = action\n", " ### END CODE HERE ###\n", "\n", " return self.last_action\n", "\n", " def agent_step(self, reward, state):\n", " \"\"\"A step taken by the agent.\n", " Args:\n", " reward (float): the reward received for taking the last action taken\n", " state (Numpy array): the state from the\n", " environment's step based, where the agent ended up after the\n", " last step\n", " Returns:\n", " The action the agent is taking.\n", " \"\"\"\n", " \n", " ### Compute TD error (5 lines)\n", " # delta = None\n", "\n", " ### START CODE HERE ###\n", " last_state_vector = one_hot(self.last_state, self.num_states)\n", " last_value = get_value(last_state_vector, self.weights)\n", " \n", " current_state_vector = one_hot(state, self.num_states)\n", " current_value = get_value(current_state_vector, self.weights)\n", " \n", " delta = reward + self.discount_factor * current_value - last_value\n", " ### END CODE HERE ###\n", "\n", " ### Retrieve gradients (1 line)\n", " # grads = None\n", "\n", " ### START CODE HERE ###\n", " grads = get_gradient(last_state_vector, self.weights)\n", " ### END CODE HERE ###\n", "\n", " ### Compute g (1 line)\n", " g = [dict() for i in range(self.num_hidden_layer+1)]\n", " for i in range(self.num_hidden_layer+1):\n", " for param in self.weights[i].keys():\n", "\n", " # g[i][param] = None\n", " ### START CODE HERE ###\n", " g[i][param] = delta * grads[i][param]\n", " ### END CODE HERE ###\n", "\n", " ### update the weights using self.optimizer (1 line)\n", " # self.weights = None\n", " \n", " ### START CODE HERE ###\n", " self.weights = self.optimizer.update_weights(self.weights, g)\n", " ### END CODE HERE ###\n", "\n", " ### update self.last_state and self.last_action (2 lines)\n", "\n", " ### START CODE HERE ###\n", " action = self.agent_policy(state)\n", " self.last_state = state\n", " self.last_action = action\n", " ### END CODE HERE ###\n", "\n", " return self.last_action\n", "\n", " def agent_end(self, reward):\n", " \"\"\"Run when the agent terminates.\n", " Args:\n", " reward (float): the reward the agent received for entering the\n", " terminal state.\n", " \"\"\"\n", "\n", " ### compute TD error (3 lines)\n", " # delta = None\n", "\n", " ### START CODE HERE ###\n", " last_state_vector = one_hot(self.last_state, self.num_states)\n", " last_value = get_value(last_state_vector, self.weights)\n", " \n", " delta = reward - last_value\n", " ### END CODE HERE ###\n", "\n", " ### Retrieve gradients (1 line)\n", " # grads = None\n", "\n", " ### START CODE HERE ###\n", " grads = get_gradient(last_state_vector, self.weights)\n", " ### END CODE HERE ###\n", "\n", " ### Compute g (1 line)\n", " g = [dict() for i in range(self.num_hidden_layer+1)]\n", " for i in range(self.num_hidden_layer+1):\n", " for param in self.weights[i].keys():\n", "\n", " # g[i][param] = None\n", " ### START CODE HERE ###\n", " g[i][param] = delta * grads[i][param]\n", " ### END CODE HERE ###\n", "\n", " ### update the weights using self.optimizer (1 line)\n", " # self.weights = None\n", " \n", " ### START CODE HERE ###\n", " self.weights = self.optimizer.update_weights(self.weights, g)\n", " ### END CODE HERE ###\n", "\n", " def agent_message(self, message):\n", " if message == 'get state value':\n", " state_value = np.zeros(self.num_states)\n", " for state in range(1, self.num_states + 1):\n", " s = one_hot(state, self.num_states)\n", " state_value[state - 1] = get_value(s, self.weights)\n", " return state_value" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "d1e29604e5409950b1b655aed4c68358", "grade": false, "grade_id": "cell-abac904ca1f57c77", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `agent_init()` function:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "ebabcf523ef713941511e9dffe085908", "grade": true, "grade_id": "cell-3061ed3fc479ee08", "locked": true, "points": 10, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "layer_size: [5 2 1]\n", "weights[0][\"W\"] shape: (5, 2)\n", "weights[0][\"b\"] shape: (1, 2)\n", "weights[1][\"W\"] shape: (2, 1)\n", "weights[1][\"b\"] shape: (1, 1) \n", "\n", "weights[0][\"W\"]\n", " [[ 1.11568467 0.25308164]\n", " [ 0.61900825 1.4172653 ]\n", " [ 1.18114738 -0.6180848 ]\n", " [ 0.60088868 -0.0957267 ]\n", " [-0.06528133 0.25968529]] \n", "\n", "weights[0][\"b\"]\n", " [[0.09110115 0.91976332]] \n", "\n", "weights[1][\"W\"]\n", " [[0.76103773]\n", " [0.12167502]] \n", "\n", "weights[1][\"b\"]\n", " [[0.44386323]] \n", "\n", "Passed the asserts!\n" ] } ], "source": [ "## Test Code for agent_init() ## \n", "\n", "agent_info = {\"num_states\": 5,\n", " \"num_hidden_layer\": 1,\n", " \"num_hidden_units\": 2,\n", " \"step_size\": 0.25,\n", " \"discount_factor\": 0.9,\n", " \"beta_m\": 0.9,\n", " \"beta_v\": 0.99,\n", " \"epsilon\": 0.0001,\n", " \"seed\": 0\n", " }\n", "\n", "test_agent = TDAgent()\n", "test_agent.agent_init(agent_info)\n", "\n", "print(\"layer_size: {}\".format(test_agent.layer_size))\n", "assert(np.allclose(test_agent.layer_size, np.array([agent_info[\"num_states\"], \n", " agent_info[\"num_hidden_units\"], \n", " 1])))\n", "\n", "print(\"weights[0][\\\"W\\\"] shape: {}\".format(test_agent.weights[0][\"W\"].shape))\n", "print(\"weights[0][\\\"b\\\"] shape: {}\".format(test_agent.weights[0][\"b\"].shape))\n", "print(\"weights[1][\\\"W\\\"] shape: {}\".format(test_agent.weights[1][\"W\"].shape))\n", "print(\"weights[1][\\\"b\\\"] shape: {}\".format(test_agent.weights[1][\"b\"].shape), \"\\n\")\n", "\n", "assert(test_agent.weights[0][\"W\"].shape == (agent_info[\"num_states\"], agent_info[\"num_hidden_units\"]))\n", "assert(test_agent.weights[0][\"b\"].shape == (1, agent_info[\"num_hidden_units\"]))\n", "assert(test_agent.weights[1][\"W\"].shape == (agent_info[\"num_hidden_units\"], 1))\n", "assert(test_agent.weights[1][\"b\"].shape == (1, 1))\n", "\n", "print(\"weights[0][\\\"W\\\"]\\n\", (test_agent.weights[0][\"W\"]), \"\\n\")\n", "print(\"weights[0][\\\"b\\\"]\\n\", (test_agent.weights[0][\"b\"]), \"\\n\")\n", "print(\"weights[1][\\\"W\\\"]\\n\", (test_agent.weights[1][\"W\"]), \"\\n\")\n", "print(\"weights[1][\\\"b\\\"]\\n\", (test_agent.weights[1][\"b\"]), \"\\n\")\n", "\n", "\n", "agent_weight_answer = np.load(\"asserts/agent_init_weights_1.npz\")\n", "assert(np.allclose(test_agent.weights[0][\"W\"], agent_weight_answer[\"W0\"]))\n", "assert(np.allclose(test_agent.weights[0][\"b\"], agent_weight_answer[\"b0\"]))\n", "assert(np.allclose(test_agent.weights[1][\"W\"], agent_weight_answer[\"W1\"]))\n", "assert(np.allclose(test_agent.weights[1][\"b\"], agent_weight_answer[\"b1\"]))\n", "\n", "print(\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "ca8f82862187a73c5e8dbeed3ea47d23", "grade": false, "grade_id": "cell-5cc06cee020ffd5e", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " layer_size: [5 2 1]\n", " weights[0][\"W\"] shape: (5, 2)\n", " weights[0][\"b\"] shape: (1, 2)\n", " weights[1][\"W\"] shape: (2, 1)\n", " weights[1][\"b\"] shape: (1, 1) \n", "\n", " weights[0][\"W\"]\n", " [[ 1.11568467 0.25308164]\n", " [ 0.61900825 1.4172653 ]\n", " [ 1.18114738 -0.6180848 ]\n", " [ 0.60088868 -0.0957267 ]\n", " [-0.06528133 0.25968529]] \n", "\n", " weights[0][\"b\"]\n", " [[0.09110115 0.91976332]] \n", "\n", " weights[1][\"W\"]\n", " [[0.76103773]\n", " [0.12167502]] \n", "\n", " weights[1][\"b\"]\n", " [[0.44386323]]\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "4314e3d66b268acdd963c634c64794f1", "grade": false, "grade_id": "cell-3b7fd0c37d10e8e5", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `agent_start()` function:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "a34fd74bd64b42a07cbc0290a4c1c5a6", "grade": true, "grade_id": "cell-8e05d0347d000b8f", "locked": true, "points": 5, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Agent state: 250\n", "Agent selected action: 1\n", "Passed the asserts!\n" ] } ], "source": [ "# Do not modify this cell!\n", "\n", "## Test Code for agent_start() ## \n", "\n", "agent_info = {\"num_states\": 500,\n", " \"num_hidden_layer\": 1,\n", " \"num_hidden_units\": 100,\n", " \"step_size\": 0.1,\n", " \"discount_factor\": 1.0,\n", " \"beta_m\": 0.9,\n", " \"beta_v\": 0.99,\n", " \"epsilon\": 0.0001,\n", " \"seed\": 10\n", " }\n", "\n", "# Suppose state = 250\n", "state = 250\n", "\n", "test_agent = TDAgent()\n", "test_agent.agent_init(agent_info)\n", "test_agent.agent_start(state)\n", "\n", "print(\"Agent state: {}\".format(test_agent.last_state))\n", "print(\"Agent selected action: {}\".format(test_agent.last_action))\n", "\n", "assert(test_agent.last_state == 250)\n", "assert(test_agent.last_action == 1)\n", "\n", "print(\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "f1dd9a1eb96e3c5873282e1fea477909", "grade": false, "grade_id": "cell-c88b673865769cf2", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " Agent state: 250\n", " Agent selected action: 1" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "6a30c17eee9895c80e343ba8740a56d3", "grade": false, "grade_id": "cell-f59067a616f5df77", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `agent_step()` function:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "0091506a29350418401174f6e09922d7", "grade": true, "grade_id": "cell-4d3501419a656e08", "locked": true, "points": 10, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "updated_weights[0][\"W\"]\n", " [[ 1.10893459 0.30763738]\n", " [ 0.63690565 1.14778865]\n", " [ 1.23397791 -0.48152743]\n", " [ 0.72792093 -0.15829832]\n", " [ 0.15021996 0.39822163]] \n", "\n", "updated_weights[0][\"b\"]\n", " [[0.29798822 0.96254535]] \n", "\n", "updated_weights[1][\"W\"]\n", " [[0.76628754]\n", " [0.11486511]] \n", "\n", "updated_weights[1][\"b\"]\n", " [[0.58530057]] \n", "\n", "Agent last state: 1\n", "Agent last action: 1 \n", "\n", "Passed the asserts!\n" ] } ], "source": [ "# Do not modify this cell!\n", "\n", "## Test Code for agent_step() ## \n", "\n", "agent_info = {\"num_states\": 5,\n", " \"num_hidden_layer\": 1,\n", " \"num_hidden_units\": 2,\n", " \"step_size\": 0.1,\n", " \"discount_factor\": 1.0,\n", " \"beta_m\": 0.9,\n", " \"beta_v\": 0.99,\n", " \"epsilon\": 0.0001,\n", " \"seed\": 0\n", " }\n", "\n", "test_agent = TDAgent()\n", "test_agent.agent_init(agent_info)\n", "\n", "# load initial weights\n", "agent_initial_weight = np.load(\"asserts/agent_step_initial_weights.npz\")\n", "test_agent.weights[0][\"W\"] = agent_initial_weight[\"W0\"]\n", "test_agent.weights[0][\"b\"] = agent_initial_weight[\"b0\"]\n", "test_agent.weights[1][\"W\"] = agent_initial_weight[\"W1\"]\n", "test_agent.weights[1][\"b\"] = agent_initial_weight[\"b1\"]\n", "\n", "# load m and v for the optimizer\n", "m_data = np.load(\"asserts/agent_step_initial_m.npz\")\n", "test_agent.optimizer.m[0][\"W\"] = m_data[\"W0\"]\n", "test_agent.optimizer.m[0][\"b\"] = m_data[\"b0\"]\n", "test_agent.optimizer.m[1][\"W\"] = m_data[\"W1\"]\n", "test_agent.optimizer.m[1][\"b\"] = m_data[\"b1\"]\n", "\n", "v_data = np.load(\"asserts/agent_step_initial_v.npz\")\n", "test_agent.optimizer.v[0][\"W\"] = v_data[\"W0\"]\n", "test_agent.optimizer.v[0][\"b\"] = v_data[\"b0\"]\n", "test_agent.optimizer.v[1][\"W\"] = v_data[\"W1\"]\n", "test_agent.optimizer.v[1][\"b\"] = v_data[\"b1\"]\n", "\n", "# Assume the agent started at State 3\n", "start_state = 3\n", "test_agent.agent_start(start_state)\n", "\n", "# Assume the reward was 10.0 and the next state observed was State 1\n", "reward = 10.0\n", "next_state = 1\n", "test_agent.agent_step(reward, next_state)\n", "\n", "# updated weights asserts\n", "print(\"updated_weights[0][\\\"W\\\"]\\n\", test_agent.weights[0][\"W\"], \"\\n\")\n", "print(\"updated_weights[0][\\\"b\\\"]\\n\", test_agent.weights[0][\"b\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"W\\\"]\\n\", test_agent.weights[1][\"W\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"b\\\"]\\n\", test_agent.weights[1][\"b\"], \"\\n\")\n", "\n", "agent_updated_weight_answer = np.load(\"asserts/agent_step_updated_weights.npz\")\n", "assert(np.allclose(test_agent.weights[0][\"W\"], agent_updated_weight_answer[\"W0\"]))\n", "assert(np.allclose(test_agent.weights[0][\"b\"], agent_updated_weight_answer[\"b0\"]))\n", "assert(np.allclose(test_agent.weights[1][\"W\"], agent_updated_weight_answer[\"W1\"]))\n", "assert(np.allclose(test_agent.weights[1][\"b\"], agent_updated_weight_answer[\"b1\"]))\n", "\n", "# last_state and last_action assert\n", "print(\"Agent last state:\", test_agent.last_state)\n", "print(\"Agent last action:\", test_agent.last_action, \"\\n\")\n", "\n", "assert(test_agent.last_state == 1)\n", "assert(test_agent.last_action == 1)\n", "\n", "print (\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "bfc86292e1cc7edb9cfe97addd6aacff", "grade": false, "grade_id": "cell-7063c509f6f48ff0", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output**:\n", "\n", " updated_weights[0][\"W\"]\n", " [[ 1.10893459 0.30763738]\n", " [ 0.63690565 1.14778865]\n", " [ 1.23397791 -0.48152743]\n", " [ 0.72792093 -0.15829832]\n", " [ 0.15021996 0.39822163]] \n", "\n", " updated_weights[0][\"b\"]\n", " [[0.29798822 0.96254535]] \n", "\n", " updated_weights[1][\"W\"]\n", " [[0.76628754]\n", " [0.11486511]] \n", "\n", " updated_weights[1][\"b\"]\n", " [[0.58530057]] \n", "\n", " Agent last state: 1\n", " Agent last action: 1 " ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "5ba003cd2b9ee7270177c75652db5d07", "grade": false, "grade_id": "cell-d734c2a22fa11660", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Run the following code to test your implementation of the `agent_end()` function:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "bd232cf2650f88080cdfc39a0d6732c1", "grade": true, "grade_id": "cell-a8031e265fdf211d", "locked": true, "points": 5, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "updated_weights[0][\"W\"]\n", " [[ 1.10893459 0.30763738]\n", " [ 0.63690565 1.14778865]\n", " [ 1.17531054 -0.51043162]\n", " [ 0.75062903 -0.13736817]\n", " [ 0.15021996 0.39822163]] \n", "\n", "updated_weights[0][\"b\"]\n", " [[0.30846523 0.95937346]] \n", "\n", "updated_weights[1][\"W\"]\n", " [[0.68861703]\n", " [0.15986364]] \n", "\n", "updated_weights[1][\"b\"]\n", " [[0.586074]] \n", "\n", "Passed the asserts!\n" ] } ], "source": [ "# Do not modify this cell!\n", "\n", "## Test Code for agent_end() ## \n", "\n", "agent_info = {\"num_states\": 5,\n", " \"num_hidden_layer\": 1,\n", " \"num_hidden_units\": 2,\n", " \"step_size\": 0.1,\n", " \"discount_factor\": 1.0,\n", " \"beta_m\": 0.9,\n", " \"beta_v\": 0.99,\n", " \"epsilon\": 0.0001,\n", " \"seed\": 0\n", " }\n", "\n", "test_agent = TDAgent()\n", "test_agent.agent_init(agent_info)\n", "\n", "# load initial weights\n", "agent_initial_weight = np.load(\"asserts/agent_end_initial_weights.npz\")\n", "test_agent.weights[0][\"W\"] = agent_initial_weight[\"W0\"]\n", "test_agent.weights[0][\"b\"] = agent_initial_weight[\"b0\"]\n", "test_agent.weights[1][\"W\"] = agent_initial_weight[\"W1\"]\n", "test_agent.weights[1][\"b\"] = agent_initial_weight[\"b1\"]\n", "\n", "# load m and v for the optimizer\n", "m_data = np.load(\"asserts/agent_step_initial_m.npz\")\n", "test_agent.optimizer.m[0][\"W\"] = m_data[\"W0\"]\n", "test_agent.optimizer.m[0][\"b\"] = m_data[\"b0\"]\n", "test_agent.optimizer.m[1][\"W\"] = m_data[\"W1\"]\n", "test_agent.optimizer.m[1][\"b\"] = m_data[\"b1\"]\n", "\n", "v_data = np.load(\"asserts/agent_step_initial_v.npz\")\n", "test_agent.optimizer.v[0][\"W\"] = v_data[\"W0\"]\n", "test_agent.optimizer.v[0][\"b\"] = v_data[\"b0\"]\n", "test_agent.optimizer.v[1][\"W\"] = v_data[\"W1\"]\n", "test_agent.optimizer.v[1][\"b\"] = v_data[\"b1\"]\n", "\n", "# Assume the agent started at State 4\n", "start_state = 4\n", "test_agent.agent_start(start_state)\n", "\n", "# Assume the reward was 10.0 and reached the terminal state\n", "reward = 10.0\n", "test_agent.agent_end(reward)\n", "\n", "# updated weights asserts\n", "print(\"updated_weights[0][\\\"W\\\"]\\n\", test_agent.weights[0][\"W\"], \"\\n\")\n", "print(\"updated_weights[0][\\\"b\\\"]\\n\", test_agent.weights[0][\"b\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"W\\\"]\\n\", test_agent.weights[1][\"W\"], \"\\n\")\n", "print(\"updated_weights[1][\\\"b\\\"]\\n\", test_agent.weights[1][\"b\"], \"\\n\")\n", "\n", "agent_updated_weight_answer = np.load(\"asserts/agent_end_updated_weights.npz\")\n", "assert(np.allclose(test_agent.weights[0][\"W\"], agent_updated_weight_answer[\"W0\"]))\n", "assert(np.allclose(test_agent.weights[0][\"b\"], agent_updated_weight_answer[\"b0\"]))\n", "assert(np.allclose(test_agent.weights[1][\"W\"], agent_updated_weight_answer[\"W1\"]))\n", "assert(np.allclose(test_agent.weights[1][\"b\"], agent_updated_weight_answer[\"b1\"]))\n", "\n", "print (\"Passed the asserts!\")\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "dd572a2b2edac4a1a09af8451f1ecc80", "grade": false, "grade_id": "cell-cf7301495f685eaf", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "**Expected output:**\n", "\n", " updated_weights[0][\"W\"]\n", " [[ 1.10893459 0.30763738]\n", " [ 0.63690565 1.14778865]\n", " [ 1.17531054 -0.51043162]\n", " [ 0.75062903 -0.13736817]\n", " [ 0.15021996 0.39822163]] \n", "\n", " updated_weights[0][\"b\"]\n", " [[0.30846523 0.95937346]] \n", "\n", " updated_weights[1][\"W\"]\n", " [[0.68861703]\n", " [0.15986364]] \n", "\n", " updated_weights[1][\"b\"]\n", " [[0.586074]] " ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "d4a06a16ec5c30a856e81579136e369b", "grade": false, "grade_id": "cell-f8442d4f392c0918", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## Section 2 - Run Experiment\n", "\n", "Now that you implemented the agent, we can run the experiment. Similar to Course 3 Programming Assignment 1, we will plot the learned state value function and the learning curve of the TD agent. To plot the learning curve, we use Root Mean Squared Value Error (RMSVE). " ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "9fcc43fdb0c69e8c5ac96f8a13fc07ed", "grade": false, "grade_id": "cell-137829b5646a5222", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## 2-1: Run Experiment for Semi-gradient TD with a Neural Network\n", "\n", "We have already provided you the experiment/plot code, so you can go ahead and run the two cells below.\n", "\n", "Note that running the cell below will take **approximately 12 minutes**." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "05de82f9ea256714e4af3bb2a08e8870", "grade": false, "grade_id": "cell-2a279281a516eb2c", "locked": true, "schema_version": 1, "solution": false } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Setting - Neural Network with 100 hidden units\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 20/20 [10:29<00:00, 31.45s/it]\n" ] }, { "data": { "image/png": "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vR4/q1mzZlG7dm1CQkKIjIzMtatFVvHx8XTu3Jny5ctTvnx5+vTpw/nz513r33rrLapVq0ZwcDDVq1dnzJgx2WKfNGkSN998M0FBQSQkJNCyZUteffVVOnXqREhICNWrV2fOnDke5/31119p2LAh4eHh1K5dmxkzZnisnzx5MtWrVyc0NJRu3bqRnp6e57WsW7fO6+/zb7/9RtmyZcnMzHRte/78eYKDg1m5cmW+3yshhBBCiOKgSpUqjBgxgu7du/Piiy+itc62zfr16wkJCXE93E6aNAmlFEuXLgXgxIkTmM1m11Sazu6+R48e5b777sNms7mqQKZOneo67tKlS7n55psJCQmhbdu2HDt2LMc4L7XN6a1bRM+ePenduzeQc1u3IG19b5RSjB8/3vU8c8cdd7iejyD3Z5Pvv/+e9957j2XLlrnev3379nHLLbcwc+ZMwHjGCggIoEePHq5j3nfffXz44YeA8Wzx0ksvUalSJcqUKUOHDh2Ij4/3eF9ffvllOnToQGhoKB9//HG2a1i0aBHly5dn/vz5+b7uAtNay1c+vxo2bKi92bZtm9flV6Mff/xR79u3T9vtdv3vv//qiIgIPWHCBNd6QNeuXVvv3r1bp6am6ueff17Xrl1bW61WrbXWLVq00MHBwXrx4sXaYrHo6dOna39/f71//36ttdZDhgzRZrNZf/TRRzojI0OnpKToxMREHRERoYcNG6bT09P1tm3bdNWqVfUHH3zgOu8777yj69atq1NSUnTPnj11dHS065xLly7VZrPZtW2PHj10QECAnj9/vrbZbPqLL77QJUqU0O3bt9eHDh3SKSkpOjo6Wj/zzDP5vu4qVarob7/91uO9GjhwoG7SpIneu3evtlqteuLEibp06dL6zJkzXt/bRx55RL/00kuu13v37tVms1kfOHBAa631woUL9ZYtW7TNZtO7d+/WtWvX1m+++aZr+xYtWujhw4drrbXev3+/BvShQ4dc67/55htdvXp11+vHH39ct2vXTh8/flxnZGTowYMH65o1a+rMzEydkpKifXx8dFxcnNZa6+TkZL1ixQqvcWc9d1pamq5evbp+5513dGpqqj5z5oy+77779FNPPeXa/ttvv9VHjhzRdrtdx8XF6YCAAL1o0SKP2Fu1aqWPHTumMzIytNVq1S1atNClS5fWq1at0jabTY8ePVqH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MHHiRDp06ECZMmUKFLcQl43NClt/hfVT895WCCGEuEK+//57KlSoQM2aNb2ub9OmDf/88w8///wzbdq0wWw207x5c8aPH8/x48e5++67ve5X0LbjxcqrzRkcHJxt7Ar3dri3eAva1i+o/DybeHv/goODuf3225k1axYHDx6kSZMmxMTEsGTJEo9qmLJly+Lv7+/Rtk5OTiYhIcHjmc3bOSIjI1m8eDEDBgzg22+/LZTrzY0kMIqR5ORk7HY7ZcuWxdfXlzVr1nj9Jfvkk0/Yu3cv6enpvPnmm1SrVo3bb7/dtf7XX38lLi4Om83GzJkz+eeff+jSpUuO523Xrh3p6em89957ZGZmsnPnTkaNGkWvXr0AsNvtPPnkk8TExDB+/HgGDBjAo48+WqBBby71usuVK+cqKwOj2uCll17itddecy1PTk7m999/9/ofmNNTTz3Fb7/9xqhRo3j88ccJCAgAjP/U0tPTKVmyJIGBgWzbto1x48bleJwyZcpQpUoVJk+ejM1mY/PmzXz99deu9RERETzxxBP069ePI0eOAJCYmMgvv/xCcnIyJ06c4KeffuLcuXOYzWZXNUVu3XKcHnjgASwWC++99x7nz59Ha82RI0dco00nJSVhNpspW7YsSikWLFjgMZDrxXr55ZcZM2YMK1euxGazkZmZyfr1612lc927d+fHH38kLi4Oq9XK9OnTPUracpLX73OfPn2YPHky06dP55lnnrnk6xDikqWegVWfwNj6MLsHrJsMhdQAEkIIIS7WoUOHGDJkCFOmTGHs2LEelQDuqlWrRqVKlRgzZgxt2rQBoHXr1nz44YfceeedroqIrMqVK4fNZsvXB1SXIq8256233kpCQgLz58/Hbrfzyy+/sGLFilyPWdC2fkHl59mkXLlyxMfHewxQD0ZFzMcff0yLFi0wm81ER0ezcuVK1q1b50pgOLvlv/POOxw9epTU1FReffVVatWqla8u202bNmXx4sW89tprjB8/vtCu2xtJYBQjtWvX5t133+Whhx4iPDyc999/3/VpvbvevXvTsWNHypYty8aNG5kzZ47Hg2+vXr0YPXo0YWFhDBs2jJ9//tmj3CirsLAwFi9eTGxsLJGRkdxzzz10796dV155BYDhw4dz9OhR1y/722+/TcWKFenbt+8Vu+5BgwYxffp0SpYsyX333Qfg2uehhx4i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5s0cPXqUl156qUDHnDx5Mp9++innzp2jTZs29OjRw7Xuq6++YtSoUcyYMYOzZ88ycuRIOnbsyJ49e7jzzjuZMGEC1apVIzk5meTkZFq2bElMTIzrvd+5cyc2m42NGzeSnJwMGD+fmJgYAD788EOmTZvGwoULOXbsGM2bN6dNmzYkJV2YUWf27Nnce++9nDx5ko8//tgj9rlz5/LAAw/w9ddfS/JCXN9O74WfnoEv7oL9K6DlW/DyJmj1doGSFwBpmRcSGKmZVqavOchfe08XdsRCCCGEENc8SWCIi1KxYkUATp/23siOiYnxSGCMGDGCuLg4tNbExsYSHR2NyVSwX78BAwZQuXJl/P396dmzJ+vWrct1+1atWvHggw9iMpno2LEj4eHhbNiwAYAvvviCRx55hIceegiz2UytWrV44YUXmDZtWoFieu2116hevTpmsxl/f3+aNWtG48aN8fHxoVy5crz++usFHi/k2WefpU6dOpjNZnr37s2ePXs4d+4cAJ9++imDBw+mfv36mEwm7r//fqKjo5k1a1aOx4uJiWH16tWkpaURGxvLPffcw+23387y5cuJj49n//79REdHA/DNN9/wxhtvUKtWLfz9/Rk8eDBms5kFCxa4jtesWTMee+wxzGYzQUFBruWffvopL7zwAosWLeLee+8t0DULcc04dwTmvQTjGhvdRZq9DC9thJZvQGB43vt74V6BceBUKuP+2MPsdYcKK2IhhBBCiOuGdCERF8U5yGbp0qW9ro+JieHpp5/m7Nmz7Nq1i44dOzJs2DA2btxIbGwsTzzxRIHPWb58edf3JUqU4Pz58/nePus++/fv548//uDnn392rbfb7VSqVKlAMUVFRXm8Xr9+PW+99RYbN24kNTUVrbWr0iG/sl4nwPnz5wkLC2P//v08//zzHmOPWK1WV0LJmzp16lCqVClWrlxJbGwsjz76KIcPH2bJkiUcP36chg0buroBObulOJlMJqKiojh06MLDVNZrBuO9GzlyJH379qVBgwYFul4hrgkpp4zpUP+ZCNoOjXtD81chJPKSD+2ewDiXZsFis2Oxy8BvQgghhBBZSQJDXJTvv/+ewMBA7rjjDq/r7777bk6fPs24ceNo3rw5vr6+xMTE8Msvv7B27VomT57sdb+CVmVcrCpVqvD000/z+eefe10fHByMzWYjIyMDf39/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yK/Bnb7H9ysiIiIiIiKytynAaIes4MIRE1SkJzkwjFZWYFRvha/+CvOeBwwYewUc/jtI7dSGdywiIiIiIiKydynAaIes4CK2hcRmM0h3O1oWYNSVw6xHYPYT4PPAyAvgiBshs+veuGURERERERGRvUoBRjvktCowbE0Hap47tjvDirKaf7KnAuY8Dd88Cp5yOOB0mHArdOq7t25XREREREREZK9TgNEOhSswbE0rLW45flD8J9WVw5wnYfbjwRCj//Ew8VYoHLY3b1VERERERERkn1CA0Q45Q60jTnsLVprWbg+2icx5EuorYeBJMP4G6DJiL9+liIiIiIiIyL6jAKMdsjczxDNKTRnMfizYLuKtgkGTg8GFKi5ERERERERkP6QAox1yNLNGFYCKjcGKi3kvQEMtDJ4SHM5ZMGQf36WIiIiIiIjIvqMAox2KO8Rz61L4+mFY9G8wTTjgNDj8OshvZiaGiIiIiIiIyH5EAUY71LhG1QbrvoGv/wbLPwRHMoz5BYy7CrJ7JPguRURERERERPYdBRjtkNNmcrRtHsfOuQ92LIDkHJhwCxx4KaTmJvr2RERERERERPY5BRjtiacSFrzCpK8eZ4prPTWeIjj+fhh5AbhSEn13IiIiIiIiIgmjAKM9KFsFc56CBa+Atxpv7khu3HEKx07+JZNHqVVERERERERERAFGopgmrJoeDC5WfAw2BxxwOoy9nPlVRUx7cR7HO5yJvksRERERERGRdkEBRiJsXQr/vgi2LYPUPDjiJhhzCaQXAOBYXhp8a7Ml8i5FRERERERE2g0FGImQ2Q3S8uGwa4PrUB3uqIet9alOuxHv2SIiIiIiIiL/cxRgJII7DS7+b7MPuxwRa1RFREREREREBP2E3A4N75bFDccOYGyvnETfioiIiIiIiEi7oAqMdshpt3HVxL6Jvg0RERERERGRdkMVGCIiIiIiIiLS7inAEBEREREREZF2TwGGiIiIiIiIiLR7CjBEREREREREpN1TgCEiIiIiIiIi7Z4CDBERERERERFp9xRgiIiIiIiIiEi7pwBDRERERERERNo9BRgiIiIiIiIi0u4pwBARERERERGRdk8BhoiIiIiIiIi0ewowRERERERERKTdU4AhIiIiIiIiIu2eAgwRERERERERafc6fIBhGMazhmH8aBiGzzCMZ1twvt0wjPsNwyg1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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "text/plain": [ "'/home/jovyan/work/week2/results.zip'" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Do not modify this cell!\n", "\n", "true_state_val = np.load('data/true_V.npy') \n", "state_distribution = np.load('data/state_distribution.npy')\n", "\n", "def calc_RMSVE(learned_state_val):\n", " assert(len(true_state_val) == len(learned_state_val) == len(state_distribution))\n", " MSVE = np.sum(np.multiply(state_distribution, np.square(true_state_val - learned_state_val)))\n", " RMSVE = np.sqrt(MSVE)\n", " return RMSVE\n", "\n", "# Define function to run experiment\n", "def run_experiment(environment, agent, environment_parameters, agent_parameters, experiment_parameters):\n", " \n", " rl_glue = RLGlue(environment, agent)\n", " \n", " # save rmsve at the end of each episode\n", " agent_rmsve = np.zeros((experiment_parameters[\"num_runs\"], \n", " int(experiment_parameters[\"num_episodes\"]/experiment_parameters[\"episode_eval_frequency\"]) + 1))\n", " \n", " # save learned state value at the end of each run\n", " agent_state_val = np.zeros((experiment_parameters[\"num_runs\"], \n", " environment_parameters[\"num_states\"]))\n", "\n", " env_info = {\"num_states\": environment_parameters[\"num_states\"],\n", " \"start_state\": environment_parameters[\"start_state\"],\n", " \"left_terminal_state\": environment_parameters[\"left_terminal_state\"],\n", " \"right_terminal_state\": environment_parameters[\"right_terminal_state\"]}\n", "\n", " agent_info = {\"num_states\": environment_parameters[\"num_states\"],\n", " \"num_hidden_layer\": agent_parameters[\"num_hidden_layer\"],\n", " \"num_hidden_units\": agent_parameters[\"num_hidden_units\"],\n", " \"step_size\": agent_parameters[\"step_size\"],\n", " \"discount_factor\": environment_parameters[\"discount_factor\"],\n", " \"beta_m\": agent_parameters[\"beta_m\"],\n", " \"beta_v\": agent_parameters[\"beta_v\"],\n", " \"epsilon\": agent_parameters[\"epsilon\"]\n", " }\n", " \n", " print('Setting - Neural Network with 100 hidden units')\n", " os.system('sleep 1')\n", "\n", " # one agent setting\n", " for run in tqdm(range(1, experiment_parameters[\"num_runs\"]+1)):\n", " env_info[\"seed\"] = run\n", " agent_info[\"seed\"] = run\n", " rl_glue.rl_init(agent_info, env_info)\n", " \n", " # Compute initial RMSVE before training\n", " current_V = rl_glue.rl_agent_message(\"get state value\")\n", " agent_rmsve[run-1, 0] = calc_RMSVE(current_V)\n", " \n", " for episode in range(1, experiment_parameters[\"num_episodes\"]+1):\n", " # run episode\n", " rl_glue.rl_episode(0) # no step limit\n", "\n", " if episode % experiment_parameters[\"episode_eval_frequency\"] == 0:\n", " current_V = rl_glue.rl_agent_message(\"get state value\")\n", " agent_rmsve[run-1, int(episode/experiment_parameters[\"episode_eval_frequency\"])] = calc_RMSVE(current_V)\n", " elif episode == experiment_parameters[\"num_episodes\"]: # if last episode\n", " current_V = rl_glue.rl_agent_message(\"get state value\")\n", "\n", " agent_state_val[run-1, :] = current_V\n", "\n", " save_name = \"{}\".format(rl_glue.agent.name).replace('.','')\n", " \n", " if not os.path.exists('results'):\n", " os.makedirs('results')\n", " \n", " # save avg. state value\n", " np.save(\"results/V_{}\".format(save_name), agent_state_val)\n", "\n", " # save avg. rmsve\n", " np.savez(\"results/RMSVE_{}\".format(save_name), rmsve = agent_rmsve,\n", " eval_freq = experiment_parameters[\"episode_eval_frequency\"],\n", " num_episodes = experiment_parameters[\"num_episodes\"])\n", "\n", "\n", "# Run Experiment\n", "\n", "# Experiment parameters\n", "experiment_parameters = {\n", " \"num_runs\" : 20,\n", " \"num_episodes\" : 1000,\n", " \"episode_eval_frequency\" : 10 # evaluate every 10 episode\n", "}\n", "\n", "# Environment parameters\n", "environment_parameters = {\n", " \"num_states\" : 500,\n", " \"start_state\" : 250,\n", " \"left_terminal_state\" : 0,\n", " \"right_terminal_state\" : 501,\n", " \"discount_factor\" : 1.0\n", "}\n", "\n", "# Agent parameters\n", "agent_parameters = {\n", " \"num_hidden_layer\": 1,\n", " \"num_hidden_units\": 100,\n", " \"step_size\": 0.001,\n", " \"beta_m\": 0.9,\n", " \"beta_v\": 0.999,\n", " \"epsilon\": 0.0001,\n", "}\n", "\n", "current_env = RandomWalkEnvironment\n", "current_agent = TDAgent\n", "\n", "# run experiment\n", "run_experiment(current_env, current_agent, environment_parameters, agent_parameters, experiment_parameters)\n", "\n", "# plot result\n", "plot_script.plot_result([\"td_agent\"])\n", "\n", "shutil.make_archive('results', 'zip', 'results')" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "0db92bf291ef2988b3f6beb6d1941fad", "grade": false, "grade_id": "cell-bea11a6380a836e3", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "You plotted the learning curve for 1000 episodes. As you can see the RMSVE is still decreasing. Here we provide the pre-computed result for 5000 episodes and 20 runs so that you can see the performance of semi-gradient TD with a neural network after being trained for a long time.\n", "\n", "![](nn_5000_episodes.png)\n" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "46d058a429acfa3122e14091d263670f", "grade": false, "grade_id": "cell-95a7ccf1f6e2737a", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "Does semi-gradient TD with a neural network find a good approximation within 5000 episodes? \n", "\n", "As you may remember from the previous assignment, semi-gradient TD with 10-state aggregation converged within 100 episodes. Why is TD with a neural network slower?\n", "\n", "Would it be faster if we decrease the number of hidden units? Or what about if we increase the number of hidden units?" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "17e4631fea0f972c3204ed058931e0bc", "grade": false, "grade_id": "cell-f2aec56a76cc3b45", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "## 2-2: Compare Performance of Semi-gradient TD with a Neural Network and Semi-gradient TD with Tile-coding\n", "\n", "In this section, we compare the performance of semi-gradient TD with a Neural Network and semi-gradient TD with tile-coding. Tile-coding is a kind of coarse coding that uses multiple overlapping partitions of the state space to produce features. For tile-coding, we used 50 tilings each with 6 tiles. We set the step-size for semi-gradient TD with tile-coding to $\\frac{0.1}{\\text{# tilings}}$. See the figure below for the comparison between semi-gradient TD with tile-coding and semi-gradient TD with a neural network and Adam algorithm. This result is for 5000 episodes and 20 runs:\n", "![](nn_vs_tc.png)" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "a499a96e2a5d29d0fe94e360ad2b9947", "grade": false, "grade_id": "cell-100dd59ca8cf87fd", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "How are the results?\n", "\n", "Semi-gradient TD with tile-coding is much faster than semi-gradient TD with a neural network. Why?\n", "\n", "Which method has a lower RMSVE at the end of 5000 episodes?" ] }, { "cell_type": "markdown", "metadata": { "deletable": false, "editable": false, "nbgrader": { "checksum": "f0df5ef7680decccecfb89bd404d0e19", "grade": false, "grade_id": "cell-cc391c0e03ced2bb", "locked": true, "schema_version": 1, "solution": false } }, "source": [ "### Wrapping up! \n", "\n", "You have successfully implemented Course 3 Programming Assignment 2.\n", "\n", "You have implemented **semi-gradient TD with a Neural Network and Adam algorithm** in 500-state Random Walk. \n", "\n", "You also compared semi-gradient TD with a neural network and semi-gradient TD with tile-coding. \n", "\n", "From the experiments and lectures, you should be more familiar with some of the strengths and weaknesses of using neural networks as the function approximator for an RL agent. On one hand, neural networks are powerful function approximators capable of representing a wide class of functions. They are also capable of producing features without exclusively relying on hand-crafted mechanisms. On the other hand, compared to a linear function approximator with tile-coding, neural networks can be less sample efficient. When implementing your own Reinforcement Learning agents, you may consider these strengths and weaknesses to choose the proper function approximator for your problems.\n", "\n", "---\n", "\n", "**Note**: Apart from using the 'Submit' button in the notebook, you have to submit an additional `zip` file containing the 'npy' files that were generated from running the experiment cells. To do so:\n", "\n", "1. Generate the zip file by running the experiment cells in the notebook. On the top of the notebook, navigate to 'File->Open' to open the directory view of this assignment. Select \"results.zip\" and click on \"Download\". Alternatively, you can download just the results folder and run \"zip -jr results.zip results/\" (The flag 'j' is required by the grader!).\n", "2. Go to the \"My submission\" tab and click on \"+ Create submission\".\n", "3. Click on \"C3M2 Data-file Grader\" and upload your `results.zip`.\n", "\n", "**These account for 40% of the marks, so don't forget to do so!**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "coursera": { "course_slug": "prediction-control-function-approximation", "graded_item_id": "ZJrJN", "launcher_item_id": "jSYQa" }, "kernelspec": { "display_name": "Python 3", "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.7.1" } }, "nbformat": 4, "nbformat_minor": 2 }