narada-env / README.md
KrishVenky's picture
update README.md
b90b33c verified
|
Raw
History Blame Contribute Delete
13.4 kB
---
title: Narada Env
emoji: 🧬
colorFrom: blue
colorTo: green
sdk: docker
app_port: 7860
pinned: false
---
# Narada β€” Rare Disease Diagnosis RL Environment
**Meta Γ— PyTorch OpenEnv Hackathon Γ— Scaler School of Technology β€” Grand Finale**
**Live environment:** [krishvenky-narada-env.hf.space](https://huggingface.co/spaces/KrishVenky/narada-env) Β· **Blog:** [Blog.md](https://huggingface.co/spaces/KrishVenky/narada-env/blob/main/Blog.md) Β· **Colab training notebook:** [Open in Colab](https://colab.research.google.com/drive/15tPrE95ASXcBA2zKImWgmRoFM0OozPQm?usp=sharing)
---
## Results
GRPO training on Qwen3-1.7B (LoRA rank 16, 200 steps curriculum) vs zero-shot baseline:
| Task | Baseline | After GRPO | Gain |
|---|---|---|---|
| monogenic | 0.4955 | **0.572** | +15.4% |
| oligogenic | 0.4955 | **0.561** | +13.2% |
| phenotype_mismatch | 0.4955 | **0.552** | +11.4% |
| **Average** | 0.4955 | **0.562** | **+13.3%** |
![Before/After GRPO](results/before_after.png)
*Zero-shot baseline vs. trained agent across all three task tiers*
![Reward Curve](results/reward_curve.png)
*Mean reward across 200 training steps (curriculum order). Shaded region = reward_std. Dotted line = zero-shot baseline.*
![Loss Curve](results/loss_curve.png)
*Policy loss across curriculum. Phase boundaries shown as dashed vertical lines.*
![Reward Std](results/reward_std.png)
*reward_std > 0 throughout confirms GRPO received real gradient signal β€” model never collapsed to uniform reward.*
---
## The Problem
Most rare disease patients wait **4–7 years** for a correct diagnosis ([Lancet 2024](https://www.thelancet.com/journals/langlo/article/PIIS2214-109X(24)00056-1/fulltext), [Nature 2024](https://www.nature.com/articles/s41431-024-01604-z)). The delay is systemic, not a simple workflow gap:
- Rare diseases are **clinically heterogeneous** β€” many present with non-specific symptoms common across hundreds of conditions ([PMC 2024](https://pmc.ncbi.nlm.nih.gov/articles/PMC11323401/))
- **60% of patients are initially misdiagnosed** with either a different physical illness or a psychological condition; patients consult 8+ specialists (EU) or 17+ specialists (US) before a correct diagnosis ([EURORDIS survey](https://www.eurordis.org/survey-reveals-lengthy-diagnostic-delays/))
- **"Medical ping-pong"** β€” patients are passed between specialists with poor cross-service communication ([NHS Genomics Education](https://www.genomicseducation.hee.nhs.uk/genotes/knowledge-hub/the-diagnostic-odyssey-in-rare-disease/))
- **95% of rare diseases have no approved treatment**, and poor clinician/patient awareness of symptom profiles compounds the problem ([Lancet 2024](https://www.thelancet.com/journals/langlo/article/PIIS2214-109X(24)00056-1/fulltext))
ClinVar contains 2M+ catalogued genetic variants; HPO maps 15,000+ diseases to phenotypic signatures. The bottleneck is **reasoning under uncertainty** β€” cross-referencing patient symptoms against thousands of candidate variants while resisting high-salience but causally irrelevant signals.
This is exactly where current LLMs fail: they follow pathogenicity scores, not causal chains.
**Narada's scope:** The full diagnostic odyssey involves referral pathways, specialist availability, and fragmented care across institutions. Narada targets the **variant prioritization substage** β€” given a patient's phenotype profile and a shortlist of candidate genomic variants, which variant is causally responsible? This substage has a ground-truth signal (ClinVar), a tractable action space (graph traversal), and measurable improvement via RL β€” making it ideal for methodology proof-of-concept work.
---
## What Narada Is
A **reinforcement learning environment** where an LLM agent navigates a 55,000-node gene-disease knowledge graph built from real ClinVar and HPO data. The agent must diagnose a rare disease patient by reasoning through phenotype β†’ disease β†’ gene β†’ variant chains.
**Three task tiers, increasing difficulty:**
| Task | Description | Key Challenge |
|---|---|---|
| `monogenic` | Single causal gene, 3–4 phenotypes | Basic directional reasoning |
| `oligogenic` | 2 causal genes (one variant each), 5–7 phenotypes | Multi-objective tracking across long trajectory |
| `phenotype_mismatch` | Cardiac patient + BRCA1 frameshift decoy | Causal discipline β€” resist the highest-salience wrong signal |
**Three-agent system:**
- **Detective (Qwen3-1.7B, trainable)** β€” navigates the graph, flags the causal variant
- **Overseer** β€” local heuristic (no LLM) that scores trajectory quality: penalises hallucinated hops, rewards touching the causal gene, and scales with a concise trail. Added only to *successful* terminal rewards.
- **Adversary** *(planned)* β€” curriculum case generation targeting Detective failure patterns; reliable adversarial curriculum from agent error logs is an open research problem, deferred to future work
---
## Proof-of-Concept Framing
This project uses Qwen3-1.7B as the Detective agent. At this scale, the honest goal is **methodology proof-of-concept**: can GRPO training on a verifiable graph-navigation task move the needle even on a constrained model? Measurable improvement from a low baseline is a legitimate research contribution.
The environment and training pipeline are designed to generalize to larger models β€” the same loop applies to Qwen2.5-72B with zero code changes.
---
## Graph
Built at runtime from:
- `data/hp.obo` β€” 19,389 HPO phenotype terms
- `data/clinvar_pathogenic.tsv` β€” 92,000 high-confidence pathogenic variants (GRCh38, criteria provided/expert panel, deduplicated)
**Graph stats:**
- 55,000+ nodes (phenotype, disease, gene, variant, pathway)
- 70,000+ edge pairs
- 3,268 genes represented
---
## Action Space
| Action | Effect | Reward |
|---|---|---|
| `hop(node_id)` | Move to connected node | +0.15 relevant / βˆ’0.05 irrelevant |
| `flag_causal(variant_id)` | Declare diagnosis; oligogenic cases allow multiple flags | +1.0 correct terminal, βˆ’0.5 wrong |
| `backtrack()` | Return to previous node | +0.05 after wrong direction |
| `request_lab(test)` | Get additional phenotype data | βˆ’0.10 penalty |
| `summarise_trail()` | Compressed visit summary | 0.0 (neutral) |
Signed raw rewards are mapped into OpenEnv's required score interval `(0.01, 0.99)`. This preserves penalties while keeping the validator-compatible output range.
---
## Running Locally
```bash
# 1. Clone
git clone https://github.com/KrishVenky/Narada-Env.git
cd Narada-Env
# 2. Create virtualenv (Python 3.10+)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3. Install
pip install -r requirements.txt
# 4. Set up environment variables
cp .env.example .env
# Edit .env and fill in your API keys
# 5. Generate filtered ClinVar (one-time, ~3 min)
python scripts/filter_clinvar.py
# 6. Start server
PYTHONPATH=src/envs uvicorn narada.server.app:app --port 7860
# 7. Open browser β†’ http://localhost:7860
```
---
## Contributing
Pull requests welcome. To add a new task tier, reward component, or agent variant:
1. Fork the repo and create a branch off `main`
2. Environment logic lives in [src/envs/narada/](src/envs/narada/) β€” `environment.py` for reward design, `case_generator.py` for case sampling, `graph.py` for the knowledge graph
3. Add your changes and run the OpenEnv validator before opening a PR:
```bash
openenv validate http://localhost:7860
```
4. Update `ARCHITECTURE.md` if you change reward values, node types, or the session protocol
**Key files:**
| File | Purpose |
|---|---|
| `src/envs/narada/server/environment.py` | Core RL loop, reward computation |
| `src/envs/narada/server/app.py` | FastAPI entry point (WebSocket + HTTP debug) |
| `src/envs/narada/graph.py` | Knowledge graph build + singleton |
| `src/envs/narada/case_generator.py` | Patient case generation per tier |
| `src/envs/narada/models.py` | Pydantic schemas (action/observation/state) |
| `src/envs/narada/client.py` | Python WebSocket client for agents |
| `training/narada_grpo.ipynb` | GRPO training notebook (Colab) |
| `inference.py` | Benchmark script (Groq or HF backend) |
---
## OpenEnv Validation
```bash
openenv validate https://krishvenky-narada-env.hf.space
```
All scores strictly in `(0.01, 0.99)`. `[END]` line always includes `score=` field.
---
## Baseline Benchmark
Zero-shot evaluation via `inference.py` (Groq backend, no fine-tuning). Two single-episode samples per model show the core problem: zero-shot LLMs are **inconsistent** and frequently collapse into `summarise_trail` loops instead of navigating the graph.
> Note: the exact scores below are reference runs from before reward hardening. Re-run `inference.py` before final submission so the README plots and tables match the current reward mapping.
### llama-3.3-70b-versatile (reference runs)
| Task | Run 1 Score | Run 2 Score | Notes |
|---|---|---|---|
| `monogenic` | **0.990** | 0.433 | Run 1: solved in 4 steps. Run 2: hit summarise_trail loop after step 3 |
| `oligogenic` | 0.500 | 0.240 | Run 1: WS disconnect mid-episode. Run 2: full summarise_trail timeout |
| `phenotype_mismatch` | 0.060 | 0.225 | Run 1: looped on wrong gene 9Γ— before giving up. Run 2: pure timeout |
### Multi-model comparison (zero-shot, all 3 tasks, single run each)
| Model | monogenic | oligogenic | phenotype_mismatch | Behavior |
|---|---|---|---|---|
| `llama-3.3-70b-versatile` | **0.990** | 0.500 | 0.060 | Hops graph; inconsistent |
| `llama-3.1-8b-instant` | 0.310 | 0.425 | 0.310 | Hops but flags wrong variant |
| `mixtral-8x7b-32768` | 0.233 | 0.220 | 0.225 | Full summarise_trail timeout |
| `gemma2-9b-it` | 0.233 | 0.220 | 0.225 | Full summarise_trail timeout |
The pattern is clear: large frontier models (llama-3.3-70b) occasionally navigate the graph correctly but are inconsistent (0.990 vs 0.433 on the same task across runs). Mid-size models (llama-3.1-8b) attempt navigation but misfire on the final flag. Smaller models (mixtral, gemma2) collapse entirely to the `summarise_trail` loop and never issue a `flag_causal`. Fine-tuning on the graph-navigation reward signal is intended to make correct phenotype β†’ gene β†’ variant chaining the default, not a lucky outcome that only large models achieve occasionally.
**Target post-GRPO (Qwen3-1.7B):** consistent flag accuracy > 50% on monogenic, causal path coverage > 60%.
> To switch backends: set `GROQ_API_KEY` for Groq, or `HF_TOKEN` for HF Inference Router. The script auto-detects which to use.
---
## Training
Training notebook: `training/narada_grpo.ipynb` (Colab, Unsloth + HF TRL GRPO)
Base model: `Qwen/Qwen3-1.7B` β€” fits on a free T4 (4-bit quantised, 17.4M trainable LoRA params)
Tasks trained in curriculum order: `monogenic β†’ oligogenic β†’ phenotype_mismatch`
### Architecture
**Multi-step outcome GRPO** β€” the model generates a complete 3–5 action *diagnostic plan* per prompt. The full plan is executed in the environment and the terminal reward (correct flag / wrong flag / timeout) becomes the training signal. This gives 10Γ— more reward variance than single-step GRPO:
| Approach | Typical reward range | reward_std | Learning |
|---|---|---|---|
| 1-step (old) | 0.47–0.56 | ~0.03 | Slow |
| Multi-step plan | 0.28–0.99 | ~0.20–0.35 | Strong |
**Async-parallel reward** β€” all G=8 completions are evaluated concurrently via `asyncio.gather`. Each completion's plan runs as an independent WebSocket session. Total overhead β‰ˆ one episode round-trip, not 8Γ—.
**Curriculum learning** — monogenic cases establish the basic hop→flag behaviour before oligogenic (multi-objective) and phenotype_mismatch (decoy resistance) add harder constraints.
**Milestone reward** β€” environment gives a +0.10 bonus the first time the agent visits the actual causal gene node. Creates a two-stage reward landscape: find the gene (+0.10) β†’ flag the variant (+1.0).
### Key training config
| Param | Value | Why |
|---|---|---|
| `num_generations` | 8 | More completions per prompt β†’ higher reward_std |
| `temperature` | 1.1 | Forces diverse hop targets within a group |
| `max_completion_length` | 800 | Fits 4–5 JSON action blocks |
| `N_SEEDS_PER_TASK` | 40 | 120 total prompts (was 60) β€” more case diversity |
| `LR` | 5e-6 | Conservative to avoid catastrophic forgetting on 1.7B |
---
## Multi-Model Benchmark
Run zero-shot comparison across sub-5B models (all runnable on T4):
```bash
HF_TOKEN=hf_... python benchmark.py --n_seeds 3
```
Models compared: Qwen3-0.6B, Qwen3-1.7B, Qwen3-4B, Llama-3.2-3B, Phi-3.5-mini
Results are saved to `benchmark_results.json` after each run.
---
## Graph Export (Neo4j)
Export the 55K-node knowledge graph to Cypher for Neo4j Aura (free tier) or Desktop:
```bash
PYTHONPATH=src/envs python scripts/export_neo4j.py
# Generates neo4j_nodes.cypher + neo4j_rels.cypher
# Import into Neo4j Browser, then:
# MATCH (n:NaradaNode) RETURN n LIMIT 100
```
The live environment also exposes a subgraph JSON endpoint for D3.js visualization:
```
GET /graph/subgraph?node_id=GENE:BRCA2&depth=2
```