diff --git a/arutunyan_dmitry_lab_4/README.md b/arutunyan_dmitry_lab_4/README.md new file mode 100644 index 0000000..6eb14e9 --- /dev/null +++ b/arutunyan_dmitry_lab_4/README.md @@ -0,0 +1,131 @@ + +## Лабораторная работа 4. Вариант 4. +### Задание +Использовать метод кластеризации по варианту для данных из курсовой работы. Самостоятельно сформулировав задачу. Интерпретировать результаты и оценить, насколько хорошо он подходит для +решения сформулированной задачи. + +Алгоритм кластеризации: + +- Пространственная кластеризация данных с шумом на основе плотности `DBSCAN`. + +### Как запустить +Для запуска программы необходимо с помощью командной строки в корневой директории файлов прокета прописать: +``` +python main.py +``` +После этого в папке `static` сгенерируются 3 графика, по которым оценивается результат выполнения программы. + +### Используемые технологии +- Библиотека `numpy`, используемая для обработки массивов данных и вычислений +- Библиотека `pyplot`, используемая для построения графиков. +- Библиотека `pandas`, используемая для работы с данными для анализа scv формата. +- Библиотека `sklearn` - большой набор функционала для анализа данных. Из неё были использованы инструменты: + - `DBSCAN` - инструмент работы с моделью "Пространственная кластеризация данных с шумом на основе плотности" + - `metrics` - набор инструменов для оценки моделей + - `LinearRegression` - инструмент работы с моделью "Линейная регрессия" + +`DBSCAN` - это алгоритм кластеризации, который используется для кластеризации данных на основе плотности, что позволяет обнаруживать кластеры произвольной формы и обнаруживать выбросы (шум). `DBSCAN` может быть полезным при предварительной обработке данных перед задачей предсказания: + - Удаление выбросов (шума): `DBSCAN` может помочь в идентификации и удалении выбросов из данных. + - Генерация новых признаков: `DBSCAN` может быть использован для генерации новых признаков на основе кластеров. + +### Описание работы +#### Описание набора данных +Набор данных - набор для определения возможности наличия ССЗ заболеваний у челоека + +Названия столбцов набора данных и их описание: + + * HeartDisease - Имеет ли человек ССЗ (No / Yes), + * BMI - Индекс массы тела человека (float), + * Smoking - Выкурил ли человек хотя бы 5 пачек сигарет за всю жизнь (No / Yes), + * AlcoholDrinking - Сильно ли человек употребляет алкоголь (No / Yes), + * Stroke - Был ли у человека инсульт (No / Yes), + * PhysicalHealth - Сколько дней за последний месяц человек чувствовал себя плохо (0-30), + * MentalHealth - Сколько дней за последний месяц человек чувствовал себя удручённо (0-30), + * DiffWalking - Ииспытывает ли человек трудности при ходьбе (No / Yes), + * Sex - Пол (female, male), + * AgeCategory - Возрастная категория (18-24, 25-29, 30-34, 35-39, 40-44, 45-49, 50-54, 55-59, 60-64, 65-69, 70-74, 75-79, 80 or older), + * Race - Национальная принадлежность человека (White, Black, Hispanic, American Indian/Alaskan Native, Asian, Other), + * Diabetic - Был ли у человека диабет (No / Yes), + * PhysicalActivity - Занимался ли человек спротом за последний месяц (No / Yes), + * GenHealth - Общее самочувствие человека (Excellent, Very good, Good, Fair, Poor), + * SleepTime - Сколько человек в среднем спит за 24 часа (0-24), + * Asthma - Была ли у человека астма (No / Yes), + * KidneyDisease - Было ли у человека заболевание почек (No / Yes), + * SkinCancer - Был ли у человека рак кожи (No / Yes). + +Ссылка на страницу набора на kuggle: [Indicators of Heart Disease](https://www.kaggle.com/datasets/kamilpytlak/personal-key-indicators-of-heart-disease/data) + +#### Формулировка задачи +Согласно прописанным в литературе варантам использования, `DBSCAN` может помочь в идентификации и удалении выбросов из данных, а также может быть использован для генерации новых признаков на основе кластеров. Исходя из этого сформулируем задачу: +> "В наборе данных с помощью `DBSCAN` определить и исключить строки содержащие шум, а также сгенерировать новый признак для данных на сонове кластеров. Проверить результат через решение задачи предсказания моделью линейной регрессии на исходных и модифицированных данных" + +#### Использование алгоритма `DBSCAN` +Чтобы эффективно использовать алгоритм `DBSCAN` необходимо правильно определить два параметра: `eps` - радиус окрестности вокруг каждой точки и `min_samples` - минимальное количество точек, которые должны находиться в окрестности, чтобы рассматривать ее как ядро кластера. + +Начнём с получения датасета из csv файла и признаков кластеризации: +```python +df = pd.read_csv(filein, sep=',').iloc[0:10000] +x = df.drop("HeartDisease", axis=1) +``` +> **Warning** +> +> Алгоритм `DBSCAN` - очень жадная по памяти программа. В худшем случае алгоритм может занимать Q(N^2) оперативной памяти устройства, поэтому исследование получится провести лишь на частичной выборке в 10000 строк данных. + +Для нахождения оптимального значения параметра `eps` воспользуемся методом рассчёта средней плотности данных. Для этого необходимо найти суммы максимальных и минимальных значений каждого признака и взять среднее арифметическое этих двух значений: + +```python +eps_opt = (x.max().values.mean() + x.min().values.mean()) / 2 +``` +Оптимальное значение параметра `min_samples` будем искать эмпирически. Условимся, что нам будет достаточно разделить высе данные на 6 кластеров (пусть это будут степени риска возникновения ССЗ), но нам нельзя терять в качестве выбросов более 10% данных. Тогда мы будем варьировать параметр `min_samples` от 1 до кол-ва всех данных и закончим эксперимент при выполнении одного из указанных условий: + +```python +developed_data = [] + for i in range(len(x)): + if i == 0: + continue + dbscan = DBSCAN(eps=eps_opt, min_samples=i) + clusters = dbscan.fit_predict(x.values) + if len(set(clusters)) <= 7: + developed_data = clusters + break + if list(clusters).count(-1) / len(clusters) >= 0.1: + developed_data = clusters + break +``` + +Таким образом в массиве `developed_data` мы получим значение кластеров для каждй строки датасета. Добавим её как дополнительный признак. + +График кластеров для значений датасета: + +![](dbscan.png "") + +#### Решение задачи предсказания +Создадим два обучающих модуля. В 1м удалим все строки с кластером `-1`, что указывает на то, что они шум и воспользуемся дополнительным признаком `DBSCAN`: +```python +df_mod = df.loc[df["DBSCAN"] != -1] +x_train_mod = df_mod.drop("HeartDisease", axis=1).iloc[0:round(len(df) / 100 * 99)] +y_train_mod = df_mod["HeartDisease"].iloc[0:round(len(df) / 100 * 99)] +x_test_mod = df_mod.drop("HeartDisease", axis=1).iloc[round(len(df) / 100 * 99):len(df)] +y_test_mod = df_mod["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)] +``` +Во 2м модуле для разделения на выборки оставим исходные данные: +```python +x_train = df.drop(["HeartDisease", "DBSCAN"], axis=1).iloc[0:round(len(df) / 100 * 99)] +y_train = df["HeartDisease"].iloc[0:round(len(df) / 100 * 99)] +x_test = df.drop(["HeartDisease", "DBSCAN"], axis=1).iloc[round(len(df) / 100 * 99):len(df)] +y_test = df["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)] +``` +Создаим две модели регрессии и на каждой решим задачу предсказания. Вычислим ошибки и построим графики. + +График решения задачи предсказания на модифицированных данных: + +![](regdbscan.png "") + +График решения задачи предсказания на исходных данных: + +![](reg.png "") + +### Вывод +Согласно графиком, модель, обученная на исходных данных показала результат лучше, чем модель, обученная на модифицированных данных. Получается, что на данном наборе, используя алгоритм `DBSCAN`, мы не только невероятно увеличиваем затратность памяти на обучение модели, но и отрицательно влияем на результат её работы. Это означает, что использование алгоритма на таком наборе данных абсолютно нецелесообразно. + +Связанно это может быть с большим количеством бинарных признаков в данных. В таких случаях задачи кластеризации решаются сравнительно хуже. \ No newline at end of file diff --git a/arutunyan_dmitry_lab_4/dbscan.png b/arutunyan_dmitry_lab_4/dbscan.png new file mode 100644 index 0000000..67dc36e Binary files /dev/null and b/arutunyan_dmitry_lab_4/dbscan.png differ diff --git a/arutunyan_dmitry_lab_4/main.py b/arutunyan_dmitry_lab_4/main.py new file mode 100644 index 0000000..a3c2a30 --- /dev/null +++ b/arutunyan_dmitry_lab_4/main.py @@ -0,0 +1,96 @@ +import pandas as pd +import numpy as np +from matplotlib import pyplot as plt +from sklearn import metrics +from sklearn.cluster import DBSCAN +from sklearn.linear_model import LinearRegression + +filein = "P:\\ULSTU\\ИИС\\Datasets\\heart_2020_norm.csv" +fileout = "P:\\ULSTU\\ИИС\\Datasets\\heart_2020_classified.csv" + + +# Метод устранения шумов и кластеризации данных алгоритмом DBSCAN +def dbscan(): + df = pd.read_csv(filein, sep=',').iloc[0:10000] # Считывание датасета + x = df.drop("HeartDisease", axis=1) # Определение кластеризуемых параметров + + eps_opt = (x.max().values.mean() + x.min().values.mean()) / 2 # Рассчёт опционального радиуса окрестности методом средней плотности + + developed_data = [] # Подбор значения минимального количества точек в окрестности + for i in range(len(x)): # - Начинаем с одной точки + if i == 0: + continue # - Увеличиваем значение кол-ва точек на 1 + dbscan = DBSCAN(eps=eps_opt, min_samples=i) # - Обучаем модель и получаем массив кластеров + clusters = dbscan.fit_predict(x.values) + if len(set(clusters)) <= 7: # - Прекращаем увеличивать значение точек, если кол-во кластеров уменьшилось до требуемого + developed_data = clusters + break + if list(clusters).count(-1) / len(clusters) >= 0.1: # - Или если "шум" превышает 10% от данных + developed_data = clusters + break + + make_plot(x, developed_data) + df["DBSCAN"] = developed_data + df.to_csv(fileout, index=False) # Сохраняем полученные кластеры как доп. столбец датасета + + +# Метод оценки эффективности кластеризации DBSCAN +def linear_reg(): # Создаём две выборки данных + df = pd.read_csv(fileout, sep=',') # В 1й избавляемся от "шумов" и используем столбец кластеров как признак + df_mod = df.loc[df["DBSCAN"] != -1] + x_train_mod = df_mod.drop("HeartDisease", axis=1).iloc[0:round(len(df) / 100 * 99)] + y_train_mod = df_mod["HeartDisease"].iloc[0:round(len(df) / 100 * 99)] + x_test_mod = df_mod.drop("HeartDisease", axis=1).iloc[round(len(df) / 100 * 99):len(df)] + y_test_mod = df_mod["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)] + # Во 2й оставляем обычные данные + x_train = df.drop(["HeartDisease", "DBSCAN"], axis=1).iloc[0:round(len(df) / 100 * 99)] + y_train = df["HeartDisease"].iloc[0:round(len(df) / 100 * 99)] + x_test = df.drop(["HeartDisease", "DBSCAN"], axis=1).iloc[round(len(df) / 100 * 99):len(df)] + y_test = df["HeartDisease"].iloc[round(len(df) / 100 * 99):len(df)] + + lr_mod = LinearRegression() # Обучаем модель без "шума" и с признаком кластеров + lr_mod.fit(x_train_mod.values, y_train_mod.values) + y_mod_pred = lr_mod.predict(x_test_mod.values) + err = pred_errors(y_mod_pred, y_test_mod.values) + make_plots(y_test_mod.values, y_mod_pred, err[0], err[1], "Регрессия с кластеризацией dbscan") + + lr = LinearRegression() # Обучаем модель на исходных данных + lr.fit(x_train.values, y_train.values) + y_pred = lr.predict(x_test.values) + err = pred_errors(y_pred, y_test.values) + make_plots(y_test.values, y_pred, err[0], err[1], "Чистая линейная регрессия") + + +# Метод рассчёта ошибок +def pred_errors(y_predict, y_test): + mid_square = np.round(np.sqrt(metrics.mean_squared_error(y_test, y_predict)),3) # Рассчёт среднеквадратичной ошибки модели + det_kp = np.round(metrics.r2_score (y_test, y_predict), 2) # Рассчёт коэфициента детерминации модели + return mid_square, det_kp + + +# Метод отрисовки графиков +def make_plots(y_test, y_predict, mid_sqrt, det_kp, title): + plt.plot(y_test, c="red", label="\"y\" исходная") # Создание графика исходной функции + plt.plot(y_predict, c="green", label="\"y\" предсказанная \n" + "Ср^2 = " + str(mid_sqrt) + "\n" + "Кд = " + str(det_kp)) # Создание графика предсказанной функции + plt.legend(loc='lower left') + plt.title(title) + plt.savefig('static/' + title + '.png') + plt.close() + + +# Метод построения графика кластеризации +def make_plot(x, c): + plt.scatter(x.values[:, 0], x.values[:, 13], c=c, cmap='viridis') + plt.xlabel('BMI') + plt.ylabel('SleepTime') + plt.colorbar() + plt.title('DBSCAN Clustering') + plt.savefig('static/dbscan.png') + plt.close() + + +if __name__ == '__main__': + dbscan() + linear_reg() diff --git a/arutunyan_dmitry_lab_4/reg.png b/arutunyan_dmitry_lab_4/reg.png new file mode 100644 index 0000000..774bf7e Binary files /dev/null and b/arutunyan_dmitry_lab_4/reg.png differ diff --git a/arutunyan_dmitry_lab_4/regdbscan.png b/arutunyan_dmitry_lab_4/regdbscan.png new file mode 100644 index 0000000..f35a140 Binary files /dev/null and b/arutunyan_dmitry_lab_4/regdbscan.png differ