DEV Community

Cover image for How I Built an AI That Tells Doctors "Wait ...Are You Sure?"
Meryeme ramdi
Meryeme ramdi

Posted on

How I Built an AI That Tells Doctors "Wait ...Are You Sure?"

How a student project turned into a lesson about humility, hard negatives, and why the best models know when to shut up.

The number that wouldn't leave me alone

I remember the exact article that ruined my week.

It was a study saying that diagnostic errors affect around 12 million adults in the US every year. Twelve million. That's roughly one in every twenty outpatient visits, and a decent chunk of them cause real harm.

I read it, closed my laptop, and then reopened it ten minutes later because my brain was doing the thing it does the "but what if you could just…" thing.

I'm a student. I'm not going to reform healthcare. But I do know how to train models. And the problem, when you squint at it, looks like a machine learning problem: given a set of symptoms and a proposed diagnosis, does this pairing actually make sense?

So I started building. The project became my final-year adventure. I called it Misdiagnosis Detector, which is a slightly dramatic name for what it actually does: it's a second opinion. A tiny, opinionated little AI that reads a case and says one of three things:

"Yeah, that fits."
"Hmm, maybe double-check."
"No, that doesn't add up."

That third option ; the confident "no" , is the fun part. But getting there was a saga.

Attempt 1: The Naive Version (and why it embarrassed me)

My first instinct was the same instinct every ML student has: throw a classifier at it.

I scraped clinical cases from PubMed. I extracted symptoms and diagnoses. I trained a model to predict "diagnosis given symptoms." Classic supervised learning.

It got ~90% accuracy on my test set.

I was ecstatic for about six minutes, which is roughly how long it took me to realize the model was cheating. It had learned that "chest pain + shortness of breath" almost always co-occurs with "myocardial infarction" in the data because PubMed publishes the interesting cases where the diagnosis was, in fact, an MI. My model wasn't verifying anything. It was pattern-matching on publication bias.

A doctor doesn't need an AI to tell them "chest pain probably means heart attack." A doctor needs an AI to tell them when the symptoms and the diagnosis they wrote down don't actually match.

That's a different problem. And it required a completely different framing.

Attempt 2: Learning what "wrong" looks like

The reframe: instead of predicting the diagnosis, I'd train a model to score coherence — how well any given symptom set matches any given diagnosis.

For that, I needed pairs of symptoms and diagnoses that don't match. Not made-up gibberish — plausible-but-wrong pairings, the kind a tired resident at 3am might actually write down.

This is where I met my new best friend: hard negatives.

Instead of pairing "chest pain" with "sprained ankle" (too obvious, model learns nothing), I mined titles from PubMed to find diagnoses that sound like they could fit the symptoms but don't. Pneumonia paired with heart failure. Migraine paired with meningitis. Anxiety paired with hyperthyroidism. The lookalikes. The gotchas.

The model that came out of this was more interesting, but the metric that mattered wasn't accuracy anymore , it was AUC, because I wanted a score, not a hard classification. I got to around 0.80 on my Phase 2 model.

Not bad. Not good enough to put in front of anyone.

Attempt 3: Standing on the shoulders of giants (aka PubMedBERT)

Here's the thing about building medical NLP as a student: you cannot possibly train a language model that understands medicine from scratch. Not with a laptop. Not with your university's GPU. Not in this lifetime.

But you don't have to. Microsoft trained one for you. It's called PubMedBERT, and it has already read basically every biomedical paper on Earth.

So I stopped trying to be clever with raw text. I ran everything — symptoms, diagnoses, case descriptions through PubMedBERT to get embeddings. Then, on top of those frozen embeddings, I trained a small projection head with a contrastive objective: pull matching (symptoms, diagnosis) pairs close in vector space, push mismatches apart.

Small model. Big shoulders. Suddenly my AUC jumped to ~0.92.

I stared at the number for a while. It felt too good. I retrained it four times to make sure I hadn't leaked test data. I hadn't.

The part where I almost shipped a lie

At 92% AUC, the temptation was to just… ship it. Slap a green/red output on it, call it a demo, put it on my CV.

But here's the thing about medicine that I kept slamming into: a model that's confidently wrong 8% of the time is worse than no model at all. If a doctor trusts your green light and a patient dies, "well, my AUC was 0.92" is not a defense.

So I did the least sexy but most important thing in the project: I made the model shut up when it wasn't sure.

I built a three-zone decision system:

