├── dogs-vs-cats ├── .gitignore └── dogs-vs-cats-part-3.ipynb ├── README.md ├── LICENSE ├── .gitignore ├── mnist-keras.ipynb └── n-armed-bandits.ipynb /dogs-vs-cats/.gitignore: -------------------------------------------------------------------------------- 1 | *.pth -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jupyter-notebooks-public -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /dogs-vs-cats/dogs-vs-cats-part-3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Dogs vs Cats - Part 3\n", 8 | "\n", 9 | "Classify whether images contain either a dog or a cat. Download the data from: https://www.kaggle.com/c/dogs-vs-cats/data\n", 10 | "\n", 11 | "Using an ensemble we achieve an accuracy of 99.0889% on our test set.\n", 12 | "\n", 13 | "This notebook assumes you have already run the steps from the Dogs vs Cats - Part 1 notebook where you downloaded the images and created the training, validation, and test directories.\n", 14 | "\n", 15 | "The dataset contains 25,000 images of dogs and cats (12,500 from each class). We will create a new dataset containing 3 subsets, a training set with 10,000 samples of each class, a validation dataset with 1250 of each class and a test set with 1250 samples of each class.\n" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 1, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "import torch\n", 25 | "import torch.nn as nn\n", 26 | "import torch.optim as optim\n", 27 | "import torch.utils.data\n", 28 | "import torch.nn.functional as F\n", 29 | "import torchvision\n", 30 | "import torchvision.models as models\n", 31 | "from torchvision import transforms\n", 32 | "from PIL import Image\n", 33 | "import matplotlib.pyplot as plt" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stderr", 43 | "output_type": "stream", 44 | "text": [ 45 | "Using cache found in /home/wtf/.cache/torch/hub/pytorch_vision_master\n", 46 | "Using cache found in /home/wtf/.cache/torch/hub/pytorch_vision_master\n" 47 | ] 48 | } 49 | ], 50 | "source": [ 51 | "model_resnet18 = torch.hub.load('pytorch/vision', 'resnet18', pretrained=True)\n", 52 | "model_resnet34 = torch.hub.load('pytorch/vision', 'resnet34', pretrained=True)" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "# Freeze all params except the BatchNorm layers, as here they are trained to the\n", 62 | "# mean and standard deviation of ImageNet and we may lose some signal\n", 63 | "for name, param in model_resnet18.named_parameters():\n", 64 | " if(\"bn\" not in name):\n", 65 | " param.requires_grad = False\n", 66 | " \n", 67 | "for name, param in model_resnet34.named_parameters():\n", 68 | " if(\"bn\" not in name):\n", 69 | " param.requires_grad = False" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": 4, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "# Replace the classifier\n", 79 | "num_classes = 2\n", 80 | "\n", 81 | "model_resnet18.fc = nn.Sequential(nn.Linear(model_resnet18.fc.in_features,512),\n", 82 | " nn.ReLU(),\n", 83 | " nn.Dropout(),\n", 84 | " nn.Linear(512, num_classes))\n", 85 | "\n", 86 | "model_resnet34.fc = nn.Sequential(nn.Linear(model_resnet34.fc.in_features,512),\n", 87 | " nn.ReLU(),\n", 88 | " nn.Dropout(),\n", 89 | " nn.Linear(512, num_classes))" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 5, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "def train(model, optimizer, loss_fn, train_loader, val_loader, epochs=5, device=\"cpu\"):\n", 99 | " for epoch in range(epochs):\n", 100 | " training_loss = 0.0\n", 101 | " valid_loss = 0.0\n", 102 | " model.train()\n", 103 | " for batch in train_loader:\n", 104 | " optimizer.zero_grad()\n", 105 | " inputs, targets = batch\n", 106 | " inputs = inputs.to(device)\n", 107 | " targets = targets.to(device)\n", 108 | " output = model(inputs)\n", 109 | " loss = loss_fn(output, targets)\n", 110 | " loss.backward()\n", 111 | " optimizer.step()\n", 112 | " training_loss += loss.data.item() * inputs.size(0)\n", 113 | " training_loss /= len(train_loader.dataset)\n", 114 | " \n", 115 | " model.eval()\n", 116 | " num_correct = 0 \n", 117 | " num_examples = 0\n", 118 | " for batch in val_loader:\n", 119 | " inputs, targets = batch\n", 120 | " inputs = inputs.to(device)\n", 121 | " output = model(inputs)\n", 122 | " targets = targets.to(device)\n", 123 | " loss = loss_fn(output,targets) \n", 124 | " valid_loss += loss.data.item() * inputs.size(0)\n", 125 | " \n", 126 | " correct = torch.eq(torch.max(F.softmax(output, dim=1), dim=1)[1], targets).view(-1)\n", 127 | " num_correct += torch.sum(correct).item()\n", 128 | " num_examples += correct.shape[0]\n", 129 | " valid_loss /= len(val_loader.dataset)\n", 130 | "\n", 131 | " print('Epoch: {}, Training Loss: {:.4f}, Validation Loss: {:.4f}, accuracy = {:.4f}'.format(epoch, training_loss,\n", 132 | " valid_loss, num_correct / num_examples))" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "batch_size=32\n", 142 | "img_dimensions = 224\n", 143 | "\n", 144 | "# Normalize to the ImageNet mean and standard deviation\n", 145 | "# Could calculate it for the cats/dogs data set, but the ImageNet\n", 146 | "# values give acceptable results here.\n", 147 | "img_transforms = transforms.Compose([\n", 148 | " transforms.Resize((img_dimensions, img_dimensions)),\n", 149 | " transforms.ToTensor(),\n", 150 | " transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225] )\n", 151 | " ])\n", 152 | "\n", 153 | "img_test_transforms = transforms.Compose([\n", 154 | " transforms.Resize((img_dimensions,img_dimensions)), \n", 155 | " transforms.ToTensor(),\n", 156 | " transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225] )\n", 157 | " ])\n", 158 | "\n", 159 | "def check_image(path):\n", 160 | " try:\n", 161 | " im = Image.open(path)\n", 162 | " return True\n", 163 | " except:\n", 164 | " return False\n", 165 | "\n", 166 | "train_data_path = \"/home/wtf/dogs-vs-cats/train/\"\n", 167 | "train_data = torchvision.datasets.ImageFolder(root=train_data_path,transform=img_transforms, is_valid_file=check_image)\n", 168 | "\n", 169 | "validation_data_path = \"/home/wtf/dogs-vs-cats/validation/\"\n", 170 | "validation_data = torchvision.datasets.ImageFolder(root=validation_data_path,transform=img_test_transforms, is_valid_file=check_image)\n", 171 | "\n", 172 | "test_data_path = \"/home/wtf/dogs-vs-cats/test/\"\n", 173 | "test_data = torchvision.datasets.ImageFolder(root=test_data_path,transform=img_test_transforms, is_valid_file=check_image)\n", 174 | "\n", 175 | "num_workers = 6\n", 176 | "train_data_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers)\n", 177 | "validation_data_loader = torch.utils.data.DataLoader(validation_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)\n", 178 | "test_data_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)\n", 179 | "\n", 180 | "\n", 181 | "if torch.cuda.is_available():\n", 182 | " device = torch.device(\"cuda\") \n", 183 | "else:\n", 184 | " device = torch.device(\"cpu\")" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": 7, 190 | "metadata": {}, 191 | "outputs": [ 192 | { 193 | "name": "stdout", 194 | "output_type": "stream", 195 | "text": [ 196 | "Num training images: 16000\n", 197 | "Num validation images: 4500\n", 198 | "Num test images: 4500\n" 199 | ] 200 | } 201 | ], 202 | "source": [ 203 | "print(f'Num training images: {len(train_data_loader.dataset)}')\n", 204 | "print(f'Num validation images: {len(validation_data_loader.dataset)}')\n", 205 | "print(f'Num test images: {len(test_data_loader.dataset)}')" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "metadata": {}, 211 | "source": [ 212 | "### Train and test the models" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 8, 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [ 221 | "def test_model(model):\n", 222 | " correct = 0\n", 223 | " total = 0\n", 224 | " with torch.no_grad():\n", 225 | " for data in test_data_loader:\n", 226 | " images, labels = data[0].to(device), data[1].to(device)\n", 227 | " outputs = model(images)\n", 228 | " _, predicted = torch.max(outputs.data, 1)\n", 229 | " total += labels.size(0)\n", 230 | " correct += (predicted == labels).sum().item()\n", 231 | " print('correct: {:d} total: {:d}'.format(correct, total))\n", 232 | " print('accuracy = {:f}'.format(correct / total))" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 9, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "name": "stdout", 242 | "output_type": "stream", 243 | "text": [ 244 | "Epoch: 0, Training Loss: 0.0855, Validation Loss: 0.0358, accuracy = 0.9878\n", 245 | "Epoch: 1, Training Loss: 0.0498, Validation Loss: 0.0309, accuracy = 0.9873\n" 246 | ] 247 | } 248 | ], 249 | "source": [ 250 | "model_resnet18.to(device)\n", 251 | "optimizer = optim.Adam(model_resnet18.parameters(), lr=0.001)\n", 252 | "train(model_resnet18, optimizer, torch.nn.CrossEntropyLoss(), train_data_loader, validation_data_loader, epochs=2, device=device)" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": 10, 258 | "metadata": {}, 259 | "outputs": [ 260 | { 261 | "name": "stdout", 262 | "output_type": "stream", 263 | "text": [ 264 | "correct: 4456 total: 4500\n", 265 | "accuracy = 0.990222\n" 266 | ] 267 | } 268 | ], 269 | "source": [ 270 | "test_model(model_resnet18)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 11, 276 | "metadata": {}, 277 | "outputs": [ 278 | { 279 | "name": "stdout", 280 | "output_type": "stream", 281 | "text": [ 282 | "Epoch: 0, Training Loss: 0.0678, Validation Loss: 0.0239, accuracy = 0.9907\n", 283 | "Epoch: 1, Training Loss: 0.0354, Validation Loss: 0.0317, accuracy = 0.9887\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "model_resnet34.to(device)\n", 289 | "optimizer = optim.Adam(model_resnet34.parameters(), lr=0.001)\n", 290 | "train(model_resnet34, optimizer, torch.nn.CrossEntropyLoss(), train_data_loader, validation_data_loader, epochs=2, device=device)" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": 12, 296 | "metadata": {}, 297 | "outputs": [ 298 | { 299 | "name": "stdout", 300 | "output_type": "stream", 301 | "text": [ 302 | "correct: 4450 total: 4500\n", 303 | "accuracy = 0.988889\n" 304 | ] 305 | } 306 | ], 307 | "source": [ 308 | "test_model(model_resnet34)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "metadata": {}, 314 | "source": [ 315 | "### Make some predictions" 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": 13, 321 | "metadata": {}, 322 | "outputs": [ 323 | { 324 | "name": "stdout", 325 | "output_type": "stream", 326 | "text": [ 327 | "dogs\n", 328 | "cats\n" 329 | ] 330 | } 331 | ], 332 | "source": [ 333 | "import os\n", 334 | "def find_classes(dir):\n", 335 | " classes = os.listdir(dir)\n", 336 | " classes.sort()\n", 337 | " class_to_idx = {classes[i]: i for i in range(len(classes))}\n", 338 | " return classes, class_to_idx\n", 339 | "\n", 340 | "def make_prediction(model, filename):\n", 341 | " labels, _ = find_classes('/home/wtf/dogs-vs-cats/test')\n", 342 | " img = Image.open(filename)\n", 343 | " img = img_test_transforms(img)\n", 344 | " img = img.unsqueeze(0)\n", 345 | " prediction = model(img.to(device))\n", 346 | " prediction = prediction.argmax()\n", 347 | " print(labels[prediction])\n", 348 | " \n", 349 | "make_prediction(model_resnet34, '/home/wtf/dogs-vs-cats/test/dogs/dog.11460.jpg')\n", 350 | "make_prediction(model_resnet34, '/home/wtf/dogs-vs-cats/test/cats/cat.12262.jpg')" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "metadata": {}, 356 | "source": [ 357 | "### Save the models to disk" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 14, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "torch.save(model_resnet18.state_dict(), \"./model_resnet18.pth\")\n", 367 | "torch.save(model_resnet34.state_dict(), \"./model_resnet34.pth\")" 368 | ] 369 | }, 370 | { 371 | "cell_type": "markdown", 372 | "metadata": {}, 373 | "source": [ 374 | "### Load the models from disk and test with an ensemble" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 15, 380 | "metadata": {}, 381 | "outputs": [ 382 | { 383 | "name": "stderr", 384 | "output_type": "stream", 385 | "text": [ 386 | "Using cache found in /home/wtf/.cache/torch/hub/pytorch_vision_master\n", 387 | "Using cache found in /home/wtf/.cache/torch/hub/pytorch_vision_master\n" 388 | ] 389 | }, 390 | { 391 | "name": "stdout", 392 | "output_type": "stream", 393 | "text": [ 394 | "done\n" 395 | ] 396 | } 397 | ], 398 | "source": [ 399 | "# Remember that you must call model.eval() to set dropout and batch normalization layers to\n", 400 | "# evaluation mode before running inference. Failing to do this will yield inconsistent inference result\n", 401 | "\n", 402 | "resnet18 = torch.hub.load('pytorch/vision', 'resnet18')\n", 403 | "resnet18.fc = nn.Sequential(nn.Linear(resnet18.fc.in_features,512),nn.ReLU(), nn.Dropout(), nn.Linear(512, num_classes))\n", 404 | "resnet18.load_state_dict(torch.load('./model_resnet18.pth'))\n", 405 | "resnet18.eval()\n", 406 | "\n", 407 | "resnet34 = torch.hub.load('pytorch/vision', 'resnet34')\n", 408 | "resnet34.fc = nn.Sequential(nn.Linear(resnet34.fc.in_features,512),nn.ReLU(), nn.Dropout(), nn.Linear(512, num_classes))\n", 409 | "resnet34.load_state_dict(torch.load('./model_resnet34.pth'))\n", 410 | "resnet34.eval()\n", 411 | "\n", 412 | "print(\"done\")" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": 16, 418 | "metadata": {}, 419 | "outputs": [ 420 | { 421 | "name": "stdout", 422 | "output_type": "stream", 423 | "text": [ 424 | "accuracy = 0.990889\n", 425 | "correct: 4459 total: 4500\n" 426 | ] 427 | } 428 | ], 429 | "source": [ 430 | "# Test against the average of each prediction from the two models\n", 431 | "models_ensemble = [resnet18.to(device), resnet34.to(device)]\n", 432 | "correct = 0\n", 433 | "total = 0\n", 434 | "with torch.no_grad():\n", 435 | " for data in test_data_loader:\n", 436 | " images, labels = data[0].to(device), data[1].to(device)\n", 437 | " predictions = [i(images).data for i in models_ensemble]\n", 438 | " avg_predictions = torch.mean(torch.stack(predictions), dim=0)\n", 439 | " _, predicted = torch.max(avg_predictions, 1)\n", 440 | "\n", 441 | " total += labels.size(0)\n", 442 | " correct += (predicted == labels).sum().item()\n", 443 | " \n", 444 | "print('accuracy = {:f}'.format(correct / total))\n", 445 | "print('correct: {:d} total: {:d}'.format(correct, total))" 446 | ] 447 | }, 448 | { 449 | "cell_type": "code", 450 | "execution_count": null, 451 | "metadata": {}, 452 | "outputs": [], 453 | "source": [] 454 | } 455 | ], 456 | "metadata": { 457 | "kernelspec": { 458 | "display_name": "Python 3", 459 | "language": "python", 460 | "name": "python3" 461 | }, 462 | "language_info": { 463 | "codemirror_mode": { 464 | "name": "ipython", 465 | "version": 3 466 | }, 467 | "file_extension": ".py", 468 | "mimetype": "text/x-python", 469 | "name": "python", 470 | "nbconvert_exporter": "python", 471 | "pygments_lexer": "ipython3", 472 | "version": "3.8.2" 473 | } 474 | }, 475 | "nbformat": 4, 476 | "nbformat_minor": 4 477 | } 478 | -------------------------------------------------------------------------------- /mnist-keras.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Neural Network Image Classification with Keras and the MNIST Dataset\n", 8 | "\n", 9 | "In this post we'll use Keras and implement the Hello, World of machine learning, predict which number is in an image using the MNIST database of handwritten digits, and achieve 99% classification accuracy.\n", 10 | "\n", 11 | "Much of this is inspired by the book Deep Learning with Python by François Chollet. I highly recommend reading the book if you would like to dig deeper or learn more.\n", 12 | "https://www.manning.com/books/deep-learning-with-python" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 1, 18 | "metadata": {}, 19 | "outputs": [ 20 | { 21 | "name": "stderr", 22 | "output_type": "stream", 23 | "text": [ 24 | "Using TensorFlow backend.\n" 25 | ] 26 | } 27 | ], 28 | "source": [ 29 | "%matplotlib inline\n", 30 | "\n", 31 | "from keras import models, layers\n", 32 | "from keras.datasets import mnist\n", 33 | "from keras.utils import to_categorical\n", 34 | "import matplotlib.pyplot as plt\n", 35 | "import numpy as np" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Since working with the MNIST digits is so common, Keras provides a function to load the data.\n", 43 | "\n", 44 | "You can see a full list of datasets Keras has packaged up here https://keras.io/datasets/\n", 45 | "\n", 46 | "Let's load the data." 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 2, 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "The training set consists of 60,000 28x28 pixel images, and the test set 10,000." 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 3, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "((60000, 28, 28), (10000, 28, 28))" 74 | ] 75 | }, 76 | "execution_count": 3, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "train_images.shape, test_images.shape" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "Lets look at the first ten training images. They are each 28x28 grayscale images with one color value between 0 and 255." 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 4, 95 | "metadata": { 96 | "scrolled": true 97 | }, 98 | "outputs": [ 99 | { 100 | "data": { 101 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlIAAABRCAYAAAAZ1Ej0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAFGtJREFUeJzt3XmUzfUfx/Hn2LLkF8VEi0o7ORJpkUR7EVK0KUMhhYpKRdqTNhXZypKKkGhxlJTtNNVkiJIlh1QaUhmlhTS/P+55f77XbGa+c5fvd7we53RMc+/c+Xzm3vu9n8/78/68Pyk5OTmIiIiISPGVSXYDRERERMJKAykRERERnzSQEhEREfFJAykRERERnzSQEhEREfFJAykRERERnzSQEhEREfFJAykRERERnzSQEhEREfGpXIJ/X9jLqKcU4T6lvY+lvX+gPoaB+lj6+wfqYxjs831UREpERETEJw2kRERERHzSQEpERETEJw2kRERERHzSQEpERETEJw2kQmrJkiWkpaWRlpZGmTJlKFOmjPv/zMzMZDdPREKob9++pKSkkJKSQoMGDWjQoAHfffddspslEhetWrVy/5WEBlIiIiIiPiW6jlTM7d69m+zs7DzfHz58OAB//vknAKtXrwZgxIgR9O/fH4DJkycDULFiRQYMGADA4MGD497mkli2bBkA5513Htu3bwcgJSVS4uKVV14BYNasWfz666/JaWCCzJs3D4Brr70WgAULFnD88ccns0kx8cgjjwBw//33k5MTKb0yf/58AFq0aJGsZkkhfv/9d/744w8A3nvvPQC2bNkCQL9+/dhvv/2S1rai2rBhAwCTJk1y15OVK1cCsGrVKo444ohkNS1m1qxZA8DOnTtZtGgRAL169QK8a2hB2rVrB8CUKVMAqFChQryaGRO7du3ik08+AeCee+4BcP8vcPvttwOQnp7O9ddfX+LHC8VAauPGjezcuRPwXgyLFy8GYNu2bUyfPn2vj3H44YcD0Lt3b9566y0AqlatCkDDhg0D/yH1+eefA9ChQwcAsrOz3Zv/f//7H+C9ubdu3Up6ejoAjRs33uO2eFq4cCEAv/zyC+3bt4/r78rIyACgSZMmcf09iTJhwgQAhgwZAkDZsmXZvXs3sPeLvCTW+vXrARg6dCgQuRivWLEi3/tmZWXx/PPPJ6xtftWsWROIDNZnzZqV5NbExldffQXAxIkTAZg2bRoA//33Hz/++CPgvbf29h6zv0nPnj0BGDZsmLvuBlF2djbnnHMOALVq1QIir0X7el9lAZNRo0YBUL58ec4999wSP66W9kRERER8CnREaunSpUAkISy/5buiKFu2LOAtmVSpUsUtBx1yyCEAVK9ePZDLQrYsmZmZyXXXXQfApk2b8tzv2GOPBeCuu+4CoFOnTjRr1gzw+n3vvffGvb22BLV27dq4RqT+++8/FxXYuHEjgFsGCytL6P3nn3+S3BL/PvvsMyZNmgR40UmLCgA8/fTTgPe+W7RoEZ07dwbgtNNOS2RTi23VqlVAJBLx6quvAvDXX38BkddenTp1AC/KbctiU6dOdctHJ5xwQkLbXBxVqlQBKBVLeMauebbcGgsW3eratStnnXVWzB43nrKysty/+3pE6tNPPwVwK1xnnXUWHTt2LPHjKiIlIiIi4lOgI1I2O6pRo0aRIlI2q61evToff/wx4OUG2cw3THr06AHA66+/Xuj9lixZAuASXlu0aOGiQwXlbsSDzdbOPPPMuP6en376iTFjxgDe8xrk2X5hPvzwQ4A8eTQnnHAC7777LgAHH3xwwttVHG+88QYQ2Tr/888/A16E8JxzzmHr1q0AbpOHycnJcbdZEm9Q2PXm7rvvBrw+2gaPaMcddxzvv/8+4M107fX4888/uz4G2bZt2wD48ssvk9yS2Dn//POBvBGp1NRUunXrBkSi2wBlyngxBcvDXbBgQSKaKT4tXLiQRx99FPA2jh144IEF3n/y5Mnu8/CYY44B4KmnnopJWwI9kLI/ypNPPsk777wDQKNGjQDo06ePu9/JJ58MeB9KVapUcUsKYUj0zM0GRvZBGr1sZQmErVu3dh9MtlRif5vogWQil7zsohRvN954o/valjXDaPHixXTp0gXI+wF95513BnaZ5d9//wW8hP+bbroJgB07drhNG4MGDQIioXNbrrQQug06ILibBWxDytixYwu8j12M586d6zazrF27Nv6NiwNLI8ivZlRGRoYbGAb1NZmfm2++GfB23Jny5csXusRl78WTTjoJwCWmRz/WqaeeGtO2JoItRZcW3bt3dzsxbSm9sOXWRx991O1mf+mll4DIRrNY0NKeiIiIiE+BjkiZdu3aucqjlsy5fPlyIDKytMiMJUyCN5uwJaAwiK4RBexRJ+qSSy4BvBDm/PnzXVjTIjS2hblhw4ZuO6+FtTMzMznllFPi0m57LjZv3hyXx8/NliHAC9+H0cSJE/NsHrCIYyxqm8SLJVvb8oi54IIL3BJY9NZw+150JAoiJUluuOGGeDbVt6lTp+b7/SOPPJKmTZsC8MQTTwBeaRXwktLDxqLaaWlpeWrpDR48mGrVqgFw6623JrxtfpUrF/l4i35+isJep7/99lue2+yxwlAbLLclS5ZwxhlnJLsZMVOpUiX3Off3338XeD/7XN24cWOR7u+HIlIiIiIiPoUiIgXkKX52wAEHuK9tvfOqq64C9kwcDIs1a9a4An+W6GoRptq1a7uZ+/777w9EcqRat26918e13Iennnpqr0nrfs2ePRuI/xq8RbysCjPAoYceGtffGQ+WfPzyyy+78hw24x84cGDS2lUUAwcO5LHHHgO8Ioa33HILECm1kV+RQouc5vb888+713jQ2DXFItoXXHABEMmLSk1NLfDnEhWVjZdBgwYF/nSHeLEND/ac27Uz2kMPPZTQNvlVrlw5d02xCP66deuS2aSYsfzLr776ihNPPBHIP9dpx44dgBc53rFjB6effjoAV1xxRUzbFL4Rh4iIiEhAhCYildsDDzwARNZ9bau/7dqz2WMY2I6m/v37u3wmm9Xb2XlNmjQpcbTn+++/L9HPF8bOMTT169ePy++xXLisrCxXQNVy5sLAImmXX355ntt69+4NUOJTyOPFZuKPPfaYyw+58MILAW/GV6lSJXd/y0H44IMP3E4w20FqM8q2bdsmoOX+WM6QXWeKqjScZxb24rbFYfl+Q4YMcREbK2ERzXaGly9fPnGNK4Fq1arRvHlzALfjPezsM8x20pYrV44RI0YA5BvZvuOOOwAv3/HQQw+N2/sztAMpSywfO3asS6K2bdgtW7Z026pt2SGo55VlZmYCe9Y6sXOdgn7+X0FisTV4+/btzJkzB/Audh988IG73ZbALHwdBtaf6Npeds5T3759k9KmvbFlgRdffBGIvI9sADVz5sw89//2228B7zDpL774wt125ZVXAl4F/rCykiq2dJCTk+OuL9GV3AGaNWsWugTfop4/F3TRBzGDN9GOZocX59dXm9A+8cQTbrNP9GRBEsOulzYBtVp1ffr0yfcz0mpD2fml5r777otbG7W0JyIiIuJTaCNS5uijj3Yjz7S0NCCyJGbLYjZrtO3ktWvXTnwjC2Hhx5ycHLf1PRaRqNzh+USG663oWW5WNdkKd86bNw+AH374wYXTX3vtNXcfm/1ZxXpbUtq1a1dgCzkWZObMme7kcdO8eXNXDT5680SQ2PNis0DwIjJbtmwBYPz48UAkkvr1118D8PvvvwORmb5t/rDzIqPLlASdJRxbvx566KE8lbKjI1LGlgbHjx/vNhRI4qxYsYLLLrsM8M7jLK6zzz4biBR+LA1++eWXZDehyKzo76uvvkrXrl0B7zPM3mvp6elu40u/fv2AyGfPtGnT9ri/bdSyk0LiQREpEREREZ9CH5ECaN++PeAd2dCvXz+3Hn7PPfcA3tEH9913XyC2zNvxL1YsLCUlxc2gYiF3noMlS8aDRY7sd/Xo0cPNFKJZRMpmCpa4WblyZbeN1WYfjRs3dhE6O2vusMMOAyJlFsJytl5hCeZ169YN/Dl6dlalbfnfsmULRx55JJB/Xom9tyy/ZNOmTdSoUQOANm3axLu5MbFr1y6WLl0KQIcOHQBc4dTKlSu7aJOdKTlnzhwX+Ta7d+8GYMaMGS7/zf6WkliFReMLu82StGfPnu1ypMLs7bffTnYTisxKUXTr1i3PdcaOBcvIyHDHVFnffvzxR/detWvWuHHj4t7eUjGQMg0aNAAiWfr2JrCzzEaNGgVEzsKaO3duUtoXzXbh2dJJamoqnTp1KtFj2g7A6J1Glsw8ZMiQEj12YSwR2c7hKmhnRJ06dQBvt1a9evUAXG2PglhdF1tKqlu3bglbnDi2oy2/5Z3cS31BZMn8lljeunVrt0RgExd7Prt06eLOx7Sabps2bXJfB529F+fMmeMmZ8beUy1btnTnedkSdqtWrfIcDm6v1QEDBrjXvZ3TFvSq2PkNLhYuXAiEp7J5gwYN3G5uSza/6KKLAKhYsWK+P/Pyyy8D4TyfNT8tW7YEwrVrz05BsDSdChUquGuQ1UGsXr06EEmLsYOlbUAVvcxu9fqsGv38+fM5+uij49JuLe2JiIiI+FSqIlKmWrVqdO7cGfDOodu1axcQmVnZTMWWjoKgYsWKvhPhLRL1yCOPADB06FA3CrckPKuIHk933313XB7XktJNrKvSxoMt2eY+Xw5wS7hWCysMLOE/Ouk8Pxa5sJliSkpK4COIdm2wit52wgDAxRdfDHh1vqpVq+b+Brbcs3z5chdlstIOFqGaNWsW11xzDeCdC3nXXXe5WbVp1KhRjHvlX37lD958800AVq5cCXjR5CCzCHlRTwuwqGNpiUhZJNTs3LnTpbjY3yZoRo8eDXhRpIEDB7p0j9yGDx/uNgKkp6fnud02NVlkLl7RKFBESkRERMS3UhWRWr58OQDTp093a6Y22zT16tVz21qDxE+iuUU9bAZt68tt27ZlxowZsWtcwFiuSZBZdf3oE+QtqmMlD0ojy/2LjmoEOUdq9+7drtL6k08+CUSit48//jgAV199NeDlimVkZLjolBXTPe644xg5ciTgzX63b98ORPIFraSHJcRaZAq8qMH69evj0T1fevbsCXjRgWiWrzhs2LCEtikR8oseh1m5cnt+vOfk5LjVi6CyfEvbnGORqfxs3brVlSUxU6ZM4aSTTtrje7ZJKZ4UkRIRERHxKfQRqdWrV/PCCy8AuChMVlZWnvvZ6Lx27dquQGAy2c4Y+3fmzJk899xzRf75Z555hocffhiA7OxswCt4aMVIJXlsx0j0bj07rigR+WrJYsfHhMWYMWNcJMoKhY4ePdpFFD/99FPAKzo6e/ZsF3WznKq0tLQ8M2cr/3DRRRe53WKTJ08GvKKzAM8++2zsO1VCVookTGzlwaJK5557brGOcxk3bhy33XZbXNqWLBbdsVIxq1atcpFE22kdNEU5Kss+76ZOneq+th3EHTt2jF/jChG6gZQNkmwr5PDhw12tnvzYuW92zk4sazWVRO6EzqysLPr06QN4tZQOOuggIHIxty28Vovp+++/dwmDdqHu1atXglqfXGvXrg3s+WW2bdcGyFZPCLy6Q6VZ2JZH7DBm8KopDx061CUer127Ns/PPPjgg4BXo66olcttmdD+DSpburQJqp2fCLjJnt0nngm8RbVo0SJXt87O49ywYUOhy0JWumL27NlAZFNO7lpglStXBsJ/vp5NbjZt2sQzzzyT5NaUnA0CR44c6erwffTRR8lskpb2RERERPwKRURq8+bNLqnMCsKtWrWqwPufdtppbhuyhTeDsJxXmH///ZcRI0YAkWR58M5fW7NmTZ77n3nmmbRq1QrYc1a9L7BtrUGzbNkyV+zVIo22Lb5Xr16Br2IeC+vWrUt2E4qlVq1arnimJeJa1Bfg0ksvBbxz19q1a+cqu5f2M/Tq168PBP857d27d56CqEOHDqVq1aoF/oy9T5csWQLsWerByuJYhN82EIRdSkpKqKvrW+mGsWPHApHPdCt/kIiE8sIEe3QhIiIiEmCBjEjZ+rWd1rxs2bJCZ0XNmjUDvOKTF154YeDXtS3Hp2nTpgB8/vnn7jbLA9u8ebP7np1XZlvJi5OYXtqkp6e7o3+CZNu2bXs8Z4A7l+3pp59ORpMSrnnz5kDhZ5gFycKFC93xN1bOIDU11eUpWuHMMM/k/bLZfpjOaDPFTaZOTU11+bN2bS3oKJmwys7Odq/1/M7+DDorG2KRqc6dO7t8xWQLzEDqs88+AyIhWasB9cMPPxR4f0sE7NOnj0skt103YWChSNtpOHr0aLcLL7e+ffty8803A96BjSJBZOdd2ut03bp1bhJUs2bNpLWrIFWrVnWnINi/EmHVy+vVq+cqmgfR+PHjXWJ8UWq0HXPMMe7zwwb+N910k3vtljZWX7BixYqhqEhfEJs8W923oGwcAy3tiYiIiPiWkuAQfIG/bMCAAcCe51yZevXq0aZNG8BL8Ozfvz/gVRxOkJS936XgPobE3vqY8P5NmDAB8EoLdO/ePd+qy0UUt+cwKyuLTp06AZEt2QBHHXUUkPCE3aS/Tu0569atGy1atAAipUogZue0Jb2PCRC492KMxew5tI0C9robOHCgSxGxkxCsNljbtm2pVatWMZvqW9Jfp5YO8s0337hl2hiftZf0PiZAoX1UREpERETEp8BEpEJinx95U/r7B+pjidlZcx07dnRbzTt06AB4VcJLmNOY9D4mgN6L6mMY7PN9VERKRERExCdFpIpnnx95U/r7B+pjzGzfvt3tqrUt6VY8sYS5UoHpYxzpvag+hsE+30cNpIpnn3/BUPr7B+pjGKiPpb9/oD6GwT7fRy3tiYiIiPiU6IiUiIiISKmhiJSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiITxpIiYiIiPikgZSIiIiIT/8Hn3hdHsbNVR4AAAAASUVORK5CYII=\n", 102 | "text/plain": [ 103 | "
" 104 | ] 105 | }, 106 | "metadata": { 107 | "needs_background": "light" 108 | }, 109 | "output_type": "display_data" 110 | } 111 | ], 112 | "source": [ 113 | "_, ax = plt.subplots(1, 10, figsize=(10,10))\n", 114 | "\n", 115 | "for i in range(0, 10):\n", 116 | " ax[i].axis('off')\n", 117 | " ax[i].imshow(train_images[i], cmap=plt.cm.binary)" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "And the labels representing which class the image represents." 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 5, 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "data": { 134 | "text/plain": [ 135 | "array([5, 0, 4, 1, 9, 2, 1, 3, 1, 4], dtype=uint8)" 136 | ] 137 | }, 138 | "execution_count": 5, 139 | "metadata": {}, 140 | "output_type": "execute_result" 141 | } 142 | ], 143 | "source": [ 144 | "train_labels[0:10]" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "### Build the neural network\n", 152 | "\n", 153 | "Now build the neural network. We'll be using a number of convolutional layers. Note that we only have to specify the input shape in the first layer. The last layer provides the output. It has 10 units (one for each digit 0 to 9) and uses a softmax activation to map the output of a network to a probability distribution over the predicted output classes.\n", 154 | "\n", 155 | "https://en.wikipedia.org/wiki/Convolutional_neural_network" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 5, 161 | "metadata": {}, 162 | "outputs": [ 163 | { 164 | "name": "stdout", 165 | "output_type": "stream", 166 | "text": [ 167 | "WARNING:tensorflow:From /Users/wtf/anaconda3/envs/tf_cpu/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\n", 168 | "Instructions for updating:\n", 169 | "Colocations handled automatically by placer.\n" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "model = models.Sequential()\n", 175 | "model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))\n", 176 | "model.add(layers.MaxPooling2D((2, 2)))\n", 177 | "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", 178 | "model.add(layers.MaxPooling2D((2, 2)))\n", 179 | "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", 180 | "model.add(layers.Flatten())\n", 181 | "model.add(layers.Dense(64, activation='relu'))\n", 182 | "model.add(layers.Dense(10, activation='softmax'))\n", 183 | "\n", 184 | "model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])" 185 | ] 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "metadata": {}, 190 | "source": [ 191 | "One way to see what the network looks like is to use the summary() function:" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 6, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "name": "stdout", 201 | "output_type": "stream", 202 | "text": [ 203 | "_________________________________________________________________\n", 204 | "Layer (type) Output Shape Param # \n", 205 | "=================================================================\n", 206 | "conv2d_1 (Conv2D) (None, 26, 26, 32) 320 \n", 207 | "_________________________________________________________________\n", 208 | "max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0 \n", 209 | "_________________________________________________________________\n", 210 | "conv2d_2 (Conv2D) (None, 11, 11, 64) 18496 \n", 211 | "_________________________________________________________________\n", 212 | "max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64) 0 \n", 213 | "_________________________________________________________________\n", 214 | "conv2d_3 (Conv2D) (None, 3, 3, 64) 36928 \n", 215 | "_________________________________________________________________\n", 216 | "flatten_1 (Flatten) (None, 576) 0 \n", 217 | "_________________________________________________________________\n", 218 | "dense_1 (Dense) (None, 64) 36928 \n", 219 | "_________________________________________________________________\n", 220 | "dense_2 (Dense) (None, 10) 650 \n", 221 | "=================================================================\n", 222 | "Total params: 93,322\n", 223 | "Trainable params: 93,322\n", 224 | "Non-trainable params: 0\n", 225 | "_________________________________________________________________\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "model.summary()" 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 7, 236 | "metadata": {}, 237 | "outputs": [], 238 | "source": [ 239 | "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()" 240 | ] 241 | }, 242 | { 243 | "cell_type": "markdown", 244 | "metadata": {}, 245 | "source": [ 246 | "We need to do some preprocessing of the images. We'll also use the first 50,000 training images for training, and the remaining 10,000 training examples for cross validation." 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 8, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [ 255 | "train_images = train_images.reshape((60000, 28, 28, 1))\n", 256 | "train_images= train_images.astype('float32') / 255 # rescale pixel values from range [0, 255] to [0, 1]\n", 257 | "\n", 258 | "test_images = test_images.reshape((10000, 28, 28, 1))\n", 259 | "test_images= test_images.astype('float32') / 255\n", 260 | "\n", 261 | "train_labels = to_categorical(train_labels)\n", 262 | "test_labels = to_categorical(test_labels)\n", 263 | "\n", 264 | "validation_images = train_images[50000:]\n", 265 | "validation_labels = train_labels[50000:]\n", 266 | "\n", 267 | "train_images = train_images[:50000]\n", 268 | "train_labels = train_labels[:50000]" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": 9, 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "name": "stdout", 278 | "output_type": "stream", 279 | "text": [ 280 | "WARNING:tensorflow:From /Users/wtf/anaconda3/envs/tf_cpu/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.\n", 281 | "Instructions for updating:\n", 282 | "Use tf.cast instead.\n", 283 | "Train on 50000 samples, validate on 10000 samples\n", 284 | "Epoch 1/5\n", 285 | "50000/50000 [==============================] - 20s 391us/step - loss: 0.1959 - acc: 0.9387 - val_loss: 0.0798 - val_acc: 0.9760\n", 286 | "Epoch 2/5\n", 287 | "50000/50000 [==============================] - 19s 380us/step - loss: 0.0509 - acc: 0.9845 - val_loss: 0.0513 - val_acc: 0.9849\n", 288 | "Epoch 3/5\n", 289 | "50000/50000 [==============================] - 19s 382us/step - loss: 0.0343 - acc: 0.9892 - val_loss: 0.0408 - val_acc: 0.9880\n", 290 | "Epoch 4/5\n", 291 | "50000/50000 [==============================] - 19s 379us/step - loss: 0.0257 - acc: 0.9918 - val_loss: 0.0448 - val_acc: 0.9874\n", 292 | "Epoch 5/5\n", 293 | "50000/50000 [==============================] - 19s 377us/step - loss: 0.0208 - acc: 0.9938 - val_loss: 0.0356 - val_acc: 0.9903\n" 294 | ] 295 | } 296 | ], 297 | "source": [ 298 | "history = model.fit(train_images, train_labels, epochs=5, batch_size=64, validation_data=(validation_images, validation_labels))" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": 10, 304 | "metadata": {}, 305 | "outputs": [ 306 | { 307 | "name": "stdout", 308 | "output_type": "stream", 309 | "text": [ 310 | "10000/10000 [==============================] - 1s 122us/step\n", 311 | "Accuracy: 0.992\n", 312 | "Loss: 0.027386048025220953\n" 313 | ] 314 | } 315 | ], 316 | "source": [ 317 | "test_loss, test_acc = model.evaluate(test_images, test_labels)\n", 318 | "print('Accuracy:', test_acc)\n", 319 | "print('Loss: ', test_loss)" 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": {}, 325 | "source": [ 326 | "Looks pretty good we're seeing ~99% accuracy on the test set." 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "metadata": {}, 332 | "source": [ 333 | "## Visualize training\n", 334 | "\n", 335 | "Now lets create a function that lets us graph the accuracy and loss values during training." 336 | ] 337 | }, 338 | { 339 | "cell_type": "code", 340 | "execution_count": 11, 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "def plot_accuracy_and_loss(history):\n", 345 | " acc = history.history['acc']\n", 346 | " val_acc = history.history['val_acc']\n", 347 | " loss = history.history['loss']\n", 348 | " val_loss = history.history['val_loss']\n", 349 | "\n", 350 | " epochs = range(1, len(acc) + 1)\n", 351 | "\n", 352 | " plt.plot(epochs, acc, 'bo', label='Training acc')\n", 353 | " plt.plot(epochs, val_acc, 'b', label='Validation acc')\n", 354 | " plt.title('Training and validation accuracy')\n", 355 | " plt.legend()\n", 356 | " plt.show()\n", 357 | "\n", 358 | " plt.plot(epochs, loss, 'bo', label='Training loss')\n", 359 | " plt.plot(epochs, val_loss, 'b', label='Validation loss')\n", 360 | " plt.title('Training and validation loss')\n", 361 | " plt.legend()\n", 362 | " plt.show()\n" 363 | ] 364 | }, 365 | { 366 | "cell_type": "code", 367 | "execution_count": 12, 368 | "metadata": {}, 369 | "outputs": [ 370 | { 371 | "data": { 372 | "image/png": "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\n", 373 | "text/plain": [ 374 | "
" 375 | ] 376 | }, 377 | "metadata": { 378 | "needs_background": "light" 379 | }, 380 | "output_type": "display_data" 381 | }, 382 | { 383 | "data": { 384 | "image/png": "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DTds9n4amgOCmTVbo0aNYtKkSRstmzRpEqNGjSpo+1122YWHHnqo3sevGhAee+wxOnfuXO/9NXcOCGbWbI0YMYJHH32UTz9Nr2lfsmQJS5cuZejQoRv6BQwaNIh9992XRx7Z9C28S5YsoX//9B6utWvXMnLkSAYMGMBpp53G2rVrN6S78MILNwydfeWVVwIwfvx4li5dyhFHHMERRxwBQElJCe+99x4AN954I/3796d///4bhs5esmQJ++yzD//6r/9Kv379OPbYYzc6Tj6zZ89myJAhDBgwgK9//et88MEHG47ft29fBgwYsGFQvT//+c8MHDiQgQMHsv/++7N69ep6X9t83A/BzAryb/8GtQz/X2cDB0J2L82rS5cuDB48mD/96U+cdNJJTJo0idNOOw1JtG/fnocffpgddtiB9957jyFDhnDiiSdW+17h2267je222445c+YwZ84cBg0atGHdNddcw0477cS6des46qijmDNnDt/73ve48cYbmTZtGl27dt1oXzNnzuSuu+5i+vTpRAQHHXQQw4YNY8cdd2ThwoU88MAD/PKXv+TUU0/ld7/7XY3vNzjzzDO55ZZbGDZsGD/5yU+4+uqrufnmm7n22mt57bXXaNeu3YZiqhtuuIEJEyZwyCGH8NFHH9G+ffs6XO3a+QnBzJq13GKj3OKiiODSSy9lwIABHH300bz11lu888471e7nmWee2XBjHjBgAAMGDNiwbvLkyQwaNIj999+fefPm1Tpw3bPPPsvXv/51tt9+ezp06MApp5zCX/7yFwB69+694fWZNQ2xDen9DCtXrmTYsGEAnHXWWTzzzDMb8jh69Gjuu+++DT2iDznkEC6++GLGjx/PypUrG7yntJ8QzKwgNf2Sb0wnn3wyF198MS+88AJr167d8Mt+4sSJLF++nJkzZ9K2bVtKSkryDnmdK9/Tw2uvvcYNN9zAjBkz2HHHHTn77LNr3U9NY8BVDp0Nafjs2oqMqvPHP/6RZ555hilTpvDTn/6UefPmMXbsWI4//ngee+wxhgwZwpNPPsnee+9dr/3nU9ATgqThkhZIWiRpbJ71h0l6QVKFpBE5y4+QNDtn+kTSydm6uyW9lrOu5b2R2swaXYcOHTj88MP5l3/5l40qk1etWsWXvvQl2rZty7Rp03g93wvUcxx22GFMzN7n+dJLLzFnzhwgDZ29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\n", 385 | "text/plain": [ 386 | "
" 387 | ] 388 | }, 389 | "metadata": { 390 | "needs_background": "light" 391 | }, 392 | "output_type": "display_data" 393 | } 394 | ], 395 | "source": [ 396 | "plot_accuracy_and_loss(history)" 397 | ] 398 | }, 399 | { 400 | "cell_type": "markdown", 401 | "metadata": {}, 402 | "source": [ 403 | "The above looks pretty good, we appear to be starting to overfit the data as we get further in, but training and validation sets are pretty close to each other.\n", 404 | "\n", 405 | "Now lets look at a prediction, first we'll generate predictions for the test set." 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": 13, 411 | "metadata": {}, 412 | "outputs": [], 413 | "source": [ 414 | "preds = model.predict(test_images)" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": {}, 420 | "source": [ 421 | "We'll use the network to try to figure out what the first digit in the test set is. If we manually look, it appears to be a 7." 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": 16, 427 | "metadata": {}, 428 | "outputs": [ 429 | { 430 | "data": { 431 | "text/plain": [ 432 | "" 433 | ] 434 | }, 435 | "execution_count": 16, 436 | "metadata": {}, 437 | "output_type": "execute_result" 438 | }, 439 | { 440 | "data": { 441 | "image/png": "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\n", 442 | "text/plain": [ 443 | "
" 444 | ] 445 | }, 446 | "metadata": { 447 | "needs_background": "light" 448 | }, 449 | "output_type": "display_data" 450 | } 451 | ], 452 | "source": [ 453 | "# reload the test images so it will be in a format imshow() will understand\n", 454 | "(_, _), (test_images, _) = mnist.load_data()\n", 455 | "\n", 456 | "plt.imshow(test_images[0], cmap=plt.cm.binary)" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "metadata": {}, 462 | "source": [ 463 | "Since the output of the network was a layer with 10 units and a softmax activation, we will get an array of length 10 with a prediction for each potential number. Here you can see that the network is 99.9% certain it is a seven." 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": 14, 469 | "metadata": {}, 470 | "outputs": [ 471 | { 472 | "name": "stdout", 473 | "output_type": "stream", 474 | "text": [ 475 | "[2.6081236e-12 1.8943378e-09 1.0174886e-08 6.8640638e-08 2.3309353e-11\n", 476 | " 1.9539477e-10 7.4824168e-19 9.9999988e-01 4.3342949e-10 8.6599723e-09]\n" 477 | ] 478 | } 479 | ], 480 | "source": [ 481 | "print(preds[0])" 482 | ] 483 | }, 484 | { 485 | "cell_type": "markdown", 486 | "metadata": {}, 487 | "source": [ 488 | "We can also find the class with the highest prediction score with a numpy function:" 489 | ] 490 | }, 491 | { 492 | "cell_type": "code", 493 | "execution_count": 15, 494 | "metadata": {}, 495 | "outputs": [ 496 | { 497 | "data": { 498 | "text/plain": [ 499 | "7" 500 | ] 501 | }, 502 | "execution_count": 15, 503 | "metadata": {}, 504 | "output_type": "execute_result" 505 | } 506 | ], 507 | "source": [ 508 | "np.argmax(preds[0])" 509 | ] 510 | }, 511 | { 512 | "cell_type": "markdown", 513 | "metadata": {}, 514 | "source": [ 515 | "The next step would be to retrain the model with all 60,000 training examples (remember that in the model above we trained on 50,000 examples and validated on the remaining 10,000). I'll leave that as an exercise to the reader." 516 | ] 517 | } 518 | ], 519 | "metadata": { 520 | "kernelspec": { 521 | "display_name": "Python 3", 522 | "language": "python", 523 | "name": "python3" 524 | }, 525 | "language_info": { 526 | "codemirror_mode": { 527 | "name": "ipython", 528 | "version": 3 529 | }, 530 | "file_extension": ".py", 531 | "mimetype": "text/x-python", 532 | "name": "python", 533 | "nbconvert_exporter": "python", 534 | "pygments_lexer": "ipython3", 535 | "version": "3.7.1" 536 | } 537 | }, 538 | "nbformat": 4, 539 | "nbformat_minor": 2 540 | } 541 | -------------------------------------------------------------------------------- /n-armed-bandits.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# N-armed Bandits" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Epsilon-greedy strategy\n", 15 | "\n", 16 | "Given 10 slot machines with differing probabilities of paying out, find a strategy to maximize getting paid. Each arm has a random probability of paying out, and we are trying to determine which has the highest probability.\n", 17 | "\n", 18 | "Most of the time choose the arm with the best known probability of paying out(exploitation), but occasionally (choosing from a random probability epsilon) pick a random arm to try to learn more about the environment and potentially find a better arm (exploration)." 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 1, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import numpy as np\n", 28 | "from scipy import stats\n", 29 | "import random\n", 30 | "import matplotlib.pyplot as plt\n", 31 | "\n", 32 | "n = 10 # number of arms\n", 33 | "probs = np.random.rand(n) # hidden probabilities associated with each arm\n", 34 | "eps = 0.2 # epsilon for the epsilon-greedy action selection" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": 2, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "def get_reward(prob, n=10):\n", 44 | " reward = 0\n", 45 | " for i in range(n):\n", 46 | " if random.random() < prob:\n", 47 | " reward += 1\n", 48 | " return reward" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "Given a single arm with a 70% probability of paying out a reward of 1 and running 10 iterations (so a max reward of 10), we see that the longer we run the closer we get to a mean reward of 7 for the arm." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 3, 61 | "metadata": {}, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "text/plain": [ 66 | "7.0335" 67 | ] 68 | }, 69 | "execution_count": 3, 70 | "metadata": {}, 71 | "output_type": "execute_result" 72 | } 73 | ], 74 | "source": [ 75 | "reward_test = [get_reward(0.7, n=10) for _ in range(2000)]\n", 76 | "np.mean(reward_test)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/plain": [ 87 | "(array([ 1., 3., 22., 73., 209., 383., 500., 489., 320.]),\n", 88 | " array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]),\n", 89 | " )" 90 | ] 91 | }, 92 | "execution_count": 4, 93 | "metadata": {}, 94 | "output_type": "execute_result" 95 | }, 96 | { 97 | "data": { 98 | "image/png": "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\n", 99 | "text/plain": [ 100 | "
" 101 | ] 102 | }, 103 | "metadata": { 104 | "needs_background": "light" 105 | }, 106 | "output_type": "display_data" 107 | } 108 | ], 109 | "source": [ 110 | "plt.figure(figsize=(9,5))\n", 111 | "plt.xlabel(\"Reward\",fontsize=22)\n", 112 | "plt.ylabel(\"# Observations\",fontsize=22)\n", 113 | "plt.hist(reward_test,bins=9)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "code", 118 | "execution_count": 5, 119 | "metadata": {}, 120 | "outputs": [ 121 | { 122 | "data": { 123 | "text/plain": [ 124 | "array([[0., 0.],\n", 125 | " [0., 0.],\n", 126 | " [0., 0.],\n", 127 | " [0., 0.],\n", 128 | " [0., 0.],\n", 129 | " [0., 0.],\n", 130 | " [0., 0.],\n", 131 | " [0., 0.],\n", 132 | " [0., 0.],\n", 133 | " [0., 0.]])" 134 | ] 135 | }, 136 | "execution_count": 5, 137 | "metadata": {}, 138 | "output_type": "execute_result" 139 | } 140 | ], 141 | "source": [ 142 | "# 10 actions x 2 columns\n", 143 | "# Columns: Count #, Avg Reward\n", 144 | "records = np.zeros((n, 2))\n", 145 | "records" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 6, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "# Takes the records array, an action (index value of the arm), and a new reward observation\n", 155 | "# Then updates the average reward for the arm\n", 156 | "def update_records(records, action, r):\n", 157 | " new_r = (records[action, 0] * records[action, 1] + r) / (records[action, 0] + 1)\n", 158 | " records[action, 0] += 1\n", 159 | " records[action, 1] = new_r\n", 160 | " return records" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 7, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "# Given our array of records, find the arm with the highest probability of payout\n", 170 | "def get_best_arm(records):\n", 171 | " arm_index = np.argmax(records[:, 1], axis=0)\n", 172 | " return arm_index" 173 | ] 174 | }, 175 | { 176 | "cell_type": "code", 177 | "execution_count": 8, 178 | "metadata": {}, 179 | "outputs": [ 180 | { 181 | "name": "stdout", 182 | "output_type": "stream", 183 | "text": [ 184 | "Highest probability: 0.9518422506414441\n" 185 | ] 186 | }, 187 | { 188 | "data": { 189 | "image/png": "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\n", 190 | "text/plain": [ 191 | "
" 192 | ] 193 | }, 194 | "metadata": { 195 | "needs_background": "light" 196 | }, 197 | "output_type": "display_data" 198 | } 199 | ], 200 | "source": [ 201 | "fig, ax = plt.subplots(1, 1)\n", 202 | "ax.set_xlabel(\"Plays\")\n", 203 | "ax.set_ylabel(\"Avg Reward\")\n", 204 | "fig.set_size_inches(9,5)\n", 205 | "\n", 206 | "records = np.zeros((n, 2))\n", 207 | "\n", 208 | "num_arms = 10\n", 209 | "probs = np.random.rand(num_arms) # hidden probabilities associated with each arm\n", 210 | "eps = 0.2 # epsilon for the epsilon-greedy action selection\n", 211 | "\n", 212 | "rewards = [0]\n", 213 | "for i in range(500):\n", 214 | " if random.random() > eps:\n", 215 | " choice = get_best_arm(records)\n", 216 | " else:\n", 217 | " choice = np.random.randint(num_arms)\n", 218 | " r = get_reward(probs[choice])\n", 219 | " records = update_records(records,choice,r)\n", 220 | " mean_reward = ((i+1) * rewards[-1] + r)/(i+2)\n", 221 | " rewards.append(mean_reward)\n", 222 | "ax.scatter(np.arange(len(rewards)),rewards)\n", 223 | "\n", 224 | "print('Highest probability: ' + str(np.amax(probs)))" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "## Softmax selection policy\n", 232 | "\n", 233 | "Softmax gives us a probability distribution over our options, with the largest probability being the best arm. This allows us to explore, but with a lower likelihood of picking a poorly performing arm.\n", 234 | "\n", 235 | "For this problem softmax tends to converge on an optimal policy faster than epsilon greedy, but is very sensitive to the tau value and can take some time to find a good value (whereas finding an epsion parameter tends to be more intuitive)." 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 9, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "def softmax(av, tau=1.12):\n", 245 | " softm = np.exp(av / tau) / np.sum(np.exp(av / tau))\n", 246 | " return softm" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 10, 252 | "metadata": {}, 253 | "outputs": [ 254 | { 255 | "name": "stdout", 256 | "output_type": "stream", 257 | "text": [ 258 | "Highest probability: 0.8402165336734575\n" 259 | ] 260 | }, 261 | { 262 | "data": { 263 | "image/png": "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\n", 264 | "text/plain": [ 265 | "
" 266 | ] 267 | }, 268 | "metadata": { 269 | "needs_background": "light" 270 | }, 271 | "output_type": "display_data" 272 | } 273 | ], 274 | "source": [ 275 | "n = 10\n", 276 | "probs = np.random.rand(n)\n", 277 | "records = np.zeros((n,2))\n", 278 | "\n", 279 | "fig,ax = plt.subplots(1,1)\n", 280 | "ax.set_xlabel(\"Plays\")\n", 281 | "ax.set_ylabel(\"Avg Reward\")\n", 282 | "fig.set_size_inches(9,5)\n", 283 | "rewards = [0]\n", 284 | "for i in range(500):\n", 285 | " p = softmax(records[:,1],tau=0.7)\n", 286 | " choice = np.random.choice(np.arange(n),p=p)\n", 287 | " r = get_reward(probs[choice])\n", 288 | " records = update_records(records,choice,r)\n", 289 | " mean_reward = ((i+1) * rewards[-1] + r)/(i+2)\n", 290 | " rewards.append(mean_reward)\n", 291 | "ax.scatter(np.arange(len(rewards)),rewards)\n", 292 | "\n", 293 | "print('Highest probability: ' + str(np.amax(probs)))" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "## Contextual Bandits" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 11, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "import numpy as np\n", 310 | "import torch" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": 12, 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "class ContextBandit:\n", 320 | " def __init__(self, arms=10):\n", 321 | " self.arms = arms\n", 322 | " self.init_distribution(arms)\n", 323 | " self.update_state()\n", 324 | " \n", 325 | " def init_distribution(self, arms):\n", 326 | " # Num states = Num Arms to keep things simple\n", 327 | " self.bandit_matrix = np.random.rand(arms,arms)\n", 328 | " # each row represents a state, each column an arm\n", 329 | " \n", 330 | " def reward(self, prob):\n", 331 | " reward = 0\n", 332 | " for i in range(self.arms):\n", 333 | " if random.random() < prob:\n", 334 | " reward += 1\n", 335 | " return reward\n", 336 | " \n", 337 | " def get_state(self):\n", 338 | " return self.state\n", 339 | " \n", 340 | " def update_state(self):\n", 341 | " self.state = np.random.randint(0,self.arms)\n", 342 | " \n", 343 | " def get_reward(self,arm):\n", 344 | " return self.reward(self.bandit_matrix[self.get_state()][arm])\n", 345 | " \n", 346 | " def choose_arm(self, arm):\n", 347 | " reward = self.get_reward(arm)\n", 348 | " self.update_state()\n", 349 | " return reward" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": 13, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "def one_hot(N, pos, val=1):\n", 359 | " one_hot_vec = np.zeros(N)\n", 360 | " one_hot_vec[pos] = val\n", 361 | " return one_hot_vec\n", 362 | "\n", 363 | "def running_mean(x,N=50):\n", 364 | " c = x.shape[0] - N\n", 365 | " y = np.zeros(c)\n", 366 | " conv = np.ones(N)\n", 367 | " for i in range(c):\n", 368 | " y[i] = (x[i:i+N] @ conv)/N\n", 369 | " return y" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 14, 375 | "metadata": {}, 376 | "outputs": [], 377 | "source": [ 378 | "arms = 10\n", 379 | "loss_fn = torch.nn.MSELoss()\n", 380 | "env = ContextBandit(arms)\n", 381 | "\n", 382 | "N = 1\n", 383 | "D_in = arms\n", 384 | "H = 100\n", 385 | "D_out = arms" 386 | ] 387 | }, 388 | { 389 | "cell_type": "code", 390 | "execution_count": 15, 391 | "metadata": {}, 392 | "outputs": [], 393 | "source": [ 394 | "model = torch.nn.Sequential(\n", 395 | " torch.nn.Linear(D_in, H),\n", 396 | " torch.nn.ReLU(),\n", 397 | " torch.nn.Linear(H, D_out),\n", 398 | " torch.nn.ReLU(),\n", 399 | ")" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": 16, 405 | "metadata": {}, 406 | "outputs": [], 407 | "source": [ 408 | "def train(env, epochs=5000, learning_rate=1e-2):\n", 409 | " # Convert the environment's current state to a PyTorch variable\n", 410 | " cur_state = torch.Tensor(one_hot(arms,env.get_state()))\n", 411 | " \n", 412 | " optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n", 413 | " rewards = []\n", 414 | " for i in range(epochs):\n", 415 | " y_pred = model(cur_state)\n", 416 | " \n", 417 | " # Convert reward predictions to probability distribution with softmax\n", 418 | " av_softmax = softmax(y_pred.data.numpy(), tau=2.0)\n", 419 | " \n", 420 | " # Normalize distribution to ensure it sums to 1\n", 421 | " av_softmax /= av_softmax.sum()\n", 422 | " \n", 423 | " # Choose new action probabilistically\n", 424 | " choice = np.random.choice(arms, p=av_softmax)\n", 425 | " \n", 426 | " # Take action, recieve reward\n", 427 | " cur_reward = env.choose_arm(choice)\n", 428 | " \n", 429 | " # Convert PyTorch data to a numpy array\n", 430 | " one_hot_reward = y_pred.data.numpy().copy()\n", 431 | " \n", 432 | " # Update one_hot_reward array to use as labeled training data\n", 433 | " one_hot_reward[choice] = cur_reward\n", 434 | " reward = torch.Tensor(one_hot_reward)\n", 435 | " rewards.append(cur_reward)\n", 436 | " loss = loss_fn(y_pred, reward)\n", 437 | " optimizer.zero_grad()\n", 438 | " loss.backward()\n", 439 | " optimizer.step()\n", 440 | " \n", 441 | " # Update current environment state\n", 442 | " cur_state = torch.Tensor(one_hot(arms,env.get_state()))\n", 443 | " return np.array(rewards)" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": 17, 449 | "metadata": {}, 450 | "outputs": [], 451 | "source": [ 452 | "rewards = train(env)" 453 | ] 454 | }, 455 | { 456 | "cell_type": "code", 457 | "execution_count": 18, 458 | "metadata": {}, 459 | "outputs": [ 460 | { 461 | "data": { 462 | "text/plain": [ 463 | "[]" 464 | ] 465 | }, 466 | "execution_count": 18, 467 | "metadata": {}, 468 | "output_type": "execute_result" 469 | }, 470 | { 471 | "data": { 472 | "image/png": "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\n", 473 | "text/plain": [ 474 | "
" 475 | ] 476 | }, 477 | "metadata": { 478 | "needs_background": "light" 479 | }, 480 | "output_type": "display_data" 481 | } 482 | ], 483 | "source": [ 484 | "plt.plot(running_mean(rewards,N=500))" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": null, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [] 493 | }, 494 | { 495 | "cell_type": "code", 496 | "execution_count": null, 497 | "metadata": {}, 498 | "outputs": [], 499 | "source": [] 500 | }, 501 | { 502 | "cell_type": "code", 503 | "execution_count": null, 504 | "metadata": {}, 505 | "outputs": [], 506 | "source": [] 507 | } 508 | ], 509 | "metadata": { 510 | "kernelspec": { 511 | "display_name": "Python 3", 512 | "language": "python", 513 | "name": "python3" 514 | }, 515 | "language_info": { 516 | "codemirror_mode": { 517 | "name": "ipython", 518 | "version": 3 519 | }, 520 | "file_extension": ".py", 521 | "mimetype": "text/x-python", 522 | "name": "python", 523 | "nbconvert_exporter": "python", 524 | "pygments_lexer": "ipython3", 525 | "version": "3.8.2" 526 | } 527 | }, 528 | "nbformat": 4, 529 | "nbformat_minor": 4 530 | } 531 | --------------------------------------------------------------------------------