top of page

COSAS VEREDES

A veces es mejor no hacer un modelo que dejar su construcción a los que denomino "los insensatos del Excel" o incluso inventárselo directamente aludiendo a que no hay datos.


Lo anterior parece raro pero lo he visto más de una vez, de hecho, en una de esas ocasiones me tocó "heredar" en un antiguo trabajo, un modelo que había hecho alguien, de cuyo nombre ni me acuerdo y ni quiero. Pero el hecho es que pequé de ingenuo ya que tras "aceptarlo" (no sin alguna pega, pero reconozco que me debí haber puesto más duro) me doy cuenta que había seguido una serie de pautas que no merecen ni ser calificadas como metodología:


  • Se consideró unas variables del tipo "me dice el cliente" y para las cuáles tenía datos


  • En cada variable creó unos tramos por "criterio" experto y asignaba por ejemplo un 1 si era un riesgo alto y un 5 si el riesgo era bajo (con un par)


  • El score básicamente era una suma de las anteriores puntuaciones


  • Al final se hizo unos cuantos tramos de población con letras desde la A creo hasta la F (no recuerdo bien) y hala, a rular


¿Qué podía fallar en todo esto?


Para mi estupor me cuentan que el cliente ya había pagado y puesto en producción (o al menos estaba usando) el modelo y que a partir de ahora yo me encargaba de la fiesta, mientras que la persona que lo hizo (creo que se sintió aliviado), se dedicaría a otros menesteres con su Excel...


Pues bien, con una documentación más simple que un sandwich vegetal, tras algunas iteraciones la cosa empieza a petar y es que claro:


  • Habían vendido una moto de mucho cuidado


  • Nada de hacer test ni pruebas de fiabilidad adicionales tipo OOT, ni tan siquiera un mínimo test simplón


  • Por supuesto, no había ni la más mínima referencia a un modelo de regresión logístico, ni nada con cierta sofisticación más allá que lo que un Excel pueda estimar


  • Y aquí no había excusa ya que había datos y por desgracia impagos más que suficientes, aunque también viví el caso en el que se paga para que alguien invente un modelo directamente porque no hay datos


Pues básicamente este fue uno de los múltiples motivos por lo que tuve que dejar un trabajo, imagínense conociendo todo esto, encima tenía que callármelo ¡Yo que soy un bocazas para estas "cacas"! y aunque intentamos hacer un modelo de verdad alternativo, sin embargo, se vendió el engendro anterior y ni oir hablar de una alternativa con un mínimo de lógica basados en tranformaciones sensatas del tipo WoE o usando la variable directamente en el modelo mediante una regresión logística, nada "dendedla y no enmendadla".


Así pues consideremos el siguiente ejemplo de código sencillo de un conjunto de datos de R de impagos con unas pocas variables:


library(dplyr)library(scorecard)library(ISLR)data("Default")df <- Default

df$default <- ifelse(df$default == "Yes", 1, 0)

df_list <- split_df(df, y = "default", ratios = c(0.6, 0.4), seed = 30)

label_list <- lapply(df_list, function(x) x$default)

df_train <- df_list$train

df_test <- df_list$test

bins_train <- woebin(df_train, y = "default")

woebin_plot(bins_train)


Con estas pocas líneas (si se tienen todas las librerías necesarias) se llega a los siguientes gráficos clave:


ree
ree


ree












Pues bien, lo que pasó con el susodicho insensato del excel es que dejó volar su intuición sin hacer ni la más mínima visión univariante tal como la que aquí presento y entonces hizo algo así como:


  • Si eres estudiante te penalizo y te doy 1 punto en caso contrario 5 (da igual si se pone 2 o 3, ... el desastre no se va a apañar si se opera así)


  • A bajo balance (suponiendo que es un concepto relacionado con el dispuesto, habría que aclarar más, pero no es ahora el objetivo, por lo que se supone lo anterior) en la tarjeta 1 punto y a alto balance 5


  • A bajo ingreso 1 punto y a alta ingreso 5


