machinelearningmastery.ru

Машинное обучение, нейронные сети, искусственный интеллект
Header decor

Home

Введение в клиническую обработку естественного языка: прогнозирование повторной госпитализации с кратким описанием выписки

Дата публикации Jun 4, 2018

Врачи всегда писали клинические заметки о своих пациентах - изначально эти заметки были на бумаге и были заперты в шкафу. К счастью для ученых данных, врачи теперь вносят свои записи в электронную медицинскую карту. Эти заметки представляют собой обширный опыт и знания, которые можно использовать для прогнозных моделей, использующих Natural Language Processing (NLP), для улучшения ухода за пациентами и работы больниц. В качестве примера я покажу вам, как прогнозировать повторную госпитализацию с помощью сводных данных о выписке.

Эта статья предназначена для людей, интересующихся наукой о здравоохранении. После завершения этого урока вы узнаете

  • Как подготовить данные для проекта машинного обучения
  • Как предварительно обработать неструктурированные заметки, используя метод слов
  • Как построить простую прогностическую модель
  • Как оценить качество вашей модели
  • Как решить следующий шаг по улучшению модели

Недавно я прочитал эту замечательную статью «Масштабируемое и точное глубокое обучение для электронных медицинских карт», автор Rajkomar et al. (бумага вhttps://arxiv.org/abs/1801.07860). Авторы построили множество современных моделей глубокого обучения с данными больниц для прогнозирования внутрибольничной смертности (AUC = 0,93–0,94), 30-дневной незапланированной реадмиссии (AUC = 0,75–76), длительной продолжительности пребывания (AUC). = 0,85–0,86) и диагнозы выписки (AUC = 0,90). AUC является метрикой производительности науки о данных (подробнее об этом ниже), где ближе к 1 лучше. Понятно, что прогнозирование реадмиссии является самой сложной задачей, так как у него более низкий AUC. Мне было любопытно, насколько хороша модель, которую мы можем получить, если использовать произвольные текстовые аннотации с простой прогностической моделью.

Если вы хотите использовать код Python в блокноте Jupyter, не стесняйтесь загрузить код с моегоGitHub,

Определение модели

В этом посте будет рассказано, как построить классификационную модель для прогнозирования того, какие пациенты подвергаются риску 30-дневной незапланированной реадмиссии, используя сводные выписки из больницы.

Набор данных

Мы будем использовать базу данных MIMIC-III (Медицинский информационный центр для интенсивной терапии III). Эта удивительная бесплатная база данных о больницах содержит неопознанные данные более чем 50 000 пациентов, которые были госпитализированы в медицинский центр Beth Israel Deaconess в Бостоне, штат Массачусетс, с 2001 по 2012 годы. Чтобы получить доступ к данным этого проекта, вам необходимо запросить доступ по этой ссылке (https://mimic.physionet.org/gettingstarted/access/).

В этом проекте мы будем использовать следующие таблицы MIMIC III

  • ADMISSIONS - таблица, содержащая даты приема и выписки (имеет уникальный идентификатор HADM_ID для каждого приема)
  • NOTEEVENTS - содержит все заметки для каждой госпитализации (ссылки с HADM_ID)

Чтобы сохранить анонимность, все даты были перенесены далеко в будущее для каждого пациента, но время между двумя последовательными событиями для пациента сохраняется в базе данных. Это важно, поскольку оно поддерживает время между двумя госпитализациями для конкретного пациента.

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

Шаг 1: Подготовьте данные для проекта машинного обучения

Мы будем следовать приведенным ниже шагам, чтобы подготовить данные из таблиц ADMISSIONS и NOTEEVENTS MIMIC для нашего проекта машинного обучения.

Во-первых, мы загружаем таблицу допусков, используя кадры данных pandas:

# set up notebook
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt# read the admissions table
df_adm = pd.read_csv('ADMISSIONS.csv')

Основные столбцы, представляющие интерес в этой таблице:

  • SUBJECT_ID: уникальный идентификатор для каждого предмета
  • HADM_ID: уникальный идентификатор для каждой госпитализации
  • ADMITTIME: дата поступления в формате ГГГГ-ММ-ДД чч: мм: сс
  • DISCHTIME: дата выписки в том же формате
  • Время смерти: время смерти (если оно существует) в том же формате
  • ADMISSION_TYPE: включает в себя ELECTIVE, EMERGENCY, NEWBORN, URGENT

Следующим шагом является преобразование дат из их строкового формата в дату и время. Мы используемerrors = ‘coerce’флаг для пропущенных дат.

# convert to dates
df_adm.ADMITTIME = pd.to_datetime(df_adm.ADMITTIME, format = '%Y-%m-%d %H:%M:%S', errors = 'coerce')
df_adm.DISCHTIME = pd.to_datetime(df_adm.DISCHTIME, format = '%Y-%m-%d %H:%M:%S', errors = 'coerce')
df_adm.DEATHTIME = pd.to_datetime(df_adm.DEATHTIME, format = '%Y-%m-%d %H:%M:%S', errors = 'coerce')

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

# sort by subject_ID and admission date
df_adm = df_adm.sort_values(['SUBJECT_ID','ADMITTIME'])
df_adm = df_adm.reset_index(drop = True)

Фрейм данных может выглядеть следующим образом для одного пациента:

Мы можем использовать оператор групповой смены, чтобы получить следующий допуск (если он существует) для каждого SUBJECT_ID

# add the next admission date and type for each subject using groupby
# you have to use groupby otherwise the dates will be from different subjects
df_adm['NEXT_ADMITTIME'] = df_adm.groupby('SUBJECT_ID').ADMITTIME.shift(-1)# get the next admission type
df_adm['NEXT_ADMISSION_TYPE'] = df_adm.groupby('SUBJECT_ID').ADMISSION_TYPE.shift(-1)

Обратите внимание, что при последнем приеме не будет следующего приема.

Но мы хотим предсказать НЕЗАКОННЫЕ повторные зачисления, поэтому нам следует отфильтровать ВЫБОР следующих зачислений.

# get rows where next admission is elective and replace with naT or nan
rows = df_adm.NEXT_ADMISSION_TYPE == 'ELECTIVE'
df_adm.loc[rows,'NEXT_ADMITTIME'] = pd.NaT
df_adm.loc[rows,'NEXT_ADMISSION_TYPE'] = np.NaN

А затем засыпать значения, которые мы удалили

# sort by subject_ID and admission date
# it is safer to sort right before the fill in case something changed the order above
df_adm = df_adm.sort_values(['SUBJECT_ID','ADMITTIME'])# back fill (this will take a little while)
df_adm[['NEXT_ADMITTIME','NEXT_ADMISSION_TYPE']] = df_adm.groupby(['SUBJECT_ID'])[['NEXT_ADMITTIME','NEXT_ADMISSION_TYPE']].fillna(method = 'bfill')

Затем мы можем рассчитать дни до следующего приема

df_adm['DAYS_NEXT_ADMIT']=  (df_adm.NEXT_ADMITTIME - df_adm.DISCHTIME).dt.total_seconds()/(24*60*60)

В нашем наборе данных с 58976 госпитализациями зарегистрировано 11399 повторных госпитализаций. Для тех, кто с повторным поступлением, мы можем построить гистограмму дней между приемами

Теперь мы готовы работать с NOTEEVENTS.csv

df_notes = pd.read_csv("NOTEEVENTS.csv")

Основные интересующие колонки:

  • subject_id
  • HADM_ID
  • КАТЕГОРИЯ: включает «Сводка по расходам», «Эхо», «ЭКГ», «Сестринское дело», «Врач», «Услуги реабилитации», «Управление случаями», «Респираторные операции», «Питание», «Общее», «Социальная работа». , «Аптека», «Консалт», «Радиология»,
    «Уход / прочее»
  • ТЕКСТ: наша колонка клинических заметок

Поскольку я не могу показать отдельные заметки, я просто опишу их здесь. Набор данных содержит 2 083 180 строк, что указывает на наличие нескольких записей на одну госпитализацию. В примечаниях даты и ЗМИ (имя, врач, место нахождения) были преобразованы для обеспечения конфиденциальности. Есть также специальные символы, такие как \ n (новая строка), цифры и знаки препинания.

Поскольку на одну госпитализацию приходится несколько заметок, нам нужно сделать выбор, какие записи использовать. Для простоты давайте воспользуемся сводкой выписок, но мы могли бы использовать все заметки, объединяя их, если захотим.

# filter to discharge summary
df_notes_dis_sum = df_notes.loc[df_notes.CATEGORY == 'Discharge summary']

Поскольку следующим шагом является объединение записей в таблице допущений, у нас может быть предположение, что для каждого допуска существует одна сводка выписок, но нам, вероятно, следует это проверить. Мы можем проверить это с помощью утверждения assert, которое в итоге завершается неудачей.

На данный момент, вы можете захотеть выяснить, почему существует несколько резюме, но для простоты давайте просто использовать последний

df_notes_dis_sum_last = (df_notes_dis_sum.groupby(['SUBJECT_ID','HADM_ID']).nth(-1)).reset_index()
assert df_notes_dis_sum_last.duplicated(['HADM_ID']).sum() == 0, 'Multiple discharge summaries per admission'

Теперь мы готовы объединить таблицы допусков и заметок. Я использую левое слияние для учета отсутствия заметок. Во многих случаях после слияния вы получаете несколько строк (хотя мы рассматривали это выше), поэтому мне нравится добавлять операторы assert после слияния

df_adm_notes = pd.merge(df_adm[['SUBJECT_ID','HADM_ID','ADMITTIME','DISCHTIME','DAYS_NEXT_ADMIT','NEXT_ADMITTIME','ADMISSION_TYPE','DEATHTIME']],
df_notes_dis_sum_last[['SUBJECT_ID','HADM_ID','TEXT']],
on = ['SUBJECT_ID','HADM_ID'],
how = 'left')
assert len(df_adm) == len(df_adm_notes), 'Number of rows increased'

10,6% пропусков отсутствуют (df_adm_notes.TEXT.isnull().sum() / len(df_adm_notes)), поэтому я исследовал немного дальше с

df_adm_notes.groupby('ADMISSION_TYPE').apply(lambda g: g.TEXT.isnull().sum())/df_adm_notes.groupby('ADMISSION_TYPE').size()

и обнаружил, что 53% поступивших в НЬЮБОРН пропускали резюме выписок против ~ 4% для остальных. На данный момент я решил удалить НОВОРОЖДЕННЫЕ. Скорее всего, эти пропущенные при поступлении в NEWBORN сводные данные хранятся вне набора данных MIMIC.

Для этой проблемы мы собираемся классифицировать, будет ли пациент принят в течение следующих 30 дней. Следовательно, нам нужно создать переменную с меткой вывода (1 = повторный доступ, 0 = повторный доступ)

df_adm_notes_clean['OUTPUT_LABEL'] = (df_adm_notes_clean.DAYS_NEXT_ADMIT < 30).astype('int')

Быстрый подсчет положительных и отрицательных результатов в 3004 положительных образцах, 48109 отрицательных образцах. Это указывает на то, что у нас есть несбалансированный набор данных, что является распространенным явлением в науке о здравоохранении.

Последний шаг для подготовки наших данных - это разделение данных на обучающие, проверочные и тестовые наборы. Для воспроизводимых результатов я сделал random_state всегда 42.

# shuffle the samples
df_adm_notes_clean = df_adm_notes_clean.sample(n = len(df_adm_notes_clean), random_state = 42)
df_adm_notes_clean = df_adm_notes_clean.reset_index(drop = True)# Save 30% of the data as validation and test data
df_valid_test=df_adm_notes_clean.sample(frac=0.30,random_state=42)df_test = df_valid_test.sample(frac = 0.5, random_state = 42)
df_valid = df_valid_test.drop(df_test.index)# use the rest of the data as training data
df_train_all=df_adm_notes_clean.drop(df_valid_test.index)

Поскольку распространенность настолько низкая, мы хотим, чтобы модель не всегда прогнозировала отрицательное (не повторное признание). Для этого у нас есть несколько вариантов сбалансировать тренировочные данные

  • подвыборка негативов
  • передискретизация положительных
  • создавать синтетические данные (например, SMOTE)

Поскольку я не ограничивал объем оперативной памяти для вашего компьютера, мы отберем негативы, но я призываю вас попробовать другие методы, если ваш компьютер или сервер могут справиться с этим, чтобы увидеть, сможете ли вы улучшить , (Опубликуйте комментарий ниже, если вы попробуете это!)

# split the training data into positive and negative
rows_pos = df_train_all.OUTPUT_LABEL == 1
df_train_pos = df_train_all.loc[rows_pos]
df_train_neg = df_train_all.loc[~rows_pos]# merge the balanced data
df_train = pd.concat([df_train_pos, df_train_neg.sample(n = len(df_train_pos), random_state = 42)],axis = 0)# shuffle the order of training samples
df_train = df_train.sample(n = len(df_train), random_state = 42).reset_index(drop = True)

Шаг 2: Предварительная обработка неструктурированных заметок с использованием пакета слов

Теперь, когда мы создали наборы данных, которые имеют метку и примечания, нам нужно предварительно обработать наши текстовые данные, чтобы преобразовать их в нечто полезное (то есть числа) для модели машинного обучения. Мы собираемся использовать подход Bag-of-Words (BOW).

BOW в основном разбивает примечание на отдельные слова и подсчитывает, сколько раз встречается каждое слово. Затем ваши числовые данные становятся рассчитанными для некоторого набора слов, как показано ниже. BOW - это самый простой способ сделать классификацию НЛП. В большинстве постов в блоге, которые я читал, более сложным методам трудно победить BOW для задач классификации НЛП.

В этом процессе есть несколько вариантов, которые необходимо сделать

  • как предварительно обработать слова
  • как считать слова
  • какие слова использовать

Не существует оптимального выбора для всех проектов НЛП, поэтому я рекомендую опробовать несколько вариантов при создании собственных моделей.

Вы можете сделать предварительную обработку двумя способами

  • изменить исходный столбец данных TEXT
  • предварительная обработка как часть вашего конвейера, чтобы вы не редактировали исходные данные

Я покажу вам, как сделать оба из них, но я предпочитаю второй, так как для достижения этой цели потребовалось много работы.

Давайте определим функцию, которая будет изменять исходный фрейм данных, заполняя пропущенные заметки пробелом и удаляя символы новой строки и возврата каретки

def preprocess_text(df):
# This function preprocesses the text by filling not a number and replacing new lines ('\n') and carriage returns ('\r')
df.TEXT = df.TEXT.fillna(' ')
df.TEXT = df.TEXT.str.replace('\n',' ')
df.TEXT = df.TEXT.str.replace('\r',' ')
return df# preprocess the text to deal with known issues
df_train = preprocess_text(df_train)
df_valid = preprocess_text(df_valid)
df_test = preprocess_text(df_test)

Другой вариант - предварительная обработка как часть конвейера. Этот процесс состоит из использования токенизатора и векторизатора. Токенайзер разбивает одну заметку на список слов, а векторизатор берет список слов и считает их.

Мы будем использоватьword_tokenizeотnltkпакет для нашего токенайзера по умолчанию, который в основном разбивает заметку на основе пробелов и некоторых знаков препинания Пример показан ниже:

import nltk
from nltk import word_tokenize
word_tokenize('This should be tokenized. 02/02/2018 sentence has stars**')

С выходом:

[‘This’, ‘should’, ‘be’, ‘tokenized’, ‘.’, ‘02/02/2018’, ‘sentence’,
‘has’, ‘stars**’]

По умолчанию показано, что некоторые знаки препинания разделены и цифры остаются в предложении. Мы напишем нашу собственную функцию токенизатора

  • заменить пунктуацию пробелами
  • заменить числа пробелами
  • строчные все слова
import string
def tokenizer_better(text):
# tokenize the text by replacing punctuation and numbers with spaces and lowercase all words

punc_list = string.punctuation+'0123456789'
t = str.maketrans(dict.fromkeys(punc_list, " "))
text = text.lower().translate(t)
tokens = word_tokenize(text)
return tokens

С этим токенизатором мы получаем из нашего оригинального предложения

['this', 'should', 'be', 'tokenized', 'sentence', 'has', 'stars']

Дополнительные вещи, которые вы можете сделать, - это лемматизировать или обрезать слова, но это более сложный вопрос, поэтому я пропущу это.

Теперь, когда у нас есть способ конвертировать свободный текст в токены, нам нужен способ подсчета токенов для каждой сводки выгрузки. Мы будем использовать встроенныйCountVectorizer из пакета scikit-learn. Этот векторизатор просто подсчитывает, сколько раз каждое слово встречается в заметке. Также естьTfidfVectorizer который учитывает, как часто слова используются во всех заметках, но для этого проекта давайте используем более простую (я получил аналогичные результаты и со второй).

В качестве примера, скажем, у нас есть 3 заметки

sample_text = ['Data science is about the data', 'The science is amazing', 'Predictive modeling is part of data science']

По сути, вы подходите CountVectorizer для изучения слов в ваших данных и преобразования ваших данных, чтобы создать счетчики для каждого слова.

from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(tokenizer = tokenizer_better)
vect.fit(sample_text)# matrix is stored as a sparse matrix (since you have a lot of zeros)
X = vect.transform(sample_text)

Матрица X будет разреженной матрицей, но если вы преобразуете ее в массив (X.toarray()), вы увидите это

array([[1, 0, 2, 1, 0, 0, 0, 0, 1, 1],
[0, 1, 0, 1, 0, 0, 0, 0, 1, 1],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0]], dtype=int64)

Где есть 3 строки (так как у нас есть 3 примечания) и количество каждого слова. Вы можете увидеть названия столбцов сvect.get_feature_names()

['about', 'amazing', 'data', 'is', 'modeling', 'of', 'part', 'predictive', 'science', 'the']

Теперь мы можем разместить наш CountVectorizer на клинических заметках. Важно использовать только данные обучения, потому что вы не хотите включать какие-либо новые слова, которые появляются в наборах проверки и тестирования. Существует гиперпараметр с именем max_features, который можно установить, чтобы ограничить количество слов, включаемых в векторизатор. Это будет использовать топ N наиболее часто используемых слов. На шаге 5 мы настроим это, чтобы увидеть его эффект.

# fit our vectorizer. This will take a while depending on your computer. from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(max_features = 3000, tokenizer = tokenizer_better)# this could take a while
vect.fit(df_train.TEXT.values)

Мы можем взглянуть на наиболее часто используемые слова и увидим, что многие из этих слов могут не добавить никакой ценности для нашей модели. Эти слова называются стоп-словами, и мы можем легко удалить их (если захотим) с помощью CountVectorizer. Существуют списки общих стоп-слов для разных корпусов НЛП, но мы просто составим наш собственный, основываясь на изображении ниже.

my_stop_words = ['the','and','to','of','was','with','a','on','in','for','name',                 'is','patient','s','he','at','as','or','one','she','his','her','am',                 'were','you','pt','pm','by','be','had','your','this','date',                'from','there','an','that','p','are','have','has','h','but','o',                'namepattern','which','every','also']

Не стесняйтесь добавлять свои собственные стоп-слова, если хотите.

from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(max_features = 3000,
tokenizer = tokenizer_better,
stop_words = my_stop_words)
# this could take a while
vect.fit(df_train.TEXT.values)

Теперь мы можем преобразовать наши заметки в числовые матрицы. На данный момент я буду использовать только данные обучения и проверки, поэтому я не испытываю желания посмотреть, как они работают с тестовыми данными.

X_train_tf = vect.transform(df_train.TEXT.values)
X_valid_tf = vect.transform(df_valid.TEXT.values)

Нам также нужны наши выходные метки как отдельные переменные

y_train = df_train.OUTPUT_LABEL
y_valid = df_valid.OUTPUT_LABEL

Как видно из положения полосы прокрутки… как всегда, для подготовки данных для прогнозной модели требуется 80% времени.

Шаг 3: Построить простую прогнозную модель

Теперь мы можем построить простую прогностическую модель, которая берет наши входные данные и предсказывает, будет ли пациент повторно принят через 30 дней (ДА = 1, НЕТ = 0).

Здесь мы будем использовать модель логистической регрессии. Логистическая регрессия является хорошей базовой моделью для задач НЛП, поскольку она хорошо работает с разреженными матрицами и интерпретируема. У нас есть несколько дополнительных вариантов (называемых гиперпараметрами), включая C, который является коэффициентом регуляризации и штрафом, который говорит, как измерить регуляризацию. Регуляризация, по сути, является техникой, чтобы попытаться минимизировать переоснащение.

# logistic regression
from sklearn.linear_model import LogisticRegression
clf=LogisticRegression(C = 0.0001, penalty = 'l2', random_state = 42)
clf.fit(X_train_tf, y_train)

Мы можем рассчитать вероятность реадмиссии для каждого образца с подобранной моделью

model = clf
y_train_preds = model.predict_proba(X_train_tf)[:,1]
y_valid_preds = model.predict_proba(X_valid_tf)[:,1]

Шаг 4: Оцените качество вашей модели

На данный момент нам нужно измерить, насколько хорошо наша модель работает. Есть несколько различных метрик производительности науки о данных. Я написал другойСообщение блогаобъясняя это подробно, если вы заинтересованы. Поскольку этот пост довольно длинный, я начну показывать результаты и цифры. Вы можете увидеть учетную запись github для кода для создания фигур.

Для порога 0,5 для прогнозирования положительного, мы получаем следующую производительность

С текущим выбором гиперпараметров у нас есть некоторые переоснащения. Следует отметить, что основное различие между точностью в двух наборах данных связано с тем, что мы уравновешивали обучающий набор, где в качестве набора проверки используется исходное распределение. В настоящее время, если мы составим список пациентов, которые, как прогнозируют, будут повторно приняты, мы поймаем их вдвое больше, чем если бы мы выбирали случайным образом пациентов (ТОЧНОСТЬ против РАСПРОСТРАНЕННОСТИ).

Другим показателем производительности, не показанным выше, является AUC или площадь под кривой ROC. Кривая ROC для нашей текущей модели показана ниже. По сути, кривая ROC позволяет вам увидеть компромисс между истинно положительной ставкой и ложно положительной ставкой, когда вы изменяете порог того, что вы определяете как предсказанный положительный и предсказанный отрицательный.

Шаг 5: Следующие шаги по улучшению модели

В этот момент у вас может возникнуть желание рассчитать производительность вашего тестового набора и посмотреть, как вы это сделали. Но ждать! Мы сделали много вариантов (несколько ниже), которые мы могли бы изменить и посмотреть, есть ли улучшения:

  • мы должны тратить время на получение дополнительных данных?
  • как токенизировать - стоит ли использовать stemming?
  • как векторизовать - мы должны изменить количество слов?
  • как упорядочить логистическую регрессию - стоит ли менять С или штраф?
  • какую модель использовать?

Когда я пытаюсь улучшить свои модели, я читаю много других постов и статей в блогах, чтобы увидеть, как люди решают подобные проблемы. Когда вы сделаете это, вы начнете видеть интересные способы визуализации данных, и я настоятельно рекомендую придерживаться этих методов для ваших собственных проектов.

Для проектов НЛП, использующих логистическую регрессию BOW +, мы можем составить наиболее важные слова, чтобы узнать, сможем ли мы получить какую-либо информацию. Для этого шага я позаимствовал код изхорошая статья НЛПпоInsight Data Science, Когда вы смотрите на самые важные слова, я вижу две непосредственные вещи:

  • К сожалению! Я забыл исключить пациентов, которые умерли с тех пор, как «истекший» появился в отрицательном списке. Сейчас я проигнорирую это и исправлю это ниже.
  • Есть также некоторые другие стоп-слова, которые мы, вероятно, должны удалить («должен», «если», «это», «был», «кто», «во время», «х»)

Когда мы хотим улучшить модель, мы хотим сделать это на основе данных. Вы можете потратить много времени на «догадки», которые не в конечном итоге проваливаются. Для этого рекомендуется выбрать один показатель производительности, который вы используете для принятия решений. Для этого проекта я собираюсь выбрать AUC.

По первому вопросу, приведенному выше, мы можем построить нечто, называемое кривой обучения, чтобы понять эффект добавления большего количества данных. Эндрю Нг имеет множество отличныхCourseraзанятия по обсуждению моделей с высоким смещением и моделями с высокой дисперсией.

первая кривая обучения с C = 0,0001

Мы можем видеть, что у нас есть некоторое переоснащение, но добавление большего количества данных, вероятно, не приведет к кардинальному изменению AUC для набора проверки. Это полезно знать, потому что это означает, что мы не должны тратить месяцы на получение большего количества данных.

Некоторые простые вещи, которые мы можем сделать, это попытаться увидеть влияние некоторых наших гиперпараметров (max_featuresа такжеC). Мы могли бы выполнить поиск по сетке, но, поскольку у нас есть только 2 параметра, мы могли бы рассмотреть их отдельно и увидеть эффект.

Эффект С
Эффект max_features

Мы видим, что увеличение C и max_features приводит к тому, что модель довольно быстро переходит на новую модель. Я выбрал C = 0,0001 и max_features = 3000, где набор валидации начал выходить на плато.

На данный момент, вы можете попробовать несколько других вещей

  • изменить подвыборку на сверхвыборку
  • добавить stemming или лемматизацию в токенизатор
  • протестировать несколько разных моделей sci-kit learn
  • объединить все заметки вместо последней сводной выписки
  • попробуйте метод глубокого обучения, такой как LSTM
  • просмотрите информацию о том, что вы ошибаетесь

Шаг 6: доработайте свою модель и протестируйте ее

Теперь мы подгоним нашу окончательную модель с выбором гиперпараметра. Мы также исключим пациентов, которые умерли с повторной балансировкой.

rows_not_death = df_adm_notes_clean.DEATHTIME.isnull()df_adm_notes_not_death = df_adm_notes_clean.loc[rows_not_death].copy()
df_adm_notes_not_death = df_adm_notes_not_death.sample(n = len(df_adm_notes_not_death), random_state = 42)
df_adm_notes_not_death = df_adm_notes_not_death.reset_index(drop = True)# Save 30% of the data as validation and test data
df_valid_test=df_adm_notes_not_death.sample(frac=0.30,random_state=42)df_test = df_valid_test.sample(frac = 0.5, random_state = 42)
df_valid = df_valid_test.drop(df_test.index)# use the rest of the data as training data
df_train_all=df_adm_notes_not_death.drop(df_valid_test.index)assert len(df_adm_notes_not_death) == (len(df_test)+len(df_valid)+len(df_train_all)),'math didnt work'# split the training data into positive and negative
rows_pos = df_train_all.OUTPUT_LABEL == 1
df_train_pos = df_train_all.loc[rows_pos]
df_train_neg = df_train_all.loc[~rows_pos]# merge the balanced data
df_train = pd.concat([df_train_pos, df_train_neg.sample(n = len(df_train_pos), random_state = 42)],axis = 0)# shuffle the order of training samples
df_train = df_train.sample(n = len(df_train), random_state = 42).reset_index(drop = True)# preprocess the text to deal with known issues
df_train = preprocess_text(df_train)
df_valid = preprocess_text(df_valid)
df_test = preprocess_text(df_test)
my_new_stop_words = ['the','and','to','of','was','with','a','on','in','for','name', 'is','patient','s','he','at','as','or','one','she','his','her','am', 'were','you','pt','pm','by','be','had','your','this','date', 'from','there','an','that','p','are','have','has','h','but','o', 'namepattern','which','every','also','should','if','it','been','who','during', 'x']from sklearn.feature_extraction.text import CountVectorizervect = CountVectorizer(lowercase = True, max_features = 3000,
tokenizer = tokenizer_better,
stop_words = my_new_stop_words)# fit the vectorizer
vect.fit(df_train.TEXT.values)X_train_tf = vect.transform(df_train.TEXT.values)
X_valid_tf = vect.transform(df_valid.TEXT.values)
X_test_tf = vect.transform(df_test.TEXT.values)y_train = df_train.OUTPUT_LABEL
y_valid = df_valid.OUTPUT_LABEL
y_test = df_test.OUTPUT_LABELfrom sklearn.linear_model import LogisticRegressionclf=LogisticRegression(C = 0.0001, penalty = 'l2', random_state = 42)
clf.fit(X_train_tf, y_train)model = clf
y_train_preds = model.predict_proba(X_train_tf)[:,1]
y_valid_preds = model.predict_proba(X_valid_tf)[:,1]
y_test_preds = model.predict_proba(X_test_tf)[:,1]

Это приводит к следующим результатам и кривой ROC.

Вывод

Поздравляем! Вы построили простую модель НЛП (AUC = 0,70) для прогнозирования повторного госпитализации на основе сводных данных о выписке из стационара, которая лишь немного хуже, чем современный метод глубокого обучения, использующий все больничные данные (AUC = 0,75). Если у вас есть какие-либо отзывы, не стесняйтесь оставлять их ниже.

Если вы заинтересованы в углубленном изучении НЛП в сфере здравоохранения, я рекомендую прочитать статью Эрин Крейг вhttps://arxiv.org/abs/1711.10663

Ссылки

Масштабируемое и точное глубокое обучение с помощью электронных медицинских карт. Раджкомар А., Орен Е., Чен К. и др. NPJ Цифровая медицина (2018). DOI: 10.1038 / s41746–018–0029–1. Доступны на:https://www.nature.com/articles/s41746-018-0029-1

MIMIC-III, свободно доступная база данных интенсивной терапии. Джонсон А.Ю., Поллард Т.Дж., Шен Л., Леман Л., Фенг М., Гассеми М., Муди Б., Соловиц П., Цели Л.А. и Марк Р.Г. Научные данные (2016). DOI: 10.1038 / sdata.2016.35. Доступны на:http://www.nature.com/articles/sdata201635

Оригинальная статья

Footer decor

© machinelearningmastery.ru | Ссылки на оригиналы и авторов сохранены. | map