{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "mWGJJFnQhflL" }, "source": [ "<style>\n", " .justified {\n", " text-align: justify;\n", " text-justify: inter-word;\n", " }\n", "</style>\n", "\n", "<div class=\"justified\">\n", "\n", "# Elementos de procesamiento de lenguajes naturales, parte II\n", "\n", "Siguiendo nuestra lección anterior, optimizaremos nuestro modelo de red neuronal para crear nombres. Ahora, lo haremos al estilo de Yoshua Bengio {cite}`Bengio2000`." ] }, { "cell_type": "code", "execution_count": 286, "metadata": { "id": "20vcSVe-RXia" }, "outputs": [], "source": [ "import torch\n", "import torch.nn.functional as F\n", "from torch import nn\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import os\n", "import requests\n", "from pathlib import Path" ] }, { "cell_type": "code", "execution_count": 287, "metadata": { "id": "2wiy1BkyVoK6" }, "outputs": [], "source": [ "path = Path('data/')\n", "if not path.is_dir():\n", " path.mkdir(parents=True, exist_ok=True)\n", "\n", "with open(path / 'nombres.txt', 'wb') as f:\n", " request = requests.get('https://github.com/DanteNoguez/CalculusRatiocinator/raw/main/data/nombres.txt')\n", " f.write(request.content)" ] }, { "cell_type": "code", "execution_count": 288, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "I_nJ3lXeWVII", "outputId": "e644144c-8e42-4ce4-c026-054f43f84668" }, "outputs": [ { "data": { "text/plain": [ "['maria', 'rosa', 'jose', 'carmen', 'ana', 'juana', 'antonio', 'elena']" ] }, "execution_count": 288, "metadata": {}, "output_type": "execute_result" } ], "source": [ "nombres = open('data/nombres.txt', 'r').read().splitlines()\n", "nombres[:8]" ] }, { "cell_type": "code", "execution_count": 289, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "okwXbLowXOQl", "outputId": "9d63074e-d763-43cf-8573-b17d4b9bdb67" }, "outputs": [ { "data": { "text/plain": [ "21029" ] }, "execution_count": 289, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(nombres)" ] }, { "cell_type": "code", "execution_count": 290, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "FOuM5vdCXkVd", "outputId": "34adb3ca-d5ab-4714-e0d0-bd7cf79a8d27" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{1: 'a', 2: 'b', 3: 'c', 4: 'd', 5: 'e', 6: 'f', 7: 'g', 8: 'h', 9: 'i', 10: 'j', 11: 'k', 12: 'l', 13: 'm', 14: 'n', 15: 'o', 16: 'p', 17: 'q', 18: 'r', 19: 's', 20: 't', 21: 'u', 22: 'v', 23: 'w', 24: 'x', 25: 'y', 26: 'z', 0: '.'}\n" ] } ], "source": [ "V = sorted(set(''.join(nombres)))\n", "paf = {p:f+1 for f, p in enumerate(V)}\n", "paf['.'] = 0\n", "fap = {f:p for p,f in paf.items()}\n", "print(fap)" ] }, { "cell_type": "markdown", "metadata": { "id": "n2O-rwNLp0YM" }, "source": [ "### Un modelo neuronal probabilístico de lenguaje\n", "\n", "Primero, comenzaremos dividiendo nuestros datos en «bloques». Por ejemplo, en nuestro modelo de bigramas, el bloque contenía un solo carácter, puesto que realizábamos la predicción a partir de una letra; pero podemos aumentar el «contexto» de nuestras predicciones para involucrar más letras al momento de predecir la siguiente. Veamos, por ejemplo, cómo luciría nuestro tratamiento de los datos si hiciéramos bloques de tres caracteres para predecir el siguiente:" ] }, { "cell_type": "code", "execution_count": 291, "metadata": { "id": "9N7EnXZTZgco" }, "outputs": [], "source": [ "def construir_dataset(nombres):\n", " block_size = 3 # longitud del contexto\n", " X, Y = [], []\n", " for n in nombres:\n", " #print(f'nombre: {n}')\n", " contexto = [0] * block_size\n", " for c in n + '.':\n", " ix = paf[c]\n", " X.append(contexto)\n", " Y.append(ix)\n", " #print(''.join(fap[i] for i in contexto), '----> ', fap[ix])\n", " contexto = contexto[1:] + [ix]\n", " \n", " X = torch.tensor(X) # contexto\n", " Y = torch.tensor(Y) # objetivo\n", " return X, Y" ] }, { "cell_type": "markdown", "metadata": { "id": "eH9gpAd8DT69" }, "source": [ "```{margin}\n", "“The training set is a sequence $w_1 · · · w_T$ of words $w_t \\in V$, where the vocabulary $V$ is a large but finite set.”\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "q-tv_YsjwIfh", "outputId": "99a8c5e9-444b-40be-b3d9-e5fae9f15ced" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "nombre: maria\n", "... ----> m\n", "..m ----> a\n", ".ma ----> r\n", "mar ----> i\n", "ari ----> a\n", "ria ----> .\n", "nombre: rosa\n", "... ----> r\n", "..r ----> o\n", ".ro ----> s\n", "ros ----> a\n", "osa ----> .\n", "nombre: jose\n", "... ----> j\n", "..j ----> o\n", ".jo ----> s\n", "jos ----> e\n", "ose ----> .\n" ] }, { "data": { "text/plain": [ "(tensor([[ 0, 0, 0],\n", " [ 0, 0, 13],\n", " [ 0, 13, 1],\n", " [13, 1, 18],\n", " [ 1, 18, 9],\n", " [18, 9, 1],\n", " [ 0, 0, 0],\n", " [ 0, 0, 18],\n", " [ 0, 18, 15],\n", " [18, 15, 19],\n", " [15, 19, 1],\n", " [ 0, 0, 0],\n", " [ 0, 0, 10],\n", " [ 0, 10, 15],\n", " [10, 15, 19],\n", " [15, 19, 5]]),\n", " tensor([13, 1, 18, 9, 1, 0, 18, 15, 19, 1, 0, 10, 15, 19, 5, 0]))" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "construir_dataset(nombres[:3])" ] }, { "cell_type": "markdown", "metadata": { "id": "dO_nn97fssxp" }, "source": [ "Nuestra intuición detrás de esta aproximación es que el lenguaje funciona mejor con contexto: así como el sentido de un concepto se entiende mejor en contexto, también los caracteres se pueden predecir más razonablemente dado un contexto más amplio. \n", "\n", "Similar a como habíamos hecho anteriormente, construimos una matriz `X` para contener el contexto como entrada y un vector `Y` que contiene el objetivo (es decir, carácter) que debe seguir a cada respectivo contexto. Como se puede apreciar, solamente estamos construyendo `X` e `Y` con sus respectivos índices del vocabulario.\n", "\n", "Dado que solo tomamos 3 nombres como ejemplo, nuestros datos únicamente contienen 16 contextos o *inputs* y 16 objetivos o *outputs*:" ] }, { "cell_type": "code", "execution_count": 292, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UnQbjfrAvkdi", "outputId": "71f74b62-77a0-4efe-a8dc-4c25d272b68d" }, "outputs": [ { "data": { "text/plain": [ "(torch.Size([16, 3]), torch.Size([16]))" ] }, "execution_count": 292, "metadata": {}, "output_type": "execute_result" } ], "source": [ "X, Y = construir_dataset(nombres[:3])\n", "X.shape, Y.shape" ] }, { "cell_type": "markdown", "metadata": { "id": "0mwDZ-E5wpGu" }, "source": [ "Ahora estamos listos para hacer el *embedding*. Mientras que en el *paper* los datos se incrustan en una tabla de consulta de 30 dimensiones (o *features*) para un vocabulario de 17,000 palabras, nosotros —que únicamente tenemos un vocabulario de 27 caracteres— podemos aproximarnos a la incrustación con algo más pequeño, como una incrustación de dos dimensiones ($m = 2$)." ] }, { "cell_type": "code", "execution_count": 293, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tGlTifYhzqv7", "outputId": "d83d47d5-b28d-481a-b689-dadfc4c44d95" }, "outputs": [ { "data": { "text/plain": [ "tensor([[ 1.1950, -1.6664],\n", " [ 1.0195, -0.4103],\n", " [-0.2143, 1.6073],\n", " [ 1.3569, 1.3905],\n", " [ 0.7943, -0.9118],\n", " [ 0.4180, 0.3789],\n", " [-1.1457, 0.4822],\n", " [-0.5390, -1.8550],\n", " [ 0.1015, 1.0336],\n", " [ 0.1822, 1.7465],\n", " [ 1.0465, 0.5108],\n", " [-0.3649, 1.3349],\n", " [-1.6279, -1.2823],\n", " [ 0.3673, -1.0746],\n", " [ 1.4023, -0.8277],\n", " [ 1.2148, -0.7885],\n", " [ 0.3532, -0.8317],\n", " [ 0.4932, 0.6096],\n", " [-1.4151, 0.7786],\n", " [ 3.8467, -1.3610],\n", " [ 1.9704, -0.0231],\n", " [-0.5216, -0.7434],\n", " [ 0.9543, -0.3574],\n", " [ 1.0646, -0.6760],\n", " [-0.2655, -0.1184],\n", " [ 2.4975, 0.1847],\n", " [-2.5699, 0.6519]])" ] }, "execution_count": 293, "metadata": {}, "output_type": "execute_result" } ], "source": [ "C = torch.randn((27, 2)) # tabla de consulta\n", "C" ] }, { "cell_type": "markdown", "metadata": { "id": "KXQ0eItb0cAM" }, "source": [ "Como vemos, cada *token* o elemento del volcabulario se incrustará en dos dimensiones, es decir, tendrá dos números asociados. Ahora, tomaremos un atajo que nos permitirá ser más eficientes con la codificación y la primera capa de la red neuronal. Anteriormente, habíamos hecho un *one-hot encoding* para luego pasarlo por una capa `W`; pero, bien visto, estos dos pasos pueden ser omitidos porque consiguen el mismo resultado que la incrustación en nuestra tabla `C`. \n", "\n", "Primero: la codificación *one-hot*, si fuéramos a multiplicarla por `C`, anularía todos los valores de `C` al multiplicarlos por 0 y conservaría una fila correspondiente a la de la multiplicación por 1. Ergo, podemos omitir la multiplicación y hacer una indexación para asociar directamente cada carácter con cada fila que un vector *one-hot* multiplicaría por 1. Dicho esto, podemos concebir a `C` como un equivalente de la capa `W`, puesto que consiste de valores aleatorios que asignan un número a cada carácter y luego pueden optimizarse con propagación hacia atrás.\n", "\n", "Dicho esto, la indexación (*embedding*) será bastante simple:\n", "\n", "```{margin}\n", "“A mapping $C$ from any element $i$ of $V$ to a real vector $C(i) \\in \\mathbb{R}^m$. It represents the distributed feature vectors associated with each word in the vocabulary. In practice, C is represented by a $\\left|V\\right| \\times m$ matrix of free parameters”.\n", "```" ] }, { "cell_type": "code", "execution_count": 295, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 37 }, "id": "AWRbIvaQAJbc", "outputId": "b4fda9e0-b821-41e0-cf39-a833e5b0f306" }, "outputs": [ { "data": { "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" }, "text/plain": [ "'Segunda fila de C: [ 1.0194591 -0.41030166] | Tercer valor del tercer bloque incrustado (es decir, letra a): [ 1.0194591 -0.41030166]'" ] }, "execution_count": 295, "metadata": {}, "output_type": "execute_result" } ], "source": [ "emb = C[X] # embedding\n", "\n", "f'Segunda fila de C: {C[1].numpy()} | Tercer valor del tercer bloque incrustado (es decir, letra a): {emb[2][2].numpy()}'" ] }, { "cell_type": "code", "execution_count": 296, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2wVT6iiNIale", "outputId": "6018f44f-0831-4c6b-975b-3af72e68023a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Dimensiones del embedding: torch.Size([16, 3, 2])\n", "Tres bloques del embedding, correspondientes a «..m», «.ma» y «mar»: tensor([[[ 1.1950, -1.6664],\n", " [ 1.1950, -1.6664],\n", " [ 0.3673, -1.0746]],\n", "\n", " [[ 1.1950, -1.6664],\n", " [ 0.3673, -1.0746],\n", " [ 1.0195, -0.4103]],\n", "\n", " [[ 0.3673, -1.0746],\n", " [ 1.0195, -0.4103],\n", " [-1.4151, 0.7786]]])\n" ] } ], "source": [ "print('Dimensiones del embedding: ', emb.shape) \n", "print('Tres bloques del embedding, correspondientes a «..m», «.ma» y «mar»:', emb[1:4])" ] }, { "cell_type": "markdown", "metadata": { "id": "Mvh5GMkLIWsk" }, "source": [ "Ahora, echemos un vistazo a la arquitectura que deseamos lograr:\n", "\n", "```{figure} ../../img/bengio2003.png\n", "---\n", "width: 70%\n", "name: bengio2003\n", "---\n", "Arquitectura neuronal $f\\left(i, w_{t-1}, \\cdots, w_{t-n+1}\\right)=g\\left(i, C\\left(w_{t-1}\\right), \\cdots, C\\left(w_{t-n+1}\\right)\\right)$ donde $g$ es la red neuronal y $C(i)$ es el $i$-ésimo vector de cada palabra. En nuestro caso, utilizamos bloques de tres letras (`contexto`, vector `X`) en lugar de palabras ($w$).\n", "```" ] }, { "cell_type": "markdown", "metadata": { "id": "Ln__Tj3MQz1p" }, "source": [ "Como vemos, tenemos casi terminado el inicio y únicamente nos falta concatenar entre sí los bloques del *embedding*, puesto que juntos atravesarán la misma capa de neuronas.\n", "```{margin}\n", "“\\[...] $x$ is the word features layer activation vector, which is the concatenation of the input word features from the matrix $C$: $x = C\\left(w_{t-1}\\right), \\left(w_{t-2}\\right) \\cdots, C\\left(w_{t-n+1}\\right)$.”\n", "```\n", "Para conseguirlo, podemos utilizar distintos métodos con PyTorch:" ] }, { "cell_type": "code", "execution_count": 297, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "llevzXqoY8kG", "outputId": "1269073c-3523-4f13-c6c9-b40b8d7fc82d" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Ahora, en lugar de estar contenidos en bloques de tres filas: \n", "tensor([[ 1.1950, -1.6664],\n", " [ 1.1950, -1.6664],\n", " [ 1.1950, -1.6664]])\n", "Estarían contenidos en bloques de una fila (seis columnas):\n", "tensor([ 1.1950, -1.6664, 1.1950, -1.6664, 1.1950, -1.6664])\n" ] } ], "source": [ "metodo1 = torch.cat([emb[:, 0, :], emb[:, 1, :], emb[:, 2, :]], 1)\n", "print(f\"\"\"Ahora, en lugar de estar contenidos en bloques de tres filas: \n", "{emb[0]}\n", "Estarían contenidos en bloques de una fila (seis columnas):\n", "{metodo1[0]}\"\"\")" ] }, { "cell_type": "code", "execution_count": 298, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 37 }, "id": "NoRTuW6YaZox", "outputId": "b47cc42a-1797-4072-cd9f-3bb540a4f9ab" }, "outputs": [ { "data": { "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" }, "text/plain": [ "'Aunque también es equivalente: tensor([ 1.1950, -1.6664, 1.1950, -1.6664, 1.1950, -1.6664])'" ] }, "execution_count": 298, "metadata": {}, "output_type": "execute_result" } ], "source": [ "metodo2 = torch.cat(torch.unbind(emb, 1), 1)\n", "f'Aunque también es equivalente: {metodo2[0]}'" ] }, { "cell_type": "markdown", "metadata": { "id": "BBMagWewbQ2-" }, "source": [ "Pero el método más eficiente[^1] y simple es `view`. Como primer argumento, colocaremos `-1` para que de esta forma PyTorch infiera el tamaño de la dimensión 0 que debería tener el tensor (sería, pues, equivalente a colocar `emb[0].shape`), y como segundo argumento `6` porque queremos que el tensor tenga las 6 columnas correspondientes a un bloque de tres *tokens*:" ] }, { "cell_type": "code", "execution_count": 299, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "PBCWB06samzE", "outputId": "67e761d7-b78b-4694-afc1-035b2bc5a023" }, "outputs": [ { "data": { "text/plain": [ "(tensor([ 1.1950, -1.6664, 1.1950, -1.6664, 1.1950, -1.6664]),\n", " torch.Size([16, 6]))" ] }, "execution_count": 299, "metadata": {}, "output_type": "execute_result" } ], "source": [ "emb.view(-1, 6)[0], emb.view(-1, 6).shape # esta es la variable x del paper" ] }, { "cell_type": "markdown", "metadata": { "id": "aQkp7vTli1RI" }, "source": [ "Ahora ya tenemos lo suficiente para definir más variables:\n", "\n", "> “Let $h$ be the number of hidden units[^2], and $m$ the number of features associated with each word. When no direct connections from word features to outputs are desired, the matrix $W$ is set to 0 . The free parameters of the model are the output biases $b$ (with $|V|$ elements), the hidden layer biases $d$ (with $h$ elements), the hidden-to-output weights $U$ (a $|V| \\times h$ matrix), the word features to output weights $W$ (a $|V| \\times(n-1) m$ matrix), the hidden layer weights $H$ (a $h \\times(n-1) m$ matrix), and the word features $C$(a $|V| \\times m$ matrix $)$: $\\theta=(b, d, W, U, H, C)$”." ] }, { "cell_type": "code", "execution_count": 320, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 37 }, "id": "wGjpIQCJTQHT", "outputId": "48fac1c7-c81f-4ccd-8323-cec4e7a8dcc3" }, "outputs": [ { "data": { "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" }, "text/plain": [ "'Número de features (m), es decir, número de componentes de cada bloque: 2 | Número de elementos por bloque «(n-1)m»: 6 | Elementos de |V|: 27'" ] }, "execution_count": 320, "metadata": {}, "output_type": "execute_result" } ], "source": [ "f'Número de features (m), es decir, número de componentes de cada bloque: {C.size(dim=1)} | Número de elementos por bloque «(n-1)m»: {emb.view(-1, 6).size(dim=1)} | Elementos de |V|: {len(V)+1}'" ] }, { "cell_type": "markdown", "metadata": { "id": "YvTldl3y1cnp" }, "source": [ "El número de parámetros ($h$) depende del problema a tratar: generalmente, a mayor cantidad de datos, es mejor mayor cantidad de parámetros. En general, cuestiones técnicas como esta dependen de la evaluación experimental que hagamos de nuestro modelo: tras pruebas con diferentes números de parámetros, podemos elegir la que más efectiva y eficiente sea. En el *paper*, por ejemplo, probaron con 50 y 100 *hidden units*. Dado que por el momento solo estamos ejemplificando con tres nombres, $h = 50$ unidades serán suficientes.\n", "\n", "Por otra parte, fijar un valor de 0 a $W$ es igual que no utilizarla, de manera que omitiremos su definición porque no la necesitamos." ] }, { "cell_type": "code", "execution_count": 301, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vn986Q_bRgr2", "outputId": "7905e35e-f4f1-42b7-dc7e-72037e798201" }, "outputs": [ { "data": { "text/plain": [ "torch.Size([16, 50])" ] }, "execution_count": 301, "metadata": {}, "output_type": "execute_result" } ], "source": [ "h = 50\n", "d = torch.randn((h))\n", "H = torch.randn((6, h))\n", "\n", "a = torch.tanh(emb.view(-1, 6) @ H + d)\n", "a.shape" ] }, { "cell_type": "markdown", "metadata": { "id": "CiZ4E0jsVs0F" }, "source": [ "Ahora, la capa oculta previa al *output*, es decir, la *hidden-to-output layer* se compone de $U$ y $b$, mientras que el resultado de esta capa son —el lector lo recordará— lo que llamamos *logits*, es decir, el resultado de la última capa de la red neuronal. Estos *logits* serán convertidos en probabilidades y, con ello, tendremos el *output* de toda la red. " ] }, { "cell_type": "code", "execution_count": 302, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9ctI-j3zV2K0", "outputId": "87cc97c0-dc98-4d1d-c755-982d9782f021" }, "outputs": [ { "data": { "text/plain": [ "torch.Size([16, 27])" ] }, "execution_count": 302, "metadata": {}, "output_type": "execute_result" } ], "source": [ "U = torch.randn((h, 27))\n", "b = torch.randn(27)\n", "\n", "logits = a @ U + b\n", "logits.shape" ] }, { "cell_type": "markdown", "metadata": { "id": "vf6uqdGxf07U" }, "source": [ "Ahora, para convertir esto en probabilidades, haremos lo mismo que en la lección pasada:" ] }, { "cell_type": "code", "execution_count": 303, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2UZCsFTOf4vc", "outputId": "fe61659c-7598-4cea-bc95-d09121fa3d77" }, "outputs": [ { "data": { "text/plain": [ "tensor(1.0000)" ] }, "execution_count": 303, "metadata": {}, "output_type": "execute_result" } ], "source": [ "counts = logits.exp()\n", "prob = counts / counts.sum(1, keepdims=True)\n", "prob[0].sum()" ] }, { "cell_type": "markdown", "metadata": { "id": "FOU_daFOf5_c" }, "source": [ "Para crear nuestra función de pérdida, necesitamos seleccionar nuestros objetivos (con base en el índice que nos dio `Y`):" ] }, { "cell_type": "code", "execution_count": 304, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "XesO8cnLgFsB", "outputId": "8dec22cf-4eba-4aa2-a488-2fbad6b7c2ea" }, "outputs": [ { "data": { "text/plain": [ "tensor([2.7201e-07, 1.4083e-02, 5.6987e-07, 3.5629e-07, 9.4318e-10, 4.6116e-08,\n", " 2.3890e-10, 9.0728e-04, 2.6708e-06, 2.7046e-05, 1.8723e-08, 2.6864e-07,\n", " 7.6290e-06, 8.3047e-06, 1.6912e-09, 1.4596e-08])" ] }, "execution_count": 304, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prob[torch.arange(16), Y]" ] }, { "cell_type": "markdown", "metadata": { "id": "vAogwu906LYo" }, "source": [ "Ejemplifiquemos: si nuestro *input* es «mar» (`emb[3]`), nuestra probabilidad debe ser alta para que el número del *embedding* que represente la letra «i» de «María» sea un *output*:" ] }, { "cell_type": "code", "execution_count": 305, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gzP5D6cn7C6-", "outputId": "7f8d6938-5562-4b40-d502-8bb24394500b" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "En un inicio, cada letra de «mar» correspondía a su índice en el vocabulario: tensor([13, 1, 18])\n", "Cuando pasamos estos índices a una matriz para que fueran representados por dos números, obtuvimos: tensor([[ 0.3673, -1.0746],\n", " [ 1.0195, -0.4103],\n", " [-1.4151, 0.7786]])\n", "Al mismo tiempo, la letra «i» fue guardada como objetivo en Y, siendo su índice: 9\n", "De manera que la probabilidad de nuestra red neuronal debe ser alta para el número que representa la letra «i» en el embedding: -3.5628877981253027e-07\n" ] } ], "source": [ "print(f\"\"\"En un inicio, cada letra de «mar» correspondía a su índice en el vocabulario: {X[3]}\n", "Cuando pasamos estos índices a una matriz para que fueran representados por dos números, obtuvimos: {emb[3]}\n", "Al mismo tiempo, la letra «i» fue guardada como objetivo en Y, siendo su índice: {Y[3]}\n", "De manera que la probabilidad de nuestra red neuronal debe ser alta para el número que representa la letra «i» en el embedding: {-prob[3, Y[3]]}\"\"\")" ] }, { "cell_type": "markdown", "metadata": { "id": "8_8s5YhMgrDD" }, "source": [ "Dado que no hemos entrenado la red, la probabilidad anterior es muy baja. Para poder entrenarla, formularemos la función de pérdida mediante el promedio del logaritmo natural de las probabilidades que la red neuronal asigna a cada objetivo, y finalmente hacemos de este un número positivo al multiplicarlo por $-1$, igual que en la lección pasada:" ] }, { "cell_type": "code", "execution_count": 306, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "t-V-YLiMg9Qi", "outputId": "d711c17f-9ba6-408c-8191-0661f1b86f43" }, "outputs": [ { "data": { "text/plain": [ "tensor(14.5898)" ] }, "execution_count": 306, "metadata": {}, "output_type": "execute_result" } ], "source": [ "perdida = -prob[torch.arange(16), Y].log().mean()\n", "perdida" ] }, { "cell_type": "markdown", "metadata": { "id": "MMDFftuZiZuJ" }, "source": [ "Usando PyTorch, podemos simplificar todo este proceso mediante el uso de `cross_entropy`:" ] }, { "cell_type": "code", "execution_count": 307, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "gHcV_mJPigMl", "outputId": "55c3a084-9acc-4439-b80c-6c34ee82ffcb" }, "outputs": [ { "data": { "text/plain": [ "tensor(14.5898)" ] }, "execution_count": 307, "metadata": {}, "output_type": "execute_result" } ], "source": [ "F.cross_entropy(logits, Y)" ] }, { "cell_type": "markdown", "metadata": { "id": "YccvWoeSutKG" }, "source": [ "Finalmente, agrupamos los parámetros —$\\theta$— en una variable:" ] }, { "cell_type": "code", "execution_count": 308, "metadata": { "id": "SozrJQzlu6Le" }, "outputs": [], "source": [ "parametros = [b, d, U, H, C]" ] }, { "cell_type": "markdown", "metadata": { "id": "5sMEdaOk9Syz" }, "source": [ "Indicamos a PyTorch que requeriremos gradientes para el entrenamiento de nuestros parámetros:" ] }, { "cell_type": "code", "execution_count": 309, "metadata": { "id": "zr_TdESFvm1p" }, "outputs": [], "source": [ "for p in parametros:\n", " p.requires_grad = True" ] }, { "cell_type": "markdown", "metadata": { "id": "jdrFZM0m9a7P" }, "source": [ "Finalmente, podemos entrenar la red:" ] }, { "cell_type": "code", "execution_count": 310, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "07Gi5EJpweFt", "outputId": "406185a2-86e6-417f-916d-c540323a7c99" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.25268855690956116\n" ] } ], "source": [ "for i in range(100):\n", " # paso hacia delante\n", " emb = C[X]\n", " h = torch.tanh(emb.view(-1, 6) @ H + d)\n", " logits = h @ U + b\n", " perdida = F.cross_entropy(logits, Y)\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", " \n", " for p in parametros:\n", " p.data += -0.1 * p.grad\n", "\n", "print(perdida.item())" ] }, { "cell_type": "markdown", "metadata": { "id": "SrSZQ3lkEQLL" }, "source": [ "Bien, hemos conseguido un resultado decente en nuestro ejemplo. Ahora es momento de entrenar nuestra red con todos los nombres a la vez. \n", "\n", "### Optimización de modelos neuronales: conjuntos de datos, sobreajuste, tasa de aprendizaje y lotes\n", "\n", "Primero hacen falta algunos ajustes. Por una parte, evitaremos que nuestro modelo «memorice» o «sobreajuste» cada *output* correspondiente a cada *input* (a este sobreajuste se le llama *overfitting*), puesto que queremos nombres nuevos y originales y no una regurgitación de los que estamos utilizando. Para ello, se han desarrollado un número de técnicas en *deep learning*; pero de momento, utilizaremos una de las más elementales, y consiste en separar nuestros datos en tres partes: entrenamiento, validación y prueba. Entrenaremos nuestro modelo con una cantidad razonable de nombres (el 80 % de ellos), definimos nuestros parámetros —en rigor, hiperparámetros[^3]— con los datos de validación (10 % de nuestros nombres) y verificamos que el modelo sepa generalizar su aprendizaje con los datos destinados a las pruebas (último 10 % de nombres)." ] }, { "cell_type": "code", "execution_count": 402, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "N0zzO1IwdW5D", "outputId": "534a8dc0-99c5-4357-9f7e-bb5b9141e37f" }, "outputs": [ { "data": { "text/plain": [ "(torch.Size([165469, 3]),\n", " torch.Size([132418, 3]),\n", " torch.Size([16559, 3]),\n", " torch.Size([16492, 3]))" ] }, "execution_count": 402, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import random\n", "random.shuffle(nombres)\n", "n1 = int(0.8*len(nombres))\n", "n2 = int(0.9*len(nombres))\n", "\n", "Xtr, Ytr = construir_dataset(nombres[:n1])\n", "Xdev, Ydev = construir_dataset(nombres[n1:n2])\n", "Xte, Yte = construir_dataset(nombres[n2:])\n", "X, Y = construir_dataset(nombres)\n", "\n", "X.shape, Xtr.shape, Xdev.shape, Xte.shape" ] }, { "cell_type": "markdown", "metadata": { "id": "HVvkgagZHBlO" }, "source": [ "Ahora definimos nuestros hiperparámetros, aunque esta vez utilizaremos $h = 100$:" ] }, { "cell_type": "code", "execution_count": 409, "metadata": { "id": "I0uqOeClM0bH" }, "outputs": [], "source": [ "C = torch.randn(27, 2)\n", "emb = C[Xtr]\n", "h = 100\n", "H = torch.randn((6, h))\n", "d = torch.randn(h)\n", "\n", "a = torch.tanh(emb.view(-1, 6) @ H + d)\n", "\n", "U = torch.randn(h, 27)\n", "b = torch.randn(27)\n", "\n", "logits = a @ U + b\n", "\n", "parametros = [C, H, d, U, b]" ] }, { "cell_type": "markdown", "metadata": { "id": "-7YQCEE9KIGl" }, "source": [ "Nuestro número total de parámetros resultantes es:" ] }, { "cell_type": "code", "execution_count": 404, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "MpijkcRINOUM", "outputId": "dc8b2199-dc89-4b76-d07a-37704e2303c6" }, "outputs": [ { "data": { "text/plain": [ "3481" ] }, "execution_count": 404, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sum(p.numel() for p in parametros)" ] }, { "cell_type": "markdown", "metadata": { "id": "OyC-Cw1ILP6c" }, "source": [ "Por otro lado, podemos acelerar el entrenamiento de la red neuronal de la siguiente forma: en cada repetición del *loop* de aprendizaje, podemos seleccionar un «lote» (*batch*) o segmento de los datos para únicamente llevar a cabo el proceso de aprendizaje en ese mismo lote, de manera que el modelo no se entrene tomando siempre en consideración todos los datos a la vez, sino datos —en este caso, nombres— aleatorios en cada iteración. Aunque esto implique sacrificar en cierta medida el desempeño de la red neuronal, sin embargo ese sacrificio se compensa con la rapidez que podemos generar a cambio.\n", "\n", "Para crear estos lotes, podemos utilizar el siguiente código, el cual generará índices correspondientes a 32 nombres aleatorios de entre aquellos que pertenezcan a nuestros datos:" ] }, { "cell_type": "code", "execution_count": 391, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 37 }, "id": "8o2LeL14VhKw", "outputId": "74b4e7e5-2389-40ee-f38b-391f272ac2cd" }, "outputs": [ { "data": { "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" }, "text/plain": [ "'Tres ejemplos: [123401 87687 70020]'" ] }, "execution_count": 391, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ix = torch.randint(0, X.shape[0], (32,))\n", "f'Tres ejemplos: {ix[:3].numpy()}'" ] }, { "cell_type": "markdown", "metadata": { "id": "lIJn4uOiKLIm" }, "source": [ "Finalmente, nos falta tratar los detalles técnicos detrás de la *learning rate* («tasa de aprendizaje») del modelo. Recordemos que, cuando modificamos los parámetros en la dirección del gradiente, generalmente atenuamos al gradiente multiplicándolo por un número pequeño para así no excedernos en el ajuste, consiguiendo una pérdida cercana a 0 sin sobrepasarla.\n", "\n", "Para determinar una *learning rate* razonable, tenemos que averiguar empíricamente los «límites» de la misma:" ] }, { "cell_type": "code", "execution_count": 410, "metadata": { "id": "srB_AEmtMTQB" }, "outputs": [], "source": [ "for p in parametros:\n", " p.requires_grad = True" ] }, { "cell_type": "code", "execution_count": 393, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ur8J9v32ZGRO", "outputId": "f4be0970-a36a-4116-febb-778f0ccca090" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "22.080612182617188\n", "18.37497329711914\n", "15.564812660217285\n", "15.621807098388672\n", "17.045852661132812\n", "18.507583618164062\n", "21.449209213256836\n", "21.56830596923828\n", "24.576093673706055\n", "15.103194236755371\n" ] } ], "source": [ "for _ in range(10):\n", " #minibatch («minilote»)\n", " ix = torch.randint(0, Xdev.shape[0], (32,))\n", "\n", " # propagación hacia delante\n", " emb = C[Xdev[ix]]\n", " a = torch.tanh(emb.view(-1, 6) @ H + d)\n", " logits = a @ U + b\n", " perdida = F.cross_entropy(logits, Ydev[ix])\n", " print(perdida.item())\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", "\n", " # actualización\n", " lr = -1 # intentaremos obtener el límite superior\n", " for p in parametros:\n", " p.data += lr * p.grad" ] }, { "cell_type": "markdown", "metadata": { "id": "VxEDRaeSCuy4" }, "source": [ "Como vemos, la pérdida disminuye con una tasa de aprendizaje de -1; pero al mismo tiempo indica que esta tasa es muy alta porque no es nada estable y se balancea hacia arriba y abajo. Este puede ser nuestro límite superior, busquemos el inferior:" ] }, { "cell_type": "code", "execution_count": 394, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Dmlss8gdDGde", "outputId": "2adc40ed-146f-4357-c612-961d1384db4a" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "16.730724334716797\n", "19.113916397094727\n", "16.265771865844727\n", "16.343080520629883\n", "19.638389587402344\n", "17.050613403320312\n", "16.68904685974121\n", "23.818950653076172\n", "16.777978897094727\n", "20.498703002929688\n" ] } ], "source": [ "for _ in range(10):\n", " #minibatch («minilote»)\n", " ix = torch.randint(0, Xdev.shape[0], (32,))\n", "\n", " # propagación hacia delante\n", " emb = C[Xdev[ix]]\n", " a = torch.tanh(emb.view(-1, 6) @ H + d)\n", " logits = a @ U + b\n", " perdida = F.cross_entropy(logits, Ydev[ix])\n", " print(perdida.item())\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", "\n", " # actualización\n", " lr = -0.001 # intentaremos obtener el límite inferior\n", " for p in parametros:\n", " p.data += lr * p.grad" ] }, { "cell_type": "markdown", "metadata": { "id": "q1-XFmj8D-Rh" }, "source": [ "De igual forma, esta pérdida es subóptima porque es demasiado lenta para eficientar el aprendizaje, de manera que este puede ser nuestro límite inferior. Ahora, haremos una prueba de entrenamiento con tasas de aprendizaje que se encuentren dentro de estos límites; pero, para que nuestra tasa de aprendizaje no cambie lineal sino exponencialmente, podemos utilizar nuestros números como potencias de 10. Es decir, en teoría, nuestras tasas de aprendizaje serían mil números del 0.001 al 1:" ] }, { "cell_type": "code", "execution_count": 395, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 265 }, "id": "TFjsRBJDFHN9", "outputId": "93f0f1e9-5cd6-4201-86f5-234832a4f0ed" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "lr = torch.linspace(0.001, 1, 1000)\n", "\n", "plt.plot(lr.numpy());" ] }, { "cell_type": "markdown", "metadata": { "id": "e4RzxflMGRlv" }, "source": [ "Pero $10^{-3} = 0.001$ y $10{^0} = 1$, de manera que utilizar este truco nos ayuda a obtener un cambio exponencial en nuestras tasas de aprendizaje. Ahora, nuestros 1000 números serán exponentes de 10 y lucirán así:" ] }, { "cell_type": "code", "execution_count": 396, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 265 }, "id": "BBz2E7b9R019", "outputId": "78a917e1-8925-4b1f-da75-837c6c5bdb6e" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "lre = torch.linspace(-3, 0, 1000)\n", "lrs = 10**lre\n", "\n", "plt.plot(lrs.numpy());" ] }, { "cell_type": "markdown", "metadata": { "id": "hiOSxckvHGGr" }, "source": [ "Ahora, para elegir el número correcto en este rango, podemos registrar nuestras pérdidas correspondientes a cada tasa durante el entrenamiento:" ] }, { "cell_type": "code", "execution_count": 406, "metadata": { "id": "FZvwJ9N_KNN0" }, "outputs": [], "source": [ "lr_i = []\n", "perdidas_i = []\n", "\n", "for i in range(1000):\n", " #minibatch («minilote»)\n", " ix = torch.randint(0, Xdev.shape[0], (32,))\n", "\n", " # propagación hacia delante\n", " emb = C[Xdev[ix]]\n", " a = torch.tanh(emb.view(-1, 6) @ H + d)\n", " logits = a @ U + b\n", " perdida = F.cross_entropy(logits, Ydev[ix])\n", " #print(perdida.item())\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", "\n", " # actualización\n", " lr = lrs[i] # intentaremos obtener el límite inferior\n", " for p in parametros:\n", " p.data += -lr * p.grad\n", "\n", " # registrar estadísticas\n", " lr_i.append(lre[i])\n", " perdidas_i.append(perdida.item())" ] }, { "cell_type": "code", "execution_count": 407, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 265 }, "id": "NG28-zOEKOAS", "outputId": "4ff177dc-1a85-4927-ebf7-cac0e22f8baa" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.plot(lr_i, perdidas_i);" ] }, { "cell_type": "markdown", "metadata": { "id": "WfGI0APWPD1F" }, "source": [ "En el gráfico podemos apreciar que nuestra tasa de aprendizaje óptima —es decir, la que mejor minimiza la pérdida— se encuentra alrededor de $10^{-1}$ (es decir, $0.1$), de manera que podemos utilizar esta tasa con mayor confianza para entrenar todos nuestros parámetros con los datos de `Xtr`." ] }, { "cell_type": "code", "execution_count": 411, "metadata": { "id": "qUhPiHU9SIDF" }, "outputs": [], "source": [ "paso_i = []\n", "perdidas_i = []\n", "\n", "for i in range(1000):\n", " #minibatch («minilote»)\n", " ix = torch.randint(0, Xtr.shape[0], (32,))\n", "\n", " # propagación hacia delante\n", " emb = C[Xtr[ix]]\n", " a = torch.tanh(emb.view(-1, 6) @ H + d)\n", " logits = a @ U + b\n", " perdida = F.cross_entropy(logits, Ytr[ix])\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", "\n", " # actualización\n", " lr = 0.1 # nueva tasa\n", " for p in parametros:\n", " p.data += -lr * p.grad\n", "\n", " # registrar estadísticas\n", " paso_i.append(i)\n", " perdidas_i.append(perdida.item())" ] }, { "cell_type": "code", "execution_count": 413, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 265 }, "id": "GW2pHmdef7qi", "outputId": "cb4b2fdc-dc7a-4c69-dfb6-fc6c99cba399" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 432x288 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.plot(paso_i, perdidas_i);" ] }, { "cell_type": "markdown", "metadata": { "id": "gTxEGBDQSwo7" }, "source": [ "Debido a que entrenamos nuestros parámetros en lotes, la función de pérdida tiene algo de ruido cuando se acerca a su valor mínimo. Comprobemos ahora la pérdida que tenemos en `Xdev`:" ] }, { "cell_type": "code", "execution_count": 414, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "SlcEcHzPSu9q", "outputId": "97eac106-8da5-4a67-eb1f-84541b52068c" }, "outputs": [ { "data": { "text/plain": [ "tensor(2.4327, grad_fn=<NllLossBackward0>)" ] }, "execution_count": 414, "metadata": {}, "output_type": "execute_result" } ], "source": [ "emb = C[Xdev]\n", "a = torch.tanh(emb.view(-1, 6) @ H + d)\n", "logits = a @ U + b\n", "perdida = F.cross_entropy(logits, Ydev)\n", "perdida" ] }, { "cell_type": "markdown", "metadata": { "id": "o_NQDww9TZ1c" }, "source": [ "Finalmente, podemos visualizar cómo luce nuestro *embedding* ahora que ha sido entrenado:" ] }, { "cell_type": "code", "execution_count": 428, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 483 }, "id": "8EANrk6BTrlM", "outputId": "7d03f486-7325-4402-cd02-6405698f38d6" }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "<Figure size 576x576 with 1 Axes>" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.figure(figsize=(8,8))\n", "plt.scatter(C[:,0].data, C[:,1].data, s=200)\n", "for i in range(C.shape[0]):\n", " plt.text(C[i,0].item(), C[i,1].item(), fap[i], ha='center', va='center', color='white')\n", "plt.grid('minor');" ] }, { "cell_type": "markdown", "metadata": { "id": "PTpjDaqmVrBB" }, "source": [ "Como vemos, las vocales —junto con la «y», aunque sin la «i»— aparecen agrupadas entre sí: esto quiere decir que nuestro modelo las interpreta como letras similares. Lo mismo aplica para las consonantes.\n", "\n", "Hasta el momento, no hemos conseguido mejorar nuestro modelo anterior. En la práctica, la optimización de modelos se hace con un número de pruebas que contengan hiperparámetros distintos. Podemos, por ejemplo, aumentar nuestros parámetros, nuestro *embedding*, nuestras capas, probar nuevas tasas de aprendizaje, entrenar el modelo por más tiempo, etcétera. Intentemos hacer un poco de todo para finalizar esta lección:\n", "\n", "### Ensayo de un modelo con hiperparámetros óptimos" ] }, { "cell_type": "code", "execution_count": 445, "metadata": { "id": "mJea2Woegj5H" }, "outputs": [], "source": [ "random.shuffle(nombres)\n", "n1 = int(0.8*len(nombres))\n", "n2 = int(0.9*len(nombres))\n", "\n", "Xtr, Ytr = construir_dataset(nombres[:n1])\n", "Xdev, Ydev = construir_dataset(nombres[n1:n2])\n", "Xte, Yte = construir_dataset(nombres[n2:])\n", "X, Y = construir_dataset(nombres)\n", "\n", "C = torch.randn(27, 10) # un embedding de 10 dimensiones\n", "h = 200 # aumentamos las unidades ocultas a 200\n", "H = torch.randn((30, h)) # recordemos: 10 * 3 = 30\n", "d = torch.randn(h)\n", "U = torch.randn(h, 27)\n", "b = torch.randn(27)\n", "\n", "parametros = [C, H, d, U, b]" ] }, { "cell_type": "code", "execution_count": 446, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "WcrQYS_Lg-m-", "outputId": "189c674b-fad4-4ca1-d3d6-caf7e85129fa" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.7781141996383667\n" ] } ], "source": [ "for p in parametros:\n", " p.requires_grad = True\n", "\n", "for i in range(15000):\n", " #minibatch («minilote»)\n", " ix = torch.randint(0, Xtr.shape[0], (32,))\n", "\n", " # propagación hacia delante\n", " emb = C[Xtr[ix]]\n", " a = torch.tanh(emb.view(-1, 30) @ H + d)\n", " logits = a @ U + b\n", " perdida = F.cross_entropy(logits, Ytr[ix])\n", " \n", " # propagación hacia atrás\n", " for p in parametros:\n", " p.grad = None\n", " perdida.backward()\n", "\n", " # actualización\n", " lr = 0.1 if i < 14000 else 0.01 # nuestra tasa de aprendizaje disminuirá hacia el final del entrenamiento\n", " for p in parametros:\n", " p.data += -lr * p.grad\n", "\n", "print(perdida.item())" ] }, { "cell_type": "markdown", "metadata": { "id": "vFEC-VhliHVW" }, "source": [ "Finalmente hemos superado al modelo anterior con fuerza bruta. Aunque podríamos seguir ajustándolo, me parece que esto será suficiente para obtener mejores muestras de nombres. Revisemos la pérdida de nuestros `Xdev` y `Xte` para cerciorarnos de que nuestro modelo haya generalizado sus aprendizajes, en vez de memorizarlos:" ] }, { "cell_type": "code", "execution_count": 448, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "VJcK54eTPpTo", "outputId": "70af0d4e-cab5-4cce-a2f1-31dcfa00656e" }, "outputs": [ { "data": { "text/plain": [ "tensor(1.9739, grad_fn=<NllLossBackward0>)" ] }, "execution_count": 448, "metadata": {}, "output_type": "execute_result" } ], "source": [ "emb = C[Xdev]\n", "a = torch.tanh(emb.view(-1, 30) @ H + d)\n", "logits = a @ U + b\n", "perdida = F.cross_entropy(logits, Ydev)\n", "perdida" ] }, { "cell_type": "code", "execution_count": 449, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "B6qHnZwVkDqn", "outputId": "f68ffdc3-23c7-4f48-b00a-c13c4e102b33" }, "outputs": [ { "data": { "text/plain": [ "tensor(1.9782, grad_fn=<NllLossBackward0>)" ] }, "execution_count": 449, "metadata": {}, "output_type": "execute_result" } ], "source": [ "emb = C[Xte]\n", "a = torch.tanh(emb.view(-1, 30) @ H + d)\n", "logits = a @ U + b\n", "perdida = F.cross_entropy(logits, Yte)\n", "perdida" ] }, { "cell_type": "markdown", "metadata": { "id": "P9gJFQCBkJcp" }, "source": [ "Tenemos también pérdidas razonables para ambos conjuntos de datos. Conforme la pérdida del entrenamiento difiera más de las otras dos (`Xdev` y `Xte`), esto indicaría que nuestros parámetros están memorizando los datos, no generalizándolos. En ese sentido, debemos ser cuidados de no sobreentrenar, ni tampoco sobredimensionar el modelo, puesto que si nos excedemos, terminará memorizando todos nuestros datos.\n", "\n", "Para finalizar, obtengamos algunas muestras de nombres generados por nuestro modelo:" ] }, { "cell_type": "code", "execution_count": 455, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vS8h0tWHQLCH", "outputId": "e53669c7-8d2b-4a81-e5d1-a61434fe4cc2" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "cedelzorena.\n", "carixto.\n", "leiscasiria.\n", "jovino.\n", "renta.\n", "marizio.\n", "alfidela.\n", "alsa.\n", "paulogia.\n", "taw.\n", "migdina.\n", "duboa.\n", "secahmidio.\n", "sihorgel.\n", "floreta.\n", "remenicia.\n", "penoyda.\n", "filiano.\n", "frinda.\n", "geria.\n" ] } ], "source": [ "block_size = 3\n", "for _ in range(20):\n", " out = []\n", " context = [0] * block_size\n", " while True:\n", " emb = C[torch.tensor([context])]\n", " a = torch.tanh(emb.view(1, -1) @ H + d)\n", " logits = a @ U + b\n", " probs = F.softmax(logits, dim=1)\n", " ix = torch.multinomial(probs, num_samples=1).item()\n", " context = context[1:] + [ix]\n", " out.append(ix)\n", " if ix == 0:\n", " break\n", "\n", " print(''.join(fap[i] for i in out))" ] }, { "cell_type": "markdown", "metadata": { "id": "uZgbBCm9nThf" }, "source": [ "Indudablemente, estos nombres son más respetables." ] }, { "cell_type": "markdown", "metadata": { "id": "rbHyRZ9gbY3P" }, "source": [ "[^1]: El método `view` es eficiente porque [no requiere de nuevo espacio](http://blog.ezyang.com/2019/05/pytorch-internals/) en la memoria de nuestra computadora, sino que utiliza el mismo tensor para reacomodarlo de manera distinta. Conforme nuestros programas se vuelvan más complejos, debemos procurar eficientar al máximo nuestros recursos computacionales. Para un acercamiento más general al tema, véase el [artículo de Horace He](https://horace.io/brrr_intro.html).\n", "\n", "[^2]: En *deep learning*, el término *hidden* («oculto») se utiliza para referirse a componentes de una red neuronal que no son «visibles» ni como entradas ni como salidas de la red. Se trata de entradas o salidas que se procesan internamente por la red neuronal antes de arrojar un resultado final. La *hidden unit*, en ese sentido, se refiere a cada unidad o componente de una capa oculta (*hidden layer*) en la red. Podemos entenderla como sinónimo de «nodo» o «neurona».\n", "\n", "[^3]: Se denominan «parámetros» a los números o coeficientes que la red neuronal aprende y determina durante el entrenamiento (por ejemplo, los números de los pesos y los sesgos). Por otra parte, los «hiperparámetros» son aquellos que nosotros definimos manualmente según la naturaleza de cada problema, y que optimizamos mediante pruebas con distintos valores (por ejemplo, nuestras unidades ocultas $h$)." ] } ], "metadata": { "colab": { "collapsed_sections": [], "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }