
Введение
С ростом спроса на специалистов по данным все больше людей хотят работать в этой области. Стало очень важно продемонстрировать правильные навыки, чтобы выделиться из толпы и попасть в ведущие компании. Вот где проекты вступают в игру. Более того, сквозные проекты дают вам доступ к рабочей среде в реальном времени. В этом руководстве я объясню полный жизненный цикл проекта Data Science с помощью практической демонстрации. Мы возьмем постановку задачи машинного обучения и создадим веб-приложение в качестве ее решения с помощью Flask и развернем его на Heroku, платформе облачных приложений. Давайте начнем!

1. Понимание проблемы
Жизненный цикл проекта начинается с понимания постановки задачи. Постановка задачи (источник) для проекта: создать веб-приложение с моделью машинного обучения на бэкэнде, которая прогнозирует уровень выгорания сотрудников компании на основе различных факторов трудовой жизни, таких как - рабочее время, работа - доступность из дома, оценка умственной усталости и т.п.
Пример из практики. Счастливые и здоровые сотрудники, несомненно, более эффективны в работе. Они помогают компании развиваться. Однако сценарий в большинстве компаний изменился с пандемией. После работы из дома более 69% сотрудников проявляют симптомы эмоционального выгорания (источник опроса: Монстр). Скорость выгорания действительно вызывает тревогу. Многие компании предпринимают шаги, чтобы их сотрудники оставались психически здоровыми. В качестве решения этой проблемы мы создадим веб-приложение, которое компании смогут использовать для отслеживания выгорания своих сотрудников. Кроме того, сами сотрудники могут использовать его, чтобы следить за своим уровнем выгорания (нет времени на оценку психического здоровья в быстрой рабочей жизни 😔).
2. Сбор соответствующих данных
На питоне много библиотек — Beautiful Soap, Selenium для парсинга данных. Кроме того, существуют API-интерфейсы веб-скрейпинга, такие как ParseHub, Scrappy, Octoparse, которые сокращают время. Утилизация веб-страниц — важная часть проекта по науке о данных, поскольку жизненный цикл зависит от качества и актуальности данных.
В этом проекте набор данных взят из Kaggle. Посмотрите на данные, прежде чем читать дальше.
Набор данных
Ниже приведены атрибуты данных и их описание.
- Идентификатор сотрудника: уникальный идентификатор, присвоенный компанией каждому сотруднику.
- Дата присоединения: дата, когда сотрудник присоединился к компании.
- Пол: пол сотрудника.
- Тип компании: тип компании, в которой работает сотрудник (услуга/продукт).
- Доступна настройка WFH: доступна ли удаленная работа для сотрудника (Да/Нет).
- Назначение. Должность сотрудника в его/ее организации. В диапазоне — [0.0, 5.0] 0.0 — самое низкое обозначение, а 5.0 — самое высокое.
- Распределение ресурсов: количество ресурсов, выделенных сотруднику для работы, интерпретируется как количество рабочих часов. В диапазоне — [1.0, 10.0] (чем выше, тем больше ресурсов).
- Оценка умственной усталости: степень умственной усталости сотрудника за рабочее время в диапазоне — [0,0, 10,0], где 0,0 означает отсутствие усталости, а 10,0 означает полную усталость.
- Уровень выгорания: целевое значение в данных каждого сотрудника, указывающее уровень выгорания в течение рабочего времени в диапазоне — [0,0, 1,0], где чем выше значение, тем больше выгорание.
Несколько важных замечаний о данных:
1. Разница между стрессом и выгоранием в том, что выгорание — это другое состояние ума. В состоянии стресса вам все же удается справляться с давлением. Но как только наступает выгорание, у вас кончается бензин, и вы теряете всякую надежду преодолеть свои сдерживающие факторы.
2. Когда вы страдаете от выгорания, вы чувствуете больше, чем просто умственную усталость.
3. Подготовка данных и EDA
После сбора данных начинается подготовка данных. Он включает в себя очистку и организацию данных, которые, как известно, занимают более 80% работы специалистов по данным. Реальные данные необработаны и могут содержать дубликаты, пропущенные значения и неверную информацию. Следовательно, данные должны быть очищены.
После того, как данные были организованы, мы извлекаем информацию, заключенную в данных, и обобщаем ее основные характеристики с помощью исследовательского анализа данных. EDA — важный этап для четко определенного проекта по науке о данных. Он выполняется перед этапом статистического моделирования или моделирования машинного обучения.
Хватит теории, приступим к практической части!
df.info()