Es de observar que en estos casos la intuición y el querer forzar al uso de todas las variables es una muy mala idea, claramente, la primera de las variables debería quitarse y habría que plantearse lo mismo con la tercera, ya que prácticamente es la segunda, la que nos daría el modelo.


No obstante, lejos de crear un modelo ahora o de interpretar esta salida vuelvo a señalar la importancia de visualizar y analizar las variables bajo distintas técnicas y metodologías sólidas que nos puedan dar luz, salirse de metodologías como la de Credit Scoring o directamente hacer algo tipo Regresión Logística o usar modelos de Analítica Avanzada, resulta arriesgado, jugar con el "juicio experto" podría aún entenderlo en aquellas ocasiones en las que no se tienen datos de impago que es cuando se empieza una línea de negocio pero desde luego pagar para que un tercero haga una invención tipo es directamente tirar el dinero y si hay datos, como fue el caso que aquí relaté, hacer algo basados en "la cuenta de la vieja", es directamente suicida.


 
 
 

Esta entrada y seguramente alguna más de las posteriores, voy a centrarla en errores graves que se cometen a nivel profesional cuando se construyen modelos, dado que he pasado por varias empresas y en los últimos 10 años no paro de encontrarme burradas de bulto que a la postre se manifiestan como caída de eficiencia o de predictividad de los modelos que se usan en la vida real.


En definitiva y por desgracia, me he encontrado en más de una ocasión que consultores supuestamente expertos en modelización, parece que no tienen claro más de un tema y creo que estas líneas son importantes para aquellas empresas demandantes de modelos, que confían plena y ciegamente en el buen hacer del proveedor y que después se encuentran con sorpresas desagradables a poco que se levanta la alfombra.


Aquí voy a trata el tema de la multicolinealidad. Un tema aparentemente sencillo de entender por todo aquel que estudia estadística y que es fácil de explicar cuando se está ante un auditorio de directores, que aunque se excusen algunos diciendo que son de letras, a poco que aclares que esa palabra significa que en un modelo no puede haber variables que contengan la misma información, te entienden perfectamente.


Cuando doy clases en másteres, este es un aspecto con el que machaco a mis alumnos porque ya he visto que parece que no se llegan a enterar qué es lo que significa la multicolinealidad y desde luego, cuando se ponen a trabajar los equipos de desarrollo, veo cosas que como diría Rambo "harían vomitar a una vaca".


Que alguien supuestamente experto, haga un modelo y pueda meter variables con cierta correlación pongamos del 0.8 o 0.9 sin justificarlo me puede parecer grave, pero más grave aún es cuando se meten variables que tiene multicolinealidad perfecta.


Así pues, si por ejemplo meto como variable explicativa una variable dummy tipo 0 – 1 donde 1 significa canal online y 0 significa canal presencial y la denomino "canal online", está claro que no necesito meter otra variable adicional que se denomine "canal presencial" ¿Cierto?


Pues parece que el tema no está claro, porque este tipo de cosas, que creía superadas, lo he visto en más de una ocasión y con modelos en fase productiva, vamos que se están aplicando en la vida real. Además hay más y es cuando se emplea en python por ejemplo funciones como get_dummies cuya documentación puede verse aquí:



Por ejemplo si se ejecuta:


import seaborn as sns

import pandas as pd


# Cargamos el dataset Titanic

titanic = sns.load_dataset("titanic")


# Mostramos las primeras filas con la variable 'sex'

print("Antes de get_dummies:")

print(titanic[['sex']].head())


# Convertimos la variable 'sex' en dummies (0/1)

titanic_dummies = pd.get_dummies(titanic, columns=['sex'])


print("\nDespués de get_dummies:")

print(titanic_dummies[['sex_female', 'sex_male']].head())


Se obtiene lo siguiente:


ree


Es decir, se genera una variable y su contraria, lo cual a priori no es malo si se sabe que hay que desechar una de esas variables, esto se consigue dentro de la misma función con el parámetro drop_first=True , pero claro, es que en versiones anteriores la opción parece que no existía y también parece que muchos no leen bien la documentación, porque para aplicarlo todo bien a un modelo, en general cambiando la línea de conversión por:


