{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"# Redes neuronales 1\n",
"---\n",
"\n",
"Las redes neuronales artificiales (RNA) constituyen un paradigma de computación inspirado en las neuronas biológicas y su interconexión. Las neuronas biológicas son células compuestas principalmente de tres partes: soma (cuerpo celular), dendritas (canales de entrada) y axón (canal de salida). Descrito de una forma muy simplificada, las neuronas procesan y transmiten información por medios electroquímicos. Cuando una neurona recibe, a través de las denritas, una cantidad de estímulos mayor a un cierto umbral, ésta se despolariza excitando, a través del axón, a otras neuronas próximas conectadas a través de las sinapsis.\n",
"\n",
"\n",
"\n",
"## La neurona artificial\n",
"\n",
"Inspirados por esta idea se concibió el modelo de neurona artificial. Fundamentalmente, consiste en una unidad de cálculo que admite como entrada un vector de características $\\vec{e}$ cuyos valores se suman de forma ponderada mediante un vector de pesos $\\vec{w}$ y, si esta suma supera un cierto umbral $\\theta$, genera un cierto valor de salida, por ejemplo $1$ y, si no lo supera, genera otro valor, por ejemplo, un $0$.\n",
"\n",
"La expresión matemática básica de la neurona artificial es la siguiente:\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=1}^{n} {w_i e_i} \\geq \\theta \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Cuando la neurona está sola, es decir, no conectada a otras conformando una red, actúa como un clasificador lineal. \n",
"\n",
"## Problemas de clasificación lineal\n",
"\n",
"Uno de las principales problemas que resuelven las redes neuronales son las tareas de **clasificación**. Pero, ¿en qué consiste? Clasificar es agrupar objetos de categorías similares. Por ejemplo, si tenemos un conjunto de monedas y se nos pide clasificarlas, podemos hacerlo, por ejemplo, por el valor de la moneda. Las de 1€ con las de 1€, las de 50 céntimos con las de 50 céntimos, etc. La propiedad que observamos para agrupar es su valor. Otro ejemplo, podría ser la clasificación de las manzanas atendiendo a su color como \"rojas\" y \"verdes\". Es posible también tener en cuenta más de una propiedad del objeto para su clasificación. Por ejemplo, supongamos que se pide clasificar teléfonos móviles como \"gama alta\" si su cámara supera los 15 megapixeles y además tiene más de 128GB de memoria. Podríamos seguir así y utilizar tantas propiedades de los objetos como queramos para su clasificación. Por tanto, definimos como **vector de características** al vector ordenado de las características o propiedades que se tendrán en cuenta para clasificar un objeto. \n",
"\n",
"\n",
"$$ \\vec{e} = (e_1, e_2, \\dots, e_n) $$\n",
"\n",
"Por tanto, un vector de características \"caracteriza\" un objeto. En el caso de los móviles, el móvil *A* podría tener como vector de características $\\vec{e_A} = (10, 64)$, siendo 10 el número de megapixeles de la cámara y 64 el de megabytes de memoria. El móvil *B* podría ser: $\\vec{e_B} = (12, 256)$, el móvil *C*: $\\vec{e_C} = (8, 32)$, etc.\n",
"\n",
"Si representamos estos vectores de características como **puntos** en unos ejes de coordenadas cartesianas tendríamos:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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k+wuO5UrV3ZGDgAAAKewe4CyJH3yySd64IEHFBYWpt9//12SNGvWLH399dcODQcAAFBYdpedOXPmKC4uTm3atFFmZqZljI6/v79mzZrl6HwAAACFYnfZeeeddzRv3jy9/PLLKlGihGV6kyZNtGvXLoeGAwAAKCy7y05ycrIaNWp0w3QvLy+dPXvWIaEAAAAcxe6yExERoaSkpBumr1y5UrVr13ZEJgAAAIex+2ysuLg4DR48WBcuXJBhGNqyZYuWLFmiqVOnav78+c7ICAAAUGB2l51+/fqpdOnSGjdunM6dO6euXbsqLCxM//rXv9S5c2dnZAQAACgwu6+gfK1z584pJydHQUFBjsx0x3EFZQAAih+nXUH5WmXKlFGZMmUKswoAAACnsrvsnD59WhMmTNCaNWuUnp6uvLw8q/kZGRkOCwcAAFBYdpedHj166PDhw+rbt6+Cg4NlMpmckQsAAMAh7C47P//8sxISEtSgQQNn5AEAAHAou6+zU6tWLZ0/f94ZWQAAABzO7rLz3nvv6eWXX9a6det0+vRpZWdnWz0AAACKErsPY/n7+ys7O1sPP/yw1XTDMGQymSw3BgUAACgK7C473bp1U8mSJbV48WIGKAMAgCLP7rKze/du/frrr6pZs6Yz8gAAADiU3WN2mjRpomPHjjkjCwAAgMPZvWdn6NChGjZsmEaPHq169eqpZMmSVvPr16/vsHAAAACFZfe9sTw8btwZZDKZivUAZe6NBQBA8eO0e2MlJycXKhgAAMCdZHfZqVy5sjNyAAAAOIXdA5QBAACKE8oOAABwa5QdAADg1ig7AADArRWo7GRmZmr+/PkaO3asMjIyJEmJiYn6448/HBoOAACgsOw+G2vnzp2Kjo6W2WzWb7/9pv79+ysgIEBLly5VSkqKFi5c6IycAAAABWL3np24uDj17t1bhw4dkre3t2V6mzZttH79eoeGAwAAKCy7y87WrVv13HPP3TC9QoUKSktLc0goAAAAR7G77Hh5eSk7O/uG6QcPHlT58uUdEgoAAMBR7C477du316uvvqrLly9L+uu+WCkpKRozZow6derk8IAAAACFYXfZeeutt5STk6OgoCCdP39eLVu2VLVq1eTr66spU6Y4IyMAAECB2X02ltlsVnx8vBISErRz507l5OSocePGio6OdkY+AACAQjEZhmG4OoSr2XqLeAAAUHTY+vtt056d2bNn2/zGL7zwgs3LAgAAOJtNe3YiIiKsnp86dUrnzp2Tv7+/pL+uqFymTBkFBQXp6NGjTgnqTOzZAQCg+LH199umAcrJycmWx5QpU9SwYUPt27dPGRkZysjI0L59+9S4cWNNnjzZYRsAAADgCHaP2alataq+/PJLNWrUyGr69u3b9eSTTyo5OdmhAe8E9uwAAFD8OHTPzrVSU1N15cqVG6bn5ubq5MmT9q4OAADAqewuO61atdJzzz2nxMREy7Tt27dr0KBBnH4OAACKHLvLzocffqiQkBA1adJEXl5e8vLy0r333qvg4GDNnz/fGRkBAAAKzO6LCpYvX17fffedDh06pH379kmSatWqpRo1ajg8HAAAQGHZXXauql69uqpXr+7ILAAAAA5n92EsR1q/fr3atWunsLAwmUwmLVu2zGp+7969ZTKZrB6xsbFWy2RkZKhbt27y8/OTv7+/+vbtq5ycnDu4FQAAoChzadk5e/asGjRooHffffemy8TGxio1NdXyWLJkidX8bt26ac+ePYqPj9eKFSu0fv16DRgwwNnRAQBAMVHgw1iO8Oijj+rRRx+95TJeXl4KCQnJd96+ffu0cuVKbd26VU2aNJEkvfPOO2rTpo3efPNNhYWFOTwzAAAoXly6Z8cWa9euVVBQkGrWrKlBgwbp9OnTlnmbNm2Sv7+/pehIUnR0tDw8PLR58+abrvPixYvKzs62egAAAPdkd9lZuXKlEhISLM/fffddNWzYUF27dtX//vc/h4aLjY3VwoULtXr1ak2fPl3r1q3To48+qtzcXElSWlqagoKCrF7j6empgIAApaWl3XS9U6dOldlstjzCw8MdmhsAABQddped0aNHW/aE7Nq1SyNHjlSbNm2UnJysuLg4h4br3Lmz2rdvr3r16qlDhw5asWKFtm7dqrVr1xZqvWPHjlVWVpblcezYMccEBgAARY7dY3aSk5MVGRkpSfrvf/+rtm3b6vXXX1diYqLatGnj8IDXqlKliu666y4dPnxYrVq1UkhIiNLT062WuXLlijIyMm46zkeS5WKIAADA/dm9Z6dUqVI6d+6cJOnHH39U69atJUkBAQFOH/ty/PhxnT59WqGhoZKkqKgoZWZmavv27ZZlfvrpJ+Xl5alZs2ZOzQIAAIoHu/fsNG/eXHFxcXrggQe0ZcsWffbZZ5KkgwcPqmLFinatKycnR4cPH7Y8T05OVlJSkgICAhQQEKBJkyapU6dOCgkJ0ZEjR/Tiiy+qWrVqiomJkSTVrl1bsbGx6t+/v+bOnavLly9ryJAh6ty5M2diAQAASQXYs/Pvf/9bnp6e+vLLLzVnzhxVqFBBkvT999/fcMG/29m2bZsaNWqkRo0aSZLi4uLUqFEjTZgwQSVKlNDOnTvVvn171ahRQ3379tU999yjn3/+2eoQ1KJFi1SrVi21atVKbdq0UfPmzfX+++/bu1kAAMBNmQzDMFwdwtWys7NlNpuVlZUlPz8/V8cBAAA2sPX3u0DX2Tly5IjGjRunLl26WAYIf//999qzZ0/B0gIAADiJ3WVn3bp1qlevnjZv3qylS5da7kO1Y8cOTZw40eEBAQAACsPusvPSSy/ptddeU3x8vEqVKmWZ/vDDD+uXX35xaDgAAIDCsrvs7Nq1Sx07drxhelBQkP7880+HhAIAAHAUu8uOv7+/UlNTb5j+66+/Ws7MAgAAKCrsLjudO3fWmDFjlJaWJpPJpLy8PG3YsEGjRo1Sz549nZERAACgwOwuO6+//rpq1aql8PBw5eTkKDIyUi1atND999+vcePGOSMjAABAgRX4OjvHjh3Trl27lJOTo0aNGql69eqOznbHcJ0dAACKH6ddZ+fVV1/VuXPnFB4erjZt2ujpp59W9erVdf78eb366quFCg0AAOBodu/ZKVGihFJTUxUUFGQ1/fTp0woKClJubq5DA94J7NkBAKD4cdqeHcMwZDKZbpi+Y8cOBQQE2Ls6AAAAp7L5ruflypWTyWSSyWRSjRo1rApPbm6ucnJyNHDgQKeEBAAAKCiby86sWbNkGIb69OmjSZMmyWw2W+aVKlVKd999t6KiopwSEgAAoKBsLju9evWSJEVEROiBBx6Qp6fNLwUAAHAZu8fsTJgwQYsXL9b58+edkQcAAMCh7C47jRo10qhRoxQSEqL+/ftz808AAFCk2V12Zs2apRMnTmjBggVKT09XixYtFBkZqTfffFMnT550RkYAAIACs7vsSJKnp6eeeOIJff311zp+/Li6du2q8ePHKzw8XB06dNBPP/3k6JwAAAAFUqCyc9WWLVs0ceJEvfXWWwoKCtLYsWN11113qW3btho1apSjMgIAABSY3VdQTk9P1yeffKIFCxbo0KFDateunfr166eYmBjLtXcSEhIUGxurnJwcp4R2NK6gDABA8WPr77fd549XrFhRVatWVZ8+fdS7d2+VL1/+hmXq16+vpk2b2rtqAAAAh7O77KxevVr/+Mc/brmMn5+f1qxZU+BQAAAAjmJ32bladNLT03XgwAFJUs2aNW+4MSgAAEBRYPcA5TNnzqhHjx6qUKGCWrZsqZYtW6pChQrq3r27srKynJERAACgwOwuO/369dPmzZu1YsUKZWZmKjMzUytWrNC2bdv03HPPOSMjAABAgdl9NpaPj49WrVql5s2bW03/+eefFRsbq7Nnzzo04J3A2VgAABQ/tv5+271nJzAw0OqO51eZzWaVK1fO3tUBAAA4ld1lZ9y4cYqLi1NaWpplWlpamkaPHq3x48c7NBwAAEBh2XQ2VqNGjSwXDJSkQ4cOqVKlSqpUqZIkKSUlRV5eXjp16hTjdgAAQJFiU9np0KGDk2MAAAA4h90DlN0RA5QBACh+nDZAGQAAoDix+wrKubm5evvtt/X5558rJSVFly5dspqfkZHhsHAAAACFZfeenUmTJmnmzJl65plnlJWVpbi4OD3xxBPy8PDQK6+84oSIAAAABWd32Vm0aJHmzZunkSNHytPTU126dNH8+fM1YcIE/fLLL87ICAAAUGB2l520tDTVq1dPklS2bFnL/bDatm2rb7/91rHpAAAACsnuslOxYkWlpqZKkqpWraoffvhBkrR161Z5eXk5Nh0AAEAh2V12OnbsqNWrV0uShg4dqvHjx6t69erq2bOn+vTp4/CAAAAAhVHo6+xs2rRJmzZtUvXq1dWuXTtH5bqjuM4OAADFj62/33afen69qKgoRUVFFXY1AAAATmF32Vm+fHm+000mk7y9vVWtWjVFREQUOhgAAIAj2F12OnToIJPJpOuPfl2dZjKZ1Lx5cy1btkzlypVzWFAAAICCsHuAcnx8vJo2bar4+HhlZWUpKytL8fHxatasmVasWKH169fr9OnTGjVqlDPyAgAA2MXuPTvDhg3T+++/r/vvv98yrVWrVvL29taAAQO0Z88ezZo1izOzAABAkWD3np0jR47kO+LZz89PR48elSRVr15df/75Z+HTAQAAFJLdZeeee+7R6NGjderUKcu0U6dO6cUXX1TTpk0lSYcOHVJ4eLjjUgIAABSQ3YexPvjgAz3++OOqWLGipdAcO3ZMVapU0ddffy1JysnJ0bhx4xybFAAAoAAKdFHBvLw8/fDDDzp48KAkqWbNmnrkkUfk4WH3jqIigYsKAgBQ/Nj6+13oKyi7A8oOAADFj0OvoDx79mwNGDBA3t7emj179i2XfeGFF+xLCgAA4EQ27dmJiIjQtm3bFBgYeMurI5tMJssZWcUJe3YAACh+HLpnJzk5Od8/AwAAFHXFc0QxAACAjWw+9TwzM1NLlizRoEGDJEndunXT+fPnLfNLlCihefPmyd/f3+EhAQAACsrmPTvz5s1TQkKC5fny5cvl4eEhs9kss9msXbt2adasWc7ICAAAUGA2l50vv/xSzz77rNW0GTNmaMGCBVqwYIGmTp1quaggAABAUWFz2Tl69Khq1qxpeV6zZk2VKlXK8rxBgwY6dOiQY9MBAAAUks1l5+zZs8rKyrI837ZtmypWrGg1Py8vz7HpAAAACsnmslOlShUlJibedP62bdtueQ0eAAAAV7C57HTs2FHjxo3TyZMnb5iXlpamiRMnqmPHjg4NBwAAUFg23xvrzJkzatasmY4fP64ePXqoRo0akqQDBw7o008/VYUKFbRlyxb5+vo6NbAzcAVlAACKH4deQVmSfH19tWHDBo0dO1ZLlixRZmamJMnf319du3bV66+/XiyLDgAAcG8Fuuu5YRg6deqUJKl8+fIymUwOD3YnsWcHAIDix+F7dq5lMpkUFBRU4HAAAAB3ikvvjbV+/Xq1a9dOYWFhMplMWrZsmdV8wzA0YcIEhYaGqnTp0oqOjr7hWj4ZGRnq1q2b/Pz85O/vr759+yonJ+cObgUAACjKXFp2zp49qwYNGujdd9/Nd/6MGTM0e/ZszZ07V5s3b5aPj49iYmJ04cIFyzLdunXTnj17FB8frxUrVmj9+vUaMGDAndoEAABQxBVozI4zmEwmffXVV+rQoYOkv/bqhIWFaeTIkRo1apQkKSsrS8HBwfroo4/UuXNn7du3T5GRkdq6dauaNGkiSVq5cqXatGmj48ePKywszKb3ZswOAADFj62/3y7ds3MrycnJSktLU3R0tGWa2WxWs2bNtGnTJknSpk2b5O/vbyk6khQdHS0PDw9t3rz5puu+ePGisrOzrR4AAMA9FajspKSkKDU11WpaamqqUlJSHBJK+utChZIUHBxsNT04ONgyLy0t7YaB0p6engoICLAsk5+pU6da7tZuNpsVHh7usNwAAKBoKVDZufvuu9WqVSuraQ8//HCxuV3E2LFjlZWVZXkcO3bM1ZEAAICT2Hzq+dNPP63//Oc/KleunH766Sf5+PhYzV+4cKHOnTvnsGAhISGSpJMnTyo0NNQy/eTJk2rYsKFlmfT0dKvXXblyRRkZGZbX58fLy0teXl4OywoAAIoum/fsHD9+XHXq1NG3336rBx98UE2bNrWa37RpU7Vs2dJhwSIiIhQSEqLVq1dbpmVnZ2vz5s2KioqSJEVFRSkzM1Pbt2+3LPPTTz8pLy9PzZo1c1gWAABQfNlcdjZs2KARI0boqaeeUr9+/RxyLZucnBwlJSUpKSlJ0l+DkpOSkpSSkiKTyaThw4frtdde0/Lly7Vr1y717NlTYWFhljO2ateurdjYWPXv319btmzRhg0bNGTIEHXu3NnmM7EAAIB7s/vU8/379+vZZ59VWlqahg4dKk9P6yNhL7zwgs3rWrt2rR566KEbpvfq1UsfffSRDMPQxIkT9f777yszM1PNmzfXe++9Z7kJqfTXRQWHDBmib775Rh4eHurUqZNmz56tsmXL2pyDU88BACh+bP39LtB1dubPn6+BAwcqNDTUquyYTCYdPXq0YIldiLIDAEDx45R7Y508eVL9+vVTQkKCPvjgA/Xq1avQQQEAAJzJ5jE7//d//6c6dero/Pnz2rFjB0UHAAAUCzaXnb59+2rixIn68ccfValSJWdmAgAAcBibD2MlJSWpevXqzswCAADgcDbv2aHoAACA4qjI3ggUAADAESg7AADArVF2AACAWytQ2cnMzNT8+fM1duxYZWRkSJISExP1xx9/ODQcAABAYdl1UUFJ2rlzp6Kjo2U2m/Xbb7+pf//+CggI0NKlS5WSkqKFCxc6IycAAECB2L1nJy4uTr1799ahQ4fk7e1tmd6mTRutX7/eoeEAAAAKy+6ys3XrVj333HM3TK9QoYLS0tIcEgoAAMBR7C47Xl5eys7OvmH6wYMHVb58eYeEAgAAcBS7y0779u316quv6vLly5L+utN5SkqKxowZo06dOjk8IAAAQGHYXXbeeust5eTkKCgoSOfPn1fLli1VrVo1+fr6asqUKc7ICAAAUGB2n41lNpsVHx+vhIQE7dy5Uzk5OWrcuLGio6OdkQ8AAKBQTIZhGK4O4WrZ2dkym83KysqSn5+fq+MAAAAb2Pr7bdOendmzZ2vAgAHy9vbW7Nmzb7ls2bJlVadOHTVr1sy+xAAAAE5g056diIgIbdu2TYGBgYqIiLjlshcvXlR6erpGjBihN954w2FBnYk9OwAAFD+2/n475TBWfHy8unbtqlOnTjl61U5B2QEAoPix9ffbKTcCbd68ucaNG+eMVQMAANjF5rLTpk0bZWVlWZ5PmzZNmZmZluenT59WZGSkJKl06dIaNmyY41ICAAAUkM1lZ9WqVbp48aLl+euvv26547kkXblyRQcOHHBsOgAAgEKyuexcP7SHM9YBAEBx4JQxOwAAAEWFzWXHZDLJZDLdMA0AAKAos/l2EYZhqHfv3vLy8pIkXbhwQQMHDpSPj48kWY3nAQAAKCpsLju9evWyet69e/cblunZs2fhEwEAADiQzWVnwYIFzswBAADgFAxQBgAAbo2yAwAA3BplBwAAuDXKDgAAcGuUHQAA4NYoOwAAwK1RdgAAgFuj7AAAALdG2QEAAG6NsgMAANwaZQcAALg1yg4AAHBrlB0AAODWbL7rOfB3k5tnaEtyhtLPXFCQr7fujQhQCQ+Tq2MBAOxE2QHysXJ3qiZ9s1epWRcs00LN3prYLlKxdUNdmAwAYC8OYwHXWbk7VYM+TbQqOpKUlnVBgz5N1MrdqS5KBgAoCMoOcI3cPEOTvtkrI595V6dN+mavcvPyWwIAUBRRdoBrbEnOuGGPzrUMSalZF7QlOePOhQIAFAplB7hG+pmbF52CLAcAcD3KDnCNIF9vhy4HAHA9yg5wjXsjAhRq9tbNTjA36a+zsu6NCLiTsQAAhUDZAa5RwsOkie0iJemGwnP1+cR2kVxvBwCKEcoOcJ3YuqGa072xQszWh6pCzN6a070x19kBgGKGiwoC+YitG6pHIkO4gjIAuAHKDnATJTxMiqoa6OoYAIBC4jAWAABwa5QdAADg1ig7AADArVF2AACAW6PsAAAAt0bZAQAAbo2yAwAA3BplBwAAuDXKDgAAcGuUHQAA4NaKdNl55ZVXZDKZrB61atWyzL9w4YIGDx6swMBAlS1bVp06ddLJkyddmBgAABQ1RbrsSFKdOnWUmppqeSQkJFjmjRgxQt98842++OILrVu3TidOnNATTzzhwrQAAKCoKfI3AvX09FRISMgN07OysvTBBx9o8eLFevjhhyVJCxYsUO3atfXLL7/ovvvuu9NRAQBAEVTk9+wcOnRIYWFhqlKlirp166aUlBRJ0vbt23X58mVFR0dblq1Vq5YqVaqkTZs23XKdFy9eVHZ2ttUDAAC4pyJddpo1a6aPPvpIK1eu1Jw5c5ScnKx//OMfOnPmjNLS0lSqVCn5+/tbvSY4OFhpaWm3XO/UqVNlNpstj/DwcCduBQAAcKUifRjr0Ucftfy5fv36atasmSpXrqzPP/9cpUuXLvB6x44dq7i4OMvz7OxsCg8AAG6qSO/ZuZ6/v79q1Kihw4cPKyQkRJcuXVJmZqbVMidPnsx3jM+1vLy85OfnZ/UAAADuqViVnZycHB05ckShoaG65557VLJkSa1evdoy/8CBA0pJSVFUVJQLUwIAgKKkSB/GGjVqlNq1a6fKlSvrxIkTmjhxokqUKKEuXbrIbDarb9++iouLU0BAgPz8/DR06FBFRUVxJhYAALAo0mXn+PHj6tKli06fPq3y5curefPm+uWXX1S+fHlJ0ttvvy0PDw916tRJFy9eVExMjN577z0XpwYAAEWJyTAMw9UhXC07O1tms1lZWVmM3wEAoJiw9fe7WI3ZAQAAsBdlBwAAuDXKDgAAcGuUHQAA4NYoOwAAwK1RdgAAgFuj7AAAALdG2QEAAG6NsgMAANwaZQcAALg1yg4AAHBrlB0AAODWKDsAAMCtUXYAAIBbo+wAAAC35unqAO4qN8/QluQMpZ+5oCBfb90bEaASHiZXxwIA4G+HsuMEK3enatI3e5WadcEyLdTsrYntIhVbN9SFyQAA+PvhMJaDrdydqkGfJloVHUlKy7qgQZ8mauXuVBclAwDg74my40C5eYYmfbNXRj7zrk6b9M1e5ebltwQAAHAGyo4DbUnOuGGPzrUMSalZF7QlOePOhQIA4G+OsuNA6WduXnQKshwAACg8yo4DBfl6O3Q5AABQeJQdB7o3IkChZm/d7ARzk/46K+veiIA7GQsAgL81yo4DlfAwaWK7SEm6ofBcfT6xXSTX2wEA4A6i7DhYbN1QzeneWCFm60NVIWZvzenemOvsAABwh3FRQSeIrRuqRyJDuIIyAABFAGXHSUp4mBRVNdDVMQAA+NvjMBYAAHBrlB0AAODWKDsAAMCtUXYAAIBbo+wAAAC3RtkBAABujbIDAADcGmUHAAC4NcoOAABwa1xBWZJhGJKk7OxsFycBAAC2uvq7ffV3/GYoO5LOnDkjSQoPD3dxEgAAYK8zZ87IbDbfdL7JuF0d+hvIy8vTiRMn5OvrK5PJcTfrzM7OVnh4uI4dOyY/Pz+HrbcocfdtZPuKP3ffRrav+HP3bXTm9hmGoTNnzigsLEweHjcfmcOeHUkeHh6qWLGi09bv5+fnll/ga7n7NrJ9xZ+7byPbV/y5+zY6a/tutUfnKgYoAwAAt0bZAQAAbo2y40ReXl6aOHGivLy8XB3Fadx9G9m+4s/dt5HtK/7cfRuLwvYxQBkAALg19uwAAAC3RtkBAABujbIDAADcGmUHAAC4NcqOE+Tm5mr8+PGKiIhQ6dKlVbVqVU2ePPm29+4oTs6cOaPhw4ercuXKKl26tO6//35t3brV1bEKbP369WrXrp3CwsJkMpm0bNkyq/mGYWjChAkKDQ1V6dKlFR0drUOHDrkmbAHcbvuWLl2q1q1bKzAwUCaTSUlJSS7JWVC32r7Lly9rzJgxqlevnnx8fBQWFqaePXvqxIkTrgtcALf7O3zllVdUq1Yt+fj4qFy5coqOjtbmzZtdE7YAbrd91xo4cKBMJpNmzZp1x/IV1u22r3fv3jKZTFaP2NhY14QtIFv+Dvft26f27dvLbDbLx8dHTZs2VUpKitOzUXacYPr06ZozZ47+/e9/a9++fZo+fbpmzJihd955x9XRHKZfv36Kj4/XJ598ol27dql169aKjo7WH3/84epoBXL27Fk1aNBA7777br7zZ8yYodmzZ2vu3LnavHmzfHx8FBMTowsXLtzhpAVzu+07e/asmjdvrunTp9/hZI5xq+07d+6cEhMTNX78eCUmJmrp0qU6cOCA2rdv74KkBXe7v8MaNWro3//+t3bt2qWEhATdfffdat26tU6dOnWHkxbM7bbvqq+++kq//PKLwsLC7lAyx7Bl+2JjY5Wammp5LFmy5A4mLLzbbeORI0fUvHlz1apVS2vXrtXOnTs1fvx4eXt7Oz+cAYd77LHHjD59+lhNe+KJJ4xu3bq5KJFjnTt3zihRooSxYsUKq+mNGzc2Xn75ZRelchxJxldffWV5npeXZ4SEhBhvvPGGZVpmZqbh5eVlLFmyxAUJC+f67btWcnKyIcn49ddf72gmR7rV9l21ZcsWQ5Lx+++/35lQDmbLNmZlZRmSjB9//PHOhHKgm23f8ePHjQoVKhi7d+82KleubLz99tt3PJsj5Ld9vXr1Mh5//HGX5HGG/LbxmWeeMbp37+6SPOzZcYL7779fq1ev1sGDByVJO3bsUEJCgh599FEXJ3OMK1euKDc394Y2Xrp0aSUkJLgolfMkJycrLS1N0dHRlmlms1nNmjXTpk2bXJgMBZWVlSWTySR/f39XR3GKS5cu6f3335fZbFaDBg1cHcch8vLy1KNHD40ePVp16tRxdRynWLt2rYKCglSzZk0NGjRIp0+fdnUkh8nLy9O3336rGjVqKCYmRkFBQWrWrNktD1c6EmXHCV566SV17txZtWrVUsmSJdWoUSMNHz5c3bp1c3U0h/D19VVUVJQmT56sEydOKDc3V59++qk2bdqk1NRUV8dzuLS0NElScHCw1fTg4GDLPBQfFy5c0JgxY9SlSxe3u+niihUrVLZsWXl7e+vtt99WfHy87rrrLlfHcojp06fL09NTL7zwgqujOEVsbKwWLlyo1atXa/r06Vq3bp0effRR5ebmujqaQ6SnpysnJ0fTpk1TbGysfvjhB3Xs2FFPPPGE1q1b5/T3567nTvD5559r0aJFWrx4serUqaOkpCQNHz5cYWFh6tWrl6vjOcQnn3yiPn36qEKFCipRooQaN26sLl26aPv27a6OBtzU5cuX9fTTT8swDM2ZM8fVcRzuoYceUlJSkv7880/NmzdPTz/9tDZv3qygoCBXRyuU7du361//+pcSExNlMplcHccpOnfubPlzvXr1VL9+fVWtWlVr165Vq1atXJjMMfLy8iRJjz/+uEaMGCFJatiwoTZu3Ki5c+eqZcuWTn1/9uw4wejRoy17d+rVq6cePXpoxIgRmjp1qqujOUzVqlW1bt065eTk6NixY9qyZYsuX76sKlWquDqaw4WEhEiSTp48aTX95MmTlnko+q4Wnd9//13x8fFut1dHknx8fFStWjXdd999+uCDD+Tp6akPPvjA1bEK7eeff1Z6eroqVaokT09PeXp66vfff9fIkSN19913uzqeU1SpUkV33XWXDh8+7OooDnHXXXfJ09NTkZGRVtNr167N2VjF1blz5+ThYf3RlihRwtJs3YmPj49CQ0P1v//9T6tWrdLjjz/u6kgOFxERoZCQEK1evdoyLTs7W5s3b1ZUVJQLk8FWV4vOoUOH9OOPPyowMNDVke6IvLw8Xbx40dUxCq1Hjx7auXOnkpKSLI+wsDCNHj1aq1atcnU8pzh+/LhOnz6t0NBQV0dxiFKlSqlp06Y6cOCA1fSDBw+qcuXKTn9/DmM5Qbt27TRlyhRVqlRJderU0a+//qqZM2eqT58+ro7mMKtWrZJhGKpZs6YOHz6s0aNHq1atWnr22WddHa1AcnJyrP4HlZycrKSkJAUEBKhSpUoaPny4XnvtNVWvXl0REREaP368wsLC1KFDB9eFtsPtti8jI0MpKSmWa89c/QcpJCSkWOy9utX2hYaG6sknn1RiYqJWrFih3Nxcy1irgIAAlSpVylWx7XKrbQwMDNSUKVPUvn17hYaG6s8//9S7776rP/74Q0899ZQLU9vudt/R6wtqyZIlFRISopo1a97pqAVyq+0LCAjQpEmT1KlTJ4WEhOjIkSN68cUXVa1aNcXExLgwtX1u93c4evRoPfPMM2rRooUeeughrVy5Ut98843Wrl3r/HAuOQfMzWVnZxvDhg0zKlWqZHh7extVqlQxXn75ZePixYuujuYwn332mVGlShWjVKlSRkhIiDF48GAjMzPT1bEKbM2aNYakGx69evUyDOOv08/Hjx9vBAcHG15eXkarVq2MAwcOuDa0HW63fQsWLMh3/sSJE12a21a32r6rp9Pn91izZo2ro9vsVtt4/vx5o2PHjkZYWJhRqlQpIzQ01Gjfvr2xZcsWV8e22e2+o9crbqee32r7zp07Z7Ru3dooX768UbJkSaNy5cpG//79jbS0NFfHtostf4cffPCBUa1aNcPb29to0KCBsWzZsjuSzWQYbnRZXwAAgOswZgcAALg1yg4AAHBrlB0AAODWKDsAAMCtUXYAAIBbo+wAAAC3RtkBAABujbIDAH9z//nPf+7MVWwBF6HsAA5w9913a9asWa6OUew9+OCDGj58uMPW98orr6hhw4YOW58tevfuXejbiNzJ79Mnn3yiefPmqWnTpnfk/QBXoOwAt9G7d2+ZTKYbHrGxsZZltm7dqgEDBhT4PXbs2KFSpUpp+fLlVtP/+9//ytvbW7t377Z5Xa+88soN+a564403ZDKZ9OCDDxY4qzMtXbpUkydPdnWMv42DBw9qxowZWrFihXx8fFwdB3AabgQK2CA2NlYLFiywmubl5WX5c/ny5Qu1/gYNGmjChAkaMGCAHnjgAQUGBio9PV0DBw7UpEmTVLduXbvWFxoaqjVr1uj48eOqWLGiZfqHH36oSpUqFSqrMwUEBLg6wt9KjRo1tGvXLpdmuHTpUrG5GSuKL/bsADbw8vKy3AH86qNcuXKW+dcfdsjMzFS/fv1Uvnx5+fn56eGHH9aOHTtu+R5jx45VpUqVNHjwYEnSc889p+rVq2vUqFF25w0KClLr1q318ccfW6Zt3LhRf/75px577LEblp8/f75q164tb29v1apVS++9957V/I0bN6phw4by9vZWkyZNtGzZMplMJiUlJUmScnNz1bdvX0VERKh06dKqWbOm/vWvf1mt4+rhnUmTJlk+l4EDB+rSpUuWZa49jLV//36VKVNGixcvtsz//PPPVbp0ae3du1dSwT7nW23rpUuXNGTIEIWGhsrb21uVK1fW1KlTb7qu3NxcxcXFyd/fX4GBgXrxxRd1/e0G8/LyNHXqVMtn06BBA3355Ze3zHi9mTNnql69evLx8VF4eLief/555eTk3PI1mZmZeu655xQcHCxvb2/VrVtXK1askCSdPn1aXbp0UYUKFVSmTBnVq1dPS5YssXr9gw8+qKFDh2r48OEqV66cgoODNW/ePJ09e1bPPvusfH19Va1aNX3//fdWn4et34MpU6YoLCzMctfyTz75RE2aNJGvr69CQkLUtWtXpaen2/U5ATdD2QGc4KmnnlJ6erq+//57bd++XY0bN1arVq2UkZFx09eUKFFCH3/8sb7++mt17dpVq1at0kcffaQSJUpYllm7dq1MJpN+++2322bo06ePPvroI8vzDz/8UN26dbvhf9GLFi3ShAkTNGXKFO3bt0+vv/66xo8fbylK2dnZateunerVq6fExERNnjxZY8aMsVpHXl6eKlasqC+++EJ79+7VhAkT9M9//lOff/651XKrV6/Wvn37tHbtWi1ZskRLly7VpEmT8s1fq1Ytvfnmm3r++eeVkpKi48ePa+DAgZo+fboiIyML9Dnfbltnz56t5cuX6/PPP9eBAwe0aNEi3X333Tf9jN966y199NFH+vDDD5WQkKCMjAx99dVXVstMnTpVCxcu1Ny5c7Vnzx6NGDFC3bt317p162663ut5eHho9uzZ2rNnjz7++GP99NNPevHFF2+6fF5enh599FFt2LBBn376qfbu3atp06ZZvksXLlzQPffco2+//Va7d+/WoEGD1LNnT23ZssVqPR9//LHuuusubdmyRUOHDtWgQYP01FNP6f7771diYqJat26tHj166Ny5c5b3tfV7cODAAcXHx1sK2OXLlzV58mTt2LFDy5Yt02+//abevXvb/BkBt3RH7q0OFGO9evUySpQoYfj4+Fg9pkyZYlmmcuXKxttvv20YhmH8/PPPhp+fn3HhwgWr9VStWtX4z3/+c9v3e+mllwxJxvTp02+Yt3nzZqNmzZrG8ePHb/r6iRMnGg0aNDAuXbpkBAUFGevWrTNycnIMX19fY8eOHcawYcOMli1bWuVavHix1TomT55sREVFGYZhGHPmzDECAwON8+fPW+bPmzfPkGT8+uuvN80xePBgo1OnTpbnvXr1MgICAoyzZ89aps2ZM8coW7askZubaxiGYbRs2dIYNmyY1Xoee+wx4x//+IfRqlUro3Xr1kZeXp5hGLZ9zlc/C1u3dejQocbDDz9seY/bCQ0NNWbMmGF5fvnyZaNixYrG448/bhiGYVy4cMEoU6aMsXHjRqvX9e3b1+jSpctN13vt9yk/X3zxhREYGHjT+atWrTI8PDyMAwcO2LQdhmEYbdu2NUaOHGl53rJlS6N58+aW51euXDF8fHyMHj16WKalpqYakoxNmzbddL35fQ+Cg4ONixcv3jLP1q1bDUnGmTNnbN4G4GYYswPY4KGHHtKcOXOspt1sfMmOHTuUk5OjwMBAq+nnz5/XkSNHbvk+OTk5+uyzz1SmTBn9/PPPN/zv/d5779X+/fttylyyZEl1795dCxYs0NGjR1WjRg3Vr1/fapmzZ8/qyJEj6tu3r/r372+ZfuXKFZnNZknSgQMHVL9+fXl7e1vluN67776rDz/8UCkpKTp//rwuXbp0w5lQDRo0UJkyZSzPo6KilJOTo2PHjqly5cr5bseHH36oGjVqyMPDQ3v27JHJZJJk/+dsy7b27t1bjzzyiGrWrKnY2Fi1bdtWrVu3zjdXVlaWUlNT1axZM8s0T09PNWnSxHIo6/Dhwzp37pweeeQRq9deunRJjRo1yne9+fnxxx81depU7d+/X9nZ2bpy5YouXLigc+fOWX2eVyUlJalixYqqUaNGvuu7fPmyJkyYoM8++0x//PGH5VBi6dKlrZa79vtSokQJBQYGql69epZpwcHBkmR1uMmW70G9evVu2MO4fft2vfLKK9qxY4f+97//KS8vT5KUkpJi2ZMHFBRlB7CBj4+PqlWrZtOyOTk5Cg0Nzfe6Jf7+/rd87ejRo+Xt7a2NGzfqvvvu08KFC9WzZ88CJP5Lnz591KxZM+3evVt9+vTJN6skzZs3z+pHW5LV4bPb+b//+z+NGjVKb731lqKiouTr66s33nhDmzdvLnD2q3bs2KGzZ8/Kw8NDqampCg0NtWS353O2ZVsbN26s5ORkff/99/rxxx/19NNPKzo62u4xNte/57fffqsKFSpYzbt2gPut/Pbbb2rbtq0GDRqkKVOmKCAgQAkJCerbt68uXbqUb9m5vrRcb8aMGfr000/12WefqX79+ipbtqyeeeYZXbx40Wq5kiVLWj03mUxW064Wz6vFxNbvwfVnfp09e1YxMTGKiYnRokWLVL58eaWkpCgmJsZqTBdQUJQdwMEaN26stLQ0eXp63nK8x/Xi4+M1f/58bdy4UQ0aNNBrr72m4cOH65FHHrH8wNurTp06qlOnjnbu3KmuXbveMD84OFhhYWE6evSounXrlu86atasqU8//VQXL160/EBv3brVapkNGzbo/vvv1/PPP2+Zlt/elR07duj8+fOWH+NffvlFZcuWVXh4eL7vnZGRod69e+vll19WamqqunXrpsTERJUuXdruz9mWbZUkPz8/PfPMM3rmmWf05JNPKjY2VhkZGTfsyTObzQoNDdXmzZvVokULSX/tJbo6dkiSIiMj5eXlpZSUFLVs2fK2GfOzfft25eXl6a233pKHx1/DLK8fA3O9+vXr6/jx4zp48GC+e3c2bdqk2NhY3X///ZbcW7duvWHPn71s/R5cb//+/Tp9+rSmTZtm+S5s27atUFmAazFAGbDBxYsXlZaWZvX4888/8102OjpaUVFR6tChg3744Qf99ttv2rhxo15++eWb/gOenZ2tvn37avTo0ZaLu40YMUKRkZFW1+/ZsmWLatWqpT/++MPm7D/99JNSU1Nvuldp0qRJmjp1qmbPnq2DBw9q165dWrBggWbOnClJ6tq1q/Ly8jRgwADt27dPq1at0ptvvinp///Pvnr16tq2bZtWrVqlgwcPavz48TcUIumvwzd9+/bV3r179d1332nixIkaMmSI5Uf8egMHDlR4eLjGjRunmTNnKjc313J2WkE+59tt68yZM7VkyRLt379fBw8e1BdffKGQkJCbfnbDhg3TtGnTtGzZMu3fv1/PP/+8MjMzLfN9fX01atQojRgxQh9//LGOHDmixMREvfPOO1Znyt1KtWrVdPnyZb3zzjs6evSoPvnkE82dO/eWr2nZsqVatGihTp06KT4+3rK3auXKlZL+KrDfffedEhIStHfvXvXr1++Wg+dtZev34HqVKlVSqVKlLNu4fPlyrrcEx3L1oCGgqOvVq5ch6YZHzZo1LctcP6A0OzvbGDp0qBEWFmaULFnSCA8PN7p162akpKTk+x7PPvusUbdu3RsGbR48eNAoU6aM8fHHHxuGYRhr1qwxJBnJyck3zXv9oNzrXT9A2TAMY9GiRUbDhg2NUqVKGeXKlTNatGhhLF261DJ/w4YNRv369Y1SpUoZ99xzj7F48WJDkrF//37DMP4aiNu7d2/DbDYb/v7+xqBBg4yXXnrJKkevXr2Mxx9/3JgwYYIRGBholC1b1ujfv7/VAONrByh//PHHho+Pj3Hw4EHL/M2bNxslS5Y0vvvuO8Mwbv855/dZ3Gpb33//faNhw4aGj4+P4efnZ7Rq1cpITEy86Wd5+fJlY9iwYYafn5/h7+9vxMXFGT179rQMUDYMw8jLyzNmzZpl1KxZ0yhZsqRRvnx5IyYmxli3bt1N13v992nmzJlGaGioUbp0aSMmJsZYuHChIcn43//+d9N1nD592nj22WeNwMBAw9vb26hbt66xYsUKwzAMIyMjw+jYsaNRtmxZIygoyBg/frzl7+eq/AaL5zdwWpLx1VdfGYZh3/fgeosXLzbuvvtuw8vLy4iKijKWL19+20HwgK1MhnHdRSEA2C00NFSTJ09Wv379XB3ljli0aJGeffZZZWVl3XZ8yFW9e/dWZmamli1b5txwAHAdxuwAhXDu3Dlt2LBBJ0+eVJ06dVwdx2kWLlyoKlWqqEKFCtqxY4fGjBmjp59+2uaiAwCuRNkBCuH999/X5MmTNXz4cEVFRbk6jtOkpaVpwoQJSktLU2hoqJ566ilNmTLF1bEAwCYcxgIAAG6Ns7EAAIBbo+wAAAC3RtkBAABujbIDAADcGmUHAAC4NcoOAABwa5QdAADg1ig7AADArVF2AACAW/t/G59pOQxU1HsAAAAASUVORK5CYII=",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import numpy as np \n",
"from matplotlib import pyplot as plt \n",
"\n",
"features = [[10,16,8], # megapixeles de la cámara\n",
" [64,256,32] # gigabytes de memoria\n",
" ]\n",
"\n",
"plt.scatter(features[0], features[1])\n",
"\n",
"plt.xlabel(\"Eje X: Megapixeles de la cámara\")\n",
"plt.ylabel(\"Eje Y: Gigabytes de memoria\");\n",
" \n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Si tuviéramos un vector de características con tres propiedades su representación se llevaría a cabo en un espacio tridimensional, y así sucesivamente. \n",
"\n",
"Bien, pues ahora los móviles que cumplan con la condición anterior de gama alta serán los que aparecen marcados en azul y el resto, en rojo, serán los de gama baja."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
""
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"import numpy as np \n",
"from matplotlib import pyplot as plt \n",
"\n",
"features = [[10,16,8], # megapixeles de la cámara\n",
" [64,256,32]] # gigabytes de memoria\n",
" \n",
"classes = []\n",
"for e1, e2 in zip(features[0], features[1]):\n",
" if e1>15 and e2>128:\n",
" classes.append('b')\n",
" else:\n",
" classes.append('r')\n",
"\n",
"plt.scatter(features[0], features[1], c = classes)\n",
"\n",
"plt.xlabel(\"Eje X: Megapixeles de la cámara\")\n",
"plt.ylabel(\"Eje Y: Gigabytes de memoria\");\n",
" \n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Clasificación lineal\n",
"\n",
"Supongamos que tenemos ahora un ejemplo más complejo donde los objetos o **muestras** (así es como se suelen llamar estos puntos) tengan la siguiente disposición:\n",
"\n",
"\n",
"\n",
"> Decimos que un conjunto de muestras es **separable linealmente** si podemos trazar una recta (en un espacio tridimensional sería un plano y en un espacio multidimensional sería un hiperplano) que separe a ambas clases o categorías.\n",
"\n",
"\n",
"\n",
"Veamos cuándo dos conjuntos (clases o categorías) no son separables linealmente. En este caso, no podemos trazar una recta que separe perfectamente ambos comjuntos.\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Terminología\n",
"\n",
"Ahora que ya tenemos claro lo que significa \"clasificar\", definamos algo de terminología. Cada uno de los puntos u objetos a clasificar se denomina **muestra**. El conjunto de todas las muestras se denomina **conjunto de datos** (aunque te lo vas encontrar en muchos textos en español con el término anglosajón **dataset**). Todas estas muestras pertenecerán a un grupo u otro. A cada uno de estos dos grupos lo denominamos **clase**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## El Perceptrón como clasificador lineal\n",
"\n",
"Volvamos de nuevo a la definición de neurona articial y veamos qué relación tiene con los problemas de clasificación lineal. Recordemos su expresión como la vimos arriba, pero vamos a modificarla ligeramente moviendo $\\theta$ a la izquierda del símbolo \"mayor o igual\", de esta manera:\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=1}^{n} {w_i e_i} - \\theta\\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Si queremos, podemos visualizar gráficamente la neurona de esta manera:\n",
"\n",
"\n",
"\n",
"Donde la función $g(x)$ tiene la forma \"1 si $x\\geq 0$ y $0$ si $x<0$\". En este caso $x=\\sum_{i=1}^{n} {w_i e_i} - \\theta$. Más adelante veremos que $g(x)$ tendrá otras formas. Si estudiamos bien esta fórmula nos daremos cuenta de que se trata de un discriminador lineal. \n",
"\n",
"Supongamos que tenemos un conjunto de puntos ${a,b,c,d,e}$ en un espacio $R^2$ tal como muestra la figura.\n",
"\n",
"\n",
"\n",
"Algunos de ellos ($a,b,c$) pertencen a una clase (clase 1) y los otros a otra (clase 2). Estas dos regiones están delimitadas por una recta. Nótese que la recta que separa ambas clases no es única, puede ser cualquiera que satisfaga la condición de separación de las clases. Por tanto, tenemos la función de una recta con la ecuación genérica:\n",
"\n",
"$$\n",
"y = mx+b \n",
"$$\n",
"\n",
"Haciendo unos cálculos básicos, podemos concretar esta recta como la recta de la figura de ejemplo anterior:\n",
"\n",
"$$\n",
" y = \\frac{1}{2} x +1 \n",
"$$\n",
"\n",
"Esta recta corresponde al conjunto de todos los puntos $(x,y)$ que satisfacen la **ecuación**. Por ejemplo, el punto $a(2,2)$. Pero vemos que los puntos $b$,$c$,$d$ y $e$ no satisfacen la ecuación. Sin embargo, algunos de ellos, concretamente los puntos $a$,$b$ y $c$ no satisfacen la **ecuación** pero sí satisfarían la **inecuación**:\n",
"\n",
"$$\n",
"\ty \\geq \\frac{1}{2} x +1 \n",
"$$\n",
"\n",
"Observa entonces que la inecuación separa el espacio en dos subesapcios. Uno de estos subespacios, el sombreado de color celeste, satisface la inecuación, pero el otro subespacio, no.\n",
"\n",
"\n",
"Operando un poco sobre esta inecuación tendríamos:\n",
"\n",
"$$\n",
"\t-\\frac{1}{2} x + y \\geq 1 \n",
"$$\n",
"\n",
"Y cambiando la nomenclatura. Es decir, cambiando $x$ por $e_{1}$ e $y$ por $e_{2}$ tenemos:\n",
"\n",
"$$\n",
"\t-\\frac{1}{2} e_{1} + e_{2} \\geq 1 \n",
"$$\n",
"\n",
"Con lo cual podemos hacer que $w_1 = -\\frac{1}{2}$, $w_2 = 1$ y $\\theta=1$, que es, justamente, la neurona que actuaría de discriminador lineal de nuestro ejemplo.\n",
"\n",
"El verdadero potencial de la neuronal artificial no está en que calculemos a mano sus pesos y umbral sino en dejar que ella misma \"aprenda\" esos valores.\n",
"\n",
"\n",
"## Aprendizaje\n",
"\n",
"Antes de meternos de lleno con el aprendizaje vamos a ver antes un par de cosas: la **función sigmoide** y la técnica de **descenso por el gradiente**.\n",
"\n",
"\n",
"### Función sigmoide\n",
"\n",
"Utilizaremos la función **sigmoide** como **función de activación** en lugar de la función \"mayor o igual\" ya que ofrece una venjata importante: es derivable. Sí, ya sé lo que puedes estar pensando, *¿Y qué pasa con que sea derivable?*. Nos daremos cuenta de eso más adelante.\n",
"\n",
"La función sigmoide tiene la siguiente expresión: \n",
"\n",
"$$Sig(x)=\\frac{ 1 }{1+{ e }^{ -x }}$$ \n",
"\n",
"\n",
"Y si la representamos gráficamente tiene este aspecto:\n",
"\n",
"\n",
"\n",
"Vemos que tiene un rango que va desde $-\\infty$ a $\\infty$. Si nos fijamos bien, a partir de $-4$ hacia atrás su valor es prácticamente $0$ y a partir del $4$ hacia adelante su valor es prácticamente $1$. Es parecida a la función \"mayor o igual\" que definimos más arriba. Pero, a diferencia de la función sigmoide, esta tiene una discontinuidad en $0$, como observamos en la expresión y figura siguientes.\n",
"\n",
"\n",
"$$\n",
"f(x) = \\begin{cases} \\textrm{1, si } x \\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"La derivada de la función sigmoide es:\n",
"\n",
"$$Sig'(x)=\\frac { 1 }{ (1+e^{ x })} -\\frac { 1 }{ (1+e^{ x })^{ 2 } } $$\n",
"\n",
"Puedes hacer los cálculos tú mismo para verificarlo. Además, si reordenamos un poco los términos, surge una propiedad curiosa, y es que podemos expresar la derivada de la sigmoide utilizando la propia sigmoide:\n",
"\n",
"$$Sig'(x)=\\frac { 1 }{ (1+e^{ x }) } \\left[ 1-\\frac { 1 }{ (1+e^{ x }) } \\right] =\\frac { 1 }{ (1+e^{ -x }) } \\left[ 1-\\frac { 1 }{ (1+e^{ -x }) } \\right] =Sig(x)\\cdot \\left[ 1-Sig(x) \\right] $$\n",
"\n",
"\n",
"\n",
"### Descenso por el gradiente\n",
"\n",
"Supongamos que quiero encontrar el mínimo de una función, por ejemplo: $y=x^2-2x+2$. \n",
"\n",
"\n",
"\n",
"Lo primero que se nos ocurre es hallar su derivada: $y'=2x-2$, igualar a $0$ y despejar $x$. Lo que nos daría: $x=1$. Supongamos ahora que, por algún motivo, no podemos resolverlo de forma algebraica y lo tenemos que hacer de forma numérica. Es decir, partimos desde algún punto y nos vamos moviendo poco a poco en la dirección de bajada hasta que empecemos a remontar, lo cual quiere decir que hemos alcanzado el mínimo."
]
},
{
"cell_type": "code",
"execution_count": 155,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Aproximación al mínimo: 1.0010000000000097\n",
"Pasos: 150\n"
]
}
],
"source": [
"def f(x):\n",
" return x**2 -2*x + 2\n",
"\n",
"x = 2.501 # algún punto inicial \n",
"delta = 0.01 # algún valor pequeño\n",
"\n",
"counter = 0\n",
"while (f(x) - f(x - delta)) > 0:\n",
" x -= delta # nuevo x\n",
" counter += 1\n",
" \n",
"print(\"Aproximación al mínimo:\", x)\n",
"print(\"Pasos:\", counter) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nos han hecho falta 150 pasos para llegar a una aproximación del mínimo con un error menor de $1\\%$. Hay otro método mucho más eficiente para llegar a esa aproximación, se llama: **descenso por el gradiente**.\n",
"\n",
"Si nos fijamos en la pendiente de la función, vemos que, a medida que nos alejamos del mínimo, la pendiente (o derivada) es cada vez más pronunciada. Cuando estamos muy cerca del mínimo, la pendiente es casi $0$. El truco del descenso por el gradiente es aprovechar este hecho y utilizar la pendiente como paso (delta) para hacer avanzar la $x$ rápidamente cuando estamos lejos del mínimo y despacio cuando estamos cerca. Veámoslo en el siguiente código."
]
},
{
"cell_type": "code",
"execution_count": 157,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"x: 1.6004 - tamaño del paso: 3.002\n",
"x: 1.2402 - tamaño del paso: 1.2008\n",
"x: 1.0961 - tamaño del paso: 0.4803\n",
"x: 1.0384 - tamaño del paso: 0.1921\n",
"x: 1.0154 - tamaño del paso: 0.0769\n",
"x: 1.0061 - tamaño del paso: 0.0307\n",
"x: 1.0025 - tamaño del paso: 0.0123\n",
"Pasos: 7\n"
]
}
],
"source": [
"def f(x):\n",
" return x**2 -2*x + 2\n",
"\n",
"x = 2.501 # algún punto inicial \n",
"delta = 0.01\n",
"rho = 0.3\n",
"\n",
"counter = 0\n",
"while (f(x) - f(x - delta)) > 0:\n",
" h = (f(x + delta) - f(x - delta)) / (2*delta) # Cálculo numérico de la derivada en el punto x\n",
" x -= h * rho # nuevo x\n",
" counter += 1\n",
" print(\"x:\", round(x,4) ,\"- tamaño del paso:\", round(h,4))\n",
" \n",
"print(\"Pasos:\", counter) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vemos que con esta técnica logramos una aproximación similar... ¡¡en sólo 7 pasos!! \n",
"\n",
"Hay un parámetro nuevo que ha aparecido, rho ($\\rho$). Este parámetro lo llamaremos más adelante **tasa de aprendizaje**. ¿Qué función tiene? Ahora simplemente sirve como un parámetro de escala para el descenso. Observemos la figura siguiente, hay dos funciones que parecen la misma pero que, si nos fijamos bien, están a escalas diferentes. La de la izquierda tiene el mínimo en $x=1$ y la de la derecha en $x=0.1$. Sin embargo, el valor de la derivada en $x=2$ y en $x=0.2$ es el mismo, $2$. Prestemos atención primero a la función de la izquerda. Cuando hagamos el descenso por el gradiente, la nueva $x$ será: $x \\leftarrow x - m$, y esto nos llevará a $x=0$. Ahí la pendiente será $m=-2$, lo cual nos llevará de nuevo a $x=2$. Por tanto, necesitamos rebajar la amplitud del paso de alguna forma, y es ahí donde entra en juego el parámetro rho. Si damos a rho, por ejemplo, el valor $0.3$ conseguiremos reducir el paso y aproximarnos correctamente al mínimo. En la función de la derecha ocurre un efecto aún peor. Cuando actualicemos, la nueva $x$ será $x \\leftarrow 0.2 - 2$ lo que nos lleva a $x=-1.8$. Es decir, nos estaremos alejando progresivamente del mínimo. De nuevo, rho viene al rescate y si le damos un valor de, por ejemplo, $0.03$ nos estaremos aproximando adecuadamente al mínimo. \n",
"\n",
"La pregunta que surge es: *¿y cómo sé qué valor debe tener rho?*. La respuesta es que no lo podemos saber *a priori*. Habrá que probar hasta ver que el algoritmo converge.\n",
"\n",
"\n",
"\n",
"De la misma forma que podemos hacer descenso por el gradiente en una función de una variable $f(x)$, lo podemos hacer en una función con dos variables $f(x,y)$, y con tres, con cuatro, etc. La diferencia está en que ahora usamos **derivadas parciales** en lugar de derivadas. Por ejemplo, supongamos que tenemos la función $f(x,y,z)$, si quieremos hacer descenso por el gradiente tendríamos:\n",
"\n",
"$$\n",
"x \\leftarrow x - \\rho \\frac{\\partial f(x,y,z)}{\\partial x}\n",
"$$\n",
"\n",
"$$\n",
"y \\leftarrow y - \\rho \\frac{\\partial f(x,y,z)}{\\partial y}\n",
"$$\n",
"\n",
"$$\n",
"z \\leftarrow z - \\rho \\frac{\\partial f(x,y,z)}{\\partial z}\n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Modelo de la neurona con función de activación sigmoide\n",
"\n",
"Antes vimos el modelo de la neurona articicial de la siguiente forma:\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=1}^{n} {w_i e_i} - \\theta\\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Ahora, vamos a hacer algunos cambios \"estéticos\" a la neurona. Primero, le cambiaremos el nombre a $-\\theta$ y la llamaremos $w_0$.\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=1}^{n} {w_i e_i} + w_0 \\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Si $w_0$ tuviera un valor $e_0$ para poder integrarlo dentro del sumatorio se nos quedaría una representación más compacta. Por tanto, vamos a insertar un $e_0$ que siempre tenga el valor $1$. Así:\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=1}^{n} {w_i e_i} + w_0 e_0 \\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Y ahora sí que podemos dejarlo de una forma más compacta:\n",
"\n",
"$$\n",
"f(\\textbf{e}) = \\begin{cases} \\textrm{1, si} \\sum_{i=0}^{n} {w_i e_i} \\geq 0 \\\\ \\\\ 0, \\textrm{en caso contrario} \\end{cases}\n",
"$$\n",
"\n",
"Esta función $f(\\textbf{e})$ devuelve un $1$ si $\\sum_{i=0}^{n} {w_i e_i} \\geq 0$, y un $0$ cuando $\\sum_{i=0}^{n} {w_i e_i} < 0$. Vemos que no es una función derivable en $x=0$, ya nos daremos cuenta de lo que implica esto. Así que vamos a cambiar esos menores, mayores e iguales por nuestra función sigmoide.\n",
"\n",
"$$\n",
"f(\\textbf{e}) = Sigmoide(\\sum_{i=0}^{n} {w_i e_i})\n",
"$$\n",
"\n",
"De nuevo, esta función es prácticamente $0$ cuando $\\sum_{i=0}^{n} {w_i e_i}$ es menor que $0$ y $1$ en caso contrario. Y, además, es derivables en $x=0$.\n",
"\n",
"Si representamos el perceptrón gráficamente para el caso de dos entradas $e_1$ y $e_2$ tenemos:\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Aprendizaje\n",
"\n",
"Veamos el proceso de aprendizaje con un ejemplo muy sencillo. Tenemos un *dataset* formado por tres muestras solamente, donde cada muestra tiene dos propiedades $e_1$ y $e_2$ (además de la correspondiente $e_0$ que siempre es $1$). En la siguiente tabla vemos sus valores y en la figura su representación gráfica. Vemos que hay dos clases, una representada con la etiqueta $1$ y la otra con la etiqueta $0$.\n",
"\n",
"| $e_0$ | $e_1$ | $e_2$ | label |\n",
"|-------|-------|-------|-------|\n",
"| 1 | 1 | 2 | 1 |\n",
"| 1 | 3 | 1 | 1 |\n",
"| 1 | 4 | 5 | 0 |\n",
"\n",
"\n",
"\n",
"\n",
"El objetivo es encontrar los pesos $w_0$, $w_1$ y $w_2$ de una neuronal artificial para que esta pueda clasificar las muestras correctamente. Este proceso va a ser automático e iterativo. Al principio, la neurona se va a inicializar con valores totalmente aleatorios para los pesos y, posteriormente, se verá qué error ha cometido en la clasificación."
]
},
{
"cell_type": "code",
"execution_count": 129,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pesos: [[ 0.39293837]\n",
" [-0.42772133]\n",
" [-0.54629709]]\n",
"Resultado de la neurona: [[0.24464546]\n",
" [0.1920844 ]\n",
" [0.01713359]]\n"
]
}
],
"source": [
"import numpy as np\n",
"\n",
"# Recuerda que la primera columna con unos corresponde a la entrada e0.\n",
"x_data = [[1, 1, 2],\n",
" [1, 3, 1],\n",
" [1, 4, 5]]\n",
"\n",
"x_data = np.matrix(x_data)\n",
"\n",
"y_data = [1, 1, 0]\n",
"\n",
"np.random.seed(seed=123)\n",
"weights = np.random.uniform(low=-1, high=1, size=3).reshape((3,1))\n",
"\n",
"print(\"Pesos:\", weights)\n",
"\n",
"def sigmoid(x):\n",
" return 1.0/(1.0 + np.exp(-x))\n",
"\n",
"output = sigmoid(x_data * weights)\n",
"\n",
"print(\"Resultado de la neurona:\", output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Función de error\n",
"\n",
"¿Cómo podemos medir el error cometido por la neurona? Para el primer punto $(1,2)$ el resultado debería ser $1$, para el punto $(3,1)$, también $1$. Y, para el punto $(4,5)$, el resultado debería ser $0$. Una forma de medir el error global cometido sería: \n",
"\n",
"$$ error = \\sum_{j=1}^{m} ( \\sum_{i=0}^{n} {w_i e_i^j} - label_j)^2$$\n",
"\n",
"Siendo $m$ el número de muestras que tenemos, en este caso, tres. El error será:"
]
},
{
"cell_type": "code",
"execution_count": 138,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.2235816444340613\n"
]
}
],
"source": [
"error = np.sum(np.power((output.reshape(3) - y_data),2))\n",
"\n",
"print(error)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Es muy importante entender ahora que **esta función de error está en función de los pesos, no de las muestras**. Las muestras son estáticas, los pesos, no. Los pesos los iremos variando a medida que descendamos por el gradiente. Vemos también que esta función de error es continua y derivable en todo momento. Por eso nos interesaba prescindir de los anteriores \"mayores\" y \"menores\" y quedarmos con una función como la sigmoide, que es continua y derivable."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Descendiendo por el gradiente\n",
"\n",
"(vídeo)\n",
"\n",
"Esta función de error decrece cuando los resultados de la neurona se acercan a las etiquetas (*labels*) de cada muestra. Por lo tanto, iremos descenciendo por el gradiente hasta intentar alcanzar el mínimo de esta función. Recordamos que el desceso requiere el cálculo de las derivadas parciales de la función. Vamos a calcularlas."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Llamemos $h_{\\vec{w}}$ a la función del perceptrón para un determinado vector (conjunto de pesos) $\\vec{w}$\n",
"\n",
"\n",
"$$h_{\\vec{w}}(\\vec{e}) = Sig(\\sum_{ i=0 }^{ n } w_i e_i)$$\n",
"\n",
"\n",
"donde $n$ es el número de componentes del vector $\\vec{e}$. La salida de $h_{\\vec{w}}$ estará comprendida en el intervalo real $(0,1)$ (debido a la la función sigmoide tiene su rango comprendido en el intervalo $(0,1)$). Definimos el error $J$ en función de un conjunto de pesos $\\vec{w}$ de la siguiente forma:\n",
"\n",
"$$J(\\vec{w}) = \\sum _{ i=1 }^{ m } (h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)})^2$$\n",
"\n",
"Donde $m$ es el número de muestras. \n",
"\n",
"El nuevo conjunto de pesos $\\vec{w}$ será actualizado de la siguiente forma\n",
"\n",
"$$\\vec{w}_{t+1} := \\vec{w}_t - \\gamma \\frac{\\partial{J(\\vec{w})}}{\\partial{\\vec{w}}}$$\n",
"\n",
"La constante $\\gamma$ se define como \"tasa de aprendizaje\". Su derivada parcial con respecto a cada componente de $\\vec{w}$ será:\n",
"\n",
"$$ \n",
"\\frac{\\partial J(\\vec{w})}{\\partial w_j} = \\frac{\\partial}{\\partial w_j}\\sum_{ i=1 }^{ m } (h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)})^2 =$$\n",
"\n",
"$$\n",
"\\sum_{ i=1 }^{ m } 2(h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) \\frac{\\partial}{\\partial w_j} (h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) =\n",
"$$\n",
"\n",
"$$\n",
"\\sum_{ i=1 }^{ m } 2(h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) \\frac{\\partial}{\\partial w_j} Sig(\\textbf{e}^{(i)} \\cdot \\vec{w}) =\n",
"$$\n",
"\n",
"$$\n",
"\\sum_{ i=1 }^{ m } 2(h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) \\; Sig' (\\textbf{e}^{(i)} \\cdot \\vec{w}) \\frac{\\partial}{\\partial w_j} \\textbf{e}^{(i)} \\cdot \\vec{w} =\n",
"$$\n",
"\n",
"$$\n",
"\\sum_{ i=1 }^{ m } 2(h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) \\; Sig' (\\textbf{e}^{(i)} \\cdot \\vec{w}) \\frac{\\partial}{\\partial w_j} \\sum_{k=0}^n e^{(i)}_{k} w_k =\n",
"$$\n",
"\n",
"$$\n",
"2 \\sum_{ i=1 }^{ m } (h_{\\vec{w}}(\\textbf{e}^{(i)}) - l^{(i)}) \\; Sig' (\\textbf{e}^{(i)} \\cdot \\vec{w}) e^{(i)}_{j} \n",
"$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Código\n",
"\n",
"Veamos todo el proceso completo en código. Programaremos la función sigmoide y su derivada e iteraremos un determinado número de veces descendiendo por el gradiente de la función de error."
]
},
{
"cell_type": "code",
"execution_count": 139,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 159,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"features = [[1, 2],\n",
" [3, 1],\n",
" [4, 5]]\n",
"\n",
"labels = [1, 1, 0]"
]
},
{
"cell_type": "code",
"execution_count": 160,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def sigmoid(x):\n",
" return 1.0/(1.0 + np.exp(-x))\n",
"\n",
"\n",
"def sigmoid_derivate(o):\n",
" return o * (1.0 - o)"
]
},
{
"cell_type": "code",
"execution_count": 164,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training...\n",
"[1, 2] -> 1\n",
"0.973080122635592\n",
"-----------------------\n",
"[3, 1] -> 1\n",
"0.9855133303924121\n",
"-----------------------\n",
"[4, 5] -> 0\n",
"0.01880063364262751\n",
"-----------------------\n",
"Pesos: 7.9886492208294735 -0.6272766337348483 -1.8868855557655697\n"
]
}
],
"source": [
"def train(x_data, y_data):\n",
"\n",
" np.random.seed(seed=123)\n",
" w0, w1, w2 = np.random.rand(3)\n",
" lr = 0.1\n",
" epochs = 10000\n",
"\n",
" print(\"Training...\")\n",
"\n",
" for _ in range(epochs):\n",
" \n",
" w0_d = []\n",
" w1_d = []\n",
" w2_d = []\n",
" \n",
" for data, label in zip(x_data, y_data):\n",
" \n",
" e1, e2 = data;\n",
" o = sigmoid(w0*1.0 + w1*e1 + w2*e2)\n",
" aux = 2.*(o - label) * sigmoid_derivate(o)\n",
"\n",
" w0_d.append(aux * 1.0)\n",
" w1_d.append(aux * e1)\n",
" w2_d.append(aux * e2)\n",
" \n",
" w0 = w0 - np.sum(w0_d) * lr\n",
" w1 = w1 - np.sum(w1_d) * lr\n",
" w2 = w2 - np.sum(w2_d) * lr\n",
" \n",
" \n",
" for data, label in zip(x_data, y_data):\n",
" e1, e2 = data;\n",
" print(data, \"->\", label)\n",
" o = sigmoid(w0*1.0 + w1*e1 + w2*e2)\n",
" print(o)\n",
" print(\"-----------------------\")\n",
" \n",
" print(\"Pesos: \", w0, w1, w2)\n",
"\n",
"\n",
"train(features, labels)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"Y, finalmente, vemos cómo la neurona ha ajustado sus pesos hasta llevarlos a unos valores que hacen que sus resultados se aproximen mucho a las etiquetas. Si tomamos los pesos ($w_0 \\approx 8, w_1 \\approx -0.6, w_2 \\approx -2$) y los representamos como la típica recta a la que estamos acostumbrados tendremos:\n",
"\n",
"$$ 8e_0 -0.6e_1 -2e_2 = 0 $$\n",
"\n",
"cambiamos los nombres ($e_1 = x, e_2=y$, recuerda que $e_0=1$)...\n",
"\n",
"$$ 8 -0.6x -2y = 0 $$\n",
"\n",
"y despejamos la $y$.\n",
"\n",
"$$ y = -0.3x + 4 $$\n",
"\n",
"Y si representamos la ecuación gráficamente\n",
"\n",
"\n",
"\n",
"Por tanto, la neurona es capaz de clasificar todas las muestras correctamente. Realmente, la neurona no va a devolver un $1$ si la etiqueta es un $1$, pero sí un valor muy cercano. Así, si la neurona devuelve un valor mayor que $0.5$ decimos que la muestra la etiqueta como $1$. Y la etiqueta como $0$ en caso contrario.\n",
"\n",
"Lo que acabamos de ver se conoce como **aprendizaje supervisado**. Consiste en que un modelo, en este caso una neurona, se adapta (aprende sus pesos) para clasificar un conjunto de datos etiquetados. Ahora, si aparecieran nuevos puntos no etiquetados, la neurona sabría cómo clasificarlos correctamente."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ejercicios\n",
"\n",
"- Una vez tenemos la neurona entrenada, crea un script en Python que calcule el resultado de la clasificación de los puntos $(5,5), (4,2), (0,1)$.\n",
"- ¿Qué ocurre con los puntos que están cerca de la recta? Como, por ejemplo, el punto $(0,4)$\n",
"- Compara los pasos de la derivación parcial junto con el descenso por el gradiente e indetifica cada paso del algoritmo con el código en Python.\n",
"- ¿Qué pasaría si hubiéramos mantenido la función de activación \"mayor que\" en lugar de la función sigmoide? ¿Cómo sería la función de error? Razona la respuesta.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.7 64-bit",
"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.10.7"
},
"vscode": {
"interpreter": {
"hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
}
}
},
"nbformat": 4,
"nbformat_minor": 1
}