Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
23525b7
Importing number of folds constant from model
swfarnsworth May 19, 2020
bc1ffe7
Removed references to deprecated content
swfarnsworth May 20, 2020
d3f5591
Merge pull request #191 from NLPatVCU/development
swfarnsworth May 25, 2020
201a960
Merge branch 'master' of https://github.com/NLPatVCU/medaCy into hotfix
swfarnsworth May 25, 2020
5e7db40
Merge pull request #193 from NLPatVCU/hotfix
swfarnsworth May 25, 2020
7e3e5ba
Fixed error with paths inputed as strings not being parsed
swfarnsworth May 27, 2020
d17625e
CLI now prints command used and medaCy version number
swfarnsworth May 27, 2020
75310cd
This will be part of a bugfix release
swfarnsworth May 27, 2020
b1cd4e1
Update README.md
daikikatsuragawa Nov 15, 2020
ec9343a
Merge pull request #196 from daikikatsuragawa/master
swfarnsworth Nov 15, 2020
a564359
Restored Python3.6 support with a backport for dataclasses as a condi…
swfarnsworth Dec 12, 2020
c8d7e9d
Merge pull request #198 from NLPatVCU/v1_bugfix
bmcinnes Dec 22, 2020
7693185
Emphasize CLI, a few other changes.
swfarnsworth Jul 12, 2021
aa223d7
Optimization: use numpy functionality rather than a nested loop.
swfarnsworth Jul 18, 2021
458e513
Fix typo.
swfarnsworth Jul 18, 2021
439340a
Use fstrings and `statistics.mean`.
swfarnsworth Jul 18, 2021
0ed1b6a
Refactoring.
swfarnsworth Jul 18, 2021
85189cc
Make tests more granular (one for CRF, one without).
swfarnsworth Jul 18, 2021
f472831
Pull static methods out into nonpublic functions.
swfarnsworth Jul 18, 2021
55233a9
Convert `loss` to a float (it was a Tensor with one element).
swfarnsworth Jul 18, 2021
c7773ed
Migrate to pytest.
swfarnsworth Jul 18, 2021
cbde43c
Put fixtures in their own module.
swfarnsworth Jul 18, 2021
a3e5808
Create pytest.ini file for marking tests as slow.
swfarnsworth Jul 18, 2021
b492150
Import fixtures from other module.
swfarnsworth Jul 18, 2021
1a59a28
Migrate to pytest.
swfarnsworth Jul 18, 2021
e2e422f
Install pytest and skip slow tests.
swfarnsworth Jul 18, 2021
1a255bb
General refactoring.
swfarnsworth Aug 14, 2021
2d2d72f
Fix off-by-one errors.
swfarnsworth Aug 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ dist: trusty
group: edge

python:
- "3.7"
- "3.8"
- "3.6"
- "3.7"
- "3.8"

os:
- linux
Expand All @@ -16,7 +17,7 @@ os:
install:
- pip install --upgrade pip
- pip install -e .
- pip install pytest