titanic_dummies = pd.get_dummies(titanic, columns=['sex'], drop_first=True)


Resulta más que suficiente.


Pues bien, el problema se agudiza más cuando tenemos más de 2 categorías (imaginemos modelos donde hay códigos postales, variables carácter con múltiples denominaciones), el uso de este tipo de conversiones sin el debido cuidado es peligroso.


No obstante, si volvemos al caso sencillo de una única variable dicotómica donde se ha aplicado get_dummie y se han metido esas 2 nuevas variables generadas en un modelo ¿Qué puede pasar?


Pues depende ...


Si el modelo es de regresión lineal, desde luego vas a tener un problema de algoritmo depende de si lo haces con R o con Python, con R te dirá que una de las variables será missing, python en cambio con sklearn no te dirá nada ya que usa el método del gradiente descendente y salvo un warning, convergerá y te estimará los 2 coeficientes tan pancho.


En otros tipos de modelos el tema es más sutil. En el caso de los árboles y los modelos de ensemble como los random forest y los xgboost el tema cambia.


El algoritmo converge y te genera un modelo y por lo que puede verse en:



Se afirma que la multicolinealidad en el caaso de los xgboost no afecta el performance de este tipo de modelos.


Sin embargo, el anterior articulo, está basado en el uso de datasets tipo kaggle en el caso del primero ejemplo, el dataset de fraude tiene unas características que lo hacen un poco peculiar, por lo que allí va a funcionar casi cualquier cosa ...


Como digo siempre, si nos quedamos en el nivel accuracy o de poder discriminante, la visión que se tiene del modelo es parcial, porque no se está mostrando la interacción con otras variables y por supuesto la lógica de negocio – sentido común que tiene que haber en todo esto.


Para cualquiera que trabaja en este mundillo con un poco de experiencia, meter una variable de este tipo en un modelo con una correlación 1 te va a generar distintos problemas que podrán o no ser manifestados en la roc o en el gini que tengas cuando ajustes tu modelo en training o/y test:


  • Está usando una variable de más que complica la interpretabilidad del modelo y que no añade nueva información


  • En aplicaciones reales que no puedo mostrar, esa variable de más le roba poder explicativo a otras variables, sobre todo cuando se ha hecho algo como incluir las variables hombre y mujer o cuando, como he comentado en el ejemplo inicial, se ha incluido en una segmentación de contratación como variables contratación on-line y presencial.


  • Que haga un ajuste a corto plazo razonable, no significa que se mantenga en el tiempo, ya que al no incluir otras variables importantes, o bien sus pesos no están bien determinados, o bien vas a generar inestabilidades a poco que el modelo se ponga en producción con toda la población


  • Y referente a lo anterior se está alterando los pesos de variables importantes y eliminando otras posibles variables que podrían tener una importancia significativa porque has preferido sobre ponderar 2 variables que realmente tienen la información de 1


Así pues, mi recomendación cuando tengáis que revisar modelos que no habéis hecho vosotros es que os fijéis en todo y que como ya he vivido en más de una ocasión, cosas que parecen de sentido común y demás, bien por dejadez, bien por desconocimiento, hacen que algunos proveedores desarrollen cosas que cuando empiezan a "petar" o a "dejar de predecir", después los que estamos en el día a día, tenemos que sufrir y solucionar.


 
 
 

Tras haber leído bastante sobre Reinforcement Learning y tras buscar librerías fáciles de entender y de usar tanto en R como en Python, no he conseguido encontrar nada sencillo, que se acerque ni tan siquiera al caso más básico de agentes como es el problema del k-armed bandit y de funciones que de algún modo me permita generar un agente aplicado a algún caso o a un modelo de datos. Tal vez sea, y lo digo humildemente, porque algo se me escapa y no he visto lo suficiente, pero, aunque hay algunas cosas, resultan complejas darle una aplicación fuera del “ejemplo preparado” que se presenta como puede ser el caso de la librería contextual. Además, me temo que tal vez no sea el único que ha observado esta situación tal y como he podido hablar con más gente.


