Классификационная модель обучена 14 249 сотрудников
В HR хорошо известно, что набор новых сотрудников значительно дороже, чем сохранение существующих талантов. Уходящие сотрудники берут с собой ценный опыт и знания из вашей организации. По данным Forbes, стоимость смены должности начального уровня оценивается в 50% от заработной платы этого сотрудника. Для сотрудников среднего звена она составляет 125% от зарплаты, а для руководителей высшего звена - колоссальные 200% от зарплаты.
Мы обучим некоторые модели машинного обучения в Блокноте Jupyter, используя данные о должности, счастье сотрудника. , производительность, рабочая нагрузка и срок полномочий для прогнозирования собираются ли они остаться или уйти.
Наша целевая переменная категориальна, поэтому задача машинного обучения - классификация. (Для числовой цели задача становится регрессией.)
Мы будем использовать набор данных с elitedatascience.com, который имитирует крупную компанию с 14 249 бывшими и настоящими сотрудниками. Всего 10 столбцов.
Шаги следующие:
- EDA и обработка данных: исследуйте, визуализируйте и очищайте данные.
- Разработка функций: используйте опыт в предметной области и создавайте новые функции.
- Обучение модели: мы обучим и настроим некоторые проверенные алгоритмы классификации, такие как логистическая регрессия, случайные леса и деревья с градиентным усилением.
- Оценка эффективности. Мы рассмотрим ряд оценок, включая F1 и AUROC.
- Развертывание: пакетный запуск или попросите инженеров по обработке данных / инженеров машинного обучения построить автоматизированный конвейер?
В идеале компания будет использовать модель для своих нынешних постоянных сотрудников, чтобы выявлять тех, кто находится в группе риска. Это пример машинного обучения, дающего действенную информацию о бизнесе.
1. Исследование и обработка данных
Исследовательский анализ данных (EDA) помогает нам понять данные и предоставляет идеи и идеи для очистки данных и разработки функций. Очистка данных подготавливает данные для наших алгоритмов, в то время как разработка функций - это волшебный соус, который действительно поможет нашим алгоритмам извлечь основные закономерности из набора данных. Помнить:
Лучшие данные всегда лучше, чем более сложные алгоритмы!
Начнем с загрузки некоторых стандартных пакетов Python для обработки данных в JupyterLab.
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sb from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.model_selection import train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, roc_auc_score import pickle
Импортируйте набор данных:
df = pd.read_csv('employee_data.csv')
Вот снова снимок нашего фрейма данных. Форма - (14,249, 10).
Целевая переменная - статус. Эта категориальная переменная принимает значение Занят или Влево.
Есть 25 столбцов / функций:
- отдел
- зарплата
- удовлетворение, поданная_ жалоба - доводы счастья
- last_evaluation, Recent_promoted - прокси для производительности
- avg_monthly_hrs, n_projects - прокси для рабочей нагрузки
- срок полномочий - прокси для опыта
1.1 Числовые характеристики
Давайте построим несколько быстрых гистограмм, чтобы получить представление о распределении наших числовых характеристик.
df.hist(figsize=(10,10), xrot=-45)
Что нужно сделать с нашими числовыми функциями, чтобы данные работали правильно с нашими алгоритмами:
- Преобразуйте NaN в filed_complaint и Recent_promoted в 0. Они были неправильно помечены.
- Перед преобразованием NaN в ноль создайте индикаторную переменную для отсутствующих данных в функции last_evaluation.
df.filed_complaint.fillna(0, inplace=True) df.recently_promoted.fillna(0, inplace=True) df['last_evaluation_missing'] = df.last_evaluation.isnull().astype(int) df.last_evaluation.fillna(0, inplace=True)
Вот тепловая карта корреляции для наших числовых характеристик.
sb.heatmap(df.corr(), annot=True, cmap=’RdBu_r’, vmin=-1, vmax=1)
1.2 Категориальные признаки
Давайте построим несколько графиков быстрой панели для наших категориальных функций. Сиборн для этого отлично подходит.
for feature in df.dtypes[df.dtypes=='object'].index: sb.countplot(data=df, y='{}'.format(features))
Самый большой отдел - это продажи. Лишь небольшая часть сотрудников имеет высокую зарплату. И наш набор данных несбалансирован в том смысле, что только меньшая часть сотрудников покинула компанию, то есть лишь небольшая часть наших сотрудников имеет status = Left . Это имеет разветвления для показателей, которые мы выбираем для оценки производительности наших алгоритмов. Подробнее об этом мы поговорим в разделе «Результаты».
С точки зрения очистки данных классы IT и information_technology для функции отдела должны быть объединены вместе:
df.department.replace('information_technology', 'IT', inplace=True)
Более того, HR заботится только о постоянных сотрудниках, поэтому мы должны отфильтровать временный отдел:
df = df[df.department != 'temp']
Таким образом, наша функция отдела должна выглядеть примерно так:
Что нужно сделать с нашими категориальными функциями, чтобы гарантировать, что данные будут хорошо работать с нашими алгоритмами:
- Отсутствующие данные для функции отдела следует объединить в отдельный класс Отсутствует.
- Категориальные характеристики отдел и зарплата также должны быть быстро закодированы.
- Целевая переменная status должна быть преобразована в двоичную.
df['department'].fillna('Missing', inplace=True) df = pd.get_dummies(df, columns=['department', 'salary']) df['status'] = pd.get_dummies(df.status).Left
1.3 Сегментация
Мы можем получить дальнейшее понимание, сегментируя числовые характеристики по сравнению с категориальными. Начнем с некоторых одномерных сегментов.
В частности, мы собираемся сегментировать числовые характеристики, представляющие счастье, производительность, рабочую нагрузку и опыт, по нашей категориальной целевой переменной статус.
Сегментировать удовлетворенность по статусу:
sb.violinplot(y='status', x='satisfaction', data=df)
Понимание состоит в том, что ряд уволенных сотрудников остались очень довольны своей работой.
Статус сегмента last_evaluation:
sb.violinplot(y='status', x='last_evaluation', data=df)
Понимание состоит в том, что большое количество уволенных сотрудников были высокоэффективными. Возможно, они больше не чувствовали возможности роста, оставаясь?
Сегментируйте avg_monthly_hrs и n_projects по статусу:
sb.violinplot(y='status', x='avg_monthly_hrs', data=df) sb.violinplot(y='status', x='n_projects', data=df)
Похоже, что те, кто ушел, как правило, имели либо довольно большую рабочую нагрузку, либо довольно низкую рабочую нагрузку. Представляют ли они сгоревших и уволенных бывших сотрудников?
Сегментирование владения по статусу:
sb.violinplot(y='status', x='tenure', data=df)
Отметим, что внезапный отток сотрудников в течение 3-го года. Те, кто все еще живет после 6 лет, как правило, остаются.
2. Разработка функций
Обратите внимание на следующие двумерные сегменты, которые послужат стимулом для разработки наших функций в дальнейшем.
Для каждого графика мы разделим две числовые характеристики (представляющие счастье, производительность, рабочую нагрузку или опыт) по статусу. Это может дать нам несколько кластеров, основанных на стереотипах сотрудников.
Производительность и счастье:
К сожалению, занятые сотрудники затрудняют чтение этой диаграммы. Давайте просто отобразим только работников Left, поскольку именно их мы действительно пытаемся понять.
sb.lmplot(x='satisfaction', y='last_evaluation', data=df[df.status=='Left'], fit_reg=False )
У нас есть три группы уволенных сотрудников:
- Отстающие: last_evaluation ‹0,6
- Недовольны: уровень удовлетворенности ‹0,2
- Успевающие: last_evaluation ›0,8 и удовлетворенность› 0,7
Рабочая нагрузка и производительность:
sb.lmplot(x='last_evaluation', y='avg_monthly_hrs', data=df[df.status=='Left'], fit_reg=False )
У нас есть две группы уволенных сотрудников:
- Звезды: avg_monthly_hrs ›215 и last_evaluation› 0,75
- Slackers: avg_monthly_hrs ‹165 и last_evaluation‹ 0,65
Работа и счастье:
sb.lmplot(x='satisfaction', y='avg_monthly_hrs', data=df[df.status=='Left'], fit_reg=False, )
У нас есть три группы уволенных сотрудников:
- Трудоголики: avg_monthly_hrs ›210 и удовлетворение› 0,7
- Просто работа: avg_monthly_hrs ‹170
- Перегружен: в среднем_месяц_часов ›225 и удовлетворенность‹ 0,2
Давайте разработаем новые функции для этих 8 «стереотипных» групп сотрудников:
df['underperformer'] = ((df.last_evaluation < 0.6) & (df.last_evaluation_missing==0)).astype(int) df['unhappy'] = (df.satisfaction < 0.2).astype(int) df['overachiever'] = ((df.last_evaluation > 0.8) & (df.satisfaction > 0.7)).astype(int) df['stars'] = ((df.avg_monthly_hrs > 215) & (df.last_evaluation > 0.75)).astype(int) df['slackers'] = ((df.avg_monthly_hrs < 165) & (df.last_evaluation < 0.65) & (df.last_evaluation_missing==0)).astype(int) df['workaholic'] = ((df.avg_monthly_hrs > 210) & (df.satisfaction > 0.7)).astype(int) df['justajob'] = (df.avg_monthly_hrs < 170).astype(int) df['overworked'] = ((df.avg_monthly_hrs > 225) & (df.satisfaction < 0.2)).astype(int)
Мы можем взглянуть на долю сотрудников в каждой из этих 8 групп.
df[['underperformer', 'unhappy', 'overachiever', 'stars', 'slackers', 'workaholic', 'justajob', 'overworked']].mean() underperformer 0.285257 unhappy 0.092195 overachiever 0.177069 stars 0.241825 slackers 0.167686 workaholic 0.226685 justajob 0.339281 overworked 0.071581
34% сотрудников являются сотрудниками только на работу, не вдохновляясь и только здесь для еженедельной проверки заработной платы, в то время как только 7% полностью перегружены работой.
Аналитическая базовая таблица. Набор данных после применения всех этих шагов по очистке данных и разработке функций является нашей аналитической базовой таблицей. Это данные, на которых мы обучаем наши модели.
В нашем ABT 14 068 сотрудников и 31 столбец - см. Фрагмент ниже. Вспомните, в нашем исходном наборе данных было 14 249 сотрудников и всего 10 столбцов!
3. Моделирование
Мы собираемся обучить четыре проверенные модели классификации:
- логистические регрессии (регуляризованные L1 и L2)
- случайные леса
- деревья с градиентным усилением
Во-первых, давайте разделим нашу аналитическую базовую таблицу.
y = df.status X = df.drop('status', axis=1)
Затем мы разделимся на обучающие и тестовые наборы. Наш набор данных слегка несбалансирован, поэтому мы воспользуемся стратифицированной выборкой для компенсации.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1234, stratify=df.status)
Мы создадим объект конвейера для обучения. Это упростит процесс обучения нашей модели.
pipelines = { 'l1': make_pipeline(StandardScaler(), LogisticRegression(penalty='l1', random_state=123)), 'l2': make_pipeline(StandardScaler(), LogisticRegression(penalty='l2', random_state=123)), 'rf': make_pipeline( RandomForestClassifier(random_state=123)), 'gb': make_pipeline( GradientBoostingClassifier(random_state=123)) }
Мы также хотим настроить гиперпараметры для каждого алгоритма. Для логистической регрессии наиболее важным гиперпараметром является сила регуляризации, C.
l1_hyperparameters = {'logisticregression__C' : [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10, 50, 100, 500, 1000] } l2_hyperparameters = {'logisticregression__C' : [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10, 50, 100, 500, 1000] }
Для нашего случайного леса мы настроим количество оценок (n_estimators), максимальное количество функций, которые следует учитывать во время разделения (max_features), и минимальное количество образцов, которое должно быть листом (min_samples_leaf).
rf_hyperparameters = { 'randomforestclassifier__n_estimators' : [100, 200], 'randomforestclassifier__max_features' : ['auto', 'sqrt', 0.33], 'randomforestclassifier__min_samples_leaf' : [1, 3, 5, 10] }
Для нашего дерева с градиентным усилением мы настроим количество оценок (n_estimators), скорость обучения и максимальную глубину каждого дерева (max_depth ).
gb_hyperparameters = { 'gradientboostingclassifier__n_estimators' : [100, 200], 'gradientboostingclassifier__learning_rate' : [0.05, 0.1, 0.2], 'gradientboostingclassifier__max_depth' : [1, 3, 5] }
Мы сохраним эти гиперпараметры в словаре.
hyperparameters = { 'l1' : l1_hyperparameters, 'l2' : l2_hyperparameters, 'rf' : rf_hyperparameters, 'gb' : gb_hyperparameters }
Наконец, мы настроим и настроим наши модели. Используя GridSearchCV, мы можем обучить все эти модели с перекрестной проверкой по всем нашим объявленным гиперпараметрам с помощью всего нескольких строк кода!
fitted_models = {} for name, pipeline in pipelines.items(): model = GridSearchCV(pipeline, hyperparameters[name], cv=10, n_jobs=-1) model.fit(X_train, y_train) fitted_models[name] = model
4. Оценка
Я написал специальную статью о популярных показателях машинного обучения, в том числе о тех, которые используются ниже.
4.1 Оценка производительности
Начнем с печати результатов перекрестной проверки. Это средняя производительность по 10 временным границам, позволяющая получить надежную оценку производительности модели, используя только данные вашего обучения.
for name, model in fitted_models.items(): print(name, model.best_score_) Output: l1 0.9088324151412831 l2 0.9088324151412831 rf 0.9793851075173272 gb 0.975475386529234
Переходя к тестовым данным, мы:
- рассчитать точность;
- распечатайте матрицу путаницы и вычислите точность, отзывчивость и показатель F1;
- отобразите ROC и вычислите показатель AUROC.
Точность измеряет долю правильно помеченных прогнозов, однако этот показатель не подходит для несбалансированных наборов данных, например фильтрация спама в электронной почте (спам или не спам) и медицинское тестирование (больной или здоровый). Например, если в нашем наборе данных только 1% сотрудников удовлетворяет target = Left, то модель, которая всегда прогнозирует, что сотрудник все еще работает в компании, мгновенно получит 99%. точность. В таких ситуациях более уместны точность или отзыв. Что бы вы ни использовали, зависит от того, хотите ли вы минимизировать ошибки типа 1 (ложные срабатывания) или ошибки типа 2 (ложные отрицательные результаты). Для спамовых писем ошибки типа 1 хуже (некоторый спам допустим, если вы случайно не отфильтруете важное письмо!), В то время как ошибки типа 2 неприемлемы для медицинского тестирования (когда кому-то говорят, что у него нет рака это катастрофа!). F1-оценка дает вам лучшее из обоих миров, взяв средневзвешенное значение точности и запоминания.
Область под ROC, известная как AUROC, является еще одним стандартным показателем для проблем классификации. Это эффективное измерение способности классификатора различать классы и отделять сигнал от шума. Этот показатель также устойчив к несбалансированным наборам данных.
Вот код для создания этих оценок и графиков:
for name, model in fitted_models.items(): print('Results for:', name) # obtain predictions pred = fitted_models[name].predict(X_test) # confusion matrix cm = confusion_matrix(y_test, pred) print(cm) # accuracy score print('Accuracy:', accuracy_score(y_test, pred)) # precision precision = cm[1][1]/(cm[0][1]+cm[1][1]) print('Precision:', precision) # recall recall = cm[1][1]/(cm[1][0]+cm[1][1]) print('Recall:', recall) # F1_score print('F1:', f1_score(y_test, pred)) # obtain prediction probabilities pred = fitted_models[name].predict_proba(X_test) pred = [p[1] for p in pred] # plot ROC fpr, tpr, thresholds = roc_curve(y_test, pred) plt.title('Receiver Operating Characteristic (ROC)') plt.plot(fpr, tpr, label=name) plt.legend(loc='lower right') plt.plot([0,1],[0,1],'k--') plt.xlim([-0.1,1.1]) plt.ylim([-0.1,1.1]) plt.ylabel('True Positive Rate (TPR) i.e. Recall') plt.xlabel('False Positive Rate (FPR)') plt.show() # AUROC score print('AUROC:', roc_auc_score(y_test, pred))
Логистическая регрессия (регуляризация L1):
Output: [[2015 126] [ 111 562]] Accuracy: 0.9157782515991472 Precision: 0.8168604651162791 Recall: 0.8350668647845468 F1: 0.8258633357825129 AUROC: 0.9423905869485105
Логистическая регрессия (L2-регуляризованная):
Output: [[2014 127] [ 110 563]] Accuracy: 0.9157782515991472 Precision: 0.8159420289855073 Recall: 0.836552748885587 F1: 0.8261188554658841 AUROC: 0.9423246556128734
Дерево с градиентным усилением:
Output: [[2120 21] [ 48 625]] Accuracy: 0.9754797441364605 Precision: 0.9674922600619195 Recall: 0.9286775631500743 F1: 0.9476876421531464 AUROC: 0.9883547910913578
Случайный лес:
Output: [[2129 12] [ 45 628]] Accuracy: 0.9797441364605544 Precision: 0.98125 Recall: 0.9331352154531947 F1: 0.9565879664889566 AUROC: 0.9916117990718256
Выигрышный алгоритм - случайный лес с AUROC 99% и F1-оценкой 96%. Этот алгоритм имеет 99% шанс отличить левого и занятого работника… очень хорошо!
Из 2814 сотрудников в тестовой выборке алгоритм:
- правильно классифицировал 628 левых рабочих (истинно положительных результатов) при 12 неправильных (ошибках типа I), и
- правильно классифицировал 2129 занятых работников (True Negative), получив 45 неправильных ответов (ошибки типа II).
К вашему сведению, вот гиперпараметры выигравшего случайного леса, настроенные с помощью GridSearchCV.
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features=0.33, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0, n_estimators=200, n_jobs=None, oob_score=False, random_state=123, verbose=0, warm_start=False )
4.2 Важность функций
Рассмотрим следующий код.
coef = winning_model.feature_importances_ ind = np.argsort(-coef) for i in range(X_train.shape[1]): print("%d. %s (%f)" % (i + 1, X.columns[ind[i]], coef[ind[i]])) x = range(X_train.shape[1]) y = coef[ind][:X_train.shape[1]] plt.title("Feature importances") ax = plt.subplot() plt.barh(x, y, color='red') ax.set_yticks(x) ax.set_yticklabels(X.columns[ind]) plt.gca().invert_yaxis()
Будет напечатан список функций, ранжированных по важности, и соответствующая гистограмма.
Ranking of feature importance: 1. n_projects (0.201004) 2. satisfaction (0.178810) 3. tenure (0.169454) 4. avg_monthly_hrs (0.091827) 5. stars (0.074373) 6. overworked (0.068334) 7. last_evaluation (0.063630) 8. slackers (0.028261) 9. overachiever (0.027244) 10. workaholic (0.018925) 11. justajob (0.016831) 12. unhappy (0.016486) 13. underperformer (0.006015) 14. last_evaluation_missing (0.005084) 15. salary_low (0.004372) 16. filed_complaint (0.004254) 17. salary_high (0.003596) 18. department_engineering (0.003429) 19. department_sales (0.003158) 20. salary_medium (0.003122) 21. department_support (0.002655) 22. department_IT (0.001628) 23. department_finance (0.001389) 24. department_management (0.001239) 25. department_Missing (0.001168) 26. department_marketing (0.001011) 27. recently_promoted (0.000983) 28. department_product (0.000851) 29. department_admin (0.000568) 30. department_procurement (0.000296)
Есть три особенно сильных предиктора оттока сотрудников:
- n_projects (рабочая нагрузка)
- удовлетворение (счастье) и
- владение (опыт).
Более того, эти две спроектированные функции также имеют высокий рейтинг по важности:
- звезды (хорошее настроение и загруженность) и
- перегружен (низкий уровень счастья и высокая загруженность).
Интересно, но не совсем удивительно. Звезды, возможно, ушли в поисках лучших возможностей, в то время как переутомленные ушли после сгорания.
5. Развертывание
Исполняемую версию этой модели (.pkl) можно сохранить из записной книжки Jupyter.
with open('final_model.pkl', 'wb') as f: pickle.dump(fitted_models['rf'].best_estimator_, f)
HR может предварительно обработать данные о новых сотрудниках, прежде чем вводить их в обученную модель. Это называется запуском партии.
В большой организации они могут захотеть развернуть модель в производственной среде, обратившись к инженерам данных и инженерам машинного обучения. Эти специалисты создают автоматизированный конвейер на основе нашей модели, обеспечивая возможность предварительной обработки свежих данных и регулярной передачи прогнозов в HR.
Заключительные комментарии
Мы начали с бизнес-проблемы: HR в крупной компании хотели получить практическую информацию об уходе сотрудников.
Мы обучили победившую модель случайного леса на большом количестве исторических данных, охватывающих более 14 000 бывших и настоящих сотрудников.
Отдел кадров может обрабатывать новые данные в нашем обученном файле .pkl вручную, или их инженерный отдел может создать автоматизированный конвейер.
Наша модель была бинарной классификационной моделью, в которой целевая переменная является категориальной. Он предсказывает дискретное количество возможных вариантов - здесь отток или без оттока.
Другой стороной медали для обучения с учителем являются регрессионные модели, целевая переменная которых является числовой. Здесь я обучил человека, который предсказывает цены на жилье.
Наконец, я написал статью здесь о том, где машинное обучение находится в области математического моделирования.
Мои социальные сети
Зарегистрируйтесь в Medium
Впервые на Medium?
Поддержите мой анализ, подписавшись здесь!
Я заработаю небольшую комиссию без каких-либо дополнительных затрат для вас.
Хорошего дня.