diff --git a/05-regularizacion-1_files/figure-html/unnamed-chunk-34-1.png b/05-regularizacion-1_files/figure-html/unnamed-chunk-34-1.png index 8c3cc6ec..6eccdfd6 100644 Binary files a/05-regularizacion-1_files/figure-html/unnamed-chunk-34-1.png and b/05-regularizacion-1_files/figure-html/unnamed-chunk-34-1.png differ diff --git a/08-clasificacion-1.html b/08-clasificacion-1.html index 9f563ce8..fea08b72 100644 --- a/08-clasificacion-1.html +++ b/08-clasificacion-1.html @@ -2623,12 +2623,12 @@

[[1]]
             [,1]
-[1,]  0.34734306
+[1,]  0.34734303
 [2,]  1.01705003
-[3,] -0.05472932
-[4,] -0.02247145
-[5,]  0.51263183
-[6,]  0.55927497
+[3,] -0.05472931
+[4,] -0.02247141
+[5,]  0.51263177
+[6,]  0.55927491
 [7,]  0.45200703
 
 [[2]]
diff --git a/08-clasificacion-1_files/figure-html/unnamed-chunk-27-1.png b/08-clasificacion-1_files/figure-html/unnamed-chunk-27-1.png
index 23ebf0fa..dda6cc82 100644
Binary files a/08-clasificacion-1_files/figure-html/unnamed-chunk-27-1.png and b/08-clasificacion-1_files/figure-html/unnamed-chunk-27-1.png differ
diff --git a/09-clasificacion-2.html b/09-clasificacion-2.html
index a5da6c18..8b60cb3f 100644
--- a/09-clasificacion-2.html
+++ b/09-clasificacion-2.html
@@ -734,15 +734,15 @@ 

Ejemplo

.threshold specificity sensitivity <dbl> <dbl> <dbl> 1 -Inf 0 1 - 2 0.00306 0 1 - 3 0.00347 0.00448 1 - 4 0.00408 0.00897 1 - 5 0.00439 0.0135 1 - 6 0.00478 0.0224 1 - 7 0.00488 0.0269 1 - 8 0.00506 0.0314 1 - 9 0.00514 0.0359 1 -10 0.00564 0.0404 1 + 2 0.00305 0 1 + 3 0.00346 0.00448 1 + 4 0.00407 0.00897 1 + 5 0.00437 0.0135 1 + 6 0.00476 0.0224 1 + 7 0.00486 0.0269 1 + 8 0.00504 0.0314 1 + 9 0.00512 0.0359 1 +10 0.00562 0.0404 1 # ℹ 311 more rows
ggplot(roc_tbl, 
@@ -824,7 +824,7 @@ 

Ejemplo

modelo .metric .estimator .estimate <chr> <chr> <chr> <dbl> 1 IMC y edad roc_auc binary 0.761 -2 Todas roc_auc binary 0.866
+2 Todas roc_auc binary 0.865

En 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.

diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-17-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-17-1.png index 738c7e07..b31090b9 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-17-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-20-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-20-1.png index 940cc6bf..43d26644 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-20-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-22-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-22-1.png index d671f967..c9262a61 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-22-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-23-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-23-1.png index 50ffaf03..2577c80d 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-23-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-23-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-24-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-24-1.png index cd24d457..1334f171 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-24-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-25-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-25-1.png index e7c4bafe..4208816f 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-25-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-25-1.png differ diff --git a/09-clasificacion-2_files/figure-html/unnamed-chunk-3-1.png b/09-clasificacion-2_files/figure-html/unnamed-chunk-3-1.png index 5fb36c73..4b6f5a5a 100644 Binary files a/09-clasificacion-2_files/figure-html/unnamed-chunk-3-1.png and b/09-clasificacion-2_files/figure-html/unnamed-chunk-3-1.png differ diff --git a/10-clasificacion-calibracion.html b/10-clasificacion-calibracion.html index c598516d..b80d6031 100644 --- a/10-clasificacion-calibracion.html +++ b/10-clasificacion-calibracion.html @@ -353,23 +353,23 @@

Ejemplo: diabe dat_calibracion |> head() |> gt()
-
- @@ -885,23 +885,23 @@

Ejemplo: dia calibracion_gpos |> gt() |> fmt_number(where(is_double), decimals = 3)

-
- @@ -1458,23 +1458,23 @@

preds_tbl |> head() |> gt() |> fmt_number(where(is_double), decimals = 3)

-
- @@ -2203,15 +2203,12 @@

-
There were issues with some computations   A: x2
-

-
There were issues with some computations   A: x10
-
collect_metrics(discrim_res)
+
collect_metrics(discrim_res)
# A tibble: 2 × 6
   .metric     .estimator  mean     n std_err .config             
@@ -2221,16 +2218,16 @@ 

-
cal_plot_breaks(discrim_res, num_breaks = 15)
+
cal_plot_breaks(discrim_res, num_breaks = 15)

Corremos ahora validación con regresión logística com ejemplo:

-
logit_val <- probably::cal_validate_logistic(discrim_res, 
-  metrics = metricas, save_pred = TRUE)
-collect_metrics(logit_val)
+
logit_val <- probably::cal_validate_logistic(discrim_res, 
+  metrics = metricas, save_pred = TRUE)
+collect_metrics(logit_val)
# A tibble: 4 × 7
   .metric     .type        .estimator  mean     n std_err .config
@@ -2243,9 +2240,9 @@ 

-
collect_predictions(logit_val) |> 
-  filter(.type == "calibrated") |> 
-  cal_plot_breaks(truth = class, estimate = .pred_PS) 
+
collect_predictions(logit_val) |> 
+  filter(.type == "calibrated") |> 
+  cal_plot_breaks(truth = class, estimate = .pred_PS) 

diff --git a/10-clasificacion-calibracion_files/figure-html/unnamed-chunk-4-1.png b/10-clasificacion-calibracion_files/figure-html/unnamed-chunk-4-1.png index 7fa623d4..be3ce3bc 100644 Binary files a/10-clasificacion-calibracion_files/figure-html/unnamed-chunk-4-1.png and b/10-clasificacion-calibracion_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/12-arboles_files/figure-html/unnamed-chunk-17-1.png b/12-arboles_files/figure-html/unnamed-chunk-17-1.png index e657af74..9585bee5 100644 Binary files a/12-arboles_files/figure-html/unnamed-chunk-17-1.png and b/12-arboles_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/13-arboles-boosting.html b/13-arboles-boosting.html index c49105a5..b34a8f59 100644 --- a/13-arboles-boosting.html +++ b/13-arboles-boosting.html @@ -947,7 +947,7 @@

system.time(modelo <- xgb.train(d_entrena, verbose = 1, nrounds = 10000, params = params))

   user  system elapsed 
- 15.522   0.782   4.154 
+ 19.962 0.811 5.313
xgb.save(model = modelo, fname = "./cache/casas_boost.xgb")
diff --git a/14-interpretacion.html b/14-interpretacion.html index e8c6a2a0..594c2afc 100644 --- a/14-interpretacion.html +++ b/14-interpretacion.html @@ -984,7 +984,7 @@

Ejemplo

} graf_caso(data, contrib, 10, pred_base)
-
[1] "Predicción base: 0.17, Predicción: 0.88"
+
[1] "Predicción base: 0.17, Predicción: 0.94"

