├── .github └── book-cover.png ├── .gitignore ├── 1_logistic_regression.ipynb ├── 2_linear_regression.ipynb ├── 3_decision_trees.ipynb ├── 4_k_means.ipynb ├── 5_sentiment_analysis.ipynb ├── 6_recommender_systems.ipynb ├── 7_neural_nets.ipynb ├── 8_reinforcement_learning.ipynb ├── LICENSE └── README.md /.github/book-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/curiousily/Machine-Learning-from-Scratch/60fd9ac4e96da017343792e840b932a8f998d691/.github/book-cover.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /1_logistic_regression.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "1.logistic_regression_final.ipynb", 7 | "version": "0.3.2", 8 | "provenance": [], 9 | "collapsed_sections": [] 10 | }, 11 | "kernelspec": { 12 | "name": "python3", 13 | "display_name": "Python 3" 14 | }, 15 | "accelerator": "GPU" 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "code", 20 | "metadata": { 21 | "id": "GMkVuv4djG4x", 22 | "colab_type": "code", 23 | "colab": {} 24 | }, 25 | "source": [ 26 | "import numpy as np\n", 27 | "import pandas as pd\n", 28 | "import seaborn as sns\n", 29 | "from pylab import rcParams\n", 30 | "import matplotlib.pyplot as plt\n", 31 | "from collections import OrderedDict\n", 32 | "from scipy.special import expit\n", 33 | "import unittest\n", 34 | "\n", 35 | "%matplotlib inline\n", 36 | "\n", 37 | "sns.set(style='whitegrid', palette='muted', font_scale=1.5)\n", 38 | "\n", 39 | "rcParams['figure.figsize'] = 14, 8\n", 40 | "\n", 41 | "RANDOM_SEED = 42\n", 42 | "\n", 43 | "np.random.seed(RANDOM_SEED)\n", 44 | "\n", 45 | "def run_tests():\n", 46 | " unittest.main(argv=[''], verbosity=1, exit=False)" 47 | ], 48 | "execution_count": 0, 49 | "outputs": [] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": { 54 | "id": "V-I1L92c1Dno", 55 | "colab_type": "text" 56 | }, 57 | "source": [ 58 | "# Your data" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "metadata": { 64 | "id": "0WFneRgf2NGQ", 65 | "colab_type": "code", 66 | "colab": {} 67 | }, 68 | "source": [ 69 | "data = OrderedDict(\n", 70 | " amount_spent = [50, 10, 20, 5, 95, 70, 100, 200, 0],\n", 71 | " send_discount = [0, 1, 1, 1, 0, 0, 0, 0, 1]\n", 72 | " )" 73 | ], 74 | "execution_count": 0, 75 | "outputs": [] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "metadata": { 80 | "id": "Qg_P_LCv6kFZ", 81 | "colab_type": "code", 82 | "outputId": "541955cd-a5c6-4b1f-f974-679e7b39e9d9", 83 | "colab": { 84 | "base_uri": "https://localhost:8080/", 85 | "height": 328 86 | } 87 | }, 88 | "source": [ 89 | "df = pd.DataFrame.from_dict(data)\n", 90 | "df" 91 | ], 92 | "execution_count": 0, 93 | "outputs": [ 94 | { 95 | "output_type": "execute_result", 96 | "data": { 97 | "text/html": [ 98 | "
\n", 99 | "\n", 112 | "\n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | "
amount_spentsend_discount
0500
1101
2201
351
4950
5700
61000
72000
801
\n", 168 | "
" 169 | ], 170 | "text/plain": [ 171 | " amount_spent send_discount\n", 172 | "0 50 0\n", 173 | "1 10 1\n", 174 | "2 20 1\n", 175 | "3 5 1\n", 176 | "4 95 0\n", 177 | "5 70 0\n", 178 | "6 100 0\n", 179 | "7 200 0\n", 180 | "8 0 1" 181 | ] 182 | }, 183 | "metadata": { 184 | "tags": [] 185 | }, 186 | "execution_count": 3 187 | } 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "metadata": { 193 | "id": "qOw7gRk48G2A", 194 | "colab_type": "code", 195 | "outputId": "b0954f17-2b85-4cfb-e17a-59999a2427d8", 196 | "colab": { 197 | "base_uri": "https://localhost:8080/", 198 | "height": 506 199 | } 200 | }, 201 | "source": [ 202 | "df.plot.scatter(x='amount_spent', y='send_discount', s=108, c=\"blue\");" 203 | ], 204 | "execution_count": 0, 205 | "outputs": [ 206 | { 207 | "output_type": "display_data", 208 | "data": { 209 | "image/png": "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\nrUePrUV/rUePrUePrUePrUV/rVfdelxWPQ499Q4AAAAAagKCEgAAAAAYCEoAAAAAYCAoAQAAAICB\noAQAAAAABoISAAAAABgISgAAAABgICgBAAAAgIGgBAAAAAAGghIAAAAAGAhKAAAAAGAgKAEAAACA\ngaAEAAAAAAaCEgAAAAAYCEoAAAAAYCAoAQAAAICBoAQAAAAABoISAAAAABhcbDabzdlFWCEhIcHZ\nJQAAAACoAcLDw0uNXbVBCQAAAAAuF6feAQAAAICBoAQAAAAABoISAAAAABgISgAAAABgICgBAAAA\ngIGgBAAAAACG2s4u4GqWn5+vadOmaePGjcrJyVFgYKBeeOEFdenSxdml1VgZGRmaMWOGNm3apFOn\nTikwMFAvv/yybr/9dsXFxWn27NlydXUt8ZrnnntOL730kpMqrnkiIyOVnp6uWrVK/j/KsmXL5O/v\nrxUrVig+Pl4///yz/Pz8dP/99+uFF17Qdddd56SKa5adO3eqf//+pcaLior08MMPq0mTJuzHlyEt\nLU2xsbHasWOH1q5dq2bNmhUvq2ifzczM1OTJk7Vz507l5+erdevWev311xUcHOyszal2yuvvwoUL\ntXDhQv3222/y8fHRww8/rKFDh6pWrVo6fPiwunXrJldXV7m4uBS/xs/PT+vWrXPGplRbZfXYnn/b\n0tLSNHnyZO3evVs2m01t27bVqFGjdNNNNzl8O6qzsnrcvXt3HT16tMRcm82mwsJCHThwgP3YTuV9\nRpNq6LHYBsvExMTY/vKXv9gOHTpkO336tG3RokW24OBg28GDB51dWo31yCOP2Pr37287fvy47fTp\n07YZM2bY2rVrZzt27JjtnXfesT3xxBPOLrHG69q1q+3zzz+/5LLt27fb2rRpY1u1apXtzJkztv37\n99vuueceW1xcnIOrvLocP37c1qFDB9v27dvZjy/D6tWrbbfffrvt9ddft7Vq1cqWlpZWvMyeffbJ\nJ5+0PfPMM7bffvvNlpuba/v73/9u69Chgy0zM9MZm1PtlNffRYsW2cLDw23bt2+3FRUV2b777jtb\nWFiYbf78+TabzWZLS0sr9RqUVl6PKzomFBQU2Lp3724bPny4LSMjw5aTk2OLiYmx3XfffbaCggJH\nlF8jlNfjS3n55ZdtMTExNpuN/dhe5X1Gq6nHYk69s0hOTo6WL1+uYcOGyd/fX3Xq1FF0dLQCAgK0\n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\nunbtqqFDh6ply5Zq166dpk6dqhMnTug///lPpevs1KmToqKidNNNN6lPnz4KCAhQcnKyJOn6669X\nrVq1VLduXfn5+cnNzc2ude7Zs0f16tXTX/7yFzVt2lS33HKLJk+erDlz5ui6664rnpefn6+JEycq\nMDBQoaGhev3113XixAlt3bpVkjRnzhw1bdpU06ZNU0BAgNq2basZM2YoPT1dX331VYn37Nmzp3r0\n6KGbbrpJAwcOVP369Yu3o2HDhpKk+vXry8/Pr0QNAICqRVACAJSrVq1aWrBggXr06KGIiAiFhYVp\n/vz5OnXqlAoKCorn3XrrrcU/e3l5XXLsjz/+kCTl5ubq559/1m233VbivVq2bCkPDw99//33la6z\nbdu2Jf5+/fXXKzs7u9LruViXLl107tw59evXT4sWLVJqaqrq16+vtm3blghbwcHBJf7epk0bSVJa\nWpokaffu3erUqVOJYNO0aVM1b9681LZevB21atWSt7f3FW8HAKDyCEoAgDLZbDY999xzWrp0qQYN\nGqRPPvlEX331laKjo0vNrV+/fvHPLi4uZY5JKr52qEGDBqXW06BBg1Kno9nj4ve68H4XvvG6XLfe\nequWLFmiwMBAvfPOO+rRo4d69uyptWvXlpjn7u5+yVounEKYm5urr776SmFhYSX+/Prrr/r9998t\n3w4AQOXVdnYBAIDq64cfftD+/fs1ceJE9e7du3j84m+SLseFYHEhMF0sNzdXnp6ekv4vXF0cFC4n\nRF2JoKAgTZs2TTabTSkpKZo7d66GDRumVatW6eabb5akEtcrXVyjt7e3JMnT01N33HGHhg0bVmr9\ndevWtXYDAACXhW+UAABlKiwslPR/H/il80Fm9erVknTZ33S4u7vr5ptvVkJCQonxH374Qbm5uQoJ\nCZEkeXh4SJJycnKK51zJ7bErW29CQkLx9UEuLi4KDQ0tvgvdxafM7d69u8Sd+S7cFS8gIECS1K5d\nOx06dEgtWrQo8aeoqEi+vr6WbwcAoPIISgCAMrVs2VJeXl5auHChUlNTlZiYqP79++vPf/6zpPN3\nczO/TbHXwIEDtWHDBr377rv65Zdf9N1332n48OFq3ry5unfvLknFgem9995TWlqatm7dqjlz5pQ6\nPc0eXl5e+v7777Vv3z6dOHHCrtesX79eQ4YM0erVq3XkyBEdOnRI77//vurXr6/Q0NDiea6urho9\nerR+/PFHJScna9q0aWrcuLE6deokSXr++ee1f/9+TZ48WT/88INSU1M1a9YsPfTQQ/ruu+/s3oba\ntWurQYMGSkpK0v79+y/r7oAAAPsQlAAAZapfv75mzJih48ePq1evXho7dqwGDRqkV155RYGBgRo2\nbFjxNy6V1bdvX02aNEkrVqxQz5499de//lUtW7bUxx9/rHr16kmSbrvtNr344otau3atevbsqXfe\neUdjxoyx+651FxswYIDS09P16KOPaufOnXa95sUXX1RUVJSmTZumHj166NFHH9W+ffs0d+5cNWvW\nrHheRESEbr31Vg0YMECPP/643Nzc9O677xbfvCEiIkJz585VSkqKoqKi1KtXL23atEmzZ89Wx44d\nK7UdQ4YMUWJioh5//PESz5wCAPy/du7YhmEQiAIokZgChmAHz8I+TMNQ3oAu6c9SishyLPm9+orf\nfh3cuV5v+3sA+Nm2bam1lsYY/44CwIlslAAAAAJX7wB4nN774ZBEVEpJc86LEgFwN57eAfA4+76n\ntdbXmZxzqrVelAiAu1GUAAAAAn+UAAAAAkUJAAAgUJQAAAACRQkAACBQlAAAAIIP22XMx7fmp08A\nAAAASUVORK5CYII=\n", 210 | "text/plain": [ 211 | "
" 212 | ] 213 | }, 214 | "metadata": { 215 | "tags": [] 216 | } 217 | } 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": { 223 | "id": "OAly_VfNEtiQ", 224 | "colab_type": "text" 225 | }, 226 | "source": [ 227 | "# Making decisions with Logistic regression\n", 228 | "\n", 229 | "Logistic regression is used for classification problems when the dependant/target variable is binary. That is, its values are true or false. Logistic regression is one of the most popular and widely used algorithms in practice ([see this](https://www.kaggle.com/surveys/2017)).\n", 230 | "\n", 231 | "Some examples for problems that can be solved with Logistic regression are:\n", 232 | "\n", 233 | "- Email - deciding if it is spam or not\n", 234 | "- Online transactions - fraudelent or not\n", 235 | "- Tumor - malignant or bening\n", 236 | "- Customer upgrade - will the customer buy the premium upgrade or not\n", 237 | "\n", 238 | "We want to predict the outcome of a variable $y$, such that:\n", 239 | "\n", 240 | "$$y \\in \\{0, 1\\}$$\n", 241 | "\n", 242 | "and set $0$: negative class (e.g. email is not spam) or $1$: positive class (e.g. email is spam).\n", 243 | "\n", 244 | "## Can't we just use Linear regression?\n", 245 | "\n", 246 | "![](https://www.machinelearningplus.com/wp-content/uploads/2017/09/linear_vs_logistic_regression.jpg)\n", 247 | "\n", 248 | "The response target variable $y$ of the Linear regression model is not restricted within the $[0, 1]$ interval." 249 | ] 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "metadata": { 254 | "id": "xCclnDlvYxbY", 255 | "colab_type": "text" 256 | }, 257 | "source": [ 258 | "# Logistic regression model\n", 259 | "\n", 260 | "Given our problem, we want a model that uses 1 variable (predictor) ($x_1$ - amount_spent) to predict whether or not we should send a discount to the customer.\n", 261 | "\n", 262 | "$$h_w(x) = w_1x_1 + w_0$$\n", 263 | "\n", 264 | "where the coefficients $w_i$ are paramaters of the model. Let the coeffiecient vector $W$ be:\n", 265 | "\n", 266 | "$$\n", 267 | " W =\n", 268 | " \\begin{pmatrix}\n", 269 | " w_1 \\\\\n", 270 | " w_0 \\\\\n", 271 | " \\end{pmatrix}\n", 272 | "$$\n", 273 | "\n", 274 | "Then we can represent $h_w(x)$ in more compact form:\n", 275 | "\n", 276 | "$$h_w(x) = w^Tx$$\n", 277 | "\n", 278 | "That is the *Linear regression model*.\n", 279 | "\n", 280 | "We want to build a model that outputs values that are between $0$ and $1$, so we want to come up with a hypothesis that satisfies $0 \\leq h_w(x) \\leq 1$. For *Logistic regression* we want to modify this and introduce another function $g$:\n", 281 | "\n", 282 | "$$h_w(x) = g(w^Tx)$$\n", 283 | "\n", 284 | "We're going to define $g$ as:\n", 285 | "\n", 286 | "$$g(z) = \\frac{1}{1 + e ^{-z}}$$\n", 287 | "\n", 288 | "where $z \\in \\mathbb{R}$. $g$ is also known as the **sigmoid function** or the **logistic function**. So, after substition, we end up with this definition:\n", 289 | "\n", 290 | "$$h_w(x) = \\frac{1}{1 + e ^{-(w^Tx)}}$$\n", 291 | "\n", 292 | "for our hypothesis.\n", 293 | "\n", 294 | "# A closer look at the sigmoid function\n", 295 | "\n", 296 | "Recall that the sigmoid function is defined as:\n", 297 | "\n", 298 | "$$g(z) = \\frac{1}{1 + e ^{-z}}$$\n", 299 | "\n", 300 | "where $z \\in \\mathbb{R}$. Let's translate that to a Python function:" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "metadata": { 306 | "id": "nIU1rj4OjJf_", 307 | "colab_type": "code", 308 | "colab": {} 309 | }, 310 | "source": [ 311 | "def sigmoid(z):\n", 312 | "# return 1 / (1 + np.exp(-z))\n", 313 | " return expit(z)\n" 314 | ], 315 | "execution_count": 0, 316 | "outputs": [] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "metadata": { 321 | "id": "RW5HT9yhlph2", 322 | "colab_type": "code", 323 | "colab": {} 324 | }, 325 | "source": [ 326 | "class TestSigmoid(unittest.TestCase):\n", 327 | "\n", 328 | " def test_at_zero(self):\n", 329 | " self.assertAlmostEqual(sigmoid(0), 0.5)\n", 330 | " \n", 331 | " def test_at_negative(self):\n", 332 | " self.assertAlmostEqual(sigmoid(-100), 0)\n", 333 | " \n", 334 | " def test_at_positive(self):\n", 335 | " self.assertAlmostEqual(sigmoid(100), 1)" 336 | ], 337 | "execution_count": 0, 338 | "outputs": [] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "metadata": { 343 | "id": "Esx-TA91nmVN", 344 | "colab_type": "code", 345 | "outputId": "69c570ba-0053-417d-f747-18c6fae4fe3f", 346 | "colab": { 347 | "base_uri": "https://localhost:8080/", 348 | "height": 102 349 | } 350 | }, 351 | "source": [ 352 | "run_tests()" 353 | ], 354 | "execution_count": 0, 355 | "outputs": [ 356 | { 357 | "output_type": "stream", 358 | "text": [ 359 | "...\n", 360 | "----------------------------------------------------------------------\n", 361 | "Ran 3 tests in 0.006s\n", 362 | "\n", 363 | "OK\n" 364 | ], 365 | "name": "stderr" 366 | } 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "metadata": { 372 | "id": "MVgCLBecoQIn", 373 | "colab_type": "code", 374 | "outputId": "60b66394-95ab-47bf-f9f6-a322721f8784", 375 | "colab": { 376 | "base_uri": "https://localhost:8080/", 377 | "height": 506 378 | } 379 | }, 380 | "source": [ 381 | "x = np.linspace(-10., 10., num=100)\n", 382 | "sig = sigmoid(x)\n", 383 | "\n", 384 | "plt.plot(x, sig, label=\"sigmoid\")\n", 385 | "plt.xlabel(\"x\")\n", 386 | "plt.ylabel(\"y\")\n", 387 | "plt.legend(prop={'size' : 16})\n", 388 | "plt.show()" 389 | ], 390 | "execution_count": 0, 391 | "outputs": [ 392 | { 393 | "output_type": "display_data", 394 | "data": { 395 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0oAAAHpCAYAAAC8zcyEAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzs3Xl0VGWC/vGnKpWq7DskkCC7CEIQ\ngiwKKkFaBFwAlYja3dM/bT32MDO2jdPSaDNM26I93XaPeysu476hgigi4AqJSAggoICyJpB9TypL\nVd3fHwE0bAZI1a3l+zmHk+TeW5XHF8zl4d77vhbDMAwBAAAAAI6wmh0AAAAAAPwNRQkAAAAAjkJR\nAgAAAICjUJQAAAAA4CgUJQAAAAA4CkUJAAAAAI5iMzuAt+Tn55sdAQAAAEAAyMrKOmZb0BYl6fj/\nwWbJz8/3qzzBiDH2LsbX+xhj72OMvY8x9i7G1/sYY+/ztzE+0QUWbr0DAAAAgKNQlAAAAADgKBQl\nAAAAADgKRQkAAAAAjkJRAgAAAICjUJQAAAAA4CgUJQAAAAA4CkUJAAAAAI5CUQIAAACAo1CUAAAA\nAOAoFCUAAAAAOApFCQAAAACOQlECAAAAgKP4vCjt379fN910kwYMGKDCwsKTHrtmzRrl5ORoxIgR\nGj9+vO699145nU4fJQ0u2dnZ+sMf/mB2DH355ZcaMGCA1q9ff8JjFi9erAEDBqi4uNiHyQAAAIAf\n+LQoffTRR5o5c6a6d+/+k8fu2bNHt912m6ZMmaLPP/9c//d//6ctW7ZowYIFPkgafN58803dfffd\nZsfQsGHD9MUXX2jo0KFmRwEAAABOyKdFqbq6Wi+99JKuuuqqnzz2tddeU58+fXTTTTcpMjJSPXr0\n0O23364lS5aosrLSB2mDS1JSkmJiYsyOIbvdri5duig8PNzsKAAAAMAJ2Xz5za699lpJ0sGDB3/y\n2I0bNyozM7PdtszMTLlcLm3dulXjxo3zSsZAlpubq3/84x/asWOHJGngwIG68847NXz4cGVnZ2vM\nmDG67777JEnLli3T3//+dxUXF2vQoEFasGCBbrjhBv3qV7/S7bffrsWLF+vuu+/W4sWLde+992rn\nzp3q16+f/vrXv2r79u3629/+prKyMo0aNUoLFy5UQkKCJKmwsFAPPPCA8vLy5HQ61bt3b91yyy26\n8sorJbXdevfzn/9cL730kkaMGKGWlhYtWLBAH3zwgSRp4sSJGjJkiAmjBwAA8APDMOQxJBmScehr\n48jnktS2v+3zQ/t16PijjpN+2Pbj9zuyt93x7V9z+Ksf9h3ZffjDkfdqn/+Yw4/ZfvTeo/ed5NBj\n97X7HifbKzW7Trrbb/i0KJ2KyspKxcfHt9uWmJgoSaqoqDAjkl+rqanR7bffruuuu04PPPCAXC6X\nnnvuOf3617/WZ5991u7YHTt2aM6cOZo8ebIee+wx7d69W3Pnzj3u819///vfNW/ePNlsNv3mN7/R\n7373O3Xt2lWPPvqoysvLdfvtt+uFF17Q7Nmz5XQ69Ytf/EJdu3bVU089pYSEBL3zzjuaM2eOYmJi\nlJ2dfcz7/+///q+WLFmiP/3pT8rMzNQnn3yiJ5980mvjBAAAOs4wDLW6DLW4PGpxGWp1edTS2vZ1\nq8tQq7ttv8t96OtD21zutm0utyG3R2p1e+Q+tN3t0aGPh34d+VxHtnkOfd3+Y1sxObzfY+i4Hw1D\n8hg/lBiP8cM2w3P0th8VIh0+xirjzY3mDnyQOyvZqgtGmZ3ip/ltUToZi8XSoePy8/NP+b2Xb7Zo\nS2HH3v/UWKX3Czp05OAMQ5MyT97Ej/bdd9+psbFRffv2VXl5uSRpypQpGjRokDZv3qzm5maVl5cr\nPz9fr732mux2u2bMmKHa2lolJydr7Nix2rJliw4cOKD8/Hzt2bNHkjR8+HB5PB61tLRo6NChWrFi\nhR555BHV1tbKbrdrwIABys3N1QUXXKA1a9aosLBQd9xxh9xutyoqKjRu3DitXLlSjz/+uOLj449c\n7dq+fbssFoveeOMNjR07Vunp6aqoqNCQIUPUp08flZSUaPPmzSoqKjqlcTid33N0HOPrfYyx9zHG\n3scYe9eJxtfllppa2341H/rY5JKaWy1qcandr2a31NIqtbgtanVJre6jfh3a5vJ44+9Ep89qMWSx\nSFaLjvvRoh99fmi79fA2q2QJ+2GfRZIO7dehry1HPrb9PezHx/14//H2Hd52aNNR23/4e93R+473\nuh99OOaYdu9zkuOPfs3JfidP9lfrY3Z18D1Ppm9XIyB+TvhtUUpJSVF1dXW7bVVVVZKkLl26dOg9\nsrKyTvn7FpQUaUdp9U8feIpaWlpkt9s7dGxqaoKystJP6f0HDhyoJ598Uk888YSuv/56jR07Vuec\nc45Gjx4tSXI4HEpJSVFWVpZefPFF9e7dW2PGjDny+l69eumJJ55Q9+7dlZWVpb1790qSLr/8cvXq\n1UtS2yyE69ev18SJE4+8rmfPnjpw4ICysrK0YsUKJSQkaOrUqe2yjRkzRu+9956ysrLkcrVdax0w\nYID69++v6upqjRs3rt3v1fjx45Wbm6vMzEylpaV1eAzy8/NP6/ccHcP4eh9j7H2MsfcxxmfOMAzV\nO92qrnepusGl6nqXaupdqm106fu9BxQVm6w6p1t1jW7VOV2qb3SrzumWy31q/8j6Y7YwixzhFjnC\nrYqOtMpht8pus8oebpHdZpUj3KJwm1V2W9vX4ba2r8PDLAq3WWQLO/T14c/D2j6GHfo8zNp2XJj1\n0HZr274w6+FtktVqUZjF0vYxTAqzWmS1WBRmPfS51TeljT/D3udvY3yi0ua3RWnYsGH69NNP223L\nz8+X3W736jMsN09O182TT62kdETbH4hzO/19D4uKitIrr7yip556Si+++KL+53/+R+np6ZozZ44u\nv/zydsdWV1cfM7HD0bc5HhYREXHkc4vFosjIyHb7f3x1r76+/rgTRkRHR6u+vv6Y7Q0NDZJ0zHtG\nRUUdNwsAAIHMMAzVNrpVUduq8ppWVdS2qqK2RRW1rkOft6qmwaXq+la5PSd6F6uktn84tlqkmKgw\nxUbY1DXBrqiIMEVFWBUdEaZoR5iiD30d5QhTpMOqCHvbx0i7VRH2tu0Ou1WOcKtsYf515QjwB35T\nlDZv3qy77rpLzzzzjLp3766cnBy9+OKLeu6555STk6MDBw7o4Ycf1rXXXqvY2Fiz4/ql1NRUzZs3\nT/PmzdO3336rxx9/XL/97W/Vv3//dsc5HA41Nja223b01bvTERsbe9xCVF9ff9zfs8MF6ehno+rq\n6s44CwAAZnA2u3WgolnFVS0qqWxp+/ijX00tJ2xAirBblRhj09kZUUqIsSk+OlwJMTYlRNsUH21T\nXLRNhXt3aOTwwYqNsinSbvXZVRYgFPm0KF122WU6cODAkZkwJk2aJIvFoquuukpXXHGFdu/erdbW\nVklSRkaGnnrqKT344IP661//qri4OE2dOlV33nmnLyMHjL1792rXrl0aP368JOmcc87RggULtHz5\ncn3//fftju3Zs6fWrVun5uZmORwOSdKKFSvOOMPgwYP17LPP6vvvv1ffvn2PbC8oKDjuVcCEhAQl\nJibq66+/1vXXX39k+9q1a884CwAA3mIYhsprW1VY1qz9pU1tH8uaVFjerPKa1uO+JjrCqu7JDqUm\nhislzq7k+HAlxYYrJT5cyXFtv6Ijwn76e9dKaUmOzv5PAnAcPi1KH3744Un3b9++vd3X559/vt54\n4w1vRgoa+/bt07/+67/q7rvv1sUXXyzDMPT666/L4XAcM836ZZddpueee0733XeffvnLX+q7777T\n0qVLzzjDxIkT1bNnT/3nf/6n7r33XsXExOiNN97Q1q1b9eyzzx73NVOnTtWbb76psWPHatCgQfro\no4/03XffnXEWAAA6Q6vLo32lTdp10KnvDzj1/UGndh1wqrH52CtDXRPCNaxfrDJSHEpNsist0a7U\nJLtSE+2KjfSbm3gAdBD/1waJcePG6b/+67/0/PPP6y9/+YvCw8M1YMAAPfHEE+rWrVu7Y4cPH657\n7rlHTz75pJYsWaJhw4bpz3/+syZNmnTkCtPpcDgcevbZZ3X//ffrX/7lX9TS0qL+/fvrscceazdx\nxI/99re/VW1trf7whz/IarVqwoQJuuOOO3TXXXeddg4AAE6HYRgqKm/W1r0N+mZvg3YWObWvtKnd\nJAlWi5TexaGs1Ej16OJQjy4RyujqUEaKQxH2n74iBCBwUJSCyDXXXKNrrrnmuPtWr1595HPDMDRp\n0iTNmjVLVqtVko7cnpeRkSFJmj59uqZPn97uPWbPnq3Zs2e327Zw4cJ2X6enp+uRRx45YcZRo0a1\nu3IYFRWlBx988JjjrrrqqhO+BwAAnaGl1aMdRY3atrdB2w6Vo9pG95H9dptFfbtFqk/3SPXtHqk+\n3SLVOy2CQgSECIpSCNqxY4euvvpq3XTTTbrhhhvU0NCgBx98UKmpqRo3bpzZ8QAA8ArDMLSvtEnr\nd9Qpf0edtuypV6vrh6tFXRPCNbx/nAb1jNKgntHqlRqpMGaDA0IWRSkEDRgwQI888ogee+wxvfHG\nG4qIiNCQIUP0zDPPMDU3ACCo1DW6VPBdnfJ3tpWjitofJlvo0y1CQ3rHaFDPaA3qGa2U+I6tdwgg\nNFCUQtSECRM0YcIEs2MAANDpahpcWrO1Wp9vrtbmXfXyHLpoFBcVpkuGJmh4/zhl9Y9VUly4uUEB\n+DWKEgAACHh1jS6t3VajzzZXa+P3dfIcmpTunB5RGnlOnIb3j1O/9EiFse4QgA6iKAEAgIDU6vIo\nd1uNVm6o0oadtXIfKkdnZ0TposwEjR2coNREbqcDcHooSgAAIKAUVzZr+VcV+nB9parrXZKkft0j\nNS4zQRcNSWBBVgCdgqIEAAD8nttj6Ktva7Xsy3Ll76yTYUgxkWGaPraLJp2frB5dI8yOCCDIUJQA\nAIDfamx26728cr2XW66ymrYZ6waeFaXJo1I0bkiCHOFWkxMCCFYUJQAA4HfqnS4tWVuud9aUqc7p\nVqTdqimjkjV5VIr6dIs0Ox6AEEBRAgAAfqOu0aW315RpydoyNTR5FBsZpp9PTNOVF3RRdESY2fEA\nhBCKEgAAMF11vUtvf1GqpbnlcrZ4FB9t068mpWrK6BRFOShIAHyPogQAAEzT6vJoSW65XlpVLGez\nR4mxNt14aZomj0pWhJ2CBMA8FCUAAGCK/B21evK9Iu0va1ZsZJhunZquy0cmM0EDAL9AUQIAAD51\nsLJZTy07oNxtNbJapKmjU3TTpWmKi+avJQD8Bz+RAACATzS1ePT6pyV687NStboMDe4VrduuSFff\n7lFmRwOAY1CUAACA1236vk5/e3OfSqtblRwXrpsv766LhybIYrGYHQ0AjouiBAAAvKbF5dH/rTio\nxV+UyWKRrru4q3LGpyqSmewA+DmKEgAA8IrdxU795bW92l3cpO7Jds25rqfOOSva7FgA0CEUJQAA\n0Kk8HkPvri3Tsx8eVKvL0OUjk3XL5O5cRQIQUChKAACg09Q0Sn945ntt/L5e8dE2/cesHho9MN7s\nWABwyihKAACgU+TvqNUjH1nlbK3XyHPi9B/TeygxNtzsWABwWihKAADgjBiGoSVry/XPZUWyWqTZ\nV2fo8pHJzGgHIKBRlAAAwGlzuQ09tqRQH6yrUGKMTded36zJo1LMjgUAZ4yiBAAATkttg0v3vbxH\nm3fVq0+3SM3/eW/t+/5rs2MBQKegKAEAgFO2r7RJ85/fpYOVLbrg3HjNue4sRdjDtM/sYADQSShK\nAADglKzfXqv7X9mjxmaPcsan6qZL02S18jwSgOBCUQIAAB324VcV+t+39ysszKK7ZvbU+PMSzY4E\nAF5BUQIAAB2y7MtyPfJOoeKiwjT/F3008KxosyMBgNdQlAAAwE9asrZMjy8tUny0Tfff3Fe90yLN\njgQAXkVRAgAAJ/X2F6X657IDSoyx6f6b+6lnaoTZkQDA6yhKAADghN78rFSLPjigpFibFt7cTz26\nUpIAhAaKEgAAOK7XPynRsx8eVHJcuB64pZ/SUxxmRwIAn6EoAQCAY7y8qlgvrCxWl/hwLbyln7on\nU5IAhBaKEgAAaOe1T0r0wspipSbatfDmvkpLoiQBCD0UJQAAcMTqgko99+FBdU1ou90uNdFudiQA\nMIXV7AAAAMA/bN5Vr4fe2q/oCKsW/LIvJQlASKMoAQAA7Stt0n+/sFuSNO/G3kwBDiDkUZQAAAhx\nVXWtuve5Xapvcuvfp/fQeX1jzY4EAKajKAEAEMKaWjya/3+7VVLVohsvTdOlw5PMjgQAfoGiBABA\niHJ7DD342l7tKGzUpcMTNSs71exIAOA3KEoAAISop5YVKXdbjYb2jdG/Teshi8VidiQA8BsUJQAA\nQtC7a8v07tpy9UyN0Lwbeincxl8JAODH+KkIAECI+WZvg/65rEiJMTb91y/6KCaSZRUB4GgUJQAA\nQkhdo0sLX90jGdLvr+/JWkkAcAIUJQAAQoRhGPrbm/tUWt2qGyakKbMP04ADwIlQlAAACBHvrClT\n3je1Oq9vjGaOZ4Y7ADgZihIAACHg230NWvTBASXG2HTXzJ4KszLDHQCcDEUJAIAgV+d06f5X9shj\nSHfl9FRibLjZkQDA71GUAAAIYoZh6KE396u0ulWzslN1Xl+eSwKAjqAoAQAQxJasLW9bVLZPjK7P\nTjM7DgAEDIoSAABBavv+Rj39wQEl8FwSAJwyihIAAEGoqcWtha/ukdtjaM51PZUUx3NJAHAqKEoA\nAASh51cUq7iyRddc1FXD+/NcEgCcKooSAABB5pt9DXp3bZnSUxy6cQLPJQHA6aAoAQAQRFpcHj30\n1j4ZhvQfM3rIHs6pHgBOBz89AQAIIq9+XKL9pc26YnSKBveKMTsOAAQsihIAAEFi90GnXv+kRF0T\nwvXLSd3MjgMAAY2iBABAEHC7DT301j65PdLsq3soyhFmdiQACGgUJQAAgsDba8q0s8ipCcMSNWJA\nnNlxACDgUZQAAAhwReXNeuGjg0qIsenXU9LNjgMAQYGiBABAAPN4DP1j8T61uAzdfmWG4qJtZkcC\ngKBAUQIAIIAt/6pCX+9u0AXnxmvs4Hiz4wBA0KAoAQAQoCprW/X0BwcUHWHVb67MkMViMTsSAAQN\nihIAAAHquRUH5Wz26FeXd1dSXLjZcQAgqFCUAAAIQDuLGvVRfqX6dIvQZSOSzY4DAEGHogQAQIAx\nDENPvlckSfr1lHSFWbnlDgA6G0UJAIAA88WWGm3d0zaBw9C+sWbHAYCg5POi5HQ6NX/+fGVnZysr\nK0szZ87UmjVrTnj8u+++q6uuukrDhg3T2LFjdeedd6q4uNiHiQEA8B8trR49/X6RbGEW/b/Lu5sd\nBwCCls+L0oIFC1RQUKBFixZp7dq1mjZtmm677Tbt2rXrmGNzc3P1+9//XrfeeqvWrVunt956S6Wl\npfrd737n69gAAPiFxV+UqbS6VVdf2EXdkx1mxwGAoOXTolRTU6OlS5dq9uzZ6t27txwOh3JyctS3\nb1+9+uqrxxz/9ddfKzExUZMnT1Z4eLhSU1M1efJkff31176MDQCAX6isbdVrn5QoPtqmnPGpZscB\ngKDm06K0detWtba2asiQIe22Z2ZmatOmTcccf8kll6ihoUFLlixRS0uLKioq9P7772vSpEm+igwA\ngN94bsVBNbV49POfpSk6IszsOAAQ1CyGYRi++mbvvfee7rzzTm3evFkOxw+3Czz00ENatmyZVq5c\necxrVq5cqTlz5sjpdMowDI0cOVJPPPGEoqOjT/q98vPzOz0/AABmKaqSnlhlVWq8dPulHjHRHQB0\nnqysrGO22UzIcVzHW018/fr1mjNnjv70pz8pOztbFRUVmj9/vm6//XY9//zzP/mex/sPNkt+fr5f\n5QlGjLF3Mb7exxh7X6COsWEYeuWf38lQg/792r46z49nugvUMQ4UjK/3Mcbe529jfKILLD699S45\nuW1BvOrq6nbbq6qqlJKScszxL730kkaMGKEpU6YoMjJSGRkZuuOOO5SXl6edO3f6JDMAAGY7PB34\nmEFxfl2SACCY+LQoDR48WHa7XRs3bmy3fcOGDRoxYsQxx7vdbnk8nmO2STpmOwAAwaj9dODpZscB\ngJDh06IUGxurGTNm6OGHH9bu3bvldDq1aNEiFRUVKScnRyUlJZo0aZIKCgokSZdddpny8vL04Ycf\nqqWlRWVlZXrkkUd09tlnq1+/fr6MDgCAKd7LK1dpdauuHJOi9BSmAwcAX/H5M0pz587Vgw8+qFmz\nZqmhoUEDBw7U008/rfT0dBUWFh4pUJI0ZcoUNTQ06NFHH9Xvf/97Wa1WjR07Vk8++aTCwpjtBwAQ\n3Jpa3Hr901JFOqxMBw4APubzomS32zVv3jzNmzfvmH0ZGRnavn17u23XXXedrrvuOl/FAwDAbyzN\nLVdNg0uzslMVG+U38y8BQEjw6a13AACgYxqb3Xrzs1JFR1g1bWwXs+MAQMihKAEA4IeWrC1XbaNb\n08d2VUwkV5MAwNcoSgAA+JmGJrfe+rxUMZFhuupCriYBgBkoSgAA+Jl31pSp3unWNRd1VXQEkxcB\ngBkoSgAA+JE6p0tvf1GquOgwXTHm2MXYAQC+QVECAMCPvP15mRqaPLr2olRFObiaBABmoSgBAOAn\nahtcemdNmRJibJo6OtnsOAAQ0ihKAAD4ibc+L5WzxaPrLu6qCDtXkwDATBQlAAD8QHV9q5bklisp\n1qbJo3g2CQDMRlECAMAPvPlZqZpaPJp5Saoc4ZyeAcBs/CQGAMBklXWtei+vXCnx4Zp0Ps8mAYA/\noCgBAGCyxZ+XqrnV0MxLUmXnahIA+AV+GgMAYKJ6p0vvr6tQUqxNPxuRZHYcAMAhFCUAAEz0/roK\nOZs9uurCLrLbOC0DgL/gJzIAACZpcXn07poyRdqtmjySZ5MAwJ9QlAAAMMnHG6tUWefS5aOSFRNp\nMzsOAOBHKEoAAJjA4zH01uelCrNKV1/Qxew4AICjUJQAADDBuu212l/arEuGJqpLgt3sOACAo1CU\nAAAwwVuflUqSZlzU1eQkAIDjoSgBAOBj3+xr0JY9DRpxdqx6p0WaHQcAcBwUJQAAfOzw1aRruJoE\nAH6LogQAgA8VljVp7bYa9U+PVGafGLPjAABOgKIEAIAPvf1FmQyj7WqSxWIxOw4A4AQoSgAA+EhV\nXas+2lCptCS7Ljw3wew4AICToCgBAOAjS3PL1eoyNH1sF4WFcTUJAPwZRQkAAB9wNru1NK9ccVFh\nmpiVbHYcAMBPoCgBAOADK9ZXqt7p1hVjUhRh5/QLAP6On9QAAHiZx2Po3bVlstssmjq6i9lxAAAd\nQFECAMDL8nfW6WBliy45L1EJMTaz4wAAOoCiBACAly3NLZckXTE6xeQkAICOoigBAOBFB8qbtX5H\nrQaeFaV+6VFmxwEAdBBFCQAAL1r2ZbkMQ7piDM8mAUAgoSgBAOAlTS0erVhfqcQYm8YOjjc7DgDg\nFFCUAADwkk82Vam+ya1JI5MVbuOUCwCBhJ/aAAB4gWEYWppbJqtVmjySBWYBINBQlAAA8IKtexu0\n62CTLjw3QSnxdrPjAABOEUUJAAAvODIl+BimBAeAQERRAgCgk1XUtmrNlmr1So3Q4F7RZscBAJwG\nihIAAJ3sg3XlcnvariZZLBaz4wAATgNFCQCATtTq8uj9dRWKjrBq/HmJZscBAJwmihIAAJ1o7bYa\nVdW5NDErWZGOMLPjAABOE0UJAIBOdHgSh6mjmcQBAAIZRQkAgE7y/YFGbd3ToBFnxyo9xWF2HADA\nGaAoAQDQSd7LY0pwAAgWFCUAADpBQ5NbH2+sVmqiXVlnx5kdBwBwhihKAAB0gk82Vam51aPLzk9S\nmJUpwQEg0FGUAADoBB9+VSGrVfpZVrLZUQAAnYCiBADAGfruQKN2Fjk1ckCckuPCzY4DAOgEFCUA\nAM7Q8nUVkqRJ53M1CQCCBUUJAIAz0NTi1scbq5QcF64RTOIAAEGDogQAwBn4/OsaNTZ79LOsJIWF\nMYkDAAQLihIAAGdg+VcVsliky85PMjsKAKATUZQAADhNe0uatG1vg4b1i1VqosPsOACATkRRAgDg\nNK1YzyQOABCsKEoAAJyGFpdHKzdUKj7aptEDmcQBAIINRQkAgNOQu61GtY1uXTo8UeE2TqcAEGz4\nyQ4AwGlg7SQACG4UJQAATtHBymZt/L5eg3tHK6NLhNlxAABeQFECAOAUffhVpSRp0giuJgFAsKIo\nAQBwCtxuQx/lVygmIkxjhySYHQcA4CUUJQAATsG67bWqrHNp/LBEOcI5jQJAsOInPAAAp2D5V22T\nOFzOJA4AENQoSgAAdFBFbavWb6/V2RlR6t0t0uw4AAAvoigBANBBqwsq5TGkn41IMjsKAMDLKEoA\nAHSAYRj6aEOlwm0WXZTJJA4AEOwoSgAAdMCOwkbtL23WmEHxio20mR0HAOBlFCUAADrgo/y2tZMu\nHc5tdwAQCihKAAD8hJZWjz7dXK2kWJuG94s1Ow4AwAd8XpScTqfmz5+v7OxsZWVlaebMmVqzZs0J\nj6+vr9c999yjUaNGadiwYbr++uu1detWHyYGAIS6L7+tVb3TrexhSQoLs5gdBwDgAz4vSgsWLFBB\nQYEWLVqktWvXatq0abrtttu0a9eu4x7/7//+7zpw4IDeffddffbZZxo5cqT+9re/yePx+Dg5ACBU\ncdsdAIQenxalmpoaLV26VLNnz1bv3r3lcDiUk5Ojvn376tVXXz3m+E2bNikvL09//vOflZaWptjY\nWN1xxx1atGiRrFbuGgQAeF9lbavyd9RqQEaUeqZGmB0HAOAjPm0bW7duVWtrq4YMGdJue2ZmpjZt\n2nTM8Xl5ecrIyNCKFSuUnZ2tkSNH6tZbb9W+fft8FRkAEOJWb6ySx+BqEgCEGp/Ob1pZ2XbrQkJC\n+/UnEhMTVVFRcczxBw8eVHFxsXbu3Kl33nlHjY2Nuvvuu3XrrbdqyZIlCg8PP+n3y8/P77zwncDf\n8gQjxti7GF/vY4y971TG2DBJQcKxAAAgAElEQVSkpWusCrNK8dqr/Py9XkwWPPhz7F2Mr/cxxt4X\nCGPsNwtBWCzHPhxrGIbcbrf+8Ic/yOFwKC4uTnPnztXUqVO1adMmjRgx4qTvmZWV5a24pyw/P9+v\n8gQjxti7GF/vY4y971THePv+RpXW7tC4IQkaN6aX94IFEf4cexfj632Msff52xifqLT59Na75ORk\nSVJ1dXW77VVVVUpJSTnm+K5duyoiIkIOh+PItrPOOkuSVFxc7MWkAABIKzcwiQMAhCqfFqXBgwfL\nbrdr48aN7bZv2LDhuFeHBgwYoLq6Ou3Zs+fItr172257yMjI8GpWAEBoa3F59MmmKiXG2pTVn7WT\nACDU+LQoxcbGasaMGXr44Ye1e/duOZ1OLVq0SEVFRcrJyVFJSYkmTZqkgoICSdLFF1+sfv366d57\n71VZWZkqKyu1cOFCDR48WEOHDvVldABAiPnym0NrJ52XyNpJABCCfD7H9ty5czV69GjNmjVLo0aN\n0ooVK/T0008rPT1dra2tRwqUJIWHh+vpp59WTEyMLrvsMk2YMEFxcXF68sknj/tMEwAAnWUlaycB\nQEjz+WQOdrtd8+bN07x5847Zl5GRoe3bt7fb1q1bNz322GO+igcAgCprW7V+Z636p0eqV1qk2XEA\nACZg1VYAAI6yemOVPB5pYhZXkwAgVFGUAAD4EcMwtHJDpWxhFl2cmWh2HACASShKAAD8yHcHnNpb\n0qRRA+MUF+03yw0CAHyMogQAwI+sOrx20jBuuwOAUEZRAgDgEJfb0CebqhUXFaass1k7CQBCGUUJ\nAIBDNuysVU2DSxdnJircxikSAEIZZwEAAA5ZVVAlScoexiQOABDqKEoAAEhqaHIrb1uN0pMdGtAj\nyuw4AACTUZQAAJD0xZZqtbgMZQ9LlMViMTsOAMBkFCUAACSt5rY7AMCPUJQAACGvtLpFm3fV69xe\n0UpLcpgdBwDgByhKAICQ9/FGriYBANqjKAEAQpphGFpVUClbmEXjhiSYHQcA4CcoSgCAkPbdAaf2\nlzZr1MA4xUbazI4DAPATFCUAQEhbtaFSkjRhWJLJSQAA/oSiBAAIWW63oU83VysuKkwjzo41Ow4A\nwI9QlAAAISt/Z52q6126KDNR4TZOiQCAH3BWAACErNUFbbfdMdsdAOBoFCUAQEhqaHIrd1uN0pMd\nOqdHlNlxAAB+hqIEAAhJa7ZUq8VlaPywRFksFrPjAAD8DEUJABCSVhewyCwA4MQoSgCAkFNW3aLN\nu+s1qGe0uiU5zI4DAPBDFCUAQMj5eFOVDEOawNUkAMAJUJQAACHFMAytLqiSLcyicZkJZscBAPgp\nihIAIKTsLm7S3pImjTwnTrGRNrPjAAD8FEUJABBSjqyddB633QEAToyiBAAIGR5D+mRTtWIiwnT+\ngDiz4wAA/BhFCQAQMnaXShW1rRo7JEH2cE6BAIAT4ywBAAgZG/e1LSzL2kkAgJ/SoaLU1NTk7RwA\nAHhVU4tH24os6poQrnN7RpsdBwDg5zpUlC688EL98Y9/1JYtW7ydBwAAr/jy2xo1uywaf16irFaL\n2XEAAH6uQ0XpxhtvVF5enq699lpdddVVevHFF1VbW+vtbAAAdJrVBVWSpPHnJZmcBAAQCDpUlO64\n4w59+OGHevPNNzV27Fg9++yzGjdunO68807l5uZ6OyMAAGekut6l/B216p5gqGdqhNlxAAAB4JQm\nczj33HM1Z84crVq1Si+88ILi4+N1yy236Gc/+5leeukltbS0eCsnAACn7fOvq+T2SEPPMsyOAgAI\nEKc169369eu1ePFiffDBB4qIiNCQIUP06KOPatq0adq/f39nZwQA4IysLqiS1SJl9qAoAQA6xtbR\nA4uLi/X222/rnXfe0d69ezVkyBDdeeedmjJliiIjI9XQ0KB/+7d/0z333KPnnnvOi5EBAOi4A+XN\n+nZ/o4b3j1VsZLXZcQAAAaJDRelXv/qVvvzyS0VERGjq1Kn6+9//roEDB7Y7Jjo6Wr///e81ffp0\nrwQFAOB0fLyxbRKH7PMSJYOiBADomA4Vperqav3xj3/U1KlTFRUVdcLjunXrpt/85jedFg4AgDNh\nGIZWb6yUI9yqC86N1zZWuQAAdFCHitLixYs79GYxMTG67bbbzigQAACdZfv+Rh2oaNElQxMV6Qgz\nOw4AIICc1mQOAAAEgtWHb7sblmhyEgBAoKEoAQCCkstt6NPNVYqPtml4v1iz4wAAAgxFCQAQlDbs\nrFVtg1uXDE1QWJjF7DgAgABDUQIABKVVBW233Y0/L8nkJACAQERRAgAEnYYmt/K21Siji0NnZ0Sa\nHQcAEIAoSgCAoPPFlmq1uAxlD0uUxcJtdwCAU0dRAgAEndUFP1pkFgCA00BRAgAEldLqFm3eVa/B\nvaOVmugwOw4AIEBRlAAAQeXjQ2snTRjGJA4AgNNHUQIABA3DMLSqoFLhNovGDo43Ow4AIIBRlAAA\nQeO7A07tL23WqHPiFBNpMzsOACCAUZQAAEFj1YZKSdx2BwA4cxQlAEBQcLsNfbq5WnFRYco6O9bs\nOACAAEdRAgAEhfyddaqud+nizESF2zi9AQDODGcSAEBQWF3Qdttd9jDWTgIAnDmKEgAg4DU0uZW7\nrUbpyQ4N6BFldhwAQBCgKAEAAt6aLdVqcRnKHpYoi8VidhwAQBCgKAEAAt7qgrZFZrntDgDQWShK\nAICAVlbdos2763Vur2ilJTnMjgMACBIUJQBAQPt4Y5UMg6tJAIDORVECAAQswzC0qqBKtjCLxg1J\nMDsOACCIUJQAAAHr+4NO7Stt0qiBcYqNtJkdBwAQRChKAICAtXpD2yQOE4YlmZwEABBsKEoAgIDk\nchv6eFOV4qLCNOLsWLPjAACCDEUJABCQ1u+oVXW9S+PPS1S4jdMZAKBzcWYBAASklfmVkqRLs7jt\nDgDQ+ShKAICAU9vg0pff1qpXWoT6dos0Ow4AIAhRlAAAAeeTTVVyuQ1NHJ4ki8VidhwAQBCiKAEA\nAs7KDZWyWqXx57HILADAO3xelJxOp+bPn6/s7GxlZWVp5syZWrNmTYdee++992rAgAEqLCz0ckoA\ngL/aXezUziKnzh8Qp8TYcLPjAACClM+L0oIFC1RQUKBFixZp7dq1mjZtmm677Tbt2rXrpK9bs2aN\n3n//fR+lBAD4q5Ub2iZxmDicSRwAAN7j06JUU1OjpUuXavbs2erdu7ccDodycnLUt29fvfrqqyd8\nXX19vebNm6ff/OY3PkwLAPA3LrehjwuqFBsZpvPPiTM7DgAgiPm0KG3dulWtra0aMmRIu+2ZmZna\ntGnTCV/3wAMPKDMzUxMnTvR2RACAH8vfUauqepcuOS9RdtZOAgB4kc2X36yysu12iYSEhHbbExMT\nVVFRcdzXfPHFF1q5cqWWLVumxsbGU/p++fn5pxfUS/wtTzBijL2L8fU+xvjk3si1SrIoI6pE+fkl\np/UejLH3Mcbexfh6H2PsfYEwxj4tSidzvOldD99yd8899ygpKemUi1JWVlZnxTtj+fn5fpUnGDHG\n3sX4eh9jfHK1DS5tf3ureqU6dMWEAac1LThj7H2MsXcxvt7HGHufv43xiUqbT+9bSE5OliRVV1e3\n215VVaWUlJRjjl+4cKEyMzM1efJkn+QDAPivTze3rZ10aRZrJwEAvM+nRWnw4MGy2+3auHFju+0b\nNmzQiBEjjjn+jTfe0Jo1azRq1CiNGjVK06dPlyRNnz5dTz31lE8yAwD8w0f5rJ0EAPAdn956Fxsb\nqxkzZujhhx/W2WefrbS0NL388ssqKipSTk6OSkpK9Itf/EL333+/hg0bpk8//bTd64uLizVz5kz9\n85//VL9+/XwZHQBgoj2H1k4aOSBOSaydBADwAZ8/ozR37lw9+OCDmjVrlhoaGjRw4EA9/fTTSk9P\nV2FhoXbv3i2n0ylJSktLa/dal8slSUpJSVFMTIyvowMATHJ47aRLs1g7CQDgGz4vSna7XfPmzdO8\nefOO2ZeRkaHt27ef8LU/tR8AEHzcbkOrN1YpJjJMowaydhIAwDdYhAIA4Nfyd9apqs6l8UNZOwkA\n4DuccQAAfo3b7gAAZqAoAQD8Vk2DS7nbatQzNUL90yPNjgMACCEUJQCA31q1oVIut6HLRrB2EgDA\ntyhKAAC/ZBiGlq+vkC3Mouxh3HYHAPAtihIAwC9t29ug/aXNuvDceMVH+3ySVgBAiKMoAQD80vKv\nKiRJk0Ymm5wEABCKKEoAAL9T73Tp86+r1S3JrszeLDAOAPA9ihIAwO98sqlaza2GLjs/WVYrkzgA\nAHyPogQA8DvLv6qQ1SpNHM4kDgAAc1CUAAB+ZWdRo74/4NSoc+KVFBdudhwAQIiiKAEA/MrydYcm\ncTifq0kAAPNQlAAAfqOpxa2PN1UpJT5cWWfHmR0HABDCKEoAAL/x2eZqOZs9+llWksKYxAEAYCKK\nEgDAbyz/qkIWi/SzEaydBAAwF0UJAOAX9pY49c2+Rg3vF6vURLvZcQAAIY6iBADwC8u/qpQkTRrJ\n1SQAgPkoSgAA07W0erRqQ6USYmwadQ6TOAAAzEdRAgCYbu3WGtU53bp0eJLCbZyaAADm42wEADDd\n8q/a1k66bARrJwEA/ANFCQBgqsKyJm3aVa8hvaOV0SXC7DgAAEiiKAEATPZeXrkkaeroFJOTAADw\nA4oSAMA0zma3PsqvVFKsTRecm2B2HAAAjqAoAQBMs3pjlRqbPZo8KkW2MIvZcQAAOIKiBAAwhWEY\nWppbLluYRZezdhIAwM9QlAAApvh6d732ljRp7OB4JcWGmx0HAIB2KEoAAFMsyWUSBwCA/6IoAQB8\nrqy6RbnbatSnW6QG9Yw2Ow4AAMegKAEAfO79dRXyeKQrx6TIYmESBwCA/6EoAQB8qsXl0fKvKhQT\nGaaLhyaaHQcAgOOiKAEAfOqLr6tVXe/SZSOSFGHnNAQA8E+coQAAPrU0t1wWizSFSRwAAH6MogQA\n8JkdhY36dn+jzh8Qp25JDrPjAABwQhQlAIDPvJfXNiX4lWO4mgQA8G8UJQCAT9Q0uPTJpip1T7Zr\nWL9Ys+MAAHBSFCUAgE98uL5CrS5DU0enyGplSnAAgH+jKAEAvM7tMbQsr1yOcKsmZiWZHQcAgJ9E\nUQIAeF3ethqVVrcqe1iiYiJtZscBAOAnUZQAAF5lGIbe+KxUkjTtwi4mpwEAoGMoSgAAr9q6p0Hb\n9zdqzKA49egaYXYcAAA6hKIEAPCqw1eTZozranISAAA6jqIEAPCavSVNWvdtrQb1jNa5vWLMjgMA\nQIdRlAAAXvPW54evJvFsEgAgsFCUAABeUVHbqo83Vik9xaHRA+PNjgMAwCmhKAEAvOLdtWVyuQ3N\nGNeVBWYBAAGHogQA6HQNTW4tyytXYoxNE4Ylmh0HAIBTRlECAHS65V9VqLHZoysv6CJ7OKcaAEDg\n4ewFAOhUrS6P3v6iTBF2q6aMTjY7DgAAp4WiBADoVJ9urlZFbasmnZ+s2Eib2XEAADgtFCUAQKcx\nDENvfVYqq1WaNpYpwQEAgYuiBADoNOt31GlPSZMuzkxU1wS72XEAADhtFCUAQKd587O2BWavuair\nyUkAADgzFCUAQKfYvr9Rm3fVa3j/WPXpFml2HAAAzghFCQDQKV5eXSxJuvZiriYBAAIfRQkAcMa2\n72/Qum9rNbh3tIb2iTE7DgAAZ4yiBAA4Yy+ubLuadNOl3WSxWExOAwDAmaMoAQDOyLa9DVq/o05D\n+8Yok6tJAIAgQVECAJyRFz46KEm66dI0k5MAANB5KEoAgNO2eVe9Nn5fr6z+sTq3F1eTAADBg6IE\nADgthmHohZVtV5Nu5GoSACDIUJQAAKdl0656bdndoJED4nTOWdFmxwEAoFNRlAAAp8wwDL3wUdtM\ndzdO5GoSACD4UJQAAKdsw846bdvboDGD4tQ/PcrsOAAAdDqKEgDglLQ9m9R2NemGCVxNAgAEJ4oS\nAOCUfLW9Vtv3N+rCwfHq252rSQCA4ERRAgB02OFnkywW6UauJgEAghhFCQDQYXnf1Oq7A06NG5Kg\nXmmRZscBAMBrKEoAgA5xuQ09s/yArBaeTQIABD+fFyWn06n58+crOztbWVlZmjlzptasWXPC43Nz\nc5WTk6OsrCxdeOGFuuuuu1RZWenDxAAASVqWV67CsmZdPjJZZ3WNMDsOAABe5fOitGDBAhUUFGjR\nokVau3atpk2bpttuu027du065thvv/1Wv/71rzVlyhTl5eXp9ddf144dO3TPPff4OjYAhLTaBpde\nXFWs6Airbry0m9lxAADwOp8WpZqaGi1dulSzZ89W79695XA4lJOTo759++rVV1895viysjLdcMMN\nuummmxQeHq709HRdffXVysvL82VsAAh5L60qVr3TrVnZaUqIsZkdBwAAr/Pp2W7r1q1qbW3VkCFD\n2m3PzMzUpk2bjjl+3LhxGjduXLtthYWF6taNf80EAF/ZV9qk974sV/dku64Yk2J2HAAAfMKnRenw\ns0UJCQntticmJqqiouInX5+bm6tXX31VDz30UIe+X35+/qmH9CJ/yxOMGGPvYny9zx/H+PnPrfJ4\nLBo/wKnNmwrMjnPG/HGMgw1j7F2Mr/cxxt4XCGPsN/dPWCyWk+5funSp7rnnHs2dO1cTJ07s0Htm\nZWV1RrROkZ+f71d5ghFj7F2Mr/f54xh/tb1WO0t2aVi/GN0wte9P/qz2d/44xsGGMfYuxtf7GGPv\n87cxPlFp82lRSk5OliRVV1crNTX1yPaqqiqlpJz4do5HHnlEzz//vP7xj3/o4osv9npOAEDbdOBP\nLSuS1SLdMiU94EsSAACnwqeTOQwePFh2u10bN25st33Dhg0aMWLEcV/z+OOP67XXXtMrr7xCSQIA\nH1r2Zbn2lzVr0shk9WZxWQBAiPFpUYqNjdWMGTP08MMPa/fu3XI6nVq0aJGKioqUk5OjkpISTZo0\nSQUFbffAb9myRY8//rieeuop9evXz5dRASCk1TW69OLKYkU5rLqJ6cABACHI588ozZ07Vw8++KBm\nzZqlhoYGDRw4UE8//bTS09NVWFh4pEBJ0iuvvKKWlhZde+21x7zPM888o/PPP9/X8QEgJByeDvzm\nyd2ZDhwAEJJ8fvaz2+2aN2+e5s2bd8y+jIwMbd++/cjX9913n+677z5fxgOAkLevtElL89qmA7+S\n6cABACHKp7feAQD8m2EYemJpkTwe6ebJ6Qq3cZoAAIQmzoAAgCNWbqhSwXd1yuofq9ED48yOAwCA\naShKAABJUmVdq/65rEgRdqtmT+vBdOAAgJBGUQIASJIeX1Koeqdb/3JZN6Um2s2OAwCAqShKAACt\n2VKtL7bUaFDPaE0dzQQOAABQlAAgxNU5XXp0SaHCbRbdMaOHrFZuuQMAgKIEACHuqWUHVFXn0g0T\n0pTRJcLsOAAA+AWKEgCEsA076/RRfqX6dIvUjHFdzY4DAIDfoCgBQIhyNrv1v2/vl9Uq3XFND9nC\nuOUOAIDDKEoAEKKeX3FQJVUtuvairurXPcrsOAAA+BWKEgCEoG17G7Qkt1zpKQ7Nyk4zOw4AAH6H\nogQAIaapxa2H3tonSfqPGT1kD+dUAADA0Tg7AkCIefTdQhWWNeuqC7pocK8Ys+MAAOCXKEoAEEI+\nyq/Qyg1V6p8eqV9N6mZ2HAAA/BZFCQBCxN6SJj36bpGiI6y6e1Yvhds4BQAAcCKcJQEgBDS1eHT/\nK3vU3OrRHTPOUrckh9mRAADwaxQlAAgBjy8t1N6SJl0xJkUXDk4wOw4AAH6PogQAQW7VhkqtWF+p\nft0jdfPk7mbHAQAgIFCUACCI7S9t0iPvFirK0fZckp3nkgAA6BDOmAAQpJpbPfrzK3vU1OLRf8w4\nS92TeS4JAICOoigBQJB6YmmR9hQ3acqoZI0bwnNJAACcCooSAAShJWvLtPyrCvXpFqlfT0k3Ow4A\nAAGHogQAQSZvW42efK9ICTE23XNjL9nD+VEPAMCp4uwJAEFkR2GjFr66V+E2i+b/vI/SWC8JAIDT\nQlECgCBRUtWi+c/vUovLo//M6aUBPaLMjgQAQMCiKAFAEKh3unTvc7tUVe/SrVPSNWZQvNmRAAAI\naBQlAAhwrS6P/vTSHu0rbdLVF3bRVRd2MTsSAAABj6IEAAHMMAz9Y/F+bfq+XmMGxevmyd3NjgQA\nQFCgKAFAAHt5VYlWFVTp7Iwo3TWzp8KsFrMjAQAQFChKABCglqwt04uripWaaNf8n/dWhJ0f6QAA\ndBab2QEAAKfu7S9K9c9lB5QYa9N//7KPEmPDzY4EAEBQoSgBQIB549MSPbP8oJLjwrXw5r7K6BJh\ndiQAAIIORQkAAsirH5fo+RUHlRIfrgdu7qfuKSwoCwCAN1CUACBAvLSqWC+uLFbXhHAtvKWfuiVR\nkgAA8BaKEgD4OcMw9MLKYr2yukRpiXYtvKWvUhMpSQAAeBNFCQD8mGEYeu7Dg3r901J1S7Jr4S39\n1DXBbnYsAACCHkUJAPyUy23o8SWFen9dhdKTHVp4S1+lxFOSAADwBYoSAPih2gaX7nt5jzbvqlef\nbhFa8Mu+So5jCnAAAHyFogQAfmZfaZPmP79LBytbNGZQvOZcd5YiHWFmxwIAIKRQlADAj+TvqNWf\nX96jxmaPZl6Sqp9PTJPVajE7FgAAIYeiBAB+wDAM5e606IPNuxQWZtGc685S9rAks2MBABCyKEoA\nYDKX29BjSwr1wSarEmNsuuem3hp4VrTZsQAACGkUJQAw0YHyZv3l9b36dn+juiUYWnjr2Uz/DQCA\nH6AoAYAJDMPQh+sr9eR7RWpq8eiSoYm6qHc5JQkAAD9BUQIAH6uud+kfi/cp75taRUdY9Z8ze+qS\n8xKVn19udjQAAHAIRQkAfGjdt7X6+1v7VFXv0tA+MfrttWdxFQkAAD9EUQIAH2hq8ejp94u07MsK\n2cIsunlyd027sAtTfwMA4KcoSgDgRYZhKO+bWv3zvSIVV7WoV2qE7prZU727RZodDQAAnARFCQC8\nZH9pk554r0gbdtYpzCpdc1FX3XRpmuzhVrOjAQCAn0BRAoBO1tDk1suri/XumjK5PdLw/rG6bWq6\nenSNMDsaAADoIIoSAHQSj8fQqoJKPbv8oKrqXUpLtOvXU9M1emCcLBaeRQIAIJBQlADgDBmGoa+2\n1+rFlcXaWeSUI9yin09M04xxXbnNDgCAAEVRAoDT5PEYyvumRq+sLtF3B5ySpIuGJOj/Te7OlN8A\nAAQ4ihIAnCKPx9CarTV6eXWx9hQ3yWKRLspMUM74VPVOYzY7AACCAUUJADqo1eXR519X67VPSrWv\ntElWi5R9XqJmjk/VWUzUAABAUKEoAcBPKK5s1gfrKrQiv1LV9S5ZrdLErCTNvCRV6SkOs+MBAAAv\noCgBwHG43W0TNCz7slz5O+tkGFJsZJimj+uiK0anKC2JggQAQDCjKAHAjxwob9bHG6u0fH2Fymta\nJUmDekZr8shkjR2SIAez2AEAEBIoSgD+f3v3Hh3znf8P/DmTuSSYJGRSFcEhGksSrUvd8qtFbF2+\nFKucbMvWrVa1tewqkbDFulREzrax3RZRVV3WFo062mPp+Zai9Is0BGElZciNZJKITOb6/v0RGZlb\nCJ2ZTPJ8nDNn5vN+vz8fr3l75z3z+sx7PtPsFZbqcfR8GY5llVmvXhegkOJ/+odgdH81urTjBRqI\niIiaGyZKRNQsFZcZcOx8GY5mleHKzSoAgJ8U6Bupwgsxwfh/McFoofTzcpRERETkLUyUiKhZMJos\nuHj9Hv7vyl2cvVqB3IJqAIBUCvR+RoXBMcEY2CMIgS05LRIRERETJSJqwvLv6HHmagXOXL2Ln65V\notpgAQDIZRL0fkaF2OggDOoRjOBWnAqJiIjIFt8dEFGTYDYL5BXqkH39Hi5ev4dL1+/h9v2LMQBA\neKgSfZ5RoU9kIGI6t4K/ghdlICIiIteYKBGRTyqtMOK/+TrkaGoSoxxNFXT3PzECgKCWMgyKCrqf\nHKnQtjUv501ERESPjokSETVqFotAfoke1wp0uJavQ26+DrkFOmgrTTbtOjylRFSnlujRqSV6dGqF\nsBAFJBKJl6ImIiIiX8dEiYgahWqDGTfv6HHzth43b1dDc//+1h099EZh0/apYDkG9ghCRLsAdG0f\ngB6dWkLVgtMZERER/XL4zoKIPMJsESitMKJQa0BhqQHFWgMKtXoUag0oKjXYfJ+ollIuRXioPzo9\n5Y+IsABEhAWgS1gAVAGcuoiIiMi9+G6DiJ6IxSJwr9qMskoTtJUmlFQYUVJhxJ1yg/Vx7c1scdxf\nIgFCAuV4NqIVOoT6I1ytRHioEh2e8oc6UA6plMvniIiIyPM8nijpdDqsW7cOR48eRXl5Obp27Yp5\n8+YhNjbWafsLFy4gJSUFly5dglwuR79+/bB06VK0adPGw5ETNX0ms0BVtRmVOjPu6ky4W2XGXZ0Z\nd6tMyLkmwY/5N1FRVZMUld8z3r83OU2AavlJgTYqOZ5p3wJtWyvwdBsFnmqtwNOtlXi6tQLqYDkU\nMl6BjoiIiBoXjydKK1euxMWLF5Geno6wsDDs27cPc+bMQUZGBrp06WLTtqysDLNmzcLLL7+MtLQ0\n6PV6JCQk4I9//CM+++wzT4dO1GiYLQJGkwUGo4DeaIHeaEG10QK9wQJ9bZmhpkynN6PaYIHOYEG1\n3gKdwQydwYKqajOqqi24pzfjXrUZVdVmh+8C2ZICuGPdClBKEdxShsjwFghuJUNQSzmCW8kQEiiH\nOlCOkCA5QgLlCG4p46dCRERE5HM8miiVl5fjq6++wt/+9jd07twZABAfH49du3Zh165dSExMtGl/\n4MABCCEwf/58yGQyqFQqLFy4EOPGjcPly5fxq1/9ypPhkxcJISAEIABAABYhIICaMlFTrzcC96rN\nD9reb2ep87i2rUUAFutaFLQAABOPSURBVEvdx8KmvcUiYLEAZlFzb6m9twhYhID5/mNzbbv7j821\nj80126bacrOAyfwgwTFbAJPZAtP9cpPZAqNZwGiqqTfVPjbXbBtNAnqTBUajgOH+/r8EuUyClv5+\naKn0gzpQjhb+fjXb/n4IbOEHVQsZVAE19wWaa+j9XHeoAmQIbiWDUs5PgYiIiKjp8miilJ2dDaPR\niJiYGJvynj174qeffnJon5mZiaioKMhkD8Ls1q0blEolMjMzfSJREkLgvV3XcTFXCuX/XnLRqJ79\n66kUom4758esWy6E82M5O45909p9RZ1G9m0FxIM6Yd/WNtER1v0elFuPcz8Jgl35w/kBGecftXGj\n5icF5DIp5H4SyGQSKGVSBLWUQSGTQiGTQCGTQi6TQCGXQmm9SeCveLCtkEsRoJDCXylFgMIPAcqa\n7QClFEq5H1r4Sxu05O2MAega1sKNz5qIiIio8fBoolRaWgoACA4Otilv3bo1SkpKHNprtVoEBQXZ\nlEkkEgQFBTltb+/MmTNPEO0vQwigoEiKahNQXVntst3jLkyq72diJA4PXP87zo4jqVMnqVMmqVNZ\n97H9cWrr6h5aInFe57DtpJ21rE47qaTOdp12tdtSiXhQZnec2n2tx6hTb19X977uTSIBpFLbMj9p\nzb9bW+5XWy8F/O5v196kUkB2v17mV6dcAgBmZ/9VDSMAVAOiGqhCze1JNIa/qaaOfex+7GP3Yx+7\nF/vX/djH7ucLfdxornrX0B+GfJT2ffr0edxwflF9+9YMhsYST1PFPnYv9q/7sY/dj33sfuxj92L/\nuh/72P0aWx+7Sto8+iWDkJAQADUXaahLq9VCrVY7bW/fVgiB8vJyhIaGui9QIiIiIiJq1jyaKEVH\nR0OhUCAzM9Om/OzZs+jbt69D+169euHixYswGh/8EOX58+eh1+vRu3dvt8dLRERERETNk0cTJZVK\nhYkTJyItLQ15eXnQ6XRIT0/HrVu3EB8fj6KiIowcORLnzp0DAIwZMwZyuRypqamorKxEYWEhkpOT\nMWTIEERERHgydCIiIiIiakY8fn3fxMREDBgwAK+88gr69++PQ4cOYcuWLWjfvj2MRqM1gQJqEqut\nW7ciOzsbsbGxeOmll9ChQwds2LDB02ETEREREVEz4vGLOSgUCixduhRLly51qAsPD0dOTo5NWWRk\nJLZv3+6p8IiIiIiIiDz/iRIREREREVFjx0SJiIiIiIjIDhMlIiIiIiIiO0yUiIiIiIiI7DBRIiIi\nIiIissNEiYiIiIiIyA4TJSIiIiIiIjtMlIiIiIiIiOwwUSIiIiIiIrLDRImIiIiIiMgOEyUiIiIi\nIiI7TJSIiIiIiIjsMFEiIiIiIiKyIxFCCG8H4Q5nzpzxdghEREREROQD+vTp41DWZBMlIiIiIiKi\nx8Wld0RERERERHaYKBEREREREdlhokRERERERGSHiRIREREREZEdJkpERERERER2mCgRERERERHZ\nkXk7gKbg5MmTSEpKAgB8++23NnVmsxkffPABvvnmGxQXF6NTp06YOXMmxo4d6/J4Go0Gq1evRlZW\nFoQQePbZZ5GUlIQOHTq49Xn4ig8//BD/+Mc/HMoNBgPee+89TJgwwaEuISEBGRkZkMlsh/xf/vIX\nTJo0yW2x+qKbN28iLi4OcrkcEonEWh4aGuowvuv69NNPsXv3buTn56Ndu3aYPHkypk2b5oGIfdPF\nixeRkpKCCxcuQCqVonfv3liyZInLv3OO4YfT6XRYt24djh49ivLycnTt2hXz5s1DbGys0/YXLlxA\nSkoKLl26BLlcjn79+mHp0qVo06aNhyP3HSUlJUhJScGxY8dQVVWFrl27YsGCBRg4cKBD271792LJ\nkiVQKBQ25aNGjUJycrKnQvY5w4YNQ1FREaRS23PZ+/fvR+fOnR3aHz9+HGlpafjvf/8LlUqFF154\nAUuWLEFAQICnQvYpP/74I2bMmOFQbjKZMH78eKxdu9amnOP44TQaDRITE3H69GkcOXIE4eHh1roD\nBw4gPT0dP//8M0JDQzFq1CjMmzcPfn5+To/V0Hnc7QQ9kZSUFDFs2DAxa9YsMXToUIf6tLQ0MXjw\nYHHhwgWh1+vFf/7zHxEVFSV++OEHp8czGAxixIgR4p133hElJSWivLxcJCQkiBdffFEYDAZ3Px2f\ndfToUTFw4EBRUlLitH7x4sVi8eLFHo7KN2k0GhEZGSk0Gs0j77Nv3z7Rq1cvcfLkSaHX68WPP/4o\nevfuLfbu3evGSH1XUVGReP7558X69etFVVWVKCkpETNmzBDjxo1zuQ/H8MMlJCSIl156SeTm5orq\n6mqxc+dOER0dLa5du+bQVqvViv79+4v169eLiooKcfv2bTFz5kwxZcoUL0TuOyZPnixmzJghiouL\nRXV1tUhJSRHPPfecKCwsdGi7Z88ep6+LVL+hQ4eKPXv2PFLbvLw8ER0dLbZv3y6qqqrEjRs3xIQJ\nE0RCQoKbo2xaiouLRb9+/cSpU6cc6jiO63fo0CExcOBAsWjRIof3DqdOnRJRUVHi4MGDQq/Xi8uX\nL4shQ4aItLQ0l8dryDzuCVx694RatGiBjIwM9OjRw6FOCIHPP/8c06dPR1RUFBQKBYYPH45f//rX\n2L59u9Pjff/997h+/TqWLFmCNm3aIDAwEIsXL4ZGo8F3333n7qfjkyorK7Fs2TKeCfai7du3Y+LE\niRgwYAAUCgX69u2LiRMn4tNPP/V2aI1SUVERhg8fjgULFiAgIABt2rTB7373O1y6dAnl5eXeDs8n\nlZeX46uvvsLbb7+Nzp07Q6lUIj4+HhEREdi1a5dD+wMHDkAIgfnz50OlUkGtVmPhwoU4ffo0Ll++\n7IVn0PjdvXsXERERSExMRGhoKJRKJV5//XVUVVUhKyvL2+E1S//617/QpUsXTJ06FQEBAejQoQPm\nzp2L/fv3o7S01Nvh+Yx3330Xo0aNQr9+/bwdis8pKyvD559/jnHjxjnU7dixA4MHD8aoUaOgUCjQ\nrVs3TJs2DZ999hksFotD+4bO457AROkJvfHGG2jVqpXTuhs3bqC0tBQ9e/a0Ke/Zsyd++uknp/tk\nZmaiY8eOaN26tbUsODgYHTp0cLlPc7dx40ZERERg9OjR9bbLyclBfHw8+vbtixEjRuDjjz+G2Wz2\nUJS+JzU1FUOHDkX//v0xc+ZMXL161Wk7g8GAy5cvOx3nOTk50Ol0ngjXp8TExGDNmjU2Sw80Gg1a\ntWrlcj4BOIbrk52dDaPRiJiYGJtyV/NtZmYmoqKibJYyduvWDUqlEpmZmW6P1xepVCqsWbMGERER\n1jKNRgMAePrpp53uc+/ePbz55psYOHAgXnjhBSQmJqKsrMwj8fqyr7/+GqNHj0afPn3w29/+FocP\nH3baLjMz0+ncazKZkJ2d7YlQfd63336Ls2fPYuHChS7bcBy7NmnSJKdLQgHX47OsrAw///yzQ/uG\nzuOewESpHiaTCRUVFS5vD1N7NicoKMimvHXr1i7P9Gi1Wof2tfuUlJQ8xrPwPQ3p9/z8fPzzn//E\n/Pnz6z1meHg4wsPDsXr1apw4cQKLFi3CRx99hPT0dHc+lUbpYf2rUCgQHR2N/v374+uvv8b+/fvh\n7++P6dOn4+7duw7HKysrg9lsdjrOLRZLs3wxaejckZOTg7S0NLz99tsu121zDNevdk4NDg62KXc1\ndzqbayUSCYKCgprNXPukKisrsWTJEsTFxTm8sQFq+j4iIgJTpkzBsWPHsGnTJpw7dw7vvPOOF6L1\nHZGRkejSpQt27NiB7777Dr/5zW/w1ltvOU3gS0tLnc69ADiOH4HFYkFqaipmz57t8iQVx/Hjq298\nOnsf3NB53BN4MYd6nD59GtOnT3dZn5WVBaVS+VjHrvsleXfu44sa0u9btmzB888/7/RFuq633nrL\nZjsuLg6TJ0/G7t27MXv27CcP2oc8Sv/u2bPHuu3v7481a9ZgwIABOHLkCMaPH9+gf6+5jNu6GjKG\nT548iXnz5mHKlCn1XvyCY/jxNXQMNscx21C3bt3CnDlzoFarkZKS4rTN0KFDMXToUOt29+7dsXDh\nQsydOxcFBQVo166dp8L1KR999JHN9htvvIFDhw5h9+7deO655x75OBzHD3fo0CEUFRXh1VdfddmG\n47hx8NZ4ZqJUj0GDBiEnJ+ex91er1QDgcEZdq9UiJCTE6T4hISFOz8BrtVrr8Zq6R+13k8mEgwcP\nPvZZnY4dO6KoqOix9vVljzOug4KCEBwcjOLiYoe64OBgyGQyp+NcJpPZLCNtLh61j//9739jzZo1\nSEpKwssvv9zgf6e5jmFnaufUsrIytG3b1lruau4MCQnB7du3bcqEECgvL0doaKh7g/VxWVlZmDNn\nDl588UUkJSVBLpc/8r6dOnUCUPM9Pb7BfHSu/tbVarXTuRcAx/Ej2L9/P4YNG9bgk94cx4+moeOz\nofO4J3DpnRuFh4cjNDTUYV3lmTNn0LdvX6f79OrVCxqNxuYjxjt37uDGjRsu92mufvjhB5SVlSEu\nLq7edmazGcnJyQ7LFnJzc62THT1w4sQJvP/++zZlpaWl0Gq16Nixo0N7hUKBqKgop+M8Ojr6sT91\nbeoyMjKwbt06bN68+aFJEsfww0VHR0OhUDj00dmzZ53Onb169cLFixdhNBqtZefPn4der0fv3r3d\nHq+vunLlCl5//XXMnj0by5cvrzdJ2rlzJ7788kubsmvXrgGA07mEar7ztWLFCocluq7+1nv16uV0\n7lUoFA9dadHcVVZW4ujRoxg+fHi97TiOH5+r8RkaGuq07xo6j3sCEyU3kkgkeO2117B161ZcuHAB\nBoMBBw4cwIkTJ2yW2IwcORLffPMNACA2NhZdu3bF6tWrodVqUVpailWrViEyMhKDBg3y0jNpnDIz\nMxEWFuawlhWoudLK1KlTAQB+fn64ceMGli1bhtzcXBiNRhw+fBhffPFFvcujmqvAwEBs2rQJ27Zt\ng16vx+3bt5GUlIROnTph2LBhAGz7FwCmTZuGvXv34uTJkzAYDDh+/Dj27dvH/nWhoKAAy5cvx4YN\nG1xO/hzDDaNSqTBx4kSkpaUhLy8POp0O6enpuHXrFuLj41FUVISRI0fi3LlzAIAxY8ZALpcjNTUV\nlZWVKCwsRHJyMoYMGWJzsQJ6wGw2IyEhAZMmTXK6TDQrKwsjR45Efn4+AMBoNGLlypU4ceIETCYT\nLl++jNTUVIwfP55XKHVBrVbjyJEjWLFiBbRaLaqqqrBx40bk5eVhypQpDn0cHx8PjUaDbdu2obq6\nGrm5uUhLS8OkSZOgUqm8/Gwat0uXLsFoNKJ79+425RzHv5zXXnsN33//PQ4ePAiDwYDz58/jk08+\nwfTp061L6RYtWmT9PaqHzePewKV3T+DWrVsYOXIkgJoXELPZbD2D89e//hXjx4/HrFmzoNfrMXfu\nXJSWlqJz5854//33ba4CkpeXZ/2SvJ+fHzZt2oSVK1di2LBhkEgkGDRoEDZt2uTyS97NVXFxsctJ\nSqvV4vr169bttWvXYsOGDZg+fTpKS0sRFhaG5cuXO/1x2uYuOjoaH374If7+978jLS0NADB48GBs\n377d+oN79v07evRoVFRUYNmyZSgsLERYWBiSkpKsfx9ka9++faiqqnL43hHwYO7gGG64xMREJCcn\n45VXXsG9e/fQvXt3bNmyBe3bt8fNmzetL7xAzQvy1q1bsWrVKsTGxkKpVCIuLs764+Hk6Ny5c8jO\nzsaVK1ccLv0/btw4jB07Fnl5edZP6X7/+9/DZDJhxYoVKCgoQGBgICZMmIA333zTG+H7hICAAHzy\nySdYv349Ro0aBZ1Ohx49emDHjh3o0qULTp06ZdPH4eHh2Lx5M5KTk7FhwwYEBgZizJgx+POf/+zl\nZ9L41S4lt/8qhE6n4zhugBEjRiA/Px9CCAA1J/8lEgnGjRuHVatWITU1FR988AEWLVoEtVqNqVOn\n2vzgb0FBgc2lwuubx71BImqfGREREREREQHg0jsiIiIiIiIHTJSIiIiIiIjsMFEiIiIiIiKyw0SJ\niIiIiIjIDhMlIiIiIiIiO0yUiIiIiIiI7DBRIiIiIiIissNEiYiIiIiIyA4TJSIiIiIiIjtMlIiI\nqMk6ePAgunXrhtOnT1vLiouL0adPH7z33ntejIyIiBo7iRBCeDsIIiIid5k/fz5ycnKQkZEBhUJh\n3f7yyy+hVCq9HR4RETVSTJSIiKhJ02q1GDNmDF599VXExMRgzpw52LlzJ3r27Ont0IiIqBFjokRE\nRE3e4cOH8ac//QlqtRpjx47FggULvB0SERE1ckyUiIioyTObzYiLi0NhYSGOHDmC9u3bezskIiJq\n5HgxByIiavK2bNkCvV6PmJgYrFy50tvhEBGRD2CiRERETdrVq1exceNGJCUlYe3atTh+/Di++OIL\nb4dFRESNHJfeERFRk2U2mzF58mSo1Wp8/PHHAIC0tDRs27YNBw4cQLt27bwcIRERNVb8RImIiJqs\nzZs3Izc3F++++6617A9/+APatm2LxMRE8FwhERG5wk+UiIiIiIiI7PATJSIiIiIiIjtMlIiIiIiI\niOwwUSIiIiIiIrLDRImIiIiIiMgOEyUiIiIiIiI7TJSIiIiIiIjsMFEiIiIiIiKyw0SJiIiIiIjI\nDhMlIiIiIiIiO/8fljZALwYtWwsAAAAASUVORK5CYII=\n", 396 | "text/plain": [ 397 | "
" 398 | ] 399 | }, 400 | "metadata": { 401 | "tags": [] 402 | } 403 | } 404 | ] 405 | }, 406 | { 407 | "cell_type": "markdown", 408 | "metadata": { 409 | "id": "1wNwylPKQ-SQ", 410 | "colab_type": "text" 411 | }, 412 | "source": [ 413 | "# How can we find the parameters for our model?\n", 414 | "\n", 415 | "Let's examine some approaches to find good parameters for our model. But what does good mean in this context?\n", 416 | "\n", 417 | "## Loss function\n", 418 | "\n", 419 | "We have a model that we can use to make decisions, but we still have to find the parameters $W$. To do that, we need an objective measurement of how good some set of parameters are. For that purpose, we will use a loss (cost) function:\n", 420 | "\n", 421 | "$$J(W) = \\frac{1}{m}\\sum^m_{i = 1}Cost(h_w(x^{(i)}), y^{(i)})$$\n", 422 | "\n", 423 | "$$\n", 424 | " Cost(h_w(x), y) = \n", 425 | " \\begin{cases} \n", 426 | " -log(h_w(x)) &\\text{if} y = 1\\\\\n", 427 | " -log(1 - h_w(x)) &\\text{if} y = 0\n", 428 | " \\end{cases}\n", 429 | "$$\n", 430 | "\n", 431 | "Which is also known as the [*Log loss* or *Cross-entropy loss*](https://ml-cheatsheet.readthedocs.io/en/latest/loss_functions.html) function\n", 432 | "\n", 433 | "![](https://ml-cheatsheet.readthedocs.io/en/latest/_images/y1andy2_logistic_function.png)\n", 434 | "\n", 435 | "We can compress the above function into one:\n", 436 | "\n", 437 | "$$J(W) = \\frac{1}{m}(-y \\log{(h_w)} - (1 - y) \\log{(1 - h_w)})$$\n", 438 | "\n", 439 | "where\n", 440 | "\n", 441 | "$$h_w(x) = g(w^Tx)$$\n", 442 | "\n", 443 | "Let's implement it in Python:" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "metadata": { 449 | "id": "oIeiJSugk4qB", 450 | "colab_type": "code", 451 | "colab": {} 452 | }, 453 | "source": [ 454 | "def loss(h, y):\n", 455 | " return (-y * np.log(h) - (1 - y) * np.log(1 - h)).mean()" 456 | ], 457 | "execution_count": 0, 458 | "outputs": [] 459 | }, 460 | { 461 | "cell_type": "code", 462 | "metadata": { 463 | "id": "Vyzx34TDoBoB", 464 | "colab_type": "code", 465 | "colab": {} 466 | }, 467 | "source": [ 468 | "class TestLoss(unittest.TestCase):\n", 469 | "\n", 470 | " def test_zero_h_zero_y(self):\n", 471 | " self.assertLess(loss(h=0.000001, y=.000001), 0.0001)\n", 472 | "\n", 473 | " def test_one_h_zero_y(self):\n", 474 | " self.assertGreater(loss(h=0.9999, y=.000001), 9.0)\n", 475 | "\n", 476 | " def test_zero_h_one_y(self):\n", 477 | " self.assertGreater(loss(h=0.000001, y=0.9999), 9.0)\n", 478 | "\n", 479 | " def test_one_h_one_y(self):\n", 480 | " self.assertLess(loss(h=0.999999, y=0.999999), 0.0001)" 481 | ], 482 | "execution_count": 0, 483 | "outputs": [] 484 | }, 485 | { 486 | "cell_type": "code", 487 | "metadata": { 488 | "id": "wd6-FSTPoIsV", 489 | "colab_type": "code", 490 | "outputId": "1e84d7ce-fdb3-49fe-bfdd-3a11cb4216a1", 491 | "colab": { 492 | "base_uri": "https://localhost:8080/", 493 | "height": 102 494 | } 495 | }, 496 | "source": [ 497 | "run_tests()" 498 | ], 499 | "execution_count": 0, 500 | "outputs": [ 501 | { 502 | "output_type": "stream", 503 | "text": [ 504 | ".......\n", 505 | "----------------------------------------------------------------------\n", 506 | "Ran 7 tests in 0.010s\n", 507 | "\n", 508 | "OK\n" 509 | ], 510 | "name": "stderr" 511 | } 512 | ] 513 | }, 514 | { 515 | "cell_type": "markdown", 516 | "metadata": { 517 | "id": "AHL3KqF7Rfvf", 518 | "colab_type": "text" 519 | }, 520 | "source": [ 521 | "## Approach \\#1 - I'm thinking of a number(s)\n", 522 | "\n", 523 | "Let's think of 3 numbers that represent the coefficients $w_0, w_1, w_2$." 524 | ] 525 | }, 526 | { 527 | "cell_type": "code", 528 | "metadata": { 529 | "id": "sWKZ-4x8SP_b", 530 | "colab_type": "code", 531 | "outputId": "af2000b9-d95a-42b7-a2fd-748cdb580f91", 532 | "colab": { 533 | "base_uri": "https://localhost:8080/", 534 | "height": 34 535 | } 536 | }, 537 | "source": [ 538 | "X = df['amount_spent'].astype('float').values\n", 539 | "y = df['send_discount'].astype('float').values\n", 540 | "\n", 541 | "def predict(x, w):\n", 542 | " return sigmoid(x * w)\n", 543 | "\n", 544 | "def print_result(y_hat, y):\n", 545 | " print(f'loss: {np.round(loss(y_hat, y), 5)} predicted: {y_hat} actual: {y}')\n", 546 | " \n", 547 | "y_hat = predict(x=X[0], w=.5)\n", 548 | "print_result(y_hat, y[0])" 549 | ], 550 | "execution_count": 0, 551 | "outputs": [ 552 | { 553 | "output_type": "stream", 554 | "text": [ 555 | "loss: 25.0 predicted: 0.999999999986112 actual: 0.0\n" 556 | ], 557 | "name": "stdout" 558 | } 559 | ] 560 | }, 561 | { 562 | "cell_type": "markdown", 563 | "metadata": { 564 | "id": "OI1PYdiNTqhu", 565 | "colab_type": "text" 566 | }, 567 | "source": [ 568 | "I am pretty lazy this approach seems like too much hard work for me.\n", 569 | "\n", 570 | "## Approach \\#2 - Try out many numbers\n", 571 | "\n", 572 | "Alright, these days computers are pretty fast, 6+ core laptops are everywhere also your phones can are pretty performant, too! Let's use that power for good™ and try to find these parameters by just trying out a lot of numbers:" 573 | ] 574 | }, 575 | { 576 | "cell_type": "code", 577 | "metadata": { 578 | "id": "9kTZ4F2qhyHC", 579 | "colab_type": "code", 580 | "outputId": "bb11ea57-5ec2-4a9f-ec35-e3825d681762", 581 | "colab": { 582 | "base_uri": "https://localhost:8080/", 583 | "height": 391 584 | } 585 | }, 586 | "source": [ 587 | "for w in np.arange(-1, 1, 0.1):\n", 588 | " y_hat = predict(x=X[0], w=w)\n", 589 | " print(loss(y_hat, y[0]))" 590 | ], 591 | "execution_count": 0, 592 | "outputs": [ 593 | { 594 | "output_type": "stream", 595 | "text": [ 596 | "0.0\n", 597 | "0.0\n", 598 | "0.0\n", 599 | "6.661338147750941e-16\n", 600 | "9.359180097590508e-14\n", 601 | "1.3887890837434982e-11\n", 602 | "2.0611535832696244e-09\n", 603 | "3.059022736706331e-07\n", 604 | "4.539889921682063e-05\n", 605 | "0.006715348489118056\n", 606 | "0.6931471805599397\n", 607 | "5.006715348489103\n", 608 | "10.000045398900186\n", 609 | "15.000000305680194\n", 610 | "19.999999966169824\n", 611 | "24.99999582410784\n", 612 | "30.001020555434774\n", 613 | "34.945041100449046\n", 614 | "inf\n", 615 | "inf\n" 616 | ], 617 | "name": "stdout" 618 | }, 619 | { 620 | "output_type": "stream", 621 | "text": [ 622 | "/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:2: RuntimeWarning: divide by zero encountered in log\n", 623 | " \n" 624 | ], 625 | "name": "stderr" 626 | } 627 | ] 628 | }, 629 | { 630 | "cell_type": "markdown", 631 | "metadata": { 632 | "id": "xvnblS0UzG5E", 633 | "colab_type": "text" 634 | }, 635 | "source": [ 636 | "## Approach \\#3 - Gradient descent\n", 637 | "\n", 638 | "Gradient descent algorithms (yes, there are a lot of them) provide us with a way to find a minimum of some function $f$. They work by iteratively going in the direction of the descent as defined by the gradient.\n", 639 | "\n", 640 | "In Machine Learning, we use gradient descent algorithms to find \"good\" parameters for our models (Logistic Regression, Linear Regression, Neural Networks, etc...).\n", 641 | "\n", 642 | "![](https://i.ytimg.com/vi/b4Vyma9wPHo/maxresdefault.jpg)\n", 643 | "\n", 644 | "**Somewhat deeper look into how Gradient descent works (Source: PyTorchZeroToAll)**\n", 645 | "\n", 646 | "Starting somewhere, we take our first step downhill in the direction specified by the negative gradient. Next, we recalculate the negative gradient and take another step in the direction it specifies. This process continues until we get to a point where we can no longer move downhill - a local minimum.\n", 647 | "\n", 648 | "### First derivative of the sigmoid function\n", 649 | "\n", 650 | "The first derivative of the sigmoid function is given by the following equation:\n", 651 | "\n", 652 | "$$g'(z) = g(z)(1 - g(z))$$\n", 653 | "\n", 654 | "Complete derivation can be found [here](https://math.stackexchange.com/a/1225116/499458)." 655 | ] 656 | }, 657 | { 658 | "cell_type": "markdown", 659 | "metadata": { 660 | "id": "O54XG0ApLOUF", 661 | "colab_type": "text" 662 | }, 663 | "source": [ 664 | "### First derivative of the cost function\n", 665 | "\n", 666 | "Recall that the cost function was given by the following equation:\n", 667 | "\n", 668 | "$$J(W) = \\frac{1}{m}(-y \\log{(h_w)} - (1 - y) \\log{(1 - h_w)})$$\n", 669 | "\n", 670 | "Given $g'(z) = g(z)(1 - g(z))$\n", 671 | "\n", 672 | "Then:\n", 673 | "\n", 674 | "$$\\frac{\\partial{J(W)}}{\\partial{W}} =\\frac{1}{m}(y(1 - h_w) - (1 - y)h_w)x = \\frac{1}{m}(y - h_w)x$$" 675 | ] 676 | }, 677 | { 678 | "cell_type": "markdown", 679 | "metadata": { 680 | "id": "VOIkshOtOr4c", 681 | "colab_type": "text" 682 | }, 683 | "source": [ 684 | "### Updating our parameters $W$\n", 685 | "\n", 686 | "The parameter updating rule we're going to use is defined by:\n", 687 | "\n", 688 | "$$W := W - \\alpha (\\frac{1}{m}(y - h_w)x)$$\n", 689 | "\n", 690 | "The parameter $\\alpha$ is known as **learning rate**. High learning rate can converge quickly, but risks overshooting the lowest point. Low learning rate allows for confident moves in the direction of the negative gradient. However, it time-consuming so it will take us a lot of time to get to converge.\n", 691 | "\n", 692 | "![](https://cdn-images-1.medium.com/max/1600/0*QwE8M4MupSdqA3M4.png)\n", 693 | "\n", 694 | "**Big vs Small learning rate (Source: TowardsDataScoemce)**\n" 695 | ] 696 | }, 697 | { 698 | "cell_type": "markdown", 699 | "metadata": { 700 | "id": "xYcz3d_kTVkP", 701 | "colab_type": "text" 702 | }, 703 | "source": [ 704 | "### The gradient descent algorithm\n", 705 | "\n", 706 | "```\n", 707 | "Repeat until convergence {\n", 708 | " 1. Calculate gradient average\n", 709 | " 2. Multiply by learning rate\n", 710 | " 3. Subtract from weights\n", 711 | "}\n", 712 | "```" 713 | ] 714 | }, 715 | { 716 | "cell_type": "code", 717 | "metadata": { 718 | "id": "TLDOcJfGeV95", 719 | "colab_type": "code", 720 | "colab": {} 721 | }, 722 | "source": [ 723 | "def predict(X, W):\n", 724 | " return sigmoid(np.dot(X, W))\n", 725 | " \n", 726 | "def fit(X, y, n_iter=100000, lr=0.01):\n", 727 | "\n", 728 | " W = np.zeros(X.shape[1])\n", 729 | "\n", 730 | " for i in range(n_iter):\n", 731 | " z = np.dot(X, W)\n", 732 | " h = sigmoid(z)\n", 733 | " gradient = np.dot(X.T, (h - y)) / y.size\n", 734 | " W -= lr * gradient\n", 735 | " return W" 736 | ], 737 | "execution_count": 0, 738 | "outputs": [] 739 | }, 740 | { 741 | "cell_type": "code", 742 | "metadata": { 743 | "id": "2tY5RhfnnO9P", 744 | "colab_type": "code", 745 | "colab": {} 746 | }, 747 | "source": [ 748 | "class TestGradientDescent(unittest.TestCase):\n", 749 | "\n", 750 | " def test_correct_prediction(self):\n", 751 | " global X\n", 752 | " global y\n", 753 | " if len(X.shape) != 2: \n", 754 | " X = X.reshape(X.shape[0], 1)\n", 755 | " w, _ = fit(X, y)\n", 756 | " y_hat = predict(X, w).round()\n", 757 | " self.assertTrue((y_hat == y).all())" 758 | ], 759 | "execution_count": 0, 760 | "outputs": [] 761 | }, 762 | { 763 | "cell_type": "code", 764 | "metadata": { 765 | "id": "BV7mMdg5nc0C", 766 | "colab_type": "code", 767 | "outputId": "d3eece19-0f4e-464c-b46f-7444bbef475e", 768 | "colab": { 769 | "base_uri": "https://localhost:8080/", 770 | "height": 238 771 | } 772 | }, 773 | "source": [ 774 | "run_tests()" 775 | ], 776 | "execution_count": 0, 777 | "outputs": [ 778 | { 779 | "output_type": "stream", 780 | "text": [ 781 | "E.......\n", 782 | "======================================================================\n", 783 | "ERROR: test_correct_prediction (__main__.TestGradientDescent)\n", 784 | "----------------------------------------------------------------------\n", 785 | "Traceback (most recent call last):\n", 786 | " File \"\", line 8, in test_correct_prediction\n", 787 | " w, _ = fit(X, y)\n", 788 | "ValueError: not enough values to unpack (expected 2, got 1)\n", 789 | "\n", 790 | "----------------------------------------------------------------------\n", 791 | "Ran 8 tests in 0.888s\n", 792 | "\n", 793 | "FAILED (errors=1)\n" 794 | ], 795 | "name": "stderr" 796 | } 797 | ] 798 | }, 799 | { 800 | "cell_type": "markdown", 801 | "metadata": { 802 | "id": "xUWBzDZkpdOZ", 803 | "colab_type": "text" 804 | }, 805 | "source": [ 806 | "Well, that's not good, after all that hustling we're nowhere near achieving our goal of finding good parameters for our model. But, what went wrong? Let's start by finding whether our algorithm improves over time. We can use our loss metric for that:" 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "metadata": { 812 | "id": "pLe3grGwqNYx", 813 | "colab_type": "code", 814 | "colab": {} 815 | }, 816 | "source": [ 817 | "def fit(X, y, n_iter=100000, lr=0.01):\n", 818 | "\n", 819 | " W = np.zeros(X.shape[1])\n", 820 | " errors = []\n", 821 | " for i in range(n_iter):\n", 822 | " z = np.dot(X, W)\n", 823 | " h = sigmoid(z)\n", 824 | " gradient = np.dot(X.T, (h - y)) / y.size\n", 825 | " W -= lr * gradient\n", 826 | " \n", 827 | " if(i % 10000 == 0): \n", 828 | " e = loss(h, y) \n", 829 | " print(f'loss: {e} \\t')\n", 830 | " errors.append(e)\n", 831 | " \n", 832 | " return W, errors" 833 | ], 834 | "execution_count": 0, 835 | "outputs": [] 836 | }, 837 | { 838 | "cell_type": "code", 839 | "metadata": { 840 | "id": "MtUNQ0Krqj_q", 841 | "colab_type": "code", 842 | "outputId": "94d53d4d-9088-4f0f-ccc5-7410e8536a2c", 843 | "colab": { 844 | "base_uri": "https://localhost:8080/", 845 | "height": 408 846 | } 847 | }, 848 | "source": [ 849 | "run_tests()" 850 | ], 851 | "execution_count": 0, 852 | "outputs": [ 853 | { 854 | "output_type": "stream", 855 | "text": [ 856 | "loss: 0.6931471805599453 \t\n", 857 | "loss: 0.41899283818630056 \t\n", 858 | "loss: 0.41899283818630056 \t\n", 859 | "loss: 0.41899283818630056 \t\n", 860 | "loss: 0.41899283818630056 \t\n", 861 | "loss: 0.41899283818630056 \t\n", 862 | "loss: 0.41899283818630056 \t\n", 863 | "loss: 0.41899283818630056 \t\n", 864 | "loss: 0.41899283818630056 \t\n" 865 | ], 866 | "name": "stdout" 867 | }, 868 | { 869 | "output_type": "stream", 870 | "text": [ 871 | "F......." 872 | ], 873 | "name": "stderr" 874 | }, 875 | { 876 | "output_type": "stream", 877 | "text": [ 878 | "loss: 0.41899283818630056 \t\n" 879 | ], 880 | "name": "stdout" 881 | }, 882 | { 883 | "output_type": "stream", 884 | "text": [ 885 | "\n", 886 | "======================================================================\n", 887 | "FAIL: test_correct_prediction (__main__.TestGradientDescent)\n", 888 | "----------------------------------------------------------------------\n", 889 | "Traceback (most recent call last):\n", 890 | " File \"\", line 10, in test_correct_prediction\n", 891 | " self.assertTrue((y_hat == y).all())\n", 892 | "AssertionError: False is not true\n", 893 | "\n", 894 | "----------------------------------------------------------------------\n", 895 | "Ran 8 tests in 0.938s\n", 896 | "\n", 897 | "FAILED (failures=1)\n" 898 | ], 899 | "name": "stderr" 900 | } 901 | ] 902 | }, 903 | { 904 | "cell_type": "code", 905 | "metadata": { 906 | "id": "gi3ZCFD1znth", 907 | "colab_type": "code", 908 | "outputId": "044bc2bb-5969-43cf-a6d1-75e71b4f4f0b", 909 | "colab": { 910 | "base_uri": "https://localhost:8080/", 911 | "height": 681 912 | } 913 | }, 914 | "source": [ 915 | "_, errors = fit(X, y)\n", 916 | "plt.plot(np.arange(len(errors)), errors)\n", 917 | "plt.xlabel(\"iteration^10000\")\n", 918 | "plt.ylabel(\"error\")\n", 919 | "plt.ylim(0, 1)\n", 920 | "plt.show()" 921 | ], 922 | "execution_count": 0, 923 | "outputs": [ 924 | { 925 | "output_type": "stream", 926 | "text": [ 927 | "loss: 0.6931471805599453 \t\n", 928 | "loss: 0.41899283818630056 \t\n", 929 | "loss: 0.41899283818630056 \t\n", 930 | "loss: 0.41899283818630056 \t\n", 931 | "loss: 0.41899283818630056 \t\n", 932 | "loss: 0.41899283818630056 \t\n", 933 | "loss: 0.41899283818630056 \t\n", 934 | "loss: 0.41899283818630056 \t\n", 935 | "loss: 0.41899283818630056 \t\n", 936 | "loss: 0.41899283818630056 \t\n" 937 | ], 938 | "name": "stdout" 939 | }, 940 | { 941 | "output_type": "display_data", 942 | "data": { 943 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0oAAAHuCAYAAAChyPw8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzt3Xm41WW9///XZhbYDIIiAirBkUMC\nHtwomkOKleSshKDmcEpzKBzKKVKPYZNYDpF1UjHr5FELTSW1zPKbCmLJ4IBJKiCDgsakIMgG9u8P\nj/zaH1Cp2Gtt2I/HdXldh3vda603+/qc6+LZutdnV9TU1NQEAACA9RqVewAAAID6RigBAAAUCCUA\nAIACoQQAAFAglAAAAAqEEgAAQIFQAgAAKCh5KM2dOzcnnXRSevXqlXnz5n3g3gkTJmT48OEZMGBA\nDjrooFx++eVZuXJliSYFAAAaqpKG0u9+97sMGzYsO+6444funT17ds4888wcdthheeyxx/Kzn/0s\nzz33XEaNGlWCSQEAgIaspKG0dOnS3HbbbTnqqKM+dO+dd96Zj3zkIznppJOyzTbbpFu3bjn77LNz\n3333ZfHixSWYFgAAaKhKGkpDhw5N9+7dN2nvtGnT0q9fv1pr/fr1y5o1azJ9+vS6GA8AACBJ0qTc\nA7yfxYsXp23btrXW2rdvnyRZtGjRhz5/8uTJdTIXAACwdamqqtpgrd6G0gepqKjYpH0b+wuXy+TJ\nk+vVPDQMrjtKzTVHObjuKAfX3dbj/T5gqbe3B+/YsWOWLl1aa23JkiVJku22264cIwEAAA1EvQ2l\n/v375+mnn661Nnny5DRr1ix9+/Yt01QAAEBDUG9C6ZlnnsngwYPz6quvJkmGDx+euXPn5tZbb82q\nVasyc+bMjBkzJkOHDk1lZWWZpwUAALZmJf2O0iGHHJJXX301NTU1SZLBgwenoqIiRx11VI444ojM\nmjUr1dXVSZKuXbvmpptuyujRo/O9730vbdq0yeGHH56vfOUrpRwZAABogEoaSr/97W8/8PEZM2bU\n+vOee+6ZX/7yl3U5EgAAwAbqzdE7AACA+kIoAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIA\nACgQSgAAAAVCCQAAoEAoAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAA\noEAoAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAAoEAoAQAAFAglAACA\nAqEEAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAAoEAoAQAAFAglAACAAqEEAABQIJQAAAAK\nhBIAAECBUAIAACgQSgAAAAVCCQAAoEAoAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIAACgQ\nSgAAAAVCCQAAoEAoAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAAoEAo\nAQAAFAglAACAAqEEAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAAoEAoAQAAFAglAACAAqEE\nAABQIJQAAAAKhBIAAECBUAIAACgQSgAAAAVCCQAAoEAoAQAAFAglAACAAqEEAABQUPJQWrlyZa64\n4ooMGjQoVVVVGTZsWCZMmPC++++9994cddRR6d+/f/bbb7985StfyYIFC0o4MQAA0NCUPJRGjRqV\nqVOnZuzYsZk4cWKOOeaYnHnmmZk5c+YGe5944olccsklOeOMM/KnP/0pd911V15//fVccMEFpR4b\nAABoQEoaSsuWLcv48eMzYsSIdO/ePc2bN8/w4cPTo0eP3HHHHRvsf/bZZ9O+ffsceuihadq0aTp1\n6pRDDz00zz77bCnHBgAAGpiShtL06dNTXV2dvn371lrv169fnn766Q32H3jggVmxYkXuu+++rF69\nOosWLcoDDzyQwYMHl2pkAACgAWpSyjdbvHhxkqRdu3a11tu3b59FixZtsH/XXXfN9773vVx44YW5\n6KKLUlNTk7322iuXX375Jr3f5MmT//WhN6P6Ng8Ng+uOUnPNUQ6uO8rBdbd1K2kofZCKiooN1p56\n6qlceOGF+cY3vpFBgwZl0aJFueKKK3L22Wfnpz/96Ye+ZlVVVV2M+k+ZPHlyvZqHhsF1R6m55igH\n1x3l4Lrberxf8Jb06F2HDh2SJEuXLq21vmTJknTs2HGD/bfddlsGDBiQww47LNtss026du2a888/\nP5MmTcqLL75YkpkBAICGp6Sh1KdPnzRr1izTpk2rtT5lypQMGDBgg/1r167NunXrNlhLssE6AADA\n5lLSUKqsrMyQIUMyZsyYzJo1KytXrszYsWMzf/78DB8+PAsXLszgwYMzderUJMkhhxySSZMm5be/\n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\nBQsW1OUsbIE+e/AO6bZd89w78W95btbyco8DAACbxSaHUu/evfPkk0/W5SxsgZo1bZTzh+yUiork\n2rvmZNXqdeUeCQAA/mWbfPTuuOOOy0033ZRHH300u+22W1q1arXBnmHDhm3W4dgy9N65VY7Zb7vc\n/dgb+dlDr+ULh3cp90gAAPAv2eRQ+trXvpYkeemll3L//fdv8HhFRYVQasBO/mTnPPn8m7ln4hvZ\nr2+7fHTnDUMaAAC2FJscSr///e/rcg62cM2bNsp5n+mWi258KdeMm5MbzumV5k03+WQnAADUK5v8\nL9lx48alTZs26dKly/v+R8PWZ5fWOXKfjpn/t3fy84fd+AMAgC3XJofSbbfdltdff70uZ2ErcOoh\nndN522a5+7HX88KcFeUeBwAA/imbHErnnnturr766syePbsOx2FL16JZ45w3ZKesq0muuWtOVle7\nCx4AAFueTf6O0t13350lS5bk05/+dFq0aLHBXe8qKiry2GOPbfYB2fL0+0jrHLF3x4yf9Lf87x8W\n5NRDdiz3SAAA8A/Z5FDadddd63IOtjL/Obhz/jTjzfzy0dfzsd3aZdeuLcs9EgAAbLJNDqVvf/vb\nSZKXX345zz//fN5444185jOfSZs2bfLmm2+mTZs2dTYkW55tmjfOeUO65as3v5xrx83J9V/aNc2a\nuAseAABbhk3+l+uqVatyzjnn5PDDD8+FF16Yq6++OsuWLcvs2bMzePDgzJw5sy7nZAv0Hz0qc+jA\nDpm9cFXu+MPCco8DAACbbJND6bvf/W6mTZuWq666Ko8++mhatGiRJOnSpUv23HPPXHvttXU2JFuu\nz396x2zfrmnu/OPCvDT/7XKPAwAAm2STQ+k3v/lNRo0alSOPPDLbb7/9+vWmTZvmC1/4QiZNmlQn\nA7Jla9m8cc49dqesW5dcM25Oqte4Cx4AAPXfJofSihUr0qNHj40+VllZmVWrVm22odi67PFvlRm8\n57aZtWBVfvH//C4uAADqv00OpZ122ikPP/zwRh+bMGFCunXrttmGYutz2qFd0rFt09z+yILMfG1l\nuccBAIAPtMl3vTv66KNz9dVX58UXX8zHPvax1NTU5NFHH838+fNz++235ytf+UpdzskWrlWLxjnn\nmG65/NaZuWbcnFx39q5p0rii3GMBAMBGbXIo/ed//mdWrlyZW265JXfffXeS5Morr0ybNm1y1lln\n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\ncmRatWpV7lFoAO6///5cddVVueyyyzJ58uR861vfyp133pnnnnuu3KOxlVq3bl1OO+20tGjRIg8+\n+GAmTpyYT3/60xkxYoR/322FhFIdWbZsWcaPH58RI0ake/fuad68eYYPH54ePXrkjjvuKPd4bKXe\neuut9OjRIyNHjsx2222X5s2b5/TTT8/bb7+dZ555ptzj0QBcc8016d69e3r37l3uUWgAbrjhhpx2\n2mnZd99906xZswwcODAPPvhg+vTpU+7R2EotXrw48+fPz9FHH5127dqlWbNmOeGEE1JdXZ0XXnih\n3OOxmQmlOjJ9+vRUV1enb9++tdb79euXp59+ukxTsbWrrKzMt771rVq/Z2zu3LlJkh122KFcY9FA\nPPXUU7n33nvz9a9/vdyj0AC8/vrrefnll9OyZcscf/zx2WOPPXLEEUdk/Pjx5R6NrVjHjh1TVVWV\ncePGZfHixamurs7tt9+e9u3bZ+DAgeUej82sSbkH2FotXrw4SdKuXbta6+3bt8+iRYvKMRIN0PLl\ny/PVr341Bx988AbRDpvTypUrM3LkyFx88cXp1KlTucehAViwYEGS5M4778zVV1+dbt26Zdy4cbng\nggvSuXPnDBgwoMwTsrUaM2ZMTj/99Oyzzz6pqKhI+/btc/3116dDhw7lHo3NzCdKZVBRUVHuEWgA\n5s+fn+OPPz4dOnTId7/73XKPw1bummuuyS677JJjjz223KPQQLz3ayBPOumk9OrVKy1btszJJ5+c\nPn365O677y7zdGytVq9endNOOy3du3fP448/nqeeeipf+tKXcuaZZ+all14q93hsZkKpjrz3vyos\nXbq01vqSJUvSsWPHcoxEA/LMM89k6NChqaqqyo033piWLVuWeyS2Yu8dubvyyivLPQoNyPbbb5/k\n3ZMaf2+nnXZy8xrqzKRJk/L888+v/y5w69atc+KJJ6Zr16656667yj0em5mjd3WkT58+adasWaZN\nm5ZDDjlk/fqUKVNy0EEHlXEytnZ//etfc/rpp+ess87KqaeeWu5xaADuuuuuvP322znyyCPXry1f\nvjzPPPNM/vCHP+RXv/pVGadja7X99tunXbt2efbZZ/OJT3xi/forr7ziZg7UmXXr1iVJ1q5dW2t9\n7dq16z/lZOvhE6U6UllZmSFDhmTMmDGZNWtWVq5cmbFjx2b+/PkZPnx4ucdjK7V27dpccsklGTp0\nqEiiZC655JI8/PDDuffee9f/16dPnwwfPjw33nhjucdjK9W4ceP853/+Z37+859n4sSJWb16dW67\n7bb85S9/yfHHH1/u8dhK7bHHHunYsWO++93vZsmSJXnnnXfyi1/8IrNmzcrgwYPLPR6bWUWN/K0z\nq1evzujRo3P//fdnxYoV6d27dy666KJUVVWVezS2Uk899VROPPHENG3adIPvwh111FH5xje+UabJ\naGhOOumk7LXXXhkxYkS5R2ErVlNTkxtuuCG//OUvs2jRonTv3j0XX3xx9ttvv3KPxlbshRdeyDXX\nXJPnnnsub731Vj7ykY/knHPOycEHH1zu0djMhBIAAECBo3cAAAAFQgkAAKBAKAEAABQIJQAAgAKh\nBAAAUCCUAAAACoQSALUMGjQo559/frnHWO+kk07KcccdVyevXVNTkyOOOCIf/ehH8/DDD290z333\n3Zf+/fvnpJNO2ujjf/7znzN8+PD069cvAwcOzCWXXJKlS5fW2jNjxox8/vOfT//+/bPHHnvki1/8\nYubPn19rz6uvvpoRI0ZkwIAB2X333XPqqafmhRdeqLVn2bJl+epXv5q99947ffv2zXHHHZcnn3zy\nX/gJAPB+hBIAtYwbNy6jRo1Kklx88cUZM2ZMSd//oIMOqvWP/zFjxuTGG2+sk/f63e9+l2XLluWc\nc87JD3/4w1qPrVq1Kl/72tdy5ZVXplWrVht9/syZM/P5z38+PXv2zL333pvvf//7ee655/KlL31p\n/Z4lS5bk1FNPTdOmTfOLX/wit956a5YtW5bPfe5zWb16dZJ3f0H55z//+bz++uu59dZbc8cdd6RN\nmzY55ZRTsnjx4vWvNWLEiEyZMiU/+MEP8qtf/Sp9+vTJaaedlpdeeqkOfjoADZtQAqCWbbfdNpWV\nlUmSqVOnlvS9Fy5cmFdffbXWWrt27dKuXbvN/l41NTX54Q9/mM997nM5+eSTM3/+/DzyyCPrH3/i\niSfy3HPP5e677063bt02+ho33XRTOnbsmK9//evp3r17Bg4cmK9//ev585//nEmTJiVJbrvttqxa\ntSpXX311/u3f/i39+vXL6NGjM2fOnIwfPz5J8sADD2TmzJkZPXp0+vTpk969e+c73/lO1q5dm5//\n/OdJ3v3k6sknn8wVV1yRAQMGpGfPnrnsssuy44475qabbtrsPx+Ahk4oAVDLe0fvevXqlVdeeSU/\n+MEP0qtXr8ybNy9JMm3atHzuc5/LPvvsk/79++fkk0/O9OnT1z//ySefTK9evfKb3/wmhx12WA48\n8MAkyZo1a3Lttddm0KBB2W233bLvvvvmnHPOWf+6Tz75ZA444IAkycknn5xBgwYl2fDo3TvvvJOr\nrroqBx54YPr06ZP9998///Vf/5W33npr/Z6TTjop55xzTh588MEceuih6devXw477LBaIfT73/8+\nCxYsyLBhw9KyZcucfPLJueGGG9Y//tGPfjR33nnn+0ZSkjz++OPZd99907hx4/Vre+yxRyorK/Po\no4+u39O/f//18ZkkO+64Y3r27Ll+z2OPPZZddtklO++88/o9LVu2zIABA2q9TosWLbLnnnuu31NR\nUZH9999//R4ANh+hBMBGvfeP78997nN5/PHH07lz58yaNSunnnpqKioqcsstt+T2229PZWVlTjnl\nlA0+Cbrxxhtz4YUX5s4770yS/Pd//3fGjh2bSy65JA8//HB+9KMfZf78+TnnnHOSJP379891112X\n5N3jduPGjdvoXCNHjsy4cePy5S9/OQ888ED+67/+Kw8//HDOO++8WvteeOGF3HXXXbn66qszbty4\ntGnTJhdeeGFWrFiRJPnhD3+YU045Jdtss02Sd+Nq1qxZ+eMf/5gk6dSpU1q0aPG+P58VK1bk9ddf\n3yCkKioq0qVLl8ycOTNJMmvWrHTt2nWD53fr1u0f3tO5c+c0adKk1p6uXbtm8eLFG3wvCoB/jVAC\nYKM6dOiQ5N1PNrbbbrs0btw4t956axo1apTrr78+vXv3zr//+79n9OjRqaioWH9E7D0f+9jHcuCB\nB6ZTp05JkhNOOCH33XdfPvWpT6Vz587p169fPvOZz2T69OlZvHhxmjVrlrZt2yZJ2rZtm2233XaD\nmRYuXJj7778/Z511Vo488sjstNNO+cQnPpERI0bk8ccfz+zZs9fvfe211zJ69Ojstttu2XXXXXPS\nSSflrbfeysyZM/OHP/whr7zySj772c+u39+mTZuceOKJtT5V+iDLly9Pko1+f6lly5brH1+xYsW/\ntGfFihVZt25dli9fvtE97639/SdqAPzrmnz4FgB41zPPPJPdd989rVu3Xr/WqlWr9OvXL88//3yt\nvbvttlutPzdv3jz33Xdffv/732fhwoWprq7OmjVrkrx7w4ONhVHRc889l5qamuyxxx611nffffck\nyfPPP59ddtklSbLzzjvXes33/u+lS5dm0KBBmTx58gav/+Uvf/lDZwCgYRBKAGyy5cuXZ8aMGenf\nv3+t9dWrV68PlPcUP/244IILMmHChFx44YXZc889s8022+Shhx7Kd7/73X/o/ZPUCrW/f6/3jtUl\n734a8/cqKiqSvHsTh83hve8cvTdTcc73juRVVlbWmuvv97Rp0+ZD91RWVqZRo0aprKzc4Jbiyf//\nSdJ7n8YBsHkIJQA2WZs2bbLDDjvkG9/4xgaPFb878/eWL1+eRx55JGeccUat30e0bt26f+j934uT\n4jGz9/78XniUQsuWLdO5c+fMmTOn1vqaNWsyd+7cHHTQQUmSj3zkIxvsSd79zlGfPn3W79nYHQZn\nz56dnj17rt/zyCOPZM2aNbV+1rNnz852221X0r87QEPgO0oAfKC//wTmP/7jP9bfVGDnnXde/9+6\ndeuy3Xbbve9rVFdXp6amptZtvteuXZv77rvvQ9/z7+22225p1KjRBsfmpk6dmoqKivXhUSoHHHBA\nJkyYsP4IYZJMmjQpK1euXB9KH//4xzN16tQsW7Zs/Z6ZM2dm9uzZtfbMmTNn/Y0bknd/uezkyZNr\n7XnnnXfyxBNPrN+zZs2aPProo+vvEAjA5iOUANioJk2apFWrVpk2bVpeeOGFvPnmmzn55JOzYsWK\nXHzxxZk+fXrmzp2b2267LUceeWQeeOCB932t9u3bZ5dddsndd9+dGTNm5Pnnn88ZZ5yRqqqqJMmU\nKVNqHUWbMGFCnn/++Q2CqVOnTjnyyCPz4x//OA888EDmzp2bBx54IGPGjMlhhx2WLl26bLa//1tv\nvZU33ngjb7zxRtasWZPq6ur1f37vmNzpp5+eZcuW5fLLL8/s2bPz1FNPZdSoUTnooIPWH08cPnx4\nKisrc9FFF+XFF1/M9OnTc/HFF6d3794ZPHhwkuSQQw7Jrrvuuv7n+tJLL+Wiiy5KmzZtcsIJJyR5\nN1IPOOCAXHnllXnqqafyyiuv5PLLL8+bb76Z008/fbP9vQF4l1AC4H2dddZZmTp1ak488cS8/PLL\n2XnnnfM///M/WbZsWT772c/m05/+dO68886MGjUqRx999Ae+1tVXX52mTZtm6NChOffcc3PIIYfk\n0ksvzR577JFRo0bld7/7XXbbbbd86lOfyq233prTTjtto0fzrrzyynzmM5/Jd77znQwePDhXXXVV\nhgwZkm9/+9ub9e/+zW9+M/vtt1/222+/PPPMM5k6der6P99yyy1J3r19909+8pPMmjUrRx55ZL70\npS9ln332yfe+9731r9OmTZv87Gc/y5o1azJ06NCccsop2WmnnXLzzTevP0LXpEmTjB07NjvuuGNO\nPvnkDB06NDU1NfnZz35W6/cvXXvttdlrr73yxS9+MUcccUReeeWV3HrrrR/4u54A+OdU1Gyub7UC\nAABsJXyiBAAAUCCUAAAACoQSAABAgVACAAAoEEoAAAAFQgkAAKBAKAEAABQIJQAAgIL/D1jyliZH\n7ueBAAAAAElFTkSuQmCC\n", 944 | "text/plain": [ 945 | "
" 946 | ] 947 | }, 948 | "metadata": { 949 | "tags": [] 950 | } 951 | } 952 | ] 953 | }, 954 | { 955 | "cell_type": "markdown", 956 | "metadata": { 957 | "id": "l4ej4TD5swd0", 958 | "colab_type": "text" 959 | }, 960 | "source": [ 961 | "Good, we found a possible cause for our problem. Our loss doesn't get low enough, in other words, our algorithm gets stuck at some point that is not a good enough minimum for us. How can we fix this? Perhaps, try out different learning rate or initializing our parameter with a different value?" 962 | ] 963 | }, 964 | { 965 | "cell_type": "code", 966 | "metadata": { 967 | "id": "ODP9jfo7s1i_", 968 | "colab_type": "code", 969 | "colab": {} 970 | }, 971 | "source": [ 972 | "def fit(X, y, n_iter=100000, lr=0.001):\n", 973 | "\n", 974 | " W = np.zeros(X.shape[1])\n", 975 | " \n", 976 | " errors = []\n", 977 | "\n", 978 | " for i in range(n_iter):\n", 979 | " z = np.dot(X, W)\n", 980 | " h = sigmoid(z)\n", 981 | " gradient = np.dot(X.T, (h - y)) / y.size\n", 982 | " W -= lr * gradient\n", 983 | " \n", 984 | " if(i % 10000 == 0):\n", 985 | " e = loss(h, y)\n", 986 | " print(f'loss: {e} \\t')\n", 987 | " errors.append(e)\n", 988 | " \n", 989 | " return W, errors" 990 | ], 991 | "execution_count": 0, 992 | "outputs": [] 993 | }, 994 | { 995 | "cell_type": "code", 996 | "metadata": { 997 | "id": "X0lW0sYLs3ua", 998 | "colab_type": "code", 999 | "outputId": "c87799e9-9ca9-4cfb-9874-817a0e82f1f1", 1000 | "colab": { 1001 | "base_uri": "https://localhost:8080/", 1002 | "height": 408 1003 | } 1004 | }, 1005 | "source": [ 1006 | "run_tests()" 1007 | ], 1008 | "execution_count": 0, 1009 | "outputs": [ 1010 | { 1011 | "output_type": "stream", 1012 | "text": [ 1013 | "loss: 0.6931471805599453 \t\n", 1014 | "loss: 0.41899283818630056 \t\n", 1015 | "loss: 0.41899283818630056 \t\n", 1016 | "loss: 0.41899283818630056 \t\n", 1017 | "loss: 0.41899283818630056 \t\n", 1018 | "loss: 0.41899283818630056 \t\n", 1019 | "loss: 0.41899283818630056 \t\n", 1020 | "loss: 0.41899283818630056 \t\n", 1021 | "loss: 0.41899283818630056 \t\n" 1022 | ], 1023 | "name": "stdout" 1024 | }, 1025 | { 1026 | "output_type": "stream", 1027 | "text": [ 1028 | "F......." 1029 | ], 1030 | "name": "stderr" 1031 | }, 1032 | { 1033 | "output_type": "stream", 1034 | "text": [ 1035 | "loss: 0.41899283818630056 \t\n" 1036 | ], 1037 | "name": "stdout" 1038 | }, 1039 | { 1040 | "output_type": "stream", 1041 | "text": [ 1042 | "\n", 1043 | "======================================================================\n", 1044 | "FAIL: test_correct_prediction (__main__.TestGradientDescent)\n", 1045 | "----------------------------------------------------------------------\n", 1046 | "Traceback (most recent call last):\n", 1047 | " File \"\", line 10, in test_correct_prediction\n", 1048 | " self.assertTrue((y_hat == y).all())\n", 1049 | "AssertionError: False is not true\n", 1050 | "\n", 1051 | "----------------------------------------------------------------------\n", 1052 | "Ran 8 tests in 0.903s\n", 1053 | "\n", 1054 | "FAILED (failures=1)\n" 1055 | ], 1056 | "name": "stderr" 1057 | } 1058 | ] 1059 | }, 1060 | { 1061 | "cell_type": "markdown", 1062 | "metadata": { 1063 | "id": "yAeOAz2VtGM4", 1064 | "colab_type": "text" 1065 | }, 1066 | "source": [ 1067 | "Hmm, how about adding one more parameter for our model to find/learn?" 1068 | ] 1069 | }, 1070 | { 1071 | "cell_type": "code", 1072 | "metadata": { 1073 | "id": "vS0aN8ZHfitp", 1074 | "colab_type": "code", 1075 | "colab": {} 1076 | }, 1077 | "source": [ 1078 | "def add_intercept(X):\n", 1079 | " intercept = np.ones((X.shape[0], 1))\n", 1080 | " return np.concatenate((intercept, X), axis=1)\n", 1081 | "\n", 1082 | "def predict(X, W):\n", 1083 | " X = add_intercept(X)\n", 1084 | " return sigmoid(np.dot(X, W))\n", 1085 | " \n", 1086 | "def fit(X, y, n_iter=100000, lr=0.01):\n", 1087 | " \n", 1088 | " X = add_intercept(X)\n", 1089 | " W = np.zeros(X.shape[1])\n", 1090 | " \n", 1091 | " errors = []\n", 1092 | "\n", 1093 | " for i in range(n_iter):\n", 1094 | " z = np.dot(X, W)\n", 1095 | " h = sigmoid(z)\n", 1096 | " gradient = np.dot(X.T, (h - y)) / y.size\n", 1097 | " W -= lr * gradient\n", 1098 | " \n", 1099 | " if(i % 10000 == 0): \n", 1100 | " e = loss(h, y)\n", 1101 | " errors.append(e)\n", 1102 | " return W, errors" 1103 | ], 1104 | "execution_count": 0, 1105 | "outputs": [] 1106 | }, 1107 | { 1108 | "cell_type": "code", 1109 | "metadata": { 1110 | "id": "aWb1nxIrtldD", 1111 | "colab_type": "code", 1112 | "outputId": "8e7ba18a-c019-49f0-c408-72c7d5738ae3", 1113 | "colab": { 1114 | "base_uri": "https://localhost:8080/", 1115 | "height": 102 1116 | } 1117 | }, 1118 | "source": [ 1119 | "run_tests()" 1120 | ], 1121 | "execution_count": 0, 1122 | "outputs": [ 1123 | { 1124 | "output_type": "stream", 1125 | "text": [ 1126 | "........\n", 1127 | "----------------------------------------------------------------------\n", 1128 | "Ran 8 tests in 0.837s\n", 1129 | "\n", 1130 | "OK\n" 1131 | ], 1132 | "name": "stderr" 1133 | } 1134 | ] 1135 | }, 1136 | { 1137 | "cell_type": "code", 1138 | "metadata": { 1139 | "id": "_VlYbo6x1Q04", 1140 | "colab_type": "code", 1141 | "outputId": "7c9fad66-4af6-4278-bf8f-6c939eeae44b", 1142 | "colab": { 1143 | "base_uri": "https://localhost:8080/", 1144 | "height": 511 1145 | } 1146 | }, 1147 | "source": [ 1148 | "_, errors = fit(X, y)\n", 1149 | "plt.plot(np.arange(len(errors)), errors)\n", 1150 | "plt.xlabel(\"iteration^10000\")\n", 1151 | "plt.ylabel(\"error\")\n", 1152 | "plt.ylim(0, 1)\n", 1153 | "plt.show();" 1154 | ], 1155 | "execution_count": 0, 1156 | "outputs": [ 1157 | { 1158 | "output_type": "display_data", 1159 | "data": { 1160 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0oAAAHuCAYAAAChyPw8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzs3Xt4VPWB//HPmUkmIcmEBCIiBCXk\nQtEQzQyKrbpVrI9UbK21QsTFWqst2qLt1lqkaCm9itbaRdutGtfd1RUtapVVW6vbX1Vuq7lwk0tC\nAkowAXNPyGUyM78/kgyZSQIDZOZMZt6v5+HJzHe+58wn4+mz+ew55zuG1+v1CgAAAADgYzE7AAAA\nAABEGooSAAAAAASgKAEAAABAAIoSAAAAAASgKAEAAABAAIoSAAAAAASgKAEAAABAgLAXpY8//liL\nFi3S9OnTdeDAgWPOXb9+vYqKijRr1ixddtlluv/++9XR0RGmpAAAAABiVViL0t/+9jctWLBAkyZN\nOu7cffv2afHixZo3b57effdd/ed//qe2b9+ulStXhiEpAAAAgFgW1qLU1NSkZ599Vtdcc81x5z7/\n/POaNm2aFi1apDFjxmjKlCm644479Oqrr6qhoSEMaQEAAADEqrAWpeuvv15ZWVlBzS0vL1dBQYHf\nWEFBgXp6erRjx45QxAMAAAAASVKc2QGG09DQoLFjx/qNpaenS5Lq6+uPu31JSUlIcgEAAACILk6n\nc9BYxBalYzEMI6h5Q/3CZikpKYmoPIgNHHcIN445mIHjDmbguIsew51gidjlwTMyMtTU1OQ31tjY\nKEk67bTTzIgEAAAAIEZEbFEqLCzUli1b/MZKSkpks9k0c+ZMk1IBAAAAiAURU5S2bt2quXPn6uDB\ng5KkoqIiffzxx3r66afV2dmpqqoqrV69Wtdff73sdrvJaQEAAABEs7Deo3TllVfq4MGD8nq9kqS5\nc+fKMAxdc801+tKXvqTq6mq5XC5JUmZmpp544gmtWrVKv/nNb5Samqqrr75aP/jBD8IZGQAAAEAM\nCmtR+utf/3rM13fv3u33/Pzzz9ef/vSnUEYCAAAAgEEi5tI7AAAAAIgUFCUAAAAACEBRAgAAAIAA\nFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAA\nAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBR\nAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAA\nCEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUA\nAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAA\nFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAA\nAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBR\nAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAA\nCEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUA\nAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACBD2otTR0aEVK1Zozpw5cjqdWrBggdav\nXz/s/FdeeUXXXHONCgsLdfHFF+sHP/iBamtrw5gYAAAAQKwJe1FauXKlysrKVFxcrA0bNujaa6/V\n4sWLVVVVNWjuxo0btXTpUn3729/W//3f/+nFF1/UoUOHdPfdd4c7NgAAAIAYEtai1NzcrHXr1mnJ\nkiXKyspSQkKCioqKlJ2drTVr1gyav23bNqWnp+uqq65SfHy8Tj/9dF111VXatm1bOGMDAAAAiDFh\nLUo7duyQy+XSzJkz/cYLCgq0ZcuWQfMvvfRStbe369VXX1V3d7fq6+v1+uuva+7cueGKDAAAACAG\nxYXzzRoaGiRJaWlpfuPp6emqr68fND8vL0+/+c1v9MMf/lD33HOPvF6vLrjgAt1///1BvV9JScmp\nhx5BkZYHsYHjDuHGMQczcNzBDBx30S2sRelYDMMYNPbBBx/ohz/8oX7+859rzpw5qq+v14oVK3TH\nHXfoP/7jP467T6fTGYqoJ6WkpCSi8iA2cNwh3DjmYAaOO5iB4y56DFd4w3rp3fjx4yVJTU1NfuON\n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\nERADampqdMMNN2j8+PF66KGHzI6DKPfwww9r6tSp+upXv2p2FMSI/q+BXLRokaZPn66kpCTddNNN\nys/P10svvWRyOkSr7u5u3XrrrcrKytJ7772nDz74QN/97ne1ePFiVVZWmh0PI4yiFCL9/1+FpqYm\nv/HGxkZlZGSYEQkxZOvWrbr++uvldDr1+OOPKykpyexIiGL9l9z97Gc/MzsKYsiECRMk9V6pMdCZ\nZ57J4jUImU2bNunDDz/03QuckpKiG2+8UZmZmXrxxRfNjocRxqV3IZKfny+bzaby8nJdeeWVvvHS\n0lJddtllJiZDtNuzZ49uu+023X777br55pvNjoMY8OKLL+rIkSP68pe/7Btra2vT1q1b9b//+796\n+eWXTUyHaDVhwgSlpaVp27Zt+sIXvuAb379/P4s5IGQ8Ho8kye12+4273W7fWU5ED84ohYjdbtd1\n112n1atXq7q6Wh0dHSouLlZNTY2KiorMjoco5Xa7tXTpUl1//fWUJITN0qVL9dZbb+mVV17x/cvP\nz1dRUZEef/xxs+MhSlmtVn3jG9/QM888ow0bNqi7u1vPPvusdu7cqRtuuMHseIhSDodDGRkZeuih\nh9TY2Kiuri698MILqq6u1ty5c82OhxFmeKm/IdPd3a1Vq1bptddeU3t7u2bMmKF77rlHTqfT7GiI\nUh988IFuvPFGxcfHD7oX7pprrtHPf/5zk5Ih1ixatEgXXHCBlixZYnYURDGv16vHHntMf/rTn1Rf\nX6+srCz96Ec/0sUXX2x2NESxXbt26eGHH9b27dvV2tqqadOm6c4779Tll19udjSMMIoSAAAAAATg\n0jsAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAAAAACEBRAgAAAIAAFCUAgJ85c+bo+9//\nvtkxfBYtWqT58+eHZN9er1df+tKXdPbZZ+utt94acs6rr76qwsJCLVq0aMjX33//fRUVFamgoECz\nZ8/W0qVL1dTU5Ddn9+7d+uY3v6nCwkI5HA595zvfUU1Njd+cgwcPasmSJZo1a5bOPfdc3Xzzzdq1\na5ffnObmZt1777268MILNXPmTM2fP1+bN28+hU8AADAcihIAwM/atWu1cuVKSdKPfvQjrV69Oqzv\nf9lll/n98b969Wo9/vjjIXmvv/3tb2pubtadd96p3//+936vdXZ26sc//rF+9rOfKTk5ecjtq6qq\n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\n", 1161 | "text/plain": [ 1162 | "
" 1163 | ] 1164 | }, 1165 | "metadata": { 1166 | "tags": [] 1167 | } 1168 | } 1169 | ] 1170 | }, 1171 | { 1172 | "cell_type": "markdown", 1173 | "metadata": { 1174 | "id": "3lMGWRXJ-YP5", 1175 | "colab_type": "text" 1176 | }, 1177 | "source": [ 1178 | "### Hiding the complexity of the algorithm\n", 1179 | "\n", 1180 | "Knowing all of the details of the inner workings of the Gradient descent is good, but when solving problems in the wild, we might be hard pressed for time. In those situations, a simple & easy to use interface for fitting a Logistic Regression model might save us a lot of time. So, let's build one!\n", 1181 | "\n", 1182 | "But first, let's write some tests:" 1183 | ] 1184 | }, 1185 | { 1186 | "cell_type": "code", 1187 | "metadata": { 1188 | "id": "ZNiUHh8_iNbx", 1189 | "colab_type": "code", 1190 | "colab": {} 1191 | }, 1192 | "source": [ 1193 | "class TestLogisticRegressor(unittest.TestCase):\n", 1194 | "\n", 1195 | " def test_correct_prediction(self):\n", 1196 | " global X\n", 1197 | " global y\n", 1198 | " X = X.reshape(X.shape[0], 1)\n", 1199 | " clf = LogisticRegressor()\n", 1200 | " y_hat = clf.fit(X, y).predict(X)\n", 1201 | " self.assertTrue((y_hat == y).all())" 1202 | ], 1203 | "execution_count": 0, 1204 | "outputs": [] 1205 | }, 1206 | { 1207 | "cell_type": "code", 1208 | "metadata": { 1209 | "id": "XeDkAWxDiQo4", 1210 | "colab_type": "code", 1211 | "outputId": "58adcf12-bb4f-4601-84a8-3fc722fa0d40", 1212 | "colab": { 1213 | "base_uri": "https://localhost:8080/", 1214 | "height": 238 1215 | } 1216 | }, 1217 | "source": [ 1218 | "run_tests()" 1219 | ], 1220 | "execution_count": 0, 1221 | "outputs": [ 1222 | { 1223 | "output_type": "stream", 1224 | "text": [ 1225 | ".E.......\n", 1226 | "======================================================================\n", 1227 | "ERROR: test_correct_prediction (__main__.TestLogisticRegressor)\n", 1228 | "----------------------------------------------------------------------\n", 1229 | "Traceback (most recent call last):\n", 1230 | " File \"\", line 7, in test_correct_prediction\n", 1231 | " clf = LogisticRegressor()\n", 1232 | "NameError: name 'LogisticRegressor' is not defined\n", 1233 | "\n", 1234 | "----------------------------------------------------------------------\n", 1235 | "Ran 9 tests in 0.858s\n", 1236 | "\n", 1237 | "FAILED (errors=1)\n" 1238 | ], 1239 | "name": "stderr" 1240 | } 1241 | ] 1242 | }, 1243 | { 1244 | "cell_type": "code", 1245 | "metadata": { 1246 | "id": "oidfenvBGiy8", 1247 | "colab_type": "code", 1248 | "colab": {} 1249 | }, 1250 | "source": [ 1251 | "class LogisticRegressor:\n", 1252 | " \n", 1253 | " def _add_intercept(self, X):\n", 1254 | " intercept = np.ones((X.shape[0], 1))\n", 1255 | " return np.concatenate((intercept, X), axis=1)\n", 1256 | "\n", 1257 | " def predict_probs(self, X):\n", 1258 | " X = self._add_intercept(X)\n", 1259 | " return sigmoid(np.dot(X, self.W))\n", 1260 | " \n", 1261 | " def predict(self, X):\n", 1262 | " return self.predict_probs(X).round()\n", 1263 | " \n", 1264 | " def fit(self, X, y, n_iter=100000, lr=0.01):\n", 1265 | "\n", 1266 | " X = self._add_intercept(X)\n", 1267 | " self.W = np.zeros(X.shape[1])\n", 1268 | "\n", 1269 | " for i in range(n_iter):\n", 1270 | " z = np.dot(X, self.W)\n", 1271 | " h = sigmoid(z)\n", 1272 | " gradient = np.dot(X.T, (h - y)) / y.size\n", 1273 | " self.W -= lr * gradient\n", 1274 | " return self" 1275 | ], 1276 | "execution_count": 0, 1277 | "outputs": [] 1278 | }, 1279 | { 1280 | "cell_type": "code", 1281 | "metadata": { 1282 | "id": "R3hsL4LCBcTm", 1283 | "colab_type": "code", 1284 | "outputId": "dfeea495-21da-4503-c3f9-248d885027a6", 1285 | "colab": { 1286 | "base_uri": "https://localhost:8080/", 1287 | "height": 102 1288 | } 1289 | }, 1290 | "source": [ 1291 | "run_tests()" 1292 | ], 1293 | "execution_count": 0, 1294 | "outputs": [ 1295 | { 1296 | "output_type": "stream", 1297 | "text": [ 1298 | ".........\n", 1299 | "----------------------------------------------------------------------\n", 1300 | "Ran 9 tests in 1.695s\n", 1301 | "\n", 1302 | "OK\n" 1303 | ], 1304 | "name": "stderr" 1305 | } 1306 | ] 1307 | }, 1308 | { 1309 | "cell_type": "markdown", 1310 | "metadata": { 1311 | "id": "SbaBivt46Oxf", 1312 | "colab_type": "text" 1313 | }, 1314 | "source": [ 1315 | "## Using our Regressor to decide who should receive discount codes\n", 1316 | "\n", 1317 | "Now that you're done with the \"hard\" part let's use the model to predict whether or not we should send discount codes." 1318 | ] 1319 | }, 1320 | { 1321 | "cell_type": "code", 1322 | "metadata": { 1323 | "id": "FjUXzun46Sve", 1324 | "colab_type": "code", 1325 | "colab": {} 1326 | }, 1327 | "source": [ 1328 | "X_test = np.array([10, 250])\n", 1329 | "X_test = X_test.reshape(X_test.shape[0], 1)\n", 1330 | "y_test = LogisticRegressor().fit(X, y).predict(X_test)" 1331 | ], 1332 | "execution_count": 0, 1333 | "outputs": [] 1334 | }, 1335 | { 1336 | "cell_type": "code", 1337 | "metadata": { 1338 | "id": "dUz__0al7uQV", 1339 | "colab_type": "code", 1340 | "outputId": "3f929d21-8296-402f-f5ac-77616b3c543a", 1341 | "colab": { 1342 | "base_uri": "https://localhost:8080/", 1343 | "height": 34 1344 | } 1345 | }, 1346 | "source": [ 1347 | "y_test" 1348 | ], 1349 | "execution_count": 0, 1350 | "outputs": [ 1351 | { 1352 | "output_type": "execute_result", 1353 | "data": { 1354 | "text/plain": [ 1355 | "array([1., 0.])" 1356 | ] 1357 | }, 1358 | "metadata": { 1359 | "tags": [] 1360 | }, 1361 | "execution_count": 32 1362 | } 1363 | ] 1364 | } 1365 | ] 1366 | } -------------------------------------------------------------------------------- /3_decision_trees.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "3.decision_trees.ipynb", 7 | "version": "0.3.2", 8 | "provenance": [], 9 | "collapsed_sections": [] 10 | }, 11 | "kernelspec": { 12 | "name": "python3", 13 | "display_name": "Python 3" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "code", 19 | "metadata": { 20 | "id": "RWg6PIEtvlSs", 21 | "colab_type": "code", 22 | "outputId": "f9ee07db-ad91-4098-a763-820b6ef955d9", 23 | "colab": { 24 | "base_uri": "https://localhost:8080/", 25 | "height": 187 26 | } 27 | }, 28 | "source": [ 29 | "!apt-get install graphviz\n", 30 | "!pip install graphviz\n", 31 | "\n", 32 | "!mkdir dot" 33 | ], 34 | "execution_count": 0, 35 | "outputs": [ 36 | { 37 | "output_type": "stream", 38 | "text": [ 39 | "Reading package lists... Done\n", 40 | "Building dependency tree \n", 41 | "Reading state information... Done\n", 42 | "graphviz is already the newest version (2.40.1-2).\n", 43 | "The following package was automatically installed and is no longer required:\n", 44 | " libnvidia-common-410\n", 45 | "Use 'apt autoremove' to remove it.\n", 46 | "0 upgraded, 0 newly installed, 0 to remove and 6 not upgraded.\n", 47 | "Requirement already satisfied: graphviz in /usr/local/lib/python3.6/dist-packages (0.10.1)\n", 48 | "mkdir: cannot create directory ‘dot’: File exists\n" 49 | ], 50 | "name": "stdout" 51 | } 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "metadata": { 57 | "id": "qUl5QuVeXYcx", 58 | "colab_type": "code", 59 | "colab": {} 60 | }, 61 | "source": [ 62 | "import warnings\n", 63 | "warnings.simplefilter(action='ignore', category=FutureWarning)\n", 64 | "\n", 65 | "\n", 66 | "import numpy as np\n", 67 | "import pandas as pd\n", 68 | "import seaborn as sns\n", 69 | "from pylab import rcParams\n", 70 | "import matplotlib.pyplot as plt\n", 71 | "import matplotlib.animation as animation\n", 72 | "from matplotlib import rc\n", 73 | "import unittest\n", 74 | "import math\n", 75 | "from sklearn import metrics\n", 76 | "from sklearn.tree import export_graphviz\n", 77 | "import IPython, graphviz, re\n", 78 | "\n", 79 | "%matplotlib inline\n", 80 | "\n", 81 | "sns.set(style='whitegrid', palette='muted', font_scale=1.5)\n", 82 | "\n", 83 | "rcParams['figure.figsize'] = 12, 6\n", 84 | "\n", 85 | "RANDOM_SEED = 42\n", 86 | "\n", 87 | "np.random.seed(RANDOM_SEED)\n", 88 | "\n", 89 | "\n", 90 | "def draw_tree(t, df, size=10, ratio=0.6, precision=0):\n", 91 | " \"\"\" Draws a representation of a random forest in IPython.\n", 92 | " Parameters:\n", 93 | " -----------\n", 94 | " t: The tree you wish to draw\n", 95 | " df: The data used to train the tree. This is used to get the names of the features.\n", 96 | " Source from: https://github.com/fastai/fastai/blob/e6b56de53f80d2b2d39037c82d3a23ce72507cd7/old/fastai/structured.py#L22\n", 97 | " \"\"\"\n", 98 | " s=export_graphviz(t, out_file=None, feature_names=df.columns, filled=True,\n", 99 | " special_characters=True, rotate=True, precision=precision)\n", 100 | " IPython.display.display(graphviz.Source(re.sub('Tree {',\n", 101 | " f'Tree {{ size={size}; ratio={ratio}', s)))" 102 | ], 103 | "execution_count": 0, 104 | "outputs": [] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": { 109 | "id": "odnKwqUctlXX", 110 | "colab_type": "text" 111 | }, 112 | "source": [ 113 | "# Load the data\n", 114 | "\n", 115 | "Data [House Prices: Advanced Regression Techniques](https://www.kaggle.com/c/house-prices-advanced-regression-techniques)" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "metadata": { 121 | "id": "ulPEgkKLZPLx", 122 | "colab_type": "code", 123 | "outputId": "870a8ebc-e645-4f65-84b4-d6510136897c", 124 | "colab": { 125 | "base_uri": "https://localhost:8080/", 126 | "height": 391 127 | } 128 | }, 129 | "source": [ 130 | "!wget https://raw.githubusercontent.com/Data-Science-FMI/ml-from-scratch-2019/master/data/house_prices_train.csv\n", 131 | "!wget https://raw.githubusercontent.com/Data-Science-FMI/ml-from-scratch-2019/master/data/house_prices_test.csv" 132 | ], 133 | "execution_count": 0, 134 | "outputs": [ 135 | { 136 | "output_type": "stream", 137 | "text": [ 138 | "--2019-04-03 09:32:27-- https://raw.githubusercontent.com/Data-Science-FMI/ml-from-scratch-2019/master/data/house_prices_train.csv\n", 139 | "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...\n", 140 | "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.\n", 141 | "HTTP request sent, awaiting response... 200 OK\n", 142 | "Length: 460676 (450K) [text/plain]\n", 143 | "Saving to: ‘house_prices_train.csv.2’\n", 144 | "\n", 145 | "\rhouse_prices_train. 0%[ ] 0 --.-KB/s \rhouse_prices_train. 100%[===================>] 449.88K --.-KB/s in 0.04s \n", 146 | "\n", 147 | "2019-04-03 09:32:28 (12.3 MB/s) - ‘house_prices_train.csv.2’ saved [460676/460676]\n", 148 | "\n", 149 | "--2019-04-03 09:32:31-- https://raw.githubusercontent.com/Data-Science-FMI/ml-from-scratch-2019/master/data/house_prices_test.csv\n", 150 | "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...\n", 151 | "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.\n", 152 | "HTTP request sent, awaiting response... 200 OK\n", 153 | "Length: 451405 (441K) [text/plain]\n", 154 | "Saving to: ‘house_prices_test.csv.2’\n", 155 | "\n", 156 | "house_prices_test.c 100%[===================>] 440.83K --.-KB/s in 0.04s \n", 157 | "\n", 158 | "2019-04-03 09:32:31 (12.1 MB/s) - ‘house_prices_test.csv.2’ saved [451405/451405]\n", 159 | "\n" 160 | ], 161 | "name": "stdout" 162 | } 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "metadata": { 168 | "id": "uJV2KSJKZQ2Q", 169 | "colab_type": "code", 170 | "colab": {} 171 | }, 172 | "source": [ 173 | "df_train = pd.read_csv('house_prices_train.csv')\n", 174 | "df_test = pd.read_csv('house_prices_test.csv')" 175 | ], 176 | "execution_count": 0, 177 | "outputs": [] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": { 182 | "id": "fwUmkYUStoz0", 183 | "colab_type": "text" 184 | }, 185 | "source": [ 186 | "# Decision trees" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": { 192 | "id": "wAoyCZdvstuf", 193 | "colab_type": "text" 194 | }, 195 | "source": [ 196 | "![](https://www.xoriant.com/blog/wp-content/uploads/2017/08/Decision-Trees-modified-1.png)\n", 197 | "\n", 198 | "Decision tree models can be used for both classification and regression. The algorithms for building trees breaks down a data set into smaller and smaller subsets while at the same time an associated decision tree is incrementally developed. The final result is a tree with decision nodes and leaf nodes. A decision node has two or more branches. Leaf node represents a classification or decision (used for regression). The topmost decision node in a tree which corresponds to the best predictor (most important feature) is called a root node. Decision trees can handle both categorical and numerical data." 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "metadata": { 204 | "id": "0aGOfq3WZSh4", 205 | "colab_type": "code", 206 | "colab": {} 207 | }, 208 | "source": [ 209 | "X = df_train[['OverallQual', 'GrLivArea', 'GarageCars']]\n", 210 | "y = df_train['SalePrice']" 211 | ], 212 | "execution_count": 0, 213 | "outputs": [] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": { 218 | "id": "RUWchJE7xPF4", 219 | "colab_type": "text" 220 | }, 221 | "source": [ 222 | "# Cost function\n", 223 | "\n", 224 | "Root Mean Squared Error:\n", 225 | "\n", 226 | "$$RMSE = \\sqrt{\\frac{1}{m} \\sum_{i=1}^{m} (y^{(i)} - h(x^{(i)}))^2}$$" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "metadata": { 232 | "id": "pr2-tgH-xUg1", 233 | "colab_type": "code", 234 | "colab": {} 235 | }, 236 | "source": [ 237 | "from sklearn.metrics import mean_squared_error\n", 238 | "from math import sqrt\n", 239 | "\n", 240 | "def rmse(h, y):\n", 241 | " return sqrt(mean_squared_error(h, y))" 242 | ], 243 | "execution_count": 0, 244 | "outputs": [] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": { 249 | "id": "VtZqDfiZuZaR", 250 | "colab_type": "text" 251 | }, 252 | "source": [ 253 | "# Using scikit-learn" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "metadata": { 259 | "id": "ZeK6jxjuuevJ", 260 | "colab_type": "code", 261 | "outputId": "a4564969-5e50-409c-dc40-a8cddf7343f8", 262 | "colab": { 263 | "base_uri": "https://localhost:8080/", 264 | "height": 452 265 | } 266 | }, 267 | "source": [ 268 | "from sklearn.ensemble import RandomForestRegressor\n", 269 | "\n", 270 | "reg = RandomForestRegressor(n_estimators=1, max_depth=2, bootstrap=False, random_state=RANDOM_SEED)\n", 271 | "reg.fit(X, y)\n", 272 | "\n", 273 | "draw_tree(reg.estimators_[0], X, precision=2)" 274 | ], 275 | "execution_count": 0, 276 | "outputs": [ 277 | { 278 | "output_type": "display_data", 279 | "data": { 280 | "text/plain": [ 281 | "" 282 | ], 283 | "image/svg+xml": "\n\n\n\n\n\nTree\n\n\n\n0\n\nOverallQual ≤ 7.5\nmse = 6306788585.35\nsamples = 1460\nvalue = 180921.2\n\n\n\n1\n\nOverallQual ≤ 6.5\nmse = 2426928665.05\nsamples = 1231\nvalue = 157832.43\n\n\n\n0->1\n\n\nTrue\n\n\n\n4\n\nOverallQual ≤ 8.5\nmse = 8893038849.98\nsamples = 229\nvalue = 305035.9\n\n\n\n0->4\n\n\nFalse\n\n\n\n2\n\nmse = 1411538921.04\nsamples = 912\nvalue = 140383.98\n\n\n\n1->2\n\n\n\n\n\n3\n\nmse = 1971049955.99\nsamples = 319\nvalue = 207716.42\n\n\n\n1->3\n\n\n\n\n\n5\n\nmse = 4058765722.77\nsamples = 168\nvalue = 274735.54\n\n\n\n4->5\n\n\n\n\n\n6\n\nmse = 12714598711.22\nsamples = 61\nvalue = 388486.08\n\n\n\n4->6\n\n\n\n\n\n" 284 | }, 285 | "metadata": { 286 | "tags": [] 287 | } 288 | } 289 | ] 290 | }, 291 | { 292 | "cell_type": "code", 293 | "metadata": { 294 | "id": "6APmkksowMX6", 295 | "colab_type": "code", 296 | "outputId": "051dd06a-fdc5-4764-b7a3-52bf69a88d93", 297 | "colab": { 298 | "base_uri": "https://localhost:8080/", 299 | "height": 34 300 | } 301 | }, 302 | "source": [ 303 | "preds = reg.predict(X)\n", 304 | "metrics.r2_score(y, preds)" 305 | ], 306 | "execution_count": 0, 307 | "outputs": [ 308 | { 309 | "output_type": "execute_result", 310 | "data": { 311 | "text/plain": [ 312 | "0.6336246655552089" 313 | ] 314 | }, 315 | "metadata": { 316 | "tags": [] 317 | }, 318 | "execution_count": 8 319 | } 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "metadata": { 325 | "id": "SVnU3NtMyg-0", 326 | "colab_type": "code", 327 | "outputId": "a01d0c00-be71-49ae-fb98-c0afb141824a", 328 | "colab": { 329 | "base_uri": "https://localhost:8080/", 330 | "height": 34 331 | } 332 | }, 333 | "source": [ 334 | "rmse(preds, y)" 335 | ], 336 | "execution_count": 0, 337 | "outputs": [ 338 | { 339 | "output_type": "execute_result", 340 | "data": { 341 | "text/plain": [ 342 | "48069.23940764968" 343 | ] 344 | }, 345 | "metadata": { 346 | "tags": [] 347 | }, 348 | "execution_count": 9 349 | } 350 | ] 351 | }, 352 | { 353 | "cell_type": "markdown", 354 | "metadata": { 355 | "id": "jThz0g-3uQHu", 356 | "colab_type": "text" 357 | }, 358 | "source": [ 359 | "# Building our own Decision Tree\n", 360 | "\n", 361 | "We're going to split our tree based on information gain. Concretely, we will try to split every feature in a way that both groups have as low standard deviation as possible. In that way we're going to minimize the RMSE." 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "metadata": { 367 | "id": "pl7C-mu04spu", 368 | "colab_type": "code", 369 | "colab": {} 370 | }, 371 | "source": [ 372 | "class Node:\n", 373 | "\n", 374 | " def __init__(self, x, y, idxs, min_leaf=5):\n", 375 | " self.x = x \n", 376 | " self.y = y\n", 377 | " self.idxs = idxs \n", 378 | " self.min_leaf = min_leaf\n", 379 | " self.row_count = len(idxs)\n", 380 | " self.col_count = x.shape[1]\n", 381 | " self.val = np.mean(y[idxs])\n", 382 | " self.score = float('inf')\n", 383 | " self.find_varsplit()\n", 384 | " \n", 385 | " def find_varsplit(self):\n", 386 | " for c in range(self.col_count): self.find_better_split(c)\n", 387 | " if self.is_leaf: return\n", 388 | " x = self.split_col\n", 389 | " lhs = np.nonzero(x <= self.split)[0]\n", 390 | " rhs = np.nonzero(x > self.split)[0]\n", 391 | " self.lhs = Node(self.x, self.y, self.idxs[lhs], self.min_leaf)\n", 392 | " self.rhs = Node(self.x, self.y, self.idxs[rhs], self.min_leaf)\n", 393 | " \n", 394 | " def find_better_split(self, var_idx):\n", 395 | " \n", 396 | " x = self.x.values[self.idxs, var_idx]\n", 397 | "\n", 398 | " for r in range(self.row_count):\n", 399 | " lhs = x <= x[r]\n", 400 | " rhs = x > x[r]\n", 401 | " if rhs.sum() < self.min_leaf or lhs.sum() < self.min_leaf: continue\n", 402 | "\n", 403 | " curr_score = self.find_score(lhs, rhs)\n", 404 | " if curr_score < self.score: \n", 405 | " self.var_idx = var_idx\n", 406 | " self.score = curr_score\n", 407 | " self.split = x[r]\n", 408 | " \n", 409 | " def find_score(self, lhs, rhs):\n", 410 | " y = self.y[self.idxs]\n", 411 | " lhs_std = y[lhs].std()\n", 412 | " rhs_std = y[rhs].std()\n", 413 | " return lhs_std * lhs.sum() + rhs_std * rhs.sum()\n", 414 | " \n", 415 | " @property\n", 416 | " def split_col(self): return self.x.values[self.idxs,self.var_idx]\n", 417 | " \n", 418 | " @property\n", 419 | " def is_leaf(self): return self.score == float('inf') \n", 420 | "\n", 421 | " def predict(self, x):\n", 422 | " return np.array([self.predict_row(xi) for xi in x])\n", 423 | "\n", 424 | " def predict_row(self, xi):\n", 425 | " if self.is_leaf: return self.val\n", 426 | " node = self.lhs if xi[self.var_idx] <= self.split else self.rhs\n", 427 | " return node.predict_row(xi)" 428 | ], 429 | "execution_count": 0, 430 | "outputs": [] 431 | }, 432 | { 433 | "cell_type": "code", 434 | "metadata": { 435 | "id": "az-uVislq4rr", 436 | "colab_type": "code", 437 | "colab": {} 438 | }, 439 | "source": [ 440 | "class DecisionTreeRegressor:\n", 441 | " \n", 442 | " def fit(self, X, y, min_leaf = 5):\n", 443 | " self.dtree = Node(X, y, np.array(np.arange(len(y))), min_leaf)\n", 444 | " return self\n", 445 | " \n", 446 | " def predict(self, X):\n", 447 | " return self.dtree.predict(X.values)\n", 448 | " " 449 | ], 450 | "execution_count": 0, 451 | "outputs": [] 452 | }, 453 | { 454 | "cell_type": "markdown", 455 | "metadata": { 456 | "id": "bo-WXSmDuTy3", 457 | "colab_type": "text" 458 | }, 459 | "source": [ 460 | "# Evaluation" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "metadata": { 466 | "id": "EOZ12otNsNE_", 467 | "colab_type": "code", 468 | "colab": {} 469 | }, 470 | "source": [ 471 | "regressor = DecisionTreeRegressor().fit(X, y)\n", 472 | "preds = regressor.predict(X)" 473 | ], 474 | "execution_count": 0, 475 | "outputs": [] 476 | }, 477 | { 478 | "cell_type": "code", 479 | "metadata": { 480 | "id": "lEJASz_k5yxl", 481 | "colab_type": "code", 482 | "outputId": "95675657-408e-435c-881f-10b21cc0ab87", 483 | "colab": { 484 | "base_uri": "https://localhost:8080/", 485 | "height": 34 486 | } 487 | }, 488 | "source": [ 489 | "metrics.r2_score(y, preds)" 490 | ], 491 | "execution_count": 0, 492 | "outputs": [ 493 | { 494 | "output_type": "execute_result", 495 | "data": { 496 | "text/plain": [ 497 | "0.8504381072711565" 498 | ] 499 | }, 500 | "metadata": { 501 | "tags": [] 502 | }, 503 | "execution_count": 13 504 | } 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "metadata": { 510 | "id": "OJmGr7Zr578G", 511 | "colab_type": "code", 512 | "outputId": "084dd022-8afc-4695-f112-89b72b54948a", 513 | "colab": { 514 | "base_uri": "https://localhost:8080/", 515 | "height": 34 516 | } 517 | }, 518 | "source": [ 519 | "rmse(preds, y)" 520 | ], 521 | "execution_count": 0, 522 | "outputs": [ 523 | { 524 | "output_type": "execute_result", 525 | "data": { 526 | "text/plain": [ 527 | "30712.460628635836" 528 | ] 529 | }, 530 | "metadata": { 531 | "tags": [] 532 | }, 533 | "execution_count": 14 534 | } 535 | ] 536 | }, 537 | { 538 | "cell_type": "markdown", 539 | "metadata": { 540 | "id": "ni3_lftj2TQY", 541 | "colab_type": "text" 542 | }, 543 | "source": [ 544 | "# Sending to Kaggle" 545 | ] 546 | }, 547 | { 548 | "cell_type": "code", 549 | "metadata": { 550 | "id": "mkiOm43q2Wij", 551 | "colab_type": "code", 552 | "colab": {} 553 | }, 554 | "source": [ 555 | "X_test = df_test[['OverallQual', 'GrLivArea', 'GarageCars']]\n", 556 | "pred_test = regressor.predict(X_test)\n", 557 | "\n", 558 | "submission = pd.DataFrame({'Id': df_test.Id, 'SalePrice': pred_test})\n", 559 | "submission.to_csv('submission.csv', index=False)" 560 | ], 561 | "execution_count": 0, 562 | "outputs": [] 563 | }, 564 | { 565 | "cell_type": "code", 566 | "metadata": { 567 | "id": "pAbbDJoW2vtG", 568 | "colab_type": "code", 569 | "colab": {} 570 | }, 571 | "source": [ 572 | "from google.colab import files\n", 573 | "files.download('submission.csv')" 574 | ], 575 | "execution_count": 0, 576 | "outputs": [] 577 | }, 578 | { 579 | "cell_type": "markdown", 580 | "metadata": { 581 | "id": "YpP0mfw40sUZ", 582 | "colab_type": "text" 583 | }, 584 | "source": [ 585 | "# Resources\n", 586 | "\n", 587 | "[Let’s Write a Decision Tree Classifier from Scratch](https://www.youtube.com/watch?v=LDRbO9a6XPU)" 588 | ] 589 | } 590 | ] 591 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Venelin Valkov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Hands-On Machine Learning from Scratch 2 | 3 | Interested in deeper understanding of Machine Learning algorithms? Implement them in Python from scratch: 4 | 5 | 6 | 7 | 8 | 9 | Read the book here 10 | --------------------------------------------------------------------------------