AIM-PIbd-31-Afanasev-S-S/lab_4/lab4.ipynb
2024-11-16 12:52:11 +04:00

386 KiB
Raw Blame History

Начало лабораторной работы

Вариант 3: Диабет у индейцев Пима

In [46]:
import pandas as pd
from sklearn import set_config

# Установим параметры для вывода
set_config(transform_output="pandas")

random_state = 42

# Подключим датафрейм и выгрузим данные
df = pd.read_csv("C:/Users/TIGR228/Desktop/МИИ/Lab1/AIM-PIbd-31-Afanasev-S-S/static/csv/diabetes.csv")
print(df.columns)
df
Index(['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin',
       'BMI', 'DiabetesPedigreeFunction', 'Age', 'Outcome'],
      dtype='object')
Out[46]:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
0 6 148 72 35 0 33.6 0.627 50 1
1 1 85 66 29 0 26.6 0.351 31 0
2 8 183 64 0 0 23.3 0.672 32 1
3 1 89 66 23 94 28.1 0.167 21 0
4 0 137 40 35 168 43.1 2.288 33 1
... ... ... ... ... ... ... ... ... ...
763 10 101 76 48 180 32.9 0.171 63 0
764 2 122 70 27 0 36.8 0.340 27 0
765 5 121 72 23 112 26.2 0.245 30 0
766 1 126 60 0 0 30.1 0.349 47 1
767 1 93 70 31 0 30.4 0.315 23 0

768 rows × 9 columns

Бизнес-цели:

  1. Прогнозирование риска развития диабета

Описание: Классифицировать пациентов на основе их медицинских данных для определения риска развития диабета (используя целевой признак "Outcome"). Эта задача актуальна для раннего выявления диабета и разработки профилактических мер, направленных на улучшение здоровья населения.

  1. Оценка факторов, влияющих на развитие диабета

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

Определение достижимого уровня качества модели для первой задачи

Разделение набора данных на обучающую и тестовые выборки (80/20) для задачи классификации

Целевой признак -- Outcome

In [47]:
from typing import Tuple
import pandas as pd
from pandas import DataFrame
from sklearn.model_selection import train_test_split

# Устанавливаем случайное состояние
random_state = 42

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  # Contains all columns.
    y = df_input[
        [stratify_colname]
    ]  # Dataframe of just the column on which to stratify.
    # Split original dataframe into train and temp dataframes.
    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
    # Split the temp dataframe into val and test dataframes.
    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="Outcome", frac_train=0.80, frac_val=0, frac_test=0.20, random_state=random_state
)

display("X_train", X_train)
display("y_train", y_train)

display("X_test", X_test)
display("y_test", y_test)
'X_train'
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
353 1 90 62 12 43 27.2 0.580 24 0
711 5 126 78 27 22 29.6 0.439 40 0
373 2 105 58 40 94 34.9 0.225 25 0
46 1 146 56 0 0 29.7 0.564 29 0
682 0 95 64 39 105 44.6 0.366 22 0
... ... ... ... ... ... ... ... ... ...
451 2 134 70 0 0 28.9 0.542 23 1
113 4 76 62 0 0 34.0 0.391 25 0
556 1 97 70 40 0 38.1 0.218 30 0
667 10 111 70 27 0 27.5 0.141 40 1
107 4 144 58 28 140 29.5 0.287 37 0

614 rows × 9 columns

'y_train'
Outcome
353 0
711 0
373 0
46 0
682 0
... ...
451 1
113 0
556 0
667 1
107 0

614 rows × 1 columns

'X_test'
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
44 7 159 64 0 0 27.4 0.294 40 0
672 10 68 106 23 49 35.5 0.285 47 0
700 2 122 76 27 200 35.9 0.483 26 0
630 7 114 64 0 0 27.4 0.732 34 1
81 2 74 0 0 0 0.0 0.102 22 0
... ... ... ... ... ... ... ... ... ...
32 3 88 58 11 54 24.8 0.267 22 0
637 2 94 76 18 66 31.6 0.649 23 0
593 2 82 52 22 115 28.5 1.699 25 0
425 4 184 78 39 277 37.0 0.264 31 1
273 1 71 78 50 45 33.2 0.422 21 0

154 rows × 9 columns

'y_test'
Outcome
44 0
672 0
700 0
630 1
81 0
... ...
32 0
637 0
593 0
425 1
273 0

154 rows × 1 columns

Формирование конвейера для классификации данных

preprocessing_num -- конвейер для обработки числовых данных: заполнение пропущенных значений и стандартизация

preprocessing_cat -- конвейер для обработки категориальных данных: заполнение пропущенных данных и унитарное кодирование

features_preprocessing -- трансформер для предобработки признаков

features_engineering -- трансформер для конструирования признаков

drop_columns -- трансформер для удаления колонок

pipeline_end -- основной конвейер предобработки данных и конструирования признаков

In [48]:
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split

# Построение конвейеров предобработки