script:
- "python setup.py test || exit 0"
- "python setup.py test"
- "python -m pytest ./medacy/tests/ -m 'not slow'"
45 changes: 14 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,34 @@ workflow by providing utilities for model training, prediction and organization
- Customizable pipelines with detailed development instructions and documentation.
- Allows the designing of replicable NLP systems for reproducing results and encouraging the distribution of models whilst still allowing for privacy.
- Active community development spearheaded and maintained by [NLP@VCU](https://nlp.cs.vcu.edu/).
- Detailed [API](https://medacy.readthedocs.io/en/latest/).

## :thought_balloon: Where to ask questions

MedaCy is actively maintained by a team of researchers at Virginia Commonwealth University. The best way to
receive immediate responses to any questions is to raise an issue. Make sure to first consult the
[API](https://medacy.readthedocs.io/en/latest/). See how to formulate a good issue or feature request in the [Contribution Guide](CONTRIBUTING.md).

## :computer: Installation Instructions
MedaCy can be installed for general use or for pipeline development / research purposes.
MedaCy supports Python >= 3.6

| Application | Run |
| ----------- |:-------------:|
| Prediction and Model Training (stable) | `pip install git+https://github.com/NLPatVCU/medaCy.git` |
| Prediction and Model Training (latest) | `pip install git+https://github.com/NLPatVCU/medaCy.git@development` |
| Pipeline Development and Contribution | [See Contribution Instructions](/CONTRIBUTING.md) |


# :books: Power of medaCy
After installing medaCy and [medaCy's clinical model](guide/models/clinical_notes_model.md), simply run:
# :books: How to use medaCy

```python
from medacy.model.model import Model
MedaCy's components can be imported into other Python programs, but is designed primarily to be used via its command line interface.
Once medaCy is installed, one can read the instructions at any time with this command.

model = Model.load_external('medacy_model_clinical_notes')
annotation = model.predict("The patient was prescribed 1 capsule of Advil for 5 days.")
print(annotation)
```
and receive instant predictions:
```python
[
('Drug', 40, 45, 'Advil'),
('Dosage', 27, 28, '1'),
('Form', 29, 36, 'capsule'),
('Duration', 46, 56, 'for 5 days')
]
```bash
python -m medacy --help
```

MedaCy can also be used through its command line interface, documented [here](./guide/command_line_interface.md)
More thorough documentation is provided [here](./guide/command_line_interface.md).

## :thought_balloon: Where to Ask Questions

To explore medaCy's other models or train your own, visit the [examples section](guide).
MedaCy is actively maintained by a team of researchers at Virginia Commonwealth University. The best way to
receive immediate responses to any questions is to open an issue in this repository.

Reference
=========
```
```bibtex
@ARTICLE {
author = "Andriy Mulyar, Natassja Lewinski and Bridget McInnes",
title = "TAC SRIE 2018: Extracting Systematic Review Information with MedaCy",
Expand All @@ -74,9 +57,9 @@ This package is licensed under the GNU General Public License.

Authors
=======
Current contributors: Steele Farnsworth, Anna Conte, Gabby Gurdin, Aidan Kierans, Aidan Myers, and Bridget T. McInnes
Current contributors: Steele Farnsworth, Gabby Gurdin, Aidan Myers, and Bridget T. McInnes

Former contributors: Andriy Mulyar, Jorge Vargas, Corey Sutphin, and Bobby Best
Former contributors: Andriy Mulyar, Jorge Vargas, Corey Sutphin, Bobby Best, Anna Conte, and Aidan Kierans

Acknowledgments
===============
Expand Down
28 changes: 0 additions & 28 deletions guide/models/clinical_notes_model.md

This file was deleted.

1 change: 0 additions & 1 deletion guide/models/epa_systematic_review_model.md

This file was deleted.

1 change: 0 additions & 1 deletion guide/models/nanomedicine_drug_labels.md

This file was deleted.

52 changes: 4 additions & 48 deletions guide/walkthrough/data_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ MedaCy **does not** alter the data you load in any way - it only reads from it.

A common data work flow might look like this.

running:

```
```pythonstub
>>> from medacy.data.datset import Dataset
>>> from medacy.pipeline_components.feature_overlayers.metamap.metamap import MetaMap

>>> dataset = Dataset('/home/medacy/data')
>>> for data_file in dataset:
... data_file.file_name
... data_file.file_name
'file_one'
'file_two'
>>> data
Expand All @@ -63,7 +61,7 @@ running:
False
>>> metamap = Metamap('/home/path/to/metamap/binary')
>>> with metamap:
... data.metamap(metamap)
... data.metamap(metamap)
data.is_metamapped()
True
```
Expand All @@ -81,47 +79,6 @@ home/medacy/data
└── file_two.metamapped
```



## Loading a medaCy compatible dataset
Using a *medaCy compatible dataset* package to manage your training data insures that data is easy and efficient to access, versioned for replicability, and distributable (selectively!).

A *medaCy compatible dataset* is python package wrapping data that can be hooked into medaCy. We can install a *medaCy compatible dataset* just like any python package. For instance,


`pip install https://github.com/NanoNLP/medaCy_dataset_end/archive/v1.0.3.tar.gz#egg=medacy_dataset_end-1.0.3`

will install `v1.0.03` of the [END](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5644562/) dataset. Alternatively,

`pip install git+https://github.com/NanoNLP/medaCy_dataset_end.git`

will install the latest version of the [END](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5644562/) dataset.

After you have installed a *medaCy compatible dataset*, loading it returns a configured `Dataset` object alongside meta-data in a `tuple` as follows:

```python
from medacy.data import Dataset

training_dataset, evaluation_dataset, meta_data = Dataset.load_external('medacy_dataset_end')

```

alternatively, import the datasets package and directly call the load method:

```python
import medacy_dataset_end

training_dataset, evaluation_dataset, meta_data = medacy_dataset_end.load()

print(meta_data['entities']) #entities this dataset annotates
print(meta_data['relations']) #relations this dataset annotates (END has None)

training_dataset = medacy_dataset_end.load_training_dataset() #access just training

evaluation_dataset = medacy_dataset_end.load_evaluation_dataset() #access just evaluation

```

## Using a Dataset
A *Dataset* is utilized for two main tasks:

Expand All @@ -139,7 +96,7 @@ from medacy.pipelines import FDANanoDrugLabelPipeline
dataset = Dataset('/home/medacy/data')
entities = ['Nanoparticle', 'Dose']
pipeline = FDANanoDrugLabelPipeline(entities=entities)
model = Model(pipeline, n_jobs=1)
model = Model(pipeline)

model.fit(dataset)
```
Expand Down Expand Up @@ -172,4 +129,3 @@ By default, this creates a sub-directory in your prediction dataset named *predi
```

where all files under *predictions* are the trained models predictions over your test data.

76 changes: 3 additions & 73 deletions guide/walkthrough/model_training.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ The previously mentioned components make up a medaCy model. In summary training

```python
import os
from medacy.data import Dataset
from medacy.data.dataset import Dataset
from medacy.pipelines import ClinicalPipeline
from medacy.ner import Model
from medacy.model.model import Model

entities = ['Drug', 'Strength']

training_dataset = Dataset('/home/medacy/clinical_training_data/')
pipeline = ClinicalPipeline(metamap=None, entities=entities)
model = Model(pipeline, n_jobs=30) #distribute documents between 30 processes during training and prediction
model = Model(pipeline)

output_file_path = '/home/medacy/clinical_model.pickle'
# Protect against running fit() without having a valid place to save it
Expand All @@ -81,74 +81,4 @@ assert os.path.isfile(output_file_path)
model.fit(training_dataset)

model.dump(output_file_path)


```

The `ClinicalPipeline` source looks like this:

```python
import sklearn_crfsuite
import spacy

from medacy.pipeline_components.feature_extractors.discrete_feature_extractor import FeatureExtractor
from medacy.pipeline_components.feature_overlayers.metamap.metamap import MetaMap
from medacy.pipeline_components.feature_overlayers.metamap.metamap_component import MetaMapOverlayer
from medacy.pipeline_components.tokenizers.clinical_tokenizer import ClinicalTokenizer
from medacy.pipelines.base.base_pipeline import BasePipeline


class ClinicalPipeline(BasePipeline):
"""
A pipeline for clinical named entity recognition. A special tokenizer that breaks down a clinical document
to character level tokens defines this pipeline. It was created for the extraction of ADE related entities
from the 2018 N2C2 Shared Task.

Created by Andiy Mulyar (andriymulyar.com) of NLP@VCU
"""


def __init__(self, entities, metamap=None, **kwargs):
"""
Create a pipeline with the name 'clinical_pipeline' utilizing
by default spaCy's small english model.

:param entities: a list of entities to use in this pipeline.
:param metamap: an instance of MetaMap if metamap should be used, defaults to None.
"""

super().__init__(entities, spacy_pipeline=spacy.load("en_core_web_sm"))

if isinstance(metamap, MetaMap):
self.add_component(MetaMapOverlayer, metamap)

def get_learner(self):
return ("CRF_l2sgd",
sklearn_crfsuite.CRF(
algorithm='l2sgd',
c2=0.1,
max_iterations=100,
all_possible_transitions=True
)
)

def get_tokenizer(self):
return ClinicalTokenizer(self.spacy_pipeline)

def get_feature_extractor(self):
return FeatureExtractor(window_size=3, spacy_features=['pos_', 'shape_', 'prefix_', 'suffix_', 'text'])
```


The `__init__` method defines pipeline meta-data along with initializing the sequence of components the pipeline will use to annotate custom token attributes over the document. Components are imported and initialized as part of the pipeline by calling the `add_component` method. The first paramater is a component and the subsequent parameters are any arguments that are passed to the component on initialization. Token attributes beginning with `feature_` are automically collected by the `FeatureExtractor` initialized in the `get_feature_extractor` method. Note the instantiation of the `FeatureExtractor` allows the definition of an array of `spacy_features` to utilize - these can be any attribute of a spaCy [Token](https://spacy.io/api/token#attributes).

The `get_learner` method returns a configured instance of the machine learning algorithm to utilize for training a model. Currently only CRF models wrapped by the package [sklearn-crfsuite](https://sklearn-crfsuite.readthedocs.io/en/latest/) are allowed.

The `get_tokenizer` method returns a configured medaCy tokenizer. An interface for building and maintaining a tokenizer is provided and the pattern from `ClinicalTokenizer` can be followed for engineering your own.

The `get_feature_extractor` method returns a configured feature extractor. This defines how and what features from annotated documents are collected to be fed into the model during training or prediction. The example configuration means that all medaCy annotated features and the specified `spacy_features` are collected in a range of three tokens to the left and three tokens to the right of every token (ie. the `window_size`).





2 changes: 0 additions & 2 deletions guide/walkthrough/model_utilization.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ One of medaCy's most powerful features is the ability to maintain, version and d
Once a model has been [packaged](packaging_a_medacy_model.md) and installed it can be used as follows:

```python
import medacy_model_clinical_notes #import the python package wrapping the model
from medacy.model.model import Model

model = Model.load_external('medacy_model_clinical_notes')

annotations = model.predict("The patient took 5 mg of aspirin.")
```

Expand Down
2 changes: 1 addition & 1 deletion medacy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '1.0.0'
__version__ = '1.0.1'
__authors__ = "Andriy Mulyar, Jorge Vargas, Corey Sutphin, Steele Farnsworth, Bobby Best, Bridget T. McInnes"
9 changes: 6 additions & 3 deletions medacy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import importlib
import json
import logging
from sys import argv

from medacy import __version__
from medacy.data.dataset import Dataset
from medacy.model.model import Model
from medacy.model.model import Model, DEFAULT_NUM_FOLDS
from medacy.pipelines import bert_pipeline
from medacy.tools.json_to_pipeline import json_to_pipeline

Expand Down Expand Up @@ -145,7 +147,7 @@ def main():

# Cross Validation arguments
parser_validate = subparsers.add_parser('validate', help='Cross validate a model on a given dataset.')
parser_validate.add_argument('-k', '--k_folds', default=5, type=int, help='Number of folds to use for cross-validation.')
parser_validate.add_argument('-k', '--k_folds', default=DEFAULT_NUM_FOLDS, type=int, help='Number of folds to use for cross-validation.')
parser_validate.add_argument('-gt', '--groundtruth', type=str, default=None, help='Directory to write groundtruth files.')
parser_validate.add_argument('-pd', '--predictions', type=str, default=None, help='Directory to write prediction files.')
parser_validate.set_defaults(func=cross_validate)
Expand Down Expand Up @@ -174,7 +176,8 @@ def main():
logger.addHandler(logging.StreamHandler())
if args.test_mode:
logger.setLevel(logging.DEBUG)
logging.info("Test mode enabled: logging set to debug")
logging.info("Test mode enabled: logging set to debug")
logging.info(f"medaCy v{__version__}\nCommand: python -m medacy {' '.join(argv[1:])}")
start_time = dt.datetime.now()
start_timestamp = start_time.strftime('%Y-%m-%d %H:%M:%S')
logging.info(f'\n\nSTART TIME: {start_timestamp}')
Expand Down
2 changes: 1 addition & 1 deletion medacy/pipeline_components/learners/bert_learner.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def decode_labels(self, sequence_labels, mappings):
if map_value != null_label:
remapped_labels.append(label)

# Decode list of label indices useing self.vectorizer
# Decode list of label indices using self.vectorizer
decoded_labels.append(self.vectorizer.devectorize_tag(remapped_labels))

return decoded_labels
Expand Down
6 changes: 3 additions & 3 deletions medacy/pipeline_components/learners/bilstm_crf_learner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import logging
import random
from statistics import mean

import torch
import torch.nn as nn
Expand Down Expand Up @@ -101,10 +102,9 @@ def fit(self, x_data, y_data):
optimizer.zero_grad()
loss.backward()
optimizer.step()
epoch_losses.append(loss)
epoch_losses.append(float(loss))

average_loss = sum(epoch_losses) / len(epoch_losses)
logging.info('Epoch %d average loss: %f', i, average_loss)
logging.info(f'Epoch {i} average loss: {mean(epoch_losses)}')

self.model = model

Expand Down
Loading