Едут дальнобойщик с напарником. Смотрят — девка голосует симпатичная. — Возьмём? — Конечно! Она сзади села и в спальник забралась. Водила сразу: — Ну–ка, порули пока, я сейчас... — и к ней в спальник. Через 10 минут выбрался, сел вперёд, закурил: — Ай, хороша девка! Напарник: — Чё, правда хороша? Ну–ка, порули пока... — и к ней в спальник. Через 10 минут девка вылезает, садится с водилой, закуривает: — Ай, хорош у тебя напарник! Водила: — Чё правда так хорош? Ну–ка, порули...
157 KiB
Лабораторная работа №4. Обучение с учителем.¶
Датасет "Набор данных для анализа и прогнозирования сердечного приступа".¶
Описание датасета¶
Проблемная область: Датасет связан с медицинской статистикой и направлен на анализ факторов, связанных с риском сердечного приступа. Это важно для прогнозирования и разработки стратегий профилактики сердечно-сосудистых заболеваний.
Актуальность: Сердечно-сосудистые заболевания являются одной из ведущих причин смертности во всем мире. Анализ данных об образе жизни, состоянии здоровья и наследственных факторах позволяет выделить ключевые предикторы, влияющие на развитие сердечно-сосудистых заболеваний. Этот датасет предоставляет инструменты для анализа таких факторов и может быть полезен в создании прогнозных моделей, направленных на снижение рисков и своевременную диагностику.
Объекты наблюдения: Каждая запись представляет собой данные о человеке, включая информацию об их состоянии здоровья, образе жизни, демографических характеристиках и наличию определенных заболеваний. Объекты наблюдений — это индивидуальные пациенты.
Атрибуты объектов:
HeartDisease
— наличие сердечного приступа (Yes/No) (целевая переменная).BMI
— индекс массы тела (Body Mass Index), числовой показатель.Smoking
— курение (Yes/No).AlcoholDrinking
— употребление алкоголя (Yes/No).Stroke
— наличие инсульта (Yes/No).PhysicalHealth
— количество дней в месяц, когда физическое здоровье было неудовлетворительным.MentalHealth
— количество дней в месяц, когда психическое здоровье было неудовлетворительным.DiffWalking
— трудности при ходьбе (Yes/No).Sex
— пол (Male/Female).AgeCategory
— возрастная категория (например, 55-59, 80 or older).Race
— расовая принадлежность (например, White, Black).Diabetic
— наличие диабета (Yes/No/No, borderline diabetes).PhysicalActivity
— физическая активность (Yes/No).GenHealth
— общее состояние здоровья (от Excellent до Poor).SleepTime
— среднее количество часов сна за сутки.Asthma
— наличие астмы (Yes/No).KidneyDisease
— наличие заболеваний почек (Yes/No).SkinCancer
— наличие кожного рака (Yes/No).
import pandas as pd
df = pd.read_csv(".//static//csv//heart_2020_cleaned.csv")
df.head()
Бизнес-цель №1. Задача классификации¶
Описание бизнес-цели¶
Цель: предсказание наличия сердечного приступа. Цель состоит в разработке модели, которая будет предсказывать, возникнет ли у человека сердечный приступ (признак HeartDisease
). Это важная задача для профилактики сердечно-сосудистых заболеваний, позволяющая выявить группы риска и назначить своевременное лечение или профилактические меры.
Достижимый уровень качества модели¶
Основные метрики для классификации:
- Accuracy (точность) – показывает долю правильно классифицированных примеров среди всех наблюдений. Легко интерпретируется, но может быть недостаточно информативной для несбалансированных классов.
- F1-Score – гармоническое среднее между точностью (precision) и полнотой (recall). Подходит для задач, где важно одновременно учитывать как ложные положительные, так и ложные отрицательные ошибки, особенно при несбалансированных классах.
- ROC AUC (Area Under the ROC Curve) – отражает способность модели различать положительные и отрицательные классы на всех уровнях порога вероятности. Значение от 0.5 (случайное угадывание) до 1.0 (идеальная модель). Полезна для оценки модели на несбалансированных данных.
- Cohen's Kappa – измеряет степень согласия между предсказаниями модели и истинными метками с учётом случайного угадывания. Значения варьируются от -1 (полное несогласие) до 1 (идеальное согласие). Удобна для оценки на несбалансированных данных.
- MCC (Matthews Correlation Coefficient) – метрика корреляции между предсказаниями и истинными классами, учитывающая все типы ошибок (TP, TN, FP, FN). Значение варьируется от -1 (полная несоответствие) до 1 (идеальное совпадение). Отлично подходит для задач с несбалансированными классами.
- Confusion Matrix (матрица ошибок) – матрица ошибок отражает распределение предсказаний модели по каждому из классов.
Выбор ориентира¶
В качестве базовой модели для оценки качества предсказаний выбрано использование самой распространённой категории целевой переменной HeartDisease
в обучающей выборке. Этот подход, известный как "most frequent class baseline", заключается в том, что модель всегда предсказывает наиболее часто встречающееся значение наличия сердечного приступа.
Разбиение набора данных на выборки¶
Выполним разбиение исходного набора на обучающую (80%) и тестовую (20%) выборки:
from typing import Tuple
import pandas as pd
from pandas import DataFrame
from sklearn.model_selection import train_test_split
def split_stratified_into_train_val_test(
df_input,
stratify_colname="y",
frac_train=0.6,
frac_val=0.15,
frac_test=0.25,
random_state=None,
) -> Tuple[DataFrame, DataFrame, DataFrame, DataFrame, DataFrame, DataFrame]:
if frac_train + frac_val + frac_test != 1.0:
raise ValueError(
"fractions %f, %f, %f do not add up to 1.0"
% (frac_train, frac_val, frac_test)
)
if stratify_colname not in df_input.columns:
raise ValueError("%s is not a column in the dataframe" % (stratify_colname))
X = df_input
y = df_input[
[stratify_colname]
]
df_train, df_temp, y_train, y_temp = train_test_split(
X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
)
if frac_val <= 0:
assert len(df_input) == len(df_train) + len(df_temp)
return df_train, pd.DataFrame(), df_temp, y_train, pd.DataFrame(), y_temp
relative_frac_test = frac_test / (frac_val + frac_test)
df_val, df_test, y_val, y_test = train_test_split(
df_temp,
y_temp,
stratify=y_temp,
test_size=relative_frac_test,
random_state=random_state,
)
assert len(df_input) == len(df_train) + len(df_val) + len(df_test)
return df_train, df_val, df_test, y_train, y_val, y_test
X_train, X_val, X_test, y_train, y_val, y_test = split_stratified_into_train_val_test(
df, stratify_colname='HeartDisease', frac_train=0.8, frac_val=0, frac_test=0.2, random_state=9
)
display("X_train", X_train)
display("y_train", y_train)
display("X_test", X_test)
display("y_test", y_test)
Построим базовую модель, описанную выше, и оценим ее метрики Accuracy и F1-Score:
from sklearn.metrics import accuracy_score, f1_score
# Определяем самый частый класс
most_frequent_class = y_train.mode().values[0][0]
print(f"Самый частый класс: {most_frequent_class}")
# Вычисляем предсказания базовой модели (все предсказания равны самому частому классу)
baseline_predictions: list[str] = [most_frequent_class] * len(y_test)
# Оцениваем базовую модель
print('Baseline Accuracy:', accuracy_score(y_test, baseline_predictions))
print('Baseline F1:', f1_score(y_test, baseline_predictions, average='weighted'))
# Унитарное кодирование для целевого признака
y_train = y_train['HeartDisease'].map({'Yes': 1, 'No': 0})
y_test = y_test['HeartDisease'].map({'Yes': 1, 'No': 0})
Выбор моделей обучения¶
Для обучения были выбраны следующие модели:
- Случайный лес (Random Forest): Ансамблевая модель, которая использует множество решающих деревьев. Она хорошо справляется с нелинейными зависимостями и шумом в данных, а также обладает устойчивостью к переобучению.
- Логистическая регрессия (Logistic Regression): Статистический метод для бинарной классификации, который моделирует зависимость между целевой переменной и независимыми признаками, используя логистическую функцию. Она проста в интерпретации и быстра в обучении.
- Метод ближайших соседей (KNN): Алгоритм классификации, который предсказывает класс на основе ближайших k обучающих примеров. KNN интуитивно понятен и не требует обучения, но может быть медленным на больших данных и чувствительным к выбору параметров.
Построение конвейера¶
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# Разделение признаков на числовые и категориальные
num_columns = [
column
for column in df.columns
if df[column].dtype != "object"
]
cat_columns = [
column
for column in df.columns
if df[column].dtype == "object"
]
# Числовая обработка: заполнение пропусков медианой и стандартизация
num_imputer = SimpleImputer(strategy="median")
num_scaler = StandardScaler()
preprocessing_num = Pipeline(
[
("imputer", num_imputer),
("scaler", num_scaler),
]
)
# Категориальная обработка: заполнение пропусков значением "unknown" и кодирование
cat_imputer = SimpleImputer(strategy="constant", fill_value="unknown")
cat_encoder = OneHotEncoder(handle_unknown="ignore", sparse_output=False, drop="first")
preprocessing_cat = Pipeline(
[
("imputer", cat_imputer),
("encoder", cat_encoder),
]
)
# Общий конвейер обработки признаков
features_preprocessing = ColumnTransformer(
verbose_feature_names_out=False,
transformers=[
("prepocessing_num", preprocessing_num, num_columns),
("prepocessing_cat", preprocessing_cat, cat_columns),
],
remainder="passthrough"
)
# Итоговый конвейер
pipeline_end = Pipeline(
[
("features_preprocessing", features_preprocessing),
]
)
Использование конвейера на тренировочных данных¶
preprocessing_result = pipeline_end.fit_transform(X_train)
preprocessed_df = pd.DataFrame(
preprocessing_result,
columns=pipeline_end.get_feature_names_out(),
)
preprocessed_df
Обучение моделей¶
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn import metrics
# Оценка качества различных моделей на основе метрик
def evaluate_models(models,
pipeline_end: Pipeline,
X_train: DataFrame, y_train,
X_test: DataFrame, y_test):
results = {}
for model_name, model in models.items():
# Создание конвейера для текущей модели
model_pipeline = Pipeline(
[
("pipeline", pipeline_end),
("model", model),
]
)
# Обучение модели
model_pipeline.fit(X_train, y_train)
# Предсказание для обучающей и тестовой выборки
y_train_predict = model_pipeline.predict(X_train)
y_test_predict = model_pipeline.predict(X_test)
# Вычисление метрик для текущей модели
metrics_dict = {
"Precision_train": metrics.precision_score(y_train, y_train_predict),
"Precision_test": metrics.precision_score(y_test, y_test_predict),
"Recall_train": metrics.recall_score(y_train, y_train_predict),
"Recall_test": metrics.recall_score(y_test, y_test_predict),
"Accuracy_train": metrics.accuracy_score(y_train, y_train_predict),
"Accuracy_test": metrics.accuracy_score(y_test, y_test_predict),
"F1_train": metrics.f1_score(y_train, y_train_predict),
"F1_test": metrics.f1_score(y_test, y_test_predict),
"ROC_AUC_test": metrics.roc_auc_score(y_test, y_test_predict),
"Cohen_kappa_test": metrics.cohen_kappa_score(y_test, y_test_predict),
"MCC_test": metrics.matthews_corrcoef(y_test, y_test_predict),
"Confusion_matrix": metrics.confusion_matrix(y_test, y_test_predict),
}
# Сохранение результатов
results[model_name] = metrics_dict
return results
# Выбранные модели для классификации
models_classification = {
"RandomForestClassifier": RandomForestClassifier(random_state=42),
"LogisticRegression": LogisticRegression(max_iter=1000),
"KNN": KNeighborsClassifier(),
}
results = evaluate_models(models_classification,
pipeline_end,
X_train, y_train,
X_test, y_test)
# Вывод результатов
for model_name, metrics_dict in results.items():
print(f"Модель: {model_name}")
for metric_name, value in metrics_dict.items():
print(f"\t{metric_name}: {value}")
print()
Результаты:
Случайный лес (Random Forest):
- Метрики:
- Precision (обучение): 1.0
- Precision (тест): 1.0
- Recall (обучение): 1.0
- Recall (тест): 1.0
- Accuracy (обучение): 1.0
- Accuracy (тест): 1.0
- F1 Score (обучение): 1.0
- F1 Score (тест): 1.0
- ROC AUC (тест): 1.0
- Cohen Kappa (тест): 1.0
- MCC (тест): 1.0
- Confusion Matrix (тест):
[[58484 0] [ 0 5475]]
- Вывод: модель продемонстрировала идеальные результаты как на обучающей, так и на тестовой выборке. Все метрики (Precision, Recall, Accuracy, F1 Score, ROC AUC и др.) равны 1.0, что свидетельствует о 100%-й точности классификации. Вероятно, модель переобучилась, так как такие результаты практически невозможно достичь на реальных данных. Необходимо проверить данные, наличие утечек информации (например, коррелирующих с целевой переменной признаков), а также параметры модели.
- Метрики:
Логистическая регрессия (Logistic Regression):
- Метрики:
- Precision (обучение): 1.0
- Precision (тест): 1.0
- Recall (обучение): 1.0
- Recall (тест): 1.0
- Accuracy (обучение): 1.0
- Accuracy (тест): 1.0
- F1 Score (обучение): 1.0
- F1 Score (тест): 1.0
- ROC AUC (тест): 1.0
- Cohen Kappa (тест): 1.0
- MCC (тест): 1.0
- Confusion Matrix (тест):
[[58484 0] [ 0 5475]]
- Вывод: аналогично Random Forest, результаты выглядят идеальными: все метрики равны 1.0, включая ROC AUC, что предполагает идеальную работу модели. Здесь также есть признаки переобучения или утечки данных. Необходимо пересмотреть подготовку данных и проверить конвейер обработки (особенно этапы предобработки).
- Метрики:
Метод ближайших соседей (KNN):
- Метрики:
- Precision (обучение): 0.999
- Precision (тест): 0.993
- Recall (обучение): 0.823
- Recall (тест): 0.746
- Accuracy (обучение): 0.985
- Accuracy (тест): 0.978
- F1 Score (обучение): 0.902
- F1 Score (тест): 0.852
- ROC AUC (тест): 0.872
- Cohen Kappa (тест): 0.840
- MCC (тест): 0.850
- Confusion Matrix (тест):
[[58457 27] [ 1391 4084]]
- Вывод: модель KNN выглядит наиболее реалистичной и стабильной, но уступает случайному лесу и логистической регрессии в точности. Эта модель является хорошей точкой отсчета для дальнейших экспериментов.
- Метрики:
Матрица неточностей¶
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt
from math import ceil
_, ax = plt.subplots(ceil(len(models_classification) / 2), 2, figsize=(12, 10), sharex=False, sharey=False)
for index, key in enumerate(models_classification.keys()):
c_matrix = results[key]["Confusion_matrix"]
disp = ConfusionMatrixDisplay(
confusion_matrix=c_matrix, display_labels=["Yes", "No"]
).plot(ax=ax.flat[index])
disp.ax_.set_title(key)
plt.subplots_adjust(top=1, bottom=0, hspace=0.4, wspace=0.1)
plt.show()
Подбор гиперпараметров¶
from sklearn.model_selection import GridSearchCV
# Создание конвейера
pipeline = Pipeline([
("processing", pipeline_end),
("model", RandomForestClassifier(random_state=42))
])
# Установка параметров для поиска по сетке
param_grid = {
"model__n_estimators": [10, 50, 100],
"model__max_features": ["sqrt", "log2"],
"model__max_depth": [5, 7, 10],
"model__criterion": ["gini", "entropy"],
}
# Подбор гиперпараметров с помощью поиска по сетке
grid_search = GridSearchCV(estimator=pipeline,
param_grid=param_grid,
n_jobs=-1)
# Обучение модели на тренировочных данных
grid_search.fit(X_train, y_train)
# Результаты подбора гиперпараметров
print("Лучшие параметры:", grid_search.best_params_)
Сравнение наборов гиперпараметров¶
# Обучение модели со старыми гипермараметрами
pipeline.fit(X_train, y_train)
# Предсказание для обучающей и тестовой выборки
y_train_predict = pipeline.predict(X_train)
y_test_predict = pipeline.predict(X_test)
# Вычисление метрик для модели со старыми гипермараметрами
base_model_metrics = {
"Precision_train": metrics.precision_score(y_train, y_train_predict),
"Precision_test": metrics.precision_score(y_test, y_test_predict),
"Recall_train": metrics.recall_score(y_train, y_train_predict),
"Recall_test": metrics.recall_score(y_test, y_test_predict),
"Accuracy_train": metrics.accuracy_score(y_train, y_train_predict),
"Accuracy_test": metrics.accuracy_score(y_test, y_test_predict),
"F1_train": metrics.f1_score(y_train, y_train_predict),
"F1_test": metrics.f1_score(y_test, y_test_predict),
"ROC_AUC_test": metrics.roc_auc_score(y_test, y_test_predict),
"Cohen_kappa_test": metrics.cohen_kappa_score(y_test, y_test_predict),
"MCC_test": metrics.matthews_corrcoef(y_test, y_test_predict),
"Confusion_matrix": metrics.confusion_matrix(y_test, y_test_predict),
}
# Модель с новыми гипермараметрами
optimized_model = RandomForestClassifier(
random_state=42,
criterion="gini",
max_depth=5,
max_features="sqrt",
n_estimators=10,
)
# Создание конвейера для модели с новыми гипермараметрами
optimized_model_pipeline = Pipeline(
[
("pipeline", pipeline_end),
("model", optimized_model),
]
)
# Обучение модели с новыми гипермараметрами
optimized_model_pipeline.fit(X_train, y_train)
# Предсказание для обучающей и тестовой выборки
y_train_predict = optimized_model_pipeline.predict(X_train)
y_test_predict = optimized_model_pipeline.predict(X_test)
# Вычисление метрик для модели с новыми гипермараметрами
optimized_model_metrics = {
"Precision_train": metrics.precision_score(y_train, y_train_predict),
"Precision_test": metrics.precision_score(y_test, y_test_predict),
"Recall_train": metrics.recall_score(y_train, y_train_predict),
"Recall_test": metrics.recall_score(y_test, y_test_predict),
"Accuracy_train": metrics.accuracy_score(y_train, y_train_predict),
"Accuracy_test": metrics.accuracy_score(y_test, y_test_predict),
"F1_train": metrics.f1_score(y_train, y_train_predict),
"F1_test": metrics.f1_score(y_test, y_test_predict),
"ROC_AUC_test": metrics.roc_auc_score(y_test, y_test_predict),
"Cohen_kappa_test": metrics.cohen_kappa_score(y_test, y_test_predict),
"MCC_test": metrics.matthews_corrcoef(y_test, y_test_predict),
"Confusion_matrix": metrics.confusion_matrix(y_test, y_test_predict),
}
# Вывод информации
print('Стоковая модель:')
for metric_name, value in base_model_metrics.items():
print(f"\t{metric_name}: {value}")
print('\nОптимизированная модель:')
for metric_name, value in optimized_model_metrics.items():
print(f"\t{metric_name}: {value}")