@@ -993,7 +993,7 @@

Ejemplo

graf_caso(data, contrib, 102, pred_base)
-
[1] "Predicción base: 0.17, Predicción: 0.77"
+
[1] "Predicción base: 0.17, Predicción: 0.41"

diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-10-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-10-1.png index 30ef3534..f7dc28ca 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-10-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-10-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-27-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-27-1.png index 09343bf1..86906c2d 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-27-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-27-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-28-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-28-1.png index 0bfe723a..b23bd014 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-28-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-28-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-29-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-29-1.png index d248e9d8..117207e3 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-29-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-30-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-30-1.png index 40a22f70..7012f498 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-30-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-30-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-4-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-4-1.png index 945d5c51..be1d1234 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-4-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-5-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-5-1.png index 8331d880..4281a561 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-5-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-5-2.png b/14-interpretacion_files/figure-html/unnamed-chunk-5-2.png index 68d5de69..a515c4e0 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-5-2.png and b/14-interpretacion_files/figure-html/unnamed-chunk-5-2.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-6-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-6-1.png index 8410dbf4..56406eda 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-6-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-6-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-7-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-7-1.png index 22392bb6..e7d5247d 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-7-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-8-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-8-1.png index a5337526..dd855d7e 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-8-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/14-interpretacion_files/figure-html/unnamed-chunk-9-1.png b/14-interpretacion_files/figure-html/unnamed-chunk-9-1.png index a0799a30..4cca98ae 100644 Binary files a/14-interpretacion_files/figure-html/unnamed-chunk-9-1.png and b/14-interpretacion_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/15-reduccion-dim_files/figure-html/unnamed-chunk-70-1.png b/15-reduccion-dim_files/figure-html/unnamed-chunk-70-1.png index 71f82e67..53b8ce41 100644 Binary files a/15-reduccion-dim_files/figure-html/unnamed-chunk-70-1.png and b/15-reduccion-dim_files/figure-html/unnamed-chunk-70-1.png differ diff --git a/16-recom-factorizacion.html b/16-recom-factorizacion.html index 03c03759..f316a23c 100644 --- a/16-recom-factorizacion.html +++ b/16-recom-factorizacion.html @@ -1597,7 +1597,7 @@

Ejemplo


 ── R CMD build ─────────────────────────────────────────────────────────────────
-* checking for file ‘/tmp/RtmpPIgqH8/remotes8fbba8c9463/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK
+* checking for file ‘/tmp/Rtmpk59PUS/remotes8fbe5304b8b8/bmschmidt-wordVectors-ad127c1/DESCRIPTION’ ... OK
 * preparing ‘wordVectors’:
 * checking DESCRIPTION meta-information ... OK
 * cleaning src
