• Home
  • artículos
  • Your Empire needs you! Y también a la regresión logística.
Search

Your Empire needs you! Y también a la regresión logística.

 

El Imperio necesita nuevos reclutas. Pilotos de caza TIE para ser más exactos.

Su número ha disminuido de forma preocupante. Es uno de los puestos dentro de la Marina Imperial que registra una mayor tasa de mortalidad.

Así que el tío Darth ha abierto un proceso de reclutamiento.

Se han presentado tropecientos n mil voluntarios en los distintos puntos de reclutamiento que el Imperio tiene a lo largo y ancho de la galaxia.

Las pruebas para entrar en la academia de vuelo son bastante duras. Y aún superándolas, no garantizan convertirse en piloto.

Una vez dentro de la academia, los reclutas se enfrentan a 6 años de formación. No todos los novatos terminan con éxito el proceso, el más exigente dentro de la Marina Imperial.

Formar un piloto le cuesta a la Marina un auténtico pastizal. Por eso quiere ajustar el tiro. Le interesa algún sistema para que se descarte a los que, aún habiendo superado las pruebas de reclutamiento, tengan mayor probabilidad de NO superar los 6 años de formación en la academia.

Y aquí entramos nosotros, porque lo valemos, y porque en post anteriores acabamos de ver un algoritmo de Machine Learning que nos viene que al pelo: la regresión logística. Éste modelo nos va a clasificar a los reclutas aspirantes a entrar en la academia que con mayor probabilidad se convertirán finalmente en pilotos de caza.

Cómo vamos con prisa, ya sabéis que el tío Darth no es conocido por su paciencia, vamos a utilizar el modelo de scikit-learn y explicar un poquito cómo ponerlo en producción para que la Marina pueda usarlo cuanto antes.

Para entrenar el modelo, se nos facilitan datos de los anteriores procesos, en los que figuran las notas obtenidas por los reclutas y una variable que nos dice si el recluta superó los 6 años de adiestramiento o si finalmente fue expulsado de la academia de vuelo.

Os lo podéis bajar de aquí.

Vamos a ver los campos que componen el dataset.

df.columns
Index(['nombre', 'planeta', 'especie', 'test_1', 'test_2', 'test_3', 'test_4', 'test_5', 'test_6', 'test_7', 'test_8', 'test_9', 'test_10', 'second_try', 'outcome'], dtype='object')

Vemos que en total se les realizaron 10 test. Además de los campos con los valores de dichos tests, se incluyen algunos datos de los aspirantes, y una columna llamada second_try, que indica si era la primera vez que el aspirante se presentaba a las pruebas o por el contrario se había presentado más veces. Su primera vez corresponde al valor cero, y el resto de valores indican el número de veces que lo intentó. El campo outcome indica si el aspirante logró terminar con éxito el curso de 6 años de la academia y finalmente convertirse en piloto de caza.

Podemos ver que en la base de datos del Imperio se guardan los resultados de las pruebas de 1.906.714 aspirantes.

df.shape

(1906714, 15)

Vamos a sacar una muestra para ver qué pinta tienen los datos.

nombreplanetaespecietest_1test_2test_3test_4test_5test_6test_7test_8test_9test_10second_tryoutcome
0Bortr LussUbaOgemite769944491500
1Tripp AfuAnoatTwi’lek829327279600
2Rowan AtarMardona IIITwi’lek648939462901
3Genna ZheenMijosTof965638269200
4Rudo VargaChandelCerean697634566801
5Regal KenzeeKethmandiBith654892715600
6Lyzo BoketeVendaxaTerentatek974717788300
7Spike VerbekeDinzoTroig565786738700
8Voda BujoldTroganSelonian592898888100
9Kel WooroEmpress TetaP’w’eck798646154200
10Nik TallavFaranaTrianii769227534901
11Karreal KhelWoostriCoway634855173200
12Mathius SamlonDinwa PrimeSelkath896241589901
13Aurine LockheartCandovantThakwaash317493291900
14Zon LeeDagobahKubaz975378773701
15Spike CvetkovicLola SayuChalactan465865516800
16Tootu NwythonXalaYuuzhan Vong934248261100
17Arten KeepsalaZeltrosYevetha356253317100
18Nuray ZorabosPlexisWhiphid517564324100
19Riczo RichardsonTrasseTeek214434593700

Podemos sacar una gráfica para ver cuántos aspirantes consiguieron convertirse en pilotos y cuántos no.

df['outcome'].value_counts()

0    1502740
1     403974
Name: outcome, dtype: int64

Vemos que solo un 21.18% de los aspirantes logró convertirse en piloto.

Por curiosidad podemos sacar el top 5 y el bottom 5 de los planetas y razas de los aspirantes de superan el curso y de los que no, respectivamente.

