diff --git a/romanova_adelina_lab_3/1.png b/romanova_adelina_lab_3/1.png new file mode 100644 index 0000000..990d479 Binary files /dev/null and b/romanova_adelina_lab_3/1.png differ diff --git a/romanova_adelina_lab_3/2.png b/romanova_adelina_lab_3/2.png new file mode 100644 index 0000000..8a00c6e Binary files /dev/null and b/romanova_adelina_lab_3/2.png differ diff --git a/romanova_adelina_lab_3/3.png b/romanova_adelina_lab_3/3.png new file mode 100644 index 0000000..126d9fb Binary files /dev/null and b/romanova_adelina_lab_3/3.png differ diff --git a/romanova_adelina_lab_3/4.png b/romanova_adelina_lab_3/4.png new file mode 100644 index 0000000..dbb23aa Binary files /dev/null and b/romanova_adelina_lab_3/4.png differ diff --git a/romanova_adelina_lab_3/README.md b/romanova_adelina_lab_3/README.md new file mode 100644 index 0000000..5ee4931 --- /dev/null +++ b/romanova_adelina_lab_3/README.md @@ -0,0 +1,77 @@ +# Лабораторная работа №3. Вариант 21 + +## Тема: +Деревья решений + +## Модель: + +Decision Tree Classifier + +## Как запустить программу: +Установить *python, numpy, matplotlib, sklearn* +``` +python main.py +``` + +## Какие технологии использовались: +Язык программирования Python, библиотеки numpy, matplotlib, sklearn + +Среда разработки VSCode + +# Что делает лабораторная работа: +Использует данные из набора "UCI Heart Disease Data" и обучает модель: ```Decision Tree Classifier``` + +Датасет UCI Heart Disease Data содержит информацию о различных клинических признаках, таких как возраст, пол, артериальное давление, холестерин, наличие электрокардиографических признаков и другие, а также целевую переменную, отражающую наличие или отсутствие заболевания сердца. + +Для начала нужно предобработать данные, чтобы модель могла принимать их на вход. Изначально данный имеют следующий вид: + +![](1.png "") + +Так как модели машинного обучения умеют работать исключительно с числовыми значениями, то нужно свести все данных к данному формату и использовать только полные строки, значение признаков которых не являются пустыми значениями. Это происходит с использованием функции, представленной ниже: + +![](2.png "") + +Далее нужно привести целевое значение к бинарному виду, т.к изначально данное поле принимает 4 значения. После этого применить подход, называемый “feature engineering”, для получения большего количества признаков, которые возможно помогут модели при решении задачи, т.к. обычно в машинном и глубоком обучении действует следующая логика: Больше данных - лучше результат. Получение новых признаков происходит с помощью функции ниже и далее обновленный набор данных снова преобразовывается к численному формату. + +``` +def fe_creation(df): + # Feature engineering (FE) + df['age2'] = df['age']//10 + df['trestbps2'] = df['trestbps']//10 + df['chol2'] = df['chol']//60 + df['thalch2'] = df['thalch']//40 + df['oldpeak2'] = df['oldpeak']//0.4 + for i in ['sex', 'age2', 'fbs', 'restecg', 'exang']: + for j in ['cp','trestbps2', 'chol2', 'thalch2', 'oldpeak2', 'slope']: + df[i + "_" + j] = df[i].astype('str') + "_" + df[j].astype('str') + return df +``` +После применения данной функции количество признаков увеличилось с 12 до 47. Далее все признаки стандартизируются с помощью следующей формулы z = (x-mean)/std, где х - текущее значение признак, mean - математическое ожидание столбца с этим признаком, std - стандартное отклонение данного признака, а z - соответственно новое значение признака x. После всех описанных действий данные стали готовыми для их использования для обучения деревьев. + +```Decision Tree Classifier```- это алгоритм машинного обучения, который использует структуру дерева для принятия решений. Каждый узел дерева представляет собой тест по какому-то признаку, а каждая ветвь представляет возможный результат этого теста. Цель - разделить данные на подгруппы так, чтобы в каждой подгруппе преобладал один класс. + +``` + decision_tree = DecisionTreeClassifier() + param_grid = {'min_samples_leaf': [i for i in range(2,12)]} + decision_tree_CV = GridSearchCV(decision_tree, param_grid=param_grid, cv=cv_train, verbose=False) + decision_tree_CV.fit(train, train_target) + print(decision_tree_CV.best_params_) + + acc_all = acc_metrics_calc(0, acc_all, decision_tree_CV, train, valid, train_target, valid_target, title="Decision Tree Classifier") + plot_learning_curve(decision_tree_CV, "Decision Tree", train, train_target, cv=cv_train) + + feature_importances_dt = decision_tree_CV.best_estimator_.feature_importances_ + plot_feature_importance(feature_importances_dt, data.columns, "Decision Tree") +``` + +Первым был обучен Decision Tree Classifier, который с помощью алгоритма GridSearch нашел наилучшие гиперпараметры для решения задачи. Ниже приведены графики, отображающие качество и процесс обучения данного классификатора. + +![](3.png "") + +На следующем графике мы можем увидеть какие признаки модель посчитала наиболее важными: + +![](4.png "") + +## Вывод + +На обучающихся данных мы в большинстве случаев предсказываем правильно, а в валидационных появляется проблема с выявлением второго класса, которое отображает наличие заболеваний. \ No newline at end of file diff --git a/romanova_adelina_lab_3/main.py b/romanova_adelina_lab_3/main.py new file mode 100644 index 0000000..517ca1c --- /dev/null +++ b/romanova_adelina_lab_3/main.py @@ -0,0 +1,302 @@ +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import seaborn as sns + +import sklearn +from sklearn.preprocessing import (LabelEncoder, + StandardScaler, + MinMaxScaler, + RobustScaler) +from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold, learning_curve, ShuffleSplit +from sklearn.model_selection import cross_val_predict as cvp +from sklearn import metrics +from sklearn.metrics import mean_absolute_error, mean_squared_error, accuracy_score, confusion_matrix, explained_variance_score + +from sklearn.tree import DecisionTreeClassifier, plot_tree +from sklearn.ensemble import RandomForestClassifier + + +def str_features_to_numeric(data): + # Преобразовывает все строковые признаки в числовые. + + # Определение категориальных признаков + categorical_columns = [] + numerics = ['int8', 'int16', 'int32', 'int64', 'float16', 'float32', 'float64'] + features = data.columns.values.tolist() + for col in features: + if data[col].dtype in numerics: continue + categorical_columns.append(col) + + # Кодирование категориальных признаков + for col in categorical_columns: + if col in data.columns: + le = LabelEncoder() + le.fit(list(data[col].astype(str).values)) + data[col] = le.transform(list(data[col].astype(str).values)) + + return data + + +def fe_creation(df): + # Feature engineering (FE) + df['age2'] = df['age']//10 + df['trestbps2'] = df['trestbps']//10 + df['chol2'] = df['chol']//60 + df['thalch2'] = df['thalch']//40 + df['oldpeak2'] = df['oldpeak']//0.4 + for i in ['sex', 'age2', 'fbs', 'restecg', 'exang']: + for j in ['cp','trestbps2', 'chol2', 'thalch2', 'oldpeak2', 'slope']: + df[i + "_" + j] = df[i].astype('str') + "_" + df[j].astype('str') + return df + + +def acc_d(y_meas, y_pred): + # Относительная погрешность между прогнозируемыми значениями y_pred и измеренными значениями y_meas + return mean_absolute_error(y_meas, y_pred)*len(y_meas)/sum(abs(y_meas)) + + +def acc_rmse(y_meas, y_pred): + # Среднеквадратичная ошибка между прогнозируемыми значениями y_pred и измеренными значениями y_meas + return (mean_squared_error(y_meas, y_pred))**0.5 + + +def plot_cm(train_target, train_target_pred, valid_target, valid_target_pred, title): + # Построение матриц ошибок + + def cm_calc(y_true, y_pred): + cm = confusion_matrix(y_true, y_pred, labels=np.unique(y_true)) + cm_sum = np.sum(cm, axis=1, keepdims=True) + cm_perc = cm / cm_sum.astype(float) * 100 + annot = np.empty_like(cm).astype(str) + nrows, ncols = cm.shape + for i in range(nrows): + for j in range(ncols): + c = cm[i, j] + p = cm_perc[i, j] + if i == j: + s = cm_sum[i] + annot[i, j] = '%.1f%%\n%d/%d' % (p, c, s) + elif c == 0: + annot[i, j] = '' + else: + annot[i, j] = '%.1f%%\n%d' % (p, c) + cm = pd.DataFrame(cm, index=np.unique(y_true), columns=np.unique(y_true)) + cm.index.name = 'Actual' + cm.columns.name = 'Predicted' + return cm, annot + + + # Построение матриц ошибок + fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(16, 6), sharex=True) + + # Обучающие данные + ax = axes[0] + ax.set_title("for training data") + cm0, annot0 = cm_calc(train_target, train_target_pred) + sns.heatmap(cm0, cmap= "YlGnBu", annot=annot0, fmt='', ax=ax) + + # Тестовые данные + ax = axes[1] + ax.set_title("for test (validation) data") + cm1, annot1 = cm_calc(valid_target, valid_target_pred) + sns.heatmap(cm1, cmap= "YlGnBu", annot=annot1, fmt='', ax=ax) + + fig.suptitle(f'CONFUSION MATRICES for {title}') + plt.savefig(f'CONFUSION MATRICES for {title}.png') + plt.show() + + +def acc_metrics_calc(num, acc_all, model, train, valid, train_target, valid_target, title): + # Этап выбора моделей + # Расчет точности модели по различным показателям + + ytrain = model.predict(train).astype(int) + yvalid = model.predict(valid).astype(int) + print('train_target = ', train_target[:5].values) + print('ytrain = ', ytrain[:5]) + print('valid_target =', valid_target[:5].values) + print('yvalid =', yvalid[:5]) + + num_acc = 0 + for x in metrics_now: + if x == 1: + #критерий точности score + acc_train = round(metrics.accuracy_score(train_target, ytrain), 2) + acc_valid = round(metrics.accuracy_score(valid_target, yvalid), 2) + elif x == 2: + # rmse критерий + acc_train = round(acc_rmse(train_target, ytrain), 2) + acc_valid = round(acc_rmse(valid_target, yvalid), 2) + elif x == 3: + # критерий относительной погрешности + acc_train = round(acc_d(train_target, ytrain) * 100, 2) + acc_valid = round(acc_d(valid_target, yvalid) * 100, 2) + + print('acc of', metrics_all[x], 'for train =', acc_train) + print('acc of', metrics_all[x], 'for valid =', acc_valid) + acc_all[num_acc].append(acc_train) #train + acc_all[num_acc+1].append(acc_valid) #valid + num_acc += 2 + + # Построение матриц + plot_cm(train_target, ytrain, valid_target, yvalid, title) + + return acc_all + + +def plot_feature_importance(feature_importances, feature_names, model_name): + import matplotlib.pyplot as plt + import seaborn as sns + + # Создание цветовой палитры + colors = sns.color_palette('viridis', len(feature_importances)) + + # Сортировка индексов важностей признаков + indices = feature_importances.argsort()[::-1] + + # Создание стильного барплота + plt.figure(figsize=(12, 8)) + ax = sns.barplot(x=feature_importances[indices], y=feature_names[indices], palette=colors) + + # Добавление декораций + plt.xlabel('Важность признака', fontsize=14) + plt.ylabel('Признаки', fontsize=14) + plt.title(f'Важность признаков в модели {model_name}', fontsize=16) + plt.xticks(fontsize=12) + plt.yticks(fontsize=12) + + # Добавление цветовой шкалы и ее описания + cbar = plt.colorbar(plt.cm.ScalarMappable(cmap='viridis'), ax=ax) + cbar.set_label('Уровень важности', rotation=270, labelpad=15, fontsize=12) + + # Добавление сетки для лучшей читаемости + plt.grid(axis='x', linestyle='--', alpha=0.6) + + # Сохранение графика в файл + plt.savefig('feature_importance_plot.png', bbox_inches='tight') + + # Отображение графика + plt.savefig(f'feature_importances_{model_name}.png') + plt.show() + + +def plot_learning_curve(estimator, title, X, y, cv=None, axes=None, ylim=None, + n_jobs=None, train_sizes=np.linspace(.1, 1.0, 5), random_state=0): + fig, axes = plt.subplots(2, 1, figsize=(20, 10)) + + if axes is None: + _, axes = plt.subplots(1, 2, figsize=(20, 5)) + + axes[0].set_title(title) + if ylim is not None: + axes[0].set_ylim(*ylim) + axes[0].set_xlabel("Training examples") + axes[0].set_ylabel("Score") + + cv_train = ShuffleSplit(n_splits=cv_n_split, test_size=test_train_split_part, random_state=random_state) + + train_sizes, train_scores, test_scores, fit_times, _ = \ + learning_curve(estimator=estimator, X=X, y=y, cv=cv, + train_sizes=train_sizes, + return_times=True) + + train_scores_mean = np.mean(train_scores, axis=1) + train_scores_std = np.std(train_scores, axis=1) + test_scores_mean = np.mean(test_scores, axis=1) + test_scores_std = np.std(test_scores, axis=1) + fit_times_mean = np.mean(fit_times, axis=1) + fit_times_std = np.std(fit_times, axis=1) + + # Plot learning curve + axes[0].grid() + axes[0].fill_between(train_sizes, train_scores_mean - train_scores_std, + train_scores_mean + train_scores_std, alpha=0.1, + color="r") + axes[0].fill_between(train_sizes, test_scores_mean - test_scores_std, + test_scores_mean + test_scores_std, alpha=0.1, + color="g") + axes[0].plot(train_sizes, train_scores_mean, 'o-', color="r", + label="Training score") + axes[0].plot(train_sizes, test_scores_mean, 'o-', color="g", + label="Cross-validation score") + axes[0].legend(loc="best") + + # Plot n_samples vs fit_times + axes[1].grid() + axes[1].plot(train_sizes, fit_times_mean, 'o-') + axes[1].fill_between(train_sizes, fit_times_mean - fit_times_std, + fit_times_mean + fit_times_std, alpha=0.1) + axes[1].set_xlabel("Training examples") + axes[1].set_ylabel("fit_times") + axes[1].set_title("Scalability of the model") + + plt.savefig(f'{title}.png') + + plt.show() + return + + +if __name__ == "__main__": + # Загрузка данных + # Преобразование данных и предобработка + # Обучение моделей Decision Tree Classifier и Random Forest Classifier + # Расчет метрик и построение графиков + cv_n_split = 5 + random_state = 42 + test_train_split_part = 0.25 + + metrics_all = {1: 'acc', 2 : 'rmse', 3 : 're'} + metrics_now = [1, 2, 3] + + data = pd.read_csv("..//heart_disease_uci.csv") + data['target'] = data['num'] + data = data.drop(columns=['id', 'dataset', 'ca', 'thal', 'num']) + + data = data[(data['chol'] <= 420) & (data['oldpeak'] >=0) & (data['oldpeak'] <=4)].reset_index(drop=True) + data = data.dropna().reset_index(drop=True) + print(data.info()) + + data = str_features_to_numeric(data) + data = data[data['target'].isin([0, 1])] # приволим столбец с целевыми значениями к бинарному виду + + data = fe_creation(data) + data = str_features_to_numeric(data) + + dataset = data.copy() # original data + target_name = 'target' + target = data.pop(target_name) + + # Model standartization + # The standard score of a sample x is calculated as: + # z = (x - мат.ож.) / (стандартное отклонение) + scaler = StandardScaler() + data = pd.DataFrame(scaler.fit_transform(data), columns = data.columns) + + train, valid, train_target, valid_target = train_test_split(data, target, test_size=test_train_split_part, random_state=random_state) + + # list of accuracy of all model - amount of metrics_now * 2 (train & valid datasets) + num_models = 6 + acc_train = [] + acc_valid = [] + acc_all = np.empty((len(metrics_now)*2, 0)).tolist() + acc_all + + acc_all_pred = np.empty((len(metrics_now), 0)).tolist() + acc_all_pred + + cv_train = ShuffleSplit(n_splits=cv_n_split, test_size=test_train_split_part, random_state=random_state) + + decision_tree = DecisionTreeClassifier() + param_grid = {'min_samples_leaf': [i for i in range(2,12)]} + decision_tree_CV = GridSearchCV(decision_tree, param_grid=param_grid, cv=cv_train, verbose=False) + decision_tree_CV.fit(train, train_target) + print(decision_tree_CV.best_params_) + + acc_all = acc_metrics_calc(0, acc_all, decision_tree_CV, train, valid, train_target, valid_target, title="Decision Tree Classifier") + plot_learning_curve(decision_tree_CV, "Decision Tree", train, train_target, cv=cv_train) + + feature_importances_dt = decision_tree_CV.best_estimator_.feature_importances_ + plot_feature_importance(feature_importances_dt, data.columns, "Decision Tree") + +