Skip to content

Commit 6989978

Browse files
jhnwu3plandesJohn Wu
authored
SDOH Coding Model (#538)
* add sentence level sdoh multi-label classification model and test * revert test mask * sdoh: llama download HF api key; response parse test; fix inf test * doc * just added more details to the docs * readthedocs updates --------- Co-authored-by: Paul Landes <landes@mailc.net> Co-authored-by: John Wu <johnwu3@sunlab-serv-03.cs.illinois.edu>
1 parent b36399c commit 6989978

9 files changed

Lines changed: 1051 additions & 141 deletions

File tree

docs/api/models.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ We implement the following models for supporting multiple healthcare predictive
3131
models/pyhealth.models.TCN
3232
models/pyhealth.models.GAN
3333
models/pyhealth.models.VAE
34+
models/pyhealth.models.SDOH
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pyhealth.models.TransformersModel
2+
===================================
3+
4+
5+
Model for classifying social determininants of health (SDoH)
6+
7+
.. autoclass:: pyhealth.models.sdoh
8+
:members:
9+
:undoc-members:
10+
:show-inheritance:

pixi.lock

Lines changed: 307 additions & 141 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyhealth/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
from .transformer import Transformer, TransformerLayer
2626
from .transformers_model import TransformersModel
2727
from .vae import VAE
28+
from .sdoh import SdohClassifier

pyhealth/models/sdoh.py

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""Social determinants of health (SDoH) classification.
2+
3+
"""
4+
__author__ = 'Paul Landes'
5+
from typing import Dict, Any, Set, ClassVar
6+
from dataclasses import dataclass, field
7+
import re
8+
import torch
9+
import transformers
10+
from transformers import AutoModelForCausalLM, AutoTokenizer
11+
from transformers.pipelines import Pipeline
12+
from peft import PeftModelForCausalLM
13+
from pyhealth.models.base_model import BaseModel
14+
15+
16+
# the prompt and role used to supervised-fine tune the model
17+
_PROMPT: str = """\
18+
Classify sentences for social determinants of health (SDOH).
19+
20+
Definitions SDOHs are given with labels in back ticks:
21+
22+
* `housing`: The status of a patient’s housing is a critical SDOH, known to affect the outcome of treatment.
23+
24+
* `transportation`: This SDOH pertains to a patient’s inability to get to/from their healthcare visits.
25+
26+
* `relationship`: Whether or not a patient is in a partnered relationship is an abundant SDOH in the clinical notes.
27+
28+
* `parent`: This SDOH should be used for descriptions of a patient being a parent to at least one child who is a minor (under the age of 18 years old).
29+
30+
* `employment`: This SDOH pertains to expressions of a patient’s employment status. A sentence should be annotated as an Employment Status SDOH if it expresses if the patient is employed (a paid job), unemployed, retired, or a current student.
31+
32+
* `support`: This SDOH is a sentence describes a patient that is actively receiving care support, such as emotional, health, financial support. This support comes from family and friends but not health care professionals.
33+
34+
* `-`: If no SDOH is found.
35+
36+
Classify sentences for social determinants of health (SDOH) as a list labels in three back ticks. The sentence can be a member of multiple classes so output the labels that are mostly likely to be present.
37+
38+
### Sentence: {sent}
39+
### SDOH labels:"""
40+
41+
42+
@dataclass
43+
class SdohClassifier(BaseModel):
44+
"""This predicts sentence level social determinants of health (SDoH) as a
45+
multi-label classification from clinical text. The model was trained from
46+
the MIMIC-III derived dataset from `Guevara et al. (2024)`_.
47+
48+
**Important**: The :obj:`api_key` needs to be populated if the ``Llama 3.1
49+
8B Instruct`` (or the setting of :obj:`base_model_id`) has not yet been
50+
downloaded.
51+
52+
53+
Example::
54+
>>> from pyhealth.models import SdohClassifier
55+
>>> sdoh = SdohClassifier()
56+
>>> sent = 'Pt is homeless and has no car and has no parents or support'
57+
>>> print(sdoh.predict(sent))
58+
>>> {'housing', 'transportation'}
59+
60+
61+
Citation:
62+
63+
`Guevara et al. (2024)`_ Large language models to identify social determinants of
64+
health in electronic health records
65+
66+
.. _Guevara et al. (2024): https://www.nature.com/articles/s41746-023-00970-0
67+
68+
"""
69+
_ROLE: ClassVar[str] = 'You are a social determinants of health (SDOH) classifier.'
70+
_LABELS: ClassVar[str] = 'transportation housing relationship employment support parent'.split()
71+
72+
api_key: str = field(default=None)
73+
"""The API token that starts with ``tf_`` needed to download the Llama
74+
model.
75+
76+
"""
77+
base_model_id: str = field(default='meta-llama/Llama-3.1-8B-Instruct')
78+
"""The base model ID, which probably should not be modified."""
79+
80+
adapter_model_id: str = field(default='plandes/sdoh-llama-3-1-8b')
81+
"""The LoRA adapter model ID, which probably should not be modified."""
82+
83+
def _parse_response(self, text: str) -> Set[str]:
84+
"""Parse the LLM response (also used in the unit test case).."""
85+
res_regs = (re.compile(r'(?:.*?`([a-z,` ]{3,}`))', re.DOTALL),
86+
re.compile(r'.*?[`#-]([a-z, \t\n\r]{3,}?)[`-].*', re.DOTALL))
87+
matched: str = ''
88+
for pat in res_regs:
89+
m: re.Match = pat.match(text)
90+
if m is not None:
91+
matched = m.group(1)
92+
break
93+
return set(filter(lambda s: matched.find(s) > -1, self._LABELS))
94+
95+
def _mod_ignore_check_type(self):
96+
from transformers.pipelines.text_generation import TextGenerationPipeline
97+
98+
def noop(*args, **kwargs):
99+
pass
100+
101+
TextGenerationPipeline.check_model_type = noop
102+
103+
def _get_pipeline(self) -> Pipeline:
104+
"""Create the text generation pipeline. The output is parsed by
105+
:meth:`_parse_response`."""
106+
if not hasattr(self, '_pipeline'):
107+
params: Dict[str, Any] = {}
108+
if self.api_key is not None:
109+
params['token'] = self.api_key
110+
base_model = AutoModelForCausalLM.from_pretrained(
111+
self.base_model_id, **params)
112+
model = PeftModelForCausalLM.from_pretrained(
113+
base_model, self.adapter_model_id, **params)
114+
tokenizer = AutoTokenizer.from_pretrained(
115+
self.base_model_id, **params)
116+
# suppress bogus error logging message under transformers 4.53
117+
# https://github.com/huggingface/transformers/issues/29395
118+
self._mod_ignore_check_type()
119+
# create a pipeline for inferencing
120+
self._pipeline = transformers.pipeline(
121+
'text-generation',
122+
framework='pt',
123+
model=model,
124+
tokenizer=tokenizer,
125+
model_kwargs={'torch_dtype': torch.bfloat16},
126+
device_map='auto')
127+
return self._pipeline
128+
129+
def predict(self, sent: str) -> Set[str]:
130+
"""Predict the SDoH labels of ``sent`` (see class docs).
131+
132+
:param sent: the sentence text used for prediction
133+
134+
:return: the SDoH labels predicted by the model
135+
136+
"""
137+
# prompt used by the chat template
138+
messages = [
139+
{'role': 'system', 'content': self._ROLE},
140+
{'role': 'user', 'content': _PROMPT.format(sent=sent)}]
141+
142+
pipeline: Pipeline = self._get_pipeline()
143+
# inference the LLM
144+
outputs = pipeline(
145+
messages,
146+
max_new_tokens=512,
147+
eos_token_id=[
148+
pipeline.tokenizer.eos_token_id,
149+
pipeline.tokenizer.convert_tokens_to_ids('<|eot_id|>'),
150+
],
151+
pad_token_id=pipeline.tokenizer.eos_token_id,
152+
do_sample=True,
153+
temperature=0.01)
154+
155+
# the textual LLM output
156+
output = outputs[0]['generated_text'][-1]['content']
157+
return self._parse_response(output)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ dependencies = [
2525
"torch~=2.7.1",
2626
"torchvision",
2727
"transformers~=4.53.2",
28+
"peft",
29+
"accelerate",
2830
"rdkit",
2931
"scikit-learn~=1.7.0",
3032
"networkx",

0 commit comments

Comments
 (0)