Costruendo TrialProof, ho smesso di inseguire l'accuratezza e ho iniziato a contare i pazienti idonei che non ho perso: 0 vs 3 su un gold set fisso di 13 casi.
Clinical TrialsMachine LearningHealthcare

Ho costruito una demo per riprodurre un famoso errore di IA. La mia baseline ha rifiutato di farlo.

Ashutosh SinghalAshutosh Singhal29 giugno 202612 min

L'errore che non riuscivo a far accadere

Ho iniziato questo build volendo ricreare un fallimento specifico e ben documentato. L'IA di matching dei pazienti legge una nota clinica come testo, quindi confonde parole che sembrano simili ma in medicina significano cose diverse. L'esempio canonico è limpido: uno studio di Fase III su anticoagulanti esclude i pazienti con un precedente cateterismo cardiaco, la nota di un paziente dice posizionamento di catetere venoso centrale, un matcher di similarità vede due procedure di cateterismo cardiovascolare, le valuta vicine e esclude un paziente che in realtà era idoneo. Valutazioni pubblicate confermano che modelli reali commettono proprio questo errore (Fierce Biotech, 2025). Volevo che la mia demo lo mostrasse in azione, e poi mostrasse il mio motore che lo intercetta.

Così ho scritto una baseline equa perché facesse da cattivo. Similarità coseno TF-IDF a livello di entità, n-grammi di parola e di carattere da 3 a 5, un vero metodo di similarità vettoriale. Gli ho perfino dato un setup generoso e ho validato in cross-validation la soglia di decisione a suo favore (ROC a 3-fold stratificata, Youden's J, seed 13, con clamping a t = 0.6932), perché un uomo di paglia non prova niente. Poi ho eseguito il caso del cateterismo cardiaco e ho aspettato l'esclusione ingiusta.

Non è arrivata. La baseline ha valutato le due frasi sul catetere ben al di sotto della propria soglia validata in cross-validation e ha restituito idoneo. L'errore attorno a cui avevo costruito l'intera demo semplicemente non si riproduceva.

Mi ero proposto di mettere in scena un fallimento famoso e ho scoperto che il mio cattivo onesto era troppo debole per commetterlo.

Il motivo si è rivelato istruttivo, e voglio essere preciso perché è facile esagerare. Una baseline lessicale sparsa non produce quella particolare falsa esclusione. Servono embedding semantici densi per avvicinare abbastanza quelle due frasi da far scattare la soglia. Aggiungere un modello di embedding pesante avrebbe gonfiato la demo in qualcosa che non puoi eseguire con un solo comando offline, quindi ho preso una decisione: tenere la baseline onesta e sparsa, e smettere di fingere che commetta un crimine che non può commettere. Quella scelta ha riorganizzato l'intero pezzo che sto costruendo. Se vuoi eseguirlo tu stesso, vive su veriprajna.com/it/demos/motore-di-ragionamento-per-l-eleggibilita-agli-studi-clinici.

Cos'è davvero una linea centrale?

Ho comunque tenuto il caso del cateterismo cardiaco: alla fine ha dimostrato qualcosa di meglio di un errore intercettato. Dimostra perché la risposta del mio motore sia affidabile in assoluto. Entrambi i concetti qui hanno identificatori SNOMED-CT reali e verificabili. Cateterismo venoso centrale è 392230005. Cateterismo cardiaco è 41976001. Puoi incollare entrambi in qualsiasi browser SNOMED pubblico e confermare che stanno su rami diversi della gerarchia. Non c'è alcun percorso is-a dall'uno all'altro. Una linea centrale non è un cateterismo cardiaco, e solo una gerarchia lo sa.

È l'intera tesi in un solo arco di un grafo. Un punteggio di similarità non può rappresentare "is-a." Può rappresentare soltanto "queste stringhe si assomigliano," e assomigliarsi non è lo stesso che significare la stessa cosa. Quando il mio motore valuta l'esclusione "nessun cateterismo cardiaco pregresso," non assegna alcun punteggio. Pone una domanda strutturale: il fatto verificato del paziente è sussunto dal concetto proibito? Cammina sull'ontologia, non trova alcun percorso di sussunzione e restituisce idoneo con una traccia in tre passaggi che nomina entrambi gli ID di concetto e l'arco del grafo che ha controllato.

Traccia di ragionamento TrialProof che mostra Cateterismo venoso centrale 392230005 is-a Cateterismo cardiaco 41976001 valutato come False, ramo diverso della gerarchia, verdetto ELIGIBLE
La traccia EXCL-CARDCATH sul paziente sintetico P-074. Il passo 2 chiede se Cateterismo venoso centrale (392230005) is-a Cateterismo cardiaco (41976001), risponde False (ramo diverso della gerarchia) e restituisce ELIGIBLE. Il pannello della baseline sotto riporta un punteggio di similarità sotto soglia senza provenance e niente di riproducibile.

Quando ho visto per la prima volta quella traccia renderizzarsi, ciò che mi ha colpito non è stato il verdetto. È stata la ricevuta sotto di esso. Il riquadro della baseline sulla stessa schermata mostra un numero di similarità senza provenance e niente di riproducibile. Il riquadro del mio motore nomina i due SCTID e l'esatta domanda is-a che ha posto. Una di queste un regolatore può depositarla. L'altra è un numero con un'alzata di spalle attaccata. Quel contrasto, non un errore intercettato, è ciò che il caso del cateterismo cardiaco guadagna davvero.

Una di queste può essere depositata da un regolatore. L'altra è un numero con un'alzata di spalle attaccata.

Avevo ancora bisogno di un paziente perso per davvero, così sono andato a cercare dove la mia baseline onesta fallisce davvero, e l'ho trovato in una sola parola: non. La cartella sintetica protagonista P-074 riporta la riga di nota "No evidence of diabetes." Una delle esclusioni del protocollo oncologico è "nessuna diagnosi di diabete mellito." La baseline vettoriale vede il token "diabetes" seduto proprio accanto al "diabetes" del criterio e li abbina a similarità 1.0. Un punteggio perfetto. Non ha alcun modello della negazione, quindi legge una frase che mette il diabete fuori come se lo avesse messo dentro, e esclude un paziente che era idoneo.

Questo è il paziente che il matcher scarta, ed è la scena che mi aspettavo originariamente dal caso del cateterismo cardiaco. La negazione è dove una baseline sparsa si rompe onestamente, alla sua stessa migliore soglia, senza trucchi.

Traccia TrialProof EXCL-DM: la baseline abbina diabetes a diabetes a similarità 1.0 e restituisce EXCLUDED, mentre il verificatore elimina la menzione negata e il motore restituisce ELIGIBLE
La decisione EXCL-DM su P-074. La migliore menzione della baseline abbina "Diabetes mellitus" a "Diabetes mellitus" a similarità 1.0 e restituisce EXCLUDED. Il verificatore di TrialProof elimina la menzione negata, quindi nessun fatto verificato è sussunto dal concetto proibito, e il verdetto è ELIGIBLE.

Continuo a pensare a quanto sia silenzioso questo fallimento. Non c'è alcun messaggio di errore, alcun flag di bassa confidenza, alcun segnale che qualcosa sia andato storto. Il punteggio è 1.0, il più alto possibile, il massimo di certezza che il sistema possa mai avere. La baseline non è mai più certa che nel momento esatto in cui sbaglia di più. Un coordinatore che rivede una coda di questi non ha modo di sapere che questo particolare match perfetto è un paziente che avrebbe dovuto essere arruolato. Moltiplica tutto ciò su un protocollo e capisci perché l'80% dei trial manca le tempistiche di arruolamento (consenso di settore, 2025), e perché ogni fallimento di screening costa in media circa $1,200 (Antidote.me, 2025).

La baseline non è mai stata più fiduciosa che nel momento esatto in cui sbagliava di più. Non è un bug che puoi regolare via. È un errore di categoria.

Perché il verdetto vive fuori dal modello?

La baseline non è mai stata più fiduciosa che nel momento esatto in cui sbagliava di più. Non è un bug che puoi eliminare a colpi di tuning. È un errore di categoria.

Tutto ciò che viene dopo è codice deterministico che posso auditare. Prima che qualsiasi fatto proposto raggiunga una decisione, un verificatore avversariale lo sfida contro la nota letterale con tre controlli: lo span è davvero presente, è negato, e il soggetto è il paziente piuttosto che un familiare. Il fatto "No evidence of diabetes" fallisce il controllo di negazione e non raggiunge mai il motore. Sulla stessa cartella, "Family history of breast cancer" fallisce il controllo del soggetto, perché quella anamnesi appartiene a un familiare e non al paziente, e viene segnalato come rejected con il controllo fallito nominato.

Pannello Verify Facts di TrialProof che mostra ogni fatto proposto sfidato per span presente, negazione e soggetto prima di poter entrare in una decisione
Lo stadio Verify Facts su P-074. Ogni fatto che il modello propone viene sfidato prima di poter entrare in una decisione: span presente, non negato, soggetto del paziente. Qui il fatto sul cateterismo venoso centrale è ACCEPTED, motivo: span presente, non negato, soggetto paziente. I fatti che falliscono un controllo sono segnalati REJECTED con il motivo nominato.

Sull'intero gold set, questo verificatore ha rifiutato 7 istanze di fatto, 3 fatti errati distinti (una menzione di diabete negata, un'attribuzione di carcinoma mammario in anamnesi familiare e un farmaco allucinato piantato senza span di supporto), distribuiti su 4 dei 13 case-run valutati, tutti prima che potessero toccare un verdetto. Quando mi chiedono "come mi fido di ciò che l'agente ha estratto dalle mie note," questo pannello è l'intera risposta. Non ti chiedo di fidarti. Ti mostro cosa ha proposto e cosa è stato scartato e perché.