- Есть 5 категориальных признаков и 4 числовых признака. Из 5 функций полезны все, кроме идентификатора сотрудника, поскольку он не имеет ничего общего с целью, т. е. Burn Rate. Давайте сначала проведем исследовательский анализ данных, а вместе с этим преобразуем категориальные признаки в форму, понятную модели.
#Date and month might not be a useful feature. But the year of joining is. It has some significant information that the model can use.
df['Year of Joining'] = df['Date of Joining'].apply(lambda x : x.split('-')[0])
df['Year of Joining'].describe()
Вывод:функция имеет только одно уникальное значение, то есть 2008. Это может быть бесполезной функцией. Так что бросаем.
df.drop('Year of Joining',axis=1,inplace=True)
Давайте проведем EDA по другим категориальным функциям. Наряду с этим мы также выполним часть подготовки данных -
ЭДА
sns.boxplot(x = 'Gender', y = 'Burn Rate',data = df);

- Средний (медианный) уровень выгорания среди сотрудников-мужчин выше, чем у сотрудников-женщин. Давайте найдем возможные причины, сравнив другие факторы, такие как назначение, рабочее время и т. д. среди двух полов в компании.
Примечание. На диаграмме видно, что в записях Burn Rate сотрудников-женщин есть выбросы. Мы должны позаботиться об этом.
sns.countplot(x = 'Gender',palette=sns.color_palette("hls"),data = df);

- Данные содержат больше записей о сотрудниках-женщинах.
sns.countplot(x = 'Designation',hue ='Gender',palette='Blues_d',data = df);

- Мужчин на более высоких должностях больше (>2,0).
sns.boxplot(x = 'Gender', y = 'Resource Allocation',palette=sns.color_palette("Paired"),data = df);

- В то время как большинство сотрудников-женщин работают до 8 часов, сотрудники-мужчины работают до 10 часов. Средняя продолжительность рабочего времени (медиана) между обоими полами отличается на 1 час.
Примечание. Мы снова наблюдаем выбросы в функции Распределение ресурсов в записях о женщинах.
sns.boxplot(x = 'Company Type', y = 'Mental Fatigue Score',palette=sns.color_palette("hls"),data = df);

- Показатели усталости остаются одинаковыми для двух типов компаний. Примечание –выбросы
sns.scatterplot(x = 'Mental Fatigue Score', y = 'Burn Rate',hue = 'Designation',palette=sns.color_palette("rocket"),data = df);

- Это указывает на очень сильную линейную зависимость между Показателем усталости и Уровнем выгорания.
sns.heatmap(df.corr(),annot = True)

- Помимо показателя умственной усталости, показатель выгорания показывает сильную положительную линейную связь с распределением ресурсов.
Категорное кодирование
Это процесс преобразования текстовых данных (данных под категориальной переменной) в числа, чтобы модель могла понять и извлечь из них ценную информацию. Вот несколько мощных методов категориального кодирования.
- Порядковое кодирование — включает сопоставление каждой уникальной метки с целочисленным значением. Этот тип кодирования рекомендуется только в том случае, если существует известный порядок/отношение между категориями. Например. Возьмем в качестве примера переменную Качество продукта со значениями: «Отлично», «Очень хорошо», «Хорошо», «Средне» и «Плохо». Поскольку эти данные имеют порядок, т. е. «Отлично», «Очень хорошо», «Хорошо», «Средне», «Плохо», мы можем использовать порядковое кодирование для отображения: «Плохо»: 0, «Средне»: 1, «Хорошо». ’: 2, «Очень хорошо»: 3, «Отлично»: 4.
- Одно горячее кодирование — это преобразование данных в двоичные значения на основе наличия категорий. Это используется только тогда, когда между категориями нет порядка/отношения. Например, в наших данных есть переменная с именем Gender, которая состоит из значений — Male и Female. Поскольку порядка нет, используем One Hot Encoding.
Продолжим практическую часть -
df.drop('Employee ID',axis=1,inplace=True)
- Мы будем использовать «Дату присоединения» в части разработки функций.
dat = pd.Series(df['Date of Joining'])
df.drop('Date of Joining',axis=1,inplace=True)
#Applying One hot encoding -
cat = []
num = []
for feat in df.columns:
if(df[feat].dtype=='object'):
cat.append(feat)
else:
num.append(feat)
encoder = OneHotEncoder(handle_unknown='ignore', sparse=False)
dummy_df = pd.DataFrame(encoder.fit_transform((df[cat])))
dummy_df.index = df.index
dummy_df.columns = encoder.get_feature_names(cat)
df.drop(cat,axis=1,inplace = True)
df = pd.concat([df,dummy_df],axis=1)
df.drop(['WFH Setup Available_No','Gender_Male','Company Type_Product'],axis=1,inplace=True)
Работа с отсутствующими значениями
Вот некоторые из методов, которые используются для обработки нулевых значений в данных:
- Вменение случайной выборки. Он включает в себя выборку случайных наблюдений из набора данных и их использование для замены нулевых значений. Это уместно, когда данные отсутствуют полностью случайным образом (MCAR).
- Ввод среднего/медианы.Это метод заполнения отсутствующих данных средним/медианным значением соответствующего атрибута. Это уместно, когда данные отсутствуют случайным образом (MAR).
- Прогнозирование нулевых значений.Если имеется высокая коллинеарность между атрибутом с нулевыми значениями и атрибутом, не содержащим нулевых значений, можно использовать линейную регрессию для прогнозирования и заполнения отсутствующих значений.
df.isnull().sum()
- Нулевые значения во всех столбцах отсутствуют случайным образом. Вероятно, сотрудники не хотели/не решались делиться такой информацией, как показатель стресса, уровень выгорания и рабочее время.
- Отсутствуют значения в параметрах Показатель выгорания и Показатель умственной усталости. Представим себе их распределение.
sns.heatmap(df.isnull())