class DiabetesFeatures(BaseEstimator, TransformerMixin):
    def __init__(self):
        pass

    def fit(self, X, y=None):
        return self

    def transform(self, X, y=None):
        # Создание новых признаков
        X = X.copy()
        X["BMI_to_Age_ratio"] = X["BMI"] / X["Age"]
        return X

    def get_feature_names_out(self, features_in):
        # Добавление имен новых признаков
        new_features = ["BMI_to_Age_ratio"]
        return np.append(features_in, new_features, axis=0)

# Обработка числовых данных. Числовой конвейр: заполнение пропущенных значений медианой и стандартизация
preprocessing_num_class = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler())
])

preprocessing_cat_class = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False, drop='first'))
])

columns_to_drop = []
numeric_columns = ["Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin",
                   "BMI", "DiabetesPedigreeFunction", "Age"]
cat_columns = ["Outcome"]

features_preprocessing = ColumnTransformer(
    verbose_feature_names_out=False,
    transformers=[
        ("preprocessing_num", preprocessing_num_class, numeric_columns),
        ("preprocessing_cat", preprocessing_cat_class, cat_columns),
    ],
    remainder="passthrough"
)

drop_columns = ColumnTransformer(
    verbose_feature_names_out=False,
    transformers=[
        ("drop_columns", "drop", columns_to_drop),
    ],
    remainder="passthrough",
)

features_postprocessing = ColumnTransformer(
    verbose_feature_names_out=False,
    transformers=[
        ('preprocessing_cat', preprocessing_cat_class, ["Outcome"]),
    ],
    remainder="passthrough",
)

pipeline_end = Pipeline(
    [
        ("features_preprocessing", features_preprocessing),
        ("custom_features", DiabetesFeatures()),
        ("drop_columns", drop_columns),
    ]
)

Демонстрация работы конвейера

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

preprocessed_df
Out[49]:
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome_1 BMI_to_Age_ratio
353 -0.851355 -0.980131 -0.404784 -0.553973 -0.331319 -0.607678 0.310794 -0.792169 0.0 0.767107
711 0.356576 0.161444 0.465368 0.392787 -0.526398 -0.302139 -0.116439 0.561034 0.0 -0.538540
373 -0.549372 -0.504474 -0.622322 1.213312 0.142444 0.372594 -0.764862 -0.707594 0.0 -0.526564
46 -0.851355 0.795653 -0.731091 -1.311380 -0.730766 -0.289408 0.262314 -0.369293 0.0 0.783681
682 -1.153338 -0.821579 -0.296015 1.150195 0.244628 1.607482 -0.337630 -0.961320 0.0 -1.672162
... ... ... ... ... ... ... ... ... ... ...
451 -0.549372 0.415128 0.030292 -1.311380 -0.730766 -0.391255 0.195653 -0.876744 1.0 0.446259
113 0.054593 -1.424076 -0.404784 -1.311380 -0.730766 0.258017 -0.261879 -0.707594 0.0 -0.364639
556 -0.851355 -0.758158 0.030292 1.213312 -0.730766 0.779980 -0.786072 -0.284718 0.0 -2.739481
667 1.866489 -0.314212 0.030292 0.392787 -0.730766 -0.569486 -1.019383 0.561034 1.0 -1.015065
107 0.054593 0.732232 -0.622322 0.455904 0.569759 -0.314870 -0.577001 0.307308 0.0 -1.024606

614 rows × 10 columns

Формирование набора моделей для классификации

logistic -- логистическая регрессия

ridge -- гребневая регрессия

decision_tree -- дерево решений

knn -- k-ближайших соседей

naive_bayes -- наивный Байесовский классификатор

gradient_boosting -- метод градиентного бустинга (набор деревьев решений)

random_forest -- метод случайного леса (набор деревьев решений)

mlp -- многослойный персептрон (нейронная сеть)

In [50]:
from sklearn import ensemble, linear_model, naive_bayes, neighbors, neural_network, tree

# Определите random_state для воспроизводимости результатов
random_state = 42

# Определите модели машинного обучения для классификации
class_models = {
    "logistic": {"model": linear_model.LogisticRegression(random_state=random_state)},
    "ridge": {"model": linear_model.LogisticRegression(penalty="l2", class_weight="balanced", random_state=random_state)},
    "decision_tree": {
        "model": tree.DecisionTreeClassifier(max_depth=7, random_state=random_state)
    },
    "knn": {"model": neighbors.KNeighborsClassifier(n_neighbors=7)},
    "naive_bayes": {"model": naive_bayes.GaussianNB()},
    "gradient_boosting": {
        "model": ensemble.GradientBoostingClassifier(n_estimators=210, random_state=random_state)
    },
    "random_forest": {
        "model": ensemble.RandomForestClassifier(
            max_depth=11, class_weight="balanced", random_state=random_state
        )
    },
    "mlp": {
        "model": neural_network.MLPClassifier(
            hidden_layer_sizes=(7,),
            max_iter=500,
            early_stopping=True,
            random_state=random_state,
        )
    },
}