Por tanto, voy a intentar en las siguientes líneas crear un ejemplo sencillo y espero que claro sobre RL que por supuesto podría mejorarse pero que podría ser implementado bajo un sistema que se “alimente correctamente” bajo la aplicación que describo a continuación.


ree

El caso que traigo a colación puede imaginarse como que tengo unas 2500 máquinas tragaperras cuyo coste de uso es de 10€ y podemos ganar 1000€ si acertamos y 0€ si erramos. Para exponer este caso en mi github:



Puede descargarse el código que consiste en 3 programas (conviene guardar todo en una carpeta donde existan 2 sub-carpetas code y data si se quiere que todo funcione), ya que este ejemplo viene con demo para que se pueda adaptar a algún caso real:


  • Un primer programa en la subcarpeta data crea un dataset denominado bandits este dataset consta de un ID por máquina y la probabilidad de éxito asignada a cada una. Por supuesto, en un caso real, sólo tendremos la columna ID (que es la que se usa en el código), la otra no se tiene, porque será desconocida, salvo a lo sumo para el dueño de las tragaperras. En este caso se generan máquinas 2500 con números aleatorios uniformes entre 0 y 0.02 (en el programa 3, se pueden modificar estos parámetros para otros ejemplos). Además, ejecutando este programa vía el citado programa 3, se genera un histórico que en este caso tendría unas n_historico = 20 observaciones (tipo 0 – 1) por cada una de las 2500 máquinas (siendo esta la muestra base inicial y el punto de partida, conocido del problema)

 

  • Un segundo programa contiene las funciones específicas que van a utilizarse en las iteraciones del agente de modo que, si se lee con detenimiento el código, lo que permiten estas funcione es dar los siguientes pasos:

 

  1. La creación de un número aleatorio entre 0 – 1 que permita aplicar por iteración y muestra de máquinas a utilizar en cada una (que en el caso del ejemplo básico que dejo es n_inversion = 100) el dilema exploración – explotación 


  2. Bajo el dilema del paso anterior el resto de las funciones permiten montar la siguiente lógica por iteración:


    1. Se eligen el (1 – dilema)% de las máquinas con mayor tasa de éxito del paso anterior

    2. Se elige aleatoriamente dilema% del resto de las máquinas que no están entre las anteriores

    3. Se generan las observaciones de los anteriores pasos y se dejan a missing las no conocidas asociándose cada dato a cada máquina

    4. Se integra la fila al fichero histórico

 

  • El tercer programa básicamente permite activarlo todo y realiza las siguientes acciones:

 

  • Va a ordenar al agente que actúe n_iter = 100 veces sobre un número de máquinas igual n_inversion = 100 y con una estrategia exploración – explotación de dilema = 0.1 (todo esto se puede modificar por el usuario pero por favor, meter cosas lógicas y no “romper” el programa, no he puesto nada de tratamiento de errores ya que no es una aplicación sino una idea lo que estoy tratando de explicar!!!!)

 

  • Por otro lado, va a calcular el beneficio (o coste) inicial de haber hecho el estudio (con el programa 1) por haber generado 20 observaciones por cada una de las 2500 máquinas donde se fue a modo fuerza bruta (en este caso se tiene un coste que sería coste_uso = 10 que multiplicado por cada una de las 2500 máquinas y las 20 observaciones sería igual a 500.000€ pero como algunas dan premio igual a ganancia_acierto = 1000, gran parte de ese número se reduce como se observa tras la ejecución), saliendo el coste inicial por -3000€ en el caso de la poc que está preparada

 

  • La inicialización (re-generación de la información) se hace con la función genera_entorno() que además de producir la información en data necesaria, genera la matriz_historico que es la que tiene la información que vendría de la observación de las máquinas (este riesgo, que en muchas ocasiones como es este caso sería un coste, debe ser asumido si se quiere tener una aproximación a la realidad)


  • Tras esto con la función genera_dinamica_prob_futuras() se generan las probabilidades anteriormente comentadas para aplicar el dilema exploración – explotación

 

  • Finalmente se aplica el agente que en la primera iteración hace uso exclusivamente de la matriz_historico y en el resto de las iteraciones incorpora la información que obtiene tras revisar las n_inversion = 100 máquinas. Esto genera un dataset que se denomina dinamico.csv que si se observa tendrá un número de observaciones igual n_historico + n_iter = 20 + 100 = 120. Sin missing en los primero 20 registros y con missing a partir de la fila 21 hasta la 120

 

