Salta al contenuto principale

04 - Implementare l'IA in azienda

Come valutare un progetto di machine learning prima del codice: scheda a cinque campi, CRISP-DM, soglia ricavata dai costi e scelta tra costruire o comprare.

Cosa ne pensi di questo articolo?

04 - Implementare l'IA in azienda
Machine Learning Engineerfedericocalo.dev

In Breve

Come valutare un progetto di machine learning prima del codice: scheda a cinque campi, CRISP-DM, soglia ricavata dai costi e scelta tra costruire o comprare.

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:

Il ciclo CRISP-DMSei fasi disposte in cerchio con i dati al centro: business, dati, preparazione, modellazione, valutazione, rilascio.Comprensione del businessComprensione dei datiPreparazione dei datiModellazioneValutazioneRilascioi datial centro
Figura: le sei fasi, ripetute in cicli successivi.
  1. Comprensione del business: obiettivi, criteri di successo (la scheda a cinque campi).
  2. Comprensione dei dati: che cosa c'è, qualità, lacune.
  3. Preparazione dei dati: pulizia, costruzione delle variabili.
  4. Modellazione: scelta degli algoritmi, taratura.
  5. Valutazione: il modello risponde all'obiettivo di business, non solo alla metrica tecnica?
  6. 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 perditaTratti
0.02600
0.10200
0.25160
0.6040
  1. Qual è la soglia ottimale? E se la rottura costasse 450 euro invece di 1350? E se ne costasse 2850?
  2. Calcola il costo annuo atteso alla soglia ottimale e alla soglia di default 0.5. Quanto costa il default?
  3. 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.

  1. Calcola il valore netto annuo delle due opzioni, e confrontale con «non fare».
  2. A quale canone mensile il fornitore diventerebbe indifferente? In forma chiusa.
  3. 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:

  1. Cambia C_FN e C_FP in 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.
  2. Aggiungi un costo di esercizio E e calcola il valore_netto rispetto alla migliore tra le due baseline.
  3. Trova il valore di E oltre 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_VP per un vero positivo (ad esempio il costo dell'azione di recupero) e C_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

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

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti