AIM-PIbd-32-Shabunov-O-A/lab_4/lab4.ipynb
olshab 044b414ad1 Вторая мировая война. Немецкий концлагерь...
Вторая мировая война. Немецкий концлагерь. Пленных казнят в газовых камерах. Через весь лагерь бежит немецкий офицер, подбегает к шестой камере. Заключенных уже завели в неё и закрывают дверь. Офицер успевает засунуть ногу между дверью и косяком двери, и спрашивает:
— Молдаване?
— Угу.
— Плитку кладете?
— Кладем.
— Положить плитку в ванной сколько за метр возьмете?
— Да, пять рейхсмарок, пожалуй, возьмем.
— Давайте за три?
— Ногу убери.
2024-12-14 11:55:47 +04:00

247 KiB
Raw Blame History

Лабораторная работа №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).
In [2]:
import pandas as pd
df = pd.read_csv(".//static//csv//heart_2020_cleaned.csv")
df.head()
Out[2]:
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
0 No 16.60 Yes No No 3.0 30.0 No Female 55-59 White Yes Yes Very good 5.0 Yes No Yes
1 No 20.34 No No Yes 0.0 0.0 No Female 80 or older White No Yes Very good 7.0 No No No
2 No 26.58 Yes No No 20.0 30.0 No Male 65-69 White Yes Yes Fair 8.0 Yes No No
3 No 24.21 No No No 0.0 0.0 No Female 75-79 White No No Good 6.0 No No Yes
4 No 23.71 No No No 28.0 0.0 Yes Female 40-44 White No Yes Very good 8.0 No No No

Бизнес-цель №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%) выборки:

In [3]:
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)
'X_train'
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
249455 No 65.00 No No No 3.0 0.0 No Female 30-34 White No Yes Good 7.0 Yes No No
14270 No 31.89 No No No 0.0 0.0 No Female 55-59 Hispanic No Yes Good 8.0 No No No
163088 No 24.41 No No No 0.0 5.0 No Male 40-44 White No Yes Very good 7.0 No No No
136626 Yes 36.86 Yes No No 30.0 0.0 Yes Male 65-69 White No, borderline diabetes No Good 8.0 No No No
265773 No 35.15 Yes No No 2.0 0.0 No Male 70-74 White No Yes Good 7.0 No Yes No
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
193686 No 30.43 Yes No No 0.0 0.0 No Male 55-59 White No Yes Excellent 7.0 No No No
207316 No 33.66 Yes No No 0.0 0.0 No Female 55-59 White No Yes Very good 6.0 No No No
229094 No 38.95 No No No 0.0 0.0 No Male 60-64 White Yes Yes Good 7.0 No No No
148788 No 35.44 Yes No No 0.0 0.0 No Male 70-74 White No No Very good 6.0 No No No
35742 No 27.26 Yes No No 0.0 0.0 No Male 35-39 White No Yes Very good 8.0 No No No

255836 rows × 18 columns

'y_train'
HeartDisease
249455 No
14270 No
163088 No
136626 Yes
265773 No
... ...
193686 No
207316 No
229094 No
148788 No
35742 No

255836 rows × 1 columns

'X_test'
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
215485 No 32.89 No No No 10.0 5.0 Yes Male 45-49 White Yes No Good 6.0 Yes No No
150930 No 33.00 Yes No No 0.0 0.0 No Male 50-54 Black No No Fair 8.0 No No No
305511 No 39.16 Yes No No 0.0 0.0 Yes Female 75-79 White Yes Yes Very good 7.0 No No No
284576 No 28.89 Yes No No 0.0 0.0 No Male 65-69 White Yes Yes Very good 7.0 No No No
170107 No 33.96 No No No 0.0 0.0 No Female 60-64 White No Yes Very good 7.0 No No No
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
318712 No 34.70 Yes No No 30.0 0.0 No Male 25-29 Hispanic No Yes Excellent 8.0 Yes No No
169792 No 32.61 Yes No No 7.0 0.0 No Female 60-64 Hispanic No Yes Fair 7.0 No No No
19564 No 25.09 Yes No No 0.0 2.0 No Male 60-64 Black No Yes Very good 8.0 No No No
74293 No 21.29 Yes Yes No 0.0 0.0 Yes Male 60-64 White Yes Yes Good 8.0 No No No
284877 Yes 23.30 No No No 0.0 0.0 No Female 80 or older White No Yes Good 6.0 No No No

63959 rows × 18 columns

'y_test'
HeartDisease
215485 No
150930 No
305511 No
284576 No
170107 No
... ...
318712 No
169792 No
19564 No
74293 No
284877 Yes

63959 rows × 1 columns

Построим базовую модель, описанную выше, и оценим ее метрики Accuracy и F1-Score:

In [5]:
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})
Самый частый класс: No
Baseline Accuracy: 0.9143982864022264
Baseline F1: 0.8735112563715003

