Ejercicio: interpreta la red en términos de qué unidades están encendidas (valor cercano a 1) o apagadas (valor cercano a 0). ¿Puedes ajustar este modelo con dos tres unidades intermedias? Haz varias pruebas: ¿qué dificultades encuentras?
── R CMD build ─────────────────────────────────────────────────────────────────
-* checking for file ‘/tmp/RtmpptQpA3/remotes8fbc1db72913/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK
+* checking for file ‘/tmp/Rtmp7U5hIj/remotes656331cad8dc/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK
* preparing ‘wordVectors’:
* checking DESCRIPTION meta-information ... OK
* cleaning src
@@ -1188,7 +1188,7 @@
Ejemplo
Beginning tokenization to text file at ./cache/noticias_w2v.txt
-
Prepping /tmp/RtmpptQpA3/file8fbc9d92b0a
+
Prepping /tmp/Rtmp7U5hIj/file6563ed5d547
Starting training using file ./cache/noticias_w2v.txt
@@ -1777,7 +1777,7 @@
+<environment: 0x5653dcf026b8>
que es un vector en el espacio de representación de palabras. Ahora sumamos este vector a un sustantivo en singular, y vemos qué palabras están cercas de esta “palabra sintética”:
diff --git a/82-apendice-descenso-estocastico_files/figure-html/unnamed-chunk-24-1.png b/82-apendice-descenso-estocastico_files/figure-html/unnamed-chunk-24-1.png
index 2908239c..96cf4f0b 100644
Binary files a/82-apendice-descenso-estocastico_files/figure-html/unnamed-chunk-24-1.png and b/82-apendice-descenso-estocastico_files/figure-html/unnamed-chunk-24-1.png differ
diff --git a/search.json b/search.json
index 05bc6992..041efb22 100644
--- a/search.json
+++ b/search.json
@@ -228,7 +228,7 @@
"href": "06-redes-neuronales-1.html#interacciones-en-redes-neuronales",
"title": "6 Redes neuronales (intro)",
"section": "6.2 Interacciones en redes neuronales",
- "text": "6.2 Interacciones en redes neuronales\nEs posible capturar interacciones con redes neuronales. Consideremos el siguiente ejemplo simple:\n\nf <- function(x1, x2){\n 2 + 0.1* x1 + 0.1 * x2 + 10 * (x1 - 0.5) * (x2 - 0.5)\n}\ndat <- expand.grid(x1 = seq(0, 1, 0.05), x2 = seq(0, 1, 0.05))\ndat <- dat |> mutate(f = f(x1, x2))\nggplot(dat, aes(x=x1, y=x2)) + geom_tile(aes(fill=f))\n\n\n\n\nEsta función puede entenderse como un o exclusivo: la respuesta es alta sólo cuando \\(x_1\\) y \\(x_2\\) tienen valores opuestos (\\(x_1\\) grande pero \\(x_2\\) chica y viceversa).\nNo es posible modelar correctamente esta función mediante el modelo lineal (sin interacciones). Pero podemos incluir la interacción en el modelo lineal o intentar usar una red neuronal. Primero simulamos unos datos y probamos el modelo logístico con y sin interacciones:\n\nset.seed(322)\nn <- 2000\ndat_ent <- tibble(x1 = rbeta(n, 1, 1), x2 = rbeta(n, 1, 1)) |>\n mutate(f = f(x1, x2)) |>\n mutate(y = f + rnorm(n, 0, 0.1))\nmod_1 <- lm(y ~ x1 + x2, data = dat_ent)\nmod_1\n\n\nCall:\nlm(formula = y ~ x1 + x2, data = dat_ent)\n\nCoefficients:\n(Intercept) x1 x2 \n 1.8936 0.2046 0.2097 \n\n\nEl resultado del modelo lineal no es bueno:\n\ntibble(y_hat = fitted(mod_1), y = dat_ent$y) |> \n ggplot(aes(x = y_hat, y = y)) + geom_point(color = \"red\") +\n geom_abline() +\n coord_obs_pred()\n\n\n\n\nSin embargo, agregando una interacción lo mejoramos considerablemente (examina la raíz del error cuadrático medio, por ejemplo):\n\nmod_2 <- lm(y ~ x1 + x2 + x1:x2, data = dat_ent)\nmod_2\n\n\nCall:\nlm(formula = y ~ x1 + x2 + x1:x2, data = dat_ent)\n\nCoefficients:\n(Intercept) x1 x2 x1:x2 \n 4.499 -4.895 -4.885 9.964 \n\ntibble(y_hat = fitted(mod_2), y = dat_ent$y) |> \n ggplot(aes(x = y_hat, y = y)) + geom_point(color = \"red\") +\n geom_abline() +\n coord_obs_pred()\n\n\n\n\nObservese la gran diferencia de error entre los dos modelos (en este caso, el sobreajuste no es un problema).\nAhora consideramos qué red neuronal puede ser apropiada.\n\ntensorflow::set_random_seed(421) \nmod_inter <- keras_model_sequential()\nmod_inter |> \n layer_dense(units = 4, activation = \"sigmoid\",\n name = \"capa_intermedia\", input_shape = c(2)) |>\n layer_dense(units = 1, name = \"capa_final\",\n activation = \"linear\") \n\n\nmod_inter |> compile(loss = \"mse\", \n optimizer = optimizer_sgd(learning_rate = 0.3, momentum = 0.5))\nhistoria <- mod_inter |> \n fit(dat_ent |> select(x1, x2) |> as.matrix(), dat_ent$y,\n batch_size = 20,\n epochs = 100, verbose = 0)\n\nVerificamos que esta red captura la interacción:\n\npreds <- predict(mod_inter,\n dat |> select(x1, x2) |> as.matrix())\ndat <- dat |> mutate(f_red = preds)\nggplot(dat, aes(x = x1, y = x2)) + \n geom_tile(aes(fill = f_red))\n\n\n\n\n\npreds_ent <- predict(mod_inter, dat_ent |> select(x1, x2) |> as.matrix())\ntibble(pred = preds_ent[,1], f = dat_ent$y) |> \n ggplot(aes(x = pred, y = f)) +\n geom_point() +\n geom_abline(colour = \"red\") +\n coord_obs_pred()\n\n\n\n\nAunque podemos extraer los cálculos de la red ajustada, vamos a hacer el cálculo de la red a mano. La función feed forward es:\n\nbeta <- get_weights(mod_inter)\nfeed_fow <- function(beta, x){\n a <- h(t(beta[[1]]) %*% x + as.matrix(beta[[2]], 2, 1)) \n f <- t(beta[[3]]) %*% a + as.matrix(beta[[4]], 1, 1)\n f\n}\n\nObservación: ¿cómo funciona esta red? Consideremos la capa intermedia (3 unidades) para cuatro casos: \\((0,0), (0,1), (1,0), (1,1)\\):\n\nmat_entrada <- tibble(x_1 = c(0,0,1,1), x_2 = c(0,1,0,1)) |> as.matrix()\ncapa_1 <- keras_model(inputs = mod_inter$input,\n outputs = get_layer(mod_inter, \"capa_intermedia\")$output)\npred_mat <- predict(capa_1, mat_entrada) |> round(2)\nrownames(pred_mat) <- c(\"apagadas\", \"segunda\", \"primera\", \"ambas\")\npred_mat\n\n [,1] [,2] [,3] [,4]\napagadas 0.15 0.00 0.64 0.12\nsegunda 0.59 0.05 0.09 0.01\nprimera 0.01 0.05 0.07 0.60\nambas 0.05 0.56 0.00 0.06\n\n\nLos pesos de la última capa son:\n\nbeta[3:4]\n\n[[1]]\n [,1]\n[1,] -5.422091\n[2,] 4.781280\n[3,] 5.289693\n[4,] -5.083941\n\n[[2]]\n[1] 2.370601\n\n\nEjercicio: interpreta la red en términos de qué unidades están encendidas (valor cercano a 1) o apagadas (valor cercano a 0). ¿Puedes ajustar este modelo con dos tres unidades intermedias? Haz varias pruebas: ¿qué dificultades encuentras?"
+ "text": "6.2 Interacciones en redes neuronales\nEs posible capturar interacciones con redes neuronales. Consideremos el siguiente ejemplo simple:\n\nf <- function(x1, x2){\n 2 + 0.1* x1 + 0.1 * x2 + 10 * (x1 - 0.5) * (x2 - 0.5)\n}\ndat <- expand.grid(x1 = seq(0, 1, 0.05), x2 = seq(0, 1, 0.05))\ndat <- dat |> mutate(f = f(x1, x2))\nggplot(dat, aes(x=x1, y=x2)) + geom_tile(aes(fill=f))\n\n\n\n\nEsta función puede entenderse como un o exclusivo: la respuesta es alta sólo cuando \\(x_1\\) y \\(x_2\\) tienen valores opuestos (\\(x_1\\) grande pero \\(x_2\\) chica y viceversa).\nNo es posible modelar correctamente esta función mediante el modelo lineal (sin interacciones). Pero podemos incluir la interacción en el modelo lineal o intentar usar una red neuronal. Primero simulamos unos datos y probamos el modelo logístico con y sin interacciones:\n\nset.seed(322)\nn <- 2000\ndat_ent <- tibble(x1 = rbeta(n, 1, 1), x2 = rbeta(n, 1, 1)) |>\n mutate(f = f(x1, x2)) |>\n mutate(y = f + rnorm(n, 0, 0.1))\nmod_1 <- lm(y ~ x1 + x2, data = dat_ent)\nmod_1\n\n\nCall:\nlm(formula = y ~ x1 + x2, data = dat_ent)\n\nCoefficients:\n(Intercept) x1 x2 \n 1.8936 0.2046 0.2097 \n\n\nEl resultado del modelo lineal no es bueno:\n\ntibble(y_hat = fitted(mod_1), y = dat_ent$y) |> \n ggplot(aes(x = y_hat, y = y)) + geom_point(color = \"red\") +\n geom_abline() +\n coord_obs_pred()\n\n\n\n\nSin embargo, agregando una interacción lo mejoramos considerablemente (examina la raíz del error cuadrático medio, por ejemplo):\n\nmod_2 <- lm(y ~ x1 + x2 + x1:x2, data = dat_ent)\nmod_2\n\n\nCall:\nlm(formula = y ~ x1 + x2 + x1:x2, data = dat_ent)\n\nCoefficients:\n(Intercept) x1 x2 x1:x2 \n 4.499 -4.895 -4.885 9.964 \n\ntibble(y_hat = fitted(mod_2), y = dat_ent$y) |> \n ggplot(aes(x = y_hat, y = y)) + geom_point(color = \"red\") +\n geom_abline() +\n coord_obs_pred()\n\n\n\n\nObservese la gran diferencia de error entre los dos modelos (en este caso, el sobreajuste no es un problema).\nAhora consideramos qué red neuronal puede ser apropiada.\n\ntensorflow::set_random_seed(421) \nmod_inter <- keras_model_sequential()\nmod_inter |> \n layer_dense(units = 4, activation = \"sigmoid\",\n name = \"capa_intermedia\", input_shape = c(2)) |>\n layer_dense(units = 1, name = \"capa_final\",\n activation = \"linear\") \n\n\nmod_inter |> compile(loss = \"mse\", \n optimizer = optimizer_sgd(learning_rate = 0.3, momentum = 0.5))\nhistoria <- mod_inter |> \n fit(dat_ent |> select(x1, x2) |> as.matrix(), dat_ent$y,\n batch_size = 20,\n epochs = 100, verbose = 0)\n\nVerificamos que esta red captura la interacción:\n\npreds <- predict(mod_inter,\n dat |> select(x1, x2) |> as.matrix())\ndat <- dat |> mutate(f_red = preds)\nggplot(dat, aes(x = x1, y = x2)) + \n geom_tile(aes(fill = f_red))\n\n\n\n\n\npreds_ent <- predict(mod_inter, dat_ent |> select(x1, x2) |> as.matrix())\ntibble(pred = preds_ent[,1], f = dat_ent$y) |> \n ggplot(aes(x = pred, y = f)) +\n geom_point() +\n geom_abline(colour = \"red\") +\n coord_obs_pred()\n\n\n\n\nAunque podemos extraer los cálculos de la red ajustada, vamos a hacer el cálculo de la red a mano. La función feed forward es:\n\nbeta <- get_weights(mod_inter)\nfeed_fow <- function(beta, x){\n a <- h(t(beta[[1]]) %*% x + as.matrix(beta[[2]], 2, 1)) \n f <- t(beta[[3]]) %*% a + as.matrix(beta[[4]], 1, 1)\n f\n}\n\nObservación: ¿cómo funciona esta red? Consideremos la capa intermedia (3 unidades) para cuatro casos: \\((0,0), (0,1), (1,0), (1,1)\\):\n\nmat_entrada <- tibble(x_1 = c(0,0,1,1), x_2 = c(0,1,0,1)) |> as.matrix()\ncapa_1 <- keras_model(inputs = mod_inter$input,\n outputs = get_layer(mod_inter, \"capa_intermedia\")$output)\npred_mat <- predict(capa_1, mat_entrada) |> round(2)\nrownames(pred_mat) <- c(\"apagadas\", \"segunda\", \"primera\", \"ambas\")\npred_mat\n\n [,1] [,2] [,3] [,4]\napagadas 0.15 0.00 0.64 0.12\nsegunda 0.59 0.05 0.09 0.01\nprimera 0.01 0.05 0.07 0.60\nambas 0.05 0.56 0.00 0.06\n\n\nLos pesos de la última capa son:\n\nbeta[3:4]\n\n[[1]]\n [,1]\n[1,] -5.422077\n[2,] 4.781271\n[3,] 5.289690\n[4,] -5.083926\n\n[[2]]\n[1] 2.370607\n\n\nEjercicio: interpreta la red en términos de qué unidades están encendidas (valor cercano a 1) o apagadas (valor cercano a 0). ¿Puedes ajustar este modelo con dos tres unidades intermedias? Haz varias pruebas: ¿qué dificultades encuentras?"
},
{
"objectID": "06-redes-neuronales-1.html#cálculo-en-redes-feed-forward",
@@ -263,7 +263,7 @@
"href": "06-redes-neuronales-1.html#ajuste-de-parámetros-introducción",
"title": "6 Redes neuronales (intro)",
"section": "6.7 Ajuste de parámetros (introducción)",
- "text": "6.7 Ajuste de parámetros (introducción)\nConsideramos la versión con regularización ridge (también llamada L2) de la devianza de entrenamiento como nuestro función objetivo:\n\n\n\n\n\n\nAjuste de redes neuronales\n\n\n\nPara un problema de regresión, ajustamos los pesos \\(\\Theta^{(1)},\\Theta^{(2)},\\ldots, \\Theta^{(L)}\\) de la red intentando minimizar el error cuadrático medio (penalizado) sobre la muestra de entrenamiento: \\[D = -\\frac{1}{2n}\\sum_{i=1}^n (y^{(i)} - f(x^{i}))^2= + \\lambda \\sum_{l=2}^{L} \\sum_{k=1}^{n_{l-1}} \\sum_{j=1}^{n_l}(\\theta_{j,k}^{(l)})^2.\\] Este problema en general no es convexo y puede tener múltiples mínimos.\n\n\nVeremos el proceso de ajuste, selección de arquitectura, etc. más adelante. Por el momento hacemos unas observaciones acerca de este problema de minimización:\n\nHay varios algoritmos para minimizar este error, algunos avanzados incluyendo información de segundo orden (como Newton), pero actualmente las técnicas más populares, para redes grandes, están derivadas de descenso en gradiente. Más específicamente, una variación, que es descenso estocástico.\nQue el algoritmo depende principalmente de multiplicaciones de matrices y acumulaciones implica que puede escalarse de diversas maneras. Una es paralelizando sobre la muestra de entrenamiento (y calcular acumulados al final), pero también se pueden paralelizar las multiplicaciones de matrices (para lo cual los GPUs se prestan muy bien).\nPara redes neuronales, el gradiente se calcula con un algoritmo que se llama back-propagation, que es una aplicación de la regla de la cadena para propagar errores desde la capa de salida a lo largo de todas las capas para ajustar los pesos y sesgos.\nEn estos problemas no buscamos el mínimo global, sino un mínimo local de buen desempeño. Puede haber múltiples mínimos, puntos silla, regiones relativamente planas, precipicios (curvatura alta). Nótese que la simetría implica que podemos obtener la misma red cambiando pesos entre neuronas y las conexiones correspondientes. Esto implica que necesariamente hay varios mínimos.\nTodo esto dificulta el entrenamiento de redes neuronales grandes. Para redes grandes, ni siquiera esperamos a alcanzar un mínimo local, sino que nos a veces detenemos prematuramente cuando obtenemos el mejor desempeño posible.\nPara este problema, no tiene sentido comenzar las iteraciones con todos los pesos igual a cero, pues las unidades de la red son simétricas: no hay nada que distinga una de otra si todos los pesos son iguales. Esto quiere decir que si iteramos, todas las neuronas van a aprender lo mismo.\nEs importante no comenzar valores de los pesos grandes, pues las funciones logísticas pueden quedar en regiones planas donde la minimización es lenta, o podemos tener gradientes demasiado grandes y produzcan inestabilidad en el cálculo del gradiente.\nEl ajuste de la tasa de aprendizaje es un parámetro importante, más delicado que para problemas convexos. Generalmente lo tratamos con un hiperparámetro más que hay que afinar. Tasas demasiado grandes pueden llevarnos a mínimos locales relativamente malos.\nGeneralmente los pesos se inicializan al azar con variables independientes gaussianas o uniformes centradas en cero, y con varianza chica (por ejemplo \\(U(-0.5,0.5)\\)). Una recomendación es usar \\(U(-1/\\sqrt{m}, 1/\\sqrt{m})\\) donde \\(m\\) es el número de entradas. En general, hay que experimentar con este parámetro.\n\nEl proceso para ajustar una red es entonces:\n\nDefinir número de capas ocultas, número de neuronas por cada capa, y un valor del parámetro de regularización. Estandarizar las entradas. Usualmente podemos probar comenzar con una o dos capas ocultas, de tamaño proporcional al número de entradas. Es buena idea comenzar con una red relativamente grande que tienen error bajo de entrenamiento aunque sobreajuste, y después regularizar y refinar su tamaño.\nSeleccionar parámetros al azar para \\(\\Theta^{(2)},\\Theta^{(3)},\\ldots, \\Theta^{(L)}\\). Se toman, por ejemplo, normales con media 0 y varianza chica.\nCorrer un algoritmo de minimización del error mostrada arriba. Es necesario experimentar con los parámetros del algoritmo de minimización.\nVerificar convergencia del algoritmo a un mínimo local (o el algoritmo no está mejorando).\nPredecir usando el modelo ajustado.\n\nFinalmente, podemos probar distintas arquitecturas y valores del parámetros de regularización, para afinar estos parámetros según validación cruzada o una muestra de validación.\n\nEjemplo (regresión)\n\ndat_grasa <- read_csv(file = '../datos/bodyfat.csv') \nset.seed(183)\ngrasa_particion <- initial_split(dat_grasa, 0.5)\ngrasa_ent <- training(grasa_particion)\ngrasa_pr <- testing(grasa_particion)\nnrow(grasa_ent)\n\n[1] 126\n\n\nUna exploración de este conjunto de datos revela algunos datos sospechosos. En particular un individuo con estatura de 30 pulgadas (alrededor de 75 cm), con peso normal. Probablemente no queremos incluir en entrenamiento este caso, y tampoco hacer predicciones para posibles personas que tengan tales dimensiones:\n\nlibrary(patchwork)\ng_1 <- grasa_ent |> ggplot(aes(x = estatura, y = peso)) + \n geom_point()\ng_2 <- grasa_ent |> ggplot(aes(x = abdomen, y = peso)) + \n geom_point()\ng_1 + g_2\n\n\n\n\n\ngrasa_receta <- recipe(grasacorp ~ ., grasa_ent) |> \n step_filter(estatura > 50) |>\n step_normalize(all_predictors()) |> \n prep()\n\n\nlibrary(keras)\n# entrenamiento\nx_grasa <- grasa_receta |> juice() |> \n select(-grasacorp) |> as.matrix()\nvars_nombres <- colnames(x_grasa)\ny_grasa <- grasa_receta |> juice() |> pull(grasacorp)\n# validación\nx_grasa_pr <- grasa_receta |> bake(grasa_pr) |> \n select(-grasacorp) |> as.matrix()\ny_grasa_pr <- grasa_receta |> bake(grasa_pr) |> pull(grasacorp)\n\n\nmodelo_red <- keras_model_sequential() |> \n layer_dense(units = 50, activation = \"sigmoid\") |> \n layer_dense(units = 50, activation = \"sigmoid\") |> \n layer_dense(units = 1, activation = \"linear\")\nmodelo_red |> compile(\n loss = \"mse\", metrics = \"mse\",\n optimizer = optimizer_sgd(learning_rate = 0.01, momentum = 0.9)\n)\n# esto es más eficiente hacerlo con callbacks en general:\nhistoria <- modelo_red |> fit(\n x = x_grasa, y = y_grasa,\n validation_data = list(x_grasa_pr, y_grasa_pr),\n batch_size = 30, epochs = 250, verbose = 1)\n\nVemos que con esta red podemos alcanzar un error de entrenamiento cercano a cero, aún cuando vemos que sobreajusta al evaluar con la muestra de validación.\n\nplot(historia, smooth = FALSE)\n\n\n\n\nNotamos que las predicciones no son muy buenas:\n\npreds <- predict(modelo_red, x_grasa_pr) \npreds |> head()\n\n [,1]\n[1,] 9.117618\n[2,] 22.663441\n[3,] 8.569652\n[4,] 8.558598\n[5,] 8.842549\n[6,] 8.559493\n\n\nY obtenemos el siguiente resultado:\n\ng_1 <- tibble(preds = preds[, 1], y = y_grasa_pr) |> \n ggplot(aes(x = preds, y = y)) + \n geom_point() + \n geom_abline(slope = 1, intercept = 0, color = \"red\") +\n coord_obs_pred()\ng_1\n\n\n\n\nPodemos ahora experimentar con los parámetros del optimizador, número de unidades, número de capas y regularización L2.\n\nCada cambio de número de unidades/capas o regularización requiere ajustes a la tasa de aprendizaje y otros parámetros del optimizador.\nVarias arquitecturas (número de capas y unidades) pueden dar resultados similares. En este caso, usualmente escogemos el modelo más computacionalmente simple, o dependiendo del tipo de errores de cada modelo.\nRecordamos que una consecuencia del sobreajuste es que el error de prueba es mayor que el de entrenamiento (hay un gap de generalización). Para mejorar el desempeño, podemos reducir el error de entrenamiento (reducir sesgo), y/o reducir sobreajuste (gap entre entrenamiento y prueba).\n\n\nmodelo_red_2 <- keras_model_sequential() |> \n layer_dense(units = 30, activation = \"sigmoid\", \n kernel_regularizer = regularizer_l2(0.1)) |> \n layer_dense(units = 30, activation = \"sigmoid\",\n kernel_regularizer = regularizer_l2(0.1)) |> \n layer_dense(units = 1, activation = \"linear\", \n kernel_regularizer = regularizer_l2(0.01))\nmodelo_red_2 |> compile(loss = \"mse\",metrics = c(\"mse\"),\n optimizer = optimizer_sgd(learning_rate = 0.0005, momentum = 0.95)\n)\n# esto es más eficiente hacerlo con callbacks en general:\nhistoria <- modelo_red_2 |> fit(\n x = x_grasa, y = y_grasa,\n validation_data = list(x_grasa_pr, y_grasa_pr),\n batch_size = 30, epochs = 500, verbose = 1)\n\n\nplot(historia, smooth = FALSE)\n\n\n\n\nLa afinación nos da mejores resultados:\n\npreds_2 <- predict(modelo_red_2, x_grasa_pr) \ng_2 <- tibble(preds_2 = preds_2[, 1], y = y_grasa_pr) |> \n ggplot(aes(x = preds_2, y = y)) + \n geom_point() + \n geom_abline(slope = 1, intercept = 0, color = \"red\") +\n coord_obs_pred()\ng_1 + g_2\n\n\n\n\n\n\nResumen\nEjercicio: En nuestra referencia The Deep Learning Book, puedes revisar la sección 11.4.1 (afinación manual) y 11.4.2 (afinación por búsqueda automática). Explica qué efecto tienen sobre sesgo y sobreajuste los siguientes parámetros:\n\nNúmero de capas\nNúmero de unidades por capa oculta\nRegularización L2 de los pesos\nTasa de aprendizaje (ojo, este parámetro es especial)\n\nEn la parte 2 de redes neuronales veremos más estrategias de regularización, y cómo agregar estructura a redes adaptada a problemas particulares. Notamos para por el momento que las redes neuronales con solamente capas conexas no han sido particularmente exitosas para ningún problema particular. Típicamente requieren una gran cantidad de datos y entrenarlas es más difícil que otros métodos. Sin embargo, la historia es diferente cuando escogemos adecuadamente la arquitectura de la red para un problema dado, es decir, qué unidades están conectadas y bajo qué patrones. En estos casos, las conexiones pueden ser relativamente pocas comparadas con redes totalmente conexas con el mismo tamaño de unidades, y si la estructura es correcta, no incurrimos en mucho sesgo. Ejemplos son procesamiento de imágenes, audio, o lenguaje natural."
+ "text": "6.7 Ajuste de parámetros (introducción)\nConsideramos la versión con regularización ridge (también llamada L2) de la devianza de entrenamiento como nuestro función objetivo:\n\n\n\n\n\n\nAjuste de redes neuronales\n\n\n\nPara un problema de regresión, ajustamos los pesos \\(\\Theta^{(1)},\\Theta^{(2)},\\ldots, \\Theta^{(L)}\\) de la red intentando minimizar el error cuadrático medio (penalizado) sobre la muestra de entrenamiento: \\[D = -\\frac{1}{2n}\\sum_{i=1}^n (y^{(i)} - f(x^{i}))^2= + \\lambda \\sum_{l=2}^{L} \\sum_{k=1}^{n_{l-1}} \\sum_{j=1}^{n_l}(\\theta_{j,k}^{(l)})^2.\\] Este problema en general no es convexo y puede tener múltiples mínimos.\n\n\nVeremos el proceso de ajuste, selección de arquitectura, etc. más adelante. Por el momento hacemos unas observaciones acerca de este problema de minimización:\n\nHay varios algoritmos para minimizar este error, algunos avanzados incluyendo información de segundo orden (como Newton), pero actualmente las técnicas más populares, para redes grandes, están derivadas de descenso en gradiente. Más específicamente, una variación, que es descenso estocástico.\nQue el algoritmo depende principalmente de multiplicaciones de matrices y acumulaciones implica que puede escalarse de diversas maneras. Una es paralelizando sobre la muestra de entrenamiento (y calcular acumulados al final), pero también se pueden paralelizar las multiplicaciones de matrices (para lo cual los GPUs se prestan muy bien).\nPara redes neuronales, el gradiente se calcula con un algoritmo que se llama back-propagation, que es una aplicación de la regla de la cadena para propagar errores desde la capa de salida a lo largo de todas las capas para ajustar los pesos y sesgos.\nEn estos problemas no buscamos el mínimo global, sino un mínimo local de buen desempeño. Puede haber múltiples mínimos, puntos silla, regiones relativamente planas, precipicios (curvatura alta). Nótese que la simetría implica que podemos obtener la misma red cambiando pesos entre neuronas y las conexiones correspondientes. Esto implica que necesariamente hay varios mínimos.\nTodo esto dificulta el entrenamiento de redes neuronales grandes. Para redes grandes, ni siquiera esperamos a alcanzar un mínimo local, sino que nos a veces detenemos prematuramente cuando obtenemos el mejor desempeño posible.\nPara este problema, no tiene sentido comenzar las iteraciones con todos los pesos igual a cero, pues las unidades de la red son simétricas: no hay nada que distinga una de otra si todos los pesos son iguales. Esto quiere decir que si iteramos, todas las neuronas van a aprender lo mismo.\nEs importante no comenzar valores de los pesos grandes, pues las funciones logísticas pueden quedar en regiones planas donde la minimización es lenta, o podemos tener gradientes demasiado grandes y produzcan inestabilidad en el cálculo del gradiente.\nEl ajuste de la tasa de aprendizaje es un parámetro importante, más delicado que para problemas convexos. Generalmente lo tratamos con un hiperparámetro más que hay que afinar. Tasas demasiado grandes pueden llevarnos a mínimos locales relativamente malos.\nGeneralmente los pesos se inicializan al azar con variables independientes gaussianas o uniformes centradas en cero, y con varianza chica (por ejemplo \\(U(-0.5,0.5)\\)). Una recomendación es usar \\(U(-1/\\sqrt{m}, 1/\\sqrt{m})\\) donde \\(m\\) es el número de entradas. En general, hay que experimentar con este parámetro.\n\nEl proceso para ajustar una red es entonces:\n\nDefinir número de capas ocultas, número de neuronas por cada capa, y un valor del parámetro de regularización. Estandarizar las entradas. Usualmente podemos probar comenzar con una o dos capas ocultas, de tamaño proporcional al número de entradas. Es buena idea comenzar con una red relativamente grande que tienen error bajo de entrenamiento aunque sobreajuste, y después regularizar y refinar su tamaño.\nSeleccionar parámetros al azar para \\(\\Theta^{(2)},\\Theta^{(3)},\\ldots, \\Theta^{(L)}\\). Se toman, por ejemplo, normales con media 0 y varianza chica.\nCorrer un algoritmo de minimización del error mostrada arriba. Es necesario experimentar con los parámetros del algoritmo de minimización.\nVerificar convergencia del algoritmo a un mínimo local (o el algoritmo no está mejorando).\nPredecir usando el modelo ajustado.\n\nFinalmente, podemos probar distintas arquitecturas y valores del parámetros de regularización, para afinar estos parámetros según validación cruzada o una muestra de validación.\n\nEjemplo (regresión)\n\ndat_grasa <- read_csv(file = '../datos/bodyfat.csv') \nset.seed(183)\ngrasa_particion <- initial_split(dat_grasa, 0.5)\ngrasa_ent <- training(grasa_particion)\ngrasa_pr <- testing(grasa_particion)\nnrow(grasa_ent)\n\n[1] 126\n\n\nUna exploración de este conjunto de datos revela algunos datos sospechosos. En particular un individuo con estatura de 30 pulgadas (alrededor de 75 cm), con peso normal. Probablemente no queremos incluir en entrenamiento este caso, y tampoco hacer predicciones para posibles personas que tengan tales dimensiones:\n\nlibrary(patchwork)\ng_1 <- grasa_ent |> ggplot(aes(x = estatura, y = peso)) + \n geom_point()\ng_2 <- grasa_ent |> ggplot(aes(x = abdomen, y = peso)) + \n geom_point()\ng_1 + g_2\n\n\n\n\n\ngrasa_receta <- recipe(grasacorp ~ ., grasa_ent) |> \n step_filter(estatura > 50) |>\n step_normalize(all_predictors()) |> \n prep()\n\n\nlibrary(keras)\n# entrenamiento\nx_grasa <- grasa_receta |> juice() |> \n select(-grasacorp) |> as.matrix()\nvars_nombres <- colnames(x_grasa)\ny_grasa <- grasa_receta |> juice() |> pull(grasacorp)\n# validación\nx_grasa_pr <- grasa_receta |> bake(grasa_pr) |> \n select(-grasacorp) |> as.matrix()\ny_grasa_pr <- grasa_receta |> bake(grasa_pr) |> pull(grasacorp)\n\n\nmodelo_red <- keras_model_sequential() |> \n layer_dense(units = 50, activation = \"sigmoid\") |> \n layer_dense(units = 50, activation = \"sigmoid\") |> \n layer_dense(units = 1, activation = \"linear\")\nmodelo_red |> compile(\n loss = \"mse\", metrics = \"mse\",\n optimizer = optimizer_sgd(learning_rate = 0.01, momentum = 0.9)\n)\n# esto es más eficiente hacerlo con callbacks en general:\nhistoria <- modelo_red |> fit(\n x = x_grasa, y = y_grasa,\n validation_data = list(x_grasa_pr, y_grasa_pr),\n batch_size = 30, epochs = 250, verbose = 1)\n\nVemos que con esta red podemos alcanzar un error de entrenamiento cercano a cero, aún cuando vemos que sobreajusta al evaluar con la muestra de validación.\n\nplot(historia, smooth = FALSE)\n\n\n\n\nNotamos que las predicciones no son muy buenas:\n\npreds <- predict(modelo_red, x_grasa_pr) \npreds |> head()\n\n [,1]\n[1,] 9.117623\n[2,] 22.663464\n[3,] 8.569628\n[4,] 8.558584\n[5,] 8.842519\n[6,] 8.559466\n\n\nY obtenemos el siguiente resultado:\n\ng_1 <- tibble(preds = preds[, 1], y = y_grasa_pr) |> \n ggplot(aes(x = preds, y = y)) + \n geom_point() + \n geom_abline(slope = 1, intercept = 0, color = \"red\") +\n coord_obs_pred()\ng_1\n\n\n\n\nPodemos ahora experimentar con los parámetros del optimizador, número de unidades, número de capas y regularización L2.\n\nCada cambio de número de unidades/capas o regularización requiere ajustes a la tasa de aprendizaje y otros parámetros del optimizador.\nVarias arquitecturas (número de capas y unidades) pueden dar resultados similares. En este caso, usualmente escogemos el modelo más computacionalmente simple, o dependiendo del tipo de errores de cada modelo.\nRecordamos que una consecuencia del sobreajuste es que el error de prueba es mayor que el de entrenamiento (hay un gap de generalización). Para mejorar el desempeño, podemos reducir el error de entrenamiento (reducir sesgo), y/o reducir sobreajuste (gap entre entrenamiento y prueba).\n\n\nmodelo_red_2 <- keras_model_sequential() |> \n layer_dense(units = 30, activation = \"sigmoid\", \n kernel_regularizer = regularizer_l2(0.1)) |> \n layer_dense(units = 30, activation = \"sigmoid\",\n kernel_regularizer = regularizer_l2(0.1)) |> \n layer_dense(units = 1, activation = \"linear\", \n kernel_regularizer = regularizer_l2(0.01))\nmodelo_red_2 |> compile(loss = \"mse\",metrics = c(\"mse\"),\n optimizer = optimizer_sgd(learning_rate = 0.0005, momentum = 0.95)\n)\n# esto es más eficiente hacerlo con callbacks en general:\nhistoria <- modelo_red_2 |> fit(\n x = x_grasa, y = y_grasa,\n validation_data = list(x_grasa_pr, y_grasa_pr),\n batch_size = 30, epochs = 500, verbose = 1)\n\n\nplot(historia, smooth = FALSE)\n\n\n\n\nLa afinación nos da mejores resultados:\n\npreds_2 <- predict(modelo_red_2, x_grasa_pr) \ng_2 <- tibble(preds_2 = preds_2[, 1], y = y_grasa_pr) |> \n ggplot(aes(x = preds_2, y = y)) + \n geom_point() + \n geom_abline(slope = 1, intercept = 0, color = \"red\") +\n coord_obs_pred()\ng_1 + g_2\n\n\n\n\n\n\nResumen\nEjercicio: En nuestra referencia The Deep Learning Book, puedes revisar la sección 11.4.1 (afinación manual) y 11.4.2 (afinación por búsqueda automática). Explica qué efecto tienen sobre sesgo y sobreajuste los siguientes parámetros:\n\nNúmero de capas\nNúmero de unidades por capa oculta\nRegularización L2 de los pesos\nTasa de aprendizaje (ojo, este parámetro es especial)\n\nEn la parte 2 de redes neuronales veremos más estrategias de regularización, y cómo agregar estructura a redes adaptada a problemas particulares. Notamos para por el momento que las redes neuronales con solamente capas conexas no han sido particularmente exitosas para ningún problema particular. Típicamente requieren una gran cantidad de datos y entrenarlas es más difícil que otros métodos. Sin embargo, la historia es diferente cuando escogemos adecuadamente la arquitectura de la red para un problema dado, es decir, qué unidades están conectadas y bajo qué patrones. En estos casos, las conexiones pueden ser relativamente pocas comparadas con redes totalmente conexas con el mismo tamaño de unidades, y si la estructura es correcta, no incurrimos en mucho sesgo. Ejemplos son procesamiento de imágenes, audio, o lenguaje natural."
},
{
"objectID": "07-intervalos-predictivos.html#inferencia-predictiva-conforme",
@@ -361,7 +361,7 @@
"href": "08-clasificacion-1.html#ejercicio-datos-de-diabetes",
"title": "8 Clasificación y probabilidad",
"section": "8.8 Ejercicio: datos de diabetes",
- "text": "8.8 Ejercicio: datos de diabetes\nYa están divididos los datos en entrenamiento y prueba\n\ndiabetes_ent <- as_tibble(MASS::Pima.tr)\ndiabetes_pr <- as_tibble(MASS::Pima.te)\ndiabetes_ent |> head() |> gt()\n\n\n\n\n\n \n \n \n npreg\n glu\n bp\n skin\n bmi\n ped\n age\n type\n \n \n \n 5\n86\n68\n28\n30.2\n0.364\n24\nNo\n 7\n195\n70\n33\n25.1\n0.163\n55\nYes\n 5\n77\n82\n41\n35.8\n0.156\n35\nNo\n 0\n165\n76\n43\n47.9\n0.259\n26\nNo\n 0\n107\n60\n25\n26.4\n0.133\n23\nNo\n 5\n97\n76\n27\n35.6\n0.378\n52\nYes\n \n \n \n\n\n\ndiabetes_ent$id <- 1:nrow(diabetes_ent)\ndiabetes_pr$id <- 1:nrow(diabetes_pr)\n\nAunque no es necesario, podemos normalizar:\n\nreceta_diabetes <- recipe(type ~ ., diabetes_ent) |>\n update_role(id, new_role = \"id_variable\") |> \n step_normalize(all_numeric()) \ndiabetes_ent_s <- receta_diabetes |> prep() |> juice() \ndiabetes_pr_s <- receta_diabetes |> prep() |> bake(diabetes_pr)\n\n\nmodelo_lineal <- logistic_reg(mode = \"classification\") |> \n set_engine(\"glm\")\nflujo_diabetes <- workflow() |> \n add_model(modelo_lineal) |> \n add_recipe(receta_diabetes)\nflujo_ajustado <- fit(flujo_diabetes, diabetes_ent)\nsaveRDS(flujo_ajustado, \"cache/flujo_ajustado_diabetes.rds\")\nflujo_ajustado\n\n══ Workflow [trained] ══════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: logistic_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n1 Recipe Step\n\n• step_normalize()\n\n── Model ───────────────────────────────────────────────────────────────────────\n\nCall: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)\n\nCoefficients:\n(Intercept) npreg glu bp skin bmi \n -0.95583 0.34734 1.01705 -0.05473 -0.02247 0.51263 \n ped age \n 0.55928 0.45201 \n\nDegrees of Freedom: 199 Total (i.e. Null); 192 Residual\nNull Deviance: 256.4 \nResidual Deviance: 178.4 AIC: 194.4\n\n\nAhora calculamos devianza de prueba y error de clasificación:\n\npreds_prueba <- \n predict(flujo_ajustado, diabetes_pr, type= \"prob\") |> \n bind_cols(predict(flujo_ajustado, diabetes_pr)) |> \n bind_cols(diabetes_pr |> select(type))\npreds_prueba\n\n# A tibble: 332 × 4\n .pred_No .pred_Yes .pred_class type \n <dbl> <dbl> <fct> <fct>\n 1 0.232 0.768 Yes Yes \n 2 0.960 0.0403 No No \n 3 0.975 0.0253 No No \n 4 0.959 0.0413 No Yes \n 5 0.204 0.796 Yes Yes \n 6 0.265 0.735 Yes Yes \n 7 0.590 0.410 No Yes \n 8 0.780 0.220 No No \n 9 0.558 0.442 No No \n10 0.798 0.202 No Yes \n# ℹ 322 more rows\n\n\n\nlevels(preds_prueba$type)\n\n[1] \"No\" \"Yes\"\n\n# ponemos event_level si \"positivo\" no es el primer factor\nmetricas <- metric_set(accuracy, mn_log_loss)\nmetricas(preds_prueba, truth = type, .pred_Yes, estimate = .pred_class, \n event_level = \"second\")\n\n# A tibble: 2 × 3\n .metric .estimator .estimate\n <chr> <chr> <dbl>\n1 accuracy binary 0.801\n2 mn_log_loss binary 0.441\n\n\nVamos a repetir usando keras.\n\nlibrary(keras)\nx_ent <- diabetes_ent_s |> select(-type, -id) |> as.matrix()\ny_ent <- diabetes_ent_s$type == \"Yes\"\nx_prueba <- diabetes_pr_s |> select(-type, -id) |> as.matrix()\ny_prueba <- diabetes_pr_s$type == 'Yes'\n# definición de estructura del modelo (regresión logística)\n# es posible hacerlo con workflows como vimos arriba, \n# pero aquí usamos directamente la interfaz de keras en R\nn_entrena <- nrow(x_ent)\nmodelo_diabetes <- keras_model_sequential() |>\n layer_dense(units = 1, #una sola respuesta,\n activation = \"sigmoid\", # combinar variables linealmente y aplicar función logística\n kernel_initializer = initializer_constant(0), #inicializamos coeficientes en 0\n bias_initializer = initializer_constant(0)) #inicializamos ordenada en 0\n# compilar seleccionando cantidad a minimizar, optimizador y métricas\nmodelo_diabetes |> compile(\n loss = \"binary_crossentropy\", # devianza es entropía cruzada\n optimizer = optimizer_sgd(learning_rate = 0.75), # descenso en gradiente\n metrics = list(\"binary_crossentropy\"))\n# Ahora iteramos\n# Primero probamos con un número bajo de iteraciones\nhistoria <- modelo_diabetes |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 10 # número de iteraciones\n)\nplot(historia)\n\n\n\n\nY ahora podemos correr más iteraciones adicionales:\n\nhistoria <- modelo_diabetes |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 1000, # número de iteraciones\n verbose = 0\n)\n\nLos errores de entrenamiento y prueba son:\n\nevaluate(modelo_diabetes, x_ent, y_ent)\n\n loss binary_crossentropy \n 0.4459766 0.4459766 \n\n\n\nevaluate(modelo_diabetes, x_prueba, y_prueba)\n\n loss binary_crossentropy \n 0.4406986 0.4406986 \n\n\nVeamos que coeficientes obtuvimos:\n\nget_weights(modelo_diabetes)\n\n[[1]]\n [,1]\n[1,] 0.34734303\n[2,] 1.01705003\n[3,] -0.05472931\n[4,] -0.02247146\n[5,] 0.51263183\n[6,] 0.55927491\n[7,] 0.45200703\n\n[[2]]\n[1] -0.9558302\n\n\nque coinciden con los valores que obtuvimos usando regresión logística de glm. La única diferencia es que el algoritmo de optimización que se usa en cada caso es diferente: con keras utilizamos descenso en gradiente, mientras que glm usa Newton-Raphson.\n\nflujo_ajustado |> extract_fit_parsnip()\n\nparsnip model object\n\n\nCall: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)\n\nCoefficients:\n(Intercept) npreg glu bp skin bmi \n -0.95583 0.34734 1.01705 -0.05473 -0.02247 0.51263 \n ped age \n 0.55928 0.45201 \n\nDegrees of Freedom: 199 Total (i.e. Null); 192 Residual\nNull Deviance: 256.4 \nResidual Deviance: 178.4 AIC: 194.4"
+ "text": "8.8 Ejercicio: datos de diabetes\nYa están divididos los datos en entrenamiento y prueba\n\ndiabetes_ent <- as_tibble(MASS::Pima.tr)\ndiabetes_pr <- as_tibble(MASS::Pima.te)\ndiabetes_ent |> head() |> gt()\n\n\n\n\n\n \n \n \n npreg\n glu\n bp\n skin\n bmi\n ped\n age\n type\n \n \n \n 5\n86\n68\n28\n30.2\n0.364\n24\nNo\n 7\n195\n70\n33\n25.1\n0.163\n55\nYes\n 5\n77\n82\n41\n35.8\n0.156\n35\nNo\n 0\n165\n76\n43\n47.9\n0.259\n26\nNo\n 0\n107\n60\n25\n26.4\n0.133\n23\nNo\n 5\n97\n76\n27\n35.6\n0.378\n52\nYes\n \n \n \n\n\n\ndiabetes_ent$id <- 1:nrow(diabetes_ent)\ndiabetes_pr$id <- 1:nrow(diabetes_pr)\n\nAunque no es necesario, podemos normalizar:\n\nreceta_diabetes <- recipe(type ~ ., diabetes_ent) |>\n update_role(id, new_role = \"id_variable\") |> \n step_normalize(all_numeric()) \ndiabetes_ent_s <- receta_diabetes |> prep() |> juice() \ndiabetes_pr_s <- receta_diabetes |> prep() |> bake(diabetes_pr)\n\n\nmodelo_lineal <- logistic_reg(mode = \"classification\") |> \n set_engine(\"glm\")\nflujo_diabetes <- workflow() |> \n add_model(modelo_lineal) |> \n add_recipe(receta_diabetes)\nflujo_ajustado <- fit(flujo_diabetes, diabetes_ent)\nsaveRDS(flujo_ajustado, \"cache/flujo_ajustado_diabetes.rds\")\nflujo_ajustado\n\n══ Workflow [trained] ══════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: logistic_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n1 Recipe Step\n\n• step_normalize()\n\n── Model ───────────────────────────────────────────────────────────────────────\n\nCall: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)\n\nCoefficients:\n(Intercept) npreg glu bp skin bmi \n -0.95583 0.34734 1.01705 -0.05473 -0.02247 0.51263 \n ped age \n 0.55928 0.45201 \n\nDegrees of Freedom: 199 Total (i.e. Null); 192 Residual\nNull Deviance: 256.4 \nResidual Deviance: 178.4 AIC: 194.4\n\n\nAhora calculamos devianza de prueba y error de clasificación:\n\npreds_prueba <- \n predict(flujo_ajustado, diabetes_pr, type= \"prob\") |> \n bind_cols(predict(flujo_ajustado, diabetes_pr)) |> \n bind_cols(diabetes_pr |> select(type))\npreds_prueba\n\n# A tibble: 332 × 4\n .pred_No .pred_Yes .pred_class type \n <dbl> <dbl> <fct> <fct>\n 1 0.232 0.768 Yes Yes \n 2 0.960 0.0403 No No \n 3 0.975 0.0253 No No \n 4 0.959 0.0413 No Yes \n 5 0.204 0.796 Yes Yes \n 6 0.265 0.735 Yes Yes \n 7 0.590 0.410 No Yes \n 8 0.780 0.220 No No \n 9 0.558 0.442 No No \n10 0.798 0.202 No Yes \n# ℹ 322 more rows\n\n\n\nlevels(preds_prueba$type)\n\n[1] \"No\" \"Yes\"\n\n# ponemos event_level si \"positivo\" no es el primer factor\nmetricas <- metric_set(accuracy, mn_log_loss)\nmetricas(preds_prueba, truth = type, .pred_Yes, estimate = .pred_class, \n event_level = \"second\")\n\n# A tibble: 2 × 3\n .metric .estimator .estimate\n <chr> <chr> <dbl>\n1 accuracy binary 0.801\n2 mn_log_loss binary 0.441\n\n\nVamos a repetir usando keras.\n\nlibrary(keras)\nx_ent <- diabetes_ent_s |> select(-type, -id) |> as.matrix()\ny_ent <- diabetes_ent_s$type == \"Yes\"\nx_prueba <- diabetes_pr_s |> select(-type, -id) |> as.matrix()\ny_prueba <- diabetes_pr_s$type == 'Yes'\n# definición de estructura del modelo (regresión logística)\n# es posible hacerlo con workflows como vimos arriba, \n# pero aquí usamos directamente la interfaz de keras en R\nn_entrena <- nrow(x_ent)\nmodelo_diabetes <- keras_model_sequential() |>\n layer_dense(units = 1, #una sola respuesta,\n activation = \"sigmoid\", # combinar variables linealmente y aplicar función logística\n kernel_initializer = initializer_constant(0), #inicializamos coeficientes en 0\n bias_initializer = initializer_constant(0)) #inicializamos ordenada en 0\n# compilar seleccionando cantidad a minimizar, optimizador y métricas\nmodelo_diabetes |> compile(\n loss = \"binary_crossentropy\", # devianza es entropía cruzada\n optimizer = optimizer_sgd(learning_rate = 0.75), # descenso en gradiente\n metrics = list(\"binary_crossentropy\"))\n# Ahora iteramos\n# Primero probamos con un número bajo de iteraciones\nhistoria <- modelo_diabetes |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 10 # número de iteraciones\n)\nplot(historia)\n\n\n\n\nY ahora podemos correr más iteraciones adicionales:\n\nhistoria <- modelo_diabetes |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 1000, # número de iteraciones\n verbose = 0\n)\n\nLos errores de entrenamiento y prueba son:\n\nevaluate(modelo_diabetes, x_ent, y_ent)\n\n loss binary_crossentropy \n 0.4459766 0.4459766 \n\n\n\nevaluate(modelo_diabetes, x_prueba, y_prueba)\n\n loss binary_crossentropy \n 0.4406986 0.4406986 \n\n\nVeamos que coeficientes obtuvimos:\n\nget_weights(modelo_diabetes)\n\n[[1]]\n [,1]\n[1,] 0.34734303\n[2,] 1.01705003\n[3,] -0.05472933\n[4,] -0.02247144\n[5,] 0.51263183\n[6,] 0.55927497\n[7,] 0.45200706\n\n[[2]]\n[1] -0.9558302\n\n\nque coinciden con los valores que obtuvimos usando regresión logística de glm. La única diferencia es que el algoritmo de optimización que se usa en cada caso es diferente: con keras utilizamos descenso en gradiente, mientras que glm usa Newton-Raphson.\n\nflujo_ajustado |> extract_fit_parsnip()\n\nparsnip model object\n\n\nCall: stats::glm(formula = ..y ~ ., family = stats::binomial, data = data)\n\nCoefficients:\n(Intercept) npreg glu bp skin bmi \n -0.95583 0.34734 1.01705 -0.05473 -0.02247 0.51263 \n ped age \n 0.55928 0.45201 \n\nDegrees of Freedom: 199 Total (i.e. Null); 192 Residual\nNull Deviance: 256.4 \nResidual Deviance: 178.4 AIC: 194.4"
},
{
"objectID": "08-clasificacion-1.html#probabilidades-y-pérdida-0-1",
@@ -417,7 +417,7 @@
"href": "09-clasificacion-2.html#perfil-de-un-clasificador-binario-y-curvas-roc",
"title": "9 Decisiones de clasificación",
"section": "9.5 Perfil de un clasificador binario y curvas ROC",
- "text": "9.5 Perfil de un clasificador binario y curvas ROC\nEn lugar de examinar cada punto de corte por separado, podemos hacer el análisis de todos los posibles puntos de corte mediante la curva ROC (receiver operating characteristic, de ingeniería).\n\n\n\n\n\n\nTip\n\n\n\nPara un problema de clasificación binaria, dadas estimaciones \\(\\hat{p}_1(x)\\), la curva ROC grafica todos los pares de (1-especificidad, sensibilidad) para cada posible punto de corte \\(\\hat{p}_1(x) > \\alpha\\).\n\n\n\nEjemplo\n\nroc_tbl <- roc_curve(preds_prueba, \n truth = type, .pred_Yes)\nroc_tbl\n\n# A tibble: 321 × 3\n .threshold specificity sensitivity\n <dbl> <dbl> <dbl>\n 1 -Inf 0 1\n 2 0.00307 0 1\n 3 0.00348 0.00448 1\n 4 0.00410 0.00897 1\n 5 0.00440 0.0135 1\n 6 0.00480 0.0224 1\n 7 0.00490 0.0269 1\n 8 0.00507 0.0314 1\n 9 0.00516 0.0359 1\n10 0.00565 0.0404 1\n# ℹ 311 more rows\n\nggplot(roc_tbl, \n aes(x = 1 - specificity, y = sensitivity)) +\n geom_path(aes(colour = .threshold), size = 1.2) +\n geom_abline(colour = \"gray\") + \n coord_equal() +\n xlab(\"Tasa de falsos positivos\") + \n ylab(\"Sensibilidad\")\n\nWarning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.\nℹ Please use `linewidth` instead.\n\n\n\n\n\nEn esta gráfica podemos ver todos los clasificadores posibles basados en las probabilidades de clase. Podemos usar estas curvas como evaluación de nuestros clasificadores, dejando para más tarde la selección del punto de corte, si esto es necesario (por ejemplo, dependiendo de los costos de cada tipo de error).\nTambién podemos definir una medida resumen del desempeño de un clasificador según esta curva:\n\n\n\n\n\n\nTip\n\n\n\nLa medida AUC (area under the curve) para un clasificador es el área bajo la curva generada por los pares sensibilidad-especificidad de la curva ROC.\n\n\n\nroc_auc(preds_prueba, type, .pred_Yes)\n\n# A tibble: 1 × 3\n .metric .estimator .estimate\n <chr> <chr> <dbl>\n1 roc_auc binary 0.761\n\n\nCuanto más cerca de uno es este valor, mejor discriminan las probabilidades.\nTambién es útil para comparar modelos. Consideremos el modelo de los datos de diabetes que incluyen todas las variables:\n\nmod_2 <- logistic_reg() |> \n set_engine(\"keras\") |> \n set_mode(\"classification\") |>\n set_args(epochs = 250, \n optimizer = optimizer_sgd(lr = 0.3),\n batch_size = nrow(diabetes_ent), \n verbose = FALSE) |> \n fit(type ~ ., juice(receta_diabetes))\npreds_prueba_completo <- predict(\n mod_2, prueba_baked, type ='prob') |> \n bind_cols(prueba_baked)\npreds_prueba_2 <- bind_rows(\n preds_prueba |> mutate(modelo = \"IMC y edad\"),\n preds_prueba_completo |> mutate(modelo = \"Todas\")) |> \n group_by(modelo)\n\nY graficamos juntas:\n\nroc_2_tbl <- roc_curve(preds_prueba_2, type, .pred_Yes)\nggplot(roc_2_tbl, aes(x = 1 - specificity, y = sensitivity)) +\n geom_path(aes(colour = modelo), size = 1.2) +\n geom_abline(colour = \"gray\") + \n coord_equal() +\n xlab(\"Tasa de falsos positivos\") + \n ylab(\"Sensibilidad\")\n\n\n\n\nComparación auc:\n\nroc_auc(preds_prueba_2, type, .pred_Yes) \n\n# A tibble: 2 × 4\n modelo .metric .estimator .estimate\n <chr> <chr> <chr> <dbl>\n1 IMC y edad roc_auc binary 0.761\n2 Todas roc_auc binary 0.866\n\n\nEn este ejemplo, vemos que casi no importa qué perfil de especificidad y sensibilidad busquemos: el clasificador que usa todas las variables domina casi siempre al clasificador que sólo utiliza IMC y edad."
+ "text": "9.5 Perfil de un clasificador binario y curvas ROC\nEn lugar de examinar cada punto de corte por separado, podemos hacer el análisis de todos los posibles puntos de corte mediante la curva ROC (receiver operating characteristic, de ingeniería).\n\n\n\n\n\n\nTip\n\n\n\nPara un problema de clasificación binaria, dadas estimaciones \\(\\hat{p}_1(x)\\), la curva ROC grafica todos los pares de (1-especificidad, sensibilidad) para cada posible punto de corte \\(\\hat{p}_1(x) > \\alpha\\).\n\n\n\nEjemplo\n\nroc_tbl <- roc_curve(preds_prueba, \n truth = type, .pred_Yes)\nroc_tbl\n\n# A tibble: 321 × 3\n .threshold specificity sensitivity\n <dbl> <dbl> <dbl>\n 1 -Inf 0 1\n 2 0.00305 0 1\n 3 0.00346 0.00448 1\n 4 0.00407 0.00897 1\n 5 0.00437 0.0135 1\n 6 0.00476 0.0224 1\n 7 0.00486 0.0269 1\n 8 0.00504 0.0314 1\n 9 0.00512 0.0359 1\n10 0.00562 0.0404 1\n# ℹ 311 more rows\n\nggplot(roc_tbl, \n aes(x = 1 - specificity, y = sensitivity)) +\n geom_path(aes(colour = .threshold), size = 1.2) +\n geom_abline(colour = \"gray\") + \n coord_equal() +\n xlab(\"Tasa de falsos positivos\") + \n ylab(\"Sensibilidad\")\n\nWarning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.\nℹ Please use `linewidth` instead.\n\n\n\n\n\nEn esta gráfica podemos ver todos los clasificadores posibles basados en las probabilidades de clase. Podemos usar estas curvas como evaluación de nuestros clasificadores, dejando para más tarde la selección del punto de corte, si esto es necesario (por ejemplo, dependiendo de los costos de cada tipo de error).\nTambién podemos definir una medida resumen del desempeño de un clasificador según esta curva:\n\n\n\n\n\n\nTip\n\n\n\nLa medida AUC (area under the curve) para un clasificador es el área bajo la curva generada por los pares sensibilidad-especificidad de la curva ROC.\n\n\n\nroc_auc(preds_prueba, type, .pred_Yes)\n\n# A tibble: 1 × 3\n .metric .estimator .estimate\n <chr> <chr> <dbl>\n1 roc_auc binary 0.761\n\n\nCuanto más cerca de uno es este valor, mejor discriminan las probabilidades.\nTambién es útil para comparar modelos. Consideremos el modelo de los datos de diabetes que incluyen todas las variables:\n\nmod_2 <- logistic_reg() |> \n set_engine(\"keras\") |> \n set_mode(\"classification\") |>\n set_args(epochs = 250, \n optimizer = optimizer_sgd(lr = 0.3),\n batch_size = nrow(diabetes_ent), \n verbose = FALSE) |> \n fit(type ~ ., juice(receta_diabetes))\npreds_prueba_completo <- predict(\n mod_2, prueba_baked, type ='prob') |> \n bind_cols(prueba_baked)\npreds_prueba_2 <- bind_rows(\n preds_prueba |> mutate(modelo = \"IMC y edad\"),\n preds_prueba_completo |> mutate(modelo = \"Todas\")) |> \n group_by(modelo)\n\nY graficamos juntas:\n\nroc_2_tbl <- roc_curve(preds_prueba_2, type, .pred_Yes)\nggplot(roc_2_tbl, aes(x = 1 - specificity, y = sensitivity)) +\n geom_path(aes(colour = modelo), size = 1.2) +\n geom_abline(colour = \"gray\") + \n coord_equal() +\n xlab(\"Tasa de falsos positivos\") + \n ylab(\"Sensibilidad\")\n\n\n\n\nComparación auc:\n\nroc_auc(preds_prueba_2, type, .pred_Yes) \n\n# A tibble: 2 × 4\n modelo .metric .estimator .estimate\n <chr> <chr> <chr> <dbl>\n1 IMC y edad roc_auc binary 0.761\n2 Todas roc_auc binary 0.866\n\n\nEn este ejemplo, vemos que casi no importa qué perfil de especificidad y sensibilidad busquemos: el clasificador que usa todas las variables domina casi siempre al clasificador que sólo utiliza IMC y edad."
},
{
"objectID": "09-clasificacion-2.html#curvas-de-precisión-sensibilidad",
@@ -466,7 +466,7 @@
"href": "10-clasificacion-calibracion.html#calibración-de-probabilidades-1",
"title": "10 Calibración de probabilidades",
"section": "10.5 Calibración de probabilidades",
- "text": "10.5 Calibración de probabilidades\nCuando la calibración no es muy buena, es posible seguir el camino de predicción conforme de clase, o podemos también intentar un proceso de calibración. La idea es construir una funcion \\({f}_{cal}\\) tal que si \\(p'(x) = f_{cal}(p(x))\\) , las probabilidades dadas por \\(p'(x)\\) tienen buena calibración.\nLos métodos más comunes son:\n\nAplicar regresión logística (quizá con splines, por ejemplo), usando la probabilidad/score del modelo original como variable de entrada, y la respuesta como la variable de salida.\nAplicar regresión isotónica, que es similar pero restringe a que la calibración preserve el orden de las probabilidades/scores del modelo original.\n\nExisten otros métodos ( ver por ejemplo https://www.tidymodels.org/learn/models/calibration/)\nVeamos un ejemplo donde queremos contruir un modelo para predecir que células están correctamente segmentadas y cuáles no, bajo un método que se llama High content screening. Las clases son PS (poorly segmented) y WS (well segmented):\n\ndata(cells)\ndim(cells)\n\n[1] 2019 58\n\ncells$case <- NULL\ncells |> count(class)\n\n# A tibble: 2 × 2\n class n\n <fct> <int>\n1 PS 1300\n2 WS 719\n\n\n\nlibrary(probably)\n\n\nAttaching package: 'probably'\n\n\nThe following objects are masked from 'package:base':\n\n as.factor, as.ordered\n\nlibrary(discrim)\n\n\nAttaching package: 'discrim'\n\n\nThe following object is masked from 'package:dials':\n\n smoothness\n\nset.seed(128)\nsplit <- initial_split(cells, strata = class)\ncells_tr <- training(split)\ncells_te <- testing(split)\n\ncells_rs <- vfold_cv(cells_tr, v = 10, strata = class) \ndiscrim_wflow <-\n workflow() |> \n add_formula(class ~ .) |> \n add_model(discrim_linear() |> set_mode(\"classification\")) \nmetricas <- metric_set(roc_auc, brier_class)\n\ndiscrim_res <-\n discrim_wflow |>\n fit_resamples(resamples = cells_rs, \n metrics = metricas, \n control = control_resamples(save_pred = TRUE))\n\n→ A | warning: variables are collinear\n\n\nThere were issues with some computations A: x1\n\n\nThere were issues with some computations A: x10\n\n\n\n\ncollect_metrics(discrim_res)\n\n# A tibble: 2 × 6\n .metric .estimator mean n std_err .config \n <chr> <chr> <dbl> <int> <dbl> <chr> \n1 brier_class binary 0.136 10 0.00281 Preprocessor1_Model1\n2 roc_auc binary 0.879 10 0.00411 Preprocessor1_Model1\n\n\n\ncal_plot_breaks(discrim_res, num_breaks = 15)\n\n\n\n\nCorremos ahora validación con regresión logística com ejemplo:\n\nlogit_val <- probably::cal_validate_logistic(discrim_res, \n metrics = metricas, save_pred = TRUE)\ncollect_metrics(logit_val)\n\n# A tibble: 4 × 7\n .metric .type .estimator mean n std_err .config\n <chr> <chr> <chr> <dbl> <int> <dbl> <chr> \n1 brier_class uncalibrated binary 0.136 10 0.00281 config \n2 roc_auc uncalibrated binary 0.879 10 0.00411 config \n3 brier_class calibrated binary 0.134 10 0.00296 config \n4 roc_auc calibrated binary 0.879 10 0.00411 config \n\n\nY el resultado es ahora como sigue:\n\ncollect_predictions(logit_val) |> \n filter(.type == \"calibrated\") |> \n cal_plot_breaks(truth = class, estimate = .pred_PS) \n\n\n\n\n\n\n\n\nAngelopoulos, Anastasios N., y Stephen Bates. 2021. «A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification». https://arxiv.org/abs/2107.07511.\n\n\nKuhn, M., y K. Johnson. 2013. Applied Predictive Modeling. SpringerLink : Bücher. Springer New York. https://books.google.com.mx/books?id=xYRDAAAAQBAJ."
+ "text": "10.5 Calibración de probabilidades\nCuando la calibración no es muy buena, es posible seguir el camino de predicción conforme de clase, o podemos también intentar un proceso de calibración. La idea es construir una funcion \\({f}_{cal}\\) tal que si \\(p'(x) = f_{cal}(p(x))\\) , las probabilidades dadas por \\(p'(x)\\) tienen buena calibración.\nLos métodos más comunes son:\n\nAplicar regresión logística (quizá con splines, por ejemplo), usando la probabilidad/score del modelo original como variable de entrada, y la respuesta como la variable de salida.\nAplicar regresión isotónica, que es similar pero restringe a que la calibración preserve el orden de las probabilidades/scores del modelo original.\n\nExisten otros métodos ( ver por ejemplo https://www.tidymodels.org/learn/models/calibration/)\nVeamos un ejemplo donde queremos contruir un modelo para predecir que células están correctamente segmentadas y cuáles no, bajo un método que se llama High content screening. Las clases son PS (poorly segmented) y WS (well segmented):\n\ndata(cells)\ndim(cells)\n\n[1] 2019 58\n\ncells$case <- NULL\ncells |> count(class)\n\n# A tibble: 2 × 2\n class n\n <fct> <int>\n1 PS 1300\n2 WS 719\n\n\n\nlibrary(probably)\n\n\nAttaching package: 'probably'\n\n\nThe following objects are masked from 'package:base':\n\n as.factor, as.ordered\n\nlibrary(discrim)\n\n\nAttaching package: 'discrim'\n\n\nThe following object is masked from 'package:dials':\n\n smoothness\n\nset.seed(128)\nsplit <- initial_split(cells, strata = class)\ncells_tr <- training(split)\ncells_te <- testing(split)\n\ncells_rs <- vfold_cv(cells_tr, v = 10, strata = class) \ndiscrim_wflow <-\n workflow() |> \n add_formula(class ~ .) |> \n add_model(discrim_linear() |> set_mode(\"classification\")) \nmetricas <- metric_set(roc_auc, brier_class)\n\ndiscrim_res <-\n discrim_wflow |>\n fit_resamples(resamples = cells_rs, \n metrics = metricas, \n control = control_resamples(save_pred = TRUE))\n\n→ A | warning: variables are collinear\n\n\nThere were issues with some computations A: x1\n\n\nThere were issues with some computations A: x3\n\n\nThere were issues with some computations A: x10\n\n\n\n\ncollect_metrics(discrim_res)\n\n# A tibble: 2 × 6\n .metric .estimator mean n std_err .config \n <chr> <chr> <dbl> <int> <dbl> <chr> \n1 brier_class binary 0.136 10 0.00281 Preprocessor1_Model1\n2 roc_auc binary 0.879 10 0.00411 Preprocessor1_Model1\n\n\n\ncal_plot_breaks(discrim_res, num_breaks = 15)\n\n\n\n\nCorremos ahora validación con regresión logística com ejemplo:\n\nlogit_val <- probably::cal_validate_logistic(discrim_res, \n metrics = metricas, save_pred = TRUE)\ncollect_metrics(logit_val)\n\n# A tibble: 4 × 7\n .metric .type .estimator mean n std_err .config\n <chr> <chr> <chr> <dbl> <int> <dbl> <chr> \n1 brier_class uncalibrated binary 0.136 10 0.00281 config \n2 roc_auc uncalibrated binary 0.879 10 0.00411 config \n3 brier_class calibrated binary 0.134 10 0.00296 config \n4 roc_auc calibrated binary 0.879 10 0.00411 config \n\n\nY el resultado es ahora como sigue:\n\ncollect_predictions(logit_val) |> \n filter(.type == \"calibrated\") |> \n cal_plot_breaks(truth = class, estimate = .pred_PS) \n\n\n\n\n\n\n\n\nAngelopoulos, Anastasios N., y Stephen Bates. 2021. «A Gentle Introduction to Conformal Prediction and Distribution-Free Uncertainty Quantification». https://arxiv.org/abs/2107.07511.\n\n\nKuhn, M., y K. Johnson. 2013. Applied Predictive Modeling. SpringerLink : Bücher. Springer New York. https://books.google.com.mx/books?id=xYRDAAAAQBAJ."
},
{
"objectID": "11-val-cruzada.html#validación-cruzada",
@@ -613,7 +613,7 @@
"href": "13-arboles-boosting.html#submuestreo",
"title": "13 Métodos basados en árboles: boosting",
"section": "13.11 Submuestreo",
- "text": "13.11 Submuestreo\nPuede funcionar bien construir cada uno de los árboles con submuestras de la muestra de entrenamiento, como una manera adicional de reducir varianza al construir nuestro predictor (esta idea es parecida a la de los bosques aleatorios, aquí igualmente perturbamos la muestra de entrenamiento en cada paso para evitar sobreajuste). Adicionalmente, este proceso acelera considerablemente las iteraciones de boosting, y en algunos casos sin penalización en desempeño.\nEn boosting se pueden tomar submuestras (una fracción mayor a 0.5 de la muestra de entrenamiento, pero puede ser más chica para conjuntos grandes de entrenamiento) sin reemplazo.\nEste parámetro también puede ser afinado con muestra de validación o validación cruzada.\nFinalmente, hacemos una evaluación correcta de validación cruzada:\n\n#xgboost es el default\nmodelo_boosting <- boost_tree(learn_rate = 0.003, trees = tune(), \n mtry = 5, tree_depth = 4) |> \n set_mode(\"regression\") |> \n set_args(objective = \"reg:squarederror\")\nflujo_casas <- workflow() |> add_recipe(receta_casas) |> add_model(modelo_boosting)\n\n\nnum_arboles_tbl <- tibble(trees = seq(1, 10000, 100))\nset.seed(81)\nparticion_vc <- vfold_cv(casas_entrena, v = 10)\nmis_metricas <- metric_set(mape, rsq)\nresultados <- tune_grid(flujo_casas, particion_vc, grid = num_arboles_tbl, metrics = mis_metricas)\n\n\ncollect_metrics(resultados) |> \n filter(.metric == \"mape\", trees > 10) |> \n ggplot(aes(x = trees, y = mean, ymin = mean - std_err, ymax = mean + std_err)) + \n geom_point() +\n geom_line() + geom_ribbon(alpha = 0.2) + ylab(\"mape val_cruzada\")\n\n\n\n\n\ncollect_metrics(resultados) |> filter(.metric == \"mape\") |> arrange(mean) |> head()\n\n# A tibble: 6 × 7\n trees .metric .estimator mean n std_err .config \n <dbl> <chr> <chr> <dbl> <int> <dbl> <chr> \n1 9901 mape standard 9.62 10 0.516 Preprocessor1_Model100\n2 9801 mape standard 9.62 10 0.516 Preprocessor1_Model099\n3 9701 mape standard 9.62 10 0.516 Preprocessor1_Model098\n4 9301 mape standard 9.62 10 0.515 Preprocessor1_Model094\n5 9401 mape standard 9.62 10 0.515 Preprocessor1_Model095\n6 9201 mape standard 9.62 10 0.515 Preprocessor1_Model093\n\n\nFinalmente podemos guardar el modelo en un formato estándar (R, Python, GCP y otros):\n\nsystem.time(modelo <- xgb.train(d_entrena, verbose = 1, nrounds = 10000, params = params))\n\n user system elapsed \n 15.822 0.695 4.235 \n\nxgb.save(model = modelo, fname = \"./cache/casas_boost.xgb\")\n\n[1] TRUE"
+ "text": "13.11 Submuestreo\nPuede funcionar bien construir cada uno de los árboles con submuestras de la muestra de entrenamiento, como una manera adicional de reducir varianza al construir nuestro predictor (esta idea es parecida a la de los bosques aleatorios, aquí igualmente perturbamos la muestra de entrenamiento en cada paso para evitar sobreajuste). Adicionalmente, este proceso acelera considerablemente las iteraciones de boosting, y en algunos casos sin penalización en desempeño.\nEn boosting se pueden tomar submuestras (una fracción mayor a 0.5 de la muestra de entrenamiento, pero puede ser más chica para conjuntos grandes de entrenamiento) sin reemplazo.\nEste parámetro también puede ser afinado con muestra de validación o validación cruzada.\nFinalmente, hacemos una evaluación correcta de validación cruzada:\n\n#xgboost es el default\nmodelo_boosting <- boost_tree(learn_rate = 0.003, trees = tune(), \n mtry = 5, tree_depth = 4) |> \n set_mode(\"regression\") |> \n set_args(objective = \"reg:squarederror\")\nflujo_casas <- workflow() |> add_recipe(receta_casas) |> add_model(modelo_boosting)\n\n\nnum_arboles_tbl <- tibble(trees = seq(1, 10000, 100))\nset.seed(81)\nparticion_vc <- vfold_cv(casas_entrena, v = 10)\nmis_metricas <- metric_set(mape, rsq)\nresultados <- tune_grid(flujo_casas, particion_vc, grid = num_arboles_tbl, metrics = mis_metricas)\n\n\ncollect_metrics(resultados) |> \n filter(.metric == \"mape\", trees > 10) |> \n ggplot(aes(x = trees, y = mean, ymin = mean - std_err, ymax = mean + std_err)) + \n geom_point() +\n geom_line() + geom_ribbon(alpha = 0.2) + ylab(\"mape val_cruzada\")\n\n\n\n\n\ncollect_metrics(resultados) |> filter(.metric == \"mape\") |> arrange(mean) |> head()\n\n# A tibble: 6 × 7\n trees .metric .estimator mean n std_err .config \n <dbl> <chr> <chr> <dbl> <int> <dbl> <chr> \n1 9901 mape standard 9.62 10 0.516 Preprocessor1_Model100\n2 9801 mape standard 9.62 10 0.516 Preprocessor1_Model099\n3 9701 mape standard 9.62 10 0.516 Preprocessor1_Model098\n4 9301 mape standard 9.62 10 0.515 Preprocessor1_Model094\n5 9401 mape standard 9.62 10 0.515 Preprocessor1_Model095\n6 9201 mape standard 9.62 10 0.515 Preprocessor1_Model093\n\n\nFinalmente podemos guardar el modelo en un formato estándar (R, Python, GCP y otros):\n\nsystem.time(modelo <- xgb.train(d_entrena, verbose = 1, nrounds = 10000, params = params))\n\n user system elapsed \n 11.248 0.447 5.980 \n\nxgb.save(model = modelo, fname = \"./cache/casas_boost.xgb\")\n\n[1] TRUE"
},
{
"objectID": "13-arboles-boosting.html#otros-parámetros",
@@ -669,7 +669,7 @@
"href": "14-interpretacion.html#explicación-de-predicciones",
"title": "14 Interpretación de modelos",
"section": "14.5 Explicación de predicciones",
- "text": "14.5 Explicación de predicciones\nUna tarea común que se puede requerir para transparentar las predicciones es distribuir contribuciones a la predicción de cada variable.\nConsideremos una predictor \\(f(X)\\), y hacemos una predicción \\(f(x)\\) para un caso \\(x = (x_1,x_2,\\ldots, x_p)\\) particular. ¿Cómo contribuye cada variable a la predicción f(x)?\nUna manera común (ojo: no es la única) de asignar contribuciones si el modelo es lineal y no tiene interacciones es la siguiente:\nSi \\(f(X_1,\\ldots, X_p) = \\beta_0 + \\beta_1 X_1 + \\cdots + \\beta_p X_p\\) , y \\(x^* = (x_1^*,x_2^*,\\ldots, x_p^*)\\) es un caso o instancia particular, podemos definir la contribución \\(\\phi_j = \\phi_j (x^*)\\) de la variable \\(j\\) en la predicción como\n\\[\\phi_j = \\beta_j(x_j^* - \\mu_j),\\] donde \\(\\mu_j\\) es la media de la variable \\(x_j\\) (por ejemplo en el conjunto de entrenamiento). Podemos también definir fácilmente la contribución de un subconjunto \\(W\\) de variables dada, como \\[\\phi_W (x) = \\sum_{j\\in W} \\beta_j(x_j^* - \\mu_j)\\].\nContribución es un buen nombre para estas cantidades, pues satisfacen (usando la linealidad y las definiciones de arriba):\n\nLas contribuciones suman la diferencia de la predicción con la predicción media: \\[\\sum_j \\phi_j(x) = f(x) - E(f(x))\\]\nSi una variable satisface \\(\\phi_W (x) = \\phi_{W\\cup j} (x)\\) para cualquier subconjunto de variables \\(W\\), entonces \\(\\phi_j\\) es cero.\nSi nuestro predictor se puede escribir como \\(f(x) = f_1(x) + f_2(x)\\) entonces la contribución de cada variable en la predicción \\(f(x)\\) es la suma de las contribución en \\(f_1\\) y en \\(f_2\\)\nPara cualquier subconjunto de variables, podemos considerar la contribución de una variable \\(j\\) cuando agregamos al subcojunto la variable \\(j\\) como \\(\\phi_{W \\cup j} (x) - \\phi_W (x)\\). Si para cualquier subconjunto \\(W\\) tenemos que las variables \\(j\\) y \\(k\\) tienen la misma contribución al agregarlos a \\(W\\) (para toda \\(W\\)), entonces \\(\\phi_j =\\phi_k\\).\n\nAhora la pregunta es: ¿cómo definimos las contribuciones para un predictor \\(f\\) más complejo? (por ejemplo, un bosque aleatorio). Resulta que el concepto de contribución o atribución es uno que se estudia en teoría de juegos, y se puede demostrar que hay una sola forma de hacerlo cumpliendo las propiedades señaladas arriba: mediante los valores de Shapley.\nAntes de seguir, veremos las dificultades que podemos encontrar para definir la atribución o contribuciones:\n\nEjemplo\nConsideramos un modelo lineal para empezar. Por ejemplo \\[f(x_1, x_2) = \\beta_0 + \\beta_1 x_1 + \\beta_2 x_2,\\] ¿Cómo podemos atribuir contribuciones de \\(x_1\\) y \\(x_2\\) para una entrada particular \\(x^* = (x_1^*, x_2^*).\\)\nEn primer lugar, podríamos ver qué pasa cuando pensamos que no tenemos ninguna variable disponible. Esta cantidad la podríamos definir como (promediando sobre la muestra de entrenamiento): \\[v(0) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{(i)}) = \\beta_0 + \\beta_1 \\bar{x_1} + \\beta_2\\bar{x_2}\\]\nque es la predicción media (usamos linealidad de \\(f\\)).\nAhora supongamos que usamos la variable \\(x_1\\). En este caso, podríamos calcular una predicción promediando sobre la variable \\(x_2\\), que no tenemos disponible:\n\\[v(1) = \\frac{1}{N}\\sum_i f(x_1^{*},x_2^{(i)}) = \\beta_0 + \\beta_1 x_1^* + \\beta_2\\bar{x_2}\\] Entonces la contribución cuando agregamos la variable 1 es: \\[v(1) -v(0) = \\beta_1(x_1^* - \\bar{x_1})\\] Ahora supongamos que ya tenemos la variable 2, y agregamos la variable 1. La predicción con todas las variables es \\[v(1,2) = \\beta_0 + \\beta_1 x_1^* + \\beta_2 x_2^*\\] y cuando solo tenemos la variable \\(2\\) es \\[v(2) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{*}) = \\beta_0 + \\beta_1\\bar{x_1} + \\beta_2 x_2^*, \\] y la contribución en este caso es: \\[v(1,2) - v(2) = \\beta_1 (x_1^* - \\bar{x_1}),\\] que es la misma cantidad que \\(v(1)-v(0)\\). Así que no importa como “incluyamos” la variable 1 bajo este criterio, el resultado es el mismo, y podemos definir la contribución de \\(x_1\\) como definimos arriba para un predictor lineal.\nAhora consideremos el caso más complicado donde tenemos una interacción multiplicativa:\n\\[f(x_1, x_2) = \\beta_0 + \\beta_1 x_1 + \\beta_2 x_2 + \\beta_3x_1x_2\\]\nVeremos qué pasa cuando seguimos la misma idea de arriba: en primer lugar, tenemos\n\\[v(0) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{(i)}) =\n\\beta_0 + \\beta_1 \\overline{x_1} + \\beta_2\\overline{x_2} + \\beta_3\\overline{x_1x_2}\\]\ny \\[v(1) = \\frac{1}{N}\\sum_i f(x_1^{*},x_2^{(i)}) = \\beta_0 + \\beta_1 x_1^* + \\beta_2\\bar{x_2} + (\\beta_3 \\bar{x_2})x_1^*\\] La contribución sería \\[v(1)-v(0) = \\beta_1(x_1^* - \\bar{x_1}) +\\beta_3\\bar{x_2}\\left(x_1^*-\\frac{\\overline{x_1x_2}}{\\bar{x_2}}\\right)\\] Por otro lado, igual que arriba\n\\[v(2) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{*}) = \\beta_0 + \\beta_1\\bar{x_1} + \\beta_2 x_2^* + \\beta_3x_2^*\\bar{x_1}\\]\ny entonces: \\[v(1,2) - v(2) = \\beta_1(x_1^*-\\bar{x_1})+ \\beta_3x_2^*(x_1^* - \\bar{x_1})\\] Y las cantidades \\(v(1) - v(0)\\) y \\(v(1,2)- v(2)\\) no son iguales en este caso. Tenemos dos órdenes distintos para poner las variables en el modelo, así que ponemos:\n\\[\\phi_1 = \\frac{1}{2}(v(1,2)-v(2)) + \\frac{1}{2}(v(1)-v(0)).\\] y análogamente para \\[\\phi_2 = \\frac{1}{2}(v(1,2)-v(1)) + \\frac{1}{2}(v(2)-v(0)).\\]\nNótese que estas cantidades satisfacen: \\(\\phi_1 + \\phi_2 = v(1,2) -v(0) = f(x_1^*, x_2^*) - \\frac{1}{N}\\sum_i f(x_1^{(i)}, x_2^{(i)}),\\)\nes decir, la diferencia entre la predicción que nos interesa y la predicción media, que es la primera propiedad que buscábamos.\nConsideremos ahora el caso de 3 variables, y nos interesa encontrar la contribución de la variable 1. Ahora tenemos 6 órdenes distintos para poner las variables en el predictor. Dos de ellos terminan con \\(v(1,2,3) - v(2,3)\\), uno de ellos contiene la comparación \\(v(1,2)-v(2)\\), uno de ellos contiene la comparación \\(v(1,3)-v(3)\\) y dos de ellos contiene la comparación \\(v(1)-v(0)\\). Así que ponderaríamos:\n\\[\\phi_1 = \\frac{2}{6}(v(1,2,3) - v(2,3)) + \\frac{1}{6}(v(1,2)-v(2)) + \\frac{1}{6}(v(1,3)-v(3)) + \\frac{2}{6}(v(1)-v(0))\\] Puedes checar que \\(\\phi_1+ \\phi_2 +\\phi_3 = f(x_1^*, x_2^*, x_3^*) - \\frac{1}{N}\\sum_i f(x_1^{(i)}, x_2^{(i)}, x_3^{(i)})\\). Los valores así definidos satisfacen todas las propiedades que discutimos arriba.\n\n\n14.5.1 Valores de Shapley\nLa idea principal en los valores de Shapley es que para entender la contribución de una variable, es necesario considerar su contribución según distintos órdenes en el que consideramos las variables.\nDefinimos primero la contribución de un conjunto de variables \\(S\\subset M,\\) donde \\(M =\\{x_1, x_2\\ldots, x_p\\}\\), marginalizando como sigue con la muestra de entrenamiento:\nSuponiendo que \\(S = \\{1,2,3,\\ldots ,k\\}\\) entonces \\[v(S) = \\frac{1}{N}\\sum_i f(x_1^*,x_2^*,\\ldots, x_k^*, x_k^{(i)}, x_{k+1}^{(i)}, \\ldots, x_p^{(i)})\\] Donde se mantienen fijos los elementos de \\(S\\), y promediamos sobre los elementos que no están en \\(S\\). Esto podemos hacerlo para cualquier subconjunto \\(S\\) de variables. Ahora consideramos una variable dada \\(j\\), y consideramos las diferencias: \\[v(S\\cup {j}) - v(S)\\] Es decir, cuánto contribuye la variable \\(j\\) cuando la agregamos a las variables que ya teníamos en \\(S\\).\nEl valor de Shapley de la variable \\(j\\) es\n\\[\\phi_j = \\sum_{S\\subset M, j\\notin S} \\frac{|S|!(M - |S| -1)!}{M!} (v(S\\cup j) -v(S))\\]\nLos ponderadores están dados como discutimos arriba: hay \\(M!\\) posibles ordenamientos de las variables. Los ordenamientos que incluyen la comparación \\(v(S\\cup j) -v(S)\\) satisfacen: las primeras \\(|S|\\) variables pueden haber entrado de \\(|S|!\\) maneras, luego sigue la \\(j\\), y el restante de las variables pueden entrar de \\((M-|S|-1)!\\) maneras.\n\n\nEjemplo: precios de casas\nConsideremos entonces el bosque que construimos arriba para los precios de casas, y consideramos una casa particular:\n\nset.seed(232)\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[21,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\n\nLa suma de los valores phi da (aproximadamente, por el algoritmo usado) la diferencia entre nuestra predicción para esta instancia y la predicción promedio:\n\nsum(shapley_val$results$phi)\n\n[1] -10.40783\n\n\nAhora hacemos otro ejemplo:\n\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[52,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\nsum(shapley_val$results$phi)\n\n[1] -47.69727\n\n\n\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[121,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\n\nObservaciones:\n\nNótese que las contribuciones de cada variable en un mismo nivel puede ser diferente en diferentes instancias, pues los modelos que usamos típicamente incluyen interacciones.\nCalcular sobre todas las posibles permutaciones de las variables es demasiado costoso. Para estimar el valor de Shapley, en iml se toma una muestra de permutaciones, y para cada una se calcula el valor correspondiente \\(v(S\\cup j) - v(S),\\) dependiendo dónde aparece \\(j\\), y promediando sobre las variables que no aparecen en \\(S\\) como mostramos arriba. No es necesario ponderar la muestra de permutaciones.\nExisten también versiones adaptadas a los árboles que son más rápìdas.\n\n\n\nEjemplo\nPara un modelo de clasificación, xgboost calcula los valores de shapley en la escala logit (del predictor lineal)\n\nlibrary(xgboost)\nnse_tbl <- nse_entrena %>% ungroup %>% \n dplyr::select(ab, conex_inte, ocupados, \n num_tvd, educa_jefe, tam_loc, combustible, num_auto)\nmat_x <- model.matrix(ab ~ -1 + ., nse_tbl)\nd_entrena <- xgb.DMatrix(data = mat_x, label = nse_tbl$ab)\nboost_nse <- xgboost(mat_x, label = as.numeric(nse_tbl$ab == \"1\"), \n nrounds = 500, eta = 0.1, \n max_depth = 3, subsample = .5,\n objective = \"binary:logistic\", nthread = 2, verbose = 0)\nshap_nse <- xgb.plot.shap(mat_x, model = boost_nse, top_n = 8, n_col = 2)\n\n\n\npred_base <- mean(nse_tbl$ab==1)\n\n\ndata <- shap_nse$data\ncontrib <- shap_nse$shap_contrib\ndat_g <- tibble(educa_jefe = data[, \"educa_jefe\"], shap_educa = contrib[, \"educa_jefe\"],\n shap_ocupados = contrib[, \"ocupados\"], ) %>% \n sample_n(2000)\nggplot(dat_g, aes(x = educa_jefe, y = shap_educa, colour = shap_ocupados)) + \n geom_jitter(alpha = 0.9, width = 0.5, height = 0) +\n scale_color_gradient(low=\"orange\", high=\"black\")\n\n\n\n\nY podemos ver predicciones individuales:\n\ngraf_caso <- function(data, contrib, ind_caso, pred_base){\n dat_tbl <- tibble(variable = colnames(data), valor = data[ind_caso, ], \n shap = contrib[ind_caso,]) %>% \n unite(\"variable_valor\", variable, valor) %>% \n mutate(variable_valor = fct_reorder(variable_valor, shap))\n pred_logit <- log(pred_base / (1-pred_base)) + sum(dat_tbl$shap)\n pred <- 1 / (1 + exp(-pred_logit))\n sprintf(\"Predicción base: %0.2f, Predicción: %0.2f\", pred_base, pred) %>% print\n ggplot(dat_tbl, aes(x= variable_valor, y = shap, ymax = shap, ymin = 0)) + \n coord_flip() +geom_point() + geom_hline(yintercept = 0, colour = \"gray\") +\n geom_linerange() \n}\ngraf_caso(data, contrib, 10, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.02\"\n\n\n\n\n\n\ngraf_caso(data, contrib, 102, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.91\"\n\n\n\n\n\nDiscusión:\n\nUn valor de contribución que puede ser más apropiado para los valores Shapley es condicionando en cada caso a la información que se tiene durante el proceso de adición de variables. Es decir, usamos \\[v(S) = E_{X_C|X_S}\\left [ f(X_S,X_C) \\, | \\, X_S = x_s^* \\right].\\] Esta cantidad es teóricamente más apropiada para hacer predicciones cuando “no tenemos” las variables de \\(X_C\\). Sin embargo, calcular esta cantidad es considerablemente difícil, pues requiere modelar también la conjunta de \\((X_1,\\ldots, X_p)\\), lo cuál en general es difícil. Hasta en regresión lineal, incluso sin interacciones, no es trivial hacer estos cálculos. Generalmente se utilizan como mostramos arriba. Una desventaja clara del proceso como lo mostramos arriba es que puede ser que al hacer estos promedios, usemos partes del modelo con poca información y malas predicciones. Los valores de Shapley pueden ser ruidosos en este caso.\nLos que mostramos arriba son llamados comunmente valores SHAP o explicación aditiva de Shapley. Existen muchas variaciones de la aplicación de valores de Shapley para entender predictores y es un tema de investigación activo.\n\n\n\n\n\nHastie, Trevor, Robert Tibshirani, y Jerome Friedman. 2017. The Elements of Statistical Learning. Springer Series en Statistics. Springer New York Inc. http://web.stanford.edu/~hastie/ElemStatLearn/."
+ "text": "14.5 Explicación de predicciones\nUna tarea común que se puede requerir para transparentar las predicciones es distribuir contribuciones a la predicción de cada variable.\nConsideremos una predictor \\(f(X)\\), y hacemos una predicción \\(f(x)\\) para un caso \\(x = (x_1,x_2,\\ldots, x_p)\\) particular. ¿Cómo contribuye cada variable a la predicción f(x)?\nUna manera común (ojo: no es la única) de asignar contribuciones si el modelo es lineal y no tiene interacciones es la siguiente:\nSi \\(f(X_1,\\ldots, X_p) = \\beta_0 + \\beta_1 X_1 + \\cdots + \\beta_p X_p\\) , y \\(x^* = (x_1^*,x_2^*,\\ldots, x_p^*)\\) es un caso o instancia particular, podemos definir la contribución \\(\\phi_j = \\phi_j (x^*)\\) de la variable \\(j\\) en la predicción como\n\\[\\phi_j = \\beta_j(x_j^* - \\mu_j),\\] donde \\(\\mu_j\\) es la media de la variable \\(x_j\\) (por ejemplo en el conjunto de entrenamiento). Podemos también definir fácilmente la contribución de un subconjunto \\(W\\) de variables dada, como \\[\\phi_W (x) = \\sum_{j\\in W} \\beta_j(x_j^* - \\mu_j)\\].\nContribución es un buen nombre para estas cantidades, pues satisfacen (usando la linealidad y las definiciones de arriba):\n\nLas contribuciones suman la diferencia de la predicción con la predicción media: \\[\\sum_j \\phi_j(x) = f(x) - E(f(x))\\]\nSi una variable satisface \\(\\phi_W (x) = \\phi_{W\\cup j} (x)\\) para cualquier subconjunto de variables \\(W\\), entonces \\(\\phi_j\\) es cero.\nSi nuestro predictor se puede escribir como \\(f(x) = f_1(x) + f_2(x)\\) entonces la contribución de cada variable en la predicción \\(f(x)\\) es la suma de las contribución en \\(f_1\\) y en \\(f_2\\)\nPara cualquier subconjunto de variables, podemos considerar la contribución de una variable \\(j\\) cuando agregamos al subcojunto la variable \\(j\\) como \\(\\phi_{W \\cup j} (x) - \\phi_W (x)\\). Si para cualquier subconjunto \\(W\\) tenemos que las variables \\(j\\) y \\(k\\) tienen la misma contribución al agregarlos a \\(W\\) (para toda \\(W\\)), entonces \\(\\phi_j =\\phi_k\\).\n\nAhora la pregunta es: ¿cómo definimos las contribuciones para un predictor \\(f\\) más complejo? (por ejemplo, un bosque aleatorio). Resulta que el concepto de contribución o atribución es uno que se estudia en teoría de juegos, y se puede demostrar que hay una sola forma de hacerlo cumpliendo las propiedades señaladas arriba: mediante los valores de Shapley.\nAntes de seguir, veremos las dificultades que podemos encontrar para definir la atribución o contribuciones:\n\nEjemplo\nConsideramos un modelo lineal para empezar. Por ejemplo \\[f(x_1, x_2) = \\beta_0 + \\beta_1 x_1 + \\beta_2 x_2,\\] ¿Cómo podemos atribuir contribuciones de \\(x_1\\) y \\(x_2\\) para una entrada particular \\(x^* = (x_1^*, x_2^*).\\)\nEn primer lugar, podríamos ver qué pasa cuando pensamos que no tenemos ninguna variable disponible. Esta cantidad la podríamos definir como (promediando sobre la muestra de entrenamiento): \\[v(0) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{(i)}) = \\beta_0 + \\beta_1 \\bar{x_1} + \\beta_2\\bar{x_2}\\]\nque es la predicción media (usamos linealidad de \\(f\\)).\nAhora supongamos que usamos la variable \\(x_1\\). En este caso, podríamos calcular una predicción promediando sobre la variable \\(x_2\\), que no tenemos disponible:\n\\[v(1) = \\frac{1}{N}\\sum_i f(x_1^{*},x_2^{(i)}) = \\beta_0 + \\beta_1 x_1^* + \\beta_2\\bar{x_2}\\] Entonces la contribución cuando agregamos la variable 1 es: \\[v(1) -v(0) = \\beta_1(x_1^* - \\bar{x_1})\\] Ahora supongamos que ya tenemos la variable 2, y agregamos la variable 1. La predicción con todas las variables es \\[v(1,2) = \\beta_0 + \\beta_1 x_1^* + \\beta_2 x_2^*\\] y cuando solo tenemos la variable \\(2\\) es \\[v(2) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{*}) = \\beta_0 + \\beta_1\\bar{x_1} + \\beta_2 x_2^*, \\] y la contribución en este caso es: \\[v(1,2) - v(2) = \\beta_1 (x_1^* - \\bar{x_1}),\\] que es la misma cantidad que \\(v(1)-v(0)\\). Así que no importa como “incluyamos” la variable 1 bajo este criterio, el resultado es el mismo, y podemos definir la contribución de \\(x_1\\) como definimos arriba para un predictor lineal.\nAhora consideremos el caso más complicado donde tenemos una interacción multiplicativa:\n\\[f(x_1, x_2) = \\beta_0 + \\beta_1 x_1 + \\beta_2 x_2 + \\beta_3x_1x_2\\]\nVeremos qué pasa cuando seguimos la misma idea de arriba: en primer lugar, tenemos\n\\[v(0) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{(i)}) =\n\\beta_0 + \\beta_1 \\overline{x_1} + \\beta_2\\overline{x_2} + \\beta_3\\overline{x_1x_2}\\]\ny \\[v(1) = \\frac{1}{N}\\sum_i f(x_1^{*},x_2^{(i)}) = \\beta_0 + \\beta_1 x_1^* + \\beta_2\\bar{x_2} + (\\beta_3 \\bar{x_2})x_1^*\\] La contribución sería \\[v(1)-v(0) = \\beta_1(x_1^* - \\bar{x_1}) +\\beta_3\\bar{x_2}\\left(x_1^*-\\frac{\\overline{x_1x_2}}{\\bar{x_2}}\\right)\\] Por otro lado, igual que arriba\n\\[v(2) = \\frac{1}{N}\\sum_i f(x_1^{(i)},x_2^{*}) = \\beta_0 + \\beta_1\\bar{x_1} + \\beta_2 x_2^* + \\beta_3x_2^*\\bar{x_1}\\]\ny entonces: \\[v(1,2) - v(2) = \\beta_1(x_1^*-\\bar{x_1})+ \\beta_3x_2^*(x_1^* - \\bar{x_1})\\] Y las cantidades \\(v(1) - v(0)\\) y \\(v(1,2)- v(2)\\) no son iguales en este caso. Tenemos dos órdenes distintos para poner las variables en el modelo, así que ponemos:\n\\[\\phi_1 = \\frac{1}{2}(v(1,2)-v(2)) + \\frac{1}{2}(v(1)-v(0)).\\] y análogamente para \\[\\phi_2 = \\frac{1}{2}(v(1,2)-v(1)) + \\frac{1}{2}(v(2)-v(0)).\\]\nNótese que estas cantidades satisfacen: \\(\\phi_1 + \\phi_2 = v(1,2) -v(0) = f(x_1^*, x_2^*) - \\frac{1}{N}\\sum_i f(x_1^{(i)}, x_2^{(i)}),\\)\nes decir, la diferencia entre la predicción que nos interesa y la predicción media, que es la primera propiedad que buscábamos.\nConsideremos ahora el caso de 3 variables, y nos interesa encontrar la contribución de la variable 1. Ahora tenemos 6 órdenes distintos para poner las variables en el predictor. Dos de ellos terminan con \\(v(1,2,3) - v(2,3)\\), uno de ellos contiene la comparación \\(v(1,2)-v(2)\\), uno de ellos contiene la comparación \\(v(1,3)-v(3)\\) y dos de ellos contiene la comparación \\(v(1)-v(0)\\). Así que ponderaríamos:\n\\[\\phi_1 = \\frac{2}{6}(v(1,2,3) - v(2,3)) + \\frac{1}{6}(v(1,2)-v(2)) + \\frac{1}{6}(v(1,3)-v(3)) + \\frac{2}{6}(v(1)-v(0))\\] Puedes checar que \\(\\phi_1+ \\phi_2 +\\phi_3 = f(x_1^*, x_2^*, x_3^*) - \\frac{1}{N}\\sum_i f(x_1^{(i)}, x_2^{(i)}, x_3^{(i)})\\). Los valores así definidos satisfacen todas las propiedades que discutimos arriba.\n\n\n14.5.1 Valores de Shapley\nLa idea principal en los valores de Shapley es que para entender la contribución de una variable, es necesario considerar su contribución según distintos órdenes en el que consideramos las variables.\nDefinimos primero la contribución de un conjunto de variables \\(S\\subset M,\\) donde \\(M =\\{x_1, x_2\\ldots, x_p\\}\\), marginalizando como sigue con la muestra de entrenamiento:\nSuponiendo que \\(S = \\{1,2,3,\\ldots ,k\\}\\) entonces \\[v(S) = \\frac{1}{N}\\sum_i f(x_1^*,x_2^*,\\ldots, x_k^*, x_k^{(i)}, x_{k+1}^{(i)}, \\ldots, x_p^{(i)})\\] Donde se mantienen fijos los elementos de \\(S\\), y promediamos sobre los elementos que no están en \\(S\\). Esto podemos hacerlo para cualquier subconjunto \\(S\\) de variables. Ahora consideramos una variable dada \\(j\\), y consideramos las diferencias: \\[v(S\\cup {j}) - v(S)\\] Es decir, cuánto contribuye la variable \\(j\\) cuando la agregamos a las variables que ya teníamos en \\(S\\).\nEl valor de Shapley de la variable \\(j\\) es\n\\[\\phi_j = \\sum_{S\\subset M, j\\notin S} \\frac{|S|!(M - |S| -1)!}{M!} (v(S\\cup j) -v(S))\\]\nLos ponderadores están dados como discutimos arriba: hay \\(M!\\) posibles ordenamientos de las variables. Los ordenamientos que incluyen la comparación \\(v(S\\cup j) -v(S)\\) satisfacen: las primeras \\(|S|\\) variables pueden haber entrado de \\(|S|!\\) maneras, luego sigue la \\(j\\), y el restante de las variables pueden entrar de \\((M-|S|-1)!\\) maneras.\n\n\nEjemplo: precios de casas\nConsideremos entonces el bosque que construimos arriba para los precios de casas, y consideramos una casa particular:\n\nset.seed(232)\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[21,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\n\nLa suma de los valores phi da (aproximadamente, por el algoritmo usado) la diferencia entre nuestra predicción para esta instancia y la predicción promedio:\n\nsum(shapley_val$results$phi)\n\n[1] -10.40783\n\n\nAhora hacemos otro ejemplo:\n\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[52,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\nsum(shapley_val$results$phi)\n\n[1] -47.69727\n\n\n\nshapley_val <- Shapley$new(predictor, x.interest = casas_entrena[121,])\nshapley_val$results <- filter(shapley_val$results, feature %in% vars_usadas)\nshapley_val$plot()\n\n\n\n\nObservaciones:\n\nNótese que las contribuciones de cada variable en un mismo nivel puede ser diferente en diferentes instancias, pues los modelos que usamos típicamente incluyen interacciones.\nCalcular sobre todas las posibles permutaciones de las variables es demasiado costoso. Para estimar el valor de Shapley, en iml se toma una muestra de permutaciones, y para cada una se calcula el valor correspondiente \\(v(S\\cup j) - v(S),\\) dependiendo dónde aparece \\(j\\), y promediando sobre las variables que no aparecen en \\(S\\) como mostramos arriba. No es necesario ponderar la muestra de permutaciones.\nExisten también versiones adaptadas a los árboles que son más rápìdas.\n\n\n\nEjemplo\nPara un modelo de clasificación, xgboost calcula los valores de shapley en la escala logit (del predictor lineal)\n\nlibrary(xgboost)\nnse_tbl <- nse_entrena %>% ungroup %>% \n dplyr::select(ab, conex_inte, ocupados, \n num_tvd, educa_jefe, tam_loc, combustible, num_auto)\nmat_x <- model.matrix(ab ~ -1 + ., nse_tbl)\nd_entrena <- xgb.DMatrix(data = mat_x, label = nse_tbl$ab)\nboost_nse <- xgboost(mat_x, label = as.numeric(nse_tbl$ab == \"1\"), \n nrounds = 500, eta = 0.1, \n max_depth = 3, subsample = .5,\n objective = \"binary:logistic\", nthread = 2, verbose = 0)\nshap_nse <- xgb.plot.shap(mat_x, model = boost_nse, top_n = 8, n_col = 2)\n\n\n\npred_base <- mean(nse_tbl$ab==1)\n\n\ndata <- shap_nse$data\ncontrib <- shap_nse$shap_contrib\ndat_g <- tibble(educa_jefe = data[, \"educa_jefe\"], shap_educa = contrib[, \"educa_jefe\"],\n shap_ocupados = contrib[, \"ocupados\"], ) %>% \n sample_n(2000)\nggplot(dat_g, aes(x = educa_jefe, y = shap_educa, colour = shap_ocupados)) + \n geom_jitter(alpha = 0.9, width = 0.5, height = 0) +\n scale_color_gradient(low=\"orange\", high=\"black\")\n\n\n\n\nY podemos ver predicciones individuales:\n\ngraf_caso <- function(data, contrib, ind_caso, pred_base){\n dat_tbl <- tibble(variable = colnames(data), valor = data[ind_caso, ], \n shap = contrib[ind_caso,]) %>% \n unite(\"variable_valor\", variable, valor) %>% \n mutate(variable_valor = fct_reorder(variable_valor, shap))\n pred_logit <- log(pred_base / (1-pred_base)) + sum(dat_tbl$shap)\n pred <- 1 / (1 + exp(-pred_logit))\n sprintf(\"Predicción base: %0.2f, Predicción: %0.2f\", pred_base, pred) %>% print\n ggplot(dat_tbl, aes(x= variable_valor, y = shap, ymax = shap, ymin = 0)) + \n coord_flip() +geom_point() + geom_hline(yintercept = 0, colour = \"gray\") +\n geom_linerange() \n}\ngraf_caso(data, contrib, 10, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.02\"\n\n\n\n\n\n\ngraf_caso(data, contrib, 102, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.74\"\n\n\n\n\n\nDiscusión:\n\nUn valor de contribución que puede ser más apropiado para los valores Shapley es condicionando en cada caso a la información que se tiene durante el proceso de adición de variables. Es decir, usamos \\[v(S) = E_{X_C|X_S}\\left [ f(X_S,X_C) \\, | \\, X_S = x_s^* \\right].\\] Esta cantidad es teóricamente más apropiada para hacer predicciones cuando “no tenemos” las variables de \\(X_C\\). Sin embargo, calcular esta cantidad es considerablemente difícil, pues requiere modelar también la conjunta de \\((X_1,\\ldots, X_p)\\), lo cuál en general es difícil. Hasta en regresión lineal, incluso sin interacciones, no es trivial hacer estos cálculos. Generalmente se utilizan como mostramos arriba. Una desventaja clara del proceso como lo mostramos arriba es que puede ser que al hacer estos promedios, usemos partes del modelo con poca información y malas predicciones. Los valores de Shapley pueden ser ruidosos en este caso.\nLos que mostramos arriba son llamados comunmente valores SHAP o explicación aditiva de Shapley. Existen muchas variaciones de la aplicación de valores de Shapley para entender predictores y es un tema de investigación activo.\n\n\n\n\n\nHastie, Trevor, Robert Tibshirani, y Jerome Friedman. 2017. The Elements of Statistical Learning. Springer Series en Statistics. Springer New York Inc. http://web.stanford.edu/~hastie/ElemStatLearn/."
},
{
"objectID": "15-reduccion-dim.html#descomposición-aditiva-en-matrices-de-rango-1",
@@ -767,7 +767,7 @@
"href": "16-recom-factorizacion.html#retroalimentación-implícita",
"title": "16 Dimensiones latentes: embeddings",
"section": "16.4 Retroalimentación implícita",
- "text": "16.4 Retroalimentación implícita\nEsta sección está basada en (Hu, Koren, y Volinsky 2008).\nEn el ejemplo que vimos arriba, la retroalimentación es expícita en el sentido de que los usuarios califican los artículos (\\(1-\\) no me gustó nada, hasta \\(5-\\) me gustó mucho). Sin embargo, es común encontrar casos donde no existe esta retroalimentación explícita, y solo tenemos medidas del gusto implícito, por ejemplo:\n\nCuántas veces un usuario ha pedido un cierto artículo.\nQué porcentaje del programa fue visto.\nCuánto tiempo pasó en la página web.\nCuántas veces oyó una canción.\n\nEstos datos tienen la ventaja de que describen acciones del usuario, en lugar de un rating que puede estar influido por sesgos de imagen o de la calificación que “debería” tener un artículo además de la preferencia: quizá disfruto muchísimo Buffy the Vampire Slayer, pero lo califico con un \\(3\\), aunque un documental de ballenas que simplemente me gustó le pongo un \\(5\\). En los datos implícitos se vería de todas formas mi consumo frecuente de Buffy the Vampire Slayer, y quizá unos cuantos de documentales famosos.\nSea \\(r_{ij}\\) una medida implícita como las mencionadas arriba, para el usuario \\(i\\) y el artículo \\(j\\). Ponemos \\(r_{i,j}=0\\) cuando no se ha observado interacción entre este usuario y el artículo.\nUna diferencia importante con los ratings explícitos es que los datos implícitos son en un sentido menos informativos que los explícitos:\n\nPuede ser que el valor de \\(r_{ij}\\) sea relativamente bajo (pocas interacciones), pero de todas formas se trate de un artículo que es muy preferido (por ejemplo, solo vi Star Wars I una vez, pero me gusta mucho, o nunca he encontrado Star Wars I en el catálogo). Esto no pasa con los ratings, pues ratings bajos indican baja preferencia.\nSin embargo, estamos seguros de que niveles altos de interacción (oyó muchas veces una canción, etc.), es indicación de preferencia alta.\nUsualmente la medida \\(r_{ij}\\) no tiene faltantes, o tiene un valor implícito para faltantes. Por ejemplo, si la medida es % de la película que vi, todas las películas con las que no he interactuado tienen \\(r_{ij}=0\\).\n\nAsí que en nuestro modelo no necesariamente queremos predecir directamente la variable \\(r_{ij}\\): puede haber artículos con predicción baja de \\(r_{ij}\\) que descubramos de todas formas van a ser altamente preferidos. Un modelo que haga una predicción de \\(r_{îj}\\) reflejaría más los patrones de consumo actual en lugar de permitirnos descubrir artículos preferidos con los que no necesariamente existe interacción.\n\n16.4.1 Ejemplo\nConsideremos los siguientes usuarios, donde medimos por ejemplo el número de minutos que pasó cada usuario viendo cada película:\n\nimp <- tibble(usuario = 1:6,\n StarWars1 = c(0, 0, 0, 150, 300, 250),\n StarWars2 = c(250, 200, 0, 200, 220,180), \n StarWars3 = c(0, 250, 300, 0, 0, 0),\n Psycho = c(5, 1, 0, 0, 0, 2)) \nimp\n\n# A tibble: 6 × 5\n usuario StarWars1 StarWars2 StarWars3 Psycho\n <int> <dbl> <dbl> <dbl> <dbl>\n1 1 0 250 0 5\n2 2 0 200 250 1\n3 3 0 0 300 0\n4 4 150 200 0 0\n5 5 300 220 0 0\n6 6 250 180 0 2\n\n\nQuiséramos encontrar una manera de considerar los 0’s como información más suave (es decir, alguien puede tener valores bajos de interacción con una película, pero esto no implica necesariamente que no sea preferida). Esto implica que es más importante ajustar los valores altos del indicador implícito de preferencia.\n\nUna solución propuesta en (Hu, Koren, y Volinsky 2008) (e implementada en spark) es darle menos importancia al valor \\(r_{ij}\\) en la construcción de los factores latentes, especialmente si tiene valores bajos.\nPara hacer esto, primero definimos la variable de preferencia\n\\[p_{ij} =\n\\begin{cases}\n1, &\\mbox{si } r_{ij}>0,\\\\\n0, &\\mbox{si } r_{ij}=0.\\\\\n\\end{cases}\\]\nEsta variable \\(p_{ij}\\), cuando vale uno, indica algún nivel de confianza en la preferencia. ¿Pero qué tanto valor debemos darle a esta preferencia? Definimos la confianza como \\[c_{ij} = 1+ \\alpha r_{ui},\\] donde \\(\\alpha\\) es un parámetro que hay que afinar (por ejemplo \\(\\alpha\\) entre \\(1\\) y \\(50\\)). Para predicciones de vistas de TV, en (Hu, Koren, y Volinsky 2008) utilizan \\(\\alpha = 40\\), donde \\(r_{ij}\\) es el número de veces que el usuario ha visto un programa (contando vistas parciales, así que es un número real).\nLa función objetivo (sin regularización) se define como\n\\[\nL = \\sum_{(i,j)} c_{ij}(p_{ij} - \\sum_{l=1}^k u_{i,l}v_{j,l})^2\n\\tag{16.1}\\]\nNótese que :\n\nCuando \\(c_ij\\) es alta (porque \\(r_{i,j}\\) es alta), para minimizar esta cantidad tenemos que hacer la predicción de \\(p_{ij}\\) cercana a 1, pues el error se multiplica por \\(c_{ij}\\). Sin embargo,\nCuando \\(r_{i,j}\\) es bajo, no es tan importante ajustar esta información con precisión: si \\(p_{ij} = 1\\), puede ser que \\(\\sum_{l=1}^k u_{i,l}v_{j,l}\\) sea muy bajo, y si \\(p_{ij}=0\\), puede ser que \\(\\sum_{l=1}^k u_{i,l}v_{j,l}\\) sea cercano a 1 sin afectar tanto el error.\nEsto permite que en el ajuste podamos descubrir artículos con \\(p_{ij}\\) alta para algún usuario, aún cuando \\(r_{ij}\\) es cero o muy chico.\n\n\n\n16.4.2 Ejemplo\nVeamos cómo se ven soluciones de un factor\n\nimp_mat <- imp |> select(-usuario) |> as.matrix()\nerror_explicito <- function(uv){\n u <- matrix(uv[1:6], ncol = 1)\n v <- matrix(uv[7:10], ncol = 1)\n sum((imp_mat - u %*% t(v))^2)\n}\nerror_implicito <- function(uv){\n u <- matrix(uv[1:6], ncol = 1)\n v <- matrix(uv[7:10], ncol = 1)\n pref_mat <- as.numeric(imp_mat > 0) - u %*% t(v)\n confianza <- 1 + 0.1 * imp_mat\n sum((confianza * pref_mat)^2 )\n}\n\nSi intentamos ajustar los ratings implícitos como si fueran explícitos, obtenemos los siguientes ajustados con un solo factor latente:\n\nuv_inicial <- runif(10)\nopt_exp <- optim(par = uv_inicial, error_explicito, method = \"BFGS\")\nopt_exp$par[7:10]\n\n[1] 11.32636293 14.03753092 3.55842584 0.09139421\n\nt(t(opt_exp$par[1:6])) %*% t(opt_exp$par[7:10]) |> round()\n\n [,1] [,2] [,3] [,4]\n[1,] 118 146 37 1\n[2,] 124 154 39 1\n[3,] 36 44 11 0\n[4,] 151 187 47 1\n[5,] 217 269 68 2\n[6,] 180 223 56 1\n\n\nNótese que esta solución no es muy buena: una componente intenta capturar los patrones de consumo de estas cuatro películas.\nSi usamos preferencias y confianza, obtenemos:\n\nopt_imp <- optim(par = uv_inicial, error_implicito, method = \"BFGS\")\nopt_imp$par[7:10]\n\n[1] 1.524054 1.526493 1.519993 0.946767\n\nt(t(opt_imp$par[1:6])) %*% t(opt_imp$par[7:10]) |> round(2)\n\n [,1] [,2] [,3] [,4]\n[1,] 1 1 0.99 0.62\n[2,] 1 1 1.00 0.62\n[3,] 1 1 1.00 0.62\n[4,] 1 1 0.99 0.62\n[5,] 1 1 1.00 0.62\n[6,] 1 1 1.00 0.62\n\n\nque indica que la información en esta matriz es consistente con que todos los usuarios tienen preferencia alta por las tres películas de Star Wars, y menos por la cuarta.\n\nIgual que en los ejemplos anteriores, usualmente se agregan términos de regularización para los vectores renglón \\(u_i\\) y \\(v_j\\).\n\n\n16.4.3 Evaluación para modelos implícitos\nLa evaluación para modelos implícitos no es tan simple como en el caso explícito, pues no estamos modelando directamente los valores observados \\(r_{ij}\\). Medidas como RECM o MAD que usamos en el caso explícito no son tan apropiadas para este problema.\nUna alternativa es, para cada usuario \\(i\\), ordenar los artículos de mayor a menor valor de \\(\\hat{p}_{ij} = u_iv_j^t\\) (canciones, pellículas), y calculamos:\n\\[\nrank = \\frac{\\sum_{j} p_{ij}rank_{i,j}}{\\sum_j p_{ij}}\n\\]\ndonde \\(rank_{ij}\\) es el percentil del artículo \\(j\\) en la lista ordenada de artículos. \\(rank_{ij}=0\\) para el mejor artículo, y \\(rank_{ij}=1\\) para el peor. Es decir, obtenemos valores más bajos si observamos que los usuarios interactúan con artículos que están más arriba en el ranking.\nEsta suma es un promedio sobre los rankings del usuario con \\(p_{ij}=1\\), y menores valores son mejores (quiere decir que hubo alguna preferencia por los items con \\(rank_{ij}\\) bajo, es decir, los mejores de nuestra lista predicha. Es posible también hacer un promedio ponderado por \\(r_{ij}\\): \\[\nrank = \\frac{\\sum_{j} r_{ij}rank_{i,j}}{\\sum_j r_{ij}}\n\\]\nque es lo mismo que la ecuación anterior pero ponderando por el interés mostrado en cada artículo con \\(p_{ij}=1\\).\n\nMenores valores de \\(rank\\) son mejores.\nSi escogemos al azar el ordenamiento de los artículos, el valor esperado de \\(rank_{ij}\\) es \\(0.5\\) (en medio de la lista), lo que implica que el valor esperado de \\(rank\\) es \\(0.50\\). Cualquier modelo con \\(rank\\geq 0.5\\) es peor que dar recomendaciones al azar.\n\nEsta cantidad la podemos evaluar en entrenamiento y en validación. Para construir el conjunto de validación podemos hacer:\n\nEscogemos un número de usuarios para validación (por ejemplo \\(20\\%\\))\nPonemos \\(50\\%\\) de los artículos evaluados por estas personas en validación, por ejemplo.\n\nEstas cantidades dependen de cuántos datos tengamos, como siempre, para tener un tamaño razonable de datos de validación."
+ "text": "16.4 Retroalimentación implícita\nEsta sección está basada en (Hu, Koren, y Volinsky 2008).\nEn el ejemplo que vimos arriba, la retroalimentación es expícita en el sentido de que los usuarios califican los artículos (\\(1-\\) no me gustó nada, hasta \\(5-\\) me gustó mucho). Sin embargo, es común encontrar casos donde no existe esta retroalimentación explícita, y solo tenemos medidas del gusto implícito, por ejemplo:\n\nCuántas veces un usuario ha pedido un cierto artículo.\nQué porcentaje del programa fue visto.\nCuánto tiempo pasó en la página web.\nCuántas veces oyó una canción.\n\nEstos datos tienen la ventaja de que describen acciones del usuario, en lugar de un rating que puede estar influido por sesgos de imagen o de la calificación que “debería” tener un artículo además de la preferencia: quizá disfruto muchísimo Buffy the Vampire Slayer, pero lo califico con un \\(3\\), aunque un documental de ballenas que simplemente me gustó le pongo un \\(5\\). En los datos implícitos se vería de todas formas mi consumo frecuente de Buffy the Vampire Slayer, y quizá unos cuantos de documentales famosos.\nSea \\(r_{ij}\\) una medida implícita como las mencionadas arriba, para el usuario \\(i\\) y el artículo \\(j\\). Ponemos \\(r_{i,j}=0\\) cuando no se ha observado interacción entre este usuario y el artículo.\nUna diferencia importante con los ratings explícitos es que los datos implícitos son en un sentido menos informativos que los explícitos:\n\nPuede ser que el valor de \\(r_{ij}\\) sea relativamente bajo (pocas interacciones), pero de todas formas se trate de un artículo que es muy preferido (por ejemplo, solo vi Star Wars I una vez, pero me gusta mucho, o nunca he encontrado Star Wars I en el catálogo). Esto no pasa con los ratings, pues ratings bajos indican baja preferencia.\nSin embargo, estamos seguros de que niveles altos de interacción (oyó muchas veces una canción, etc.), es indicación de preferencia alta.\nUsualmente la medida \\(r_{ij}\\) no tiene faltantes, o tiene un valor implícito para faltantes. Por ejemplo, si la medida es % de la película que vi, todas las películas con las que no he interactuado tienen \\(r_{ij}=0\\).\n\nAsí que en nuestro modelo no necesariamente queremos predecir directamente la variable \\(r_{ij}\\): puede haber artículos con predicción baja de \\(r_{ij}\\) que descubramos de todas formas van a ser altamente preferidos. Un modelo que haga una predicción de \\(r_{îj}\\) reflejaría más los patrones de consumo actual en lugar de permitirnos descubrir artículos preferidos con los que no necesariamente existe interacción.\n\n16.4.1 Ejemplo\nConsideremos los siguientes usuarios, donde medimos por ejemplo el número de minutos que pasó cada usuario viendo cada película:\n\nimp <- tibble(usuario = 1:6,\n StarWars1 = c(0, 0, 0, 150, 300, 250),\n StarWars2 = c(250, 200, 0, 200, 220,180), \n StarWars3 = c(0, 250, 300, 0, 0, 0),\n Psycho = c(5, 1, 0, 0, 0, 2)) \nimp\n\n# A tibble: 6 × 5\n usuario StarWars1 StarWars2 StarWars3 Psycho\n <int> <dbl> <dbl> <dbl> <dbl>\n1 1 0 250 0 5\n2 2 0 200 250 1\n3 3 0 0 300 0\n4 4 150 200 0 0\n5 5 300 220 0 0\n6 6 250 180 0 2\n\n\nQuiséramos encontrar una manera de considerar los 0’s como información más suave (es decir, alguien puede tener valores bajos de interacción con una película, pero esto no implica necesariamente que no sea preferida). Esto implica que es más importante ajustar los valores altos del indicador implícito de preferencia.\n\nUna solución propuesta en (Hu, Koren, y Volinsky 2008) (e implementada en spark) es darle menos importancia al valor \\(r_{ij}\\) en la construcción de los factores latentes, especialmente si tiene valores bajos.\nPara hacer esto, primero definimos la variable de preferencia\n\\[p_{ij} =\n\\begin{cases}\n1, &\\mbox{si } r_{ij}>0,\\\\\n0, &\\mbox{si } r_{ij}=0.\\\\\n\\end{cases}\\]\nEsta variable \\(p_{ij}\\), cuando vale uno, indica algún nivel de confianza en la preferencia. ¿Pero qué tanto valor debemos darle a esta preferencia? Definimos la confianza como \\[c_{ij} = 1+ \\alpha r_{ui},\\] donde \\(\\alpha\\) es un parámetro que hay que afinar (por ejemplo \\(\\alpha\\) entre \\(1\\) y \\(50\\)). Para predicciones de vistas de TV, en (Hu, Koren, y Volinsky 2008) utilizan \\(\\alpha = 40\\), donde \\(r_{ij}\\) es el número de veces que el usuario ha visto un programa (contando vistas parciales, así que es un número real).\nLa función objetivo (sin regularización) se define como\n\\[\nL = \\sum_{(i,j)} c_{ij}(p_{ij} - \\sum_{l=1}^k u_{i,l}v_{j,l})^2\n\\tag{16.1}\\]\nNótese que :\n\nCuando \\(c_ij\\) es alta (porque \\(r_{i,j}\\) es alta), para minimizar esta cantidad tenemos que hacer la predicción de \\(p_{ij}\\) cercana a 1, pues el error se multiplica por \\(c_{ij}\\). Sin embargo,\nCuando \\(r_{i,j}\\) es bajo, no es tan importante ajustar esta información con precisión: si \\(p_{ij} = 1\\), puede ser que \\(\\sum_{l=1}^k u_{i,l}v_{j,l}\\) sea muy bajo, y si \\(p_{ij}=0\\), puede ser que \\(\\sum_{l=1}^k u_{i,l}v_{j,l}\\) sea cercano a 1 sin afectar tanto el error.\nEsto permite que en el ajuste podamos descubrir artículos con \\(p_{ij}\\) alta para algún usuario, aún cuando \\(r_{ij}\\) es cero o muy chico.\n\n\n\n16.4.2 Ejemplo\nVeamos cómo se ven soluciones de un factor\n\nimp_mat <- imp |> select(-usuario) |> as.matrix()\nerror_explicito <- function(uv){\n u <- matrix(uv[1:6], ncol = 1)\n v <- matrix(uv[7:10], ncol = 1)\n sum((imp_mat - u %*% t(v))^2)\n}\nerror_implicito <- function(uv){\n u <- matrix(uv[1:6], ncol = 1)\n v <- matrix(uv[7:10], ncol = 1)\n pref_mat <- as.numeric(imp_mat > 0) - u %*% t(v)\n confianza <- 1 + 0.1 * imp_mat\n sum((confianza * pref_mat)^2 )\n}\n\nSi intentamos ajustar los ratings implícitos como si fueran explícitos, obtenemos los siguientes ajustados con un solo factor latente:\n\nuv_inicial <- runif(10)\nopt_exp <- optim(par = uv_inicial, error_explicito, method = \"BFGS\")\nopt_exp$par[7:10]\n\n[1] 9.91945290 12.29362409 3.11633998 0.08002767\n\nt(t(opt_exp$par[1:6])) %*% t(opt_exp$par[7:10]) |> round()\n\n [,1] [,2] [,3] [,4]\n[1,] 118 146 37 1\n[2,] 124 154 39 1\n[3,] 36 44 11 0\n[4,] 151 187 47 1\n[5,] 217 269 68 2\n[6,] 180 223 56 1\n\n\nNótese que esta solución no es muy buena: una componente intenta capturar los patrones de consumo de estas cuatro películas.\nSi usamos preferencias y confianza, obtenemos:\n\nopt_imp <- optim(par = uv_inicial, error_implicito, method = \"BFGS\")\nopt_imp$par[7:10]\n\n[1] 0.8903332 0.8917554 0.8879665 0.5530978\n\nt(t(opt_imp$par[1:6])) %*% t(opt_imp$par[7:10]) |> round(2)\n\n [,1] [,2] [,3] [,4]\n[1,] 1 1 0.99 0.62\n[2,] 1 1 1.00 0.62\n[3,] 1 1 1.00 0.62\n[4,] 1 1 0.99 0.62\n[5,] 1 1 1.00 0.62\n[6,] 1 1 1.00 0.62\n\n\nque indica que la información en esta matriz es consistente con que todos los usuarios tienen preferencia alta por las tres películas de Star Wars, y menos por la cuarta.\n\nIgual que en los ejemplos anteriores, usualmente se agregan términos de regularización para los vectores renglón \\(u_i\\) y \\(v_j\\).\n\n\n16.4.3 Evaluación para modelos implícitos\nLa evaluación para modelos implícitos no es tan simple como en el caso explícito, pues no estamos modelando directamente los valores observados \\(r_{ij}\\). Medidas como RECM o MAD que usamos en el caso explícito no son tan apropiadas para este problema.\nUna alternativa es, para cada usuario \\(i\\), ordenar los artículos de mayor a menor valor de \\(\\hat{p}_{ij} = u_iv_j^t\\) (canciones, pellículas), y calculamos:\n\\[\nrank = \\frac{\\sum_{j} p_{ij}rank_{i,j}}{\\sum_j p_{ij}}\n\\]\ndonde \\(rank_{ij}\\) es el percentil del artículo \\(j\\) en la lista ordenada de artículos. \\(rank_{ij}=0\\) para el mejor artículo, y \\(rank_{ij}=1\\) para el peor. Es decir, obtenemos valores más bajos si observamos que los usuarios interactúan con artículos que están más arriba en el ranking.\nEsta suma es un promedio sobre los rankings del usuario con \\(p_{ij}=1\\), y menores valores son mejores (quiere decir que hubo alguna preferencia por los items con \\(rank_{ij}\\) bajo, es decir, los mejores de nuestra lista predicha. Es posible también hacer un promedio ponderado por \\(r_{ij}\\): \\[\nrank = \\frac{\\sum_{j} r_{ij}rank_{i,j}}{\\sum_j r_{ij}}\n\\]\nque es lo mismo que la ecuación anterior pero ponderando por el interés mostrado en cada artículo con \\(p_{ij}=1\\).\n\nMenores valores de \\(rank\\) son mejores.\nSi escogemos al azar el ordenamiento de los artículos, el valor esperado de \\(rank_{ij}\\) es \\(0.5\\) (en medio de la lista), lo que implica que el valor esperado de \\(rank\\) es \\(0.50\\). Cualquier modelo con \\(rank\\geq 0.5\\) es peor que dar recomendaciones al azar.\n\nEsta cantidad la podemos evaluar en entrenamiento y en validación. Para construir el conjunto de validación podemos hacer:\n\nEscogemos un número de usuarios para validación (por ejemplo \\(20\\%\\))\nPonemos \\(50\\%\\) de los artículos evaluados por estas personas en validación, por ejemplo.\n\nEstas cantidades dependen de cuántos datos tengamos, como siempre, para tener un tamaño razonable de datos de validación."
},
{
"objectID": "16-recom-factorizacion.html#modelo-de-red-neuronal",
@@ -788,14 +788,14 @@
"href": "16-recom-factorizacion.html#modelos-de-word2vec",
"title": "16 Dimensiones latentes: embeddings",
"section": "17.3 Modelos de word2vec",
- "text": "17.3 Modelos de word2vec\nEn estos ejemplos veremos cómo producir embeddings de palabras que son precursores de embeddings más refinados como los producidos por Modelos grandes de lenguajes (LLMs). Ver por ejemplo aquí.\nSi lo que principalmente nos interesa es obtener una representación vectorial de palabras, es posible simplificar considerablemente el modelo de arriba o LLMs para poder entrenarlos mucho más rápido, y obtener una representación que en muchas tareas se desempeña bien ((Mikolov et al. 2013)).\nHay dos ideas básicas que se pueden usar para reducir la complejidad del entrenamiento (ver más en (Mikolov et al. 2013)):\n\nEliminar la segunda capa oculta: modelo de bag-of-words continuo y modelo de skip-gram.\nCambiar la función objetivo (minimizar devianza/maximizar verosimilitud) por una más simple, mediante un truco que se llama negative sampling.\n\nComo ya no es de interés central predecir la siguiente palabra a partir de las anteriores, en estos modelos intentamos predecir la palabra central a partir de las que están alrededor.\n\nArquitectura continuous bag-of-words\nLa entrada es igual que en el modelo completo. En primer lugar, simplificamos la segunda capa oculta pondiendo en \\(z\\) el promedio de los vectores \\(C(w_{n-2}), C(w_{n-1})\\). La última capa la dejamos igual por el momento:\n\n\n\nImagen\n\n\nEl modelo se llama bag-of-words porque todas las entradas de la primera capa oculta contribuyen de la misma manera en la salida, independientemente del orden. Aunque esto no suena como buena idea para construir un modelo de lenguaje, veremos que resulta en una representación adecuada para algunos problemas.\n\nEn la primera capa oculta, tenemos un mapeo de las entradas \\(w_1,\\ldots, w_{n-1}\\) a \\(x=C(w_1),\\ldots, C(w_{n-1})\\), donde \\(C\\) es una función que mapea palabras a vectores de dimensión \\(d\\). \\(C\\) también se puede pensar como una matriz de dimensión \\(|V|\\) por \\(d\\). En la capa de entrada,\n\n\\[w_{n-2},w_{n-1} \\to x = (C(w_{n-2}), C(w_{n-1})).\\]\n\nEn la siguiente “capa” oculta simplemente sumamos las entradas de \\(x\\). Aquí nótese que realmente no hay parámetros.\nFinalmente, la capa de salida debe ser un vector de probabilidades sobre todo el vocabulario \\(|V|\\). En esta capa tenemos pesos \\(U\\) y hacemos \\[y = b + U\\sigma (z),\\] y finalmente usamos softmax para tener probabilidades que suman uno: \\[p_i = \\frac{\\exp (y_i) }{\\sum_j exp(y_j)}.\\]\n\nEn el ajuste maximizamos la verosimilitud sobre el corpus. Por ejemplo, para una frase, su log verosimilitud es:\n\\[\\sum_t \\log \\hat{P}(w_{t,n}|w_{t,n+1} \\cdots w_{t-n-1}) \\]\n\n\nArquitectura skip-grams\nOtro modelo simplificado, con más complejidad computacional pero mejores resultados (ver (Mikolov et al. 2013)) que el bag-of-words, es el modelo de skip-grams. En este caso, dada cada palabra que encontramos, intentamos predecir un número fijo de las palabras anteriores y palabras posteriores (el contexto es una vecindad de la palabra).\n\n\n\nImagen\n\n\nLa función objetivo se defina ahora (simplificando) como suma sobre \\(t\\):\n\\[-\\sum_t \\sum_{ -2\\leq j \\leq 2, j\\neq 0} \\log P(w_{t-j} | w_t)\\] (no tomamos en cuenta dónde aparece exactamente \\(w_{t-j}\\) en relación a \\(w_t\\), simplemente consideramos que está en su contexto), donde\n\\[\\log P(w_{t-j}|w_t) = u_{t-j}^tC(w_t) - \\log\\sum_k \\exp{u_{k}^tC(w_t)}\\]\nTodavía se propone una simplificación adicional que resulta ser efectiva:\n\n\nMuestreo negativo\nLa siguiente simplificación consiste en cambiar la función objetivo. En word2vec puede usarse “muestreo negativo”.\nPara empezar, la función objetivo original (para contexto de una sola palabra) es\n\\[E = -\\log \\hat{P}(w_{a}|w_{n}) = -y_{w_a} + \\log\\sum_j \\exp(y_j),\\]\ndonde las \\(y_i\\) son las salidas de la penúltima capa. La dificultad está en el segundo término, que es sobre todo el vocabulario en incluye todos los parámetros del modelo (hay que calcular las parciales de \\(y_j\\)’s sobre cada una de las palabras del vocabulario).\nLa idea del muestreo negativo es que si \\(w_a\\) está en el contexto de \\(w_{n}\\), tomamos una muestra de \\(k\\) palabras \\(v_1,\\ldots v_k\\) al azar (2-50, dependiendo del tamaño de la colección), y creamos \\(k\\) “contextos falsos” \\(v_j w_{n}\\), \\(j=1\\ldots,k\\). Minimizamos en lugar de la observación de arriba\n\\[E = -\\log\\sigma(y_{w_a}) + \\sum_{j=1}^k \\log\\sigma(y_j),\\] en donde queremos maximizar la probabilidad de que ocurra \\(w_a\\) vs. la probabilidad de que ocurra alguna de las \\(v_j\\). Es decir, solo buscamos optimizar parámetros para separar lo mejor que podamos la observación de \\(k\\) observaciones falsas, lo cual implica que tenemos que mover un número relativamente chico de parámetros (en lugar de todos los parámetros de todas las palabras del vocabulario).\nLas palabras “falsas” se escogen según una probabilidad ajustada de unigramas (se observó empíricamente mejor desempeño cuando escogemos cada palabra con probabilidad proporcional a \\(P(w)^{3/4}\\), en lugar de \\(P(w)\\), ver (Mikolov et al. 2013)).\n\n\nEjemplo\n\nif(!require(wordVectors)){\n devtools::install_github(\"bmschmidt/wordVectors\", \n dependencies = TRUE)\n}\n\n\n── R CMD build ─────────────────────────────────────────────────────────────────\n* checking for file ‘/tmp/RtmpptQpA3/remotes8fbc1db72913/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK\n* preparing ‘wordVectors’:\n* checking DESCRIPTION meta-information ... OK\n* cleaning src\n* checking for LF line-endings in source and make files and shell scripts\n* checking for empty or unneeded directories\n* building ‘wordVectors_2.0.tar.gz’\n\nlibrary(wordVectors)\n\n\nlibrary(tidyverse)\nruta <- \"../datos/noticias/ES_Newspapers.txt\"\nif(!file.exists(ruta)){\n periodico <- \n read_lines(file= \"https://es-noticias.s3.amazonaws.com/Es_Newspapers.txt\",\n progress = FALSE)\n write_lines(periodico, ruta)\n} else {\n periodico <- read_lines(file= ruta,\n progress = FALSE)\n}\nnormalizar <- function(texto, vocab = NULL){\n # minúsculas\n texto <- tolower(texto)\n # varios ajustes\n texto <- gsub(\"\\\\s+\", \" \", texto)\n texto <- gsub(\"\\\\.[^0-9]\", \" _punto_ \", texto)\n texto <- gsub(\" _s_ $\", \"\", texto)\n texto <- gsub(\"\\\\.\", \" _punto_ \", texto)\n texto <- gsub(\"[«»¡!¿?-]\", \"\", texto) \n texto <- gsub(\";\", \" _punto_coma_ \", texto) \n texto <- gsub(\"\\\\:\", \" _dos_puntos_ \", texto) \n texto <- gsub(\"\\\\,[^0-9]\", \" _coma_ \", texto)\n texto <- gsub(\"\\\\s+\", \" \", texto)\n texto\n}\nperiodico_df <- tibble(txt = periodico) |>\n mutate(id = row_number()) |>\n mutate(txt = normalizar(txt))\n\nConstruimos un modelo con vectores de palabras de tamaño 100, skip-grams de tamaño 4, y ajustamos con muestreo negativo de tamaño 20:\n\nif(!file.exists('./cache/noticias_w2v.txt')){\n tmp <- tempfile()\n # tokenización\n write_lines(periodico_df$txt, tmp)\n prep <- prep_word2vec(tmp, \n destination = './cache/noticias_w2v.txt', bundle_ngrams = 2)\n } \n\nBeginning tokenization to text file at ./cache/noticias_w2v.txt\n\n\nPrepping /tmp/RtmpptQpA3/file8fbc9d92b0a\n\n\nStarting training using file ./cache/noticias_w2v.txt\nWords processed: 100K Vocab size: 73K \nWords processed: 200K Vocab size: 124K \nWords processed: 300K Vocab size: 168K \nWords processed: 400K Vocab size: 209K \nWords processed: 500K Vocab size: 247K \nWords processed: 600K Vocab size: 281K \nWords processed: 700K Vocab size: 314K \nWords processed: 800K Vocab size: 346K \nWords processed: 900K Vocab size: 376K \nWords processed: 1000K Vocab size: 406K \nWords processed: 1100K Vocab size: 434K \nWords processed: 1200K Vocab size: 462K \nWords processed: 1300K Vocab size: 489K \nWords processed: 1400K Vocab size: 515K \nWords processed: 1500K Vocab size: 540K \nWords processed: 1600K Vocab size: 565K \nWords processed: 1700K Vocab size: 590K \nWords processed: 1800K Vocab size: 613K \nWords processed: 1900K Vocab size: 637K \nWords processed: 2000K Vocab size: 661K \nWords processed: 2100K Vocab size: 684K \nWords processed: 2200K Vocab size: 706K \nWords processed: 2300K Vocab size: 729K \nWords processed: 2400K Vocab size: 750K \nWords processed: 2500K Vocab size: 771K \nWords processed: 2600K Vocab size: 792K \nWords processed: 2700K Vocab size: 813K \nWords processed: 2800K Vocab size: 834K \nWords processed: 2900K Vocab size: 854K \nWords processed: 3000K Vocab size: 873K \nWords processed: 3100K Vocab size: 893K \nWords processed: 3200K Vocab size: 913K \nWords processed: 3300K Vocab size: 932K \nWords processed: 3400K Vocab size: 951K \nWords processed: 3500K Vocab size: 970K \nWords processed: 3600K Vocab size: 989K \nWords processed: 3700K Vocab size: 1007K \nWords processed: 3800K Vocab size: 1026K \nWords processed: 3900K Vocab size: 1044K \nWords processed: 4000K Vocab size: 1062K \nWords processed: 4100K Vocab size: 1080K \nWords processed: 4200K Vocab size: 1098K \nWords processed: 4300K Vocab size: 1115K \nWords processed: 4400K Vocab size: 1132K \nWords processed: 4500K Vocab size: 1150K \nWords processed: 4600K Vocab size: 1167K \nWords processed: 4700K Vocab size: 1184K \nWords processed: 4800K Vocab size: 1201K \nWords processed: 4900K Vocab size: 1218K \nWords processed: 5000K Vocab size: 1235K \nWords processed: 5100K Vocab size: 1252K \nWords processed: 5200K Vocab size: 1268K \nWords processed: 5300K Vocab size: 1285K \nWords processed: 5400K Vocab size: 1301K \nWords processed: 5500K Vocab size: 1317K \nWords processed: 5600K Vocab size: 1333K \nWords processed: 5700K Vocab size: 1349K \nWords processed: 5800K Vocab size: 1364K \nWords processed: 5900K Vocab size: 1380K \nWords processed: 6000K Vocab size: 1395K \nWords processed: 6100K Vocab size: 1411K \nWords processed: 6200K Vocab size: 1426K \nWords processed: 6300K Vocab size: 1441K \nWords processed: 6400K Vocab size: 1456K \nWords processed: 6500K Vocab size: 1471K \nWords processed: 6600K Vocab size: 1486K \nWords processed: 6700K Vocab size: 1501K \nWords processed: 6800K Vocab size: 1516K \nWords processed: 6900K Vocab size: 1530K \nWords processed: 7000K Vocab size: 1545K \nWords processed: 7100K Vocab size: 1560K \nWords processed: 7200K Vocab size: 1575K \nWords processed: 7300K Vocab size: 1589K \nWords processed: 7400K Vocab size: 1604K \nWords processed: 7500K Vocab size: 1618K \nWords processed: 7600K Vocab size: 1632K \nWords processed: 7700K Vocab size: 1646K \nWords processed: 7800K Vocab size: 1661K \nWords processed: 7900K Vocab size: 1675K \nWords processed: 8000K Vocab size: 1689K \nWords processed: 8100K Vocab size: 1703K \nWords processed: 8200K Vocab size: 1717K \nWords processed: 8300K Vocab size: 1731K \nWords processed: 8400K Vocab size: 1744K \nWords processed: 8500K Vocab size: 1758K \nWords processed: 8600K Vocab size: 1771K \nWords processed: 8700K Vocab size: 1785K \nWords processed: 8800K Vocab size: 1798K \nWords processed: 8900K Vocab size: 1812K \nWords processed: 9000K Vocab size: 1825K \nWords processed: 9100K Vocab size: 1839K \nWords processed: 9200K Vocab size: 1852K \nWords processed: 9300K Vocab size: 1865K \nWords processed: 9400K Vocab size: 1878K \nWords processed: 9500K Vocab size: 1892K \nWords processed: 9600K Vocab size: 1905K \nWords processed: 9700K Vocab size: 1918K \nWords processed: 9800K Vocab size: 1931K \nWords processed: 9900K Vocab size: 1943K \nWords processed: 10000K Vocab size: 1956K \nWords processed: 10100K Vocab size: 1969K \nWords processed: 10200K Vocab size: 1982K \nWords processed: 10300K Vocab size: 1995K \nWords processed: 10400K Vocab size: 2008K \nWords processed: 10500K Vocab size: 2020K \nWords processed: 10600K Vocab size: 2033K \nWords processed: 10700K Vocab size: 2045K \nWords processed: 10800K Vocab size: 2057K \nWords processed: 10900K Vocab size: 2070K \nWords processed: 11000K Vocab size: 2082K \nWords processed: 11100K Vocab size: 2094K \nWords processed: 11200K Vocab size: 2107K \nWords processed: 11300K Vocab size: 2119K \nWords processed: 11400K Vocab size: 2131K \nWords processed: 11500K Vocab size: 2143K \nWords processed: 11600K Vocab size: 2156K \nWords processed: 11700K Vocab size: 2168K \nWords processed: 11800K Vocab size: 2180K \nWords processed: 11900K Vocab size: 2192K \nWords processed: 12000K Vocab size: 2204K \nWords processed: 12100K Vocab size: 2216K \nWords processed: 12200K Vocab size: 2227K \nWords processed: 12300K Vocab size: 2239K \nWords processed: 12400K Vocab size: 2251K \nWords processed: 12500K Vocab size: 2263K \nWords processed: 12600K Vocab size: 2274K \nWords processed: 12700K Vocab size: 2286K \nWords processed: 12800K Vocab size: 2298K \nWords processed: 12900K Vocab size: 2310K \nWords processed: 13000K Vocab size: 2321K \nWords processed: 13100K Vocab size: 2333K \nWords processed: 13200K Vocab size: 2344K \nWords processed: 13300K Vocab size: 2355K \nWords processed: 13400K Vocab size: 2367K \nWords processed: 13500K Vocab size: 2378K \nWords processed: 13600K Vocab size: 2390K \nWords processed: 13700K Vocab size: 2401K \nWords processed: 13800K Vocab size: 2412K \nWords processed: 13900K Vocab size: 2424K \nWords processed: 14000K Vocab size: 2435K \nWords processed: 14100K Vocab size: 2446K \nWords processed: 14200K Vocab size: 2457K \nWords processed: 14300K Vocab size: 2469K \nWords processed: 14400K Vocab size: 2479K \nWords processed: 14500K Vocab size: 2490K \nWords processed: 14600K Vocab size: 2502K \nWords processed: 14700K Vocab size: 2513K \nWords processed: 14800K Vocab size: 2524K \nWords processed: 14900K Vocab size: 2535K \nWords processed: 15000K Vocab size: 2546K \nWords processed: 15100K Vocab size: 2557K \nWords processed: 15200K Vocab size: 2568K \nWords processed: 15300K Vocab size: 2579K \nWords processed: 15400K Vocab size: 2590K \nWords processed: 15500K Vocab size: 2600K \nWords processed: 15600K Vocab size: 2611K \nWords processed: 15700K Vocab size: 2622K \nWords processed: 15800K Vocab size: 2633K \nWords processed: 15900K Vocab size: 2644K \nWords processed: 16000K Vocab size: 2654K \nWords processed: 16100K Vocab size: 2665K \nWords processed: 16200K Vocab size: 2676K \nWords processed: 16300K Vocab size: 2687K \nWords processed: 16400K Vocab size: 2697K \nWords processed: 16500K Vocab size: 2708K \nWords processed: 16600K Vocab size: 2719K \nWords processed: 16700K Vocab size: 2729K \nWords processed: 16800K Vocab size: 2739K \nWords processed: 16900K Vocab size: 2750K \nWords processed: 17000K Vocab size: 2760K \nWords processed: 17100K Vocab size: 2771K \nWords processed: 17200K Vocab size: 2781K \nWords processed: 17300K Vocab size: 2792K \nWords processed: 17400K Vocab size: 2802K \nWords processed: 17500K Vocab size: 2813K \nWords processed: 17600K Vocab size: 2823K \nWords processed: 17700K Vocab size: 2833K \nWords processed: 17800K Vocab size: 2844K \nWords processed: 17900K Vocab size: 2854K \nWords processed: 18000K Vocab size: 2864K \nWords processed: 18100K Vocab size: 2875K \nWords processed: 18200K Vocab size: 2885K \nWords processed: 18300K Vocab size: 2896K \nWords processed: 18400K Vocab size: 2906K \nWords processed: 18500K Vocab size: 2916K \nWords processed: 18600K Vocab size: 2926K \nWords processed: 18700K Vocab size: 2936K \nWords processed: 18800K Vocab size: 2946K \nWords processed: 18900K Vocab size: 2956K \nWords processed: 19000K Vocab size: 2966K \nWords processed: 19100K Vocab size: 2975K \nWords processed: 19200K Vocab size: 2986K \nWords processed: 19300K Vocab size: 2995K \nWords processed: 19400K Vocab size: 3005K \nWords processed: 19500K Vocab size: 3015K \nWords processed: 19600K Vocab size: 3025K \nWords processed: 19700K Vocab size: 3035K \nWords processed: 19800K Vocab size: 3045K \nWords processed: 19900K Vocab size: 3055K \nWords processed: 20000K Vocab size: 3065K \nWords processed: 20100K Vocab size: 3075K \nWords processed: 20200K Vocab size: 3085K \nWords processed: 20300K Vocab size: 3095K \nWords processed: 20400K Vocab size: 3104K \nWords processed: 20500K Vocab size: 3114K \nWords processed: 20600K Vocab size: 3124K \nWords processed: 20700K Vocab size: 3133K \nWords processed: 20800K Vocab size: 3143K \nWords processed: 20900K Vocab size: 3153K \nWords processed: 21000K Vocab size: 3162K \nVocab size (unigrams + bigrams): 1814048\nWords in train file: 21011278\nWords written: 100K\nWords written: 200K\nWords written: 300K\nWords written: 400K\nWords written: 500K\nWords written: 600K\nWords written: 700K\nWords written: 800K\nWords written: 900K\nWords written: 1000K\nWords written: 1100K\nWords written: 1200K\nWords written: 1300K\nWords written: 1400K\nWords written: 1500K\nWords written: 1600K\nWords written: 1700K\nWords written: 1800K\nWords written: 1900K\nWords written: 2000K\nWords written: 2100K\nWords written: 2200K\nWords written: 2300K\nWords written: 2400K\nWords written: 2500K\nWords written: 2600K\nWords written: 2700K\nWords written: 2800K\nWords written: 2900K\nWords written: 3000K\nWords written: 3100K\nWords written: 3200K\nWords written: 3300K\nWords written: 3400K\nWords written: 3500K\nWords written: 3600K\nWords written: 3700K\nWords written: 3800K\nWords written: 3900K\nWords written: 4000K\nWords written: 4100K\nWords written: 4200K\nWords written: 4300K\nWords written: 4400K\nWords written: 4500K\nWords written: 4600K\nWords written: 4700K\nWords written: 4800K\nWords written: 4900K\nWords written: 5000K\nWords written: 5100K\nWords written: 5200K\nWords written: 5300K\nWords written: 5400K\nWords written: 5500K\nWords written: 5600K\nWords written: 5700K\nWords written: 5800K\nWords written: 5900K\nWords written: 6000K\nWords written: 6100K\nWords written: 6200K\nWords written: 6300K\nWords written: 6400K\nWords written: 6500K\nWords written: 6600K\nWords written: 6700K\nWords written: 6800K\nWords written: 6900K\nWords written: 7000K\nWords written: 7100K\nWords written: 7200K\nWords written: 7300K\nWords written: 7400K\nWords written: 7500K\nWords written: 7600K\nWords written: 7700K\nWords written: 7800K\nWords written: 7900K\nWords written: 8000K\nWords written: 8100K\nWords written: 8200K\nWords written: 8300K\nWords written: 8400K\nWords written: 8500K\nWords written: 8600K\nWords written: 8700K\nWords written: 8800K\nWords written: 8900K\nWords written: 9000K\nWords written: 9100K\nWords written: 9200K\nWords written: 9300K\nWords written: 9400K\nWords written: 9500K\nWords written: 9600K\nWords written: 9700K\nWords written: 9800K\nWords written: 9900K\nWords written: 10000K\nWords written: 10100K\nWords written: 10200K\nWords written: 10300K\nWords written: 10400K\nWords written: 10500K\nWords written: 10600K\nWords written: 10700K\nWords written: 10800K\nWords written: 10900K\nWords written: 11000K\nWords written: 11100K\nWords written: 11200K\nWords written: 11300K\nWords written: 11400K\nWords written: 11500K\nWords written: 11600K\nWords written: 11700K\nWords written: 11800K\nWords written: 11900K\nWords written: 12000K\nWords written: 12100K\nWords written: 12200K\nWords written: 12300K\nWords written: 12400K\nWords written: 12500K\nWords written: 12600K\nWords written: 12700K\nWords written: 12800K\nWords written: 12900K\nWords written: 13000K\nWords written: 13100K\nWords written: 13200K\nWords written: 13300K\nWords written: 13400K\nWords written: 13500K\nWords written: 13600K\nWords written: 13700K\nWords written: 13800K\nWords written: 13900K\nWords written: 14000K\nWords written: 14100K\nWords written: 14200K\nWords written: 14300K\nWords written: 14400K\nWords written: 14500K\nWords written: 14600K\nWords written: 14700K\nWords written: 14800K\nWords written: 14900K\nWords written: 15000K\nWords written: 15100K\nWords written: 15200K\nWords written: 15300K\nWords written: 15400K\nWords written: 15500K\nWords written: 15600K\nWords written: 15700K\nWords written: 15800K\nWords written: 15900K\nWords written: 16000K\nWords written: 16100K\nWords written: 16200K\nWords written: 16300K\nWords written: 16400K\nWords written: 16500K\nWords written: 16600K\nWords written: 16700K\nWords written: 16800K\nWords written: 16900K\nWords written: 17000K\nWords written: 17100K\nWords written: 17200K\nWords written: 17300K\nWords written: 17400K\nWords written: 17500K\nWords written: 17600K\nWords written: 17700K\nWords written: 17800K\nWords written: 17900K\nWords written: 18000K\nWords written: 18100K\nWords written: 18200K\nWords written: 18300K\nWords written: 18400K\nWords written: 18500K\nWords written: 18600K\nWords written: 18700K\nWords written: 18800K\nWords written: 18900K\nWords written: 19000K\nWords written: 19100K\nWords written: 19200K\nWords written: 19300K\nWords written: 19400K\nWords written: 19500K\nWords written: 19600K\nWords written: 19700K\nWords written: 19800K\nWords written: 19900K\nWords written: 20000K\nWords written: 20100K\nWords written: 20200K\nWords written: 20300K\nWords written: 20400K\nWords written: 20500K\nWords written: 20600K\nWords written: 20700K\nWords written: 20800K\nWords written: 20900K\nWords written: 21000K\n\n\n\nif (!file.exists(\"./cache/noticias_vectors.bin\")) {\n modelo <- train_word2vec(\"./cache/noticias_w2v.txt\", \n \"./cache/noticias_vectors.bin\",\n vectors = 100, threads = 8, window = 4, cbow = 0, \n iter = 20, negative_samples = 20, min_count = 10) \n} else {\n modelo <- read.vectors(\"./cache/noticias_vectors.bin\")\n}\n\nEl resultado son los vectores aprendidos de las palabras, por ejemplo\n\nvector_gol <- modelo[[\"gol\"]] |> as.numeric()\nvector_gol\n\n [1] -0.389627248 0.048135430 0.501164794 -0.035961468 0.291238993\n [6] 0.642733335 -0.386596769 0.281559557 -0.199183896 -0.554564893\n [11] 0.451201737 0.495587140 -0.525584400 0.166191801 -0.180947676\n [16] 0.034590811 0.731496751 0.259901792 -0.201457486 -0.308042079\n [21] -0.177875623 -0.220428273 0.408699900 0.001920983 0.011449666\n [26] -0.718980432 0.153631359 -0.049470965 0.981541216 0.082757361\n [31] -0.331263602 0.458369821 -0.429754555 0.128275126 -0.421742797\n [36] 0.596242130 -0.093633644 0.066455603 -0.016802812 -0.301688135\n [41] 0.079358041 0.446704596 -0.244078919 -0.137954682 0.695054173\n [46] 0.335903019 0.216709450 0.604890466 -0.538004100 -0.291783333\n [51] -0.579949379 -0.048889056 0.324184030 -0.055591993 -0.012452535\n [56] -0.200338170 0.254620761 0.082836255 0.389545202 -0.185363784\n [61] -0.021011911 0.307440221 0.415608138 0.248776823 -0.139897019\n [66] 0.008641024 0.235776618 0.324411124 -0.171800703 0.131596789\n [71] -0.163520932 0.370538741 -0.134094939 -0.193797469 -0.543500543\n [76] 0.312639445 -0.172534481 -0.115350038 -0.293528855 -0.534602344\n [81] 0.515545666 0.708557248 0.444676250 -0.054800753 0.388787180\n [86] 0.483029991 0.281573176 0.434132993 0.441057146 -0.347387016\n [91] -0.174339339 0.060069371 -0.034651209 0.407196820 0.661161661\n [96] 0.261399239 -0.089392163 -0.043052837 -0.539683878 0.105241157"
+ "text": "17.3 Modelos de word2vec\nEn estos ejemplos veremos cómo producir embeddings de palabras que son precursores de embeddings más refinados como los producidos por Modelos grandes de lenguajes (LLMs). Ver por ejemplo aquí.\nSi lo que principalmente nos interesa es obtener una representación vectorial de palabras, es posible simplificar considerablemente el modelo de arriba o LLMs para poder entrenarlos mucho más rápido, y obtener una representación que en muchas tareas se desempeña bien ((Mikolov et al. 2013)).\nHay dos ideas básicas que se pueden usar para reducir la complejidad del entrenamiento (ver más en (Mikolov et al. 2013)):\n\nEliminar la segunda capa oculta: modelo de bag-of-words continuo y modelo de skip-gram.\nCambiar la función objetivo (minimizar devianza/maximizar verosimilitud) por una más simple, mediante un truco que se llama negative sampling.\n\nComo ya no es de interés central predecir la siguiente palabra a partir de las anteriores, en estos modelos intentamos predecir la palabra central a partir de las que están alrededor.\n\nArquitectura continuous bag-of-words\nLa entrada es igual que en el modelo completo. En primer lugar, simplificamos la segunda capa oculta pondiendo en \\(z\\) el promedio de los vectores \\(C(w_{n-2}), C(w_{n-1})\\). La última capa la dejamos igual por el momento:\n\n\n\nImagen\n\n\nEl modelo se llama bag-of-words porque todas las entradas de la primera capa oculta contribuyen de la misma manera en la salida, independientemente del orden. Aunque esto no suena como buena idea para construir un modelo de lenguaje, veremos que resulta en una representación adecuada para algunos problemas.\n\nEn la primera capa oculta, tenemos un mapeo de las entradas \\(w_1,\\ldots, w_{n-1}\\) a \\(x=C(w_1),\\ldots, C(w_{n-1})\\), donde \\(C\\) es una función que mapea palabras a vectores de dimensión \\(d\\). \\(C\\) también se puede pensar como una matriz de dimensión \\(|V|\\) por \\(d\\). En la capa de entrada,\n\n\\[w_{n-2},w_{n-1} \\to x = (C(w_{n-2}), C(w_{n-1})).\\]\n\nEn la siguiente “capa” oculta simplemente sumamos las entradas de \\(x\\). Aquí nótese que realmente no hay parámetros.\nFinalmente, la capa de salida debe ser un vector de probabilidades sobre todo el vocabulario \\(|V|\\). En esta capa tenemos pesos \\(U\\) y hacemos \\[y = b + U\\sigma (z),\\] y finalmente usamos softmax para tener probabilidades que suman uno: \\[p_i = \\frac{\\exp (y_i) }{\\sum_j exp(y_j)}.\\]\n\nEn el ajuste maximizamos la verosimilitud sobre el corpus. Por ejemplo, para una frase, su log verosimilitud es:\n\\[\\sum_t \\log \\hat{P}(w_{t,n}|w_{t,n+1} \\cdots w_{t-n-1}) \\]\n\n\nArquitectura skip-grams\nOtro modelo simplificado, con más complejidad computacional pero mejores resultados (ver (Mikolov et al. 2013)) que el bag-of-words, es el modelo de skip-grams. En este caso, dada cada palabra que encontramos, intentamos predecir un número fijo de las palabras anteriores y palabras posteriores (el contexto es una vecindad de la palabra).\n\n\n\nImagen\n\n\nLa función objetivo se defina ahora (simplificando) como suma sobre \\(t\\):\n\\[-\\sum_t \\sum_{ -2\\leq j \\leq 2, j\\neq 0} \\log P(w_{t-j} | w_t)\\] (no tomamos en cuenta dónde aparece exactamente \\(w_{t-j}\\) en relación a \\(w_t\\), simplemente consideramos que está en su contexto), donde\n\\[\\log P(w_{t-j}|w_t) = u_{t-j}^tC(w_t) - \\log\\sum_k \\exp{u_{k}^tC(w_t)}\\]\nTodavía se propone una simplificación adicional que resulta ser efectiva:\n\n\nMuestreo negativo\nLa siguiente simplificación consiste en cambiar la función objetivo. En word2vec puede usarse “muestreo negativo”.\nPara empezar, la función objetivo original (para contexto de una sola palabra) es\n\\[E = -\\log \\hat{P}(w_{a}|w_{n}) = -y_{w_a} + \\log\\sum_j \\exp(y_j),\\]\ndonde las \\(y_i\\) son las salidas de la penúltima capa. La dificultad está en el segundo término, que es sobre todo el vocabulario en incluye todos los parámetros del modelo (hay que calcular las parciales de \\(y_j\\)’s sobre cada una de las palabras del vocabulario).\nLa idea del muestreo negativo es que si \\(w_a\\) está en el contexto de \\(w_{n}\\), tomamos una muestra de \\(k\\) palabras \\(v_1,\\ldots v_k\\) al azar (2-50, dependiendo del tamaño de la colección), y creamos \\(k\\) “contextos falsos” \\(v_j w_{n}\\), \\(j=1\\ldots,k\\). Minimizamos en lugar de la observación de arriba\n\\[E = -\\log\\sigma(y_{w_a}) + \\sum_{j=1}^k \\log\\sigma(y_j),\\] en donde queremos maximizar la probabilidad de que ocurra \\(w_a\\) vs. la probabilidad de que ocurra alguna de las \\(v_j\\). Es decir, solo buscamos optimizar parámetros para separar lo mejor que podamos la observación de \\(k\\) observaciones falsas, lo cual implica que tenemos que mover un número relativamente chico de parámetros (en lugar de todos los parámetros de todas las palabras del vocabulario).\nLas palabras “falsas” se escogen según una probabilidad ajustada de unigramas (se observó empíricamente mejor desempeño cuando escogemos cada palabra con probabilidad proporcional a \\(P(w)^{3/4}\\), en lugar de \\(P(w)\\), ver (Mikolov et al. 2013)).\n\n\nEjemplo\n\nif(!require(wordVectors)){\n devtools::install_github(\"bmschmidt/wordVectors\", \n dependencies = TRUE)\n}\n\n\n── R CMD build ─────────────────────────────────────────────────────────────────\n* checking for file ‘/tmp/Rtmp7U5hIj/remotes656331cad8dc/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK\n* preparing ‘wordVectors’:\n* checking DESCRIPTION meta-information ... OK\n* cleaning src\n* checking for LF line-endings in source and make files and shell scripts\n* checking for empty or unneeded directories\n* building ‘wordVectors_2.0.tar.gz’\n\nlibrary(wordVectors)\n\n\nlibrary(tidyverse)\nruta <- \"../datos/noticias/ES_Newspapers.txt\"\nif(!file.exists(ruta)){\n periodico <- \n read_lines(file= \"https://es-noticias.s3.amazonaws.com/Es_Newspapers.txt\",\n progress = FALSE)\n write_lines(periodico, ruta)\n} else {\n periodico <- read_lines(file= ruta,\n progress = FALSE)\n}\nnormalizar <- function(texto, vocab = NULL){\n # minúsculas\n texto <- tolower(texto)\n # varios ajustes\n texto <- gsub(\"\\\\s+\", \" \", texto)\n texto <- gsub(\"\\\\.[^0-9]\", \" _punto_ \", texto)\n texto <- gsub(\" _s_ $\", \"\", texto)\n texto <- gsub(\"\\\\.\", \" _punto_ \", texto)\n texto <- gsub(\"[«»¡!¿?-]\", \"\", texto) \n texto <- gsub(\";\", \" _punto_coma_ \", texto) \n texto <- gsub(\"\\\\:\", \" _dos_puntos_ \", texto) \n texto <- gsub(\"\\\\,[^0-9]\", \" _coma_ \", texto)\n texto <- gsub(\"\\\\s+\", \" \", texto)\n texto\n}\nperiodico_df <- tibble(txt = periodico) |>\n mutate(id = row_number()) |>\n mutate(txt = normalizar(txt))\n\nConstruimos un modelo con vectores de palabras de tamaño 100, skip-grams de tamaño 4, y ajustamos con muestreo negativo de tamaño 20:\n\nif(!file.exists('./cache/noticias_w2v.txt')){\n tmp <- tempfile()\n # tokenización\n write_lines(periodico_df$txt, tmp)\n prep <- prep_word2vec(tmp, \n destination = './cache/noticias_w2v.txt', bundle_ngrams = 2)\n } \n\nBeginning tokenization to text file at ./cache/noticias_w2v.txt\n\n\nPrepping /tmp/Rtmp7U5hIj/file6563ed5d547\n\n\nStarting training using file ./cache/noticias_w2v.txt\nWords processed: 100K Vocab size: 73K \nWords processed: 200K Vocab size: 124K \nWords processed: 300K Vocab size: 168K \nWords processed: 400K Vocab size: 209K \nWords processed: 500K Vocab size: 247K \nWords processed: 600K Vocab size: 281K \nWords processed: 700K Vocab size: 314K \nWords processed: 800K Vocab size: 346K \nWords processed: 900K Vocab size: 376K \nWords processed: 1000K Vocab size: 406K \nWords processed: 1100K Vocab size: 434K \nWords processed: 1200K Vocab size: 462K \nWords processed: 1300K Vocab size: 489K \nWords processed: 1400K Vocab size: 515K \nWords processed: 1500K Vocab size: 540K \nWords processed: 1600K Vocab size: 565K \nWords processed: 1700K Vocab size: 590K \nWords processed: 1800K Vocab size: 613K \nWords processed: 1900K Vocab size: 637K \nWords processed: 2000K Vocab size: 661K \nWords processed: 2100K Vocab size: 684K \nWords processed: 2200K Vocab size: 706K \nWords processed: 2300K Vocab size: 729K \nWords processed: 2400K Vocab size: 750K \nWords processed: 2500K Vocab size: 771K \nWords processed: 2600K Vocab size: 792K \nWords processed: 2700K Vocab size: 813K \nWords processed: 2800K Vocab size: 834K \nWords processed: 2900K Vocab size: 854K \nWords processed: 3000K Vocab size: 873K \nWords processed: 3100K Vocab size: 893K \nWords processed: 3200K Vocab size: 913K \nWords processed: 3300K Vocab size: 932K \nWords processed: 3400K Vocab size: 951K \nWords processed: 3500K Vocab size: 970K \nWords processed: 3600K Vocab size: 989K \nWords processed: 3700K Vocab size: 1007K \nWords processed: 3800K Vocab size: 1026K \nWords processed: 3900K Vocab size: 1044K \nWords processed: 4000K Vocab size: 1062K \nWords processed: 4100K Vocab size: 1080K \nWords processed: 4200K Vocab size: 1098K \nWords processed: 4300K Vocab size: 1115K \nWords processed: 4400K Vocab size: 1132K \nWords processed: 4500K Vocab size: 1150K \nWords processed: 4600K Vocab size: 1167K \nWords processed: 4700K Vocab size: 1184K \nWords processed: 4800K Vocab size: 1201K \nWords processed: 4900K Vocab size: 1218K \nWords processed: 5000K Vocab size: 1235K \nWords processed: 5100K Vocab size: 1252K \nWords processed: 5200K Vocab size: 1268K \nWords processed: 5300K Vocab size: 1285K \nWords processed: 5400K Vocab size: 1301K \nWords processed: 5500K Vocab size: 1317K \nWords processed: 5600K Vocab size: 1333K \nWords processed: 5700K Vocab size: 1349K \nWords processed: 5800K Vocab size: 1364K \nWords processed: 5900K Vocab size: 1380K \nWords processed: 6000K Vocab size: 1395K \nWords processed: 6100K Vocab size: 1411K \nWords processed: 6200K Vocab size: 1426K \nWords processed: 6300K Vocab size: 1441K \nWords processed: 6400K Vocab size: 1456K \nWords processed: 6500K Vocab size: 1471K \nWords processed: 6600K Vocab size: 1486K \nWords processed: 6700K Vocab size: 1501K \nWords processed: 6800K Vocab size: 1516K \nWords processed: 6900K Vocab size: 1530K \nWords processed: 7000K Vocab size: 1545K \nWords processed: 7100K Vocab size: 1560K \nWords processed: 7200K Vocab size: 1575K \nWords processed: 7300K Vocab size: 1589K \nWords processed: 7400K Vocab size: 1604K \nWords processed: 7500K Vocab size: 1618K \nWords processed: 7600K Vocab size: 1632K \nWords processed: 7700K Vocab size: 1646K \nWords processed: 7800K Vocab size: 1661K \nWords processed: 7900K Vocab size: 1675K \nWords processed: 8000K Vocab size: 1689K \nWords processed: 8100K Vocab size: 1703K \nWords processed: 8200K Vocab size: 1717K \nWords processed: 8300K Vocab size: 1731K \nWords processed: 8400K Vocab size: 1744K \nWords processed: 8500K Vocab size: 1758K \nWords processed: 8600K Vocab size: 1771K \nWords processed: 8700K Vocab size: 1785K \nWords processed: 8800K Vocab size: 1798K \nWords processed: 8900K Vocab size: 1812K \nWords processed: 9000K Vocab size: 1825K \nWords processed: 9100K Vocab size: 1839K \nWords processed: 9200K Vocab size: 1852K \nWords processed: 9300K Vocab size: 1865K \nWords processed: 9400K Vocab size: 1878K \nWords processed: 9500K Vocab size: 1892K \nWords processed: 9600K Vocab size: 1905K \nWords processed: 9700K Vocab size: 1918K \nWords processed: 9800K Vocab size: 1931K \nWords processed: 9900K Vocab size: 1943K \nWords processed: 10000K Vocab size: 1956K \nWords processed: 10100K Vocab size: 1969K \nWords processed: 10200K Vocab size: 1982K \nWords processed: 10300K Vocab size: 1995K \nWords processed: 10400K Vocab size: 2008K \nWords processed: 10500K Vocab size: 2020K \nWords processed: 10600K Vocab size: 2033K \nWords processed: 10700K Vocab size: 2045K \nWords processed: 10800K Vocab size: 2057K \nWords processed: 10900K Vocab size: 2070K \nWords processed: 11000K Vocab size: 2082K \nWords processed: 11100K Vocab size: 2094K \nWords processed: 11200K Vocab size: 2107K \nWords processed: 11300K Vocab size: 2119K \nWords processed: 11400K Vocab size: 2131K \nWords processed: 11500K Vocab size: 2143K \nWords processed: 11600K Vocab size: 2156K \nWords processed: 11700K Vocab size: 2168K \nWords processed: 11800K Vocab size: 2180K \nWords processed: 11900K Vocab size: 2192K \nWords processed: 12000K Vocab size: 2204K \nWords processed: 12100K Vocab size: 2216K \nWords processed: 12200K Vocab size: 2227K \nWords processed: 12300K Vocab size: 2239K \nWords processed: 12400K Vocab size: 2251K \nWords processed: 12500K Vocab size: 2263K \nWords processed: 12600K Vocab size: 2274K \nWords processed: 12700K Vocab size: 2286K \nWords processed: 12800K Vocab size: 2298K \nWords processed: 12900K Vocab size: 2310K \nWords processed: 13000K Vocab size: 2321K \nWords processed: 13100K Vocab size: 2333K \nWords processed: 13200K Vocab size: 2344K \nWords processed: 13300K Vocab size: 2355K \nWords processed: 13400K Vocab size: 2367K \nWords processed: 13500K Vocab size: 2378K \nWords processed: 13600K Vocab size: 2390K \nWords processed: 13700K Vocab size: 2401K \nWords processed: 13800K Vocab size: 2412K \nWords processed: 13900K Vocab size: 2424K \nWords processed: 14000K Vocab size: 2435K \nWords processed: 14100K Vocab size: 2446K \nWords processed: 14200K Vocab size: 2457K \nWords processed: 14300K Vocab size: 2469K \nWords processed: 14400K Vocab size: 2479K \nWords processed: 14500K Vocab size: 2490K \nWords processed: 14600K Vocab size: 2502K \nWords processed: 14700K Vocab size: 2513K \nWords processed: 14800K Vocab size: 2524K \nWords processed: 14900K Vocab size: 2535K \nWords processed: 15000K Vocab size: 2546K \nWords processed: 15100K Vocab size: 2557K \nWords processed: 15200K Vocab size: 2568K \nWords processed: 15300K Vocab size: 2579K \nWords processed: 15400K Vocab size: 2590K \nWords processed: 15500K Vocab size: 2600K \nWords processed: 15600K Vocab size: 2611K \nWords processed: 15700K Vocab size: 2622K \nWords processed: 15800K Vocab size: 2633K \nWords processed: 15900K Vocab size: 2644K \nWords processed: 16000K Vocab size: 2654K \nWords processed: 16100K Vocab size: 2665K \nWords processed: 16200K Vocab size: 2676K \nWords processed: 16300K Vocab size: 2687K \nWords processed: 16400K Vocab size: 2697K \nWords processed: 16500K Vocab size: 2708K \nWords processed: 16600K Vocab size: 2719K \nWords processed: 16700K Vocab size: 2729K \nWords processed: 16800K Vocab size: 2739K \nWords processed: 16900K Vocab size: 2750K \nWords processed: 17000K Vocab size: 2760K \nWords processed: 17100K Vocab size: 2771K \nWords processed: 17200K Vocab size: 2781K \nWords processed: 17300K Vocab size: 2792K \nWords processed: 17400K Vocab size: 2802K \nWords processed: 17500K Vocab size: 2813K \nWords processed: 17600K Vocab size: 2823K \nWords processed: 17700K Vocab size: 2833K \nWords processed: 17800K Vocab size: 2844K \nWords processed: 17900K Vocab size: 2854K \nWords processed: 18000K Vocab size: 2864K \nWords processed: 18100K Vocab size: 2875K \nWords processed: 18200K Vocab size: 2885K \nWords processed: 18300K Vocab size: 2896K \nWords processed: 18400K Vocab size: 2906K \nWords processed: 18500K Vocab size: 2916K \nWords processed: 18600K Vocab size: 2926K \nWords processed: 18700K Vocab size: 2936K \nWords processed: 18800K Vocab size: 2946K \nWords processed: 18900K Vocab size: 2956K \nWords processed: 19000K Vocab size: 2966K \nWords processed: 19100K Vocab size: 2975K \nWords processed: 19200K Vocab size: 2986K \nWords processed: 19300K Vocab size: 2995K \nWords processed: 19400K Vocab size: 3005K \nWords processed: 19500K Vocab size: 3015K \nWords processed: 19600K Vocab size: 3025K \nWords processed: 19700K Vocab size: 3035K \nWords processed: 19800K Vocab size: 3045K \nWords processed: 19900K Vocab size: 3055K \nWords processed: 20000K Vocab size: 3065K \nWords processed: 20100K Vocab size: 3075K \nWords processed: 20200K Vocab size: 3085K \nWords processed: 20300K Vocab size: 3095K \nWords processed: 20400K Vocab size: 3104K \nWords processed: 20500K Vocab size: 3114K \nWords processed: 20600K Vocab size: 3124K \nWords processed: 20700K Vocab size: 3133K \nWords processed: 20800K Vocab size: 3143K \nWords processed: 20900K Vocab size: 3153K \nWords processed: 21000K Vocab size: 3162K \nVocab size (unigrams + bigrams): 1814048\nWords in train file: 21011278\nWords written: 100K\nWords written: 200K\nWords written: 300K\nWords written: 400K\nWords written: 500K\nWords written: 600K\nWords written: 700K\nWords written: 800K\nWords written: 900K\nWords written: 1000K\nWords written: 1100K\nWords written: 1200K\nWords written: 1300K\nWords written: 1400K\nWords written: 1500K\nWords written: 1600K\nWords written: 1700K\nWords written: 1800K\nWords written: 1900K\nWords written: 2000K\nWords written: 2100K\nWords written: 2200K\nWords written: 2300K\nWords written: 2400K\nWords written: 2500K\nWords written: 2600K\nWords written: 2700K\nWords written: 2800K\nWords written: 2900K\nWords written: 3000K\nWords written: 3100K\nWords written: 3200K\nWords written: 3300K\nWords written: 3400K\nWords written: 3500K\nWords written: 3600K\nWords written: 3700K\nWords written: 3800K\nWords written: 3900K\nWords written: 4000K\nWords written: 4100K\nWords written: 4200K\nWords written: 4300K\nWords written: 4400K\nWords written: 4500K\nWords written: 4600K\nWords written: 4700K\nWords written: 4800K\nWords written: 4900K\nWords written: 5000K\nWords written: 5100K\nWords written: 5200K\nWords written: 5300K\nWords written: 5400K\nWords written: 5500K\nWords written: 5600K\nWords written: 5700K\nWords written: 5800K\nWords written: 5900K\nWords written: 6000K\nWords written: 6100K\nWords written: 6200K\nWords written: 6300K\nWords written: 6400K\nWords written: 6500K\nWords written: 6600K\nWords written: 6700K\nWords written: 6800K\nWords written: 6900K\nWords written: 7000K\nWords written: 7100K\nWords written: 7200K\nWords written: 7300K\nWords written: 7400K\nWords written: 7500K\nWords written: 7600K\nWords written: 7700K\nWords written: 7800K\nWords written: 7900K\nWords written: 8000K\nWords written: 8100K\nWords written: 8200K\nWords written: 8300K\nWords written: 8400K\nWords written: 8500K\nWords written: 8600K\nWords written: 8700K\nWords written: 8800K\nWords written: 8900K\nWords written: 9000K\nWords written: 9100K\nWords written: 9200K\nWords written: 9300K\nWords written: 9400K\nWords written: 9500K\nWords written: 9600K\nWords written: 9700K\nWords written: 9800K\nWords written: 9900K\nWords written: 10000K\nWords written: 10100K\nWords written: 10200K\nWords written: 10300K\nWords written: 10400K\nWords written: 10500K\nWords written: 10600K\nWords written: 10700K\nWords written: 10800K\nWords written: 10900K\nWords written: 11000K\nWords written: 11100K\nWords written: 11200K\nWords written: 11300K\nWords written: 11400K\nWords written: 11500K\nWords written: 11600K\nWords written: 11700K\nWords written: 11800K\nWords written: 11900K\nWords written: 12000K\nWords written: 12100K\nWords written: 12200K\nWords written: 12300K\nWords written: 12400K\nWords written: 12500K\nWords written: 12600K\nWords written: 12700K\nWords written: 12800K\nWords written: 12900K\nWords written: 13000K\nWords written: 13100K\nWords written: 13200K\nWords written: 13300K\nWords written: 13400K\nWords written: 13500K\nWords written: 13600K\nWords written: 13700K\nWords written: 13800K\nWords written: 13900K\nWords written: 14000K\nWords written: 14100K\nWords written: 14200K\nWords written: 14300K\nWords written: 14400K\nWords written: 14500K\nWords written: 14600K\nWords written: 14700K\nWords written: 14800K\nWords written: 14900K\nWords written: 15000K\nWords written: 15100K\nWords written: 15200K\nWords written: 15300K\nWords written: 15400K\nWords written: 15500K\nWords written: 15600K\nWords written: 15700K\nWords written: 15800K\nWords written: 15900K\nWords written: 16000K\nWords written: 16100K\nWords written: 16200K\nWords written: 16300K\nWords written: 16400K\nWords written: 16500K\nWords written: 16600K\nWords written: 16700K\nWords written: 16800K\nWords written: 16900K\nWords written: 17000K\nWords written: 17100K\nWords written: 17200K\nWords written: 17300K\nWords written: 17400K\nWords written: 17500K\nWords written: 17600K\nWords written: 17700K\nWords written: 17800K\nWords written: 17900K\nWords written: 18000K\nWords written: 18100K\nWords written: 18200K\nWords written: 18300K\nWords written: 18400K\nWords written: 18500K\nWords written: 18600K\nWords written: 18700K\nWords written: 18800K\nWords written: 18900K\nWords written: 19000K\nWords written: 19100K\nWords written: 19200K\nWords written: 19300K\nWords written: 19400K\nWords written: 19500K\nWords written: 19600K\nWords written: 19700K\nWords written: 19800K\nWords written: 19900K\nWords written: 20000K\nWords written: 20100K\nWords written: 20200K\nWords written: 20300K\nWords written: 20400K\nWords written: 20500K\nWords written: 20600K\nWords written: 20700K\nWords written: 20800K\nWords written: 20900K\nWords written: 21000K\n\n\n\nif (!file.exists(\"./cache/noticias_vectors.bin\")) {\n modelo <- train_word2vec(\"./cache/noticias_w2v.txt\", \n \"./cache/noticias_vectors.bin\",\n vectors = 100, threads = 8, window = 4, cbow = 0, \n iter = 20, negative_samples = 20, min_count = 10) \n} else {\n modelo <- read.vectors(\"./cache/noticias_vectors.bin\")\n}\n\nEl resultado son los vectores aprendidos de las palabras, por ejemplo\n\nvector_gol <- modelo[[\"gol\"]] |> as.numeric()\nvector_gol\n\n [1] -0.389627248 0.048135430 0.501164794 -0.035961468 0.291238993\n [6] 0.642733335 -0.386596769 0.281559557 -0.199183896 -0.554564893\n [11] 0.451201737 0.495587140 -0.525584400 0.166191801 -0.180947676\n [16] 0.034590811 0.731496751 0.259901792 -0.201457486 -0.308042079\n [21] -0.177875623 -0.220428273 0.408699900 0.001920983 0.011449666\n [26] -0.718980432 0.153631359 -0.049470965 0.981541216 0.082757361\n [31] -0.331263602 0.458369821 -0.429754555 0.128275126 -0.421742797\n [36] 0.596242130 -0.093633644 0.066455603 -0.016802812 -0.301688135\n [41] 0.079358041 0.446704596 -0.244078919 -0.137954682 0.695054173\n [46] 0.335903019 0.216709450 0.604890466 -0.538004100 -0.291783333\n [51] -0.579949379 -0.048889056 0.324184030 -0.055591993 -0.012452535\n [56] -0.200338170 0.254620761 0.082836255 0.389545202 -0.185363784\n [61] -0.021011911 0.307440221 0.415608138 0.248776823 -0.139897019\n [66] 0.008641024 0.235776618 0.324411124 -0.171800703 0.131596789\n [71] -0.163520932 0.370538741 -0.134094939 -0.193797469 -0.543500543\n [76] 0.312639445 -0.172534481 -0.115350038 -0.293528855 -0.534602344\n [81] 0.515545666 0.708557248 0.444676250 -0.054800753 0.388787180\n [86] 0.483029991 0.281573176 0.434132993 0.441057146 -0.347387016\n [91] -0.174339339 0.060069371 -0.034651209 0.407196820 0.661161661\n [96] 0.261399239 -0.089392163 -0.043052837 -0.539683878 0.105241157"
},
{
"objectID": "16-recom-factorizacion.html#esprep",
"href": "16-recom-factorizacion.html#esprep",
"title": "16 Dimensiones latentes: embeddings",
"section": "17.4 Espacio de representación de palabras",
- "text": "17.4 Espacio de representación de palabras\nComo discutimos arriba, palabras que se usan en contextos similares por su significado o por su función (por ejemplo, “perro” y “gato”“) deben tener representaciones similares, pues su contexto tiende a ser similar. La similitud que usamos el similitud coseno.\nPodemos verificar con nuestro ejemplo:\n\nejemplos <- modelo |> closest_to(\"gol\", n = 5)\nejemplos\n\n word similarity to \"gol\"\n1 gol 1.0000000\n2 golazo 0.8252912\n3 segundo_gol 0.7978025\n4 penalti 0.7764458\n5 potente_disparo 0.7755037\n\n\nTambién podríamos calcular manualmente:\nQue también podemos calcular como:\n\nvector_penalti <- modelo[[\"penalti\"]] |> as.numeric()\ncosineSimilarity(modelo[[\"gol\"]], modelo[[\"penalti\"]])\n\n [,1]\n[1,] 0.7764458\n\n\nO directamente:\n\nnorma <- function(x) sqrt(sum(x^2))\nsum(vector_gol * vector_penalti) / (norma(vector_gol) * norma(vector_penalti))\n\n[1] 0.7764458\n\n\n\nGeometría en el espacio de representaciones\nAhora consideremos cómo se distribuyen las palabras en este espacio, y si existe estructura geométrica en este espacio que tenga información acerca del lenguaje.\nConsideremos primero el caso de plurales de sustantivos.\n\nComo el contexto de los plurales es distinto de los singulares, nuestro modelo debería poder capturar en los vectores su diferencia.\nExaminamos entonces cómo son geométricamente diferentes las representaciones de plurales vs singulares\nSi encontramos un patrón reconocible, podemos utilizar este patrón, por ejemplo, para encontrar la versión plural de una palabra singular, sin usar ninguna regla del lenguaje.\n\nUna de las relaciones geométricas más simples es la adición de vectores. Por ejemplo, extraemos la diferencia entre gol y goles:\n\nejemplos <- modelo |> closest_to(\"dos\", n = 15)\nejemplos\n\n word similarity to \"dos\"\n1 dos 1.0000000\n2 tres 0.9666800\n3 cuatro 0.9527403\n4 cinco 0.9205234\n5 siete 0.9024807\n6 seis 0.8977667\n7 ocho 0.8879153\n8 nueve 0.8550580\n9 trece 0.8514542\n10 catorce 0.8321762\n11 diez 0.8133345\n12 quince 0.8102052\n13 doce 0.8085939\n14 once 0.8033385\n15 veinte 0.7814970\n\n\n\nejemplos <- modelo |> closest_to(c(\"lluvioso\"), n = 5)\nejemplos\n\n word similarity to c(\"lluvioso\")\n1 lluvioso 1.0000000\n2 caluroso 0.8041209\n3 cálido 0.6896448\n4 húmedo 0.6866749\n5 gélido 0.6660152\n\n\n\nejemplos <- modelo |> closest_to(\"presidente\", n = 5)\nejemplos\n\n word similarity to \"presidente\"\n1 presidente 1.0000000\n2 vicepresidente 0.8412900\n3 ex_presidente 0.8321029\n4 máximo_representante 0.7781001\n5 máximo_dirigente 0.7629962\n\n\n\nejemplos <- modelo |> closest_to(\"parís\", n = 5)\nejemplos\n\n word similarity to \"parís\"\n1 parís 1.0000000\n2 londres 0.9232452\n3 nueva_york 0.8464673\n4 roma 0.8443222\n5 berlín 0.8081766\n\n\nY vemos, por ejemplo, que el modelo puede capturar conceptos relacionados con el estado del clima, capitales de países y números - aún cuando no hemos anotado estas funciones en el corpus original. Estos vectores son similares porque tienden a ocurrir en contextos similares.\n\n\nGeometría en el espacio de representaciones\nAhora consideremos cómo se distribuyen las palabras en este espacio, y si existe estructura geométrica en este espacio que tenga información acerca del lenguaje.\nConsideremos primero el caso de plurales de sustantivos.\n\nComo el contexto de los plurales es distinto de los singulares, nuestro modelo debería poder capturar en los vectores su diferencia.\nExaminamos entonces cómo son geométricamente diferentes las representaciones de plurales vs singulares\nSi encontramos un patrón reconocible, podemos utilizar este patrón, por ejemplo, para encontrar la versión plural de una palabra singular, sin usar ninguna regla del lenguaje.\n\nUna de las relaciones geométricas más simples es la adición de vectores. Por ejemplo, extraemos la diferencia entre gol y goles:\n\nplural_1 <- modelo[[\"goles\"]] - modelo[[\"gol\"]]\nplural_1\n\nA VectorSpaceModel object of 1 words and 100 vectors\n [,1] [,2] [,3] [,4] [,5] [,6]\n[1,] -0.2301596 -0.2543171 -0.04071745 -0.2292878 0.004059255 -0.2283908\nattr(,\".cache\")\n<environment: 0x560a60e79388>\n\n\nque es un vector en el espacio de representación de palabras. Ahora sumamos este vector a un sustantivo en singular, y vemos qué palabras están cercas de esta “palabra sintética”:\n\nclosest_to(modelo, ~ \"partido\" + \"goles\" - \"gol\", n = 5)\n\n word similarity to \"partido\" + \"goles\" - \"gol\"\n1 partidos 0.7961097\n2 goles 0.7589920\n3 partidos_disputados 0.6937101\n4 últimos_encuentros 0.6788752\n5 encuentros 0.6697611\n\n\nNótese que la más cercana es justamente el plural correcto, o otros plurales con relación al que buscábamos (como encuentros)\nOtro ejemplo:\n\nclosest_to(modelo, ~ \"mes\" + \"días\" - \"día\", n = 20) \n\n word similarity to \"mes\" + \"días\" - \"día\"\n1 tres_meses 0.7858109\n2 días 0.7708776\n3 meses 0.7655628\n4 diez_días 0.7199002\n5 seis_meses 0.7110105\n6 quince_días 0.7092209\n7 nueve_meses 0.6903626\n8 doce_meses 0.6887811\n9 mes 0.6785786\n10 18_meses 0.6483637\n11 48_horas 0.6392776\n12 diez_años 0.6365554\n13 años 0.6339559\n14 semanas 0.6284049\n15 quince_años 0.6281021\n16 dos_semanas 0.6147185\n17 trimestres 0.6012591\n18 días_hábiles 0.5972889\n19 veinticinco_años 0.5955164\n20 nueves_meses 0.5947687\n\n\nVeremos ahora cómo funciona para el género de sustantivos:\n\nfem_1 <- modelo[[\"presidenta\"]] - modelo[[\"presidente\"]]\nclosest_to(modelo, ~ \"rey\" + \"presidenta\" - \"presidente\", n = 5) |> filter(word != \"rey\")\n\n word similarity to \"rey\" + \"presidenta\" - \"presidente\"\n1 reina 0.7402226\n2 princesa 0.6662326\n3 pía 0.6249812\n4 perla 0.6189366\n\n\n\nclosest_to(modelo, ~ \"tío\" + \"presidenta\" - \"presidente\", n = 5) |> filter(word != \"tío\")\n\n word similarity to \"tío\" + \"presidenta\" - \"presidente\"\n1 dueña 0.7036596\n2 hermana 0.6947787\n3 abuela 0.6871846\n4 tía 0.6850960\n\n\n\n\nEvaluación de calidad de modelos\nLa evaluación de estas aplicaciones puede hacerse por ejemplo, con tareas de analogía, con listas de singular/plurales, de adjetivos/adverbios, masculino/femenino, etc (ver (Mikolov et al. 2013)), ver por ejemplo aquí. Adicionalmente, si se utilizan en alguna tarea downstream, pueden evaluarse en el desempeño de esa tarea particular.\nEjercicio: ¿cómo usarías esta geometría para encontrar el país en el que está una capital dada?\nObservación: falta afinar los parámetros en este modelo. Puedes probar cambiando negative sampling (por ejemplo, incrementa a 40), el número de vectores (50-200, por ejemplo), e incrementando window y el número de iteraciones.\nConsidera también un modelo preentrenado mucho más grande como este. Puedes bajar los vectores de palabras y repetir las tareas mostradas (el formato bin es estándar para la implementación que usamos de word2vec).\n\n\n\n\nBengio, Yoshua, Réjean Ducharme, Pascal Vincent, y Christian Janvin. 2003. «A Neural Probabilistic Language Model». J. Mach. Learn. Res. 3 (marzo): 1137-55. http://dl.acm.org/citation.cfm?id=944919.944966.\n\n\nHu, Y., Y. Koren, y C. Volinsky. 2008. «Collaborative Filtering for Implicit Feedback Datasets». En 2008 Eighth IEEE International Conference on Data Mining, 263-72. https://doi.org/10.1109/ICDM.2008.22.\n\n\nMikolov, Tomas, Kai Chen, Greg Corrado, y Jeffrey Dean. 2013. «Efficient Estimation of Word Representations in Vector Space». CoRR abs/1301.3781. http://arxiv.org/abs/1301.3781.\n\n\nZhou, Yunhong, Dennis Wilkinson, Robert Schreiber, y Rong Pan. 2008. «Large-Scale Parallel Collaborative Filtering for the Netflix Prize». En Algorithmic Aspects in Information and Management, editado por Rudolf Fleischer y Jinhui Xu, 337-48. Berlin, Heidelberg: Springer Berlin Heidelberg."
+ "text": "17.4 Espacio de representación de palabras\nComo discutimos arriba, palabras que se usan en contextos similares por su significado o por su función (por ejemplo, “perro” y “gato”“) deben tener representaciones similares, pues su contexto tiende a ser similar. La similitud que usamos el similitud coseno.\nPodemos verificar con nuestro ejemplo:\n\nejemplos <- modelo |> closest_to(\"gol\", n = 5)\nejemplos\n\n word similarity to \"gol\"\n1 gol 1.0000000\n2 golazo 0.8252912\n3 segundo_gol 0.7978025\n4 penalti 0.7764458\n5 potente_disparo 0.7755037\n\n\nTambién podríamos calcular manualmente:\nQue también podemos calcular como:\n\nvector_penalti <- modelo[[\"penalti\"]] |> as.numeric()\ncosineSimilarity(modelo[[\"gol\"]], modelo[[\"penalti\"]])\n\n [,1]\n[1,] 0.7764458\n\n\nO directamente:\n\nnorma <- function(x) sqrt(sum(x^2))\nsum(vector_gol * vector_penalti) / (norma(vector_gol) * norma(vector_penalti))\n\n[1] 0.7764458\n\n\n\nGeometría en el espacio de representaciones\nAhora consideremos cómo se distribuyen las palabras en este espacio, y si existe estructura geométrica en este espacio que tenga información acerca del lenguaje.\nConsideremos primero el caso de plurales de sustantivos.\n\nComo el contexto de los plurales es distinto de los singulares, nuestro modelo debería poder capturar en los vectores su diferencia.\nExaminamos entonces cómo son geométricamente diferentes las representaciones de plurales vs singulares\nSi encontramos un patrón reconocible, podemos utilizar este patrón, por ejemplo, para encontrar la versión plural de una palabra singular, sin usar ninguna regla del lenguaje.\n\nUna de las relaciones geométricas más simples es la adición de vectores. Por ejemplo, extraemos la diferencia entre gol y goles:\n\nejemplos <- modelo |> closest_to(\"dos\", n = 15)\nejemplos\n\n word similarity to \"dos\"\n1 dos 1.0000000\n2 tres 0.9666800\n3 cuatro 0.9527403\n4 cinco 0.9205234\n5 siete 0.9024807\n6 seis 0.8977667\n7 ocho 0.8879153\n8 nueve 0.8550580\n9 trece 0.8514542\n10 catorce 0.8321762\n11 diez 0.8133345\n12 quince 0.8102052\n13 doce 0.8085939\n14 once 0.8033385\n15 veinte 0.7814970\n\n\n\nejemplos <- modelo |> closest_to(c(\"lluvioso\"), n = 5)\nejemplos\n\n word similarity to c(\"lluvioso\")\n1 lluvioso 1.0000000\n2 caluroso 0.8041209\n3 cálido 0.6896448\n4 húmedo 0.6866749\n5 gélido 0.6660152\n\n\n\nejemplos <- modelo |> closest_to(\"presidente\", n = 5)\nejemplos\n\n word similarity to \"presidente\"\n1 presidente 1.0000000\n2 vicepresidente 0.8412900\n3 ex_presidente 0.8321029\n4 máximo_representante 0.7781001\n5 máximo_dirigente 0.7629962\n\n\n\nejemplos <- modelo |> closest_to(\"parís\", n = 5)\nejemplos\n\n word similarity to \"parís\"\n1 parís 1.0000000\n2 londres 0.9232452\n3 nueva_york 0.8464673\n4 roma 0.8443222\n5 berlín 0.8081766\n\n\nY vemos, por ejemplo, que el modelo puede capturar conceptos relacionados con el estado del clima, capitales de países y números - aún cuando no hemos anotado estas funciones en el corpus original. Estos vectores son similares porque tienden a ocurrir en contextos similares.\n\n\nGeometría en el espacio de representaciones\nAhora consideremos cómo se distribuyen las palabras en este espacio, y si existe estructura geométrica en este espacio que tenga información acerca del lenguaje.\nConsideremos primero el caso de plurales de sustantivos.\n\nComo el contexto de los plurales es distinto de los singulares, nuestro modelo debería poder capturar en los vectores su diferencia.\nExaminamos entonces cómo son geométricamente diferentes las representaciones de plurales vs singulares\nSi encontramos un patrón reconocible, podemos utilizar este patrón, por ejemplo, para encontrar la versión plural de una palabra singular, sin usar ninguna regla del lenguaje.\n\nUna de las relaciones geométricas más simples es la adición de vectores. Por ejemplo, extraemos la diferencia entre gol y goles:\n\nplural_1 <- modelo[[\"goles\"]] - modelo[[\"gol\"]]\nplural_1\n\nA VectorSpaceModel object of 1 words and 100 vectors\n [,1] [,2] [,3] [,4] [,5] [,6]\n[1,] -0.2301596 -0.2543171 -0.04071745 -0.2292878 0.004059255 -0.2283908\nattr(,\".cache\")\n<environment: 0x5653dcf026b8>\n\n\nque es un vector en el espacio de representación de palabras. Ahora sumamos este vector a un sustantivo en singular, y vemos qué palabras están cercas de esta “palabra sintética”:\n\nclosest_to(modelo, ~ \"partido\" + \"goles\" - \"gol\", n = 5)\n\n word similarity to \"partido\" + \"goles\" - \"gol\"\n1 partidos 0.7961097\n2 goles 0.7589920\n3 partidos_disputados 0.6937101\n4 últimos_encuentros 0.6788752\n5 encuentros 0.6697611\n\n\nNótese que la más cercana es justamente el plural correcto, o otros plurales con relación al que buscábamos (como encuentros)\nOtro ejemplo:\n\nclosest_to(modelo, ~ \"mes\" + \"días\" - \"día\", n = 20) \n\n word similarity to \"mes\" + \"días\" - \"día\"\n1 tres_meses 0.7858109\n2 días 0.7708776\n3 meses 0.7655628\n4 diez_días 0.7199002\n5 seis_meses 0.7110105\n6 quince_días 0.7092209\n7 nueve_meses 0.6903626\n8 doce_meses 0.6887811\n9 mes 0.6785786\n10 18_meses 0.6483637\n11 48_horas 0.6392776\n12 diez_años 0.6365554\n13 años 0.6339559\n14 semanas 0.6284049\n15 quince_años 0.6281021\n16 dos_semanas 0.6147185\n17 trimestres 0.6012591\n18 días_hábiles 0.5972889\n19 veinticinco_años 0.5955164\n20 nueves_meses 0.5947687\n\n\nVeremos ahora cómo funciona para el género de sustantivos:\n\nfem_1 <- modelo[[\"presidenta\"]] - modelo[[\"presidente\"]]\nclosest_to(modelo, ~ \"rey\" + \"presidenta\" - \"presidente\", n = 5) |> filter(word != \"rey\")\n\n word similarity to \"rey\" + \"presidenta\" - \"presidente\"\n1 reina 0.7402226\n2 princesa 0.6662326\n3 pía 0.6249812\n4 perla 0.6189366\n\n\n\nclosest_to(modelo, ~ \"tío\" + \"presidenta\" - \"presidente\", n = 5) |> filter(word != \"tío\")\n\n word similarity to \"tío\" + \"presidenta\" - \"presidente\"\n1 dueña 0.7036596\n2 hermana 0.6947787\n3 abuela 0.6871846\n4 tía 0.6850960\n\n\n\n\nEvaluación de calidad de modelos\nLa evaluación de estas aplicaciones puede hacerse por ejemplo, con tareas de analogía, con listas de singular/plurales, de adjetivos/adverbios, masculino/femenino, etc (ver (Mikolov et al. 2013)), ver por ejemplo aquí. Adicionalmente, si se utilizan en alguna tarea downstream, pueden evaluarse en el desempeño de esa tarea particular.\nEjercicio: ¿cómo usarías esta geometría para encontrar el país en el que está una capital dada?\nObservación: falta afinar los parámetros en este modelo. Puedes probar cambiando negative sampling (por ejemplo, incrementa a 40), el número de vectores (50-200, por ejemplo), e incrementando window y el número de iteraciones.\nConsidera también un modelo preentrenado mucho más grande como este. Puedes bajar los vectores de palabras y repetir las tareas mostradas (el formato bin es estándar para la implementación que usamos de word2vec).\n\n\n\n\nBengio, Yoshua, Réjean Ducharme, Pascal Vincent, y Christian Janvin. 2003. «A Neural Probabilistic Language Model». J. Mach. Learn. Res. 3 (marzo): 1137-55. http://dl.acm.org/citation.cfm?id=944919.944966.\n\n\nHu, Y., Y. Koren, y C. Volinsky. 2008. «Collaborative Filtering for Implicit Feedback Datasets». En 2008 Eighth IEEE International Conference on Data Mining, 263-72. https://doi.org/10.1109/ICDM.2008.22.\n\n\nMikolov, Tomas, Kai Chen, Greg Corrado, y Jeffrey Dean. 2013. «Efficient Estimation of Word Representations in Vector Space». CoRR abs/1301.3781. http://arxiv.org/abs/1301.3781.\n\n\nZhou, Yunhong, Dennis Wilkinson, Robert Schreiber, y Rong Pan. 2008. «Large-Scale Parallel Collaborative Filtering for the Netflix Prize». En Algorithmic Aspects in Information and Management, editado por Rudolf Fleischer y Jinhui Xu, 337-48. Berlin, Heidelberg: Springer Berlin Heidelberg."
},
{
"objectID": "81-apendice-descenso.html#cálculo-del-gradiente",
@@ -809,14 +809,14 @@
"href": "81-apendice-descenso.html#implementación",
"title": "Apéndice A — Apéndice 1: descenso en gradiente",
"section": "A.2 Implementación",
- "text": "A.2 Implementación\nEn este punto, podemos intentar una implementación simple basada en el código anterior para hacer descenso en gradiente para nuestro problema de regresión (es un buen ejercicio). En lugar de eso, mostraremos cómo usar librerías ahora estándar para hacer esto. En particular usamos keras (con tensorflow), que tienen la ventaja:\n\nEn tensorflow y keras no es necesario calcular las derivadas a mano. Utiliza diferenciación automática, que no es diferenciación numérica ni simbólica: se basa en la regla de la cadena y la codificación explícita de las derivadas de funciones elementales.\n\n\nlibrary(tidymodels)\nlibrary(keras)\nsource(\"../R/casas_traducir_geo.R\")\nset.seed(68821)\n# dividir muestra\ncasas_split <- initial_split(casas |>\n select(precio_m2_miles, area_hab_m2, calidad_gral, num_coches), \n prop = 0.75)\n# obtener muestra de entrenamiento\ncasas_entrena <- training(casas_split)\ncasas_receta <- recipe(precio_m2_miles ~ ., casas_entrena) \n\n\n# definición de estructura del modelo (regresión lineal)\nx_ent <- casas_receta |> prep() |> juice() |> select(-precio_m2_miles) |> as.matrix()\ny_ent <- casas_receta |> prep() |> juice() |> pull(precio_m2_miles)\nn_entrena <- nrow(x_ent)\ncrear_modelo <- function(lr = 0.01){\n modelo_casas <- \n keras_model_sequential() |>\n layer_dense(units = 1, #una sola respuesta,\n activation = \"linear\", # combinar variables linealmente\n kernel_initializer = initializer_constant(0), #inicializamos coeficientes en 0\n bias_initializer = initializer_constant(0)) #inicializamos ordenada en 0\n # compilar seleccionando cantidad a minimizar, optimizador y métricas\n modelo_casas |> compile(\n loss = \"mean_squared_error\", # pérdida cuadrática\n optimizer = optimizer_sgd(learning_rate = lr), # descenso en gradiente\n metrics = list(\"mean_squared_error\"))\n modelo_casas\n}\n# tasa de aprendizaje es lr, tenemos que poner una tasa chica (prueba)\nmodelo_casas <- crear_modelo(lr = 0.00001)\n# Ahora iteramos\n# Primero probamos con un número bajo de iteraciones\nhistoria <- modelo_casas |> fit(\n x_ent, # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 20, # número de iteraciones\n verbose = 0\n)\n\n\nplot(historia, metrics = \"mean_squared_error\", smooth = FALSE) +\n geom_line()\n\n\n\nhistoria$metrics$mean_squared_error |> round(4)\n\n [1] 1.7903 0.7636 0.4549 0.3621 0.3342 0.3258 0.3232 0.3224 0.3222 0.3221\n[11] 0.3220 0.3220 0.3220 0.3219 0.3219 0.3219 0.3218 0.3218 0.3218 0.3217\n\n\nProbamos con más corridas para checar convergencia:\n\n# Agregamos iteraciones: esta historia comienza en los últimos valores de\n# la corrida anterior\nhistoria <- modelo_casas |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 1000, # número de iteraciones\n verbose = 0\n)\n\n\nplot(historia, metrics = \"mean_squared_error\", smooth = FALSE) \n\n\n\n\nEl modelo parece todavía ir mejorando. Veamos de todas formas los coeficientes estimados hasta ahora:\n\nkeras::get_weights(modelo_casas)\n\n[[1]]\n [,1]\n[1,] 0.007331367\n[2,] 0.016437238\n[3,] 0.004633876\n\n[[2]]\n[1] 0.003028648\n\n\nLa implementación oficial de R es lm, que en general tiene buen desempeño para datos que caben en memoria:\n\nlm(precio_m2_miles ~ area_hab_m2 + calidad_gral + num_coches, \n data = casas_entrena) |> \n coef()\n\n (Intercept) area_hab_m2 calidad_gral num_coches \n 0.66869194 -0.00449751 0.16807663 0.13115749 \n\n\nDe modo que todavía requerimos más iteraciones para alcanzar convergencia. ¿Por qué la convergencia es tan lenta? En parte, la razón es que las escalas de las variables de entrada son muy diferentes, de modo que es difícil ajustar una tasa de aprendizaje constante que funcione bien. Podemos remediar esto poniendo todas las entradas en la misma escala (normalizando)"
+ "text": "A.2 Implementación\nEn este punto, podemos intentar una implementación simple basada en el código anterior para hacer descenso en gradiente para nuestro problema de regresión (es un buen ejercicio). En lugar de eso, mostraremos cómo usar librerías ahora estándar para hacer esto. En particular usamos keras (con tensorflow), que tienen la ventaja:\n\nEn tensorflow y keras no es necesario calcular las derivadas a mano. Utiliza diferenciación automática, que no es diferenciación numérica ni simbólica: se basa en la regla de la cadena y la codificación explícita de las derivadas de funciones elementales.\n\n\nlibrary(tidymodels)\nlibrary(keras)\nsource(\"../R/casas_traducir_geo.R\")\nset.seed(68821)\n# dividir muestra\ncasas_split <- initial_split(casas |>\n select(precio_m2_miles, area_hab_m2, calidad_gral, num_coches), \n prop = 0.75)\n# obtener muestra de entrenamiento\ncasas_entrena <- training(casas_split)\ncasas_receta <- recipe(precio_m2_miles ~ ., casas_entrena) \n\n\n# definición de estructura del modelo (regresión lineal)\nx_ent <- casas_receta |> prep() |> juice() |> select(-precio_m2_miles) |> as.matrix()\ny_ent <- casas_receta |> prep() |> juice() |> pull(precio_m2_miles)\nn_entrena <- nrow(x_ent)\ncrear_modelo <- function(lr = 0.01){\n modelo_casas <- \n keras_model_sequential() |>\n layer_dense(units = 1, #una sola respuesta,\n activation = \"linear\", # combinar variables linealmente\n kernel_initializer = initializer_constant(0), #inicializamos coeficientes en 0\n bias_initializer = initializer_constant(0)) #inicializamos ordenada en 0\n # compilar seleccionando cantidad a minimizar, optimizador y métricas\n modelo_casas |> compile(\n loss = \"mean_squared_error\", # pérdida cuadrática\n optimizer = optimizer_sgd(learning_rate = lr), # descenso en gradiente\n metrics = list(\"mean_squared_error\"))\n modelo_casas\n}\n# tasa de aprendizaje es lr, tenemos que poner una tasa chica (prueba)\nmodelo_casas <- crear_modelo(lr = 0.00001)\n# Ahora iteramos\n# Primero probamos con un número bajo de iteraciones\nhistoria <- modelo_casas |> fit(\n x_ent, # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 20, # número de iteraciones\n verbose = 0\n)\n\n\nplot(historia, metrics = \"mean_squared_error\", smooth = FALSE) +\n geom_line()\n\n\n\nhistoria$metrics$mean_squared_error |> round(4)\n\n [1] 1.7903 0.7636 0.4549 0.3621 0.3342 0.3258 0.3232 0.3224 0.3222 0.3221\n[11] 0.3220 0.3220 0.3220 0.3219 0.3219 0.3219 0.3218 0.3218 0.3218 0.3217\n\n\nProbamos con más corridas para checar convergencia:\n\n# Agregamos iteraciones: esta historia comienza en los últimos valores de\n# la corrida anterior\nhistoria <- modelo_casas |> fit(\n as.matrix(x_ent), # x entradas\n y_ent, # y salida o target\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = 1000, # número de iteraciones\n verbose = 0\n)\n\n\nplot(historia, metrics = \"mean_squared_error\", smooth = FALSE) \n\n\n\n\nEl modelo parece todavía ir mejorando. Veamos de todas formas los coeficientes estimados hasta ahora:\n\nkeras::get_weights(modelo_casas)\n\n[[1]]\n [,1]\n[1,] 0.007331367\n[2,] 0.016437238\n[3,] 0.004633876\n\n[[2]]\n[1] 0.003028649\n\n\nLa implementación oficial de R es lm, que en general tiene buen desempeño para datos que caben en memoria:\n\nlm(precio_m2_miles ~ area_hab_m2 + calidad_gral + num_coches, \n data = casas_entrena) |> \n coef()\n\n (Intercept) area_hab_m2 calidad_gral num_coches \n 0.66869194 -0.00449751 0.16807663 0.13115749 \n\n\nDe modo que todavía requerimos más iteraciones para alcanzar convergencia. ¿Por qué la convergencia es tan lenta? En parte, la razón es que las escalas de las variables de entrada son muy diferentes, de modo que es difícil ajustar una tasa de aprendizaje constante que funcione bien. Podemos remediar esto poniendo todas las entradas en la misma escala (normalizando)"
},
{
"objectID": "81-apendice-descenso.html#normalización-de-entradas",
"href": "81-apendice-descenso.html#normalización-de-entradas",
"title": "Apéndice A — Apéndice 1: descenso en gradiente",
"section": "A.3 Normalización de entradas",
- "text": "A.3 Normalización de entradas\nLa convergencia de descenso en gradiente (y también el desempeño numérico para otros algoritmos) puede dificultarse cuando las variables tienen escalas muy diferentes. Esto produce curvaturas altas en la función que queremos minimizar.\nEn este ejemplo simple, una variable tiene desviación estándar 10 y otra 1:\n\nx1 <- rnorm(100, 0, 5) \nx2 <- rnorm(100, 0, 1) + 0.1*x1\ny <- 0*x1 + 0*x2 + rnorm(100, 0, 0.1) \ndat <- tibble(x1, x2, y)\nrss <- function(beta) mean((as.matrix(dat[, 1:2]) %*% beta - y)^2) \ngrid_beta <- expand.grid(beta1 = seq(-1, 1, length.out = 50), \n beta2 = seq(-1, 1, length.out = 50))\nrss_1 <- apply(grid_beta, 1, rss) \ndat_x <- data.frame(grid_beta, rss_1)\nggplot(dat_x, aes(x = beta1, y = beta2, z = rss_1)) + \n geom_contour(binwidth = 0.5) +\n coord_equal() \n\n\n\n\nEn algunas direcciones el gradiente es muy grande, y en otras chico. Esto implica que la convergencia puede ser muy lenta en algunas direcciones, puede diverger en otras, y que hay que ajustar el paso \\(\\eta > 0\\) con cuidado, dependiendo de dónde comiencen las iteraciones.\nPor ejemplo, con un tamaño de paso relativamente chico, damos unos saltos grandes al principio y luego avanzamos muy lentamente:\n\ngrad_calc <- function(x_ent, y_ent){\n # calculamos directamente el gradiente\n salida_grad <- function(beta){\n n <- length(y_ent)\n f_beta <- as.matrix(cbind(1, x_ent)) %*% beta\n e <- y_ent - f_beta\n grad_out <- - as.numeric(t(cbind(1, x_ent)) %*% e) / n\n names(grad_out) <- c('Intercept', colnames(x_ent))\n grad_out\n }\n salida_grad\n}\ngrad_sin_norm <- grad_calc(dat[, 1:2, drop = FALSE], dat$y)\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.02, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\nSi incrementamos el tamaño de paso observamos también convergencia lenta. En este caso particular, subir más el tamaño de paso puede producir divergencia:\n\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.07, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\nUna normalización usual es con la media y desviación estándar, donde hacemos, para cada variable de entrada \\(j=1,2,\\ldots, p\\) \\[ x_j^{(i)} = \\frac{ x_j^{(i)} - \\bar{x}_j}{s_j}\\] donde \\[\\bar{x}_j = \\frac{1}{N} \\sum_{i=1}^N x_j^{(i)}\\] \\[s_j = \\sqrt{\\frac{1}{N-1}\\sum_{i=1}^N (x_j^{(i)}- \\bar{x}_j )^2}\\] es decir, centramos y normalizamos por columna. Otra opción común es restar el mínimo y dividir entre la diferencia del máximo y el mínimo, de modo que las variables resultantes toman valores en \\([0,1]\\).\nEntonces escalamos antes de ajustar:\n\nx1_s = (x1 - mean(x1))/sd(x1)\nx2_s = (x2 - mean(x2))/sd(x2)\ndat <- tibble(x1_s = x1_s, x2_s = x2_s, y = y)\nrss <- function(beta) mean((as.matrix(dat[, 1:2]) %*% beta - y)^2) \ngrid_beta <- expand.grid(beta1 = seq(-1, 1, length.out = 50), \n beta2 = seq(-1, 1, length.out = 50))\nrss_1 <- apply(grid_beta, 1, rss) \ndat_x <- data.frame(grid_beta, rss_1)\nggplot(dat_x, aes(x = beta1, y = beta2, z = rss_1)) + \n geom_contour(binwidth = 0.5) +\n coord_equal() \n\n\n\n\nNótese que los coeficientes ajustados serán diferentes a los del caso no normalizado.\nSi normalizamos, obtenemos convergencia más rápida\n\ngrad_sin_norm <- grad_calc(dat[, 1:2, drop = FALSE], dat$y)\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.5, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\n\n\n\n\n\n\nTip\n\n\n\nCuando normalizamos antes de ajustar el modelo, las predicciones deben hacerse con entradas normalizadas. La normalización se hace con los mismos valores que se usaron en el entrenamiento (y no recalculando medias y desviaciones estándar con el conjunto de prueba). En cuanto a la forma funcional del predictor \\(f\\), el problema con entradas normalizadas es equivalente al de las entradas no normalizadas. Asegúrate de esto escribiendo cómo correponden los coeficientes de cada modelo normalizado con los coeficientes del modelo no normalizado.\n\n\nSupongamos que el modelo en las variables originales es \\[{f}_\\beta (X) = \\beta_0 + \\beta_1 X_1 + \\beta_2 X_2 + \\cdots + \\beta_p X_p,\\] Consideramos el modelo con variables estandarizadas \\[{g}_{\\beta^s} (X) = \\beta_0^s + \\beta_1^s Z_1 + \\beta_2^s Z_2 + \\cdots + \\beta_p^s Z_p,\\]\nSustituyendo \\(Z_j = (X_j - \\mu_j)/s_j,\\)\n\\[{g}_{\\beta^s} (X) = (\\beta_0^s - \\sum_{j=1}^p \\beta_j^s \\mu_j/s_j) + \\frac{\\beta_1^s}{s_j} X_1 + \\frac{\\beta_2^s}{s_2} X_2 + \\cdots + \\frac{\\beta_p^s}{s_p} X_p,\\] Y vemos que tiene la misma forma funcional de \\(f_\\beta(X)\\). Si la solución de mínimos cuadrados es única, entonces una vez que ajustemos tenemos que tener \\(\\hat{f}_\\beta(X) = \\hat{g}_{\\beta^s} (X)\\), lo que implica que \\[\\hat{\\beta}_0 = \\hat{\\beta}_0^s - \\sum_{j=1}^p \\hat{\\beta}_j^s\\mu_j/s_j\\] y \\[\\hat{\\beta}_j = \\hat{\\beta}_j^s/s_j.\\]\nNótese que para pasar del problema estandarizado al no estandarizado simplemente se requiere escalar los coeficientes por la \\(s_j\\) correspondiente.\n\nEjemplo\nRepetimos nuestro modelo, pero normalizando las entradas:\n\n# usamos recipes para este ejemplo, no necesitas usarlo\ncasas_receta <- recipe(precio_m2_miles ~ ., casas_entrena) |>\n step_normalize(all_predictors()) \ncasas_receta |> summary()\n\n# A tibble: 4 × 4\n variable type role source \n <chr> <list> <chr> <chr> \n1 area_hab_m2 <chr [2]> predictor original\n2 calidad_gral <chr [2]> predictor original\n3 num_coches <chr [2]> predictor original\n4 precio_m2_miles <chr [2]> outcome original\n\n\n\nmodelo_lineal <- linear_reg() |>\n set_engine(\"lm\")\ncasas_flujo <- workflow() |>\n add_recipe(casas_receta) |> \n add_model(modelo_lineal)\n\n\nlibrary(keras)\n# definición de estructura del modelo (regresión lineal)\nx_ent_s <- prep(casas_receta) |> juice() |> select(-precio_m2_miles) |> \n as.matrix()\najustar_casas <- function(modelo, x, y, n_epochs = 100){\n ajuste <- modelo |> fit(\n as.matrix(x), y,\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = n_epochs, # número de iteraciones\n verbose = 0) |> as_tibble()\n ajuste\n}\nmodelo_casas_ns <- crear_modelo(0.00001)\nmodelo_casas_s <- crear_modelo(0.2)\nhistoria_s <- ajustar_casas(modelo_casas_s, x_ent_s, y_ent) |>\n mutate(tipo = \"Estandarizar\")\nhistoria_ns <- ajustar_casas(modelo_casas_ns, x_ent, y_ent) |> \n mutate(tipo = \"Sin estandarizar\")\nhistoria <- bind_rows(historia_ns, historia_s) |> filter(metric == \"mean_squared_error\")\nggplot(historia, aes(x = epoch, y = value, colour = tipo)) +\n geom_line() + geom_point() +scale_x_log10() + scale_y_log10()\n\n\n\n\nObservamos que el modelo con datos estandarizados convergió:\n\nkeras::get_weights(modelo_casas_s)\n\n[[1]]\n [,1]\n[1,] -0.22045918\n[2,] 0.23149671\n[3,] 0.09673478\n\n[[2]]\n[1] 1.295027\n\ncoef(lm.fit(cbind(1,x_ent_s), y_ent))\n\n area_hab_m2 calidad_gral num_coches \n 1.29502679 -0.22045919 0.23149675 0.09673476 \n\n\nMientras que el modelo no estandarizado todavía requiere iteraciones:\n\nkeras::get_weights(modelo_casas_ns)\n\n[[1]]\n [,1]\n[1,] 0.0079817399\n[2,] 0.0019486141\n[3,] 0.0005532746\n\n[[2]]\n[1] 0.0003491883\n\ncoef(lm.fit(cbind(1, x_ent), y_ent))\n\n area_hab_m2 calidad_gral num_coches \n 0.66869194 -0.00449751 0.16807663 0.13115749"
+ "text": "A.3 Normalización de entradas\nLa convergencia de descenso en gradiente (y también el desempeño numérico para otros algoritmos) puede dificultarse cuando las variables tienen escalas muy diferentes. Esto produce curvaturas altas en la función que queremos minimizar.\nEn este ejemplo simple, una variable tiene desviación estándar 10 y otra 1:\n\nx1 <- rnorm(100, 0, 5) \nx2 <- rnorm(100, 0, 1) + 0.1*x1\ny <- 0*x1 + 0*x2 + rnorm(100, 0, 0.1) \ndat <- tibble(x1, x2, y)\nrss <- function(beta) mean((as.matrix(dat[, 1:2]) %*% beta - y)^2) \ngrid_beta <- expand.grid(beta1 = seq(-1, 1, length.out = 50), \n beta2 = seq(-1, 1, length.out = 50))\nrss_1 <- apply(grid_beta, 1, rss) \ndat_x <- data.frame(grid_beta, rss_1)\nggplot(dat_x, aes(x = beta1, y = beta2, z = rss_1)) + \n geom_contour(binwidth = 0.5) +\n coord_equal() \n\n\n\n\nEn algunas direcciones el gradiente es muy grande, y en otras chico. Esto implica que la convergencia puede ser muy lenta en algunas direcciones, puede diverger en otras, y que hay que ajustar el paso \\(\\eta > 0\\) con cuidado, dependiendo de dónde comiencen las iteraciones.\nPor ejemplo, con un tamaño de paso relativamente chico, damos unos saltos grandes al principio y luego avanzamos muy lentamente:\n\ngrad_calc <- function(x_ent, y_ent){\n # calculamos directamente el gradiente\n salida_grad <- function(beta){\n n <- length(y_ent)\n f_beta <- as.matrix(cbind(1, x_ent)) %*% beta\n e <- y_ent - f_beta\n grad_out <- - as.numeric(t(cbind(1, x_ent)) %*% e) / n\n names(grad_out) <- c('Intercept', colnames(x_ent))\n grad_out\n }\n salida_grad\n}\ngrad_sin_norm <- grad_calc(dat[, 1:2, drop = FALSE], dat$y)\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.02, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\nSi incrementamos el tamaño de paso observamos también convergencia lenta. En este caso particular, subir más el tamaño de paso puede producir divergencia:\n\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.07, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\nUna normalización usual es con la media y desviación estándar, donde hacemos, para cada variable de entrada \\(j=1,2,\\ldots, p\\) \\[ x_j^{(i)} = \\frac{ x_j^{(i)} - \\bar{x}_j}{s_j}\\] donde \\[\\bar{x}_j = \\frac{1}{N} \\sum_{i=1}^N x_j^{(i)}\\] \\[s_j = \\sqrt{\\frac{1}{N-1}\\sum_{i=1}^N (x_j^{(i)}- \\bar{x}_j )^2}\\] es decir, centramos y normalizamos por columna. Otra opción común es restar el mínimo y dividir entre la diferencia del máximo y el mínimo, de modo que las variables resultantes toman valores en \\([0,1]\\).\nEntonces escalamos antes de ajustar:\n\nx1_s = (x1 - mean(x1))/sd(x1)\nx2_s = (x2 - mean(x2))/sd(x2)\ndat <- tibble(x1_s = x1_s, x2_s = x2_s, y = y)\nrss <- function(beta) mean((as.matrix(dat[, 1:2]) %*% beta - y)^2) \ngrid_beta <- expand.grid(beta1 = seq(-1, 1, length.out = 50), \n beta2 = seq(-1, 1, length.out = 50))\nrss_1 <- apply(grid_beta, 1, rss) \ndat_x <- data.frame(grid_beta, rss_1)\nggplot(dat_x, aes(x = beta1, y = beta2, z = rss_1)) + \n geom_contour(binwidth = 0.5) +\n coord_equal() \n\n\n\n\nNótese que los coeficientes ajustados serán diferentes a los del caso no normalizado.\nSi normalizamos, obtenemos convergencia más rápida\n\ngrad_sin_norm <- grad_calc(dat[, 1:2, drop = FALSE], dat$y)\niteraciones <- descenso(10, c(0, -0.25, -0.75), 0.5, grad_sin_norm)\nggplot(dat_x) + \n geom_contour(aes(x = beta1, y = beta2, z = rss_1), binwidth = 0.5) +\n coord_equal() +\n geom_path(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red') +\n geom_point(data = data.frame(iteraciones[, 2:3]), aes(x=X1, y=X2), colour = 'red')\n\n\n\n\n\n\n\n\n\n\nTip\n\n\n\nCuando normalizamos antes de ajustar el modelo, las predicciones deben hacerse con entradas normalizadas. La normalización se hace con los mismos valores que se usaron en el entrenamiento (y no recalculando medias y desviaciones estándar con el conjunto de prueba). En cuanto a la forma funcional del predictor \\(f\\), el problema con entradas normalizadas es equivalente al de las entradas no normalizadas. Asegúrate de esto escribiendo cómo correponden los coeficientes de cada modelo normalizado con los coeficientes del modelo no normalizado.\n\n\nSupongamos que el modelo en las variables originales es \\[{f}_\\beta (X) = \\beta_0 + \\beta_1 X_1 + \\beta_2 X_2 + \\cdots + \\beta_p X_p,\\] Consideramos el modelo con variables estandarizadas \\[{g}_{\\beta^s} (X) = \\beta_0^s + \\beta_1^s Z_1 + \\beta_2^s Z_2 + \\cdots + \\beta_p^s Z_p,\\]\nSustituyendo \\(Z_j = (X_j - \\mu_j)/s_j,\\)\n\\[{g}_{\\beta^s} (X) = (\\beta_0^s - \\sum_{j=1}^p \\beta_j^s \\mu_j/s_j) + \\frac{\\beta_1^s}{s_j} X_1 + \\frac{\\beta_2^s}{s_2} X_2 + \\cdots + \\frac{\\beta_p^s}{s_p} X_p,\\] Y vemos que tiene la misma forma funcional de \\(f_\\beta(X)\\). Si la solución de mínimos cuadrados es única, entonces una vez que ajustemos tenemos que tener \\(\\hat{f}_\\beta(X) = \\hat{g}_{\\beta^s} (X)\\), lo que implica que \\[\\hat{\\beta}_0 = \\hat{\\beta}_0^s - \\sum_{j=1}^p \\hat{\\beta}_j^s\\mu_j/s_j\\] y \\[\\hat{\\beta}_j = \\hat{\\beta}_j^s/s_j.\\]\nNótese que para pasar del problema estandarizado al no estandarizado simplemente se requiere escalar los coeficientes por la \\(s_j\\) correspondiente.\n\nEjemplo\nRepetimos nuestro modelo, pero normalizando las entradas:\n\n# usamos recipes para este ejemplo, no necesitas usarlo\ncasas_receta <- recipe(precio_m2_miles ~ ., casas_entrena) |>\n step_normalize(all_predictors()) \ncasas_receta |> summary()\n\n# A tibble: 4 × 4\n variable type role source \n <chr> <list> <chr> <chr> \n1 area_hab_m2 <chr [2]> predictor original\n2 calidad_gral <chr [2]> predictor original\n3 num_coches <chr [2]> predictor original\n4 precio_m2_miles <chr [2]> outcome original\n\n\n\nmodelo_lineal <- linear_reg() |>\n set_engine(\"lm\")\ncasas_flujo <- workflow() |>\n add_recipe(casas_receta) |> \n add_model(modelo_lineal)\n\n\nlibrary(keras)\n# definición de estructura del modelo (regresión lineal)\nx_ent_s <- prep(casas_receta) |> juice() |> select(-precio_m2_miles) |> \n as.matrix()\najustar_casas <- function(modelo, x, y, n_epochs = 100){\n ajuste <- modelo |> fit(\n as.matrix(x), y,\n batch_size = nrow(x_ent), # para descenso en gradiente\n epochs = n_epochs, # número de iteraciones\n verbose = 0) |> as_tibble()\n ajuste\n}\nmodelo_casas_ns <- crear_modelo(0.00001)\nmodelo_casas_s <- crear_modelo(0.2)\nhistoria_s <- ajustar_casas(modelo_casas_s, x_ent_s, y_ent) |>\n mutate(tipo = \"Estandarizar\")\nhistoria_ns <- ajustar_casas(modelo_casas_ns, x_ent, y_ent) |> \n mutate(tipo = \"Sin estandarizar\")\nhistoria <- bind_rows(historia_ns, historia_s) |> filter(metric == \"mean_squared_error\")\nggplot(historia, aes(x = epoch, y = value, colour = tipo)) +\n geom_line() + geom_point() +scale_x_log10() + scale_y_log10()\n\n\n\n\nObservamos que el modelo con datos estandarizados convergió:\n\nkeras::get_weights(modelo_casas_s)\n\n[[1]]\n [,1]\n[1,] -0.22045916\n[2,] 0.23149671\n[3,] 0.09673478\n\n[[2]]\n[1] 1.295027\n\ncoef(lm.fit(cbind(1,x_ent_s), y_ent))\n\n area_hab_m2 calidad_gral num_coches \n 1.29502679 -0.22045919 0.23149675 0.09673476 \n\n\nMientras que el modelo no estandarizado todavía requiere iteraciones:\n\nkeras::get_weights(modelo_casas_ns)\n\n[[1]]\n [,1]\n[1,] 0.0079817399\n[2,] 0.0019486140\n[3,] 0.0005532746\n\n[[2]]\n[1] 0.0003491882\n\ncoef(lm.fit(cbind(1, x_ent), y_ent))\n\n area_hab_m2 calidad_gral num_coches \n 0.66869194 -0.00449751 0.16807663 0.13115749"
},
{
"objectID": "82-apendice-descenso-estocastico.html#algoritmo-de-descenso-estocástico",
@@ -851,7 +851,7 @@
"href": "82-apendice-descenso-estocastico.html#ajuste-de-redes-con-descenso-estocástico",
"title": "Apéndice B — Apéndice 2: Descenso estocástico",
"section": "B.5 Ajuste de redes con descenso estocástico",
- "text": "B.5 Ajuste de redes con descenso estocástico\n\nlibrary(keras)\n\n\nset.seed(21321)\nx_ent <- as.matrix(dat_ent[,c('x_1','x_2','x_3')])\nx_valid <- as.matrix(dat_valid[,c('x_1','x_2','x_3')])\ny_ent <- dat_ent$y\ny_valid <- dat_valid$y\n\nEmpezamos con regresión (sin capas ocultas), que se escribe y ajusta como sigue:\n\nmodelo <- keras_model_sequential() \nmodelo |>\n layer_dense(units = 1, \n activation = \"linear\",\n input_shape = c(3))\n\nmodelo |> compile(loss = 'mse',\n optimizer = optimizer_sgd(learning_rate = 0.1, momentum = 0,\n decay = 0))\n\nhistory <- modelo |> \n fit(x_ent, y_ent, \n epochs = 50, batch_size = 10, \n verbose = 0,\n validation_data = list(x_valid, y_valid))\n\nPodemos ver el progreso del algoritmo por época\n\naprendizaje <- as_tibble(history)\nggplot(aprendizaje, \n aes(x=epoch, y=value, colour=data, group=data)) +\n facet_wrap(~metric, ncol = 1) + geom_line() + geom_point(size = 0.5)\n\n\n\n\nVer los pesos:\n\nget_weights(modelo)\n\n[[1]]\n [,1]\n[1,] -2.39331102\n[2,] 0.03662571\n[3,] -0.06726333\n\n[[2]]\n[1] -0.3801353\n\n\nY verificamos que concuerda con la salida de lm:\n\nmod_lineal <- lm(y ~ x_1 + x_2+ x_3, data = dat_ent) \ncoef(mod_lineal)\n\n(Intercept) x_1 x_2 x_3 \n-0.36904266 -2.46877687 -0.07368414 0.06632769 \n\n\n\n\n\n\nGoodfellow, Ian, Yoshua Bengio, y Aaron Courville. 2016. Deep Learning. MIT Press."
+ "text": "B.5 Ajuste de redes con descenso estocástico\n\nlibrary(keras)\n\n\nset.seed(21321)\nx_ent <- as.matrix(dat_ent[,c('x_1','x_2','x_3')])\nx_valid <- as.matrix(dat_valid[,c('x_1','x_2','x_3')])\ny_ent <- dat_ent$y\ny_valid <- dat_valid$y\n\nEmpezamos con regresión (sin capas ocultas), que se escribe y ajusta como sigue:\n\nmodelo <- keras_model_sequential() \nmodelo |>\n layer_dense(units = 1, \n activation = \"linear\",\n input_shape = c(3))\n\nmodelo |> compile(loss = 'mse',\n optimizer = optimizer_sgd(learning_rate = 0.1, momentum = 0,\n decay = 0))\n\nhistory <- modelo |> \n fit(x_ent, y_ent, \n epochs = 50, batch_size = 10, \n verbose = 0,\n validation_data = list(x_valid, y_valid))\n\nPodemos ver el progreso del algoritmo por época\n\naprendizaje <- as_tibble(history)\nggplot(aprendizaje, \n aes(x=epoch, y=value, colour=data, group=data)) +\n facet_wrap(~metric, ncol = 1) + geom_line() + geom_point(size = 0.5)\n\n\n\n\nVer los pesos:\n\nget_weights(modelo)\n\n[[1]]\n [,1]\n[1,] -2.51229262\n[2,] -0.06604927\n[3,] 0.08724566\n\n[[2]]\n[1] -0.351571\n\n\nY verificamos que concuerda con la salida de lm:\n\nmod_lineal <- lm(y ~ x_1 + x_2+ x_3, data = dat_ent) \ncoef(mod_lineal)\n\n(Intercept) x_1 x_2 x_3 \n-0.36904266 -2.46877687 -0.07368414 0.06632769 \n\n\n\n\n\n\nGoodfellow, Ian, Yoshua Bengio, y Aaron Courville. 2016. Deep Learning. MIT Press."
},
{
"objectID": "99-referencias.html",