Эта статья является частью серии, в которой мы описываем все аннотаторы и основные функции Spark NLP for Healthcare. Каждая статья включает примеры и Jupyter Notebook для тестирования.

Извлечение сущности в тексте

Извлечение различных сущностей из медицинских карт - обычная задача в здравоохранении. Это может быть проблема, которую пациент проходил, получал лечение и прошел тесты, или дозировка, такая как наркотики, Дозировка и так далее. Эта операция позволяет преобразовывать необработанные данные в структурированные, которые можно анализировать с помощью статистических подходов или отображать в отчетах.

Иногда в клинических текстах вам нужно извлечь коды заболеваний, дозировку или даты с заранее определенными правилами. Первая идея, которая может прийти вам в голову, почему бы мне не использовать простое Regex? Звучит логично и просто.

Но что, если вам нужно интегрировать его в существующий конвейер и после этого внести некоторые преобразования? Конечно, с конвейерами Spark NLP вы всегда можете завершить предварительно обработанный текст, применить пользовательскую функцию (UDF) и создать новый конвейер. Но зачем нам повторяться? Есть более простое решение.

Контекстный парсер

Команда Spark NLP создала специальный аннотатор для такой операции как часть конвейера, который позволит вам использовать результаты регулярных выражений в следующих преобразователях (или просто извлекать нужную информацию).

Выражение регулярного выражения для ContextualParser хранится в файле JSON. Что приятно в этом, формат содержит не только само регулярное выражение, но и дополнительные настройки, что значительно упрощает используемое регулярное выражение или позволяет сэкономить время на добавление нескольких вариантов слова в словарь. Словарь - это способ ссылаться на фиксированный словарь для совпадения вместо установки регулярного выражения в JSON. Это очень полезно, если вы сегодня не хотите писать сложное регулярное выражение, но вам нужна рабочая версия конвейера.

ContextualParserApproach () происходит от класса sparknlp-jsl.annotator и имеет следующие настраиваемые параметры. Смотрите полный список здесь.

  • setJsonPath () - ›Устанавливает расположение файла JSON с помощью регулярного выражения.
  • setCaseSensitive () - ›необязательный: использовать ли регистр при сопоставлении значений, по умолчанию false
  • setPrefixAndSuffixMatch () - ›необязательный: определяет, должны ли мы сопоставлять префикс и суффикс, чтобы аннотировать попадание.
  • setDictionary () - ›необязательно: задает путь к файлу словаря в формате tsv или csv.

Давайте посмотрим на конкретный пример:

Вы работаете над статистическим отчетом по онкологическим заболеваниям в регионе. В качестве набора данных у вас есть большой объем клинических отчетов. Теперь вы хотите извлечь такие объекты, как pT1bN0M0, cT4bcN2M1, cT3cN2 и т. Д. (С последующим определенным шаблоном), чтобы вычислить общее количество этапов. Пример имеющегося у вас текста:

A patient has liver metastases pT1bN0M0 and the T5 primary site may be colon or lung. If the primary site is not clearly identified , this case is cT4bcN2M1, Stage Grouping 88. N4 A child T?N3M1  has soft tissue aM3 sarcoma and the staging has been left unstaged. Both clinical and pathologic staging would be coded pT1bN0M0 as unstageable cT3cN2.Medications started.

вы определяете имя извлекаемого объекта, значение регулярного выражения и matchScope, который сообщит регулярному выражению, следует ли выполнять полное или частичное совпадение:

{
  "entity": "Stage",
  "ruleScope": "sentence",
  "regex": "[cpyrau]?[T][0-9X?][a-z^cpyrau]*",
  "matchScope": "token"
}

Пока не обращайте внимания на ruleScope, он всегда на уровне sentence. Это означает, что нужно найти совпадение в каждом предложении.

В результате получится:

expectedResult = ["pT1bN0M0", "T5", "cT4bcN2M1", "T?N3M1", "pT1bN0M0", "cT3cN2.Medications"]

Если вы используете matchScope на уровне суб-токена, конвейер будет выводить:

expectedResult = ["pT1b", "T5", "cT4bc", "T?", "pT1b", "cT3c"]

Определение трубопровода

Мы уже определили, зачем вам может понадобиться Contextual Parser, вот как вы можете интегрировать его в существующий конвейер Spark NLP:

document_assembler = DocumentAssembler() \
    .setInputCol("text") \
    .setOutputCol("document")
sentence_detector = SentenceDetector() \
    .setInputCols(["document"]) \
    .setOutputCol("sentence")
tokenizer = Tokenizer() \
    .setInputCols(["sentence"]) \
    .setOutputCol("token")
stage_contextual_parser = ContextualParserApproach() \
        .setInputCols(["sentence", "token"]) \
        .setOutputCol("entity_stage") \
        .setJsonPath("data/Stage.json")
parser_pipeline = Pipeline(stages=[
    document_assembler, 
    sentence_detector,
    tokenizer,
    stage_contextual_parser])
empty_data = spark.createDataFrame([[""]]).toDF("text")
parser_model = parser_pipeline.fit(empty_data)
light_model = LightPipeline(parser_model)
annotations = light_model.fullAnnotate(sample_text)[0]

Параметр contextLength указывает максимальное расстояние, на которое префиксные или суффиксные слова могут находиться от слова для сопоставления, тогда как context - это слова, которые должны быть сразу после или перед словом для сопоставления.

Функция словаря

Еще одна полезная функция параметра parser id dictionary. Чтобы использовать ut, вам нужно определить набор слов, которым вы хотите сопоставить, и слово, которое заменит это совпадение.

Например, этим определением вы сообщаете ContextualParser, что при сопоставлении слов woman, female и girl они будут заменены на female, тогда как man, male, boy и gentleman будут заменены на male.

female  woman   female  girl
male    man male    boy gentleman

Так, например, для этого текста:

At birth, the typical boy is growing slightly faster than the typical girl, but the velocities become equal at about seven months, and then the girl grows faster until four years. From then until adolescence no differences in velocity can be detected.

Ожидаемый результат работы аннотатора будет:

expectedResult = ["boy", "girl", "girl"]

а заменяющие слова могут быть извлечены из метаданных:

expectedMetadata =
[{"field" -> "Gender", "normalized" -> "male", "confidenceValue" -> "0.13", "hits" -> "regex", "sentence" -> "0"},
{"field" -> "Gender", "normalized" -> "female", "confidenceValue" -> "0.13", "hits" -> "regex", "sentence" -> "0"},
{"field" -> "Gender", "normalized" -> "female", "confidenceValue" -> "0.13", "hits" -> "regex", "sentence" -> "0"}]

Для dictionary вам просто нужно определить файл csv или tsv, где первый элемент строки - это нормализованное слово, а другие элементы будут значениями для сопоставления.

Вот Jupyter Notebook, где вы можете найти больше примеров использования аннотаторов и полных конвейеров.

Заключение

В этой статье мы познакомили вас с ContextualParserApproach (), одним из аннотаторов, который позволяет нам извлекать объекты из текста. Это упрощенный аннотатор, который вы можете использовать, если можете применять регулярные выражения для извлечения ваших сущностей и конкретных правил. Если вам нужно также принять во внимание контекстную информацию, я предлагаю вам проверить наши предварительно обученные модели NerDL и даже обучить свою собственную модель с помощью аннотатора NerDL.

Вот ссылки на статьи, которые помогут вам в этом. Не забывайте следить за нашей страницей и оставайтесь с нами!

Проблемы?

Не стесняйтесь пинговать нас на Github или просто присоединяйтесь к нашему slack-каналу!