Poi il verdetto stesso è Python semplice che sta fuori dal framework dell'agente: un motore di logica deontica che valuta divieti, eccezioni temporali e requisiti sull'ontologia e un po' di aritmetica sulle date. Un modello non può sovrascrivere questo gate, perché il modello non è nella stanza quando il gate gira. È anche ciò che rende il motore riproducibile. Quando la logica è codice deterministico su un'ontologia fissa, rieseguire la stessa cartella produce la stessa risposta, byte per byte, ogni singola volta.

Il modello legge. Non vota. Quel singolo confine è ciò che rende una riesecuzione byte-identica.

L'unico numero a cui teneva il mio lettore di clinical-ops

Ho passato settimane a ottimizzare metriche che, alla fine ho ammesso a me stesso, non fanno perdere il sonno all'acquirente. L'accuratezza delle decisioni è un numero da classifica. Chi gestisce la fattibilità presso uno sponsor o una CRO non confronta i punteggi da classifica. Guarda una timeline di arruolamento che scivola, e ogni giorno di scivolamento è costoso. Il Tufts CSDD Impact Report (2024) colloca il costo di un ritardo di arruolamento a circa $800K al giorno in vendite di prescrizioni perse, e più alto nelle aree terapeutiche toccate da questa demo: circa $840K al giorno in oncologia e $1.4M al giorno in cardiovascolare. La complessità dei protocolli è salita del 139% nelle procedure di trial dal 2005 (IQVIA, 2026), il che significa più criteri, più clausole e più punti in cui un matcher testuale può sbagliare uno.

