Implementare l'IA in azienda: dal problema alla decisione, con i costi in mano
Corso Machine Learning Engineer · Modulo 00 · Mindset data-driven · Lezione 4
Obiettivo. Insegnare a valutare un'iniziativa di machine learning prima di scrivere codice, e a trasformare l'uscita di un modello (una probabilità) in una decisione con un costo. Il lettore impara la domanda a cinque campi, il ciclo CRISP-DM, la soglia ottimale derivata a mano e verificata nel codice, e un criterio di scelta fra costruire, comprare, o non fare.
Falso mito. "Il progetto ML consiste nel costruire il modello." Il modello è il pezzo più piccolo; il resto è definire la decisione, procurare i dati, inserire il risultato in un processo, e mantenerlo.
Introduzione
La lezione 03 ti ha dato le metriche. Questa ti dà il passo che le metriche da sole non fanno: dire "quindi che cosa facciamo?". Un modello restituisce probabilità; un'azienda prende decisioni (chiamare o non chiamare un cliente, bloccare o lasciar passare un pagamento). Il ponte fra i due è la soglia, e la soglia giusta dipende dai costi, non dalla matematica del modello.
Roadmap. (1) La scheda a cinque campi. (2) Il ciclo di vita CRISP-DM. (3) Dal punteggio alla decisione: formula e derivazione. (4) Codice: ricerca della soglia. (5) Costruire, comprare, o non fare. (6) Il costo che non vedi: il debito tecnico dei sistemi ML.
Cos'è, perché esiste, quando NON farlo
La scheda a cinque campi
Prima di ogni progetto, cinque righe. Se una resta vuota, il progetto non è pronto.
| Campo | Domanda | Esempio (previsione di abbandono clienti) |
|---|---|---|
| 1. Decisione | Quale scelta cambia grazie al modello? | Chi contattare con un'offerta di fidelizzazione |
| 2. Azione | Cosa succede dopo la previsione, e chi lo fa? | Il servizio clienti chiama i clienti segnalati |
| 3. Dato | Quale informazione avevo al momento della decisione? | Storico acquisti, ticket aperti. Non l'informazione "ha disdetto", che arriva dopo |
| 4. Metrica | Come si misura il successo, in unità di business? | Clienti trattenuti, costo per cliente trattenuto |
| 5. Costo degli errori | Quanto costa un falso negativo? E un falso positivo? | Perdere un cliente contro offrire uno sconto a chi sarebbe restato |
Il campo 3 nasconde la trappola più costosa del machine learning, la fuga di informazione dal futuro (data leakage): usare come ingresso del modello un dato che nella realtà non è ancora disponibile. In laboratorio funziona benissimo; in produzione non esiste.
Quando NON farlo
- Se la decisione non cambia in base alla previsione (campo 1 e 2): anche un modello perfetto non produce valore.
- Se non puoi procurare il dato al momento giusto (campo 3).
- Se il costo del problema è inferiore al costo di costruire e mantenere il modello (sezione ROI).
- Se una regola semplice cattura già quasi tutto il valore: prova la baseline di dominio (per esempio "clienti che non comprano da 90 giorni") prima di un modello.
I quattro casi sono in ordine di costo di scoperta: i primi due li vedi compilando la scheda, in un pomeriggio e senza scrivere codice; il terzo richiede una stima dei costi, che faremo più sotto; il quarto richiede di provare davvero la regola semplice. Il vantaggio di scoprirli prima è che fermare un progetto a questo punto non costa nulla.
Il ciclo di vita: CRISP-DM
Il processo CRISP-DM (2000) descrive sei fasi in un ciclo, non in una linea:
- Comprensione del business: obiettivi, criteri di successo (la scheda a cinque campi).
- Comprensione dei dati: che cosa c'è, qualità, lacune.
- Preparazione dei dati: pulizia, costruzione delle variabili.
- Modellazione: scelta degli algoritmi, taratura.
- Valutazione: il modello risponde all'obiettivo di business, non solo alla metrica tecnica?
- Rilascio (deployment): inserimento nel processo, monitoraggio.
Le frecce tornano indietro: la valutazione spesso rimanda alla comprensione del business, perché si scopre che la domanda era sbagliata. Il corso ricalca la stessa sequenza: i moduli 01-02 sono gli attrezzi per le fasi 2-3, il 03-08 per la 4, il 11 per la 6.
Primo esempio: dal punteggio alla decisione
Analogia: la soglia dell'allarme antincendio
Un rilevatore di fumo ha una sensibilità regolabile. Troppo sensibile: suona ogni volta che tosti il pane (falsi allarmi, e prima o poi lo disattivi). Poco sensibile: tace durante un incendio vero. Dove metti la sensibilità? Dipende da cosa costa un falso allarme (fastidio) e da cosa costa un incendio non rilevato (catastrofe). Non è una scelta tecnica: è una scelta di costi. In un ospedale e in una cucina la risposta è diversa.
Formula: la soglia ottimale, derivata
Il modello ti dà p = P(y = 1 | x): la probabilità che il caso sia positivo. Definiamo i costi:
C_FN: costo di un falso negativo (dici "no" ma era "sì").C_FP: costo di un falso positivo (dici "sì" ma era "no").- Gli esiti corretti costano 0 (semplificazione; si generalizza facilmente).
Sono solo due numeri, ma sono quelli che la lezione 03 non dava: la matrice di confusione contava gli errori, qui li pesiamo. Se non riesci a stimare nemmeno l'ordine di grandezza del rapporto tra i due costi, la soglia resta un'ipotesi e l'analisi di sensibilità più sotto serve proprio a questo. Dato un caso con probabilità p, confronta il costo atteso delle due azioni:
- Se dici "sì" (agisci): sbagli quando il caso è negativo, con probabilità
1 − p. Costo atteso:(1 − p) · C_FP. - Se dici "no" (non agisci): sbagli quando il caso è positivo, con probabilità
p. Costo atteso:p · C_FN.
Le due espressioni sono simmetriche: ciascuna è il costo dell'errore moltiplicato per la probabilità di commetterlo. Non esiste un'azione senza rischio; si sceglie quella il cui errore atteso pesa meno, e il punto in cui le due pesano uguale è la soglia. Conviene dire "sì" quando il suo costo atteso è minore o uguale:
(1 − p) · C_FP ≤ p · C_FN
C_FP − p·C_FP ≤ p·C_FN
C_FP ≤ p · (C_FN + C_FP)
p ≥ C_FP / (C_FP + C_FN)
Soglia ottimale = C_FP / (C_FP + C_FN) (risultato noto in letteratura, per esempio in Elkan 2001, sotto l'ipotesi che le probabilità siano calibrate).
Leggila: se un falso negativo costa 20 volte un falso positivo (C_FN = 100, C_FP = 5), la soglia è 5/105 ≈ 0.048. Cioè agisci anche quando il caso ha solo il 5% di probabilità di essere positivo. La soglia "automatica" 0.5 è ottimale solo se i due errori costano uguale, che quasi mai è vero.
Verifica con il codice
Simuliamo un problema (abbandono clienti, dati inventati), alleniamo una regressione logistica, e cerchiamo la soglia che minimizza il costo totale per forza bruta. Poi confrontiamo con la formula.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(11)
n = 20_000
X = rng.normal(size=(n, 3))
logit = -2.2 + 1.4 * X[:, 0] - 0.9 * X[:, 1] + 0.5 * X[:, 2]
p_vera = 1 / (1 + np.exp(-logit))
y = (rng.random(n) < p_vera).astype(int)
print("tasso base", y.mean().round(3))
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.5, random_state=0, stratify=y)
modello = LogisticRegression().fit(X_tr, y_tr)
p = modello.predict_proba(X_te)[:, 1]
C_FN, C_FP = 100.0, 5.0 # PARAMETRI INVENTATI: sostituiscili con i tuoi
def costo_totale(soglia):
pred = p >= soglia
fn = np.sum((~pred) & (y_te == 1))
fp = np.sum(pred & (y_te == 0))
return C_FN * fn + C_FP * fp
soglie = np.linspace(0.01, 0.99, 99)
costi = np.array([costo_totale(s) for s in soglie])
s_best = soglie[costi.argmin()]
s_teorica = C_FP / (C_FP + C_FN)
print("soglia ottimale empirica:", round(float(s_best), 2), "| costo", costi.min())
print("soglia teorica :", round(s_teorica, 3), "| costo", costo_totale(s_teorica))
print("costo con soglia 0.5 :", costo_totale(0.5))
print("costo 'non fare nulla' :", C_FN * y_te.sum())
print("costo 'agisci su tutti' :", C_FP * (y_te == 0).sum())
Come leggere il codice. Le prime righe costruiscono un problema sintetico: tre caratteristiche casuali X, una combinazione lineare logit che dice quanto ciascuna spinge verso l'abbandono, e la probabilità vera p_vera = 1/(1 + e^{-logit}) (la funzione sigmoide della lezione di analisi). L'etichetta y è estratta con quella probabilità: sappiamo quindi come è fatto il mondo, cosa che nella realtà non accade mai. Si divide in metà training e metà test (stratify=y conserva la quota di positivi), si addestra la regressione logistica e si ottengono le probabilità sul test con predict_proba(...)[:, 1]. costo_totale(soglia) conta falsi negativi e falsi positivi per una data soglia e li moltiplica per i rispettivi costi; il ciclo prova 99 soglie e prende la migliore. Le ultime righe calcolano i costi delle due baseline di business: «non fare nulla» (tutti i positivi sono falsi negativi) e «agire su tutti» (tutti i negativi sono falsi positivi).
Output ottenuto (seed 11, NumPy 2.3.5, scikit-learn 1.8):
tasso base 0.178
soglia ottimale empirica: 0.06 | costo 31435.0
soglia teorica : 0.048 | costo 31435.0
costo con soglia 0.5 : 115790.0
costo 'non fare nulla' : 178400.0
costo 'agisci su tutti' : 41080.0
Che cosa leggere:
- La soglia teorica (0.048) e quella empirica (0.06) danno lo stesso costo totale, 31435: intorno al minimo la curva di costo è piatta, quindi la griglia di 0.01 non distingue le due. La formula funziona.
- Con la soglia di default 0.5 il costo è 115790, oltre il triplo di 31435. Con costi asimmetrici, 0.5 è una scelta costosa.
- Le due baseline di business sono 178400 ("non fare nulla") e 41080 ("agisci su tutti"). Il modello alla soglia ottimale (31435) batte entrambe, ma la distanza dalla seconda è la misura reale del suo valore: 41080 − 31435 = 9645 sul campione di test. Se il modello costa più di così in esercizio per lo stesso periodo, non conviene.
- Nota anche cosa mostra la soglia 0.5: con quel valore il modello costa meno di non fare nulla (115790 contro 178400), ma più di agire su tutti (41080). Un modello mal tarato può perdere contro la baseline più banale.
Il numero da portare in riunione è il terzo punto, non il primo: il valore del modello è la distanza dalla migliore baseline, perché «agisci su tutti» è l'alternativa che il management ha già in mano. Il quarto punto è il monito: la soglia di default non è neutra, e in questo esempio trasforma un modello utile in uno peggiore di una regola banale. I costi 100 e 5 sono ipotesi, non dati di mercato.
Esercizio 1
Quanto costa la soglia sbagliata
La rete idrica di un piccolo comune (scenario simulato, numeri scelti perché i conti vengano interi): 1000 tratti di tubazione, un modello calibrato che li raggruppa in quattro fasce di rischio. Un'ispezione a vuoto costa 150 euro; una perdita non segnalata che degenera in rottura ne costa 1350.
| Probabilità stimata di perdita | Tratti |
|---|---|
| 0.02 | 600 |
| 0.10 | 200 |
| 0.25 | 160 |
| 0.60 | 40 |
- Qual è la soglia ottimale? E se la rottura costasse 450 euro invece di 1350? E se ne costasse 2850?
- Calcola il costo annuo atteso alla soglia ottimale e alla soglia di default 0.5. Quanto costa il default?
- Confronta con le due baseline di business: «non fare nulla» e «ispezionare tutti i tratti».
La tabella è già raggruppata per fasce, quindi non serve nessun modello da eseguire: serve moltiplicare le dimensioni di ogni fascia per la probabilità di perdita. Tieni a mente la regola dell'articolo: in ogni fascia si sceglie l'azione con il costo atteso minore, e la soglia dice solo da quale fascia in poi conviene agire.
Mostra la soluzione
1. 150/(150 + 1350) = 150/1500 = 1/10. Con 450: 150/600 = 1/4. Con 2850: 150/3000 = 1/20. Più cara è la rottura, più basso è il sospetto che basta a far uscire la squadra.
2. Su una fascia in cui si agisce si pagano i sani (n·(1−p) falsi positivi a 150 euro); su una fascia in cui non si agisce si pagano le perdite (n·p falsi negativi a 1350). Soglia 0.10 (si agisce sulle ultime tre fasce): falsi positivi 180 + 120 + 16 = 316 → 47.400 euro; falsi negativi 12 → 16.200 euro; totale 63.600 euro. Soglia 0.5 (si agisce solo sull'ultima fascia): falsi positivi 16 → 2.400 euro; falsi negativi 12 + 20 + 40 = 72 → 97.200 euro; totale 99.600 euro. Il default costa 36.000 euro all'anno in più, con lo stesso identico modello: cambia solo dove si taglia.
3. Le perdite attese in un anno sono 12 + 20 + 40 + 24 = 96. «Non fare nulla»: 96 × 1350 = 129.600 euro. «Ispezionare tutto»: 904 × 150 = 135.600 euro. Il modello alla soglia giusta (63.600) risparmia 66.000 euro all'anno sulla migliore delle due — ed è questo, non l'AUC, il numero da portare in riunione.
Un controllo che vale la pena fare: prova anche la soglia 0.25. Costa 63.600 euro, esattamente come la 0.10. Non è una coincidenza. L'unica fascia trattata in modo diverso dalle due soglie è quella a p = 1/10, e lì agire costa 200 × 0.9 × 150 = 27.000 mentre non agire costa 200 × 0.1 × 1350 = 27.000: alla soglia ottimale le due azioni si equivalgono per definizione, perché è il punto in cui la si è trovata. Da qui la morale pratica: intorno al minimo la curva di costo è piatta, quindi sbagliare la soglia di poco non si paga quasi; sbagliarla di molto, come fa il default, si paga tutto.
Un secondo esempio, contrastante: quando la formula sbaglia
Il trade-off che segue dice che la formula vale per probabilità calibrate. Vediamo che cosa succede quando non lo sono. Un modo semplice per ottenere probabilità non calibrate è chiedere alla regressione logistica di dare più peso ai positivi (class_weight="balanced"): si usa spesso per «aiutare» il modello con classi rare, ma sposta le probabilità verso l'alto.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(11)
n = 20_000
X = rng.normal(size=(n, 3))
logit = -2.2 + 1.4 * X[:, 0] - 0.9 * X[:, 1] + 0.5 * X[:, 2]
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.5, random_state=0, stratify=y)
C_FN, C_FP = 100.0, 5.0
def costo(p, soglia):
pred = p >= soglia
return C_FN * np.sum((~pred) & (y_te == 1)) + C_FP * np.sum(pred & (y_te == 0))
soglia_teorica = C_FP / (C_FP + C_FN)
griglia = np.linspace(0.01, 0.99, 99)
for nome, modello in (("standard", LogisticRegression()), ("pesi bilanciati", LogisticRegression(class_weight="balanced"))):
p = modello.fit(X_tr, y_tr).predict_proba(X_te)[:, 1]
costi = [costo(p, s) for s in griglia]
print(f"{nome:<16}: probabilità media {p.mean():.3f} (tasso vero {y_te.mean():.3f}) | "
f"soglia migliore {griglia[int(np.argmin(costi))]:.2f} costo {min(costi):.0f} | "
f"con la soglia teorica {soglia_teorica:.3f} costo {costo(p, soglia_teorica):.0f}")
Output ottenuto:
standard : probabilità media 0.180 (tasso vero 0.178) | soglia migliore 0.06 costo 31435 | con la soglia teorica 0.048 costo 31435
pesi bilanciati : probabilità media 0.387 (tasso vero 0.178) | soglia migliore 0.19 costo 31355 | con la soglia teorica 0.048 costo 36840
Come leggere il codice. Si ripete lo stesso problema con due modelli. Una probabilità è calibrata se, fra tutti i casi in cui il modello dice «0.3», circa il 30% è davvero positivo; un modo rapido per accorgersi di una cattiva calibrazione è confrontare la probabilità media prevista con il tasso reale. Il modello standard è calibrato (media 0.180 contro un tasso vero di 0.178), e la formula funziona: il costo alla soglia teorica coincide con il minimo. Con i pesi bilanciati la probabilità media sale a 0.387, più del doppio del tasso reale: il modello «esagera» e la soglia teorica 0.048, applicata a probabilità gonfiate, è troppo bassa e fa scattare l'azione su troppi casi (costo 36840 contro un minimo di 31355, ottenuto con una soglia attorno a 0.19). Lezione: la formula ha una condizione. Se la rompi (pesi, ricampionamento, alcuni algoritmi), o ricalibri le probabilità (scikit-learn offre CalibratedClassifierCV), o cerchi la soglia sperimentalmente su dati di validazione, come abbiamo fatto con la griglia.
L'analisi di sensibilità: quanto dipende tutto dai costi inventati?
I costi 100 e 5 sono ipotesi. Prima di fidarsi del risultato bisogna sapere quanto la conclusione dipenda da loro. Il metodo è elementare: si ripete il calcolo con costi dimezzati e raddoppiati, e si guarda se la risposta («conviene il modello») cambia.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(11)
n = 20_000
X = rng.normal(size=(n, 3))
logit = -2.2 + 1.4 * X[:, 0] - 0.9 * X[:, 1] + 0.5 * X[:, 2]
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.5, random_state=0, stratify=y)
p = LogisticRegression().fit(X_tr, y_tr).predict_proba(X_te)[:, 1]
positivi, negativi = int(y_te.sum()), int((y_te == 0).sum())
print("C_FN C_FP soglia costo modello migliore baseline risparmio")
for c_fn in (50.0, 100.0, 200.0):
for c_fp in (2.5, 5.0, 10.0):
soglia = c_fp / (c_fp + c_fn)
pred = p >= soglia
costo_modello = c_fn * np.sum((~pred) & (y_te == 1)) + c_fp * np.sum(pred & (y_te == 0))
baseline = min(c_fn * positivi, c_fp * negativi) # il migliore tra «non fare nulla» e «agire su tutti»
print(f"{c_fn:5.0f} {c_fp:6.1f} {soglia:7.3f} {costo_modello:14.0f} {baseline:18.0f} {baseline - costo_modello:10.0f}")
Output ottenuto:
C_FN C_FP soglia costo modello migliore baseline risparmio
50 2.5 0.048 15718 20540 4822
50 5.0 0.091 26230 41080 14850
50 10.0 0.167 39840 82160 42320
100 2.5 0.024 17722 20540 2818
100 5.0 0.048 31435 41080 9645
100 10.0 0.091 52460 82160 29700
200 2.5 0.012 18925 20540 1615
200 5.0 0.024 35445 41080 5635
200 10.0 0.048 62870 82160 19290
Come leggere il codice. Per ciascuna coppia di costi si calcola la soglia teorica, il costo del modello a quella soglia, e il costo della migliore baseline di business (il minore tra non agire mai e agire sempre). La colonna «risparmio» è la differenza: il valore lordo del modello su 10 000 clienti di test. Nelle nove combinazioni il risparmio resta positivo: la conclusione «il modello conviene» è robusta rispetto a costi dimezzati o raddoppiati; ciò che cambia è l'ordine di grandezza del guadagno. Se invece, in una combinazione plausibile, il risparmio fosse negativo o inferiore al costo di esercizio, il progetto andrebbe ripensato. L'analisi è semplice ma disciplina la discussione: sposta il dibattito da «i tuoi costi sono sbagliati» a «per quali costi cambia la decisione?».
Un controllo di dati: la fuga dal futuro, vista nei numeri
Il campo 3 della scheda («dato disponibile al momento della decisione») sembra una formalità. Mostriamo che una sola variabile sbagliata può rendere il modello apparentemente perfetto. Aggiungiamo ai dati una caratteristica «ha aperto una richiesta di disdetta», che nella realtà si genera dopo la decisione di abbandonare.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(11)
n = 20_000
X = rng.normal(size=(n, 3))
logit = -2.2 + 1.4 * X[:, 0] - 0.9 * X[:, 1] + 0.5 * X[:, 2]
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
richiesta_disdetta = np.where(y == 1, rng.random(n) < 0.95, rng.random(n) < 0.01).astype(float) # nasce DOPO l'abbandono
X_leak = np.column_stack([X, richiesta_disdetta])
for nome, M in (("senza la variabile del futuro", X), ("con la variabile del futuro", X_leak)):
X_tr, X_te, y_tr, y_te = train_test_split(M, y, test_size=0.5, random_state=0, stratify=y)
p = LogisticRegression().fit(X_tr, y_tr).predict_proba(X_te)[:, 1]
print(f"{nome:<32}: AUC sul test = {roc_auc_score(y_te, p):.3f}")
Output ottenuto:
senza la variabile del futuro : AUC sul test = 0.850
con la variabile del futuro : AUC sul test = 0.990
Come leggere il codice. La variabile richiesta_disdetta vale 1 per il 95% di chi abbandona e per l'1% di chi resta: nasce come conseguenza dell'abbandono, quindi in produzione, al momento di decidere chi contattare, non esiste ancora. L'AUC (area sotto la curva ROC) è una misura di quanto il modello ordina bene i casi, tra 0.5 (caso) e 1 (perfetto): con la variabile del futuro passa da un valore realistico a un valore vicino a 1. È esattamente il sintomo del leakage: risultati troppo belli in laboratorio. Il rimedio è nella scheda: per ogni caratteristica, chiedersi «quando la conosco?». Se la risposta è «dopo l'esito», va esclusa.
Trade-off. La formula vale se le probabilità del modello sono calibrate (un p = 0.3 significa che il 30% di quei casi è davvero positivo). La regressione logistica è tipicamente ben calibrata; gli alberi e le foreste spesso no, e richiedono una calibrazione (CalibratedClassifierCV). Se non lo sono, cerca la soglia per forza bruta su dati di validazione, come nel codice.
Il valore di un modello e la scelta build / buy / skip
Formula del valore
Con costo_baseline il costo atteso del miglior comportamento senza modello, costo_modello quello con modello alla soglia ottimale, ed E il costo di esercizio (infrastruttura, monitoraggio, persone, riaddestramento):
valore_netto = (costo_baseline − costo_modello) − E
Sullo stesso periodo di tempo. Se valore_netto ≤ 0, non farlo, per quanto elegante sia il modello. Se il numero dipende molto dai tuoi parametri inventati, fai l'analisi di sensibilità: ripeti il calcolo con costi dimezzati e raddoppiati.
Costruire, comprare o non fare
| Opzione | Quando conviene | Quando no |
|---|---|---|
| Comprare (servizio o prodotto esistente) | Il problema è comune (OCR, trascrizione, traduzione), il dato non è critico, il tempo conta | Dati troppo sensibili per uscire dal perimetro; serve un comportamento molto specifico |
| Costruire | Il dato è il tuo vantaggio, il problema è specifico, hai persone per mantenerlo | Non hai nessuno che lo manterrà: un modello non mantenuto degrada |
| Non fare | La decisione non cambia, il valore netto è ≤ 0, una regola basta | (non esiste un "quando no": va sempre considerata come opzione) |
Esercizio 2
Costruire, comprare o non fare
Stessa rete idrica simulata. Il risparmio teorico dell'esercizio 1 è 66.000 euro all'anno, ed è il massimo che l'iniziativa possa valere. Due modi di realizzarla:
- Costruire: 30.000 euro di sviluppo ammortizzati su 3 anni, più 18.000 euro all'anno di infrastruttura, monitoraggio e riaddestramento. Cattura tutto il risparmio.
- Comprare: un fornitore chiede 4.500 euro al mese. Il suo modello non è tarato su questa rete, quindi Cattura solo l'80% del risparmio.
Le due opzioni differiscono per struttura di costo: costruire ha un costo iniziale ammortizzato più un costo annuo, comprare ha un canone ma una resa inferiore perché il modello non conosce la tua rete. Per confrontarle porta tutto su base annua e confronta ciascuna con il «non fare», che vale zero.
- Calcola il valore netto annuo delle due opzioni, e confrontale con «non fare».
- A quale canone mensile il fornitore diventerebbe indifferente? In forma chiusa.
- Qual è, fra tutti i numeri usati, quello da cui dipende di più la conclusione?
La terza domanda non ha un calcolo unico: prova a variare ogni numero del 10% e osserva quale sposta di più il risultato. Il punto è che le decisioni di acquisto si decidono sui parametri più incerti, non su quelli più facili da misurare.
Mostra la soluzione
1. Costruire costa 30.000/3 + 18.000 = 28.000 euro all'anno e cattura tutto: 66.000 − 28.000 = +38.000 euro all'anno. Comprare cattura 0.80 × 66.000 = 52.800 euro e costa 4.500 × 12 = 54.000: valore netto −1.200 euro, negativo. «Non fare» vale esattamente 0, ed è la baseline contro cui si misurano le altre due: un'opzione con valore netto negativo è peggio del non fare niente, per quanto il modello sia elegante.
2. Si impone valore netto nullo: 0.80 × 66.000 = 12 × canone → canone = 52.800/12 = 4.400 euro al mese. Il fornitore è fuori per 100 euro al mese. Questo cambia la natura della conversazione: non è «il prodotto non va bene», è «a questo prezzo no» — e si tratta.
3. Quell'80%, che è un'ipotesi e non una misura. Rifà il conto con il 90% e comprare torna positivo: 59.400 − 54.000 = +5.400 euro. Con il 75% peggiora a −4.500. È il parametro più incerto e il più decisivo: prima di firmare va misurato su un pilota, non stimato in riunione. Questa è l'analisi di sensibilità applicata alla decisione di acquisto invece che ai costi degli errori.
Il costo nascosto
Sculley et al. (2015) sostengono che i sistemi ML accumulano un tipo particolare di debito tecnico: dipendenze dai dati più difficili da vedere di quelle sul codice, cicli di retroazione (il modello influenza i dati futuri), configurazioni intricate. La conseguenza pratica per chi pianifica: il costo di esercizio E non è un dettaglio, e cresce con il tempo.
La scheda a cinque campi in un secondo dominio
Per vedere che la scheda è generale, compilala per un problema molto diverso: la manutenzione predittiva di una macchina di produzione. Decisione: quando fermare la macchina per un controllo. Azione: il reparto manutenzione programma un intervento nelle 48 ore successive. Dato: letture dei sensori fino a un'ora prima della decisione (non il registro delle riparazioni fatte dopo il guasto). Metrica: ore di fermo non pianificato evitate per mese. Costo degli errori: un falso negativo è un guasto improvviso con fermo di linea; un falso positivo è un controllo inutile. Noti che i due costi sono in unità diverse (ore di produzione, ore di manodopera): il primo lavoro del progetto è convertirli in una unità comune, tipicamente euro, e dichiarare le ipotesi della conversione.
Errori tipici
| Errore | Sintomo | Causa | Rimedio |
|---|---|---|---|
| Ottimizzare la metrica sbagliata | Il modello "migliora" ma il business no | Metrica tecnica (accuratezza) scollegata dal costo | Calcolare il costo atteso in unità di business |
| Soglia a 0.5 per abitudine | Troppi errori del tipo costoso | I costi dei due errori sono diversi | Formula della soglia + verifica su validazione |
| Leakage temporale | Prestazioni stellari, poi crollo in produzione | Variabile non disponibile al momento della decisione | Scheda campo 3; split per tempo, non casuale |
| Nessuna baseline di business | "È più bravo di prima" senza prova | Non c'è un confronto onesto | Calcolare il costo di "non fare nulla" e "agire su tutti" |
| Progetto senza proprietario dell'azione | Il modello gira, nessuno agisce | Campo 2 vuoto | Assegnare chi riceve il punteggio e cosa deve fare |
Nota sul punto 3 (split per tempo). Quando i dati hanno una dimensione temporale, mescolare a caso le righe fa vedere al training il futuro del test. Si ordina per data, si addestra sul passato e si valuta sul periodo successivo.
Dal pilota alla produzione: che cosa guardare
Un pilota ben fatto risponde a una domanda sola: «se mettiamo il modello nel processo, i costi scendono davvero?» Per questo conviene fissare, prima di cominciare, tre cose per iscritto. Primo, il periodo e la dimensione del pilota (quanti clienti, per quante settimane), in modo da avere abbastanza casi per distinguere un miglioramento reale dal rumore (la lezione 6 mostra come calcolare quanti ne servono). Secondo, il gruppo di controllo: una parte dei clienti segnalati che non viene trattata, perché senza confronto non si sa se i clienti trattenuti sarebbero rimasti comunque. Terzo, il criterio di decisione al termine: «se il risparmio netto per cliente supera X, si estende; altrimenti si chiude». Scrivere il criterio in anticipo protegge dalla tentazione, comunissima, di leggere i risultati in modo favorevole al progetto in cui si è investito.
Dopo il lancio il lavoro non finisce: i dati cambiano (stagionalità, nuovi prodotti, cambi di prezzo) e la soglia ottimale con loro. Servono almeno un monitoraggio del tasso base e della distribuzione delle probabilità, un campione di casi verificati a mano ogni mese, e una data di revisione dei costi.
Best practice
- Compila la scheda a cinque campi per iscritto, condivisa con chi dovrà agire.
- Parti da una baseline di dominio e da due baseline di business.
- Fai un pilota piccolo con misura prima/dopo, idealmente con gruppo di controllo (lezione 06).
- Prevedi monitoraggio e riaddestramento nel budget, non dopo.
- Documenta i parametri di costo, chi li ha stabiliti e quando vanno rivisti.
Le prime due regole servono prima di scrivere codice, la terza prima di investire, le ultime due dopo il lancio. Se ne devi scegliere una sola, scegli le baseline: senza il confronto con «non fare nulla» e «agisci su tutti» non puoi sapere se il modello vale qualcosa.
Un vocabolario minimo
- Soglia di decisione: valore di probabilità sopra il quale si agisce.
- Costo atteso: costo di un'azione pesato con la probabilità che sia sbagliata.
- Calibrazione: corrispondenza fra probabilità previste e frequenze reali.
- Baseline di business: il costo del miglior comportamento senza modello (non agire, agire su tutti, una regola semplice).
- Valore netto: risparmio rispetto alla baseline meno il costo di esercizio.
- Leakage: uso di un dato non disponibile al momento della decisione.
- AUC: area sotto la curva ROC; misura di ordinamento tra 0.5 e 1.
- Analisi di sensibilità: ripetere il calcolo variando le ipotesi per vedere se la conclusione cambia.
Le otto voci seguono il flusso della lezione: la decisione (soglia, costo atteso), il confronto (baseline, valore netto), le trappole (calibrazione, leakage), gli strumenti di verifica (AUC, sensibilità). Se in una riunione qualcuno dice «il modello è buono», chiedi a quale baseline e a quale soglia si riferisce.
Mini-progetto
Prendi il codice dell'esempio iniziale e:
- Cambia
C_FNeC_FPin cinque coppie diverse (per esempio 10/10, 50/5, 100/5, 500/5, 100/50). Per ciascuna stampa soglia teorica, soglia empirica e il risparmio rispetto alla soglia 0.5. - Aggiungi un costo di esercizio
Ee calcola ilvalore_nettorispetto alla migliore tra le due baseline. - Trova il valore di
Eoltre il quale il progetto smette di convenire (il punto di pareggio).
Il mini-progetto ripercorre l'articolo in miniatura: la prima voce ti fa vedere come la soglia si muove con i costi, la seconda aggiunge il costo di esercizio, la terza lo trasforma in un numero decisionale, il punto di pareggio. Quest'ultimo è il risultato che porteresti a chi decide il budget.
Challenge
- Easy. Con
C_FN = C_FP, dimostra che la soglia diventa 0.5. - Medium. Estendi la derivazione al caso in cui anche i "corretti" hanno un costo:
C_VPper un vero positivo (ad esempio il costo dell'azione di recupero) eC_VN. Come cambia la soglia? - Hard. Mostra con un esperimento che, se le probabilità non sono calibrate (usa
DecisionTreeClassifier(max_depth=3)), la soglia teorica non coincide con quella empirica. Poi calibra e riprova. - Expert. Simula un cambio di distribuzione (il tasso base raddoppia) e mostra come la soglia ottimale si sposta con la stessa regola. Quando serve riaddestrare e quando basta cambiare la soglia?
- Enterprise. Scrivi la scheda a cinque campi per una decisione reale della tua organizzazione e individua quale campo è il più debole.
Easy e Medium sono dimostrazioni algebriche, Hard ed Expert esperimenti che rompono le ipotesi della formula (calibrazione e distribuzione stabile), Enterprise la porta nel tuo lavoro. Fai almeno Hard: è l'unica che mostra, con i tuoi numeri, perché la formula ha una condizione.
Codice di questa lezione
I due esercizi qui sopra, con il programma che ricalcola in frazioni esatte la soglia, il costo annuo fascia per fascia, le due baseline e il valore netto delle tre opzioni. Scheletro con le parti da completare e, in fondo, le soluzioni commentate. Richiede solo Python 3.12+ (nessuna dipendenza esterna).
Eseguilo con python3 esercizi.py: gira anche con le parti non completate e ti dice quali mancano; il file è nella cartella di download del sito.
Recap
Mappa mentale. Decisione → Azione → Dato (disponibile allora) → Metrica (business) → Costo errori. Soglia ottimale C_FP/(C_FP+C_FN). Valore netto = risparmio − costo di esercizio. Build / buy / skip.
3 concetti: scheda a cinque campi; soglia dal costo; valore netto. 3 errori: metrica scollegata; soglia 0.5 per default; leakage temporale. 3 best practice: baseline di business; pilota con controllo; costo di esercizio in budget. 1 esercizio: il mini-progetto di questa lezione.
Prossima lezione: la cultura del dato: trasformare le domande vaghe in domande a cui i dati possono rispondere.
Nella stessa serie trovi anche: 03 - Opportunità, sfide e rischi dell'IA (la lezione precedente), 05 - Cultura del dato (la lezione successiva).
Fonti
- C. Elkan, The Foundations of Cost-Sensitive Learning (IJCAI 2001) - la soglia di decisione ottimale ricavata dalla matrice dei costi, consultata il 2026-11-12
- CRISP-DM 1.0: Step-by-step data mining guide (Chapman et al., 2000) - le sei fasi del ciclo, da Business understanding a Deployment, consultata il 2026-11-12
- Sculley et al., Hidden Technical Debt in Machine Learning Systems (NIPS 2015) - il debito tecnico dei sistemi ML e i cicli di retroazione, consultata il 2026-11-12
- scikit-learn: Probability calibration - la definizione di classificatore ben calibrato e
CalibratedClassifierCV, consultata il 2026-11-12
I listati Python sono stati eseguiti due volte, con seme fisso, con Python 3.14.4, NumPy 2.3.5 e scikit-learn 1.8.0: le uscite riportate coincidono. I costi degli esempi (100 e 5 per il churn, 150 e 1350 per la rete idrica) e i dati sono simulati e inventati per illustrare il metodo; sostituiscili con i tuoi.
Commenti
Caricamento commenti...
Accedi per commentare