acebirim commited on
Commit
7d58df2
·
verified ·
1 Parent(s): eb4ea48

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +23 -7
  2. app.py +151 -4
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,13 +1,29 @@
1
  ---
2
- title: SNOMED NER
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.16.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Snomed NER
3
+ emoji: 🏥
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: false
9
+ license: apache-2.0
10
  ---
11
 
12
+ # SNOMED NER — Clinical Disease Entity Extractor
13
+
14
+ A Named Entity Recognition model for identifying disease entities in clinical free-text,
15
+ fine-tuned on the MedMentions corpus using Bio_ClinicalBERT.
16
+
17
+ ## Model
18
+
19
+ - **Hub**: [`acebirim/snomed-ner-model`](https://huggingface.co/acebirim/snomed-ner-model)
20
+ - **Base model**: `emilyalsentzer/Bio_ClinicalBERT`
21
+ - **Task**: Token classification (NER) — `O`, `B-DISEASE`, `I-DISEASE`
22
+ - **Training data**: MedMentions (132K PubMed abstracts with UMLS annotations)
23
+
24
+ ## How it works
25
+
26
+ Paste any clinical free-text into the form. The model identifies disease entity spans
27
+ using BIO tagging and highlights them inline. Results are also shown as a summary table.
28
+
29
+ *Built as part of an NHS/OneLondon Secure Data Environment capstone project.*
app.py CHANGED
@@ -1,7 +1,154 @@
1
  import gradio as gr
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
 
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import torch
3
+ from transformers import AutoTokenizer, AutoModelForTokenClassification
4
+ import warnings
5
+ warnings.filterwarnings("ignore")
6
 
7
+ MODEL_ID = "acebirim/snomed-ner-model"
8
+ LABEL_LIST = ["O", "B-DISEASE", "I-DISEASE"]
9
+ ID2LABEL = {i: label for i, label in enumerate(LABEL_LIST)}
10
 
11
+ print(f"Loading model from {MODEL_ID}...")
12
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
13
+ model = AutoModelForTokenClassification.from_pretrained(MODEL_ID)
14
+ model.eval()
15
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
16
+ model.to(device)
17
+ print(f"Model loaded on {device}")
18
+
19
+ STOPWORD_BLOCKLIST = {
20
+ 'patient', 'subject', 'presents', 'with', 'of', 'the', 'a', 'an', 'and',
21
+ 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'from', 'by', 'as', 'was',
22
+ 'were', 'is', 'are', 'been', 'being', 'have', 'has', 'had', 'do', 'does',
23
+ 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'must', 'can'
24
+ }
25
+
26
+ def predict(text: str):
27
+ if not text.strip():
28
+ return [], "No text provided."
29
+
30
+ tokens = text.split()
31
+ tokenized = tokenizer(
32
+ tokens,
33
+ is_split_into_words=True,
34
+ truncation=True,
35
+ padding="max_length",
36
+ max_length=128,
37
+ return_tensors="pt"
38
+ )
39
+
40
+ with torch.no_grad():
41
+ outputs = model(**{k: v.to(device) for k, v in tokenized.items()})
42
+ predictions = outputs.logits.argmax(dim=-1)[0]
43
+
44
+ word_ids = tokenized.word_ids()
45
+ predicted_labels = []
46
+ previous_word_idx = None
47
+ for word_idx, pred_id in zip(word_ids, predictions):
48
+ if word_idx is None:
49
+ continue
50
+ if word_idx != previous_word_idx:
51
+ predicted_labels.append(ID2LABEL[pred_id.item()])
52
+ previous_word_idx = word_idx
53
+
54
+ entities = []
55
+ current_entity_tokens = []
56
+ current_label = None
57
+
58
+ for token, label in zip(tokens, predicted_labels):
59
+ if label.startswith("B-"):
60
+ if current_entity_tokens:
61
+ entity_text = " ".join(current_entity_tokens)
62
+ if entity_text.lower() not in STOPWORD_BLOCKLIST:
63
+ entities.append({"text": entity_text, "label": current_label})
64
+ current_entity_tokens = [token]
65
+ current_label = label.replace("B-", "")
66
+ elif label.startswith("I-") and current_entity_tokens:
67
+ current_entity_tokens.append(token)
68
+ else:
69
+ if current_entity_tokens:
70
+ entity_text = " ".join(current_entity_tokens)
71
+ if entity_text.lower() not in STOPWORD_BLOCKLIST:
72
+ entities.append({"text": entity_text, "label": current_label})
73
+ current_entity_tokens = []
74
+ current_label = None
75
+
76
+ if current_entity_tokens:
77
+ entity_text = " ".join(current_entity_tokens)
78
+ if entity_text.lower() not in STOPWORD_BLOCKLIST:
79
+ entities.append({"text": entity_text, "label": current_label})
80
+
81
+ # Highlighted text
82
+ entity_set = {e["text"]: e["label"] for e in entities}
83
+ highlighted = []
84
+ i = 0
85
+ while i < len(tokens):
86
+ matched = False
87
+ for length in range(min(10, len(tokens) - i), 0, -1):
88
+ span = " ".join(tokens[i:i+length])
89
+ if span in entity_set:
90
+ highlighted.append((span, entity_set[span]))
91
+ i += length
92
+ matched = True
93
+ break
94
+ if not matched:
95
+ highlighted.append((tokens[i] + " ", None))
96
+ i += 1
97
+
98
+ # Markdown table
99
+ if entities:
100
+ md = "| Entity | Type |\n|--------|------|\n"
101
+ for e in entities:
102
+ md += f"| {e['text']} | {e['label']} |\n"
103
+ else:
104
+ md = "No disease entities detected."
105
+
106
+ return highlighted, md
107
+
108
+
109
+ EXAMPLES = [
110
+ ["Patient presents with hypertension and type 2 diabetes mellitus."],
111
+ ["History includes breast cancer treated with chemotherapy."],
112
+ ["Subject presents with symptoms of asthma and chronic obstructive pulmonary disease."],
113
+ ["The patient was diagnosed with pneumonia and required hospitalization."],
114
+ ["Patient suffers from depression and anxiety disorder following myocardial infarction."],
115
+ ]
116
+
117
+ with gr.Blocks(title="Clinical Disease Entity Extractor") as demo:
118
+
119
+ gr.Markdown("""
120
+ # 🏥 Clinical Disease Entity Extractor
121
+ **Model**: [`acebirim/snomed-ner-model`](https://huggingface.co/acebirim/snomed-ner-model)
122
+ &nbsp;|&nbsp; **Base**: Bio_ClinicalBERT &nbsp;|&nbsp; **Trained on**: MedMentions (132K examples)
123
+
124
+ Paste any clinical free-text below. The model will identify **disease entities** within it.
125
+ """)
126
+
127
+ with gr.Row():
128
+ with gr.Column(scale=1):
129
+ text_input = gr.Textbox(
130
+ label="Clinical Text",
131
+ placeholder="e.g. Patient presents with hypertension and type 2 diabetes mellitus.",
132
+ lines=6,
133
+ )
134
+ run_btn = gr.Button("Extract Entities", variant="primary")
135
+ gr.Examples(examples=EXAMPLES, inputs=text_input, label="Try an example")
136
+
137
+ with gr.Column(scale=1):
138
+ highlighted_output = gr.HighlightedText(
139
+ label="Highlighted Entities",
140
+ combine_adjacent=True,
141
+ show_legend=True,
142
+ )
143
+ markdown_output = gr.Markdown()
144
+
145
+ run_btn.click(fn=predict, inputs=text_input, outputs=[highlighted_output, markdown_output])
146
+ text_input.submit(fn=predict, inputs=text_input, outputs=[highlighted_output, markdown_output])
147
+
148
+ gr.Markdown("""
149
+ ---
150
+ Fine-tuned on [MedMentions](https://github.com/chanzuckerberg/MedMentions) using `emilyalsentzer/Bio_ClinicalBERT`.
151
+ *Built as part of an NHS/OneLondon Secure Data Environment capstone project.*
152
+ """)
153
+
154
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ torch>=2.0.0
2
+ transformers>=4.35.0