Score below 0.15 → "This diagnosis doesn't fit the symptoms." (red)
Score above 0.89 → "This checks out." (green)
Everything in between → "I don't know. Verify manually." (yellow)

The middle zone is the whole point. It's the model saying "I refuse to bet on this." Around 36% of cases fall into it, which sounds bad until you realize the model's accuracy on the cases it does commit to is 81%.

I would rather build an AI that admits ignorance on a third of cases than one that fakes confidence on all of them. This is the lesson I want tattooed on every ML student's forehead: coverage is not the same as competence.

Making it useful, not just correct

A number on a screen isn't a product. So I built a Streamlit app around it, and added two things I'd want if I were the doctor using it:

  1. A differential diagnosis. When the model rejects the proposed diagnosis, it doesn't just say "no" , it suggests the diagnoses that would fit the symptoms better, ranked by coherence score. Because "you're wrong" is unhelpful. "You're wrong, and here are three things it might actually be" is a second opinion.

  2. Similar cases from PubMed. For every prediction, the app shows real published case reports with similar symptom profiles, retrieved via a semantic search over MiniLM embeddings. Because a doctor should never trust the AI blindly — they should trust the AI plus the evidence the AI is pointing at.

I also spent a stupid amount of time on vocabulary mapping. Doctors write "common cold." The model was trained on "URTI" (upper respiratory tract infection). Doctors write "heart attack." The training data says "STEMI" or "NSTEMI." I built a small clinical synonym mapper so the model doesn't get thrown by the fact that real people use real words.

What I actually learned (the parts that don't fit on a CV)

If you scroll to the top of my repo, you'll see a README with metrics and architecture diagrams. What you won't see is the stuff I actually take away from this project:

Models that abstain are better than models that guess. The most valuable thing I built wasn't the classifier , it was the yellow zone. Knowing when not to answer is a feature, not a bug.

Publication bias is a monster. Every dataset lies to you in some way. PubMed publishes the interesting cases; Reddit publishes the anxious ones; NEJM publishes the impossible ones. You have to know your data's personality before you can trust what your model learned from it.

"92% AUC" is a beginning, not an ending. It's the moment you start asking harder questions: 92% on which subgroup? At what threshold? With what failure modes? A model that looks great in aggregate can be catastrophic in the tails.

Frozen giants + a tiny learned head beats an ambitious student with a from-scratch model, every single time. Use PubMedBERT. Use SapBERT. Use whatever's out there. Your originality goes into the problem framing, not the architecture.

Boring engineering matters. Half my final commits were about handling common cold vs URTI, calibrating thresholds, and making the Streamlit UI not look terrible. None of that goes on the model card. All of it decides whether the tool is usable.

The version I actually want to build next

If I keep going with this — and I probably will — the next version needs three things:

Uncertainty that a doctor can act on. Right now the middle zone just says "verify." I want it to say why it's unsure. "The symptoms overlap with three other diagnoses" is more useful than "score = 0.42."

More languages. All my training data is in English. Medicine happens everywhere. This is a fixable problem and a real one.

A humbler evaluation. My 5-out-of-8 score on reference clinical cases is fine for a demo. It is not fine for anything real. I want to build a proper eval set with medical students grading outputs, and iterate against that.

Why I wrote this down

If you're a student staring at some overwhelming problem and thinking "I can't fix healthcare / climate / education / whatever" — you're right. You can't. Neither can I.

But you can pick one small, well-defined corner of the problem and build a thing that would make one specific person's life 3% easier. That is enough. That is, honestly, more than enough.

Twelve million diagnostic errors a year is not a number you fix with a Streamlit app. But it might be a number you chip at with tools that whisper "wait, are you sure?" at exactly the moment someone needed to hear it.

I built one of those tools. It's imperfect and it's mine and I learned more from it than from any course I've ever taken.

If you want to see it, the code is on GitHub and the live demo is on Hugging Face Spaces. If you want to build something similar and get stuck, my inbox is open.

Now go find your twelve-million-shaped problem.

Btw, this is my first time writing about my work publicly, but it definitely won't be the last. I built this project to learn, but I am always looking for the next thing to build.
I’m an AI student seeking an internship for next year, and I am eager to collaborate on new projects and tackle completely different datasets. My inbox is open for new ideas, feedback, or internship opportunities. Drop me a line at meryemeramdi05@gmail.com, and let's connect on LinkedIn : https://www.linkedin.com/in/meryeme-ramdi-5a879b388/

Top comments (1)

Collapse
 
rimyy profile image
Rim

Trully impressed