Обучение моделей на обучающем наборе данных и оценка на тестовом

In [52]:
import numpy as np
from sklearn import metrics

for model_name in class_models.keys():
    print(f"Model: {model_name}")
    model = class_models[model_name]["model"]

    model_pipeline = Pipeline([("pipeline", pipeline_end), ("model", model)])
    model_pipeline = model_pipeline.fit(X_train, y_train.values.ravel())

    y_train_predict = model_pipeline.predict(X_train)
    y_test_probs = model_pipeline.predict_proba(X_test)[:, 1]
    y_test_predict = np.where(y_test_probs > 0.5, 1, 0)

    class_models[model_name]["pipeline"] = model_pipeline
    class_models[model_name]["probs"] = y_test_probs
    class_models[model_name]["preds"] = y_test_predict

    class_models[model_name]["Precision_train"] = metrics.precision_score(
        y_train, y_train_predict
    )
    class_models[model_name]["Precision_test"] = metrics.precision_score(
        y_test, y_test_predict
    )
    class_models[model_name]["Recall_train"] = metrics.recall_score(
        y_train, y_train_predict
    )
    class_models[model_name]["Recall_test"] = metrics.recall_score(
        y_test, y_test_predict
    )
    class_models[model_name]["Accuracy_train"] = metrics.accuracy_score(
        y_train, y_train_predict
    )
    class_models[model_name]["Accuracy_test"] = metrics.accuracy_score(
        y_test, y_test_predict
    )
    class_models[model_name]["ROC_AUC_test"] = metrics.roc_auc_score(
        y_test, y_test_probs
    )
    class_models[model_name]["F1_train"] = metrics.f1_score(y_train, y_train_predict)
    class_models[model_name]["F1_test"] = metrics.f1_score(y_test, y_test_predict)
    class_models[model_name]["MCC_test"] = metrics.matthews_corrcoef(
        y_test, y_test_predict
    )
    class_models[model_name]["Cohen_kappa_test"] = metrics.cohen_kappa_score(
        y_test, y_test_predict
    )
    class_models[model_name]["Confusion_matrix"] = metrics.confusion_matrix(
        y_test, y_test_predict
    )
Model: logistic
Model: ridge
Model: decision_tree
Model: knn
Model: naive_bayes
Model: gradient_boosting
Model: random_forest
Model: mlp

Сводная таблица оценок качества для использованных моделей классификации

In [ ]:
from sklearn.metrics import ConfusionMatrixDisplay
import matplotlib.pyplot as plt

# Определите количество строк и столбцов для subplots
n_rows = int(len(class_models) / 2)
n_cols = 2

fig, ax = plt.subplots(n_rows, n_cols, figsize=(12, 10), sharex=False, sharey=False)

for index, key in enumerate(class_models.keys()):
    c_matrix = class_models[key]["Confusion_matrix"]
    disp = ConfusionMatrixDisplay(
        confusion_matrix=c_matrix, display_labels=["No Diabetes", "Diabetes"]
    ).plot(ax=ax.flat[index])
    disp.ax_.set_title(key)

# Настройте расположение subplots
plt.subplots_adjust(top=1, bottom=0, hspace=0.4, wspace=0.1)
plt.show()
No description has been provided for this image

100 - количество истинных положительных диагнозов (True Positives), где модель правильно определила объекты как "No Diabetes".

54 в некоторых моделях - количество ложных отрицательных диагнозов (False Negatives), где модель неправильно определила объекты, которые на самом деле принадлежат к классу "No Diabetes", но были отнесены к классу "Diabetes".

Исходя из значений True Positives и False Negatives, можно сказать, что модель имеет высокую точность при предсказании класса "No Diabetes". В принципе, уровень ложных отрицательных результатов в некоторых моделях (54) говорит нам о том, что существует некотрое небольшое количество примеров, которые модель пропускает.

Точность, полнота, верность (аккуратность), F-мера

In [65]:
class_metrics = pd.DataFrame.from_dict(class_models, "index")[
    [
        "Precision_train",
        "Precision_test",
        "Recall_train",
        "Recall_test",
        "Accuracy_train",
        "Accuracy_test",
        "F1_train",
        "F1_test",
    ]
]
class_metrics.sort_values(
    by="Accuracy_test", ascending=False
).style.background_gradient(
    cmap="plasma",
    low=0.3,
    high=1,
    subset=["Accuracy_train", "Accuracy_test", "F1_train", "F1_test"],
).background_gradient(
    cmap="viridis",
    low=1,
    high=0.3,
    subset=[
        "Precision_train",
        "Precision_test",
        "Recall_train",
        "Recall_test",
    ],
)
Out[65]:
  Precision_train Precision_test Recall_train Recall_test Accuracy_train Accuracy_test F1_train F1_test
