{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/jannes/anaconda/lib/python3.5/site-packages/h5py/__init__.py:34: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n", " from ._conv import register_converters as _register_converters\n", "Using TensorFlow backend.\n", "/Users/jannes/anaconda/lib/python3.5/importlib/_bootstrap.py:222: RuntimeWarning: compiletime version 3.6 of module 'tensorflow.python.framework.fast_tensor_util' does not match runtime version 3.5\n", " return f(*args, **kwds)\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Couldn't import dot_parser, loading of dot files will not be possible.\n" ] } ], "source": [ "#First we import some libraries\n", "#Json for loading and saving the model (optional)\n", "import json\n", "#matplotlib for rendering\n", "import matplotlib.pyplot as plt\n", "#numpy for handeling matrix operations\n", "import numpy as np\n", "#time, to, well... keep track of time\n", "import time\n", "#Python image libarary for rendering\n", "from PIL import Image\n", "#iPython display for making sure we can render the frames\n", "from IPython import display\n", "#seaborn for rendering\n", "import seaborn\n", "#Keras is a deep learning libarary\n", "from keras.models import model_from_json\n", "from keras.models import Sequential\n", "from keras.layers.core import Dense\n", "from keras.optimizers import sgd\n", "\n", "#Setup matplotlib so that it runs nicely in iPython\n", "%matplotlib inline\n", "#setting up seaborn\n", "seaborn.set()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Setting up the game\n", "\n", "This is the code for the actual game we are training on.\n", "Catch is a simple game you might have played as a child. In the game, fruits, represented by white tiles, fall from the top. The goal is to catch the fruits with a basked (represented by white tiles, this is deep learning, not game design). If you catch a fruit, you get a point (your score goes up by one), if you miss a fruit, you loose one (your score goes down).\n", "\n", "Don't worry all too much about the details of the implementation, the focus here should be on the AI, not on the game.\n", "Just make sure you run this cell so that it is defined." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Catch(object):\n", " \"\"\"\n", " Class catch is the actual game.\n", " In the game, fruits, represented by white tiles, fall from the top.\n", " The goal is to catch the fruits with a basked (represented by white tiles, this is deep learning, not game design).\n", " \"\"\"\n", " def __init__(self, grid_size=10):\n", " self.grid_size = grid_size\n", " self.reset()\n", "\n", " def _update_state(self, action):\n", " \"\"\"\n", " Input: action and states\n", " Ouput: new states and reward\n", " \"\"\"\n", " state = self.state\n", " if action == 0: # left\n", " action = -1\n", " elif action == 1: # stay\n", " action = 0\n", " else:\n", " action = 1 # right\n", " f0, f1, basket = state[0]\n", " new_basket = min(max(1, basket + action), self.grid_size-1)\n", " f0 += 1\n", " out = np.asarray([f0, f1, new_basket])\n", " out = out[np.newaxis]\n", "\n", " assert len(out.shape) == 2\n", " self.state = out\n", "\n", " def _draw_state(self):\n", " im_size = (self.grid_size,)*2\n", " state = self.state[0]\n", " canvas = np.zeros(im_size)\n", " canvas[state[0], state[1]] = 1 # draw fruit\n", " canvas[-1, state[2]-1:state[2] + 2] = 1 # draw basket\n", " return canvas\n", " \n", " def _get_reward(self):\n", " fruit_row, fruit_col, basket = self.state[0]\n", " if fruit_row == self.grid_size-1:\n", " if abs(fruit_col - basket) <= 1:\n", " return 1\n", " else:\n", " return -1\n", " else:\n", " return 0\n", "\n", " def _is_over(self):\n", " if self.state[0, 0] == self.grid_size-1:\n", " return True\n", " else:\n", " return False\n", "\n", " def observe(self):\n", " canvas = self._draw_state()\n", " return canvas.reshape((1, -1))\n", "\n", " def act(self, action):\n", " self._update_state(action)\n", " reward = self._get_reward()\n", " game_over = self._is_over()\n", " return self.observe(), reward, game_over\n", "\n", " def reset(self):\n", " n = np.random.randint(0, self.grid_size-1, size=1)\n", " m = np.random.randint(1, self.grid_size-2, size=1)\n", " self.state = np.asarray([0, n, m])[np.newaxis]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to defining the game we need to define some helper variables and functions.\n", "Run the cells below to define them, then we will get to the meat and the potatoes of this notebook." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "\"\"\"\n", "Here we define some variables used for the game and rendering later\n", "\"\"\"\n", "#last frame time keeps track of which frame we are at\n", "last_frame_time = 0\n", "#translate the actions to human readable words\n", "translate_action = [\"Left\",\"Stay\",\"Right\",\"Create Ball\",\"End Test\"]\n", "#size of the game field\n", "grid_size = 10" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def display_screen(action,points,input_t):\n", " #Function used to render the game screen\n", " #Get the last rendered frame\n", " global last_frame_time\n", " print(\"Action %s, Points: %d\" % (translate_action[action],points))\n", " #Only display the game screen if the game is not over\n", " if(\"End\" not in translate_action[action]):\n", " #Render the game with matplotlib\n", " plt.imshow(input_t.reshape((grid_size,)*2),\n", " interpolation='none', cmap='gray')\n", " #Clear whatever we rendered before\n", " display.clear_output(wait=True)\n", " #And display the rendering\n", " display.display(plt.gcf())\n", " #Update the last frame time\n", " last_frame_time = set_max_fps(last_frame_time)\n", " \n", " \n", "def set_max_fps(last_frame_time,FPS = 1):\n", " current_milli_time = lambda: int(round(time.time() * 1000))\n", " sleep_time = 1./FPS - (current_milli_time() - last_frame_time)\n", " if sleep_time > 0:\n", " time.sleep(sleep_time)\n", " return current_milli_time()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Deep reinforcement learning" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "class ExperienceReplay(object):\n", " \"\"\"\n", " During gameplay all the experiences < s, a, r, s’ > are stored in a replay memory. \n", " In training, batches of randomly drawn experiences are used to generate the input and target for training.\n", " \"\"\"\n", " def __init__(self, max_memory=100, discount=.9):\n", " \"\"\"\n", " Setup\n", " max_memory: the maximum number of experiences we want to store\n", " memory: a list of experiences\n", " discount: the discount factor for future experience\n", " \n", " In the memory the information whether the game ended at the state is stored seperately in a nested array\n", " [...\n", " [experience, game_over]\n", " [experience, game_over]\n", " ...]\n", " \"\"\"\n", " self.max_memory = max_memory\n", " self.memory = list()\n", " self.discount = discount\n", "\n", " def remember(self, states, game_over):\n", " #Save a state to memory\n", " self.memory.append([states, game_over])\n", " #We don't want to store infinite memories, so if we have too many, we just delete the oldest one\n", " if len(self.memory) > self.max_memory:\n", " del self.memory[0]\n", "\n", " def get_batch(self, model, batch_size=10):\n", " \n", " #How many experiences do we have?\n", " len_memory = len(self.memory)\n", " \n", " #Calculate the number of actions that can possibly be taken in the game\n", " num_actions = model.output_shape[-1]\n", " \n", " #Dimensions of the game field\n", " env_dim = self.memory[0][0][0].shape[1]\n", " \n", " #We want to return an input and target vector with inputs from an observed state...\n", " inputs = np.zeros((min(len_memory, batch_size), env_dim))\n", " \n", " #...and the target r + gamma * max Q(s’,a’)\n", " #Note that our target is a matrix, with possible fields not only for the action taken but also\n", " #for the other possible actions. The actions not take the same value as the prediction to not affect them\n", " targets = np.zeros((inputs.shape[0], num_actions))\n", " \n", " #We draw states to learn from randomly\n", " for i, idx in enumerate(np.random.randint(0, len_memory,\n", " size=inputs.shape[0])):\n", " \"\"\"\n", " Here we load one transition <s, a, r, s’> from memory\n", " state_t: initial state s\n", " action_t: action taken a\n", " reward_t: reward earned r\n", " state_tp1: the state that followed s’\n", " \"\"\"\n", " state_t, action_t, reward_t, state_tp1 = self.memory[idx][0]\n", " \n", " #We also need to know whether the game ended at this state\n", " game_over = self.memory[idx][1]\n", "\n", " #add the state s to the input\n", " inputs[i:i+1] = state_t\n", " \n", " # First we fill the target values with the predictions of the model.\n", " # They will not be affected by training (since the training loss for them is 0)\n", " targets[i] = model.predict(state_t)[0]\n", " \n", " \"\"\"\n", " If the game ended, the expected reward Q(s,a) should be the final reward r.\n", " Otherwise the target value is r + gamma * max Q(s’,a’)\n", " \"\"\"\n", " # Here Q_sa is max_a'Q(s', a')\n", " Q_sa = np.max(model.predict(state_tp1)[0])\n", " \n", " #if the game ended, the reward is the final reward\n", " if game_over: # if game_over is True\n", " targets[i, action_t] = reward_t\n", " else:\n", " # r + gamma * max Q(s’,a’)\n", " targets[i, action_t] = reward_t + self.discount * Q_sa\n", " return inputs, targets\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining the model" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def baseline_model(grid_size,num_actions,hidden_size):\n", " #seting up the model with keras\n", " model = Sequential()\n", " model.add(Dense(hidden_size, input_shape=(grid_size**2,), activation='relu'))\n", " model.add(Dense(hidden_size, activation='relu'))\n", " model.add(Dense(num_actions))\n", " model.compile(sgd(lr=.1), \"mse\")\n", " return model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Parameters" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# parameters\n", "epsilon = .1 # exploration\n", "num_actions = 3 # [move_left, stay, move_right]\n", "max_memory = 500 # Maximum number of experiences we are storing\n", "hidden_size = 100 # Size of the hidden layers\n", "batch_size = 1 # Number of experiences we use for training per batch\n", "grid_size = 10 # Size of the playing field" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "_________________________________________________________________\n", "Layer (type) Output Shape Param # \n", "=================================================================\n", "dense_1 (Dense) (None, 100) 10100 \n", "_________________________________________________________________\n", "dense_2 (Dense) (None, 100) 10100 \n", "_________________________________________________________________\n", "dense_3 (Dense) (None, 3) 303 \n", "=================================================================\n", "Total params: 20,503\n", "Trainable params: 20,503\n", "Non-trainable params: 0\n", "_________________________________________________________________\n" ] } ], "source": [ "#Define model\n", "model = baseline_model(grid_size,num_actions,hidden_size)\n", "model.summary()" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "# Define environment/game\n", "env = Catch(grid_size)\n", "\n", "# Initialize experience replay object\n", "exp_replay = ExperienceReplay(max_memory=max_memory)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Training the model\n" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "def train(model,epochs, verbose = 1):\n", " # Train\n", " #Reseting the win counter\n", " win_cnt = 0\n", " # We want to keep track of the progress of the AI over time, so we save its win count history\n", " win_hist = []\n", " #Epochs is the number of games we play\n", " for e in range(epochs):\n", " loss = 0.\n", " #Resetting the game\n", " env.reset()\n", " game_over = False\n", " # get initial input\n", " input_t = env.observe()\n", " \n", " while not game_over:\n", " #The learner is acting on the last observed game screen\n", " #input_t is a vector containing representing the game screen\n", " input_tm1 = input_t\n", " \n", " \"\"\"\n", " We want to avoid that the learner settles on a local minimum.\n", " Imagine you are eating eating in an exotic restaurant. After some experimentation you find \n", " that Penang Curry with fried Tempeh tastes well. From this day on, you are settled, and the only Asian \n", " food you are eating is Penang Curry. How can your friends convince you that there is better Asian food?\n", " It's simple: Sometimes, they just don't let you choose but order something random from the menu.\n", " Maybe you'll like it.\n", " The chance that your friends order for you is epsilon\n", " \"\"\"\n", " if np.random.rand() <= epsilon:\n", " #Eat something random from the menu\n", " action = np.random.randint(0, num_actions, size=1)\n", " else:\n", " #Choose yourself\n", " #q contains the expected rewards for the actions\n", " q = model.predict(input_tm1)\n", " #We pick the action with the highest expected reward\n", " action = np.argmax(q[0])\n", "\n", " # apply action, get rewards and new state\n", " input_t, reward, game_over = env.act(action)\n", " #If we managed to catch the fruit we add 1 to our win counter\n", " if reward == 1:\n", " win_cnt += 1 \n", " \n", " #Uncomment this to render the game here\n", " #display_screen(action,3000,inputs[0])\n", " \n", " \"\"\"\n", " The experiences < s, a, r, s’ > we make during gameplay are our training data.\n", " Here we first save the last experience, and then load a batch of experiences to train our model\n", " \"\"\"\n", " \n", " # store experience\n", " exp_replay.remember([input_tm1, action, reward, input_t], game_over) \n", " \n", " # Load batch of experiences\n", " inputs, targets = exp_replay.get_batch(model, batch_size=batch_size)\n", " \n", " # train model on experiences\n", " batch_loss = model.train_on_batch(inputs, targets)\n", " \n", " #print(loss)\n", " loss += batch_loss\n", " if verbose > 0:\n", " print(\"Epoch {:03d}/{:03d} | Loss {:.4f} | Win count {}\".format(e,epochs, loss, win_cnt))\n", " win_hist.append(win_cnt)\n", " return win_hist" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Playing many games" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Training done\n" ] } ], "source": [ "epoch = 5000 # Number of games played in training, I found the model needs about 4,000 games till it plays well\n", "# Train the model\n", "# For simplicity of the noteb\n", "hist = train(model,epoch,verbose=0)\n", "print(\"Training done\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing the model" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "def test(model):\n", " #This function lets a pretrained model play the game to evaluate how well it is doing\n", " global last_frame_time\n", " plt.ion()\n", " # Define environment, game\n", " env = Catch(grid_size)\n", " #c is a simple counter variable keeping track of how much we train\n", " c = 0\n", " #Reset the last frame time (we are starting from 0)\n", " last_frame_time = 0\n", " #Reset score\n", " points = 0\n", " #For training we are playing the game 10 times\n", " for e in range(10):\n", " loss = 0.\n", " #Reset the game\n", " env.reset()\n", " #The game is not over\n", " game_over = False\n", " # get initial input\n", " input_t = env.observe()\n", " #display_screen(3,points,input_t)\n", " c += 1\n", " while not game_over:\n", " #The learner is acting on the last observed game screen\n", " #input_t is a vector containing representing the game screen\n", " input_tm1 = input_t\n", " #Feed the learner the current status and get the expected rewards for different actions from it\n", " q = model.predict(input_tm1)\n", " #Select the action with the highest expected reward\n", " action = np.argmax(q[0])\n", " # apply action, get rewards and new state\n", " input_t, reward, game_over = env.act(action)\n", " #Update our score\n", " points += reward\n", " display_screen(action,points,input_t)\n", " c += 1" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "test(model)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Evaluating progress" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "image/png": "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\nexiCHRztlB6f7vSohLI3prMbAOycRTzYdYaqhonzX/b/v6NjatVNB045F+0U6A0n6JrWvxsi9fyi\n5wvopkj94QcAhAc9X4DFwjn2KJL+8rbjL9lAetPaa6+O3hOKtu3KOgJp11DU1tXex9bvCec0Hh2x\nYvtW/JyH+zh1VTiPb6QcM3q+ACAEQvXB6+QPDyfXBkQSer4AhWfCyUC196HeekxJ2/ElTr4MGura\nurI+xuMEd9ycfD5FG4514CJ90mp6vhDzuvMLLxy/LFtvs6P/O4mT63JqbYgOnF/WiuTjS/gC2ojk\nH2jA6SK5twIIFS47AiHS2YdKOAbdh+NDzmygtdl7ghHs4Fuz5VsvG+4JR9ueL12tx+xSdKi2E2w9\n0SqUl/6j/Vh1RWe/RyPteFnW8+Xz+VRUVKS8vDwVFBSourr6nNefeOIJ5eTkaOrUqXr99detKgNA\nmETaL0NYg/MAOJ9lPV87d+5UY2OjNm7cqMrKSpWUlGjt2rWSpP379+v3v/+9XnzxRUlSfn6+hg0b\npl69ellVDhByXf3rNpgB3+GalDPUnFpXLKItnIl2iS2W9Xzt3btXI0aMkCRlZmaqqqrK/9onn3yi\noUOHKiEhQQkJCRowYID2799vVSlA0PhFiFDjnEJHWn8DAWKDZT1fdXV18nq9/sdut1tNTU3yeDy6\n9NJL9cQTT6iurk5nzpzR+++/r7y8vE7Xl5ycKI/HbVW5fqmpSZZvA11nZ/t0tq32flkGW5vV+xKO\nc7kr2+zsPcGsb9uqnKC33dVthXIdgbynu7VtW5UT0DkbjedkuHV1n2PxWHWHlcfLinVbFr68Xq/q\n6+v9j30+nzyes5tLT0/X1KlTNWvWLPXt21dXX321kpOTO11fbe1pq0r1S01N0tGjpyzfDrrG7vYJ\nZlvrCrODri3Y5YMZtN+VerqrvfYJZEB3Z3UGug+B7G9ng+9DdbyCqdfsPYEsE6j2Pjxa9jmU2+lI\nyzZi8fdrV37Ou/K+WNHRcA2rjld3Pnc6C22WXXbMyspSeXm5JKmyslIZGRn+144fP676+no9//zz\nWrx4sY4cOaLBgwdbVQoAAIBjWNbzNWbMGFVUVCg/P1+GYai4uFhlZWVKS0tTdna2Pv30U02ePFk9\nevTQ/fffL7fb+kuKQDCcPOt9rIj0WayBzr4nlHGAscuy8BUXF6clS5ac81x6err//21fAwAgVhC8\nYhsz3ANhEspfvk7uHXJybQAQDsxwD3STneHCbNC4UwVTWyA3FnBJOHgcJ/t09RstaKPYQc8XAMfh\nQwhANKPnCzGNcRfOF0mD7umRgxl+50Ci5wsAAMBWhC/g/6wrzA66tyLYsUyhRM9KaHT3ONIOaA/n\nBTpD+ALasPKXphUBjF/y4Uc7oD0t50Ug5wbnT2whfAGAjfiQBcCAeyAArQd9Wz1gtr31x/oHdiQO\nUm6pOdbbDsD56PkCAACwET1fQDvs6uVCxwKZqLKrvUpW9UZx3qAj7Z3PLedLamqSjh49FY6yECb0\nfAEAANiI8AUAgA0Y/4cWhC8gQFxKAgCEAuELCAIBDIGghwNAZxhwD3QRH7DWa28AeyQed0I7WkTi\n+YvQo+cLAADARoQvQPb+NcpfvrGFXi8AbRG+AAfhgxoAoh/hCwAAwEYMuAc60foSod29Ulye/H+R\neCwCmaEfQGyi5wsIUCQGAACA8xC+EJNmlOxyRK+EE2oAANiL8AUAAGAjwhfQBVyCBAB0FQPugSAQ\nugAA3UXPFwAAgI3o+QLCjEH3ABBb6PkCAACwEeELcCDGlkWHtu1IuwKQCF8AAAC2InwBAADYiPAF\nAABgI8IXAACAjZhqAjEtXAOg1xVmtzvFBAOyow9tCqAter4AAABsRPhCVGLiUgCAUxG+EHVaghcB\nDADgRIQvIEwYCwQAsYkB90AYEcAAIPZY1vPl8/lUVFSkvLw8FRQUqLq6+pzX161bp1tuuUWTJ0/W\nq6++alUZwHm4HAkACCfLer527typxsZGbdy4UZWVlSopKdHatWslSSdPntT69eu1Y8cONTQ06Kab\nbtKYMWOsKgUAAMAxLOv52rt3r0aMGCFJyszMVFVVlf+1Xr16qW/fvmpoaFBDQ4NcLpdVZQDnoNcL\nABBulvV81dXVyev1+h+73W41NTXJ4zm7yW9/+9saP368mpubdeedd5quLzk5UR6P26py/VJTkyzf\nBrou2PYxW572Di2Op3PRNs5G+ziXFW1jWfjyer2qr6/3P/b5fP7gVV5erpqaGr322muSpJkzZyor\nK0tXXXVVh+urrT1tVal+qalJOnr0lOXbQdd0pX3Mlqe9Q4efH+eibZyN9nGu7rRNZ6HNssuOWVlZ\nKi8vlyRVVlYqIyPD/9qFF16onj17Kj4+XgkJCUpKStLJkyetKgUAAMAxLOv5GjNmjCoqKpSfny/D\nMFRcXKyysjKlpaVp9OjReuutt5Sbm6u4uDhlZWXpBz/4gVWlIAa0jOVi6gYAgNNZFr7i4uK0ZMmS\nc55LT0/3///uu+/W3XffbdXmAVMENQBAODDDPaJK27sZubsRAOA0puHr0KFDuv322zV27FjV1NRo\n+vTpOnjwoB21AZah1wsAEC6m4auoqEgzZ85U7969lZqaqgkTJmjBggV21AYAABB1TMNXbW2thg8f\nLsMw5HK5lJubq7q6OjtqAzo1o2SX/1+gywMAEG6m4atnz5766quv/LPQv/vuu4qPj7e8MAAAgGhk\nerfjwoULdeedd+qLL75QTk6OTpw4odWrV9tRGxASM0p2McYLAOAYpuHryiuv1KZNm/T555+rublZ\ngwYNoucLAACgi0zD16effqoXXnhBJ06cOOf55cuXW1YUAABAtDINX3PmzNGNN96oSy+91I56gHa1\nnsGegfMAgEhmGr4uuOACzZkzx45aAFMELwBApDMNXzfffLN+/vOfa9iwYfJ4/n/x7373u5YWBrQg\ncAEAoolp+HrnnXf04Ycf6r333vM/53K5tH79eksLAwAAiEam4auqqko7duywoxYAAICoZxq+MjIy\n9PHHH+uyyy6zox5EodaXDVsPmLdz7i0uXQIAnMI0fH355Ze6+eablZqaqh49evi/Zui1116zoz5E\nmWBDEKEJABBtTMPXmjVr7KgDCFpLz1nbnrXWCG8AAKcxDV+pqanavXu36uvrJUnNzc06ePCg5s2b\nZ3lxgBX4qiEAQDgFNMlqQ0ODvvjiC1177bXas2ePMjMz7agNAAAg6sSZLfDZZ59p/fr1GjNmjGbN\nmqUXX3xRNTU1dtQGAAAQdUzDV58+feRyuTRw4EDt379fF198sRobG+2oDQAAIOqYXnYcPHiwli5d\nqttuu0333XefampqdObMGTtqAzrUetxWZ2O42n4XJOO9AADhZtrztWjRIv34xz/WJZdcorvvvls1\nNTVatWqVHbUBAABEHdPw9d5778kwDO3Zs0dJSUkaN26cvvnmG508edKO+hDhmOoBAIBzBTTPV1VV\nlb7//e/LMAy988476tevn+rq6jRv3jxNmDDBjjoRhWaU7OIyIAAg5piGL8MwtHXrVvXt21eS9PXX\nX+uBBx7QM888o4KCAsIXAABAEEzDV01NjT94SdLFF1+smpoaeb1eGYZhaXFAa9tW5ejo0VPhLgMA\n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\nr/Hjx3elZgAAgIjVYfjKzc1Vbm6u1qxZo7vuusvOmgAAAKKW6YD722+/XY8++qj+67/+S83NzRo2\nbJjmzZunxMREO+oDAACIKqYz3C9dulQNDQ0qLi7WihUrdObMGT388MN21AYAABB1THu+9u3bp61b\nt/ofFxUV6cYbb7S0KJxlNoFpOAfeM7kqAABdY9rzZRiGTp486X988uRJud3WT3aKwBGEAACIHKY9\nX//8z/+sW2+9Vddff70kadeuXbrjjjssLwyRhWkvAAAIjGn4mjx5sq688krt2bNHPp9PpaWluvTS\nS+2oDa2sK8ymhwsAgCgQ0NcLZWRkMAkqAABACJiO+UJ40MsFAEB0InwBAADYKKDwtW3bNv385z9X\nQ0ODtmzZYnVNaKNlMLtTB7U7tS4AAJzINHw99thj2r17t3bs2KHm5ma99NJLKikpsaM2tGNdYTZh\nBwCACGYavt588009+uijSkhIkNfrVVlZmcrLy+2oDQAAIOqYhq+4uLOLuFwuSVJjY6P/OQAAAATH\ndKqJcePG6Z577tGJEyf09NNPa+vWrZowYYIdtQEAAEQd0/B1xx136I9//KP69u2rI0eOaO7cuf7Z\n7gEAABAc0/C1Z88e9ezZU9nZZwd5u1wuffjhhxowYIAuuOACywuEszH4HwCA4JiGrzVr1qiqqkrf\n//73ZRiG3nnnHfXr1091dXWaN28elyAdYkbJLoIQAAARwHTkvGEY2rp1q0pLS/WrX/1K27ZtU0pK\nijZv3qynnnrKjhpjTldnt2dWfAAAnM80fNXU1Khv377+xxdffLFqamrk9XplGIalxQEAAEQb0/CV\nlZWl+fPn64033tCuXbs0f/58XXPNNXrjjTeUmJhoR41oB5cYAQCITKZjvhYvXqznn39eGzdulNvt\n1nXXXafc3FxVVFRo5cqVdtQIAAAQNUzDl8fj0YQJEzR69GgZhqHm5mbt2bNHo0aNsqM+BKll3JdV\nPWOMKwMAoHtMw9eqVav029/+Vk1NTUpOTtbXX3+tK664Qi+++KId9aGLrLj7keAFAED3mY75euWV\nV7R7927deOONWr9+vcrKypSSkmJHbQAAAFHHNHxddNFF8nq9Gjx4sD7++GMNGzZMx44ds6M2qPPL\nhwy6BwAg8phedvR6vdqyZYuGDBmiZ599VhdddJFOnjxpR20AAABRx7Tn65FHHtHx48f1ve99T/36\n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\nLwAAABv9L1nCmxMcGHaeAAAAAElFTkSuQmCC\n", "text/plain": [ "<Figure size 720x504 with 1 Axes>" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def moving_average_diff(a, n=100):\n", " diff = np.diff(a)\n", " ret = np.cumsum(diff, dtype=float)\n", " ret[n:] = ret[n:] - ret[:-n]\n", " return ret[n - 1:] / n\n", "\n", "plt.figure(figsize=(10,7))\n", "plt.plot(moving_average_diff(hist),antialiased=False)\n", "plt.ylabel('Average of victories per game')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "£" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python [default]", "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.5.5" } }, "nbformat": 4, "nbformat_minor": 1 }