Spaces:
Running
Running
File size: 9,098 Bytes
79b0bef 184d5a3 79b0bef 184d5a3 79b0bef 184d5a3 79b0bef 184d5a3 79b0bef 184d5a3 79b0bef 184d5a3 79b0bef 184d5a3 79b0bef | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | ---
title: Clinical Nlp Api
emoji: 🌍
colorFrom: green
colorTo: pink
sdk: docker
pinned: false
---
# Clinical NLP Pipeline
**NLP · Named Entity Recognition · Clinical Text Mining · BERT Fine-tuning**
A production-grade pipeline that extracts structured clinical knowledge
from unstructured medical notes using state-of-the-art biomedical NLP models.
[](https://github.com/ayodeji07/clinical-nlp-pipeline/actions)
---
## What it does
| Component | What it does |
|-----------|-------------|
| **NER** | Extracts diagnoses, medications, procedures, symptoms, and anatomical terms using scispaCy (`en_core_sci_lg`) |
| **ICD-10 mapping** | Maps extracted entities to ICD-10-CM codes via exact → fuzzy → embedding matching |
| **Severity classifier** | Fine-tunes Bio_ClinicalBERT to classify notes as `routine`, `urgent`, or `critical` |
| **Co-occurrence graph** | Builds an interactive network of entity pairs that appear together in clinical notes |
| **FastAPI** | REST API serving all NLP functionality |
| **Streamlit demo** | Live public demo — paste any clinical note, get results in real time |
---
## Architecture
```mermaid
flowchart LR
subgraph ETL["ETL (src/etl)"]
Extract[extract.py] --> Transform[transform.py] --> Load[load.py]
end
MTSamples[(MTSamples CSV)] --> Extract
Load --> DB[(PostgreSQL<br/>Supabase)]
subgraph NLP["NLP (src/nlp)"]
NER[ner.py<br/>scispaCy hybrid NER]
ICD[icd_mapper.py<br/>exact → fuzzy → embedding]
Classifier[classifier.py<br/>Bio_ClinicalBERT fine-tune]
Cooc[cooccurrence.py<br/>entity co-occurrence graph]
end
DB <--> NER
DB <--> ICD
DB <--> Classifier
DB <--> Cooc
subgraph API["FastAPI (src/api)"]
Routes["routes: notes, entities, icd, model"]
end
NLP <--> Routes
DB <--> Routes
subgraph Dashboard["Streamlit (dashboard/)"]
Demo[demo.py]
Explorer[explorer.py]
Metrics[model_metrics.py]
end
Routes -- HTTP --> Dashboard
Routes -.deployed on.-> HFSpace["Hugging Face Spaces<br/>(Docker)"]
Dashboard -.deployed on.-> StreamlitCloud["Streamlit Cloud"]
Classifier -.checkpoint hosted on.-> HFHub["Hugging Face Hub<br/>model repo"]
```
Batch scripts (`scripts/run_ner_batch.py`, `run_icd10_batch.py`,
`train_severity_classifier.py`) populate the NLP layer against notes
already sitting in the database — see [Batch processing](#batch-processing)
below.
---
## Screenshots
| Live Demo | Model Metrics |
|---|---|
|  |  |
**Co-occurrence network** — entities that appear together across the corpus, node size scaled by degree:

**Entity frequency** — most common extracted entities across the dataset:

---
## Live demo
🔗 [clinical-nlp-pipeline.streamlit.app](https://clinical-nlp-pipeline.streamlit.app)
Want to deploy your own copy instead? See VSCODE_GUIDE.md.
---
## Quick start
```bash
git clone https://github.com/ayodeji07/clinical-nlp-pipeline
cd clinical-nlp-pipeline
python -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
# Install the scispaCy NER model
pip install https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.5.3/en_core_sci_lg-0.5.3.tar.gz
# Download MTSamples from Kaggle → data/raw/mtsamples.csv
# Then run the pipeline
python -m src.etl.pipeline --dry-run
uvicorn src.api.main:app --reload &
streamlit run dashboard/app.py
```
Full step-by-step instructions: **[VSCODE_GUIDE.md](VSCODE_GUIDE.md)**
---
## Batch processing
Once the ETL pipeline has loaded notes into the database, these scripts
populate entities, ICD-10 mappings, and the severity classifier against
stored data. All three are idempotent and resumable — safe to interrupt
and re-run without creating duplicates:
```bash
python scripts/run_ner_batch.py # extract entities from stored notes
python scripts/run_icd10_batch.py # map DISEASE/SYMPTOM entities to ICD-10 codes
python scripts/train_severity_classifier.py --n-seeds 4 # fine-tune the severity classifier
```
Each accepts `--limit N` for a quick subset run. `run_icd10_batch.py`
also maintains an on-disk cache (`data/processed/icd10_mapping_cache.json`)
so a restart skips re-attempting entities already checked, matched or not.
`train_severity_classifier.py --n-seeds N` trains N different random
seeds and keeps only the one with the best critical-class F1 — small
fine-tuning runs are sensitive to initialisation, so a single unseeded
run isn't reliably comparable across retrains (default is 1, i.e. a
single run, if omitted).
---
## Tech stack
| Layer | Library |
|-------|---------|
| NER | spaCy + scispaCy `en_core_sci_lg` |
| Classification | HuggingFace Transformers + `Bio_ClinicalBERT` |
| ICD-10 fuzzy | rapidfuzz |
| ICD-10 embeddings | sentence-transformers |
| API | FastAPI + Pydantic |
| Database | SQLAlchemy (SQLite local / PostgreSQL cloud) |
| Dashboard | Streamlit |
| Visualisation | Plotly + NetworkX + pyvis |
| Testing | pytest (65 tests) |
| CI/CD | GitHub Actions |
---
## Project structure
```
src/
utils/ config, logger, text cleaning utilities
etl/ extract → transform → load pipeline
nlp/ ner, icd_mapper, classifier, cooccurrence
db/ connection, ORM models, repository layer
api/ FastAPI app, Pydantic schemas, route handlers
dashboard/
app.py Streamlit entry point
api_client.py typed HTTP client for the API
pages/ demo, explorer, model_metrics
notebooks/
00_data_exploration.ipynb
01_ner_walkthrough.ipynb
02_icd_mapping.ipynb
03_classification.ipynb ← fine-tune Bio_ClinicalBERT
04_visualisation.ipynb
tests/ pytest test suite — 65 tests, 0 dependencies on GPU
sql/ schema.sql for Supabase migration
```
---
## Dataset
**MTSamples** — 4,999 de-identified medical transcriptions across 40 specialties.
Download free from [Kaggle](https://www.kaggle.com/datasets/tboyle10/medicaltranscriptions).
MIMIC-III discharge summaries are optionally supported (requires PhysioNet credentialing).
---
## Severity labels
MTSamples has no severity labels, so we derive them using weak supervision:
| Label | Signal |
|-------|--------|
| `critical` | ICU, ventilator, cardiac arrest, stroke, respiratory failure |
| `urgent` | Emergency, acute, admitted, infection, chest pain, unstable |
| `routine` | Elective, outpatient, follow-up, stable, screening |
The classifier learns to generalise beyond these keyword rules.
Current performance (Bio_ClinicalBERT, class-weighted loss, retrained
2026-07-16, best of 4 random seeds selected by critical-class F1):
~72% overall test accuracy/F1. Per-class breakdown on `critical` — the
safety-relevant class — is what matters most here: **precision 0.604,
recall 0.829, F1 0.699** (up from an unweighted-loss baseline of recall
0.486, F1 0.515). The loss is deliberately weighted to favour catching
critical cases over aggregate accuracy, since missing a true critical
note is far costlier than a false positive.
Fine-tuning a 3-class head on top of a pretrained model with a small
dataset is sensitive to random initialisation — an unseeded single run
can land anywhere from critical F1 0.54 to 0.70. `train_severity_classifier.py`
trains several seeds and keeps the best rather than trusting one
arbitrary run; see `--n-seeds` in Batch processing above. Full metrics (including a
confusion matrix and per-epoch history) are recorded in the
`model_runs` table and served at `GET /model/metrics` — the dashboard's
Model Metrics page reads from there, not a local file.
---
## Deployment
See **[VSCODE_GUIDE.md](VSCODE_GUIDE.md)** — Phase 8 covers:
- Supabase (free PostgreSQL for the database — use the *pooler*
connection string, not the direct one; the direct host is IPv6-only
and unreachable from several free-tier hosts)
- Hugging Face Spaces (free API hosting — has enough RAM for the full
hybrid NER + classifier pipeline; Railway/Render are lighter-weight
alternatives but Render's free tier specifically doesn't have enough
memory for this project's models)
- Streamlit Cloud (free dashboard hosting)
Total cloud cost for a portfolio demo: **£0/month**.
---
## Running tests
```bash
pytest # all 65 tests
pytest --cov=src # with coverage
pytest tests/test_ner.py -v # one file
```
---
## Author
Built by Ayodeji as part of a HealthTech Data Engineering portfolio.
|