logistic 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
ridge 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
decision_tree 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
naive_bayes 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
random_forest 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
gradient_boosting 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
knn 0.918367 0.777778 0.841121 0.777778 0.918567 0.844156 0.878049 0.777778
mlp 0.254237 0.238095 0.070093 0.092593 0.604235 0.577922 0.109890 0.133333

Все модели в данной выборке, а именно логистическая регрессия, ридж-регрессия, дерево решений, KNN, наивный байесовский классификатор, градиентный бустинг, случайный лес и многослойный перцептрон (MLP) демонстрируют неплохие значения по всем метрикам на обучающих и тестовых наборах данных.

Модели MLP не так эффективна по сравнению с другими, но в некоторых метриках показывают высокие результаты.

In [66]:
class_metrics = pd.DataFrame.from_dict(class_models, "index")[
    [
        "Accuracy_test",
        "F1_test",
        "ROC_AUC_test",
        "Cohen_kappa_test",
        "MCC_test",
    ]
]
class_metrics.sort_values(by="ROC_AUC_test", ascending=False).style.background_gradient(
    cmap="plasma",
    low=0.3,
    high=1,
    subset=[
        "ROC_AUC_test",
        "MCC_test",
        "Cohen_kappa_test",
    ],
).background_gradient(
    cmap="viridis",
    low=1,
    high=0.3,
    subset=[
        "Accuracy_test",
        "F1_test",
    ],
)
Out[66]:
  Accuracy_test F1_test ROC_AUC_test Cohen_kappa_test MCC_test
logistic 1.000000 1.000000 1.000000 1.000000 1.000000
ridge 1.000000 1.000000 1.000000 1.000000 1.000000
decision_tree 1.000000 1.000000 1.000000 1.000000 1.000000
naive_bayes 1.000000 1.000000 1.000000 1.000000 1.000000
random_forest 1.000000 1.000000 1.000000 1.000000 1.000000
gradient_boosting 1.000000 1.000000 1.000000 1.000000 1.000000
knn 0.844156 0.777778 0.908056 0.657778 0.657778
mlp 0.577922 0.133333 0.488148 -0.078431 -0.093728

Схожий вывод можно сделать и для следующих метрик: Accuracy, F1, ROC AUC, Cohen's Kappa и MCC. Все модели, кроме KNN и MLP, указывают на хорошо-развитую способность к выделению классов

In [67]:
best_model = str(class_metrics.sort_values(by="MCC_test", ascending=False).iloc[0].name)

display(best_model)
'logistic'

Вывод данных с ошибкой предсказания для оценки

In [73]:
preprocessing_result = pipeline_end.transform(X_test)
preprocessed_df = pd.DataFrame(
    preprocessing_result,
    columns=pipeline_end.get_feature_names_out(),
)

y_pred = class_models[best_model]["preds"]

error_index = y_test[y_test["Outcome"] != y_pred].index.tolist()
display(f"Error items count: {len(error_index)}")

error_predicted = pd.Series(y_pred, index=y_test.index).loc[error_index]
error_df = X_test.loc[error_index].copy()
error_df.insert(loc=1, column="Predicted", value=error_predicted)
error_df.sort_index()
'Error items count: 0'
Out[73]:
Pregnancies Predicted Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome

Пример использования обученной модели (конвейера) для предсказания

In [88]:
model = class_models[best_model]["pipeline"]

example_id = 163
test = pd.DataFrame(X_test.loc[example_id, :]).T
test_preprocessed = pd.DataFrame(preprocessed_df.loc[example_id, :]).T
display(test)
display(test_preprocessed)
result_proba = model.predict_proba(test)[0]
result = model.predict(test)[0]
real = int(y_test.loc[example_id].values[0])
display(f"predicted: {result} (proba: {result_proba})")
display(f"real: {real}")
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
163 2.0 100.0 64.0 23.0 0.0 29.7 0.368 21.0 0.0
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome_1 BMI_to_Age_ratio
163 -0.549372 -0.663027 -0.296015 0.140318 -0.730766 -0.289408 -0.33157 -1.045895 0.0 0.276709
'predicted: 0 (proba: [0.98965692 0.01034308])'
'real: 0'

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

In [89]:
from sklearn.model_selection import GridSearchCV

optimized_model_type = "random_forest"

random_forest_model = class_models[optimized_model_type]["pipeline"]

param_grid = {
    "model__n_estimators": [10, 50, 100],
    "model__max_features": ["sqrt", "log2"],
    "model__max_depth": [5, 7, 10],
    "model__criterion": ["gini", "entropy"],
}

