├── .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 | " amount_spent | \n",
117 | " send_discount | \n",
118 | "
\n",
119 | " \n",
120 | " \n",
121 | " \n",
122 | " 0 | \n",
123 | " 50 | \n",
124 | " 0 | \n",
125 | "
\n",
126 | " \n",
127 | " 1 | \n",
128 | " 10 | \n",
129 | " 1 | \n",
130 | "
\n",
131 | " \n",
132 | " 2 | \n",
133 | " 20 | \n",
134 | " 1 | \n",
135 | "
\n",
136 | " \n",
137 | " 3 | \n",
138 | " 5 | \n",
139 | " 1 | \n",
140 | "
\n",
141 | " \n",
142 | " 4 | \n",
143 | " 95 | \n",
144 | " 0 | \n",
145 | "
\n",
146 | " \n",
147 | " 5 | \n",
148 | " 70 | \n",
149 | " 0 | \n",
150 | "
\n",
151 | " \n",
152 | " 6 | \n",
153 | " 100 | \n",
154 | " 0 | \n",
155 | "
\n",
156 | " \n",
157 | " 7 | \n",
158 | " 200 | \n",
159 | " 0 | \n",
160 | "
\n",
161 | " \n",
162 | " 8 | \n",
163 | " 0 | \n",
164 | " 1 | \n",
165 | "
\n",
166 | " \n",
167 | "
\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 | "\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\n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\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 | "\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 | "\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 | "\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 | "\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"
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 |
--------------------------------------------------------------------------------