Выбор моделей обучения

Для обучения были выбраны следующие модели:

  1. Случайный лес (Random Forest): Ансамблевая модель, которая использует множество решающих деревьев. Она хорошо справляется с нелинейными зависимостями и шумом в данных, а также обладает устойчивостью к переобучению.
  2. Логистическая регрессия (Logistic Regression): Статистический метод для бинарной классификации, который моделирует зависимость между целевой переменной и независимыми признаками, используя логистическую функцию. Она проста в интерпретации и быстра в обучении.
  3. Метод ближайших соседей (KNN): Алгоритм классификации, который предсказывает класс на основе ближайших k обучающих примеров. KNN интуитивно понятен и не требует обучения, но может быть медленным на больших данных и чувствительным к выбору параметров.

Построение конвейера

In [4]:
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),
    ]
)

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

In [5]:
preprocessing_result = pipeline_end.fit_transform(X_train)
preprocessed_df = pd.DataFrame(
    preprocessing_result,
    columns=pipeline_end.get_feature_names_out(),
)

preprocessed_df
Out[5]:
BMI PhysicalHealth MentalHealth SleepTime HeartDisease_Yes Smoking_Yes AlcoholDrinking_Yes Stroke_Yes DiffWalking_Yes Sex_Male ... Diabetic_Yes Diabetic_Yes (during pregnancy) PhysicalActivity_Yes GenHealth_Fair GenHealth_Good GenHealth_Poor GenHealth_Very good Asthma_Yes KidneyDisease_Yes SkinCancer_Yes
0 5.773647 -0.046285 -0.489254 -0.065882 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
1 0.561206 -0.424023 -0.489254 0.630754 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
2 -0.616356 -0.424023 0.140196 -0.065882 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
3 1.343623 3.353355 -0.489254 0.630754 1.0 1.0 0.0 0.0 1.0 1.0 ... 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
4 1.074421 -0.172198 -0.489254 -0.065882 0.0 1.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
255831 0.331361 -0.424023 -0.489254 -0.065882 0.0 1.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
255832 0.839853 -0.424023 -0.489254 -0.762519 0.0 1.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
255833 1.672648 -0.424023 -0.489254 -0.065882 0.0 0.0 0.0 0.0 0.0 1.0 ... 1.0 0.0 1.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0
255834 1.120075 -0.424023 -0.489254 -0.762519 0.0 1.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
255835 -0.167686 -0.424023 -0.489254 0.630754 0.0 1.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0

255836 rows × 38 columns

Обучение моделей

In [9]:
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()
Модель: RandomForestClassifier
	Precision_train: 1.0
	Precision_test: 1.0
	Recall_train: 1.0
	Recall_test: 1.0
	Accuracy_train: 1.0
	Accuracy_test: 1.0
	F1_train: 1.0
	F1_test: 1.0
	ROC_AUC_test: 1.0
	Cohen_kappa_test: 1.0
	MCC_test: 1.0
	Confusion_matrix: [[58484     0]
 [    0  5475]]

Модель: LogisticRegression
	Precision_train: 1.0
	Precision_test: 1.0
	Recall_train: 1.0
	Recall_test: 1.0
	Accuracy_train: 1.0
	Accuracy_test: 1.0
	F1_train: 1.0
	F1_test: 1.0
	ROC_AUC_test: 1.0
	Cohen_kappa_test: 1.0
	MCC_test: 1.0
	Confusion_matrix: [[58484     0]
 [    0  5475]]

Модель: KNN
	Precision_train: 0.9985596365852307
	Precision_test: 0.9934322549258088
	Recall_train: 0.8231345328340488
	Recall_test: 0.7459360730593607
	Accuracy_train: 0.9847597679763599
	Accuracy_test: 0.9778295470535812
	F1_train: 0.9024005607149115
	F1_test: 0.8520759440851241
	ROC_AUC_test: 0.872737204165273
	Cohen_kappa_test: 0.8403545562876147
	MCC_test: 0.8504421479558301
	Confusion_matrix: [[58457    27]
 [ 1391  4084]]

Результаты:

  1. Случайный лес (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%-й точности классификации. Вероятно, модель переобучилась, так как такие результаты практически невозможно достичь на реальных данных. Необходимо проверить данные, наличие утечек информации (например, коррелирующих с целевой переменной признаков), а также параметры модели.
  2. Логистическая регрессия (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, что предполагает идеальную работу модели. Здесь также есть признаки переобучения или утечки данных. Необходимо пересмотреть подготовку данных и проверить конвейер обработки (особенно этапы предобработки).
  3. Метод ближайших соседей (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 выглядит наиболее реалистичной и стабильной, но уступает случайному лесу и логистической регрессии в точности. Эта модель является хорошей точкой отсчета для дальнейших экспериментов.

Матрица неточностей

In [10]:
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()
No description has been provided for this image

Подбор гиперпараметров

In [11]:
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_)
c:\Users\Oleg\Desktop\AIM_ForLab4\lab_4\aimenv\Lib\site-packages\numpy\ma\core.py:2881: RuntimeWarning: invalid value encountered in cast
  _data = np.array(data, dtype=dtype, copy=copy,
Лучшие параметры: {'model__criterion': 'gini', 'model__max_depth': 5, 'model__max_features': 'sqrt', 'model__n_estimators': 100}

Сравнение наборов гиперпараметров

In [12]:
# Обучение модели со старыми гипермараметрами
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}")
Стоковая модель:
	Precision_train: 1.0
	Precision_test: 1.0
	Recall_train: 1.0
	Recall_test: 1.0
	Accuracy_train: 1.0
	Accuracy_test: 1.0
	F1_train: 1.0
	F1_test: 1.0
	ROC_AUC_test: 1.0
	Cohen_kappa_test: 1.0
	MCC_test: 1.0
	Confusion_matrix: [[58484     0]
 [    0  5475]]

Оптимизированная модель:
	Precision_train: 1.0
	Precision_test: 1.0
	Recall_train: 0.9995433372910768
	Recall_test: 0.9994520547945206
	Accuracy_train: 0.9999609124595444
	Accuracy_test: 0.9999530949514532
	F1_train: 0.9997716164984242
	F1_test: 0.9997259523157029
	ROC_AUC_test: 0.9997260273972604
	Cohen_kappa_test: 0.9997003049351247
	MCC_test: 0.9997003498302348
	Confusion_matrix: [[58484     0]
 [    3  5472]]

Бизнес-цель №2. Задача регрессии

Описание бизнес-цели

Цель: прогнозирование количества дней с плохим физическим здоровьем. Необходимо спрогнозировать количество дней за последний месяц, в течение которых пациент чувствовал себя физически нездоровым (признак PhysicalHealth). Эта метрика отражает общий уровень здоровья и может быть полезной для оценки влияния различных факторов на состояние пациента.

Достижимый уровень качества модели

Основные метрики для регрессии:

  • Средняя абсолютная ошибка (Mean Absolute Error, MAE) показывает среднее абсолютное отклонение между предсказанными и фактическими значениями. Легко интерпретируется, особенно в финансовых данных, где каждая ошибка в долларах имеет значение.
  • Среднеквадратичная ошибка (Mean Squared Error, MSE) показывает, насколько отклоняются прогнозы модели от истинных значений в квадрате. Подходит для оценки общего качества модели.
  • Коэффициент детерминации () указывает, какую долю дисперсии зависимой переменной объясняет модель. R² варьируется от 0 до 1 (чем ближе к 1, тем лучше).

Выбор ориентира

В качестве базовой модели для оценки качества предсказаний выбрано использование среднего значения целевого признака PhysicalHealth на обучающей выборке. Это простой и интуитивно понятный метод, который служит минимальным ориентиром для сравнения с более сложными моделями. Базовая модель помогает установить начальный уровень ошибок (MAE, MSE) и показатель качества (R²), которые сложные модели должны улучшить, чтобы оправдать своё использование.

Разбиение набора данных на выборки

Выполним разбиение исходного набора на обучающую (80%) и тестовую (20%) выборки:

In [6]:
X_df_train, X_df_val, X_df_test, y_df_train, y_df_val, y_df_test = split_stratified_into_train_val_test(
    df, stratify_colname='PhysicalHealth', frac_train=0.8, frac_val=0, frac_test=0.2, random_state=9
)

display("X_train", X_df_train)
display("y_train", y_df_train)

display("X_test", X_df_test)
display("y_test", y_df_test)
'X_train'
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
46650 No 30.90 No No No 30.0 0.0 Yes Female 70-74 White Yes No Poor 7.0 Yes No Yes
305695 No 23.75 No Yes No 0.0 0.0 No Male 45-49 White No Yes Excellent 6.0 Yes Yes No
17353 No 34.70 No No No 0.0 2.0 No Female 70-74 White No No Good 7.0 No No Yes
154614 No 26.37 Yes No No 0.0 0.0 Yes Female 80 or older Black Yes No Fair 4.0 No No No
146811 No 18.79 No No No 0.0 5.0 No Female 18-24 Other No Yes Very good 6.0 No No No
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
224078 No 24.13 Yes No No 0.0 0.0 No Female 45-49 White No No Very good 8.0 No No No
14534 No 22.32 Yes No No 0.0 0.0 No Female 50-54 White No Yes Excellent 5.0 No No No
156850 No 23.78 Yes No No 2.0 0.0 No Female 35-39 Black No Yes Very good 6.0 No No No
221285 No 26.52 No No No 0.0 0.0 No Female 65-69 Hispanic Yes Yes Good 8.0 No No No
16625 No 23.57 No No No 0.0 0.0 No Male 40-44 White No Yes Good 7.0 No No No

255836 rows × 18 columns

'y_train'
PhysicalHealth
46650 30.0
305695 0.0
17353 0.0
154614 0.0
146811 0.0
... ...
224078 0.0
14534 0.0
156850 2.0
221285 0.0
16625 0.0

255836 rows × 1 columns

'X_test'
HeartDisease BMI Smoking AlcoholDrinking Stroke PhysicalHealth MentalHealth DiffWalking Sex AgeCategory Race Diabetic PhysicalActivity GenHealth SleepTime Asthma KidneyDisease SkinCancer
146589 No 19.45 No No No 1.0 0.0 Yes Male 25-29 White No Yes Good 12.0 No No No
216017 No 26.36 No No No 0.0 0.0 No Female 40-44 White No Yes Very good 7.0 No No No
19624 No 24.59 Yes No No 0.0 0.0 Yes Male 55-59 White No Yes Good 6.0 No No No
65923 No 23.44 Yes No No 0.0 20.0 No Female 60-64 Asian No Yes Very good 6.0 No No No
63362 No 31.32 No No No 0.0 2.0 No Female 45-49 White No No Very good 6.0 Yes No No
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
252474 No 42.37 No No No 1.0 5.0 No Female 18-24 White No Yes Good 8.0 Yes No No
147913 No 32.08 Yes No No 0.0 0.0 No Male 40-44 White No Yes Excellent 8.0 No No No
244674 No 31.28 No No No 3.0 0.0 No Female 50-54 White Yes Yes Good 7.0 No No Yes
215373 No 31.65 No No No 0.0 0.0 No Male 45-49 White No Yes Very good 8.0 No No No
179461 No 27.37 No No No 0.0 0.0 No Male 65-69 White Yes No Good 7.0 No No No

63959 rows × 18 columns

'y_test'
PhysicalHealth
146589 1.0
216017 0.0
19624 0.0
65923 0.0
63362 0.0
... ...
252474 1.0
147913 0.0
244674 3.0
215373 0.0
179461 0.0

63959 rows × 1 columns

Построим базовую модель, описанную выше, и оценим ее метрики MAE, MSE и :

In [7]:
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# Вычисляем предсказания базовой модели (среднее значение целевой переменной)
baseline_predictions = [y_df_train.mean()] * len(y_df_test) # type: ignore

# Оцениваем базовую модель
print('Baseline MAE:', mean_absolute_error(y_df_test, baseline_predictions))
print('Baseline MSE:', mean_squared_error(y_df_test, baseline_predictions))
print('Baseline R²:', r2_score(y_df_test, baseline_predictions))
Baseline MAE: 5.081172924146543
Baseline MSE: 63.21384755665578
Baseline R²: -6.286438036795516e-10

Выбор моделей обучения

Для обучения были выбраны следующие модели:

  1. Случайный лес (Random Forest): Ансамблевая модель, которая использует множество решающих деревьев. Она хорошо справляется с нелинейными зависимостями и шумом в данных, а также обладает устойчивостью к переобучению.
  2. Линейная регрессия (Linear Regression): Простая модель, предполагающая линейную зависимость между признаками и целевой переменной. Она быстро обучается и предоставляет легкую интерпретацию результатов.
  3. Градиентный бустинг (Gradient Boosting): Мощная модель, создающая ансамбль деревьев, которые корректируют ошибки предыдущих. Эта модель эффективна для сложных наборов данных и обеспечивает высокую точность предсказаний.

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

Конвейер уже был построен при решении задачи классификации. Применяем готовый конвейер:

In [8]:
# Применение конвейера
preprocessing_result = pipeline_end.fit_transform(X_df_train)
preprocessed_df = pd.DataFrame(
    preprocessing_result,
    columns=pipeline_end.get_feature_names_out(),
)

preprocessed_df.head(10)
Out[8]:
BMI PhysicalHealth MentalHealth SleepTime HeartDisease_Yes Smoking_Yes AlcoholDrinking_Yes Stroke_Yes DiffWalking_Yes Sex_Male ... Diabetic_Yes Diabetic_Yes (during pregnancy) PhysicalActivity_Yes GenHealth_Fair GenHealth_Good GenHealth_Poor GenHealth_Very good Asthma_Yes KidneyDisease_Yes SkinCancer_Yes
0 0.404994 3.349099 -0.490224 -0.068438 0.0 0.0 0.0 0.0 1.0 0.0 ... 1.0 0.0 0.0 0.0 0.0 1.0 0.0 1.0 0.0 1.0
1 -0.718814 -0.424073 -0.490224 -0.765508 0.0 0.0 1.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0 1.0 0.0
2 1.002262 -0.424073 -0.238838 -0.068438 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 1.0
3 -0.307013 -0.424073 -0.490224 -2.159646 0.0 1.0 0.0 0.0 1.0 0.0 ... 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
4 -1.498407 -0.424073 0.138242 -0.765508 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
5 1.256887 1.336741 -0.490224 -0.765508 0.0 0.0 0.0 0.0 1.0 1.0 ... 1.0 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0
6 -0.506627 -0.424073 -0.490224 -0.068438 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
7 -1.690161 -0.424073 -0.490224 0.628631 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
8 -0.030384 -0.424073 -0.490224 0.628631 0.0 1.0 1.0 0.0 0.0 0.0 ... 0.0 0.0 1.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0
9 -0.167127 3.349099 1.395175 -0.068438 0.0 0.0 0.0 0.0 1.0 1.0 ... 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0

10 rows × 38 columns

Обучение моделей

In [9]:
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score


# Обучить модели
def train_models(X, y, models):
    results = {}
    
    for model_name, model in models.items():
        # Создание конвейера для текущей модели
        model_pipeline = Pipeline(
            [
                ("features_preprocessing", features_preprocessing),
                ("model", model)
            ]
        )
        
        # Обучаем модель и вычисляем кросс-валидацию
        scores = cross_val_score(model_pipeline, X, y, cv=5) # 5-кратная кросс-валидация
        
        # Вычисление метрик для текущей модели
        metrics_dict = {
            "mean_score": scores.mean(),
            "std_dev": scores.std()
        }
        
        # Сохранениерезультатов
        results[model_name] = metrics_dict
    
    return results


# Выбранные модели для регрессии
models_regression = {
    "Random Forest": RandomForestRegressor(),
    "Linear Regression": LinearRegression(),
    "Gradient Boosting": GradientBoostingRegressor(),
}

results = train_models(X_df_train, y_df_train, models_regression)

# Вывод результатов
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()
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
  return fit_method(estimator, *args, **kwargs)
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
  return fit_method(estimator, *args, **kwargs)
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
  return fit_method(estimator, *args, **kwargs)
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
  return fit_method(estimator, *args, **kwargs)
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
  return fit_method(estimator, *args, **kwargs)
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/ensemble/_gb.py:668: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)  # TODO: Is this still required?
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/ensemble/_gb.py:668: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)  # TODO: Is this still required?
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/ensemble/_gb.py:668: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)  # TODO: Is this still required?
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/ensemble/_gb.py:668: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)  # TODO: Is this still required?
/home/oleg/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/ensemble/_gb.py:668: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
  y = column_or_1d(y, warn=True)  # TODO: Is this still required?
Модель: Random Forest
	mean_score: 1.0
	std_dev: 0.0

Модель: Linear Regression
	mean_score: 1.0
	std_dev: 0.0

Модель: Gradient Boosting
	mean_score: 0.9999999324559854
	std_dev: 1.916515351322297e-08

Среднее значение и стандартное отклонение:

  1. Случайный лес (Random Forest):

    • Метрики:
      • Средний балл: 1.0
      • Стандартное отклонение: 0.0
    • Вывод: модель случайного леса продемонстрировала идеальный результат с точностью 1.0 и без колебаний в результатах (ноль стандартного отклонения). Это может свидетельствовать о том, что модель хорошо справилась с задачей и достаточно стабильна. Однако стоит учитывать, что подобные результаты могут быть признаком переобучения, так как оценка проводилась на обучающих данных.
  2. Линейная регрессия (Linear Regression):

    • Метрики:
      • Средний балл: 1.0
      • Стандартное отклонение: 0.0
    • Вывод: линейная регрессия также показала идеальный результат с точностью 1.0 и нулевым отклонением. Это говорит о том, что линейная модель очень хорошо подошла для данной задачи, но также важно проверить, не произошел ли случайный подбор данных, что привело к переобучению модели.
  3. Градиентный бустинг (Gradient Boosting):

    • Метрики:
      • Средний балл: 0.999
      • Стандартное отклонение: 0.0
    • Вывод: Градиентный бустинг показал практически идеальный результат, также с нулевым стандартным отклонением. Это подтверждает высокую стабильность модели, но она немного уступает случайному лесу по точности. В целом, модель демонстрирует отличные результаты, что может указывать на ее высокую способность к обобщению.