Così ho smesso di aprire con l'accuratezza e ho iniziato ad aprire con il numero che davvero mappa su quel dolore: i pazienti idonei che non hai perso. Su un gold set etichettato e fisso di 13 casi tratti da 7 pazienti sintetici su 2 protocolli sintetici, il mio motore perde 0 pazienti idonei. La baseline equa ne perde 3. Stesso set, stessa soglia validata in cross-validation a vantaggio della baseline.

Tile del benchmark TrialProof: accuratezza delle decisioni 100 percento vs 53.8 percento della baseline, pazienti idonei persi 0 vs 3, copertura della traccia auditabile 100 percento vs 0 percento, sul gold set etichettato
Il benchmark sul gold set. Sui 13 casi etichettati, TrialProof ottiene il 100% di accuratezza delle decisioni contro il 53.8% della baseline, perde 0 pazienti idonei dove la baseline ne perde 3, e porta una traccia di ragionamento sul 100% delle decisioni dove la baseline ne porta lo 0%. Rieseguire tutti i 13 casi produce verdetti e tracce byte-identici.

Voglio essere esatto su cosa siano e non siano quei numeri. Sono l'output dell'harness su quel singolo set fisso di 13 casi, non una promessa open-world. Il 100% è "100% su questo gold set," mai "sempre giusto." Non ti dirò che TrialProof non sbaglia mai, perché non ho i dati per dirlo e non crederei a nessuno che lo dicesse. Ciò che posso dire è più stretto e, credo, più utile: su questo set il motore perde zero pazienti idonei, ogni decisione porta una traccia riproducibile, due decisioni si sono astenute in sicurezza con NEEDS-REVIEW quando mancava un lab o un vitale richiesto invece di indovinare, e rieseguire l'intero set è stato byte-identico, 13 su 13. Tutti i pazienti, le note e i protocolli sono fixture sintetiche, nessun record reale da nessuna parte. Puoi guardare ognuna di quelle esecuzioni su veriprajna.com/it/demos/motore-di-ragionamento-per-l-eleggibilita-agli-studi-clinici.