df[df[‘Burn Rate’].isnull()][‘Mental Fatigue Score’].isnull().sum()
- Мы наблюдаем, что нулевые значения в двух функциях не встречаются одновременно (всего 172 значения одновременно) для данной выборки. Таким образом, мы вычисляем отсутствующее значение цели с помощью показателя умственной усталости и наоборот с помощью линейной регрессии из-за их сильной коллинеарности. Мы будем использовать медианное/среднее вменение для остальных пропущенных значений (где нулевые значения встречаются одновременно).
x1 = pd.DataFrame(df[df['Burn Rate'].isnull()]['Mental Fatigue Score']) x1['Mental Fatigue Score'].fillna(x1['Mental Fatigue Score'].median(),inplace=True) #Filling the simultaneously occuring null values for Mental Fatigue Score with median imputation. #Imputation using Linear Regression df_new = df[['Mental Fatigue Score','Burn Rate']] #the training data with no null values df_new.dropna(inplace=True)
- Использование показателя умственной усталости в качестве независимого признака и показателя выгорания в качестве зависимого признака в модели линейной регрессии.
X = df_new[['Mental Fatigue Score']] # X.shape should be (N, M) where M >= 1 y = df_new['Burn Rate'] model = LinearRegression() model.fit(X,y) pred = model.predict(x1) #the values to be filled in nan values of burn rate ind = df[df['Burn Rate'].isnull()].index for j,i in enumerate(ind): df['Burn Rate'].iloc[i] = pred[j] #Filling the missing values with the predicted values
- Выполните те же действия, чтобы заполнить недостающие значения в оценке умственной усталости. На этот раз мы используем Burn Rate как независимую характеристику и показатель умственной усталости как зависимую характеристику для модели линейной регрессии.
X = df_new[['Burn Rate']] # X.shape should be (N, M) where M >= 1 y = df_new['Mental Fatigue Score'] model = LinearRegression() model.fit(X,y) x2 = pd.DataFrame(df[df['Mental Fatigue Score'].isnull()]['Burn Rate']) pred = model.predict(x2) ind = df[df['Mental Fatigue Score'].isnull()].index for j,i in enumerate(ind): df['Mental Fatigue Score'].iloc[i] = pred[j]
- У нас остались недостающие значения в Распределение ресурсов. Мы будем использовать тот же метод, т. е. линейную регрессию, для вменения нулевых значений параметра Распределение ресурсов, поскольку он имеет высокую корреляцию с Назначением. Независимой функцией будет Обозначение, а целевой функцией будет Распределение ресурсов.
df_new = df[['Designation','Resource Allocation']] df_new.dropna(inplace=True) X = df_new[['Designation']] y = df_new['Resource Allocation'] model = LinearRegression() model.fit(X,y) des = pd.DataFrame(df[df['Resource Allocation'].isnull(['Designation']) pred2 = model.predict(des) ind = df[df['Resource Allocation'].isnull()].index for j,i in enumerate(ind): df['Resource Allocation'].iloc[i] = pred2[j]
Выбросы
Пришло время удалить выбросы, которые мы обнаружили в нескольких функциях во время EDA, используя визуализацию коробчатой диаграммы.
Выбросы — это точки в данных, которые сильно отклоняются от других наблюдений. Наличие выбросов может испортить и ввести в заблуждение процесс обучения модели. Поэтому важно с ними справиться.
Здесь мы будем использовать логарифмическое преобразование данных, чтобы избавиться от выбросов. Мы будем использовать этот метод для признаков с модулем их асимметрии >0,5, поскольку данные с асимметрией менее 0,5 имеют достаточно симметричное распределение.
for feat in num: if((np.abs(df[feat].skew())>0.5)): print(feat) df[feat] =np.log1p(df[feat])[0]
Вывод:нет объектов с асимметрией больше 0,5. Мы завершили подготовку данных и EDA!
3. Разработка функций
Процесс Feature Engineering включает в себя извлечение функций из необработанных данных на основе их понимания. Более того, разработка признаков также включает в себя выбор признаков, преобразование признаков и построение признаков для подготовки входных данных, которые наилучшим образом соответствуют алгоритмам машинного обучения. Feature Engineering напрямую влияет на результаты модели и, следовательно, является важной частью науки о данных.
Давайте используем Дата присоединения для создания новой функции — Проведенные дни, которая будет содержать информацию о том, сколько дней сотрудник проработал в компании до настоящего времени. Показатель выгорания для сотрудника, проработавшего много лет, возможно, будет намного выше, чем для нового сотрудника.
present = date.today()
date_df = pd.DataFrame(dat)
date_df['Date of Joining'] = date_df['Date of Joining'].apply(lambda x : datetime.strptime(x,"%Y-%m-%d").date())
df['Days Spent'] = present - date_df['Date of Joining']
#Get the total number of days spent in the company
df['Days Spent'] = df['Days Spent'].apply(lambda x : int(str(x).split(" ")[0]))
Давайте проверим, повлияет ли новая функция на цель —
sns.scatterplot(x = df[‘Days Spent’],y = df[‘Burn Rate’]);

- На диаграмме рассеяния видно, что в данных Показатель сжигания калорий и Проведенное количество дней нет тенденций. Поэтому мы опускаем Проведенные дни, так как это не имеет значения в данных.
df.drop(‘Days Spent’,axis=1,inplace=True)
4. Построение и оценка модели
Давайте найдем производительность различных методов ансамбля на данных — 1.XGBoost 2.AdaBoost 3.RandomForest.
Пожалуйста, прочитайте этот блог для понимания ансамблевых техник и их различных типов. Мы будем использовать R-квадрат в качестве метрики, поскольку мы обучаем регрессионную модель.
model1 = XGBRegressor()
X = df.drop('Burn Rate',axis=1)
y = df['Burn Rate']
score = cross_val_score(model1,X,y,cv = 5,scoring = 'r2')
#K fold cross validation
score.mean()
Вывод:0,9323
model2 = AdaBoostRegressor() score = cross_val_score(model2,X,y,cv = 5,scoring = ‘r2’) score.mean()
Выход:0,9134
model3 = RandomForestRegressor() score = cross_val_score(model3,X,y,cv = 5,scoring = ‘r2’) score.mean()
Вывод:0,9216
Все 3 алгоритма дали довольно хорошие результаты. Мы можем еще больше улучшить результаты, импровизируя предыдущие этапы жизненного цикла, такие как масштабирование данных, изменение метода вменения нулевых значений и категориальное кодирование. , выполняя выбор функций, чтобы избавиться от ненужных функций, оптимизацию гиперпараметров и т. д.
Оптимизация гиперпараметров –
Именно метод выбора оптимального набора гиперпараметров для алгоритма ML дает наилучшие результаты по выбранной метрике. Случайный поиск, поиск по сетке, Optuna и т. д. — вот некоторые из методов настройки гиперпараметров алгоритма обучения.
Давайте оптимизируем гиперпараметры XGBoost —
regressor = XGBRegressor()
param_grid = {
"colsample_bytree" : np.arange(0.1,1,0.1),
"gamma" : np.arange(0.01,0.1,0.01),
"learning_rate" : np.arange(0.01,0.1,0.01),
"max_depth" : np.arange(2,10),
"n_estimators" : np.arange(1500,2500,100),
"reg_alpha" : np.arange(0,1,0.1),
"reg_lambda" : np.arange(0,1,0.1),
"subsample" : np.arange(0.1,1,0.1),
"silent" : [1],
"nthread" : [-1],
}
model = RandomizedSearchCV(
estimator= regressor,
param_distributions= param_grid,
n_iter=20,
scoring = 'r2',
verbose= 3,
n_jobs=1,
cv = 5)
model.fit(X,y)
model.best_score_
Вывод:0,9327
- Настройка гиперпараметров дала небольшое увеличение оценки модели. Так что мы идем с этой настроенной модели.
Сохраните модель -
filename = ‘bunrout_model_xgb.pkl’ pickle.dump(model, open(filename, ‘wb’))
Пришло время для развертывания модели! Полный код для предыдущих этапов проекта.
5. Развертывание

Введение
Теперь наша цель состоит в том, чтобы создать веб-приложение, которое получает входную информацию от пользователя и дает прогноз уровня выгорания для пользователя. Для его создания мы будем использовать Flask, API Python, который позволяет нам создавать приложения. После создания приложения мы развернем его на Heroku.
Примечание. Другие платформы, на которых мы можем развертывать модели машинного обучения: Amazon AWS, Microsoft Azure, Google Cloud и т. д.
Входной информацией, которую мы собираем от пользователя, будут функции, на которых была обучена наша прогнозирующая модель Burn Rate —
- Обозначение работы пользователя в компании в диапазоне - [0–5]: 5 — наивысшее обозначение, 0 — низшее.
- Количество рабочих часов.
- Оценка умственной усталости в диапазоне - [0–10]: Насколько сильно усталость/усталость обычно ощущает пользователь в рабочее время.
- Пол Мужской Женский
- Тип компании: Сервис/Продукт
- Вы работаете из дома: Да/Нет
Сборка приложения на фляге
Создайте файл .py для приложения. Импортируйте все важные библиотеки -
из фляги импортировать флягу, render_template
import os from flask import request import pickle import pandas as pd import numpy as np from xgboost import XGBRegressor
- Запустите приложение Flask и загрузите модель сохранения —
app = Flask(__name__) #Initialize the flask app model = pickle.load(open(“model\\bunrout_model_xgb.pkl”, ‘rb’))
- Выполните маршрутизацию приложения для домашней функции, которая будет отображать HTML-страницу -
@app.route(‘/’) def home(): return render_template(‘index.html’)
- Создайте функцию «прогнозировать», которая будет самой важной работой бэкэнда, т. е. будет выдавать прогнозы на основе пользовательского ввода.
def predict():
if(request.method=="POST"):
int_feat = ['Designation', 'Resource Allocation', 'Mental Fatigue Score', 'Gender', 'Company Type', 'WFH Setup Available']
l = []
for i in int_feat:
val = int(request.form[i])
l.append(val)
#convert into array of shape -> (1,6)
feat_arr = np.array(l).reshape(-1,1).reshape(1,6)
input = pd.DataFrame(feat_arr,columns = ['Designation', 'Resource Allocation', 'Mental Fatigue Score', 'Gender_Female', 'Company Type_Service', 'WFH Setup Available_Yes'])
prediction = float(model.predict(input)[0])
prediction = round(prediction, 2)
#top 25 percentile
if(prediction<=0.3):
feedback1 = "Fantastic! You have a low burnout of {} .".format(prediction)
return render_template("index_1.html",color = "color:#33CC00;",feedback = feedback1)
#top 25 percentile to 75 percentile
elif((prediction>0.3) & (prediction<=0.59)):
feedback2 = "Not bad...You have a moderate burnout of {} .".format(prediction)
return render_template("index_1.html",color = "color:#339900;",feedback = feedback2)
#top 75 percentile to 90 percentile
elif((prediction>0.59) & (prediction<=0.80)):
feedback3 = "Oops!! You have a high burnout of {} .".format(prediction)
return render_template("index_1.html",color = "color:#FF0000;",feedback = feedback3)
#top 90 percentile to 99 percentile
elif((prediction>0.8) & (prediction<=0.9)):
feedback4 = "Ouch!!! You have a very high burnout of {} .".format(prediction)
return render_template("index_1.html",color ="color:#CC0000;",feedback = feedback4)
#top 99 percentile
else:
feedback5 = "Sorry! You have an extremly high burnout of {} .".format(prediction)
return render_template("index_1.html",color ="color:#990000;",feedback = feedback5)
- Вы можете отображать некоторые сообщения вместе с прогнозом, как в приведенном выше коде, чтобы описать степень их скорости выгорания.
- Определите основную функцию -
if __name__ == "__main__":
app.run('debug'==True)
Ваше приложение Flask готово! Пройдите через index.html для внешней части веб-приложения.
Примечание. Создайте файл index.html и сохраните его в каталоге с именем — templates.
Развертывание приложения на Heroku
Для развертывания на Heroku требуется файл reuirement.txt. Требования к этому проекту -
Flask==1.1.1 pandas==1.0.1 numpy==1.18.1 xgboost==0.90 gunicorn==20.1.0 scikit-learn>=0.18
Теперь создайте Procfile, который представляет собой текстовый файл без расширения .txt. Он определяет команды, которые выполняются приложением при запуске. Procfile для этого проекта -
web: gunicorn app:app
Теперь загрузите проект в свой Github и подключите репозиторий к Heroku для развертывания.

Поздравляем! Мы успешно создали и развернули наше веб-приложение ✌️
Ссылка веб-приложения: https://burnout-rate-prediction-api.herokuapp.com
К нему можно получить доступ из любого места и использовать для проверки психического здоровья сотрудников.
Импровизация
Еще одним важным этапом после развертывания является импровизация этапов жизненного цикла проекта для предоставления наилучшего решения.
Заключение
Итак, мы подошли к концу руководства. От понимания постановки проблемы до предоставления комплексного решения — все это мы рассмотрели в одном блоге!
Проверьте весь код проекта — https://github.com/YashK07/Burnout-Rate-Prediction-Heroku. Пожалуйста, запустите его 🌟, если вы чувствуете, что приложение того стоит. Счастливого обучения !!