Расчет метрик

In [10]:
import numpy as np

from sklearn import metrics


# Оценка качества различных моделей на основе метрик
def evaluate_models(models,
                    pipeline_end, 
                    X_train, y_train, 
                    X_test, 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 = {
            "MAE_train": metrics.mean_absolute_error(y_train, y_train_predict),
            "MAE_test": metrics.mean_absolute_error(y_test, y_test_predict),
            "MSE_train": metrics.mean_squared_error(y_train, y_train_predict),
            "MSE_test": metrics.mean_squared_error(y_test, y_test_predict),
            "R2_train": metrics.r2_score(y_train, y_train_predict),
            "R2_test": metrics.r2_score(y_test, y_test_predict),
            "STD_train": np.std(y_train - y_train_predict),
            "STD_test": np.std(y_test - y_test_predict),
        }

        # Сохранение результатов
        results[model_name] = metrics_dict
    
    return results


y_train = np.ravel(y_df_train)  
y_test = np.ravel(y_df_test) 

results = evaluate_models(models_regression,
        pipeline_end,
        X_df_train, y_train,
        X_df_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
	MAE_train: 0.0
	MAE_test: 0.0
	MSE_train: 0.0
	MSE_test: 0.0
	R2_train: 1.0
	R2_test: 1.0
	STD_train: 0.0
	STD_test: 0.0

Модель: Linear Regression
	MAE_train: 1.194371035153155e-14
	MAE_test: 1.1909445826766327e-14
	MSE_train: 1.901081790225907e-28
	MSE_test: 1.8951168132152725e-28
	R2_train: 1.0
	R2_test: 1.0
	STD_train: 9.090236366489451e-15
	STD_test: 9.090299369484082e-15

Модель: Gradient Boosting
	MAE_train: 0.00030786687422158955
	MAE_test: 0.00030731279564540775
	MSE_train: 4.381537207145074e-06
	MSE_test: 4.342684206551716e-06
	R2_train: 0.9999999306897712
	R2_test: 0.9999999313016945
	STD_train: 0.0020932121744211872
	STD_test: 0.0020839106254309228

Результаты:

  1. Случайный лес (Random Forest):

    • Метрики:
      • MAE (обучение): 0.0
      • MAE (тест): 0.0
      • MSE (обучение): 0.0
      • MSE (тест): 0.0
      • R² (обучение): 1.0
      • R² (тест): 1.0
      • STD (обучение): 0.0
      • STD (тест): 0.0
    • Вывод: модель случайного леса продемонстрировала абсолютно идеальные результаты как на обучающих, так и на тестовых данных, с нулевыми значениями ошибок и максимально возможным значением R². Эти показатели указывают на крайне высокую точность модели и её способность к обобщению. Однако, важно проверить на других наборах данных, так как такие результаты могут быть признаком переобучения, если тестовый набор данных не был независим от обучающего.
  2. Линейная регрессия (Linear Regression):

    • Метрики:
      • MAE (обучение): 1.19e-14
      • MAE (тест): 1.19e-14
      • MSE (обучение): 1.90e-28
      • MSE (тест): 1.86e-28
      • R² (обучение): 1.0
      • R² (тест): 1.0
      • STD (обучение): 9.09e-15
      • STD (тест): 9.09e-15
    • Вывод: линейная регрессия также показала выдающиеся результаты с нулевыми ошибками и максимальным R², что может свидетельствовать о её идеальной подгонке под данные. Однако крайне низкие значения ошибок и стандартного отклонения могут указывать на переобучение модели, особенно если она идеально подогнана под обучающие данные. Это значит, что такая модель может не работать хорошо на новых данных, если она слишком специфична для текущего набора.
  3. Градиентный бустинг (Gradient Boosting):

    • Метрики:
      • MAE (обучение): 0.0
      • MAE (тест): 0.0
      • MSE (обучение): 4.38e-06
      • MSE (тест): 4.34e-06
      • R² (обучение): 1.0
      • R² (тест): 1.0
      • STD (обучение): 0.002
      • STD (тест): 0.002
    • Вывод: градиентный бустинг показал отличные результаты, с минимальными ошибками и максимально возможным R², что указывает на высокую точность модели. Небольшое стандартное отклонение (около 0.002) свидетельствует о стабильности модели и её устойчивости к изменениям в данных. Это хороший показатель для модели, так как она не перегружена шумом и демонстрирует надежность на тестовых данных.

Подбор гиперпараметров

In [11]:
from sklearn.model_selection import GridSearchCV


# Применение конвейера к данным
X_train_processing_result = pipeline_end.fit_transform(X_df_train)
X_test_processing_result = pipeline_end.transform(X_df_test)

# Создание и настройка модели случайного леса
model = RandomForestRegressor()

# Установка параметров для поиска по сетке
param_grid = {
    'n_estimators': [50, 100, 200],  # Количество деревьев
    'max_depth': [None, 10, 20, 30], # Максимальная глубина дерева
    'min_samples_split': [2, 5, 10]  # Минимальное количество образцов для разбиения узла
}

# Подбор гиперпараметров с помощью поиска по сетке
grid_search = GridSearchCV(estimator=model, 
                           param_grid=param_grid,
                           scoring='neg_mean_squared_error', cv=3, n_jobs=-1, verbose=2)

# Обучение модели на тренировочных данных
grid_search.fit(X_train_processing_result, y_train)

# Результаты подбора гиперпараметров
print("Лучшие параметры:", grid_search.best_params_)
# Меняем знак, так как берем отрицательное значение среднеквадратичной ошибки
print("Лучший результат (MSE):", -grid_search.best_score_)
Fitting 3 folds for each of 36 candidates, totalling 108 fits
[CV] END max_depth=None, min_samples_split=5, n_estimators=50; total time= 1.5min
[CV] END max_depth=None, min_samples_split=5, n_estimators=50; total time= 1.5min
[CV] END max_depth=None, min_samples_split=2, n_estimators=50; total time= 1.5min
[CV] END max_depth=None, min_samples_split=2, n_estimators=50; total time= 1.6min
[CV] END max_depth=None, min_samples_split=2, n_estimators=50; total time= 1.6min
[CV] END max_depth=None, min_samples_split=5, n_estimators=50; total time= 1.6min
[CV] END max_depth=None, min_samples_split=5, n_estimators=100; total time= 3.0min
[CV] END max_depth=None, min_samples_split=2, n_estimators=100; total time= 3.0min
[CV] END max_depth=None, min_samples_split=2, n_estimators=100; total time= 3.1min
[CV] END max_depth=None, min_samples_split=10, n_estimators=50; total time= 1.5min
[CV] END max_depth=None, min_samples_split=2, n_estimators=100; total time= 3.1min
[CV] END max_depth=None, min_samples_split=5, n_estimators=100; total time= 3.1min
[CV] END max_depth=None, min_samples_split=5, n_estimators=100; total time= 3.2min
[CV] END max_depth=None, min_samples_split=10, n_estimators=50; total time= 1.6min
[CV] END max_depth=None, min_samples_split=10, n_estimators=50; total time= 1.6min
[CV] END .max_depth=10, min_samples_split=2, n_estimators=50; total time= 1.7min
[CV] END max_depth=None, min_samples_split=10, n_estimators=100; total time= 3.2min
[CV] END .max_depth=10, min_samples_split=2, n_estimators=50; total time= 1.7min
[CV] END .max_depth=10, min_samples_split=2, n_estimators=50; total time= 1.8min
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[11], line 24
     19 grid_search = GridSearchCV(estimator=model, 
     20                            param_grid=param_grid,
     21                            scoring='neg_mean_squared_error', cv=3, n_jobs=-1, verbose=2)
     23 # Обучение модели на тренировочных данных
---> 24 grid_search.fit(X_train_processing_result, y_train)
     26 # Результаты подбора гиперпараметров
     27 print("Лучшие параметры:", grid_search.best_params_)

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/base.py:1473, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs)
   1466     estimator._validate_params()
   1468 with config_context(
   1469     skip_parameter_validation=(
   1470         prefer_skip_nested_validation or global_skip_validation
   1471     )
   1472 ):
-> 1473     return fit_method(estimator, *args, **kwargs)

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1019, in BaseSearchCV.fit(self, X, y, **params)
   1013     results = self._format_results(
   1014         all_candidate_params, n_splits, all_out, all_more_results
   1015     )
   1017     return results
-> 1019 self._run_search(evaluate_candidates)
   1021 # multimetric is determined here because in the case of a callable
   1022 # self.scoring the return type is only known after calling
   1023 first_test_score = all_out[0]["test_scores"]

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1573, in GridSearchCV._run_search(self, evaluate_candidates)
   1571 def _run_search(self, evaluate_candidates):
   1572     """Search all candidates in param_grid"""
-> 1573     evaluate_candidates(ParameterGrid(self.param_grid))

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:965, in BaseSearchCV.fit.<locals>.evaluate_candidates(candidate_params, cv, more_results)
    957 if self.verbose > 0:
    958     print(
    959         "Fitting {0} folds for each of {1} candidates,"
    960         " totalling {2} fits".format(
    961             n_splits, n_candidates, n_candidates * n_splits
    962         )
    963     )
--> 965 out = parallel(
    966     delayed(_fit_and_score)(
    967         clone(base_estimator),
    968         X,
    969         y,
    970         train=train,
    971         test=test,
    972         parameters=parameters,
    973         split_progress=(split_idx, n_splits),
    974         candidate_progress=(cand_idx, n_candidates),
    975         **fit_and_score_kwargs,
    976     )
    977     for (cand_idx, parameters), (split_idx, (train, test)) in product(
    978         enumerate(candidate_params),
    979         enumerate(cv.split(X, y, **routed_params.splitter.split)),
    980     )
    981 )
    983 if len(out) < 1:
    984     raise ValueError(
    985         "No fits were performed. "
    986         "Was the CV iterator empty? "
    987         "Were there no candidates?"
    988     )

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/sklearn/utils/parallel.py:74, in Parallel.__call__(self, iterable)
     69 config = get_config()
     70 iterable_with_config = (
     71     (_with_config(delayed_func, config), args, kwargs)
     72     for delayed_func, args, kwargs in iterable
     73 )
---> 74 return super().__call__(iterable_with_config)

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/joblib/parallel.py:2007, in Parallel.__call__(self, iterable)
   2001 # The first item from the output is blank, but it makes the interpreter
   2002 # progress until it enters the Try/Except block of the generator and
   2003 # reaches the first `yield` statement. This starts the asynchronous
   2004 # dispatch of the tasks to the workers.
   2005 next(output)
-> 2007 return output if self.return_generator else list(output)

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/joblib/parallel.py:1650, in Parallel._get_outputs(self, iterator, pre_dispatch)
   1647     yield
   1649     with self._backend.retrieval_context():
-> 1650         yield from self._retrieve()
   1652 except GeneratorExit:
   1653     # The generator has been garbage collected before being fully
   1654     # consumed. This aborts the remaining tasks if possible and warn
   1655     # the user if necessary.
   1656     self._exception = True

File ~/aim_labs/lab_4/aimenv/lib/python3.12/site-packages/joblib/parallel.py:1762, in Parallel._retrieve(self)
   1757 # If the next job is not ready for retrieval yet, we just wait for
   1758 # async callbacks to progress.
   1759 if ((len(self._jobs) == 0) or
   1760     (self._jobs[0].get_status(
   1761         timeout=self.timeout) == TASK_PENDING)):
-> 1762     time.sleep(0.01)
   1763     continue
   1765 # We need to be careful: the job list can be filling up as
   1766 # we empty it and Python list are not thread-safe by
   1767 # default hence the use of the lock

KeyboardInterrupt: 

Сравнение наборов гиперпараметров

In [ ]:
# Установка параметров для поиска по сетке для старых значений
old_param_grid = {
    'n_estimators': [50, 100, 200],  # Количество деревьев
    'max_depth': [None, 10, 20, 30], # Максимальная глубина дерева
    'min_samples_split': [2, 5, 10]  # Минимальное количество образцов для разбиения узла
}

# Подбор гиперпараметров с помощью поиска по сетке для старых параметров
old_grid_search = GridSearchCV(estimator=model, 
                                param_grid=old_param_grid,
                                scoring='neg_mean_squared_error', cv=3, n_jobs=-1, verbose=2)

# Обучение модели на тренировочных данных
old_grid_search.fit(X_train_processing_result, y_train)

# Результаты подбора для старых параметров
old_best_params = old_grid_search.best_params_
# Меняем знак, так как берем отрицательное значение MSE
old_best_mse = -old_grid_search.best_score_


# Установка параметров для поиска по сетке для новых значений
new_param_grid = {
    'n_estimators': [50],
    'max_depth': [5],
    'min_samples_split': [10]
}

# Подбор гиперпараметров с помощью поиска по сетке для новых параметров
new_grid_search = GridSearchCV(estimator=model, 
                                param_grid=new_param_grid,
                                scoring='neg_mean_squared_error', cv=2)

# Обучение модели на тренировочных данных
new_grid_search.fit(X_train_processing_result, y_train)

# Результаты подбора для новых параметров
new_best_params = new_grid_search.best_params_
# Меняем знак, так как берем отрицательное значение MSE
new_best_mse = -new_grid_search.best_score_


# Обучение модели с лучшими параметрами для новых значений
model_best = RandomForestRegressor(**new_best_params)
model_best.fit(X_train_processing_result, y_train)

# Прогнозирование на тестовой выборке
y_pred = model_best.predict(X_test_processing_result)

# Оценка производительности модели
mse = metrics.mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)


# Вывод результатов
print("Старые параметры:", old_best_params)
print("Лучший результат (MSE) на старых параметрах:", old_best_mse)
print("\nНовые параметры:", new_best_params)
print("Лучший результат (MSE) на новых параметрах:", new_best_mse)
print("Среднеквадратическая ошибка (MSE) на тестовых данных:", mse)
print("Корень среднеквадратичной ошибки (RMSE) на тестовых данных:", rmse)

# Обучение модели с лучшими параметрами для старых значений
model_old = RandomForestRegressor(**old_best_params)
model_old.fit(X_train_processing_result, y_train)

# Прогнозирование на тестовой выборке для старых параметров
y_pred_old = model_old.predict(X_test_processing_result)

# Визуализация ошибок
plt.figure(figsize=(10, 5))
plt.plot(y_test, label='Реальные значения', marker='o', linestyle='-', color='black')
plt.plot(y_pred_old, label='Предсказанные значения (старые параметры)', marker='x', linestyle='--', color='blue')
plt.plot(y_pred, label='Предсказанные значения (новые параметры)', marker='s', linestyle='--', color='orange')
plt.xlabel('Объекты')
plt.ylabel('Значения')
plt.title('Сравнение реальных и предсказанных значений')
plt.legend()
plt.show()