<commit_before>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deep Recurrent Q-Network \n",
    "This notebook provides an example implementation of a Deep Recurrent Q-Network which can solve Partially Observable Markov Decision Processes. To learn more about DRQNs, see my blog post on them here: https://medium.com/p/68463e9aeefc .\n",
    "\n",
    "For more reinforcment learning tutorials, as well as the additional required `gridworld.py` and `helper.py` see:\n",
    "https://github.com/awjuliani/DeepRL-Agents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import random\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "import scipy.misc\n",
    "import os\n",
    "import csv\n",
    "import itertools\n",
    "import tensorflow.contrib.slim as slim\n",
    "%matplotlib inline\n",
    "\n",
    "from helper import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load the game environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from gridworld import gameEnv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Feel free to adjust the size of the gridworld. Making it smaller (adjusting `size`) provides an easier task for our DRQN agent, while making the world larger increases the challenge.\n",
    "\n",
    "Initializing the Gridworld with `True` limits the field of view, resulting in a partially observable MDP. Initializing it with `False` provides the agent with the entire environment, resulting in a fully MDP."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "env = gameEnv(partial=False,size=9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "env = gameEnv(partial=True,size=9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Above are examples of a starting environment in our simple game. The agent controls the blue square, and can move up, down, left, or right. The goal is to move to the green squares (for +1 reward) and avoid the red squares (for -1 reward). When the agent moves through a green or red square, it is randomly moved to a new place in the environment."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementing the network itself"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Qnetwork():\n",
    "    def __init__(self,h_size,rnn_cell,myScope):\n",
    "        #The network recieves a frame from the game, flattened into an array.\n",
    "        #It then resizes it and processes it through four convolutional layers.\n",
    "        self.scalarInput =  tf.placeholder(shape=[None,21168],dtype=tf.float32)\n",
    "        self.imageIn = tf.reshape(self.scalarInput,shape=[-1,84,84,3])\n",
    "        self.conv1 = slim.convolution2d( \\\n",
    "            inputs=self.imageIn,num_outputs=32,\\\n",
    "            kernel_size=[8,8],stride=[4,4],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv1')\n",
    "        self.conv2 = slim.convolution2d( \\\n",
    "            inputs=self.conv1,num_outputs=64,\\\n",
    "            kernel_size=[4,4],stride=[2,2],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv2')\n",
    "        self.conv3 = slim.convolution2d( \\\n",
    "            inputs=self.conv2,num_outputs=64,\\\n",
    "            kernel_size=[3,3],stride=[1,1],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv3')\n",
    "        self.conv4 = slim.convolution2d( \\\n",
    "            inputs=self.conv3,num_outputs=512,\\\n",
    "            kernel_size=[7,7],stride=[1,1],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv4')\n",
    "        \n",
    "        self.trainLength = tf.placeholder(dtype=tf.int32)\n",
    "        #We take the output from the final convolutional layer and send it to a recurrent layer.\n",
    "        #The input must be reshaped into [batch x trace x units] for rnn processing, \n",
    "        #and then returned to [batch x units] when sent through the upper levles.\n",
    "        self.batch_size = tf.placeholder(dtype=tf.int32)\n",
    "        self.convFlat = tf.reshape(slim.flatten(self.conv4),[self.batch_size,self.trainLength,h_size])\n",
    "        self.state_in = cell.zero_state(self.batch_size, tf.float32)\n",
    "        self.rnn,self.rnn_state = tf.nn.dynamic_rnn(\\\n",
    "                inputs=self.convFlat,cell=rnn_cell,dtype=tf.float32,initial_state=self.state_in,scope=myScope+'_rnn')\n",
    "        self.rnn = tf.reshape(self.rnn,shape=[-1,h_size])\n",
    "        #The output from the recurrent player is then split into separate Value and Advantage streams\n",
    "        self.streamA,self.streamV = tf.split(1,2,self.rnn)\n",
    "        self.AW = tf.Variable(tf.random_normal([h_size/2,4]))\n",
    "        self.VW = tf.Variable(tf.random_normal([h_size/2,1]))\n",
    "        self.Advantage = tf.matmul(self.streamA,self.AW)\n",
    "        self.Value = tf.matmul(self.streamV,self.VW)\n",
    "        \n",
    "        self.salience = tf.gradients(self.Advantage,self.imageIn)\n",
    "        #Then combine them together to get our final Q-values.\n",
    "        self.Qout = self.Value + tf.sub(self.Advantage,tf.reduce_mean(self.Advantage,reduction_indices=1,keep_dims=True))\n",
    "        self.predict = tf.argmax(self.Qout,1)\n",
    "        \n",
    "        #Below we obtain the loss by taking the sum of squares difference between the target and prediction Q values.\n",
    "        self.targetQ = tf.placeholder(shape=[None],dtype=tf.float32)\n",
    "        self.actions = tf.placeholder(shape=[None],dtype=tf.int32)\n",
    "        self.actions_onehot = tf.one_hot(self.actions,4,dtype=tf.float32)\n",
    "        \n",
    "        self.Q = tf.reduce_sum(tf.mul(self.Qout, self.actions_onehot), reduction_indices=1)\n",
    "        \n",
    "        self.td_error = tf.square(self.targetQ - self.Q)\n",
    "        \n",
    "        #In order to only propogate accurate gradients through the network, we will mask the first\n",
    "        #half of the losses for each trace as per Lample & Chatlot 2016\n",
    "        self.maskA = tf.zeros([self.batch_size,self.trainLength/2])\n",
    "        self.maskB = tf.ones([self.batch_size,self.trainLength/2])\n",
    "        self.mask = tf.concat(1,[self.maskA,self.maskB])\n",
    "        self.mask = tf.reshape(self.mask,[-1])\n",
    "        self.loss = tf.reduce_mean(self.td_error * self.mask)\n",
    "        \n",
    "        self.trainer = tf.train.AdamOptimizer(learning_rate=0.0001)\n",
    "        self.updateModel = self.trainer.minimize(self.loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Experience Replay"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These classes allow us to store experies and sample then randomly to train the network.\n",
    "Episode buffer stores experiences for each individal episode.\n",
    "Experience buffer stores entire episodes of experience, and sample() allows us to get training batches needed from the network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class experience_buffer():\n",
    "    def __init__(self, buffer_size = 1000):\n",
    "        self.buffer = []\n",
    "        self.buffer_size = buffer_size\n",
    "    \n",
    "    def add(self,experience):\n",
    "        if len(self.buffer) + 1 >= self.buffer_size:\n",
    "            self.buffer[0:(1+len(self.buffer))-self.buffer_size] = []\n",
    "        self.buffer.append(experience)\n",
    "            \n",
    "    def sample(self,batch_size,trace_length):\n",
    "        sampled_episodes = random.sample(self.buffer,batch_size)\n",
    "        sampledTraces = []\n",
    "        for episode in sampled_episodes:\n",
    "            point = np.random.randint(0,len(episode)-trace_length)\n",
    "            sampledTraces.append(episode[point:point+trace_length])\n",
    "        sampledTraces = np.array(sampledTraces)\n",
    "        return np.reshape(sampledTraces,[batch_size*trace_length,5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training the network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#Setting the training parameters\n",
    "batch_size = 4 #How many experience traces to use for each training step.\n",
    "trace_length = 8 #How long each experience trace will be when training\n",
    "update_freq = 5 #How often to perform a training step.\n",
    "y = .99 #Discount factor on the target Q-values\n",
    "startE = 1 #Starting chance of random action\n",
    "endE = 0.1 #Final chance of random action\n",
    "anneling_steps = 10000 #How many steps of training to reduce startE to endE.\n",<commit_msg>Fixed exp buffer not sampling final step in episodes.<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deep Recurrent Q-Network \n",
    "This notebook provides an example implementation of a Deep Recurrent Q-Network which can solve Partially Observable Markov Decision Processes. To learn more about DRQNs, see my blog post on them here: https://medium.com/p/68463e9aeefc .\n",
    "\n",
    "For more reinforcment learning tutorials, as well as the additional required `gridworld.py` and `helper.py` see:\n",
    "https://github.com/awjuliani/DeepRL-Agents"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import random\n",
    "import tensorflow as tf\n",
    "import matplotlib.pyplot as plt\n",
    "import scipy.misc\n",
    "import os\n",
    "import csv\n",
    "import itertools\n",
    "import tensorflow.contrib.slim as slim\n",
    "%matplotlib inline\n",
    "\n",
    "from helper import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load the game environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from gridworld import gameEnv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Feel free to adjust the size of the gridworld. Making it smaller (adjusting `size`) provides an easier task for our DRQN agent, while making the world larger increases the challenge.\n",
    "\n",
    "Initializing the Gridworld with `True` limits the field of view, resulting in a partially observable MDP. Initializing it with `False` provides the agent with the entire environment, resulting in a fully MDP."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "env = gameEnv(partial=False,size=9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "env = gameEnv(partial=True,size=9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Above are examples of a starting environment in our simple game. The agent controls the blue square, and can move up, down, left, or right. The goal is to move to the green squares (for +1 reward) and avoid the red squares (for -1 reward). When the agent moves through a green or red square, it is randomly moved to a new place in the environment."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Implementing the network itself"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class Qnetwork():\n",
    "    def __init__(self,h_size,rnn_cell,myScope):\n",
    "        #The network recieves a frame from the game, flattened into an array.\n",
    "        #It then resizes it and processes it through four convolutional layers.\n",
    "        self.scalarInput =  tf.placeholder(shape=[None,21168],dtype=tf.float32)\n",
    "        self.imageIn = tf.reshape(self.scalarInput,shape=[-1,84,84,3])\n",
    "        self.conv1 = slim.convolution2d( \\\n",
    "            inputs=self.imageIn,num_outputs=32,\\\n",
    "            kernel_size=[8,8],stride=[4,4],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv1')\n",
    "        self.conv2 = slim.convolution2d( \\\n",
    "            inputs=self.conv1,num_outputs=64,\\\n",
    "            kernel_size=[4,4],stride=[2,2],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv2')\n",
    "        self.conv3 = slim.convolution2d( \\\n",
    "            inputs=self.conv2,num_outputs=64,\\\n",
    "            kernel_size=[3,3],stride=[1,1],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv3')\n",
    "        self.conv4 = slim.convolution2d( \\\n",
    "            inputs=self.conv3,num_outputs=512,\\\n",
    "            kernel_size=[7,7],stride=[1,1],padding='VALID', \\\n",
    "            biases_initializer=None,scope=myScope+'_conv4')\n",
    "        \n",
    "        self.trainLength = tf.placeholder(dtype=tf.int32)\n",
    "        #We take the output from the final convolutional layer and send it to a recurrent layer.\n",
    "        #The input must be reshaped into [batch x trace x units] for rnn processing, \n",
    "        #and then returned to [batch x units] when sent through the upper levles.\n",
    "        self.batch_size = tf.placeholder(dtype=tf.int32)\n",
    "        self.convFlat = tf.reshape(slim.flatten(self.conv4),[self.batch_size,self.trainLength,h_size])\n",
    "        self.state_in = cell.zero_state(self.batch_size, tf.float32)\n",
    "        self.rnn,self.rnn_state = tf.nn.dynamic_rnn(\\\n",
    "                inputs=self.convFlat,cell=rnn_cell,dtype=tf.float32,initial_state=self.state_in,scope=myScope+'_rnn')\n",
    "        self.rnn = tf.reshape(self.rnn,shape=[-1,h_size])\n",
    "        #The output from the recurrent player is then split into separate Value and Advantage streams\n",
    "        self.streamA,self.streamV = tf.split(1,2,self.rnn)\n",
    "        self.AW = tf.Variable(tf.random_normal([h_size/2,4]))\n",
    "        self.VW = tf.Variable(tf.random_normal([h_size/2,1]))\n",
    "        self.Advantage = tf.matmul(self.streamA,self.AW)\n",
    "        self.Value = tf.matmul(self.streamV,self.VW)\n",
    "        \n",
    "        self.salience = tf.gradients(self.Advantage,self.imageIn)\n",
    "        #Then combine them together to get our final Q-values.\n",
    "        self.Qout = self.Value + tf.sub(self.Advantage,tf.reduce_mean(self.Advantage,reduction_indices=1,keep_dims=True))\n",
    "        self.predict = tf.argmax(self.Qout,1)\n",
    "        \n",
    "        #Below we obtain the loss by taking the sum of squares difference between the target and prediction Q values.\n",
    "        self.targetQ = tf.placeholder(shape=[None],dtype=tf.float32)\n",
    "        self.actions = tf.placeholder(shape=[None],dtype=tf.int32)\n",
    "        self.actions_onehot = tf.one_hot(self.actions,4,dtype=tf.float32)\n",
    "        \n",
    "        self.Q = tf.reduce_sum(tf.mul(self.Qout, self.actions_onehot), reduction_indices=1)\n",
    "        \n",
    "        self.td_error = tf.square(self.targetQ - self.Q)\n",
    "        \n",
    "        #In order to only propogate accurate gradients through the network, we will mask the first\n",
    "        #half of the losses for each trace as per Lample & Chatlot 2016\n",
    "        self.maskA = tf.zeros([self.batch_size,self.trainLength/2])\n",
    "        self.maskB = tf.ones([self.batch_size,self.trainLength/2])\n",
    "        self.mask = tf.concat(1,[self.maskA,self.maskB])\n",
    "        self.mask = tf.reshape(self.mask,[-1])\n",
    "        self.loss = tf.reduce_mean(self.td_error * self.mask)\n",
    "        \n",
    "        self.trainer = tf.train.AdamOptimizer(learning_rate=0.0001)\n",
    "        self.updateModel = self.trainer.minimize(self.loss)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Experience Replay"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These classes allow us to store experies and sample then randomly to train the network.\n",
    "Episode buffer stores experiences for each individal episode.\n",
    "Experience buffer stores entire episodes of experience, and sample() allows us to get training batches needed from the network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "class experience_buffer():\n",
    "    def __init__(self, buffer_size = 1000):\n",
    "        self.buffer = []\n",
    "        self.buffer_size = buffer_size\n",
    "    \n",
    "    def add(self,experience):\n",
    "        if len(self.buffer) + 1 >= self.buffer_size:\n",
    "            self.buffer[0:(1+len(self.buffer))-self.buffer_size] = []\n",
    "        self.buffer.append(experience)\n",
    "            \n",
    "    def sample(self,batch_size,trace_length):\n",
    "        sampled_episodes = random.sample(self.buffer,batch_size)\n",
    "        sampledTraces = []\n",
    "        for episode in sampled_episodes:\n",
    "            point = np.random.randint(0,len(episode)+1-trace_length)\n",
    "            sampledTraces.append(episode[point:point+trace_length])\n",
    "        sampledTraces = np.array(sampledTraces)\n",
    "        return np.reshape(sampledTraces,[batch_size*trace_length,5])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training the network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#Setting the training parameters\n",
    "batch_size = 4 #How many experience traces to use for each training step.\n",
    "trace_length = 8 #How long each experience trace will be when training\n",
    "update_freq = 5 #How often to perform a training step.\n",
    "y = .99 #Discount factor on the target Q-values\n",
    "startE = 1 #Starting chance of random action\n",
    "endE = 0.1 #Final chance of random action\n",
    "anneling_steps = 10000 #How many steps of training to reduce startE to endE.\n",
{'fraction_non_alphanumeric': 0.1587925152486302, 'fraction_numerical': 0.012870877700816706, 'mean_word_length': 2.5761552680221813, 'pattern_counts': {'":': 134, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4600655', 'n_tokens_mistral': 6665, 'n_tokens_neox': 6496, 'n_words': 1451}