df[['planeta', 'outcome']].groupby('planeta').sum().sort_values(by=['outcome'], ascending=False).head(5)

TOP 5 POR PLANETAS

planetaoutcome
Botajef2472
Ahakista2408
Delphon2400
Birren2393
Byss969

df[['planeta', 'outcome']].groupby('planeta').sum().sort_values(by=['outcome'], ascending=False).tail(5)

BOTTOM 5 POR PLANETAS

planetaoutcome
Polmanar433
Insk432
Mugaar428
Dennogra426
Vallt421

df[['especie', 'outcome']].groupby('especie').sum().sort_values(by=['outcome'], ascending=False).head(5)

TOP 5 POR ESPECIE

especieoutcome
Gossam1641
Gamorrean1619
Pa’lowick1615
Paaerduag1608
Sauvax1604

df[['especie', 'outcome']].groupby('especie').sum().sort_values(by=['outcome'], ascending=False).tail(5)

BOTTOM 5 POR ESPECIE

especieoutcome
Askajian1429
Chistori1421
Lannik1417
Rodian1411
Kitonak1405

Mmm… resulta que los Gamorrean son la segunda raza que más se gradúa como piloto de caza. Bueno, es lo que tiene construirte unos datos aleatorios, la magia se rompe en algún momento…

Antes de entrenar nuestro modelo con la librería scikit-learn, vamos a utilizar otra librería de python, statmodels para crear un primer modelo de regresión logística y obtener los p-valores de las variables predictoras.

Los p-valores corresponden a los contrastes de hipótesis de cada una de las variables predictoras., y nos indican el grado de importancia en el modelo. Cuanto más grande es dicho valor, la influencia en el modelo que tiene dicha variable es más pequeña, y viceversa, cuanto más pequeño sea el p-valor, mucha más importancia tendrá en el modelo la variable correspondiente.

Así pues, las variables más significativas tendrán los valores más pequeños.

Resumiendo, los p-valores se utilizan para determinar que términos deben de mantenerse en el modelo de regresión.

Vamos a comprobar si tenemos que liquidarnos alguna columna del dataset que no nos aporte nada, quedándonos de primeras con los campos de los tests y second_try.

import statsmodels.api as sm

#VARIABLES PREDICTORAS
cols = list(df.columns[3:-1])
X = df[cols]
#VARIABLE A PREDECIR
y = df['outcome']

logit_model = sm.Logit(y, X)

result = logit_model.fit()

result.summary2()
Model:LogitPseudo R-squared:0.168
Dependent Variable:outcomeAIC:1637764.9357
Date:2019-06-16 21:01BIC:1637902.0056
No. Observations:1906714Log-Likelihood:-8.1887e+05
Df Model:10LL-Null:-9.8467e+05
Df Residuals:1906703LLR p-value:0.0000
Converged:1.0000Scale:1.0000
No. Iterations:7.0000
Coef.Std.Err.zP>|z|[0.0250.975]
test_10.02050.000728.67980.00000.01910.0219
test_2-0.05060.0007-70.53940.0000-0.0520-0.0492
test_3-0.17610.0007-238.04210.0000-0.1776-0.1747
test_4-0.17610.0007-237.81350.0000-0.1775-0.1746
test_5-0.19390.0007-260.12730.0000-0.1953-0.1924
test_60.14530.0007198.53430.00000.14380.1467
test_7-0.04890.0007-68.11880.0000-0.0503-0.0475
test_80.00610.00078.48580.00000.00470.0075
test_9-0.20170.0007-269.63760.0000-0.2031-0.2002
test_100.38560.0008466.71920.00000.38400.3872
second_try-0.53340.0043-124.83680.0000-0.5418-0.5250

Bueno, como podéis ver todos los p-valores son cero. Esto es así porque, como os he dicho antes con el tema del Gamorreano, los datos los he creado de forma aleatoria y resulta que todos los campos tienen la misma relevancia. Pero es muy importante fijarte en esto cuando estemos tratando datos reales, es uno de los pasos a dar para poder ajustar bien tu modelo.

Podríamos usar esta misma librería para nuestro modelo de regresión logística. Pero hemos dicho que vamos a usar scikit-learn, así que vamos a ello:

Importamos las librerías y funciones necesarias…

from sklearn import linear_model
from sklearn.model_selection import train_test_split
from sklearn import metrics

Dividimos los datos en set de entrenamiento (70%) y set de prueba (30%)…

#VARIABLES PREDICTORAS
cols = list(df.columns[3:-1])
X = df[cols]
#VARIABLE A PREDECIR
y = df['outcome']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state = 0)

Y entrenamos el modelo…

logit_model_sk = linear_model.LogisticRegression(solver='lbfgs')
logit_model_sk.fit(X,y)



LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                   intercept_scaling=1, l1_ratio=None, max_iter=100,
                   multi_class='warn', n_jobs=None, penalty='l2',
                   random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                   warm_start=False)

Calculamos las predicciones del modelo para los datos de test…

prediction = logit_model_sk.predict(X_test)

Podemos sacar la matriz de confusión:

metrics.confusion_matrix(y_test, prediction)



array([[450064,   1060],
       [  2645, 118246]])

Calculamos alguna métrica, que ya hemos visto en post anteriores…

metrics.accuracy_score(y_test, prediction)
0.9935228971268236

metrics.precision_score(y_test, prediction)
0.9911152833889326

metrics.recall_score(y_test, prediction)
0.9781207864936182

metrics.f1_score(y_test, prediction)
0.9845751612218304

Vemos que nuestro modelo se ajusta bastante bien a los datos.

Muy bien!. Ya tenemos nuestro modelo entrenado y hemos comprobado que funciona bastante bien haciendo su trabajo de clasificación.

¿Y ahora qué?

Pues ahora hay que sacar provecho del modelo, poniéndolo en producción.

¿Qué necesitamos?. Muy sencillo. Necesitamos los coeficientes de la ecuación de regresión que vimos en Descubriendo la regresión logística

    \[  f(x) = a + \sum_{i=1}^{n}  b_{n} \cdot x_{n} = a +  \vec{B} \cdot \vec{X} \]

Necesitamos los bn y el término independiente a.

Sencillo, se los pedimos al modelo…

coeficientes = logit_model_sk.coef_[0].tolist()



[1.0638692083623609,
 0.7373641809108851,
 0.1381370721237855,
 0.13605304050542538,
 0.049279543208178164,
 1.6248878638411526,
 0.7431579591295562,
 0.9958638321778109,
 0.01793654409627288,
 2.804762990311059,
 -0.270819960978192]
a = logit_model_sk.intercept_



array([-49.7687682])

Una vez que tenemos los coeficientes y el término independiente, lo mejor es guardarlos en un repositorio al que se puede tener acceso, por ejemplo en una tabla en una base de datos. Vamos a suponer que utilizamos una base de datos relacional tipo MySQL, SQL Server, Oracle, etc.

Lo siguiente sería programar un procedimiento en la base de datos, tal que cuando se realice una nueva inserción con las notas de un aspirante a recluta, el procedimiento calcule la probabilidad del modelo de regresión y clasifique al recluta como apto o no apto.

El procedimiento debe calcular esto:

    \[ \boxed{p = \frac{1}{1 + e^{-f(x)}}} \]

En nuestro caso quedaría así:

    \[ f(x) = -49.768 +1.063 test_1 + 0.737 test_2 + 0.138 test_3 + 0.136 test_4 + 0.049 test_5 + 1.624 test_6 + 0.743 test_7 + 0.995 test_8 + 0.017 test_9 + 2.804 test_10 - 0.27 second_try \]

    \[ p = \frac{1}{1 + e^{-f(x)}}} \]

Por ejemplo, en Python podríamos hacer esto:

import math
import numpy as np

def probabilidad(nuevos_valores):
    p = 1/(1 + math.exp(-(a + np.dot(coeficientes, nuevos_valores))))
    return p

La función coge como argumento nuevos_valores, que serían los valores de las pruebas de un nuevo recluta, y devuelve la probabilidad de que ese recluta llegue a superar los 6 años de entrenamiento.

Nos quedaría tomar una decisión importante. Hemos dicho que el modelo devuelve una probabilidad. Tenemos que decidir qué valor utilizar como límite de esa probabilidad para considerar que un recluta es apto o no. A ese límite se le suele llamar threshold.

Ésta decisión depende de múltiples factores, dependiendo del caso de negocio para el que estemos utilizando el clasificador. Por ejemplo, si lo que queremos es obtener a cuántas personas llamar para una campaña de marketing, tendremos que tener en cuenta el tamaño y los recursos de los que disponemos en nuestro Call Center, y ajustar ese threshold.

Y ojo! Al cambiar el threshold, nuestra matriz de confusión también cambiará, por lo que tendremos que ser precavidos para evitar aumentar los errores en la calificación.

Un método que nos ayudará a elegir un buen threshold será la curva ROC, de la que hablaré en mi siguiente post.

Así, cambiamos nuestro procedimiento, en nuestro caso una función de python, para añadir ese threshold como argumento:

import math
import numpy as np

def probabilidad(threshold, nuevos_valores):
    res = 0
    p = 1/(1 + math.exp(-(a + np.dot(coeficientes, nuevos_valores))))
    
    if p >= threshold:
        res = 1
return res

Y ya tendríamos nuestro modelo en producción.

Nos vemos en el siguiente post!

Deja una respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.