¿Te fías de tu clasificador?
Un modelo de bajas de clientes ya está entrenado. Tú pones el umbral: cada falso positivo es una llamada de más y cada falso negativo, un cliente que se va.
MODEL es una regresión logística ya ajustada sobre CUSTOMERS (80 clientes sintéticos: meses de antigüedad, tickets de soporte, gasto mensual y si se dieron de baja). Un porcentaje de acierto no te dice nada hasta que sabes qué errores comete. Escribe seis funciones:
sigmoid(z): 1 / (1 + e^-z).
probability(model, customer): z = b + Σ w[i] · (customer[feature] - mean[i]) / sd[i] sobre FEATURES, y devuelve sigmoid(z).
confusionMatrix(probs, labels, threshold): { tp, fp, fn, tn }. Se predice baja cuando p >= threshold; labels lleva 1 para las bajas reales.
metrics(cm): { precision, recall, accuracy }, con 0 cuando un denominador es 0.
cost(cm, costFP, costFN): fp · costFP + fn · costFN.
bestThreshold(probs, labels, costFP, costFN): el umbral de THRESHOLDS con menor coste; en empate, el más bajo.
La vista previa pinta la matriz y la curva de coste, y mueve el umbral y el precio relativo de cada error.
Retos 0/4
- sigmoid y probability: la curva en S sobre las variables estandarizadas
- confusionMatrix cuenta los cuatro casos desde el umbral (p >= threshold es baja)
- metrics: precisión, recall y acierto, sin dividir entre cero
- cost y bestThreshold: el umbral baja cuando perder un cliente sale caro
function sigmoid(z) {
// 1 dividido entre (1 + e^-z), la curva en S
return 0.5;
}
function probability(model, customer) {
// z = b + suma de w[i] * (customer[variable] - mean[i]) / sd[i], y luego sigmoid
return 0.5;
}
function confusionMatrix(probs, labels, threshold) {
// se predice baja cuando p >= threshold; cuenta tp, fp, fn, tn
return { tp: 0, fp: 0, fn: 0, tn: 0 };
}
function metrics(cm) {
// precisión, recall y acierto; 0 cuando un denominador es 0
return { precision: 0, recall: 0, accuracy: 0 };
}
function cost(cm, costFP, costFN) {
// llamadas de más por costFP más bajas sin aviso por costFN
return 0;
}
function bestThreshold(probs, labels, costFP, costFN) {
// el umbral de THRESHOLDS con menor coste (en empate, el más bajo)
return 0.5;
}
const probs = CUSTOMERS.map((c) => probability(MODEL, c));
console.log(confusionMatrix(probs, LABELS, 0.5), bestThreshold(probs, LABELS, 1, 8));La idea
Un clasificador no decide: da una probabilidad. La decisión la pone el umbral, y el umbral lo pones tú con los costes de tu negocio en la mano. La matriz de confusión separa los dos errores posibles: avisar a un cliente que no se iba a ir (una llamada de más) y no avisar a uno que se va (un cliente perdido). Casi nunca cuestan lo mismo.
Con el umbral a 0,5 el modelo de este ejercicio acierta mucho, pero se le escapan bajas. Si una baja sin aviso cuesta como veinte llamadas, bajar el umbral a 0,2 sale más barato aunque el «acierto» empeore. Esa cuenta es la que importa, y es la que el porcentaje de acierto esconde.
La precisión te dice cuántas de tus alarmas eran reales; el recall, cuántas bajas reales has cazado. Subir una suele bajar la otra, y el umbral es la palanca entre las dos.
Esto conecta con la transparencia que pide la Ley de IA de la UE para los sistemas que afectan a personas: poder decir qué error acepta el sistema, y por qué, es parte de la documentación.
Hazlo en tu máquina
Con Python y scikit-learn, sobre el CSV de este ejercicio (CC BY 4.0). Cambia el umbral y mira cómo se mueve la matriz:
curl -O https://vorluxai.com/data/labs/churn-clientes.csv
python3 -c "
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
df = pd.read_csv('churn-clientes.csv')
X, y = df[['meses', 'tickets', 'gasto_mensual_eur']], df.baja
p = LogisticRegression().fit(X, y).predict_proba(X)[:, 1]
for t in (0.2, 0.5, 0.8): print(t, confusion_matrix(y, p >= t).ravel())
" Para profundizar: la referencia de Google Machine Learning Crash Course (en inglés) →
¿Esto en producción, con tus datos? Hablemos 15 minutos →