Il numero a cui tengo non è l'accuratezza. Sono i pazienti idonei che non ho scartato. Su questo set, sono zero persi contro i tre della baseline.

C'è anche una forma normativa in tutto questo, e la nominerò con attenzione. La guidance FDA di gennaio 2026 sul Clinical Decision Support è il framework rilevante per un ausilio di matching human-in-the-loop come questo. Ogni decisione emessa dal motore può esportarsi come un record CDISC SDTM IE, una riga per paziente e criterio, con il verdetto, la traccia di ragionamento, gli ID di concetto e l'operazione deontica. Non è un clearance e non ne sto rivendicando uno. È allineamento e direzione. Ma significa che la traccia non è una comodità di debugging. È un artefatto archiviabile, ed esiste per costruzione su ogni decisione piuttosto che come ripensamento.

A cosa continuo a tornare

Continuo a tornare al momento in cui il mio cattivo ha rifiutato di recitare la sua parte, perché ha cambiato la domanda che stavo ponendo. Per tre anni il campo ha chiesto come rendere il modello migliore nel decidere chi è idoneo. Prompt migliori, contesto più ampio, più retrieval, tutto mirato a rendere un sistema probabilistico abbastanza affidabile da decidere sull'arruolamento di un paziente. Ho passato il primo tratto di questo build dentro quella cornice anch'io, cercando di beccare un modello in un errore così da poter aggiustare il modello.

Ciò che alla fine ha fatto click è che era lo strato sbagliato. Un punteggio di similarità non può rappresentare "is-a," non può rappresentare "non," e non può rappresentare "a meno che la terapia non sia stata completata più di dodici mesi prima della randomizzazione." Nessuna quantità di prompting li aggiunge, perché non sono problemi di linguaggio. Sono problemi di logica. Quindi la mossa da senior non è rendere il modello affidabile. È rendere la fiducia non necessaria. Lascia che il modello faccia l'unica cosa in cui è bravo, leggere la prosa e proporre fatti con lo span da cui li ha letti. Poi fai sì che un verificatore scarti ciò che la nota non supporta, e che codice semplice e auditabile su un'ontologia medica calcoli il verdetto.

L'idoneità va calcolata, non predetta. Ciò che non mi aspettavo, entrando, era che il payoff non sarebbe sembrato affatto un benchmark. Sembra una ricevuta. La stessa cartella dà la stessa risposta ogni volta, la risposta nomina l'ID di concetto e l'arco del grafo che l'ha decisa, e il numero su cui un lead di fattibilità perde davvero il sonno va a zero pazienti idonei scartati.

E se preferisci guardarlo piuttosto che sentirmi descriverlo, ecco l'intera cosa in esecuzione da un capo all'altro.

Quindi ecco la domanda che non smetto di rimuginare, e mi piacerebbe davvero sapere come la rispondi tu. Quando la posta in gioco è l'occasione di una persona reale a un trial, dove vuoi che viva la tua fiducia: in un modello a cui devi credere, o in codice che puoi leggere?

Ricerca correlata

Pubblicato anche su

Costruisci la tua IA con fiducia.

Collabora con un team che vanta una profonda esperienza nella creazione della prossima generazione di IA aziendale. Lascia che ti aiutiamo a progettare, sviluppare e implementare una strategia di IA di cui ti puoi fidare.

Veriprajna società di consulenza Deep Tech è specializzata nella creazione di sistemi di IA safety-critical per i settori sanitario, finanziario e regolamentato. Le nostre architetture sono validate rispetto a protocolli consolidati con una documentazione di conformità completa.