414 lines
22 KiB
Plaintext
414 lines
22 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Redes generativas\n",
|
|
"\n",
|
|
"Las Redes Neuronales Recurrentes (RNNs) y sus variantes con celdas controladas, como las Celdas de Memoria a Largo Corto Plazo (LSTMs) y las Unidades Recurrentes Controladas (GRUs), proporcionaron un mecanismo para el modelado del lenguaje, es decir, pueden aprender el orden de las palabras y ofrecer predicciones para la siguiente palabra en una secuencia. Esto nos permite usar las RNNs para **tareas generativas**, como la generación de texto ordinario, la traducción automática e incluso la generación de subtítulos para imágenes.\n",
|
|
"\n",
|
|
"En la arquitectura de RNN que discutimos en la unidad anterior, cada unidad RNN producía el siguiente estado oculto como salida. Sin embargo, también podemos añadir otra salida a cada unidad recurrente, lo que nos permitiría generar una **secuencia** (que tiene la misma longitud que la secuencia original). Además, podemos usar unidades RNN que no acepten una entrada en cada paso, y simplemente tomen un vector de estado inicial, para luego producir una secuencia de salidas.\n",
|
|
"\n",
|
|
"En este cuaderno, nos centraremos en modelos generativos simples que nos ayuden a generar texto. Para simplificar, construyamos una **red a nivel de caracteres**, que genera texto letra por letra. Durante el entrenamiento, necesitamos tomar un corpus de texto y dividirlo en secuencias de letras.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n",
|
|
"Building vocab...\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import torch\n",
|
|
"import torchtext\n",
|
|
"import numpy as np\n",
|
|
"from torchnlp import *\n",
|
|
"train_dataset,test_dataset,classes,vocab = load_dataset()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Construcción de vocabulario de caracteres\n",
|
|
"\n",
|
|
"Para construir una red generativa a nivel de caracteres, necesitamos dividir el texto en caracteres individuales en lugar de palabras. Esto se puede lograr definiendo un tokenizador diferente:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Vocabulary size = 82\n",
|
|
"Encoding of 'a' is 1\n",
|
|
"Character with code 13 is c\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def char_tokenizer(words):\n",
|
|
" return list(words) #[word for word in words]\n",
|
|
"\n",
|
|
"counter = collections.Counter()\n",
|
|
"for (label, line) in train_dataset:\n",
|
|
" counter.update(char_tokenizer(line))\n",
|
|
"vocab = torchtext.vocab.vocab(counter)\n",
|
|
"\n",
|
|
"vocab_size = len(vocab)\n",
|
|
"print(f\"Vocabulary size = {vocab_size}\")\n",
|
|
"print(f\"Encoding of 'a' is {vocab.get_stoi()['a']}\")\n",
|
|
"print(f\"Character with code 13 is {vocab.get_itos()[13]}\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Veamos el ejemplo de cómo podemos codificar el texto de nuestro conjunto de datos:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"tensor([ 0, 1, 2, 2, 3, 4, 5, 6, 3, 7, 8, 1, 9, 10, 3, 11, 2, 1,\n",
|
|
" 12, 3, 7, 1, 13, 14, 3, 15, 16, 5, 17, 3, 5, 18, 8, 3, 7, 2,\n",
|
|
" 1, 13, 14, 3, 19, 20, 8, 21, 5, 8, 9, 10, 22, 3, 20, 8, 21, 5,\n",
|
|
" 8, 9, 10, 3, 23, 3, 4, 18, 17, 9, 5, 23, 10, 8, 2, 2, 8, 9,\n",
|
|
" 10, 24, 3, 0, 1, 2, 2, 3, 4, 5, 9, 8, 8, 5, 25, 10, 3, 26,\n",
|
|
" 12, 27, 16, 26, 2, 27, 16, 28, 29, 30, 1, 16, 26, 3, 17, 31, 3, 21,\n",
|
|
" 2, 5, 9, 1, 23, 13, 32, 16, 27, 13, 10, 24, 3, 1, 9, 8, 3, 10,\n",
|
|
" 8, 8, 27, 16, 28, 3, 28, 9, 8, 8, 16, 3, 1, 28, 1, 27, 16, 6])"
|
|
]
|
|
},
|
|
"execution_count": 3,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"def enc(x):\n",
|
|
" return torch.LongTensor(encode(x,voc=vocab,tokenizer=char_tokenizer))\n",
|
|
"\n",
|
|
"enc(train_dataset[0][1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Entrenando una RNN generativa\n",
|
|
"\n",
|
|
"La forma en que entrenaremos la RNN para generar texto es la siguiente. En cada paso, tomaremos una secuencia de caracteres de longitud `nchars` y pediremos a la red que genere el siguiente carácter de salida para cada carácter de entrada:\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"Dependiendo del escenario específico, también podríamos querer incluir algunos caracteres especiales, como *fin de secuencia* `<eos>`. En nuestro caso, solo queremos entrenar la red para la generación continua de texto, por lo que fijaremos el tamaño de cada secuencia para que sea igual a `nchars` tokens. En consecuencia, cada ejemplo de entrenamiento consistirá en `nchars` entradas y `nchars` salidas (que son la secuencia de entrada desplazada un símbolo hacia la izquierda). El minibatch consistirá en varias de estas secuencias.\n",
|
|
"\n",
|
|
"La forma en que generaremos los minibatches será tomando cada texto de noticias de longitud `l` y generando todas las combinaciones posibles de entrada-salida a partir de él (habrá `l-nchars` de estas combinaciones). Estas formarán un minibatch, y el tamaño de los minibatches será diferente en cada paso de entrenamiento.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(tensor([[ 0, 1, 2, ..., 28, 29, 30],\n",
|
|
" [ 1, 2, 2, ..., 29, 30, 1],\n",
|
|
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
|
" ...,\n",
|
|
" [20, 8, 21, ..., 1, 28, 1],\n",
|
|
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
|
" [21, 5, 8, ..., 1, 27, 16]]),\n",
|
|
" tensor([[ 1, 2, 2, ..., 29, 30, 1],\n",
|
|
" [ 2, 2, 3, ..., 30, 1, 16],\n",
|
|
" [ 2, 3, 4, ..., 1, 16, 26],\n",
|
|
" ...,\n",
|
|
" [ 8, 21, 5, ..., 28, 1, 27],\n",
|
|
" [21, 5, 8, ..., 1, 27, 16],\n",
|
|
" [ 5, 8, 9, ..., 27, 16, 6]]))"
|
|
]
|
|
},
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"nchars = 100\n",
|
|
"\n",
|
|
"def get_batch(s,nchars=nchars):\n",
|
|
" ins = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
|
" outs = torch.zeros(len(s)-nchars,nchars,dtype=torch.long,device=device)\n",
|
|
" for i in range(len(s)-nchars):\n",
|
|
" ins[i] = enc(s[i:i+nchars])\n",
|
|
" outs[i] = enc(s[i+1:i+nchars+1])\n",
|
|
" return ins,outs\n",
|
|
"\n",
|
|
"get_batch(train_dataset[0][1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Ahora definamos la red generadora. Puede basarse en cualquier célula recurrente que discutimos en la unidad anterior (simple, LSTM o GRU). En nuestro ejemplo, utilizaremos LSTM.\n",
|
|
"\n",
|
|
"Dado que la red toma caracteres como entrada y el tamaño del vocabulario es bastante pequeño, no necesitamos una capa de embeddings; la entrada codificada en one-hot puede ir directamente a la célula LSTM. Sin embargo, como pasamos números de caracteres como entrada, necesitamos codificarlos en one-hot antes de pasarlos a la LSTM. Esto se realiza llamando a la función `one_hot` durante el paso `forward`. El codificador de salida sería una capa lineal que convertirá el estado oculto en una salida codificada en one-hot.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMGenerator(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, hidden_dim):\n",
|
|
" super().__init__()\n",
|
|
" self.rnn = torch.nn.LSTM(vocab_size,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, vocab_size)\n",
|
|
"\n",
|
|
" def forward(self, x, s=None):\n",
|
|
" x = torch.nn.functional.one_hot(x,vocab_size).to(torch.float32)\n",
|
|
" x,s = self.rnn(x,s)\n",
|
|
" return self.fc(x),s"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Durante el entrenamiento, queremos poder muestrear texto generado. Para ello, definiremos la función `generate` que producirá una cadena de salida de longitud `size`, comenzando desde la cadena inicial `start`.\n",
|
|
"\n",
|
|
"El funcionamiento es el siguiente. Primero, pasaremos toda la cadena inicial a través de la red, y obtendremos el estado de salida `s` y el siguiente carácter predicho `out`. Dado que `out` está codificado en one-hot, tomamos `argmax` para obtener el índice del carácter `nc` en el vocabulario, y usamos `itos` para identificar el carácter real y añadirlo a la lista resultante de caracteres `chars`. Este proceso de generar un carácter se repite `size` veces para generar el número requerido de caracteres.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def generate(net,size=100,start='today '):\n",
|
|
" chars = list(start)\n",
|
|
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
|
" for i in range(size):\n",
|
|
" nc = torch.argmax(out[0][-1])\n",
|
|
" chars.append(vocab.get_itos()[nc])\n",
|
|
" out, s = net(nc.view(1,-1),s)\n",
|
|
" return ''.join(chars)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"¡Ahora vamos a entrenar! El bucle de entrenamiento es casi el mismo que en todos nuestros ejemplos anteriores, pero en lugar de la precisión, imprimimos texto generado como muestra cada 1000 épocas.\n",
|
|
"\n",
|
|
"Se debe prestar especial atención a la forma en que calculamos la pérdida. Necesitamos calcular la pérdida dado un resultado codificado en one-hot `out` y el texto esperado `text_out`, que es la lista de índices de caracteres. Por suerte, la función `cross_entropy` espera como primer argumento la salida no normalizada de la red, y como segundo argumento el número de clase, que es exactamente lo que tenemos. Además, realiza un promedio automático sobre el tamaño del minibatch.\n",
|
|
"\n",
|
|
"También limitamos el entrenamiento a `samples_to_train` muestras, para no tener que esperar demasiado tiempo. Te animamos a experimentar y probar entrenamientos más largos, posiblemente durante varias épocas (en cuyo caso necesitarías crear otro bucle alrededor de este código).\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 9,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Current loss = 4.398899078369141\n",
|
|
"today sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr sr s\n",
|
|
"Current loss = 2.161320447921753\n",
|
|
"today and to the tor to to the tor to to the tor to to the tor to to the tor to to the tor to to the tor t\n",
|
|
"Current loss = 1.6722588539123535\n",
|
|
"today and the court to the could to the could to the could to the could to the could to the could to the c\n",
|
|
"Current loss = 2.423795223236084\n",
|
|
"today and a second to the conternation of the conternation of the conternation of the conternation of the \n",
|
|
"Current loss = 1.702607274055481\n",
|
|
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
|
"Current loss = 1.692358136177063\n",
|
|
"today and the company to the company to the company to the company to the company to the company to the co\n",
|
|
"Current loss = 1.9722288846969604\n",
|
|
"today and the control the control the control the control the control the control the control the control \n",
|
|
"Current loss = 1.8705692291259766\n",
|
|
"today and the second to the second to the second to the second to the second to the second to the second t\n",
|
|
"Current loss = 1.7626899480819702\n",
|
|
"today and a security and a security and a security and a security and a security and a security and a secu\n",
|
|
"Current loss = 1.5574463605880737\n",
|
|
"today and the company and the company and the company and the company and the company and the company and \n",
|
|
"Current loss = 1.5620026588439941\n",
|
|
"today and the be that the be the be that the be the be that the be the be that the be the be that the be t\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMGenerator(vocab_size,64).to(device)\n",
|
|
"\n",
|
|
"samples_to_train = 10000\n",
|
|
"optimizer = torch.optim.Adam(net.parameters(),0.01)\n",
|
|
"loss_fn = torch.nn.CrossEntropyLoss()\n",
|
|
"net.train()\n",
|
|
"for i,x in enumerate(train_dataset):\n",
|
|
" # x[0] is class label, x[1] is text\n",
|
|
" if len(x[1])-nchars<10:\n",
|
|
" continue\n",
|
|
" samples_to_train-=1\n",
|
|
" if not samples_to_train: break\n",
|
|
" text_in, text_out = get_batch(x[1])\n",
|
|
" optimizer.zero_grad()\n",
|
|
" out,s = net(text_in)\n",
|
|
" loss = torch.nn.functional.cross_entropy(out.view(-1,vocab_size),text_out.flatten()) #cross_entropy(out,labels)\n",
|
|
" loss.backward()\n",
|
|
" optimizer.step()\n",
|
|
" if i%1000==0:\n",
|
|
" print(f\"Current loss = {loss.item()}\")\n",
|
|
" print(generate(net))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Este ejemplo ya genera un texto bastante bueno, pero se puede mejorar en varios aspectos:\n",
|
|
"\n",
|
|
"* **Mejor generación de minibatches**. La forma en que preparamos los datos para el entrenamiento fue generando un minibatch a partir de una muestra. Esto no es ideal, porque los minibatches tienen tamaños diferentes, y algunos incluso no pueden generarse porque el texto es más pequeño que `nchars`. Además, los minibatches pequeños no aprovechan suficientemente la GPU. Sería más inteligente tomar un gran bloque de texto de todas las muestras, luego generar todos los pares de entrada-salida, mezclarlos y generar minibatches de tamaño uniforme.\n",
|
|
"\n",
|
|
"* **LSTM multicapa**. Tiene sentido probar con 2 o 3 capas de células LSTM. Como mencionamos en la unidad anterior, cada capa de LSTM extrae ciertos patrones del texto, y en el caso de un generador a nivel de caracteres, podemos esperar que el nivel inferior de LSTM sea responsable de extraer sílabas, y los niveles superiores de palabras y combinaciones de palabras. Esto se puede implementar fácilmente pasando el parámetro de número de capas al constructor de LSTM.\n",
|
|
"\n",
|
|
"* También podrías experimentar con **unidades GRU** y ver cuáles funcionan mejor, así como con **diferentes tamaños de capas ocultas**. Una capa oculta demasiado grande puede resultar en sobreajuste (por ejemplo, la red aprenderá el texto exacto), mientras que un tamaño más pequeño podría no producir buenos resultados.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Generación de texto suave y temperatura\n",
|
|
"\n",
|
|
"En la definición anterior de `generate`, siempre tomábamos el carácter con la probabilidad más alta como el siguiente carácter en el texto generado. Esto daba como resultado que el texto a menudo \"ciclaba\" entre las mismas secuencias de caracteres una y otra vez, como en este ejemplo:\n",
|
|
"```\n",
|
|
"today of the second the company and a second the company ...\n",
|
|
"```\n",
|
|
"\n",
|
|
"Sin embargo, si observamos la distribución de probabilidad para el siguiente carácter, podría suceder que la diferencia entre algunas de las probabilidades más altas no sea muy grande, por ejemplo, un carácter puede tener una probabilidad de 0.2, otro de 0.19, etc. Por ejemplo, al buscar el siguiente carácter en la secuencia '*play*', el siguiente carácter podría ser igualmente un espacio o una **e** (como en la palabra *player*).\n",
|
|
"\n",
|
|
"Esto nos lleva a la conclusión de que no siempre es \"justo\" seleccionar el carácter con mayor probabilidad, ya que elegir el segundo más probable aún podría conducir a un texto significativo. Es más sensato **muestrear** caracteres de la distribución de probabilidad dada por la salida de la red.\n",
|
|
"\n",
|
|
"Este muestreo se puede realizar utilizando la función `multinomial`, que implementa la llamada **distribución multinomial**. A continuación, se define una función que implementa esta generación de texto **suave**:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"--- Temperature = 0.3\n",
|
|
"Today and a company and complete an all the land the restrational the as a security and has provers the pay to and a report and the computer in the stand has filities and working the law the stations for a company and with the company and the final the first company and refight of the state and and workin\n",
|
|
"\n",
|
|
"--- Temperature = 0.8\n",
|
|
"Today he oniis its first to Aus bomblaties the marmation a to manan boogot that pirate assaid a relaid their that goverfin the the Cappets Ecrotional Assonia Cition targets it annight the w scyments Blamity #39;s TVeer Diercheg Reserals fran envyuil that of ster said access what succers of Dour-provelith\n",
|
|
"\n",
|
|
"--- Temperature = 1.0\n",
|
|
"Today holy they a 11 will meda a toket subsuaties, engins for Chanos, they's has stainger past to opening orital his thempting new Nattona was al innerforder advan-than #36;s night year his religuled talitatian what the but with Wednesday to Justment will wemen of Mark CCC Camp as Timed Nae wome a leaders\n",
|
|
"\n",
|
|
"--- Temperature = 1.3\n",
|
|
"Today gpone 2.5 fech atcusion poor cocles toparsdorM.cht Line Pamage put 43 his calt lowed to the book, that has authh-the silia rruch ailing to'ory andhes beutirsimi- Aefffive heading offil an auf eacklets is charged evis, Gunymy oy) Mony has it after-sloythyor loveId out filme, the Natabl -Najuntaxiggs \n",
|
|
"\n",
|
|
"--- Temperature = 1.8\n",
|
|
"Today plary, P.slan chly\\401 mardregationly #39;t 8.1Mide) closes ,filtcon alfly playin roven!\\grea.-QFBEP: Iss onfarchQ/itilia CCf Zivesigntwasta orce.-Peul-aw.uicrin of fuglinfsut aftaningwo, MIEX awayew Aice Woiduar Corvagiugge oppo esig ThusBratourid canthly-RyI.co lagitems\\eexciaishes.conBabntusmor I\n",
|
|
"\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def generate_soft(net,size=100,start='today ',temperature=1.0):\n",
|
|
" chars = list(start)\n",
|
|
" out, s = net(enc(chars).view(1,-1).to(device))\n",
|
|
" for i in range(size):\n",
|
|
" #nc = torch.argmax(out[0][-1])\n",
|
|
" out_dist = out[0][-1].div(temperature).exp()\n",
|
|
" nc = torch.multinomial(out_dist,1)[0]\n",
|
|
" chars.append(vocab.get_itos()[nc])\n",
|
|
" out, s = net(nc.view(1,-1),s)\n",
|
|
" return ''.join(chars)\n",
|
|
" \n",
|
|
"for i in [0.3,0.8,1.0,1.3,1.8]:\n",
|
|
" print(f\"--- Temperature = {i}\\n{generate_soft(net,size=300,start='Today ',temperature=i)}\\n\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Hemos introducido un parámetro más llamado **temperatura**, que se utiliza para indicar qué tan estrictamente debemos adherirnos a la probabilidad más alta. Si la temperatura es 1.0, hacemos un muestreo multinomial justo, y cuando la temperatura se acerca al infinito, todas las probabilidades se vuelven iguales y seleccionamos el siguiente carácter al azar. En el ejemplo a continuación, podemos observar que el texto se vuelve sin sentido cuando aumentamos demasiado la temperatura, y se asemeja a un texto \"cíclico\" generado rígidamente cuando se acerca a 0.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Descargo de responsabilidad**: \nEste documento ha sido traducido utilizando el servicio de traducción automática [Co-op Translator](https://github.com/Azure/co-op-translator). Aunque nos esforzamos por garantizar la precisión, tenga en cuenta que las traducciones automatizadas pueden contener errores o imprecisiones. El documento original en su idioma nativo debe considerarse como la fuente autorizada. Para información crítica, se recomienda una traducción profesional realizada por humanos. No nos hacemos responsables de malentendidos o interpretaciones erróneas que puedan surgir del uso de esta traducción.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3.8.12 ('py38')",
|
|
"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.8.12"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "7673cd150d96c74c6d6011460094efb4",
|
|
"translation_date": "2025-08-31T16:53:45+00:00",
|
|
"source_file": "lessons/5-NLP/17-GenerativeNetworks/GenerativePyTorch.ipynb",
|
|
"language_code": "es"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 4
|
|
} |