microsoft/AI-For-Beginners
Publicmirrored fromhttps://github.com/microsoft/AI-For-BeginnersAvailable
translations/es/lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb
647lines · modecode
| 1 | { |
| 2 | "cells": [ |
| 3 | { |
| 4 | "cell_type": "markdown", |
| 5 | "metadata": {}, |
| 6 | "source": [ |
| 7 | "# Tarea de clasificación de texto\n", |
| 8 | "\n", |
| 9 | "En este módulo, comenzaremos con una tarea sencilla de clasificación de texto basada en el conjunto de datos **[AG_NEWS](http://www.di.unipi.it/~gulli/AG_corpus_of_news_articles.html)**: clasificaremos titulares de noticias en una de 4 categorías: Mundo, Deportes, Negocios y Ciencia/Tecnología.\n", |
| 10 | "\n", |
| 11 | "## El Conjunto de Datos\n", |
| 12 | "\n", |
| 13 | "Para cargar el conjunto de datos, utilizaremos la API de **[TensorFlow Datasets](https://www.tensorflow.org/datasets)**.\n" |
| 14 | ] |
| 15 | }, |
| 16 | { |
| 17 | "cell_type": "code", |
| 18 | "execution_count": 1, |
| 19 | "metadata": {}, |
| 20 | "outputs": [], |
| 21 | "source": [ |
| 22 | "import tensorflow as tf\n", |
| 23 | "from tensorflow import keras\n", |
| 24 | "import tensorflow_datasets as tfds\n", |
| 25 | "\n", |
| 26 | "# In this tutorial, we will be training a lot of models. In order to use GPU memory cautiously,\n", |
| 27 | "# we will set tensorflow option to grow GPU memory allocation when required.\n", |
| 28 | "physical_devices = tf.config.list_physical_devices('GPU') \n", |
| 29 | "if len(physical_devices)>0:\n", |
| 30 | " tf.config.experimental.set_memory_growth(physical_devices[0], True)\n", |
| 31 | "\n", |
| 32 | "dataset = tfds.load('ag_news_subset')" |
| 33 | ] |
| 34 | }, |
| 35 | { |
| 36 | "cell_type": "markdown", |
| 37 | "metadata": {}, |
| 38 | "source": [ |
| 39 | "Ahora podemos acceder a las partes de entrenamiento y prueba del conjunto de datos utilizando `dataset['train']` y `dataset['test']` respectivamente:\n" |
| 40 | ] |
| 41 | }, |
| 42 | { |
| 43 | "cell_type": "code", |
| 44 | "execution_count": 3, |
| 45 | "metadata": {}, |
| 46 | "outputs": [ |
| 47 | { |
| 48 | "name": "stdout", |
| 49 | "output_type": "stream", |
| 50 | "text": [ |
| 51 | "Length of train dataset = 120000\n", |
| 52 | "Length of test dataset = 7600\n" |
| 53 | ] |
| 54 | } |
| 55 | ], |
| 56 | "source": [ |
| 57 | "ds_train = dataset['train']\n", |
| 58 | "ds_test = dataset['test']\n", |
| 59 | "\n", |
| 60 | "print(f\"Length of train dataset = {len(ds_train)}\")\n", |
| 61 | "print(f\"Length of test dataset = {len(ds_test)}\")" |
| 62 | ] |
| 63 | }, |
| 64 | { |
| 65 | "cell_type": "markdown", |
| 66 | "metadata": {}, |
| 67 | "source": [ |
| 68 | "Imprimamos los primeros 10 nuevos titulares de nuestro conjunto de datos:\n" |
| 69 | ] |
| 70 | }, |
| 71 | { |
| 72 | "cell_type": "code", |
| 73 | "execution_count": 4, |
| 74 | "metadata": {}, |
| 75 | "outputs": [ |
| 76 | { |
| 77 | "name": "stdout", |
| 78 | "output_type": "stream", |
| 79 | "text": [ |
| 80 | "3 (Sci/Tech) -> b'AMD Debuts Dual-Core Opteron Processor' b'AMD #39;s new dual-core Opteron chip is designed mainly for corporate computing applications, including databases, Web services, and financial transactions.'\n", |
| 81 | "1 (Sports) -> b\"Wood's Suspension Upheld (Reuters)\" b'Reuters - Major League Baseball\\\\Monday announced a decision on the appeal filed by Chicago Cubs\\\\pitcher Kerry Wood regarding a suspension stemming from an\\\\incident earlier this season.'\n", |
| 82 | "2 (Business) -> b'Bush reform may have blue states seeing red' b'President Bush #39;s quot;revenue-neutral quot; tax reform needs losers to balance its winners, and people claiming the federal deduction for state and local taxes may be in administration planners #39; sights, news reports say.'\n", |
| 83 | "3 (Sci/Tech) -> b\"'Halt science decline in schools'\" b'Britain will run out of leading scientists unless science education is improved, says Professor Colin Pillinger.'\n", |
| 84 | "1 (Sports) -> b'Gerrard leaves practice' b'London, England (Sports Network) - England midfielder Steven Gerrard injured his groin late in Thursday #39;s training session, but is hopeful he will be ready for Saturday #39;s World Cup qualifier against Austria.'\n" |
| 85 | ] |
| 86 | } |
| 87 | ], |
| 88 | "source": [ |
| 89 | "classes = ['World', 'Sports', 'Business', 'Sci/Tech']\n", |
| 90 | "\n", |
| 91 | "for i,x in zip(range(5),ds_train):\n", |
| 92 | " print(f\"{x['label']} ({classes[x['label']]}) -> {x['title']} {x['description']}\")" |
| 93 | ] |
| 94 | }, |
| 95 | { |
| 96 | "cell_type": "markdown", |
| 97 | "metadata": {}, |
| 98 | "source": [ |
| 99 | "## Vectorización de texto\n", |
| 100 | "\n", |
| 101 | "Ahora necesitamos convertir el texto en **números** que puedan representarse como tensores. Si queremos una representación a nivel de palabras, necesitamos hacer dos cosas:\n", |
| 102 | "\n", |
| 103 | "* Usar un **tokenizador** para dividir el texto en **tokens**.\n", |
| 104 | "* Construir un **vocabulario** de esos tokens.\n", |
| 105 | "\n", |
| 106 | "### Limitando el tamaño del vocabulario\n", |
| 107 | "\n", |
| 108 | "En el ejemplo del conjunto de datos AG News, el tamaño del vocabulario es bastante grande, más de 100k palabras. En términos generales, no necesitamos palabras que estén presentes raramente en el texto — solo unas pocas frases las tendrán, y el modelo no aprenderá de ellas. Por lo tanto, tiene sentido limitar el tamaño del vocabulario a un número más pequeño pasando un argumento al constructor del vectorizador:\n", |
| 109 | "\n", |
| 110 | "Ambos pasos pueden manejarse utilizando la capa **TextVectorization**. Vamos a instanciar el objeto vectorizador y luego llamar al método `adapt` para recorrer todo el texto y construir un vocabulario:\n" |
| 111 | ] |
| 112 | }, |
| 113 | { |
| 114 | "cell_type": "code", |
| 115 | "execution_count": 5, |
| 116 | "metadata": {}, |
| 117 | "outputs": [], |
| 118 | "source": [ |
| 119 | "vocab_size = 50000\n", |
| 120 | "vectorizer = keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size)\n", |
| 121 | "vectorizer.adapt(ds_train.take(500).map(lambda x: x['title']+' '+x['description']))" |
| 122 | ] |
| 123 | }, |
| 124 | { |
| 125 | "cell_type": "markdown", |
| 126 | "metadata": {}, |
| 127 | "source": [ |
| 128 | "> **Nota** que estamos utilizando solo un subconjunto del conjunto de datos completo para construir un vocabulario. Hacemos esto para acelerar el tiempo de ejecución y no hacerte esperar. Sin embargo, corremos el riesgo de que algunas palabras del conjunto de datos completo no se incluyan en el vocabulario y sean ignoradas durante el entrenamiento. Por lo tanto, usar el tamaño completo del vocabulario y procesar todo el conjunto de datos durante `adapt` debería aumentar la precisión final, pero no de manera significativa.\n", |
| 129 | "\n", |
| 130 | "Ahora podemos acceder al vocabulario real:\n" |
| 131 | ] |
| 132 | }, |
| 133 | { |
| 134 | "cell_type": "code", |
| 135 | "execution_count": 6, |
| 136 | "metadata": {}, |
| 137 | "outputs": [ |
| 138 | { |
| 139 | "name": "stdout", |
| 140 | "output_type": "stream", |
| 141 | "text": [ |
| 142 | "['', '[UNK]', 'the', 'to', 'a', 'in', 'of', 'and', 'on', 'for']\n", |
| 143 | "Length of vocabulary: 5335\n" |
| 144 | ] |
| 145 | } |
| 146 | ], |
| 147 | "source": [ |
| 148 | "vocab = vectorizer.get_vocabulary()\n", |
| 149 | "vocab_size = len(vocab)\n", |
| 150 | "print(vocab[:10])\n", |
| 151 | "print(f\"Length of vocabulary: {vocab_size}\")" |
| 152 | ] |
| 153 | }, |
| 154 | { |
| 155 | "cell_type": "markdown", |
| 156 | "metadata": {}, |
| 157 | "source": [ |
| 158 | "Usando el vectorizador, podemos codificar fácilmente cualquier texto en un conjunto de números:\n" |
| 159 | ] |
| 160 | }, |
| 161 | { |
| 162 | "cell_type": "code", |
| 163 | "execution_count": 7, |
| 164 | "metadata": {}, |
| 165 | "outputs": [ |
| 166 | { |
| 167 | "data": { |
| 168 | "text/plain": [ |
| 169 | "<tf.Tensor: shape=(7,), dtype=int64, numpy=array([ 112, 3695, 3, 304, 11, 1041, 1], dtype=int64)>" |
| 170 | ] |
| 171 | }, |
| 172 | "execution_count": 7, |
| 173 | "metadata": {}, |
| 174 | "output_type": "execute_result" |
| 175 | } |
| 176 | ], |
| 177 | "source": [ |
| 178 | "vectorizer('I love to play with my words')" |
| 179 | ] |
| 180 | }, |
| 181 | { |
| 182 | "cell_type": "markdown", |
| 183 | "metadata": {}, |
| 184 | "source": [ |
| 185 | "## Representación de texto con bolsa de palabras\n", |
| 186 | "\n", |
| 187 | "Dado que las palabras representan significado, a veces podemos deducir el significado de un texto simplemente observando las palabras individuales, sin importar su orden en la oración. Por ejemplo, al clasificar noticias, palabras como *clima* y *nieve* probablemente indiquen *pronóstico del tiempo*, mientras que palabras como *acciones* y *dólar* podrían corresponder a *noticias financieras*.\n", |
| 188 | "\n", |
| 189 | "La representación vectorial **bolsa de palabras** (BoW) es la más sencilla de entender entre las representaciones vectoriales tradicionales. Cada palabra está vinculada a un índice de vector, y un elemento del vector contiene el número de veces que aparece cada palabra en un documento dado.\n", |
| 190 | "\n", |
| 191 | " \n", |
| 192 | "\n", |
| 193 | "> **Nota**: También puedes pensar en BoW como la suma de todos los vectores codificados en formato one-hot para las palabras individuales en el texto.\n", |
| 194 | "\n", |
| 195 | "A continuación, se muestra un ejemplo de cómo generar una representación de bolsa de palabras utilizando la biblioteca de Python Scikit Learn:\n" |
| 196 | ] |
| 197 | }, |
| 198 | { |
| 199 | "cell_type": "code", |
| 200 | "execution_count": 8, |
| 201 | "metadata": {}, |
| 202 | "outputs": [ |
| 203 | { |
| 204 | "data": { |
| 205 | "text/plain": [ |
| 206 | "array([[1, 1, 0, 2, 0, 0, 0, 0, 0]], dtype=int64)" |
| 207 | ] |
| 208 | }, |
| 209 | "execution_count": 8, |
| 210 | "metadata": {}, |
| 211 | "output_type": "execute_result" |
| 212 | } |
| 213 | ], |
| 214 | "source": [ |
| 215 | "from sklearn.feature_extraction.text import CountVectorizer\n", |
| 216 | "sc_vectorizer = CountVectorizer()\n", |
| 217 | "corpus = [\n", |
| 218 | " 'I like hot dogs.',\n", |
| 219 | " 'The dog ran fast.',\n", |
| 220 | " 'Its hot outside.',\n", |
| 221 | " ]\n", |
| 222 | "sc_vectorizer.fit_transform(corpus)\n", |
| 223 | "sc_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()" |
| 224 | ] |
| 225 | }, |
| 226 | { |
| 227 | "cell_type": "markdown", |
| 228 | "metadata": {}, |
| 229 | "source": [ |
| 230 | "También podemos usar el vectorizador de Keras que definimos anteriormente, convirtiendo cada número de palabra en una codificación one-hot y sumando todos esos vectores.\n" |
| 231 | ] |
| 232 | }, |
| 233 | { |
| 234 | "cell_type": "code", |
| 235 | "execution_count": 9, |
| 236 | "metadata": {}, |
| 237 | "outputs": [ |
| 238 | { |
| 239 | "data": { |
| 240 | "text/plain": [ |
| 241 | "array([0., 5., 0., ..., 0., 0., 0.], dtype=float32)" |
| 242 | ] |
| 243 | }, |
| 244 | "execution_count": 9, |
| 245 | "metadata": {}, |
| 246 | "output_type": "execute_result" |
| 247 | } |
| 248 | ], |
| 249 | "source": [ |
| 250 | "def to_bow(text):\n", |
| 251 | " return tf.reduce_sum(tf.one_hot(vectorizer(text),vocab_size),axis=0)\n", |
| 252 | "\n", |
| 253 | "to_bow('My dog likes hot dogs on a hot day.').numpy()" |
| 254 | ] |
| 255 | }, |
| 256 | { |
| 257 | "cell_type": "markdown", |
| 258 | "metadata": {}, |
| 259 | "source": [ |
| 260 | "> **Nota**: Puede que te sorprenda que el resultado difiera del ejemplo anterior. La razón es que, en el ejemplo de Keras, la longitud del vector corresponde al tamaño del vocabulario, que se construyó a partir de todo el conjunto de datos de AG News, mientras que en el ejemplo de Scikit Learn construimos el vocabulario a partir del texto de muestra sobre la marcha.\n" |
| 261 | ] |
| 262 | }, |
| 263 | { |
| 264 | "cell_type": "markdown", |
| 265 | "metadata": {}, |
| 266 | "source": [ |
| 267 | "## Entrenando el clasificador BoW\n", |
| 268 | "\n", |
| 269 | "Ahora que hemos aprendido a construir la representación de bolsa de palabras (bag-of-words) de nuestro texto, entrenemos un clasificador que la utilice. Primero, necesitamos convertir nuestro conjunto de datos a una representación de bolsa de palabras. Esto se puede lograr utilizando la función `map` de la siguiente manera:\n" |
| 270 | ] |
| 271 | }, |
| 272 | { |
| 273 | "cell_type": "code", |
| 274 | "execution_count": 11, |
| 275 | "metadata": {}, |
| 276 | "outputs": [], |
| 277 | "source": [ |
| 278 | "batch_size = 128\n", |
| 279 | "\n", |
| 280 | "ds_train_bow = ds_train.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)\n", |
| 281 | "ds_test_bow = ds_test.map(lambda x: (to_bow(x['title']+x['description']),x['label'])).batch(batch_size)" |
| 282 | ] |
| 283 | }, |
| 284 | { |
| 285 | "cell_type": "markdown", |
| 286 | "metadata": {}, |
| 287 | "source": [ |
| 288 | "Ahora definamos una red neuronal clasificador simple que contiene una capa lineal. El tamaño de entrada es `vocab_size`, y el tamaño de salida corresponde al número de clases (4). Debido a que estamos resolviendo una tarea de clasificación, la función de activación final es **softmax**:\n" |
| 289 | ] |
| 290 | }, |
| 291 | { |
| 292 | "cell_type": "code", |
| 293 | "execution_count": 12, |
| 294 | "metadata": {}, |
| 295 | "outputs": [ |
| 296 | { |
| 297 | "name": "stdout", |
| 298 | "output_type": "stream", |
| 299 | "text": [ |
| 300 | "938/938 [==============================] - 66s 70ms/step - loss: 0.6144 - acc: 0.8427 - val_loss: 0.4416 - val_acc: 0.8697\n" |
| 301 | ] |
| 302 | }, |
| 303 | { |
| 304 | "data": { |
| 305 | "text/plain": [ |
| 306 | "<keras.callbacks.History at 0x20c70a947f0>" |
| 307 | ] |
| 308 | }, |
| 309 | "execution_count": 12, |
| 310 | "metadata": {}, |
| 311 | "output_type": "execute_result" |
| 312 | } |
| 313 | ], |
| 314 | "source": [ |
| 315 | "model = keras.models.Sequential([\n", |
| 316 | " keras.layers.Dense(4,activation='softmax',input_shape=(vocab_size,))\n", |
| 317 | "])\n", |
| 318 | "model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n", |
| 319 | "model.fit(ds_train_bow,validation_data=ds_test_bow)" |
| 320 | ] |
| 321 | }, |
| 322 | { |
| 323 | "cell_type": "markdown", |
| 324 | "metadata": {}, |
| 325 | "source": [ |
| 326 | "Dado que tenemos 4 clases, una precisión superior al 80% es un buen resultado.\n", |
| 327 | "\n", |
| 328 | "## Entrenando un clasificador como una sola red\n", |
| 329 | "\n", |
| 330 | "Como el vectorizador también es una capa de Keras, podemos definir una red que lo incluya y entrenarla de principio a fin. De esta manera, no necesitamos vectorizar el conjunto de datos usando `map`, simplemente podemos pasar el conjunto de datos original a la entrada de la red.\n", |
| 331 | "\n", |
| 332 | "> **Nota**: Aún tendríamos que aplicar mapas a nuestro conjunto de datos para convertir campos de diccionarios (como `title`, `description` y `label`) en tuplas. Sin embargo, al cargar datos desde el disco, podemos construir un conjunto de datos con la estructura requerida desde el principio.\n" |
| 333 | ] |
| 334 | }, |
| 335 | { |
| 336 | "cell_type": "code", |
| 337 | "execution_count": 13, |
| 338 | "metadata": {}, |
| 339 | "outputs": [ |
| 340 | { |
| 341 | "name": "stdout", |
| 342 | "output_type": "stream", |
| 343 | "text": [ |
| 344 | "Model: \"model\"\n", |
| 345 | "_________________________________________________________________\n", |
| 346 | " Layer (type) Output Shape Param # \n", |
| 347 | "=================================================================\n", |
| 348 | " input_1 (InputLayer) [(None, 1)] 0 \n", |
| 349 | " \n", |
| 350 | " text_vectorization (TextVec (None, None) 0 \n", |
| 351 | " torization) \n", |
| 352 | " \n", |
| 353 | " tf.one_hot (TFOpLambda) (None, None, 5335) 0 \n", |
| 354 | " \n", |
| 355 | " tf.math.reduce_sum (TFOpLam (None, 5335) 0 \n", |
| 356 | " bda) \n", |
| 357 | " \n", |
| 358 | " dense_2 (Dense) (None, 4) 21344 \n", |
| 359 | " \n", |
| 360 | "=================================================================\n", |
| 361 | "Total params: 21,344\n", |
| 362 | "Trainable params: 21,344\n", |
| 363 | "Non-trainable params: 0\n", |
| 364 | "_________________________________________________________________\n", |
| 365 | "938/938 [==============================] - 73s 77ms/step - loss: 0.6057 - acc: 0.8414 - val_loss: 0.4202 - val_acc: 0.8736\n" |
| 366 | ] |
| 367 | }, |
| 368 | { |
| 369 | "data": { |
| 370 | "text/plain": [ |
| 371 | "<keras.callbacks.History at 0x20c721521f0>" |
| 372 | ] |
| 373 | }, |
| 374 | "execution_count": 13, |
| 375 | "metadata": {}, |
| 376 | "output_type": "execute_result" |
| 377 | } |
| 378 | ], |
| 379 | "source": [ |
| 380 | "def extract_text(x):\n", |
| 381 | " return x['title']+' '+x['description']\n", |
| 382 | "\n", |
| 383 | "def tupelize(x):\n", |
| 384 | " return (extract_text(x),x['label'])\n", |
| 385 | "\n", |
| 386 | "inp = keras.Input(shape=(1,),dtype=tf.string)\n", |
| 387 | "x = vectorizer(inp)\n", |
| 388 | "x = tf.reduce_sum(tf.one_hot(x,vocab_size),axis=1)\n", |
| 389 | "out = keras.layers.Dense(4,activation='softmax')(x)\n", |
| 390 | "model = keras.models.Model(inp,out)\n", |
| 391 | "model.summary()\n", |
| 392 | "\n", |
| 393 | "model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n", |
| 394 | "model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))\n" |
| 395 | ] |
| 396 | }, |
| 397 | { |
| 398 | "cell_type": "markdown", |
| 399 | "metadata": {}, |
| 400 | "source": [ |
| 401 | "## Bigramas, trigramas y n-gramas\n", |
| 402 | "\n", |
| 403 | "Una limitación del enfoque de bolsa de palabras es que algunas palabras forman parte de expresiones de varias palabras. Por ejemplo, la palabra 'hot dog' tiene un significado completamente diferente al de las palabras 'hot' y 'dog' en otros contextos. Si representamos las palabras 'hot' y 'dog' siempre usando los mismos vectores, esto puede confundir a nuestro modelo.\n", |
| 404 | "\n", |
| 405 | "Para abordar este problema, las **representaciones n-gram** se utilizan con frecuencia en métodos de clasificación de documentos, donde la frecuencia de cada palabra, bi-palabra o tri-palabra es una característica útil para entrenar clasificadores. En las representaciones de bigramas, por ejemplo, añadiremos todos los pares de palabras al vocabulario, además de las palabras originales.\n", |
| 406 | "\n", |
| 407 | "A continuación, se muestra un ejemplo de cómo generar una representación de bolsa de palabras con bigramas utilizando Scikit Learn:\n" |
| 408 | ] |
| 409 | }, |
| 410 | { |
| 411 | "cell_type": "code", |
| 412 | "execution_count": 14, |
| 413 | "metadata": {}, |
| 414 | "outputs": [ |
| 415 | { |
| 416 | "name": "stdout", |
| 417 | "output_type": "stream", |
| 418 | "text": [ |
| 419 | "Vocabulary:\n", |
| 420 | " {'i': 7, 'like': 11, 'hot': 4, 'dogs': 2, 'i like': 8, 'like hot': 12, 'hot dogs': 5, 'the': 16, 'dog': 0, 'ran': 14, 'fast': 3, 'the dog': 17, 'dog ran': 1, 'ran fast': 15, 'its': 9, 'outside': 13, 'its hot': 10, 'hot outside': 6}\n" |
| 421 | ] |
| 422 | }, |
| 423 | { |
| 424 | "data": { |
| 425 | "text/plain": [ |
| 426 | "array([[1, 0, 1, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],\n", |
| 427 | " dtype=int64)" |
| 428 | ] |
| 429 | }, |
| 430 | "execution_count": 14, |
| 431 | "metadata": {}, |
| 432 | "output_type": "execute_result" |
| 433 | } |
| 434 | ], |
| 435 | "source": [ |
| 436 | "bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\\b\\w+\\b', min_df=1)\n", |
| 437 | "corpus = [\n", |
| 438 | " 'I like hot dogs.',\n", |
| 439 | " 'The dog ran fast.',\n", |
| 440 | " 'Its hot outside.',\n", |
| 441 | " ]\n", |
| 442 | "bigram_vectorizer.fit_transform(corpus)\n", |
| 443 | "print(\"Vocabulary:\\n\",bigram_vectorizer.vocabulary_)\n", |
| 444 | "bigram_vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()\n" |
| 445 | ] |
| 446 | }, |
| 447 | { |
| 448 | "cell_type": "markdown", |
| 449 | "metadata": {}, |
| 450 | "source": [ |
| 451 | "La principal desventaja del enfoque de n-gramas es que el tamaño del vocabulario comienza a crecer extremadamente rápido. En la práctica, necesitamos combinar la representación de n-gramas con una técnica de reducción de dimensionalidad, como *embeddings*, que discutiremos en la próxima unidad.\n", |
| 452 | "\n", |
| 453 | "Para usar una representación de n-gramas en nuestro conjunto de datos **AG News**, necesitamos pasar el parámetro `ngrams` al constructor de `TextVectorization`. ¡La longitud de un vocabulario de bigramas es **significativamente mayor**! En nuestro caso, ¡es más de 1.3 millones de tokens! Por lo tanto, tiene sentido limitar también los tokens de bigramas a un número razonable.\n", |
| 454 | "\n", |
| 455 | "Podríamos usar el mismo código que arriba para entrenar el clasificador, sin embargo, sería muy ineficiente en términos de memoria. En la próxima unidad, entrenaremos el clasificador de bigramas utilizando embeddings. Mientras tanto, puedes experimentar con el entrenamiento del clasificador de bigramas en este notebook y ver si puedes obtener una mayor precisión.\n" |
| 456 | ] |
| 457 | }, |
| 458 | { |
| 459 | "cell_type": "markdown", |
| 460 | "metadata": {}, |
| 461 | "source": [ |
| 462 | "## Calculando automáticamente vectores BoW\n", |
| 463 | "\n", |
| 464 | "En el ejemplo anterior calculamos los vectores BoW manualmente sumando las codificaciones one-hot de palabras individuales. Sin embargo, la última versión de TensorFlow nos permite calcular los vectores BoW automáticamente al pasar el parámetro `output_mode='count` al constructor del vectorizador. Esto hace que definir y entrenar nuestro modelo sea significativamente más fácil:\n" |
| 465 | ] |
| 466 | }, |
| 467 | { |
| 468 | "cell_type": "code", |
| 469 | "execution_count": 15, |
| 470 | "metadata": {}, |
| 471 | "outputs": [ |
| 472 | { |
| 473 | "name": "stdout", |
| 474 | "output_type": "stream", |
| 475 | "text": [ |
| 476 | "Training vectorizer\n", |
| 477 | "938/938 [==============================] - 7s 7ms/step - loss: 0.5929 - acc: 0.8486 - val_loss: 0.4168 - val_acc: 0.8772\n" |
| 478 | ] |
| 479 | }, |
| 480 | { |
| 481 | "data": { |
| 482 | "text/plain": [ |
| 483 | "<keras.callbacks.History at 0x20c725217c0>" |
| 484 | ] |
| 485 | }, |
| 486 | "execution_count": 15, |
| 487 | "metadata": {}, |
| 488 | "output_type": "execute_result" |
| 489 | } |
| 490 | ], |
| 491 | "source": [ |
| 492 | "model = keras.models.Sequential([\n", |
| 493 | " keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='count'),\n", |
| 494 | " keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n", |
| 495 | "])\n", |
| 496 | "print(\"Training vectorizer\")\n", |
| 497 | "model.layers[0].adapt(ds_train.take(500).map(extract_text))\n", |
| 498 | "model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n", |
| 499 | "model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))" |
| 500 | ] |
| 501 | }, |
| 502 | { |
| 503 | "cell_type": "markdown", |
| 504 | "metadata": {}, |
| 505 | "source": [ |
| 506 | "## Frecuencia de término - frecuencia inversa de documento (TF-IDF)\n", |
| 507 | "\n", |
| 508 | "En la representación BoW, las ocurrencias de palabras se ponderan utilizando la misma técnica sin importar la palabra en sí. Sin embargo, está claro que palabras frecuentes como *a* e *in* son mucho menos importantes para la clasificación que términos especializados. En la mayoría de las tareas de PLN, algunas palabras son más relevantes que otras.\n", |
| 509 | "\n", |
| 510 | "**TF-IDF** significa **frecuencia de término - frecuencia inversa de documento**. Es una variación de bolsa de palabras, donde en lugar de un valor binario 0/1 que indica la aparición de una palabra en un documento, se utiliza un valor de punto flotante, que está relacionado con la frecuencia de aparición de la palabra en el corpus.\n", |
| 511 | "\n", |
| 512 | "De manera más formal, el peso $w_{ij}$ de una palabra $i$ en el documento $j$ se define como:\n", |
| 513 | "$$\n", |
| 514 | "w_{ij} = tf_{ij}\\times\\log({N\\over df_i})\n", |
| 515 | "$$\n", |
| 516 | "donde\n", |
| 517 | "* $tf_{ij}$ es el número de ocurrencias de $i$ en $j$, es decir, el valor de BoW que hemos visto antes\n", |
| 518 | "* $N$ es el número de documentos en la colección\n", |
| 519 | "* $df_i$ es el número de documentos que contienen la palabra $i$ en toda la colección\n", |
| 520 | "\n", |
| 521 | "El valor TF-IDF $w_{ij}$ aumenta proporcionalmente al número de veces que una palabra aparece en un documento y se ajusta por el número de documentos en el corpus que contienen la palabra, lo que ayuda a compensar el hecho de que algunas palabras aparecen con más frecuencia que otras. Por ejemplo, si la palabra aparece en *todos* los documentos de la colección, $df_i=N$, y $w_{ij}=0$, y esos términos serían completamente ignorados.\n", |
| 522 | "\n", |
| 523 | "Puedes crear fácilmente una vectorización TF-IDF de texto utilizando Scikit Learn:\n" |
| 524 | ] |
| 525 | }, |
| 526 | { |
| 527 | "cell_type": "code", |
| 528 | "execution_count": 16, |
| 529 | "metadata": {}, |
| 530 | "outputs": [ |
| 531 | { |
| 532 | "data": { |
| 533 | "text/plain": [ |
| 534 | "array([[0.43381609, 0. , 0.43381609, 0. , 0.65985664,\n", |
| 535 | " 0.43381609, 0. , 0. , 0. , 0. ,\n", |
| 536 | " 0. , 0. , 0. , 0. , 0. ,\n", |
| 537 | " 0. ]])" |
| 538 | ] |
| 539 | }, |
| 540 | "execution_count": 16, |
| 541 | "metadata": {}, |
| 542 | "output_type": "execute_result" |
| 543 | } |
| 544 | ], |
| 545 | "source": [ |
| 546 | "from sklearn.feature_extraction.text import TfidfVectorizer\n", |
| 547 | "vectorizer = TfidfVectorizer(ngram_range=(1,2))\n", |
| 548 | "vectorizer.fit_transform(corpus)\n", |
| 549 | "vectorizer.transform(['My dog likes hot dogs on a hot day.']).toarray()" |
| 550 | ] |
| 551 | }, |
| 552 | { |
| 553 | "cell_type": "markdown", |
| 554 | "metadata": {}, |
| 555 | "source": [ |
| 556 | "En Keras, la capa `TextVectorization` puede calcular automáticamente las frecuencias TF-IDF pasando el parámetro `output_mode='tf-idf'`. Repitamos el código que usamos anteriormente para ver si usar TF-IDF aumenta la precisión:\n" |
| 557 | ] |
| 558 | }, |
| 559 | { |
| 560 | "cell_type": "code", |
| 561 | "execution_count": 17, |
| 562 | "metadata": {}, |
| 563 | "outputs": [ |
| 564 | { |
| 565 | "name": "stdout", |
| 566 | "output_type": "stream", |
| 567 | "text": [ |
| 568 | "Training vectorizer\n", |
| 569 | "938/938 [==============================] - 12s 12ms/step - loss: 0.4197 - acc: 0.8662 - val_loss: 0.3432 - val_acc: 0.8849\n" |
| 570 | ] |
| 571 | }, |
| 572 | { |
| 573 | "data": { |
| 574 | "text/plain": [ |
| 575 | "<keras.callbacks.History at 0x20c729dfd30>" |
| 576 | ] |
| 577 | }, |
| 578 | "execution_count": 17, |
| 579 | "metadata": {}, |
| 580 | "output_type": "execute_result" |
| 581 | } |
| 582 | ], |
| 583 | "source": [ |
| 584 | "model = keras.models.Sequential([\n", |
| 585 | " keras.layers.experimental.preprocessing.TextVectorization(max_tokens=vocab_size,output_mode='tf-idf'),\n", |
| 586 | " keras.layers.Dense(4,input_shape=(vocab_size,), activation='softmax')\n", |
| 587 | "])\n", |
| 588 | "print(\"Training vectorizer\")\n", |
| 589 | "model.layers[0].adapt(ds_train.take(500).map(extract_text))\n", |
| 590 | "model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['acc'])\n", |
| 591 | "model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))" |
| 592 | ] |
| 593 | }, |
| 594 | { |
| 595 | "cell_type": "markdown", |
| 596 | "metadata": {}, |
| 597 | "source": [ |
| 598 | "## Conclusión\n", |
| 599 | "\n", |
| 600 | "Aunque las representaciones TF-IDF asignan pesos de frecuencia a diferentes palabras, no son capaces de representar el significado ni el orden. Como dijo el famoso lingüista J. R. Firth en 1935: \"El significado completo de una palabra siempre es contextual, y ningún estudio del significado fuera del contexto puede tomarse en serio\". Más adelante en el curso aprenderemos cómo capturar información contextual del texto utilizando modelos de lenguaje.\n" |
| 601 | ] |
| 602 | }, |
| 603 | { |
| 604 | "cell_type": "markdown", |
| 605 | "metadata": {}, |
| 606 | "source": [ |
| 607 | "\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). Si bien 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 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" |
| 608 | ] |
| 609 | } |
| 610 | ], |
| 611 | "metadata": { |
| 612 | "interpreter": { |
| 613 | "hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5" |
| 614 | }, |
| 615 | "kernel_info": { |
| 616 | "name": "conda-env-py37_tensorflow-py" |
| 617 | }, |
| 618 | "kernelspec": { |
| 619 | "display_name": "py37_tensorflow", |
| 620 | "language": "python", |
| 621 | "name": "python3" |
| 622 | }, |
| 623 | "language_info": { |
| 624 | "codemirror_mode": { |
| 625 | "name": "ipython", |
| 626 | "version": 3 |
| 627 | }, |
| 628 | "file_extension": ".py", |
| 629 | "mimetype": "text/x-python", |
| 630 | "name": "python", |
| 631 | "nbconvert_exporter": "python", |
| 632 | "pygments_lexer": "ipython3", |
| 633 | "version": "3.8.12" |
| 634 | }, |
| 635 | "nteract": { |
| 636 | "version": "nteract-front-end@1.0.0" |
| 637 | }, |
| 638 | "coopTranslator": { |
| 639 | "original_hash": "19b43951d55b377a76209c24c1f017e4", |
| 640 | "translation_date": "2025-08-31T17:19:28+00:00", |
| 641 | "source_file": "lessons/5-NLP/13-TextRep/TextRepresentationTF.ipynb", |
| 642 | "language_code": "es" |
| 643 | } |
| 644 | }, |
| 645 | "nbformat": 4, |
| 646 | "nbformat_minor": 4 |
| 647 | } |