@@ -1651,7 +1651,7 @@ 

Ejemplo

Beginning tokenization to text file at ./cache/noticias_w2v.txt
-
Prepping /tmp/RtmpPIgqH8/file8fbb3f22963d
+
Prepping /tmp/Rtmpk59PUS/file8fbe142ba973
Starting training using file ./cache/noticias_w2v.txt
@@ -2240,7 +2240,7 @@ 

+<environment: 0x559d27099f68>

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/81-apendice-descenso.html b/81-apendice-descenso.html index c8cc40ca..36d3451d 100644 --- a/81-apendice-descenso.html +++ b/81-apendice-descenso.html @@ -624,9 +624,9 @@

[[1]]
             [,1]
-[1,] 0.007331368
+[1,] 0.007331367
 [2,] 0.016437240
-[3,] 0.004633875
+[3,] 0.004633876
 
 [[2]]
 [1] 0.003028649
@@ -829,8 +829,8 @@

Ejemplo

[[1]]
              [,1]
 [1,] 0.0079817399
-[2,] 0.0019486138
-[3,] 0.0005532746
+[2,] 0.0019486140
+[3,] 0.0005532745
 
 [[2]]
 [1] 0.0003491883
diff --git a/81-apendice-descenso_files/figure-html/unnamed-chunk-19-1.png b/81-apendice-descenso_files/figure-html/unnamed-chunk-19-1.png index 7e5886e0..f7a039dd 100644 Binary files a/81-apendice-descenso_files/figure-html/unnamed-chunk-19-1.png and b/81-apendice-descenso_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/81-apendice-descenso_files/figure-html/unnamed-chunk-29-1.png b/81-apendice-descenso_files/figure-html/unnamed-chunk-29-1.png index 50355a40..f168931e 100644 Binary files a/81-apendice-descenso_files/figure-html/unnamed-chunk-29-1.png and b/81-apendice-descenso_files/figure-html/unnamed-chunk-29-1.png differ diff --git a/82-apendice-descenso-estocastico.html b/82-apendice-descenso-estocastico.html index 47b3cae7..6681634a 100644 --- a/82-apendice-descenso-estocastico.html +++ b/82-apendice-descenso-estocastico.html @@ -811,12 +811,12 @@

[[1]]
             [,1]
-[1,] -2.57393885
-[2,] -0.08030029
-[3,]  0.13267761
+[1,] -2.51001811
+[2,]  0.02752806
+[3,]  0.10498822
 
 [[2]]
-[1] -0.3571931
+[1] -0.3732712

Y verificamos que concuerda con la salida de lm:

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 34e516d5..762b3786 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 47014d2a..fdb0435c 100644 --- a/search.json +++ b/search.json @@ -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.34734306\n[2,] 1.01705003\n[3,] -0.05472932\n[4,] -0.02247145\n[5,] 0.51263183\n[6,] 0.55927497\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.05472931\n[4,] -0.02247141\n[5,] 0.51263177\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" }, { "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.00306 0 1\n 3 0.00347 0.00448 1\n 4 0.00408 0.00897 1\n 5 0.00439 0.0135 1\n 6 0.00478 0.0224 1\n 7 0.00488 0.0269 1\n 8 0.00506 0.0314 1\n 9 0.00514 0.0359 1\n10 0.00564 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.865\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: x2\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: 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.522 0.782 4.154 \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 19.962 0.811 5.313 \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.88\"\n\n\n\n\n\n\ngraf_caso(data, contrib, 102, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.77\"\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.94\"\n\n\n\n\n\n\ngraf_caso(data, contrib, 102, pred_base)\n\n[1] \"Predicción base: 0.17, Predicción: 0.41\"\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", @@ -795,14 +795,14 @@ "href": "16-recom-factorizacion.html#modelos-de-word2vec", "title": "16  Dimensiones latentes: embeddings", "section": "18.3 Modelos de word2vec", - "text": "18.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/RtmpPIgqH8/remotes8fbba8c9463/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/RtmpPIgqH8/file8fbb3f22963d\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": "18.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/Rtmpk59PUS/remotes8fbe5304b8b8/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/Rtmpk59PUS/file8fbe142ba973\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": "18.4 Espacio de representación de palabras", - "text": "18.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: 0x559f14cdb450>\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": "18.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: 0x559d27099f68>\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", @@ -816,14 +816,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.007331368\n[2,] 0.016437240\n[3,] 0.004633875\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)" + "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.016437240\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.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.0019486138\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.0005532745\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" }, { "objectID": "82-apendice-descenso-estocastico.html#algoritmo-de-descenso-estocástico", @@ -858,7 +858,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.57393885\n[2,] -0.08030029\n[3,] 0.13267761\n\n[[2]]\n[1] -0.3571931\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.51001811\n[2,] 0.02752806\n[3,] 0.10498822\n\n[[2]]\n[1] -0.3732712\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",