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

Основные инструменты

Код и алгоритмы с открытым исходным кодом, с которыми я буду работать, написаны на Python — чрезвычайно популярном, хорошо поддерживаемом и постоянно развивающемся языке анализа данных. Самый простой способ начать работу — использовать приложение для кодирования стиля нотации Jupyter Notebook для написания кода в ячейках кода и аннотирования в текстовых ячейках. Ноутбуки Jupyter можно создавать и размещать в Интернете, что означает, что они берут на себя весь хостинг, обработку графического процессора и настройку. Все, что вы делаете, это пишете и выполняете свой код. Еще проще начать с вашей учетной записи Google, создав записную книжку Google Colab, которая позволяет вам запускать Python в браузере с простым совместным использованием записных книжек. Создали блокнот? Найдем данные для работы.

Источники данных

Машинное обучение — это форма искусственного интеллекта, которая использует данные и алгоритмы для прогнозирования результатов, когда результат неизвестен. Таким образом, ключевым входом в машинное обучение являются данные. Вместо создания собственных наборов данных для обучения моделей с переменной ответа, помеченной для событий, которые произошли с известными исходами, вы можете использовать наборы, доступные в открытом доступе из таких источников, как Kaggle, UCI, AWS Open Data или Открытые данные правительства США.

Я сосредоточен на секторе здравоохранения, и у Kaggle есть несколько отличных наборов данных. В частности, интересен набор данных Инсульт с переменной отклика Инсульт, уже помеченной в записях о событиях пациента. Эта переменная ответа понадобится нам позже для обучения моделей. Итак, сформулируем прогноз, на который мы ориентируемся: вероятно ли, что у пациента будет инсульт или нет, на основе категорийных данных из историй болезни пациентов.

Исследовательский анализ данных и предварительная обработка

Сначала с помощью блокнота Colab прочитайте набор данных и начните изучать качество данных.

import pandas as pd
stroke_df = pd.read_csv('/content/sample_data/healthcare-dataset-stroke-data.csv')
print(stroke_df.shape)
print(stroke_df.head(10))
print(stroke_df.describe().T)

Я вижу, что для переменной ИМТ имеется 201 значение NaN. Я решил вычислить значения ИМТ, поскольку у 40 пациентов, перенесших инсульт, отсутствует значение ИМТ. Медиана была выбрана вместо среднего, поскольку ИМТ имеет длинный правый хвост.

stroke_df.fillna(stroke_df.median(), inplace=True)
stroke_df

Затем набор категорийных данных преобразуется перед обработкой с помощью алгоритмов. Характеристики только с двумя значениями были преобразованы в 1 и 0 с помощью сопоставления классов: когда-либо_замужем, тип проживания, пол. Функции с более чем двумя значениями были преобразованы с использованием кодирования One-Hot Label со встроенной функцией Pandas get_dummies: work_type, smoke_status.

import pandas as pd
import numpy as np
# transform nominal variables that only have 2 values
class_mapping = {label: idx for idx, label in enumerate(np.unique(stroke_df['ever_married']))}
print(class_mapping)
stroke_df['ever_married'] = stroke_df['ever_married'].map(class_mapping)
class_mapping = {label: idx for idx, label in enumerate(np.unique(stroke_df['Residence_type']))}
print(class_mapping)
stroke_df['Residence_type'] = stroke_df['Residence_type'].map(class_mapping)
class_mapping = {label: idx for idx, label in enumerate(np.unique(stroke_df['gender']))}
print(class_mapping)
stroke_df['gender'] = stroke_df['gender'].map(class_mapping)
# transform nominal variables that have more than 2 values
stroke_df[['work_type','smoking_status']] = stroke_df[['work_type','smoking_status']].astype(str)
# concatenate the nominal variables from pd.getdummies and the ordinal variables to form the final dataset
transpose = pd.get_dummies(stroke_df[['work_type','smoking_status']])
stroke_dummies_df = pd.concat([stroke_df,transpose],axis=1)[['id','age','hypertension','heart_disease','ever_married','Residence_type','avg_glucose_level','bmi','gender','work_type_Govt_job','work_type_Never_worked','work_type_Private','work_type_Self-employed','work_type_children','smoking_status_Unknown','smoking_status_formerly smoked','smoking_status_never smoked','smoking_status_smokes','stroke']]
stroke_dummies_df

Затем я анализирую переменные на асимметрию с помощью встроенных функций Python matplotlib и seaborn.

import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style="darkgrid")
sns.histplot(data=stroke_df, x="gender", discrete=True)
plt.show()

На данный момент мы хорошо понимаем набор данных, но как узнать, какие переменные больше всего влияют на переменную ответа «инсульт»? Чтобы ответить на этот вопрос, я запускаю корреляционный анализ, чтобы определить важность функций, используя классификатор случайного леса из sklearn.ensemble. Ниже мы можем ясно видеть, что переменные возраста, среднего уровня глюкозы и ИМТ оказывают самое сильное влияние на событие инсульта.