gs_optomizer = GridSearchCV(
    estimator=random_forest_model, param_grid=param_grid, n_jobs=-1
)
gs_optomizer.fit(X_train, y_train.values.ravel())
gs_optomizer.best_params_
c:\Users\TIGR228\Desktop\МИИ\Lab1\AIM-PIbd-31-Afanasev-S-S\aimenv\Lib\site-packages\numpy\ma\core.py:2881: RuntimeWarning: invalid value encountered in cast
  _data = np.array(data, dtype=dtype, copy=copy,
Out[89]:
{'model__criterion': 'gini',
 'model__max_depth': 5,
 'model__max_features': 'sqrt',
 'model__n_estimators': 10}

Обучение модели с новыми гиперпараметрами

In [90]:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from sklearn import metrics
import pandas as pd


# Определяем числовые признаки
numeric_features = X_train.select_dtypes(include=['float64', 'int64']).columns.tolist()

# Установка random_state
random_state = 42

# Определение трансформера
pipeline_end = ColumnTransformer([
    ('numeric', StandardScaler(), numeric_features),
    # Добавьте другие трансформеры, если требуется
])

# Объявление модели
optimized_model = RandomForestClassifier(
    random_state=random_state,
    criterion="gini",
    max_depth=5,
    max_features="sqrt",
    n_estimators=50,
)

# Создание пайплайна с корректными шагами
result = {}

# Обучение модели
result["pipeline"] = Pipeline([
    ("pipeline", pipeline_end),
    ("model", optimized_model)
]).fit(X_train, y_train.values.ravel())

# Прогнозирование и расчет метрик
result["train_preds"] = result["pipeline"].predict(X_train)
result["probs"] = result["pipeline"].predict_proba(X_test)[:, 1]
result["preds"] = np.where(result["probs"] > 0.5, 1, 0)

# Метрики для оценки модели
result["Precision_train"] = metrics.precision_score(y_train, result["train_preds"])
result["Precision_test"] = metrics.precision_score(y_test, result["preds"])
result["Recall_train"] = metrics.recall_score(y_train, result["train_preds"])
result["Recall_test"] = metrics.recall_score(y_test, result["preds"])
result["Accuracy_train"] = metrics.accuracy_score(y_train, result["train_preds"])
result["Accuracy_test"] = metrics.accuracy_score(y_test, result["preds"])
result["ROC_AUC_test"] = metrics.roc_auc_score(y_test, result["probs"])
result["F1_train"] = metrics.f1_score(y_train, result["train_preds"])
result["F1_test"] = metrics.f1_score(y_test, result["preds"])
result["MCC_test"] = metrics.matthews_corrcoef(y_test, result["preds"])
result["Cohen_kappa_test"] = metrics.cohen_kappa_score(y_test, result["preds"])
result["Confusion_matrix"] = metrics.confusion_matrix(y_test, result["preds"])

Формирование данных для оценки старой и новой версии модели

In [91]:
optimized_metrics = pd.DataFrame(columns=list(result.keys()))
optimized_metrics.loc[len(optimized_metrics)] = pd.Series(
    data=class_models[optimized_model_type]
)
optimized_metrics.loc[len(optimized_metrics)] = pd.Series(
    data=result
)
optimized_metrics.insert(loc=0, column="Name", value=["Old", "New"])
optimized_metrics = optimized_metrics.set_index("Name")

Оценка параметров старой и новой модели

In [92]:
optimized_metrics[
    [
        "Precision_train",
        "Precision_test",
        "Recall_train",
        "Recall_test",
        "Accuracy_train",
        "Accuracy_test",
        "F1_train",
        "F1_test",
    ]
].style.background_gradient(
    cmap="plasma",
    low=0.3,
    high=1,
    subset=["Accuracy_train", "Accuracy_test", "F1_train", "F1_test"],
).background_gradient(
    cmap="viridis",
    low=1,
    high=0.3,
    subset=[
        "Precision_train",
        "Precision_test",
        "Recall_train",
        "Recall_test",
    ],
)
Out[92]:
  Precision_train Precision_test Recall_train Recall_test Accuracy_train Accuracy_test F1_train F1_test
Name                
Old 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
New 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000
In [93]:
optimized_metrics[
    [
        "Accuracy_test",
        "F1_test",
        "ROC_AUC_test",
        "Cohen_kappa_test",
        "MCC_test",
    ]
].style.background_gradient(
    cmap="plasma",
    low=0.3,
    high=1,
    subset=[
        "ROC_AUC_test",
        "MCC_test",
        "Cohen_kappa_test",
    ],
).background_gradient(
    cmap="viridis",
    low=1,
    high=0.3,
    subset=[
        "Accuracy_test",
        "F1_test",
    ],
)
Out[93]:
  Accuracy_test F1_test ROC_AUC_test Cohen_kappa_test MCC_test
Name          
Old 1.000000 1.000000 1.000000 1.000000 1.000000
New 1.000000 1.000000 1.000000 1.000000 1.000000
In [94]:
_, ax = plt.subplots(1, 2, figsize=(10, 4), sharex=False, sharey=False
)

for index in range(0, len(optimized_metrics)):
    c_matrix = optimized_metrics.iloc[index]["Confusion_matrix"]
    disp = ConfusionMatrixDisplay(
        confusion_matrix=c_matrix, display_labels=["No Diabetes", "Diabetes"]
    ).plot(ax=ax.flat[index])

plt.subplots_adjust(top=1, bottom=0, hspace=0.4, wspace=0.3)
plt.show()
No description has been provided for this image

В желтых квадрате мы наблюдаем значение 100, что обозначает количество правильно классифицированных объектов, отнесенных к классу "No Diabetes". Это свидетельствует о том, что модель успешно идентифицирует объекты этого класса, минимизируя количество ложных положительных срабатываний.

В бирюзовом квадрате значение 0 указывает на количество правильно классифицированных объектов, отнесенных к классу "Diabetes". Это является показателем не такой высокой точности модели в определении объектов данного класса.

Определение достижимого уровня качества модели для второй задачи (задача регрессии)

In [ ]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn import set_config

# Установите random_state для воспроизводимости результатов
random_state = 42
set_config(transform_output="pandas")

df = pd.read_csv("C:/Users/TIGR228/Desktop/МИИ/Lab1/AIM-PIbd-31-Afanasev-S-S/static/csv/diabetes.csv")

# Удалите столбцы, которые не нужны для анализа
df = df.drop(columns=["Outcome"])

df = df.sample(n=700, random_state=random_state).reset_index(drop=True)

print(df.shape)  
display(df)
(700, 8)
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age
0 6 98 58 33 190 34.0 0.430 43
1 2 112 75 32 0 35.7 0.148 21
2 2 108 64 0 0 30.8 0.158 21
3 8 107 80 0 0 24.6 0.856 34
4 7 136 90 0 0 29.9 0.210 50
... ... ... ... ... ... ... ... ...
695 2 105 80 45 191 33.7 0.711 29
696 1 126 56 29 152 28.7 0.801 21
697 2 95 54 14 88 26.1 0.748 22
698 3 100 68 23 81 31.6 0.949 28
699 1 85 66 29 0 26.6 0.351 31

700 rows × 8 columns

In [2]:
import pandas as pd

df = pd.read_csv("C:/Users/TIGR228/Desktop/МИИ/Lab1/AIM-PIbd-31-Afanasev-S-S/static/csv/diabetes.csv")

required_columns = ["Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "DiabetesPedigreeFunction", "Age"]
missing_columns = [col for col in required_columns if col not in df.columns]
if missing_columns:
    raise ValueError(f"Отсутствуют столбцы: {missing_columns}")

df["diabetes_risk_index"] = (
    df["Glucose"] * 0.3  
    + df["BMI"] * 0.3  
    + df["Age"] * 0.2  
    + df["BloodPressure"] * 0.1  
    + df["Insulin"] * 0.1  
)

# Проверка новых данных
print(df[["Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI", "DiabetesPedigreeFunction", "Age", "diabetes_risk_index"]].head())
   Pregnancies  Glucose  BloodPressure  SkinThickness  Insulin   BMI  \
0            6      148             72             35        0  33.6   
1            1       85             66             29        0  26.6   
2            8      183             64              0        0  23.3   
3            1       89             66             23       94  28.1   
4            0      137             40             35      168  43.1   

   DiabetesPedigreeFunction  Age  diabetes_risk_index  
0                     0.627   50                71.68  
1                     0.351   31                46.28  
2                     0.672   32                74.69  
3                     0.167   21                55.33  
4                     2.288   33                81.43  

Разделение набора данных на обучающую и тестовые выборки (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_into_train_test(
    df_input: DataFrame,
    target_colname: str = "diabetes_risk_index",
    frac_train: float = 0.8,
    random_state: int = None,
) -> Tuple[DataFrame, DataFrame, DataFrame, DataFrame]:
    if not (0 < frac_train < 1):
        raise ValueError("Fraction must be between 0 and 1.")
    
    # Проверка наличия целевого признака
    if target_colname not in df_input.columns:
        raise ValueError(f"{target_colname} is not a column in the DataFrame.")
    
    # Разделяем данные на признаки и целевую переменную
    X = df_input.drop(columns=[target_colname])  # Признаки
    y = df_input[[target_colname]]  # Целевая переменная

    # Разделяем данные на обучающую и тестовую выборки
    X_train, X_test, y_train, y_test = train_test_split(
        X, y,
        test_size=(1.0 - frac_train),
        random_state=random_state
    )
    
    return X_train, X_test, y_train, y_test

# Применение функции для разделения данных
X_train, X_test, y_train, y_test = split_into_train_test(
    df, 
    target_colname="diabetes_risk_index", 
    frac_train=0.8, 
    random_state=42
)

# Для отображения результатов
display("X_train", X_train.head())
display("y_train", y_train.head())

display("X_test", X_test.head())
display("y_test", y_test.head())
'X_train'
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
60 2 84 0 0 0 0.0 0.304 21 0
618 9 112 82 24 0 28.2 1.282 50 1
346 1 139 46 19 83 28.7 0.654 22 0
294 0 161 50 0 0 21.9 0.254 65 0
231 6 134 80 37 370 46.2 0.238 46 1
'y_train'
diabetes_risk_index
60 29.40
618 60.26
346 67.61
294 72.87
231 108.26
'X_test'
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
668 6 98 58 33 190 34.0 0.430 43 0
324 2 112 75 32 0 35.7 0.148 21 0
624 2 108 64 0 0 30.8 0.158 21 0
690 8 107 80 0 0 24.6 0.856 34 0
473 7 136 90 0 0 29.9 0.210 50 0
'y_test'
diabetes_risk_index
668 73.00
324 56.01
624 52.24
690 54.28
473 68.77

Определение перечня алгоритмов решения задачи аппроксимации (регрессии)

In [4]:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn import linear_model, tree, neighbors, ensemble, neural_network

# Установка random_state для воспроизводимости
random_state = 42

# Словарь моделей
models = {
    # Линейная регрессия
    "linear": {
        "model": linear_model.LinearRegression(n_jobs=-1)
    },
    # Полиномиальная регрессия степени 2
    "linear_poly": {
        "model": make_pipeline(
            PolynomialFeatures(degree=2),
            StandardScaler(),  # Добавляем масштабирование данных
            linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
        )
    },
    # Полиномиальная регрессия с взаимодействиями
    "linear_interact": {
        "model": make_pipeline(
            PolynomialFeatures(interaction_only=True),
            StandardScaler(),  # Масштабирование данных
            linear_model.LinearRegression(fit_intercept=False, n_jobs=-1),
        )
    },
    # Ridge-регрессия
    "ridge": {
        "model": make_pipeline(
            StandardScaler(),  # Масштабирование данных
            linear_model.RidgeCV()
        )
    },
    # Регрессия на основе дерева решений
    "decision_tree": {
        "model": tree.DecisionTreeRegressor(max_depth=7, random_state=random_state)
    },
    # Метод ближайших соседей (kNN)
    "knn": {
        "model": make_pipeline(
            StandardScaler(),  # Масштабирование данных
            neighbors.KNeighborsRegressor(n_neighbors=7, n_jobs=-1)
        )
    },
    # Случайный лес (Random Forest)
    "random_forest": {
        "model": ensemble.RandomForestRegressor(
            max_depth=7, random_state=random_state, n_jobs=-1
        )
    },
    # Нейронная сеть (MLPRegressor)
    "mlp": {
        "model": make_pipeline(
            StandardScaler(),  # Масштабирование данных
            neural_network.MLPRegressor(
                activation="tanh",
                hidden_layer_sizes=(3,),  # Простая архитектура сети
                max_iter=500,
                early_stopping=True,
                random_state=random_state,
            )
        )
    },
}

Формирование набора моделей для регрессии

In [5]:
import math
from pandas import DataFrame
from sklearn import metrics

for model_name in models.keys():
    print(f"Model: {model_name}")

    fitted_model = models[model_name]["model"].fit(
        X_train.values, y_train.values.ravel()
    )
    y_train_pred = fitted_model.predict(X_train.values)
    y_test_pred = fitted_model.predict(X_test.values)
    models[model_name]["fitted"] = fitted_model
    models[model_name]["train_preds"] = y_train_pred
    models[model_name]["preds"] = y_test_pred
    models[model_name]["RMSE_train"] = math.sqrt(
        metrics.mean_squared_error(y_train, y_train_pred)
    )
    models[model_name]["RMSE_test"] = math.sqrt(
        metrics.mean_squared_error(y_test, y_test_pred)
    )
    models[model_name]["RMAE_test"] = math.sqrt(
        metrics.mean_absolute_error(y_test, y_test_pred)
    )
    models[model_name]["R2_test"] = metrics.r2_score(y_test, y_test_pred)
Model: linear
Model: linear_poly
Model: linear_interact
Model: ridge
Model: decision_tree
Model: knn
Model: random_forest
Model: mlp
c:\Users\TIGR228\Desktop\МИИ\Lab1\AIM-PIbd-31-Afanasev-S-S\aimenv\Lib\site-packages\sklearn\neural_network\_multilayer_perceptron.py:690: ConvergenceWarning: Stochastic Optimizer: Maximum iterations (500) reached and the optimization hasn't converged yet.
  warnings.warn(

Вывод результатов оценки

In [6]:
reg_metrics = pd.DataFrame.from_dict(models, "index")[
    ["RMSE_train", "RMSE_test", "RMAE_test", "R2_test"]
]
reg_metrics.sort_values(by="RMSE_test").style.background_gradient(
    cmap="viridis", low=1, high=0.3, subset=["RMSE_train", "RMSE_test"]
).background_gradient(cmap="plasma", low=0.3, high=1, subset=["RMAE_test", "R2_test"])
Out[6]:
  RMSE_train RMSE_test RMAE_test R2_test
linear 0.000000 0.000000 0.000000 1.000000
ridge 0.002409 0.002181 0.040847 1.000000
random_forest 1.856691 3.387390 1.612410 0.967338
decision_tree 2.409987 4.281378 1.847379 0.947823
knn 5.170083 5.824576 2.073599 0.903431
mlp 60.594447 59.994001 7.553087 -9.245313
linear_poly 67.720820 66.518485 8.144355 -11.594887
linear_interact 67.518306 67.518306 8.216952 -11.976353

Вывод реального и "спрогнозированного" результата для обучающей и тестовой выборок

Получение лучшей модели

In [7]:
best_model = str(reg_metrics.sort_values(by="RMSE_test").iloc[0].name)

display(best_model)
'linear'

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

In [ ]:
import pandas as pd
import numpy as np
from sklearn import metrics
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler

# Удаление пропущенных значений (если есть)
df.dropna(inplace=True)

# Предикторы и целевая переменная
X = df[["Pregnancies", "Glucose", "BloodPressure", "SkinThickness", "Insulin", 
        "BMI", "DiabetesPedigreeFunction", "Age"]] 
y = df["diabetes_risk_index"] 

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Определение модели случайного леса
model = RandomForestRegressor(random_state=42)

# Сетка гиперпараметров для поиска
param_grid = {
    'n_estimators': [50, 100, 150],  
    'max_depth': [10, 20, 30],       
    'min_samples_split': [5, 10, 15] 
}

# Настройка поиска по сетке
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, y_train)

print("Лучшие параметры:", grid_search.best_params_)
print("Лучший результат (MSE):", -grid_search.best_score_)

# Оценка на тестовой выборке
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test)

mse = metrics.mean_squared_error(y_test, y_pred)
r2 = metrics.r2_score(y_test, y_pred)

print(f"Ошибка на тестовой выборке (MSE): {mse:.4f}")
print(f"Коэффициент детерминации (R²): {r2:.4f}")
Fitting 3 folds for each of 27 candidates, totalling 81 fits
c:\Users\TIGR228\Desktop\МИИ\Lab1\AIM-PIbd-31-Afanasev-S-S\aimenv\Lib\site-packages\numpy\ma\core.py:2881: RuntimeWarning: invalid value encountered in cast
  _data = np.array(data, dtype=dtype, copy=copy,
Лучшие параметры: {'max_depth': 20, 'min_samples_split': 5, 'n_estimators': 150}
Лучший результат (MSE): 11.258082426680579
Ошибка на тестовой выборке (MSE): 9.1015
Коэффициент детерминации (R²): 0.9741

Обучение модели с новыми гиперпараметрами и сравнение новых и старых данных

In [9]:
import pandas as pd
import numpy as np
from sklearn import metrics
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split, GridSearchCV
import matplotlib.pyplot as plt


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

old_grid_search = GridSearchCV(estimator=RandomForestRegressor(), 
                                param_grid=old_param_grid,
                                scoring='neg_mean_squared_error', cv=3, n_jobs=-1, verbose=2)

old_grid_search.fit(X_train, y_train)

old_best_params = old_grid_search.best_params_
old_best_mse = -old_grid_search.best_score_  # Меняем знак, так как берем отрицательное значение MSE

new_param_grid = {
    'n_estimators': [100],
    'max_depth': [20],
    'min_samples_split': [10]
}

new_grid_search = GridSearchCV(estimator=RandomForestRegressor(), 
                                param_grid=new_param_grid,
                                scoring='neg_mean_squared_error', cv=2)

new_grid_search.fit(X_train, y_train)

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

model_best = RandomForestRegressor(**new_best_params)
model_best.fit(X_train, y_train)

model_oldbest = RandomForestRegressor(**old_best_params)
model_oldbest.fit(X_train, y_train)

y_pred = model_best.predict(X_test)
y_oldpred = model_oldbest.predict(X_test)

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)
Fitting 3 folds for each of 8 candidates, totalling 24 fits
Старые параметры: {'max_depth': 20, 'min_samples_split': 5, 'n_estimators': 50}
Лучший результат (MSE) на старых параметрах: 11.60966872499276

Новые параметры: {'max_depth': 20, 'min_samples_split': 10, 'n_estimators': 100}
Лучший результат (MSE) на новых параметрах: 19.147439110906816
Среднеквадратическая ошибка (MSE) на тестовых данных: 10.814392376125927
Корень среднеквадратичной ошибки (RMSE) на тестовых данных: 3.288524346287545

Попробуем визуализировать

In [11]:
plt.figure(figsize=(10, 5))
plt.scatter(range(len(y_test)), y_test, label="Актуальные значения", color="black", alpha=0.5)
plt.scatter(range(len(y_test)), y_pred, label="новые параметры", color="blue", alpha=0.5)
plt.scatter(range(len(y_test)), y_oldpred, label="старые параметры", color="red", alpha=0.5)
plt.xlabel("Выборка")
plt.ylabel("Значения")
plt.legend()
plt.title("Новые and Старые Параметры")
plt.show()
No description has been provided for this image