{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Regressão Logística" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introdução\n", "\n", "Na aprendizagem de máquina supervisionada, quando a resposta que precisa ser prevista é qualitativa (discreta), é usado o processo chamado de Classificação. São problemas de classificação, por exemplo, decidir se um e-mail recebido é spam ou não, ou decidir se uma foto contém a imagem de um gato, cachorro ou um pássaro.\n", "\n", "Para funcionar, um classificador precisa ser treinado usando dados de treinamento. Cada exemplo nos dados de treinamento possui um vetor de características $(x_1, x_2, ..., x_n)$ e uma resposta associada $y$ que é a classificação do exemplo.\n", "\n", "Apesar do nome, Regressão Logística é um algoritmo de classificação que pode ser usado para resolver problemas com duas ou mais possíveis classes. Entretanto, para facilitar o entendimento, consideraremos que $y$ pode ter apenas dois valores, 0 ou 1, sendo 0 uma classe e 1 a outra." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Função logística\n", "\n", "Com as respostas variando apenas entre dois valores, 0 e 1, usar regressão linear não é adequado, pois ela pode prever qualquer valor contínuo que pode ir muito além dos limites de 0 e 1. Assim, mudaremos a função $f(x)$, que representa a hipótese para explicar os dados, para uma função que varie apenas entre 0 e 1.\n", "\n", "Na regressão logística usaremos a função logística da seguinte forma:\n", "\n", "$$f(x) = g(w^Tx)$$\n", "\n", "$$g(z) = \\frac{1}{1+e^{-z}}$$\n", "\n", "Perceba que continua-se usando $w^Tx$ como na regressão linear, mas agora aplica-se o resultado na função logística $g(z)$.\n", "\n", "A seguir temos a implementação das duas funções acima, $f(x)$ e $g(z)$. $f(x)$ recebe o vetor de coeficientes $w$ e as características $x$, já $g(z)$ recebe apenas um valor que é aplicado a função logística." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math\n", "\n", "def f(x, w):\n", " z = 0\n", "\n", " for i in range(len(x)):\n", " z += x[i]*w[i]\n", " \n", " return g(z)\n", "\n", "def g(z):\n", " return 1 / (1 + math.exp(-z))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A função logística tem uma forma de \"S\" como pode ser vista no gráfico seguir:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = true;\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force === true) {\n", " window._bokeh_onload_callbacks = [];\n", " window._bokeh_is_loading = undefined;\n", " }\n", "\n", "\n", " \n", " if (typeof (window._bokeh_timeout) === \"undefined\" || force === true) {\n", " window._bokeh_timeout = Date.now() + 5000;\n", " window._bokeh_failed_load = false;\n", " }\n", "\n", " var NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " if (window.Bokeh !== undefined) {\n", " var el = document.getElementById(\"7f3683b6-c68d-4e86-8a59-92ddc2d96933\");\n", " el.textContent = \"BokehJS \" + Bokeh.version + \" successfully loaded.\";\n", " } else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " try {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " }\n", " finally {\n", " delete window._bokeh_onload_callbacks\n", " }\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };var element = document.getElementById(\"7f3683b6-c68d-4e86-8a59-92ddc2d96933\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid '7f3683b6-c68d-4e86-8a59-92ddc2d96933' but no matching script tag was found. \")\n", " return false;\n", " }\n", "\n", " var js_urls = [\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.js\", \"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.js\"];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " \n", " },\n", " \n", " function(Bokeh) {\n", " \n", " document.getElementById(\"7f3683b6-c68d-4e86-8a59-92ddc2d96933\").textContent = \"BokehJS is loading...\";\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.6.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.6.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " \n", " if ((window.Bokeh !== undefined) || (force === true)) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }if (force === true) {\n", " display_loaded();\n", " }} else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!window._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " window._bokeh_failed_load = true;\n", " } else if (force !== true) {\n", " var cell = $(document.getElementById(\"7f3683b6-c68d-4e86-8a59-92ddc2d96933\")).parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", "\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[2]>

" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "from bokeh.io import show, output_notebook\n", "from bokeh.plotting import figure\n", "output_notebook()\n", "\n", "x_points = np.linspace(-10, 10, 100)\n", "y_points = [g(i) for i in x_points]\n", "\n", "p = figure(plot_width=700, plot_height=250)\n", "p.xaxis.axis_label = \"z\"\n", "p.yaxis.axis_label = \"g(z)\"\n", "p.line(x_points, y_points, line_width=2)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note que quanto menor o valor de $z$, ou seja, o valor de $w^Tx$, mais próximo de zero é o resultado da função. Por outro lado, quanto maior o valor de $z$, mais próximo de 1 é o resultado. Dessa forma, o resultado nunca ultrapassará o intervalo entre 0 e 1. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Na regressão logística, interpretamos o resultado de $f(x)$ como a probabilidade da resposta ser 1 para o vetor de características $x$. Assim, se $f(x) = 0,8$, então $x$ tem probabilidade $0,8$ de ser da classe 1, e $0,2$ de ser da classe 0." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fronteira de decisão\n", "\n", "Para obter uma classificação discreta, é necessário definir um limiar em relação à probabilidade. Por exemplo, se $f(x) \\geq 0,5 \\Rightarrow y = 1$. Ou seja, se a probabilidade de ser 1 for maior ou igual 50%, então classificamos como 1. Adicionalmente, se $f(x) < 0,5 \\Rightarrow y = 0$.\n", "\n", "Na função logística $g(z)$, se $z \\geq 0$, então $g(z) \\geq 0,5$. Como a hipótese na regressão logística é $f(x) = g(w^Tx)$, então, se $w^Tx \\geq 0$, isto resultará em $g(z) \\geq 0,5$.\n", "\n", "Com isso, $w^Tx$ especifica uma fronteira de decisão que separa os pontos que são classificados como 0 ou 1. Definimos matematicamente a fronteira de decisão da seguinte forma:\n", "\n", "$$w^Tx = 0 $$\n", "\n", "Assim,\n", "\n", "$$w^Tx \\geq 0 \\Rightarrow y = 1$$\n", "\n", "$$w^Tx < 0 \\Rightarrow y = 0$$\n", "\n", "Dependendo do tipo de função definida por $w^Tx$ a fronteira de decisão pode ter diferentes formas. \n", "\n", "\n", "Por exemplo, usando a função $f(x) = w_0 + w_1x_1 + w_2x_2$, definimos a fronteira como:\n", "\n", "$$w_0 + w_1x_1 + w_2x_2 = 0$$\n", "\n", "Essa equação define uma reta.\n", "\n", "\n", "Abaixo temos o gráfico mostrando a fronteira usando a equação da reta anterior para $w^T = \\begin{bmatrix} -3 & 0,8 & 1 \\end{bmatrix}$.\n", "Ou seja, a equação é: $0,8x_1 + x_2 - 3 = 0$.\n", "\n", "Note que no gráfico, os pontos são separados em dois grupos pela reta, sendo os de cor azul correspondentes a $y=0$ e os de cor vermelha a $y=1$." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[3]>

" ], "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def boundary(x):\n", " return 3-0.8*x\n", "\n", "x = [0.5, 1, 1.2, 1.1, 1.5, 2.6, 2.3, 3, 2.8, 2.4]\n", "y = [0.7, 1.2, 0.5, 1.8, 1, 2.5, 2.8, 2.5, 3, 2.2]\n", "colors = [\"blue\", \"blue\", \"blue\", \"blue\", \"blue\", \"red\", \"red\", \"red\", \"red\", \"red\"]\n", "\n", "p = figure(plot_width=700, plot_height=250)\n", "p.circle(x, y, radius=0.025, color=colors)\n", "p.line([0, 3.5], [boundary(0), boundary(3.5)], line_width=2)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Usando funções diferentes, podemos ter curvas mais complexas. \n", "No exemplo a seguir usamos a função $f(x) = -1,2 + 3x_1^2 + 3x_2^2$, criando a fronteira $3x_1^2 + 3x_2^2 - 1,2 = 0$. Esta equação define uma elipse." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[4]>

" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import math\n", "import itertools\n", "\n", "def boundary(x1):\n", " return math.sqrt((1.2-3*x1**2)/0.3)\n", "\n", "x = [0.5, -0.1, 0.2, -0.3, -0.3, 0, 0.7, 1, 0.8, 0, -0.5, -0.8, -0.5]\n", "y = [0.2, -0.8, 0.5, -0.1, 0.7, 3.5, 2.0, 0.5, -1.5, -3.5, -2, 0, 2]\n", "colors = [\"blue\", \"blue\", \"blue\", \"blue\", \"blue\", \"red\", \"red\", \"red\", \"red\", \"red\", \"red\", \"red\", \"red\"]\n", "\n", "curve_x_points = np.linspace(-0.8, 0.8, 100000)\n", "curve_x_points = [i for i in curve_x_points if ((1.2-3*i**2)/0.3) >= 0]\n", "\n", "curve_y_points = [boundary(i) for i in curve_x_points]\n", "curve_neg_y_points = [-boundary(i) for i in curve_x_points]\n", "\n", "p = figure(plot_width=700, plot_height=250)\n", "p.circle(x, y, radius=0.015, color=colors)\n", "\n", "p.line(curve_x_points, curve_y_points, line_width=2)\n", "p.line(curve_x_points, curve_neg_y_points, line_width=2)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dois grupos foram criados pela fronteira. Os pontos que estão fora da elipse possuem classificação $y=1$ e os que estão dentro possuem classificação $y=0$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Função de erro\n", "\n", "Para descobrir os valores dos coeficientes em $w$ vamos, assim como nas outras regressões, minimizar uma função de erro através da descida de gradiente.\n", "\n", "A função de erro é definida assim:\n", "\n", "$$J(w) = \\frac{1}{m} \\sum_{i=1}^{m} Cost(f(x^{(i)}), y^{(i)})$$\n", "\n", "\n", "Onde $m$ é a quantidade de exemplos de treinamento.\n", "\n", "$Cost(f(x), y)$ varia de acordo com o valor de $y$. Para $y=1$, temos:\n", "\n", "$$Cost(f(x), y) = -\\log(f(x))$$\n", "\n", "Assim, quanto mais próximo de 1 for $f(x)$, mais próximo de 0 é o valor de $Cost$. Caso contrário, quanto mais próximo de 0 for $f(x)$, mais próximo de infinito é o valor de $Cost$.\n", "\n", "Para $y=0$:\n", "\n", "$$Cost(f(x), y) = -\\log(1 - f(x))$$\n", "\n", "Nesse caso, temos o oposto, quanto mais próximo de 0 for $f(x)$, mais próximo de 0 é o valor de $Cost$ e quanto mais próximo de 1 for $f(x)$, mais próximo de infinito é o valor de $Cost$. \n", "\n", "Com isso, se o modelo atribui probabilidades altas de forma correta paras as classificações dos exemplos, então o valor de $J(w)$ é baixo. Caso contrário, se ele atribui de forma errada probabilidades altas, o valor de $J(w)$ tende a infinito.\n", "\n", "Os gráficos a seguir mostram como os valores de $Cost$ variam para $y=1$ (esquerda) e $y=0$ (direita)." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.layouts import row\n", "\n", "x_points = np.linspace(0.01, 0.99, 10000)\n", "y1_points = [-math.log(i) for i in x_points]\n", "y0_points = [-math.log(1-i) for i in x_points]\n", "\n", "p1 = figure(plot_width=350, plot_height=250)\n", "p1.xaxis.axis_label = \"f(x)\"\n", "p1.yaxis.axis_label = \"Cost(f(x), 1)\"\n", "p1.line(x_points, y1_points, line_width=2)\n", "\n", "p0 = figure(plot_width=350, plot_height=250)\n", "p0.xaxis.axis_label = \"f(x)\"\n", "p0.yaxis.axis_label = \"Cost(f(x), 0)\"\n", "p0.line(x_points, y0_points, line_width=2)\n", "\n", "show(row(p1, p0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A função $Cost$ pode ser condensada em apenas uma definição:\n", "\n", "$$Cost(f(x), y) = -y\\log(f(x)) - (1-y)\\log(1-f(x))$$\n", "\n", "Note que quando $y=1$, a função se resume a $Cost(f(x), y) = -\\log(f(x))$ e quando $y=0$ ela tem a forma $Cost(f(x), y) = -\\log(1-f(x))$. Ou seja, as mesmas definições mostradas anteriormente.\n", "\n", "Com isso, usaremos essa definição diretamente na função de erro $J(w)$:\n", "\n", "$$J(w) = \\frac{1}{m} \\sum_{i=1}^{m} [-y^{(i)}\\log(f(x^{(i)})) - (1-y^{(i)})\\log(1-f(x^{(i)}))]$$\n", "\n", "Para simplificar, multiplicamos todo o somatório por $-1$, assim trocando os sinais internos do somatório.\n", "\n", "$$J(w) = -\\frac{1}{m} \\sum_{i=1}^{m} [y^{(i)}\\log(f(x^{(i)})) + (1-y^{(i)})\\log(1-f(x^{(i)}))]$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A seguir é mostrada a implementação da função de erro $J(w)$. Ela recebe as características dos dados de treinamento (parâmetro x) e as suas respostas (parâmetro y), além dos coeficientes (parâmetro w). \n", "O parâmetro x é uma matriz e cada linha dessa matriz corresponde ao vetor de características de um exemplo.\n", "Outro ponto importante é lembrar que o primeiro elemento do vetor de características é sempre 1, por isso é feita a concatenação com `[1]` na primeira linha dentro do `for`." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def cost(x, y, w):\n", " costs = 0\n", "\n", " for i in range(len(x)):\n", " xi = np.concatenate([[1], x[i]])\n", " yi = y[i]\n", " \n", " f_value = f(xi, w)\n", "\n", " if yi == 1:\n", " if (f_value == 0):\n", " return math.inf\n", " else:\n", " costs -= math.log(f_value)\n", " else:\n", " if (f_value == 1):\n", " return math.inf\n", " else:\n", " costs -= math.log(1 - f_value)\n", "\n", " return (1/(2*len(x)))*costs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Descida de gradiente\n", "\n", "Vamos aplicar a regressão logística no conhecido conjunto de dados sobre as flores iris. No código abaixo, carregamos e visualizamos dois tipos de flores (azul e vermelho) de acordo com duas características ($x_1$ e $x_2$)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[7]>

" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from sklearn import datasets\n", "\n", "iris_data = datasets.load_iris()\n", "\n", "x_chart = [i[0] for i in iris_data.data[:100]]\n", "y_chart = [i[1] for i in iris_data.data[:100]]\n", "colors = ([\"blue\"] * 50) + ([\"red\"] * 50)\n", "\n", "p = figure(plot_width=700, plot_height=250)\n", "p.xaxis.axis_label = \"x1\"\n", "p.yaxis.axis_label = \"x2\"\n", "p.circle(x_chart, y_chart, radius=0.015, color=colors)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Para minimizar $J(w)$ usando a descida de gradiente é necessário calcular a derivada parcial da função:\n", "\n", "\\begin{equation*}\n", "\\frac{\\partial}{\\partial w_j} J(w) = \\frac{1}{m} \\sum_{i=1}^{m} (f(x^{(i)}) - y^{(i)})x_j^{(i)}\n", "\\end{equation*}\n", "\n", "As derivadas são calculadas pelo código abaixo. A função recebe as características dos dados de treinamento (parâmetro x), as suas respostas (parâmetro y), os coeficientes (parâmetro w) e o índice $j$ (parâmetro j) da equação da derivada parcial que especifica qual derivada parcial está sendo calculada." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def d_error(x, y, w, j):\n", " total = 0\n", "\n", " for i in range(len(x)):\n", " xi = np.concatenate([[1], x[i]])\n", " yi = y[i]\n", "\n", " fxi = f(xi, w)\n", " error = (fxi - yi)\n", "\n", " total += error*xi[j]\n", " \n", " return total/(len(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Com isso, atualizamos os coeficientes iterativamente assim:\n", "\n", "\\begin{equation*}\n", "w_j := w_j - \\alpha \\frac{\\partial}{\\partial w_j} J(w)\n", "\\end{equation*}\n", "\n", "ou seja\n", "\n", "\\begin{equation*}\n", "w_j := w_j - \\alpha \\frac{1}{m} \\sum_{i=1}^{m} (f(x^{(i)}) - y^{(i)})x_j^{(i)}\n", "\\end{equation*}\n", "\n", "onde $j$ varia de 0 até $N$, sendo $N+1$ o total de coeficientes.\n", "\n", "Vamos ajustar uma reta como fronteira de decisão, ela tem a forma: $w_0 + w_1x_1 + w_2x_2 = 0$." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "x = [[i[0], i[1]] for i in iris_data.data[:100]]\n", "y = iris_data.target[:100]\n", "\n", "w = [0, 0, 0]\n", "alpha = 0.1\n", "\n", "for n in range(10000):\n", " w_temp = [0, 0, 0]\n", " for i in range(len(w)):\n", " w_temp[i] = w[i] - alpha*d_error(x, y, w, i)\n", " \n", " w = w_temp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Os coeficientes encontrados foram:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-3.3872040867695046, 6.0550889793412503, -9.5045216464262339]\n" ] } ], "source": [ "print(w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dessa forma, foi encontrada a reta: $-3,3872 + 6,055x_1 -9,5045x_2 = 0$.\n", "Traçando ela no gráfico, percebemos claramente a divisão entre os dois tipos de flores." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[11]>

" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "p = figure(plot_width=700, plot_height=250)\n", "p.xaxis.axis_label = \"x1\"\n", "p.yaxis.axis_label = \"x2\"\n", "p.circle(x_chart, y_chart, radius=0.015, color=colors)\n", "p.line([4, 8], [(-3.3872+6.055*4)/9.5045, (-3.3872+6.055*8)/9.5045], line_width=2)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos testar o classificador para os pontos (6, 2) e (4,3)." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Probabilidade do ponto (6,2) ter a classificação 1 (vermelho): 0.999999111994042\n", "Probabilidade do ponto (4,3) ter a classificação 1 (vermelho): 0.00046157094623368804\n" ] } ], "source": [ "print(\"Probabilidade do ponto (6,2) ter a classificação 1 (vermelho): {}\".format(f([1, 6, 2], w)))\n", "print(\"Probabilidade do ponto (4,3) ter a classificação 1 (vermelho): {}\".format(f([1, 4, 3], w)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos também usar uma fronteira definida por um polinômio. Por exemplo: $w_0 + w_1x_1 + w_2x_2^2 = 0$.\n", "Para isso, escreveremos a função `f` e a função para calcular a sua derivada de forma específica para esse exemplo, pois a implementação anterior não tratava expoentes nas variáveis." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def f(x, w):\n", " z = w[0]*x[0] + w[1]*x[1] + w[2]*(x[2]**2) \n", " return g(z)\n", "\n", "def d_error(x, y, w, j):\n", " total = 0\n", "\n", " for i in range(len(x)):\n", " xi = np.concatenate([[1], x[i]])\n", " yi = y[i]\n", "\n", " fxi = f(xi, w)\n", " error = (fxi - yi)\n", "\n", " if (j == 2):\n", " total += error*xi[j]**2\n", " else:\n", " total += error*xi[j]\n", " \n", " return total/(len(x))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Executando a descida de gradiente:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "x = [[i[0], i[1]] for i in iris_data.data[:100]]\n", "y = iris_data.target[:100]\n", "\n", "w = [0, 0, 0]\n", "alpha = 0.01\n", "\n", "for n in range(1000000):\n", " w_temp = [0, 0, 0]\n", " for i in range(len(w)):\n", " w_temp[i] = w[i] - alpha*d_error(x, y, w, i)\n", " \n", " w = w_temp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Os coeficientes encontrados foram:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-27.651233755257426, 8.0858324530145236, -1.6503621951007472]\n" ] } ], "source": [ "print(w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Traçando a curva no gráfico, temos:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[16]>

" ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "x_points = np.linspace(4, 8, 100)\n", "y_points = [math.sqrt(((w[0] + w[1]*i))/(-w[2])) for i in x_points]\n", "\n", "p = figure(plot_width=700, plot_height=250)\n", "p.xaxis.axis_label = \"x1\"\n", "p.yaxis.axis_label = \"x2\"\n", "p.circle(x_chart, y_chart, radius=0.015, color=colors)\n", "p.line(x_points, y_points, line_width=2)\n", "show(p, notebook_handle=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vamos testar o classificador para os pontos (6, 2) e (4,3)." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Probabilidade do ponto (6,2) ter a classificação 1 (vermelho): 0.9999993603296184\n", "Probabilidade do ponto (4,3) ter a classificação 1 (vermelho): 3.8640780844487264e-05\n" ] } ], "source": [ "print(\"Probabilidade do ponto (6,2) ter a classificação 1 (vermelho): {}\".format(f([1, 6, 2], w)))\n", "print(\"Probabilidade do ponto (4,3) ter a classificação 1 (vermelho): {}\".format(f([1, 4, 3], w)))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.1" } }, "nbformat": 4, "nbformat_minor": 1 }