Así pues, tras ejecutar el agente sobre el entorno creado y ver su actuación bajo un trade-off exploración – explotación de un 10% se obtendría el siguiente resultado:

ree

Donde beneficio0 es el coste que se ha tenido que pagar para generar el histórico mientras que en beneficio_final se observa el acumulado del beneficio tras las 100 iteraciones. Además, el agente genera, no solo las nuevas observaciones incorporadas en el dataset dinamico.csv que pueden ser estudiadas, sino que además genera el vector beneficiosIter con los beneficios (o pérdidas) que se han ido generando en cada iteración por las máquinas elegidas.


La pregunta a realizar es ¿Qué pasaría si no se hace ninguna exploración? Es decir, si sólo se consideran los resultados obtenidos en las 20 iteraciones iniciales, el resultado entonces sería el siguiente (dilema = 0):

ree

Es decir, el dato inicial es útil, pero se habría obtenido un mejor resultado con un poco más de exploración, ya que claramente en la muestra inicial no tienen por qué estar las máquinas óptimas.


Por otro lado, también cabe preguntar ¿Qué ocurre si nos lanzamos a una “locura exploratoria” con un nivel de exploración del 50% o con dilema = 0.5? En este caso el resultado, empieza a ser decaer, aunque al menos se cubre la inversión:

ree

Finalmente, y para no extenderme demasiado os dejo varias indicaciones para usar estos programas y algunos comentarios sobre los resultados que se pueden obtener:

 

  1. El programa puede preparar diversos entornos con una gran versatilidad de situaciones para analizar donde es factible, cambiar el número de máquinas, alterar la distribuciones de las probabilidades de éxito, aumentar o disminuir el número de máquinas a las que se apuesta por iteración, etc


  2. Los resultados obtenidos están en consonancia con el dilema exploración – explotación, pero se debe tener en cuenta que si se altera por ejemplo el valor máximo de probabilidad de éxito de las distribuciones, los resultados pueden cambiar radicalmente y debe pensarse en éstos ya que la teoría funciona cuando el nivel de aleatoriedad de éxito es relativamente bajo y por tanto resulta difícil encontrar “buenas máquinas” que den premios, cuando la tasa de éxito es elevada, en general el efecto del dilema exploración – explotación estará más diluido


  3. También conviene tener en cuenta que en este caso de RL se tiene que el agente se comporta de forma totalmente reactiva al entorno y realmente trata de “perseguir aciertos” cuando “toca una palanca”, dejándose llevar por esas pruebas que va haciendo, sin embargo, su actuación no altera el modo en el que cada máquina genera el dato, sino que el agente aprende en base a los resultados que su elección va generando, no teniendo en consecuencia información en las máquinas que no haya usado durante una determinada iteración


  4. Aplicaciones de este ejemplo pueden tener sentido en sistemas de promoción de anuncios on-line, compra – venta de palabras, movimientos bursátiles y otros; pero debe tenerse en cuenta que debe prepararse la información de modo que se tenga una estructura similar (y en todo caso más completa) que los datasets aquí denominados basehist.csv pero sobre todo dinamico.csv teniendo en cuenta que el valor p de bandits sólo es conocido en esta PoC e incluso si “el fabricante o responsable de las máquinas” ve que ganamos demasiado dinero bajo nuestras elecciones, lo normal es que trate de alterar y cambiar algunas p ya que se está en todo caso ante un juego de suma 0, si unos ganan otros pierden

 
 
 

© 2021 by Francisco J. Rodríguez Aragón. Proudly created with Wix.com

bottom of page