Давайте посмотрим немного ближе на нашу переменную ответа «инсульт». В конце концов, мы пытаемся предсказать, будет ли у пациента инсульт или нет. Только 249 записей из 5109 имеют пометку штрихом, что является значительным дисбалансом классов. Если мы используем эти данные для обучения модели, дисбаланс классов, скорее всего, исказит производительность. Чтобы преодолеть это, я использовал метод обработки данных под названием SMOTE из библиотеки изучения дисбаланса. SMOTE использует ближайших соседей для интерполяции точек данных в евклидовом пространстве. Я специально использовал SMOTE для номинальных и непрерывных функций (SMOTE-NC), который создает синтетические данные для категориальных функций. После запуска SMOTE_NC данные переменной ответа были увеличены с 249 до 4869, что предоставило больше данных для запуска модели.

Разделение данных на наборы для обучения и тестирования

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

# This is what the train/test dataset looks like if we do not apply SMOTENC.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=1)
print(X.shape, y.shape, X_train.shape, y_train.shape, X_test.shape, y_test.shape)

(5109, 17) (5109,) (4087, 17) (4087,) (1022, 17) (1022,)

# We will create the train and test dataset from the X and y data after running SMOTENC
from sklearn.model_selection import train_test_split
X_smote_train, X_smote_test, y_smote_train, y_smote_test = train_test_split(X_smote, y_smote, test_size=0.2, random_state=1)
print(X.shape, y.shape, X_smote_train.shape, y_smote_train.shape, X_smote_test.shape, y_smote_test.shape)

(5109, 17) (5109,) (7776, 17) (7776,) (1944, 17) (1944,)

Мы можем видеть ожидаемый процент данных для обучающих и тестовых наборов данных с увеличенным объемом данных после запуска SMOTE-NC. Чтобы дважды щелкнуть шаблоны данных, я запускаю уменьшение размерности PCA (анализ основных компонентов). Процесс PCA определяет области максимальной дисперсии многомерных данных и проецирует на меньшее количество измерений в подпространстве. This — отличная статья, в которой подробно рассказывается об этой технике.

pca = PCA(n_components=3)
#dimensionality reduction:
X_smote_train_pca = pca.fit_transform(X_smote_train_std)
X_smote_test_pca = pca.transform(X_smote_test_std)
X_smote_train_pca_df = pd.DataFrame(X_smote_train_pca)
X_smote_train_pca_df.columns = ['PCA1', 'PCA2', 'PCA3']
x1 = X_smote_train_pca_df['PCA1']
y1 = X_smote_train_pca_df['PCA2']
z1 = X_smote_train_pca_df['PCA3']
sns.set(style= 'darkgrid')
fig = plt.figure(figsize= (16, 16))
ax = plt.axes(projection='3d')
ax.scatter3D(x1, y1, z1,
alpha=1,
c = y_smote_train,
cmap = 'cool',
depthshade=False,s=10)
ax.legend(y_smote_train)
plt.show()

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

Определение лучшего алгоритма

Если вы не знакомы с моделями машинного обучения для Python, ценным ресурсом для обзора является Scikit-Learn. Я начну запускать эти наборы данных через несколько алгоритмов классификатора, регрессии и кластеризации, чтобы найти наилучшую производительность прогнозирования. Во-первых, давайте воспользуемся базовой логистической регрессией.

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
pipe_lr = make_pipeline(StandardScaler(),
PCA(n_components=4),
LogisticRegression(random_state=1, solver='lbfgs'))
pipe_lr.fit(X_smote_train, y_smote_train)
y_smote_pred = pipe_lr.predict(X_smote_test)
print('Test accuracy: %.4f' % pipe_lr.score(X_smote_test, y_smote_test))

Точность теста: 0,7140

Модель логистической регрессии показала точность предсказания только 71%, что не так впечатляюще, как хотелось бы, но мы подозревали это по форме данных. Чтобы протестировать несколько моделей с помощью воспроизводимого конвейера, я создаю конвейер и запускаю перекрестную проверку K-Folds, чтобы оценить точность данных для каждой последующей модели. Следующие модели использовались для прохождения набора обучающих данных, оценки точности, затем прохождения набора тестовых данных и определения производительности.

Мы видим, что в этом наборе данных классификатор случайного леса работает лучше, чем регрессия, SVM и K-ближайшие соседи.

Результат и рентабельность инвестиций

Моя обученная модель Random Forest в наборе данных об инсульте SMOTE-NC может предсказать инсульт пациента 94% времени с теми же категориальными переменными, а именно с возрастом, средним уровнем глюкозы и ИМТ.

В этом наборе данных было 249 пациентов с переменной ответа Stroke = True. Следовательно, эта модель может точно определить 236 пациентов из этого набора данных, у которых будет инсульт. В литературе указывается, что средняя стоимость госпитализации по поводу инсульта составляет 20 396 долларов США на пациента (Wang et. al. 2013). С помощью этого простого алгоритма машинного обучения раннее вмешательство может помочь избежать затрат на госпитализацию, эквивалентных 4,8 млн долларов США.

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

Данные могут быть источником дохода и обеспечивать финансовую отдачу за счет прогнозов машинного обучения.

Данные — это актив. Данные — ваш друг.