diff --git a/nim-game.ipynb b/nim-game.ipynb index 3f9ca80..f516866 100644 --- a/nim-game.ipynb +++ b/nim-game.ipynb @@ -1,221 +1,581 @@ { "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "from nim_env import NimEnv\n", "from utils import compute_nim_sum, optimal_policy, random_policy\n", "import random\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "# from agent import RLAgent\n", "from sarsa import RLAgent\n", + "from tqdm import tqdm\n", "\n", "# plt figure setup\n", "from matplotlib import rc\n", "\n", "plt.rc('axes', labelsize=14) # fontsize of the x and y labels\n", "plt.rc('axes', titlesize=14)\n", "plt.rc('xtick', labelsize=13) # fontsize of the tick labels\n", "plt.rc('ytick', labelsize=13)\n", "plt.rc('legend', fontsize=14) # legend fontsize\n", "plt.rc('figure', titlesize=14) # fontsize of the figure title\n", "plt.rc('lines', markersize=7)\n", "plt.rc('lines', linewidth=2)\n", "\n" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ - "num_episodes = 1000\n", - "num_seed = 10\n", + "num_episodes = 5000\n", + "num_seed = 100\n", "heaps = random.sample(range(1, 8), 3)\n", "env = NimEnv(heaps)\n", "\n", "\n", "def run_experiment(env, num_episodes):\n", " player0_rewards = []\n", " player1_rewards = []\n", " # agent = [RLAgent(), RLAgent()] # agents for player 0,1\n", " agent = RLAgent() # player 0 plays against e-greedy optimal policy\n", " # agent0 = RLAgent() # RL agent for player 0\n", " # agent1 = RLAgent() # RL agent for player 1\n", "\n", - " for episode in range(1, num_episodes+1):\n", + " for episode in tqdm(range(1, num_episodes+1)):\n", " player0_episode_reward = []\n", " player1_episode_reward = []\n", "\n", " for seed in range(num_seed):\n", " heaps = env.reset(seed) # fixed seeds to evaluate performance on same set of initial conditions\n", - " # heaps = [7,7,7]\n", - " # env = NimEnv(heaps)\n", - " # turn = 0\n", - " # print('ini: ', heaps)\n", + " #heaps = [7,7,7]\n", + " env = NimEnv(heaps)\n", + " winner = None\n", " done = False\n", " action = agent.decide(heaps)\n", - " while True:\n", - " # print('agent action: ', action)\n", - " next_heaps, winner, rewards, done, next_turn = env.step(action)\n", - " # print(next_heaps)\n", + " while not done:\n", + " # print(heaps)\n", + " # print(action)\n", + " next_heaps = None\n", + " nextnext_heaps = None\n", + " next_heaps, winner, rewards, done, _ = env.step(action)\n", " if done:\n", - " if winner == 0:\n", - " player0_episode_reward.append(1) \n", - " player1_episode_reward.append(-1) \n", - " else: # if winner == 1 \n", - " player0_episode_reward.append(-1)\n", - " player1_episode_reward.append(1) \n", + " agent.learn(heaps, action, rewards[0])\n", " break\n", - " adv_action = optimal_policy(next_heaps, randomness=0.5)\n", - " nextnext_heaps, winner, adv_reward, done, next_turn = env.step(adv_action)\n", - " # print(nextnext_heaps)\n", + " adv_action = optimal_policy(next_heaps, randomness=0.2)\n", + " # print('nextheaps1=', next_heaps)\n", + " # print('adv_action=', adv_action)\n", + " nextnext_heaps, winner, adv_reward, done, _ = env.step(adv_action)\n", " if done:\n", - " # print('done')\n", - " # print('winner: ', winner)\n", - " if winner == 0:\n", - " player0_episode_reward.append(1) \n", - " player1_episode_reward.append(-1) \n", - " else: # if winner == 1 \n", - " player0_episode_reward.append(-1)\n", - " player1_episode_reward.append(1) \n", + " agent.learn(heaps, action, adv_reward[0])\n", " break\n", " nextnext_action = agent.decide(nextnext_heaps)\n", + " # print(f'Q({heaps}, {action}) = ', agent.getQ(heaps, action))\n", " agent.learn(heaps, action, rewards[0], nextnext_heaps, nextnext_action)\n", + " # print('Q(heaps, action) = ', agent.getQ(heaps, action))\n", + "\n", + " heaps = nextnext_heaps.copy()\n", + " action = nextnext_action.copy()\n", "\n", - " heaps = nextnext_heaps\n", - " action = nextnext_action\n", - " # turn = next_turn\n", + " if winner == 0:\n", + " player0_episode_reward.append(1) \n", + " player1_episode_reward.append(-1) \n", + " else: # if winner == 1 \n", + " player0_episode_reward.append(-1)\n", + " player1_episode_reward.append(1) \n", " \n", " player0_rewards.append(np.mean(player0_episode_reward)) \n", " player1_rewards.append(np.mean(player1_episode_reward)) \n", - " return player0_rewards, player1_rewards\n" + " return player0_rewards, player1_rewards, agent\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 39, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 5000/5000 [04:56<00:00, 16.85it/s]\n" + ] + }, { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAuQAAAHnCAYAAADw2EhWAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAABXz0lEQVR4nO3deXwV1f3/8feHEHYIKEs1qOCCUVEUcN9QW/f1V221aNFacW1xwaWtVqt1X1q/WqtYFRVc6lql1hUQqICCdYWgIqCiAmENJECW8/vj3Mmdu4UEkzuX3Nfz8ZjHnZkzy+fOmZn7ybnnTsw5JwAAAADRaBV1AAAAAEA+IyEHAAAAIkRCDgAAAESIhBwAAACIEAk5AAAAECEScgAAACBCraMOIGrdu3d3ffr0yfp+169frzZt2mR9v8gu6jk/UM8tH3WcH6jn/BBVPc+cObPMOdcjXVneJ+R9+vTRjBkzsr7f0tJSlZSUZH2/yC7qOT9Qzy0fdZwfqOf8EFU9m9mCTGV0WQEAAAAiREIOAAAARIiEHAAAAIgQCTkAAAAQoZxNyM2swMxuN7MlZlZuZs+ZWfd6lh9pZnNjy35uZhdkM14AAABgY+TyU1auknSCpL0lLZX0sKTHJR2VvKCZHS/pT5IOc85NM7N9Jb1pZp87597IYswAAERq1apVWrx4saqqqqIOZZNTVVWl2bNnRx0Gmllz1XPHjh3Vu3dvtWrV+PbuXE7Ih0u63jn3pSSZ2RWSvjCzPs65+UnLbi/pQ+fcNElyzk01s48kDZBEQg4AyAurVq3SokWLVFxcrPbt28vMog5pk1JZWan27dtHHQaaWXPUc21trRYuXKiysjL17Nmz0evnZEJuZkWStpY0M5jnnJtrZqsk7SZpftIqT0n6lZntL2mqpP0l9ZP0aobtD5dP+FVcXKzS0tKmfgsbVFZWFsl+kV3Uc36gnlu+TaWOq6qq1Lt3b5mZ1q5dG3U4m5zq6mpVVlZGHQaaWXPVc1FRkRYsWKBly5Y1et2cTMgldYm9rkyavyJUFrZY0rOSJijeL/5i59wn6TbunBslaZQkDR482EXxcHj++UB+oJ7zA/Xc8m0qdTx79mx17dqVlvGNRAt5fmiuem7Xrp1at269UfeKXP1RZ3nstShpfldJq9Isf42kX0jaXVKhfFeVS8zs7GaKDwCAnEQyDkTjh1x7OZmQO+dWSPpK0sBgnpltK986/lGaVQZJesE5N8t5n0p6UdKxzR8tAAAAsPFyMiGPGSXpSjPra2ZdJN0q6bU0P+iUpP9KOtHMdpAkM9tJ0omS3s9SrAAAAMBGydU+5JJ0i6Rukt6T1Fb+aSmnS5KZDZX0gHOuU2zZ2+W7t7wRe1b5MknPxLYBAAAA5KycTcidczWSRsaG5LKxksaGpqvln1t+VdYCBAAAOWn9+vVq06ZN1GEADZbLXVYAAECemDRpkvbZZx916tRJRUVF2nvvvfXJJ59o6dKlOu2009S7d2+1b99eu+yyix555JGEdYcMGaLzzz9fI0eOVI8ePbT//vtLkh544AH169dP7dq1U48ePXTEEUeourpakvTee+/p8MMPV/fu3dWlSxcdcMABmj59etbfNyCRkAMAgIhVV1frhBNO0AEHHKAPP/xQ06dP14gRI1RQUKC1a9dq4MCBGjdunD799FONGDFC5557rt56662EbYwZM0bOOU2ePFmPPfaYZsyYoQsvvFDXXnut5syZozfffFNHHnlk3fLl5eU644wzNHnyZL377rvafffdddJJJ6msrCzbbx/I3S4rLdrrr6ttebm0CTzTFgCwaYvqKYjONXzZVatWacWKFTruuOO03XbbSVLCs5wvv/zyuvHhw4dr/PjxevLJJ3XYYYfVze/bt6/uvPPOuunnn39eHTt21PHHH6/OnTtrm2220YABA+rKDz300IQY7rnnHj377LN69dVXdfrppzc8eKAJ0EIehTPOUN+TT5ZmzYo6EgAAIrfZZpvpzDPP1BFHHKFjjjlGd911l77++mtJUk1NjW688Ubttttu2nzzzdWpUyc9//zz+uqrrxK2MWjQoITpn/zkJ9pmm23Ut29fDR06VI8++qjKy8vryhcvXqxzzz1X/fr1U1FRkTp37qwlS5akbBfIBhLyKCxe7F+/+y7aOAAALZ5z0QyN9cgjj2j69Ok66KCD9NJLL6lfv3567bXXdMcdd+jOO+/U5ZdfrrfeeksffPCBTjzxRK1fvz5h/Y4dOyZMd+7cWe+//77++c9/auutt9bNN9+skpISffvtt5KkYcOG6b333tNf/vIXvfPOO/rggw9UXFycsl0gG0jIoxB8xVZbG20cAADkkAEDBujKK6/UxIkTNWTIED366KOaMmWKjjvuOJ1xxhnafffdtd122+mzzz5r0PZat26tQw89VDfffLM++ugjrVmzRuPGjZMkTZkyRb/5zW90zDHHaJdddlHnzp31/fffN+fbAzKiD3kUWsX+DtqYJgQAAFqYefPm6YEHHtDxxx+v4uJiffnll/roo490/vnna/ny5Xr66ac1ZcoUde/eXffcc4/mzZunPfbYo95tjhs3TnPnztVBBx2kzTbbTBMmTFB5ebl22mknSVK/fv00ZswY7b333lqzZo2uuOIKHpWIyJCQRyFIyGkhBwBAHTp00GeffaZTTjlFZWVl6tWrl4YOHaorr7xSq1ev1rx583TUUUepffv2OvPMMzV06FDN2sDvsLp27aoXX3xR119/vSoqKrTddtvpH//4hw488EBJ0sMPP6zhw4dr0KBB2nLLLXXddddpcdClFMgyEvIokJADAFCnV69eev7559OWdevWLWNZYOLEiSnzDjjgAE2YMCHjOgMGDEh57vjJJ5+s9u3bbzhgoInRhzwKwTOoSMgBAADyHgl5FOhDDgAAgBgS8ijQZQUAAAAxJORRoMsKAAAAYkjIo0CXFQAAAMSQkEeBLisAAACIISGPAl1WAAAAEENCHgW6rAAAACCGhDwKdFkBAABADAl5FEjIAQAAEENCHgX6kAMAACCGhDwK9CEHACArzjzzTB177LEtbl9ROfbYY3XmmWdGHUaLQ0IeBbqsAADQ5IYMGaKLLrooYd7dd9+tMWPGRBQRcsl9992nvn37qlu3bho0aJAmT55c7/I333yz9txzT3Xp0kU9evTQcccdp08++aRZYiMhjwJdVgAAyIqioiJ17do16jCyZv369VGHkJOefvppjRgxQr///e81depU7bfffjrqqKP01VdfZVxn4sSJuuCCC/TOO+9o/Pjxat26tX784x9r2bJlTR4fCXkU6LICAECCdevW6eKLL1avXr3Url077bPPPpoyZUpd+ZAhQ3TeeedpxIgR6tatm7p166bLL79ctbHGrTPPPFNvv/22/va3v8nMZGaaP39+SjeSIUOG6Pzzz9dll12mzTbbTD169NDdd99dt/+uXbtq66231uOPP54Q36uvvqoDDzxQ3bp102abbaYjjjhCs2fPbvT7HD16tPbYYw916NBBXbp00T777KPq6uqMy69Zs0a//OUv1alTJ/Xq1Us333xzQreR4P2MHDlSPXr00P777y9Jcs7ptttu03bbbaf27dtr1113TfmmYEPLVFRU6Mwzz6zb90033ZSw/mOPPabNN99c69atS5g/dOhQHX/88c16XBrrrrvu0plnnqlzzjlHJSUluueee7TFFlvo73//e8Z1XnvtNZ111lnq37+/dt11Vz3++ONasmSJ/vvf/zZZXAES8ijQZQUAgARXXHGFnn76aT388MP63//+p1133VVHHnmkvvvuu7plxo4dq9raWk2dOlUPPPCARo0apb/+9a+SfNeUfffdV2eddZa+++47fffdd9pqq63S7mvs2LHq3Lmzpk+frquuukoXX3yxTjzxRO2www6aMWOGhg0bpl//+tf69ttv69ZZs2aNLr74Yr377ruaOHGiioqKdNxxxzWqRfrll1/WiBEjdMUVV2j27NmaNm2afve736l169YZ17nsssv09ttv64UXXtD48eP14YcfpnS1GDNmjJxzmjx5sh577DFJ0tVXX62HHnpIf/vb3zRr1iz97ne/07nnnqt///vfdettaJmRI0fqjTfe0HPPPae33npL//vf/zRp0qS69U855RTV1tbqX//6V928lStX6oUXXtDZZ5/dLMflpptuUqdOneodko/P+vXrNXPmTB1++OEJ8w8//HC98847DY6zvLxctbW16tatW4PXaTDnXF4PgwYNcll3zjnOSc7df3/2942smj17dtQhIAuo55ZvU6njWbNmpc7038dmf2iE1atXu8LCQvfoo4/Wzauurnbbbrut+8Mf/uCcc+7ggw92O+ywg6utra1b5oYbbnDFxcV10wcffLC78MILE7Y9bNgwd8wxxyQss88++9RN19bWuu7du7vjjjvOVVRUOOecW79+vSssLHTPPPNMvTG3atXKTZ48OeO+kt12221ul112cUuXLs24TFh5ebkrLCx0Tz75ZMJ+u3bt6oYNG1b3fnbdddeU2Nq1a+cmTZqUMH/EiBHuqKOOatAy5eXlrk2bNm7MmDEJ8RQVFdXt2znnLrzwQnfEEUfUTd93332uV69erqqqqkHv0bnGHZelS5e6zz//vN4hqMfAwoULnST39ttvO+dcXfmf/vQn169fvwbHecopp7jdd9/dVVdXZ1wm7TUYI2mGy5CPZv6TDM2HFnIAAOrMnTtXVVVVdd0tJKmgoED77ruvZs2aVTdvn332kQW/w5K077776pprrtGqVavUpUuXBu9vt912qxs3M/Xs2VO77rpr3bzCwkJ169ZNixcvTojxmmuu0fTp07VkyRLV1taqtra23j7Iyc4++2w9/fTT2nzzzdWxY0dNmzZN/fv319ixY3XuuefWLfef//xHBx54YN1x2WuvverKOnbsqP79+ydsd9CgQQnTs2bN0tq1a3XkkUcmHK+qqir16dOnQcvMnTtX69ev17777ltX1qlTp4TjJEnnnHOOBg4cqG+++Ua9e/fWww8/rGHDhtXb6t/Q45LOZpttps0226zB2w4Lv0/JN0onz8vk0ksv1ZQpUzRlyhQVFBRs1P7rQ0IeBfqQAwCyZRP4rHGxGNMlRw1NmBqjsLAwZR/p5tWGGs6OO+44FRcX64EHHlBxcbFat26tnXfeucFdVqqrq3Xaaadp4MCBuv/++9W1a1f17dtXknT88cdr7733rlu2uLhYUv3HJaxjx44J00HcL7/8srbeeuuEsuB9bmiZ5cuXN+h9DRgwQAMHDtTo0aN14oknasaMGY16qk19xyWdm266KaUve7LgD5pA9+7dVVBQoO+//z5hucWLF6tXr14bjPGSSy7RU089pQkTJmjbbbfd4PIbg4Q8CrSQAwBQZ/vtt1ebNm00ZcqUuoSnpqZGU6dO1S9+8Yu65aZPn57Qqjlt2jRtueWWda3jbdq0UU1NTZPHt3TpUs2ePVt/+9vfdMghh0iS3n///Ub96PCFF17Qp59+qtdeey2lrHPnzurcuXPK/O23316FhYV6991365LUiooKffLJJ9puu+0y7mvnnXdW27ZttWDBAh166KEbtczmm2+uwsJCTZs2ra5O1qxZk3bf55xzjm677TaVlZVp//3314477pj5QCSp77ikc9555+lnP/tZvcsEf9AE2rRpo0GDBumNN97QKaecUjf/jTfe0E9/+tN6tzVixAg99dRTmjhxokpKShoU48YgIY8Cjz0EAKBOx44ddf755+uqq65S9+7d1bdvX/3lL3/RokWLdMEFF9Qt9+233+riiy/WBRdcoI8//li33367rr766rryPn366N1339X8+fPVqVOnje7akKxbt27q3r27HnzwQW211VZauHChLr/88kZ1y1i3bp0WL16sRx99VAcffLAqKio0depUnXrqqSkt3IFOnTrpV7/6la688kp1795dW2yxhf785z+rtra23lbzzp07a+TIkRo5cqScczrooIO0evVqTZs2Ta1atdLw4cMbtMzZZ5+tK6+8Uj169NCWW26p66+/Pu0fPKeddpouvfRS/f3vf9f999/f4GOyMcdlY7usXHrppTrjjDO01157adCgQRo9erS+/fZbnXfeeXXL3Hvvvbr33ntVWloqSbrwwgv1+OOP68UXX1S3bt3qWtiDH482JRLyKNBlBQCABLfeeqsk6ayzztKKFSu0xx576NVXX9UWW2xRt8zQoUNVU1OjvffeW2ams88+W5dcckld+ciRIzVs2DDtvPPOqqys1Lx585oktlatWunpp5/Wb3/7W/Xv31/bb7+97rzzzg22roadeuqp+uCDD3TNNddo0aJF6tq1q/bbb78NPo3kjjvu0Jo1a3T88cerU6dOuuSSS7Ro0SK1a9eu3vVuuOEG9erVS3fccYfOP/98denSRbvvvruuuOKKBi8T7Pukk05Shw4d9Jvf/EZr1qxJ2Vfnzp31s5/9TM8880za1uvRo0frrLPO0rx58+r6sP/Q49JYP//5z7V06VL9+c9/1nfffaf+/fvrlVde0TbbbFO3TFlZmebMmVM3fd9990mSDjvssIRtXXvttbruuuuaND5zeZ4UDh482M2YMSO7O73kEumvf5XuusuPo8UqLS1t1q+4kBuo55ZvU6nj2bNna6eddoo6jGYxZMgQ9e/fX/fee2+z7aOyslLt27dvtu03hXXr1mmbbbbR5ZdfrssuuyzqcOocddRR6t27tx588MGUsmuvvVbPPvusPvzww0Z9q9BcmrOe67sGzWymc25wurLoj0o+ossKAABogP/973+aPXu29tprL5WXl+vWW29VeXm5fv7zn0cdmiRp2bJlevPNN/X666/rww8/TLvMK6+8onvvvTcnkvFcxZGJAl1WAABAA911112aM2eOWrdurd13312TJk1S7969ow5LkjRw4EAtW7ZMN910U8ZHFb733ntZjmrTQ0IeheBfAd9wgzRyZLSxAACwCZg4cWLUIURijz32UNa71jbC/Pnzow6hRWgVdQB5afp0/7pqVbRxAAAAIHIk5AAAAECESMgBAACACJGQAwDQguT744yBqPyQa4+EHACAFqKwsFCVlZVRhwHkpaqqqo1+tCMJeRSOPz7qCAAALVDPnj21cOFCVVRU0FIOZFFtba0WLVqkoqKijVqfxx5G4fTTpZdekk4+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8w1rKQn5xtx8cy2pa+gfZc21/8bsu7mQkDffvjckVxPylp7U5cJnQnPVXVOv3xgk5GiQefP81/31ISH3cukC3tQT8mwnddlMyDfGpprUZXv/2fwwa+i9YVOtu1xOyH9onM2V1LX0uiMhbx6bUkIu51xeD4MGDXJRmD17tjvwwOReUKnD++87t2aNH+/Y0bkePfz48uXOzZrlx/fbL/0+5s3z5QMGODdjhh8//HDn/vOf+PZfeCFzjMuW+WV+9KP4vMMO8/M++MC5XXbx4199lbruzTf7srvuyrz9mpp4HOkce2y8vKLCv7Zu7cueftpPX3RR4jq//72f/49/+OlvvvHTO+6Yuv3f/ja+/SVL4vOrq+PzV63yr926Ja4blO+xh3+dOzd1+3/9qy+7+ebUsq+/jm/jD39w7oYb/Pg99/jyLbf000uXxtepqko9XsOG+elXXkncfnC8JOdOOy2x7M47/fxbb3Xu8sv9+KOPxsvvv9/PO/54//rjH6fGHz4G69enlp1xRrw8OH+DuF991Y+ffnriOrff7ufffrufXr7cT/fqlbr9P/4xvs0FC9LHFRwDs8Tydu3i101wLid75BFfdsUVqWVLl8b3ceGFzt17rx+/4QZfvvPOfvrrr9PHVV3tpy++2E8/8UTicuHr4qijEsseesjP/93vnLvpJj/+17/Gy596ys875hj/OnhwavzOOde1a/z8Tvab38T3X1bmXGGhH1+71rmpU/34sccmrvPgg/Fz2TnnKiv9dJs2qdu/66749j/5JLGsSxc/v7w8vkxNTbx8iy38vEMP9a9vv526/Rde8GXnnJNaFr4uhg51bswYP37ppb48OCdmzUpcr2NHP3/1aj993XV++r77UvcRbH/PPRPnP/ecn3/eeX49yW8nEFwXhx/uX7fbLnXbzjnXp48vX7QotSz5ugg+L1ascO7TT/34/vsnrvPMM37+BRf46drazPfl4LqQnHvnncSyrbeO30vbtvXjlZXx8uC6OPJI//rSS6nbHz/el51ySmpZ+Lo4+mjnXn7Zj//qV748OOenTUtcr7g4fi47F78v33hj6j6C7W+1VeL8N9/080891bknn/TjI0bEy4Pr4uCD/evmm6du27n458WXX6aWha+LTz91bocd/Pi33/rPWMl/5oa9/rqf/4tfxOeZ+Xnr1iUuG1wXks8BwgYM8PPn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", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ - "r0, r1 = run_experiment(env, num_episodes)\n", + "r0, r1, agent = run_experiment(env, num_episodes)\n", "\n", "fig = plt.figure(figsize=(12,8))\n", - "plt.plot(range(1, num_episodes+1), r0, color='b', label='player 0')\n", - "plt.plot(range(1, num_episodes+1), r1, color='r', label='player 1')\n", + "plt.plot(range(1, num_episodes+1), r0, color='b', label='sarsa')\n", + "plt.plot(range(1, num_episodes+1), r1, color='r', label=r'optimal $\\epsilon$-greedy, $\\epsilon=0.2$')\n", "plt.legend()\n", "plt.xlabel('episode')\n", "plt.ylabel('reward')\n", "plt.grid(True,'major',linestyle='-',linewidth=0.5)\n", "plt.grid(True,'minor',linestyle='--',linewidth=0.25) \n", "plt.show()" ] }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Q-table\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
(1, 1)(1, 2)(1, 3)(1, 4)(1, 5)(1, 6)(1, 7)(2, 1)(2, 2)(2, 3)...(2, 5)(2, 6)(2, 7)(3, 1)(3, 2)(3, 3)(3, 4)(3, 5)(3, 6)(3, 7)
(0, 0, 0)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000
(0, 0, 1)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0001.0000.0000.0000.0000.0000.0000.000
(0, 0, 2)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.000-0.2001.0000.0000.0000.0000.0000.000
(0, 0, 3)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.0000.0000.4880.0000.0000.0000.000
(0, 0, 4)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.1740.0000.0000.0000.0000.0000.000
(0, 0, 5)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000
(0, 0, 6)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000
(0, 0, 7)0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000...0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000
(0, 1, 0)0.0000.0000.0000.0000.0000.0000.0001.0000.0000.000...0.0000.0000.0000.0000.0000.0000.0000.0000.0000.000
(0, 1, 1)0.000-0.900-0.900-0.180-0.9000.0000.000-1.000-0.9000.000...0.000-0.9000.000-1.0000.000-0.180-0.1800.0000.0000.000
\n", + "

10 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (2, 1) \\\n", + "(0, 0, 0) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 1) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 2) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 3) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 4) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 5) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 6) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 7) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 1, 0) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 1.000 \n", + "(0, 1, 1) 0.000 -0.900 -0.900 -0.180 -0.900 0.000 0.000 -1.000 \n", + "\n", + " (2, 2) (2, 3) ... (2, 5) (2, 6) (2, 7) (3, 1) (3, 2) \\\n", + "(0, 0, 0) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 1) 0.000 0.000 ... 0.000 0.000 0.000 1.000 0.000 \n", + "(0, 0, 2) 0.000 0.000 ... 0.000 0.000 0.000 -0.200 1.000 \n", + "(0, 0, 3) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 4) 0.000 0.000 ... 0.000 0.000 0.000 0.174 0.000 \n", + "(0, 0, 5) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 6) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 7) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 1, 0) 0.000 0.000 ... 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 1, 1) -0.900 0.000 ... 0.000 -0.900 0.000 -1.000 0.000 \n", + "\n", + " (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) \n", + "(0, 0, 0) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 1) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 2) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 3) 0.488 0.000 0.000 0.000 0.000 \n", + "(0, 0, 4) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 5) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 6) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 0, 7) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 1, 0) 0.000 0.000 0.000 0.000 0.000 \n", + "(0, 1, 1) -0.180 -0.180 0.000 0.000 0.000 \n", + "\n", + "[10 rows x 21 columns]" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "pd.options.display.float_format = '{:,.3f}'.format\n", + "print('Q-table')\n", + "agent.q.head(10)" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "\n", "def run_experiment(experiment_name, \n", " env, \n", " num_episodes, \n", " policy_learning_rate=0.001, \n", " value_learning_rate=0.001, \n", " baseline=None, \n", " entropy_cost=0, \n", " max_ent_cost=0, \n", " num_layers=3):\n", "\n", " #Initiate the learning agent\n", " agent = RLAgent(n_obs=env.observation_space.shape[0], \n", " action_space=env.action_space,\n", " policy_learning_rate=policy_learning_rate, \n", " value_learning_rate=value_learning_rate, \n", " discount=0.99, \n", " baseline=baseline, \n", " entropy_cost=entropy_cost, \n", " max_ent_cost=max_ent_cost,\n", " num_layers=num_layers)\n", "\n", " rewards = []\n", " all_episode_frames = []\n", " step = 0\n", " for episode in range(1, num_episodes+1):\n", " \n", " #Reset the environment to a new episode\n", " observation = env.reset()\n", " episode_reward = 0\n", "\n", " while True:\n", "\n", " # 1. Decide on an action based on the observations\n", " action = agent.decide(observation)\n", "\n", " # 2. Take action in the environment\n", " next_observation, reward, done, info = env.step(action)\n", " episode_reward += reward\n", "\n", " # 3. Store the information returned from the environment for training\n", " agent.observe(observation, action, reward)\n", "\n", " # 4. When we reach a terminal state (\"done\"), use the observed episode to train the network\n", " if done:\n", " rewards.append(episode_reward)\n", " agent.train()\n", " break\n", "\n", " # Reset for next step\n", " observation = next_observation\n", " step += 1\n", " \n", " return all_episode_frames, agent" ] } ], "metadata": { "interpreter": { "hash": "4369559244255f10d34bca352df9b3f8794934e60b17f3451fa3b0f2f96527a8" }, "kernelspec": { "display_name": "Python 3.8.8 64-bit ('base': conda)", "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.8.8" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 } diff --git a/nim_env.py b/nim_env.py index dc9ff5f..e68ef18 100644 --- a/nim_env.py +++ b/nim_env.py @@ -1,88 +1,88 @@ import random class NimEnv(): def __init__(self, ini_state): self.n_heap = 3 self.n_agents = 2 self.player_0_turn = True # if false, then it is the turn of player 1 self.winner = None if not isinstance(ini_state, list): raise TypeError else: self.ini_heaps = ini_state self.heaps = ini_state self.heap_avail = [True, True, True] self.heap_keys = ['1', '2', '3'] def step(self, action): """ step method takin an action as input Parameters ---------- action : list(int) action[0] = 1, 2, 3 is the selected heap to take from action[1] is the number of objects to take from the heap Returns ------- getObservation() State space (printable). reward : tuple (0,0) when not in final state, +1 for winner and -1 for loser otherwise. done : bool is the game finished. dict dunno. """ # extracting integer values h: heap id, n: nb objects to take h, n = map(int, action) assert self.heap_avail[h-1], "The selected heap is already empty" assert n >= 1, "You must take at least 1 object from the heap" assert n <= self.heaps[h-1], "You cannot take more objects than there are in the heap" self.heaps[h-1] -= n # core of the action if self.heaps[h-1] == 0: self.heap_avail[h-1] = False reward = (0, 0) done = False if self.heap_avail.count(True) == 0: done = True if self.player_0_turn: reward = (1, -1) self.winner = 0 else: reward = (-1, 1) self.winner = 1 self.player_0_turn = not self.player_0_turn - return self.heaps, self.winner, reward, done, {'next_turn': 0 if self.player_0_turn else 1} + return self.heaps.copy(), self.winner, reward, done, {'next_turn': 0 if self.player_0_turn else 1} def reset(self, seed): random.seed(seed) self.heaps = random.sample(range(1, 8), 3) self.heap_avail = [True, True, True] self.player_0_turn = True self.winner = None return self.heaps def render(self, simple=False): if simple: print(self.heaps) else: print (u'\u2500'*35) for i in range(len(self.heaps)): print("Heap {}: {:15s} \t ({})".format(self.heap_keys[i], "|" * self.heaps[i], self.heaps[i])) print (u'\u2500'*35) diff --git a/sarsa.py b/sarsa.py index 46ce313..3af4137 100644 --- a/sarsa.py +++ b/sarsa.py @@ -1,67 +1,71 @@ import random import pandas as pd import numpy as np class RLAgent(object): - def __init__(self, epsilon=0.1, alpha=0.9, gamma=0.99): - self.q = self.initQ() + def __init__(self, ini_q=0.0, epsilon=0.1, alpha=0.2, gamma=0.9): + self.q = self.initQ(ini_q) self.epsilon = epsilon self.alpha = alpha self.gamma = gamma def getQ(self, state, action): state = tuple(state) action = tuple(action) return self.q[action][state] def setQ(self, state, action, val): state = tuple(state) action = tuple(action) self.q[action][state] = val - def initQ(self, max_obj=7): + def initQ(self, ini_q, max_obj=7): actions=[] for i in range(1,4): for j in range(1,max_obj+1): actions.append((i,j)) self.actions = actions q = pd.DataFrame(columns=actions, dtype=np.float64) for i in range(0,max_obj+1): for j in range(0,max_obj+1): for k in range(0,max_obj+1): - q = q.append(pd.Series([0]*len(actions), + q = q.append(pd.Series([ini_q]*len(actions), index=q.columns, name=(i,j,k))) return q - def learnQ(self, state, action, reward, value): + def learnQ(self, state, action, value): oldv = self.getQ(state, action) newv = oldv + self.alpha * (value - oldv) self.setQ(state, action, newv) def decide(self, state): elig_actions = [a for a in self.actions if state[a[0]-1] >= a[1]] if random.random() < self.epsilon: action = random.choice(elig_actions) else: q = [self.getQ(state, a) for a in self.actions if state[a[0]-1] >= a[1]] maxQ = max(q) count = q.count(maxQ) if count > 1: best = [i for i in range(len(elig_actions)) if q[i] == maxQ] i = random.choice(best) else: i = q.index(maxQ) action = elig_actions[i] return list(action) - def learn(self, state1, action1, reward, state2, action2): - qnext = self.getQ(state2, action2) - self.learnQ(state1, action1, reward, reward + self.gamma * qnext) + def learn(self, state1, action1, reward, state2=[0,0,0], action2=None): + # print('learning') + if state2 == [0, 0, 0]: + qnext = 0.0 # terminal state + else: + qnext = self.getQ(state2, action2) + self.learnQ(state1, action1, reward + self.gamma * qnext) diff --git a/test_optimal.py b/test_optimal.py index 412684d..1b78092 100644 --- a/test_optimal.py +++ b/test_optimal.py @@ -1,44 +1,44 @@ from nim_env import NimEnv from utils import compute_nim_sum, optimal_policy import random heaps = random.sample(range(1, 15), 3) #heaps = [5,3,6] env = NimEnv(heaps) """ Here, we make the two optimal policies play against each other Player 0 is starting. If the nim sum is 0 at the beginning => player 1 will win Otherwise, player 0 will win """ print('The environment at beginning is:') env.render(simple=True) done = False nim_sum = compute_nim_sum(heaps) print(f'The nim sum at beginning is: {nim_sum}') if nim_sum == 0: print('Player 1 should win \n') else: print('Player 0 should win \n') turn = {} turn['next_turn'] = 0 while not done: action = optimal_policy(heaps) print(f"player {turn['next_turn']} takes {action[1]} objects from heap {action[0]}:") - heaps, reward, done, turn = env.step(action) + heaps, winner, reward, done, turn = env.step(action) env.render(simple=True) # print('\n') # print('\n') # print(f"player {turn['next_turn']} turn") print('\nHere is the reward: ', reward) -winner = 0 if reward[0] == 1 else 1 +# winner = 0 if reward[0] == 1 else 1 print(f'Player {winner} wins!')