Из «Бета-Банка» стали уходить клиенты. Каждый месяц. Немного, но заметно. Банковские маркетологи посчитали: сохранять текущих клиентов дешевле, чем привлекать новых.
Нужно спрогнозировать, уйдёт клиент из банка в ближайшее время или нет. Вам предоставлены исторические данные о поведении клиентов и расторжении договоров с банком.
Постройте модель с предельно большим значением F1-меры. Чтобы сдать проект успешно, нужно довести метрику до 0.59. Проверьте F1-меру на тестовой выборке самостоятельно.
Дополнительно измеряйте AUC-ROC, сравнивайте её значение с F1-мерой.
Источник данных: https://www.kaggle.com/barelydedicated/bank-customer-churn-modeling
(... загрузка данных и библиотек ...)
# просмотр первых трех строк новой таблицы
df.head(3)
# общие характеристики и проверка на наличие выбивающихся значений
df.describe()
# оценка корреляций признаков
df.corr()
Из матрицы корреляций видно, что возраст и баланс на счету коррелируют с тем, что покинет клиент банк или нет. То есть чем старше клиент и у него больше денег на счету тем больше шансов, что он покинет банк. И наоборот, чем более активен клиент, тем менее вероятно он покинет банк (эти параметры антикоррелируют).
# визуализация корреляций
sns.pairplot(data=df, hue = 'Exited')
Посмотрим данные столбика с пустыми значениями "Наличие недвижимости":
# просмотр уникальных значений этого признака
df['Tenure'].value_counts()
# построим график
sns.countplot( x= 'Tenure', hue = 'Exited', data = df)
Количество недвижимости(за исключением 0 и 10) распределено равномерно
Изучим чуть подробнее влияние наличия недвижимости
#создадим дополнительную колонку "наличие значений в колонке недвижимости" (потом ее уберем)
df['has_tenure'] = df['Tenure'].notnull()
sns.countplot( x= 'has_tenure', hue = 'Exited', data = df)
Из гистограммы видно, что соотношение клиент ушел (1) или остался в банке (0) примерно одинаковое 1 к 4 и для пустых и непустых значений в колонке "tenure". Поэтому эти пустые значения не являются критичными для данной задачи.
Рассмотрим нулевые значение в колонке "Баланс", которые обратили на себя внимание Их достаточно большое количество (25%). Посмотрим на гистограмму этой колонки
df.hist('Balance', bins=50 )
Посмотрим искажают ли как-то пустые значения колонки "tenure" значения колонки "Баланс" (вдруг все нулевые или вообще без нулей)
plt.hist(df[df["has_tenure"]==True]["Balance"], color = 'limegreen', alpha=0.5,
edgecolor="black", bins = 20)
plt.hist(df[df["has_tenure"]==False]["Balance"], color = 'indianred', alpha=0.5,
edgecolor="black", bins = 20)
red_patch = mpatches.Patch(color='limegreen', label='True')
green_patch = mpatches.Patch(color='indianred', label='False')
plt.legend(handles=[red_patch, green_patch])
plt.show()
Посмотрим как влияет уход клиента на "Баланс"
plt.hist(df[df["Exited"]==1]["Balance"], color = 'limegreen', alpha=0.5,
edgecolor="black", bins = 50)
plt.hist(df[df["Exited"]==0]["Balance"], color = 'indianred', alpha=0.5,
edgecolor="black", bins = 50)
red_patch = mpatches.Patch(color='limegreen', label='1')
green_patch = mpatches.Patch(color='indianred', label='0')
plt.legend(handles=[red_patch, green_patch])
plt.show()
Влияние пустых значений колонки "tenure" на кредитный рейтинг
plt.hist(df[df["has_tenure"]==True]["CreditScore"], color = 'limegreen', alpha=0.5,
edgecolor="black", bins = 20)
plt.hist(df[df["has_tenure"]==False]["CreditScore"], color = 'indianred', alpha=0.5,
edgecolor="black", bins = 20)
red_patch = mpatches.Patch(color='limegreen', label='True')
green_patch = mpatches.Patch(color='indianred', label='False')
plt.legend(handles=[red_patch, green_patch])
plt.show()
Распределение пустых значений колонки "tenure" достаточно симметрично, за исключением самых высоких значений кредитного рейтинга
Влияние ухода клиента на кредитный рейтинг
plt.hist(df[df["Exited"]==1]["CreditScore"], color = 'limegreen', alpha=0.5,
edgecolor="black", bins = 20)
plt.hist(df[df["Exited"]==0]["CreditScore"], color = 'indianred', alpha=0.5,
edgecolor="black", bins = 20)
red_patch = mpatches.Patch(color='limegreen', label='1')
green_patch = mpatches.Patch(color='indianred', label='0')
plt.legend(handles=[red_patch, green_patch])
plt.show()
Клиенты с низким кредитным рейтингом (меньше 420) покинули банк
# удалим созданную для анализа колонку "has_tenure"
df = df.drop('has_tenure', axis=1)
Обратимся к нашей таблице теперь как к таблице с признаками для построения моделей машинного обучения
# просмотр признаков
df.head()
Посмотрим на значения колонки с целевым признаком "Exited"
df['Exited'].value_counts()
В этом столбце видно, что клиентов, которые ушли (1) гораздо меньше чем тех, кто остался (0). Примерно 1 к 4. То есть наблюдается дисбаланс классов. Учтем его позже
В обновленной таблице две колонки с категориальными признаками: "Gender" and "Geography".
# уникальные значения колонки "Пол"
df['Gender'].value_counts()
Два пола: мужской и женский
# уникальные значения колонки "Geography"
df['Geography'].value_counts()
Три страны: Фанция, Германия и Испания
Преобразуем категориальные признаки в численные с момощью техники прямого кодирования, или отображения (One-Hot Encoding) и удалим по одному столбцу из вновь созданных с помощью этой техники (drop_first=True), чтобы ихбежать дамми-ловушку
df_ohe = pd.get_dummies(df, drop_first=True)
# удаление строк из таблицы
df_no_null = df_ohe.dropna()
# перенумерация
df_no_null = df_no_null.reset_index(drop = True)
df_no_null.info()
df_no_null.tail(3)
#Делаем отдельные датасеты для признаков features и целевого признака target
target = df_no_null['Exited']
features = df_no_null.drop('Exited', axis=1)
# 20% данных оставляем для тестовой выборки
features_train_val, features_test, target_train_val, target_test = train_test_split(
features, target, test_size=0.20, random_state=12345)
# проверка размера тестовой выборки после разбиения данных
features_test.shape
# создадим две выборки обучающую и валидационную
features_train, features_val, target_train, target_val = train_test_split(
features_train_val, target_train_val, test_size=0.25, random_state=12345)
# проверка размера валидационной выборки после разбиения данных
features_val.shape
# проверка размера обучающей выборки после разбиения данных
features_train.shape
Проведем стандартизацию данных, воспользуясь методом масштабирования. На первом шаге создадим список с названиями колонок, которые требуют масштабирования.
# список названий колонок с признаками, которые требуют стандартизации
numeric = ['CreditScore', 'Age', 'Balance', 'EstimatedSalary']
# создадим объект структуры для стандартизации данных
scaler = StandardScaler()
# настроим его на обучающих данных, т. е. вычислим среднее и дисперсию
scaler.fit(features_train[numeric])
# преобразуем обучающую выборку функцией transform()
features_train[numeric] = scaler.transform(features_train[numeric])
# преобразуем валидационную выборку функцией transform()
features_val[numeric] = scaler.transform(features_val[numeric])
# преобразуем тестовую выборку функцией transform()
features_test[numeric] = scaler.transform(features_test[numeric])
# просмотр как выглядит обучающая выборка после всех преобразований
features_train.head()
Мы выяснили, что целевой признак несбалансирован. Дисбаланс примерно 1 к 4. На первом этапе обучим модели без учета диссбаланса классов. Возьмем две модели машинного обучения: Случайный лес и Логистическую регрессию
# Построим объект класса RandomForestClassifier, используя параметры по умолчанию
model_rf = RandomForestClassifier(random_state=12345)
# запускаем обучение на обучающей выборке
model_rf.fit(features_train, target_train)
# предсказываем значения целевого признака валидационных данных
predicted_val_rf = model_rf.predict(features_val)
# Посчитаем вероятность классов
probabilities_val_rf = model_rf.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_rf = probabilities_val_rf[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_rf = roc_auc_score(target_val, probabilities_one_val_rf)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_val, predicted_val_rf)))
#Площадь по ROC-кривой
print('Площадь под кривой ошибок (ROC-кривой): {:.3f}'.format(auc_roc_rf))
# создаем объекта класса LogisticRegression c гиперпараметрами по умолчанию
model_lr = LogisticRegression(random_state=12345, solver = 'liblinear')
# запускаем обучение на обучающей выборке
model_lr.fit(features_train, target_train)
# предсказываем значения целевого признака валидационных данных
predicted_val_lr = model_lr.predict(features_val)
# Посчитаем вероятность классов
probabilities_val_lr = model_lr.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_lr = probabilities_val_lr[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_lr = roc_auc_score(target_val, probabilities_one_val_lr)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_val, predicted_val_lr)))
#Площадь по ROC-кривой
print('Площадь под кривой ошибок (ROC-кривой): {:.3f}'.format(auc_roc_lr))
Как было отмечено выше, наблюдается дисбаланс классов целевого признака. Мы можем его убрать с помощью нескольких подходов:
# Построим объект класса RandomForestClassifier с учетом взвешивания class_weight='balanced'
model_rf_b = RandomForestClassifier(random_state=12345, class_weight='balanced')
# запускаем обучение на обучающей выборке
model_rf_b.fit(features_train, target_train)
# предсказываем значения целевого признака валидационных данных
predicted_val_rf_b = model_rf_b.predict(features_val)
# Посчитаем вероятность классов
probabilities_val_rf_b = model_rf_b.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_rf_b = probabilities_val_rf_b[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_rf_b = roc_auc_score(target_val, probabilities_one_val_rf_b)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_val, predicted_val_rf_b)))
#Площадь по ROC-кривой
print('Площадь под кривой ошибок (ROC-кривой): {:.3f}'.format(auc_roc_rf_b))
# создаем объекта класса LogisticRegression c гиперпараметрами по умолчанию и с учетом взвешивания классов
model_lr_b = LogisticRegression(random_state=12345, solver = 'liblinear', class_weight='balanced')
# запускаем обучение на обучающей выборке
model_lr_b.fit(features_train, target_train)
# предсказываем значения целевого признака валидационных данных
predicted_val_lr_b = model_lr_b.predict(features_val)
# Посчитаем вероятность классов
probabilities_val_lr_b = model_lr_b.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_lr_b = probabilities_val_lr_b[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_lr_b = roc_auc_score(target_val, probabilities_one_val_lr_b)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_val, predicted_val_lr_b)))
#Площадь по ROC-кривой
print('Площадь под кривой ошибок (ROC-кривой): {:.3f}'.format(auc_roc_lr_b))
Создадим функцию upsample, которая будет преобразовывать нашу обучающую выборку:
8 нулей и 2 единицы: соотношение классов 1 к 4. Будем подавать коээфициент 4 на вход нашей функции чтобы количество нулей и 1 стало одинаковым в новой выборке
# тестируем функцию
features_tested_ups, target_tested_ups = upsample(features_tested, target_tested, 4)
# проверяем как отработала функция
target_tested_ups.value_counts()
Получилось сбалансировать классы: 8 нулей и 8 едениц.
Применим функцию ко всей нашей обучающей выборке. Как мы видели выше диссбаланс классов примерно 1 к 4, поэтому коэффициент repeat будет равен 4 в нашем случае
features_upsampled, target_upsampled = upsample(features_train, target_train, 4)
features_upsampled.info()
# соотношение классов до применения функции upsampled
target_train.value_counts()
# соотношение классов после применения функции
target_upsampled.value_counts()
Классы стали сбалансированными.
Проведем подбор параметров для "Случайного леса". Так как гиперпараметров для этого алгоритма достаточно много, то сфокусируемся на трех: n_estimamators - количество деревьев, max_features - число признаков, по которым ищется разбиение и min_samples_leaf
# создадим словарь с гипперпараметрами и их значениями
grid = {'n_estimators': range(10, 210, 10), 'max_features': [2, 4, 8], 'min_samples_leaf': [2,3,4,5]}
# пустой список, в который будут записываться значения метрики F1
f1_rf = []
# пустой список, в который будет записоваться значение метрики AUC-ROC
auc_roc_rf = []
# создаем объекта класса RandomForestClassifier
rfc = RandomForestClassifier(random_state=12345)
# Организуем цикл по сетке параметров.
for g in ParameterGrid(grid):
#распаковывваем словарь и передаем его элемент в функцию .set_params
rfc.set_params(**g)
# запускаем обучение на обучающей выборке
rfc.fit(features_upsampled, target_upsampled)
# предсказываем значения целевого признака валидационных данных
predictions_val = rfc.predict(features_val)
# F1-мера - среднее гармоническое полноты и точности. Важна для правильного прогнозирования класса 1
f1_score_rf = f1_score(target_val, predictions_val)
# Посчитаем вероятность классов
probabilities_val_rfc = rfc.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_rfc = probabilities_val_rfc[:, 1]
# AUC-ROC площадь под кривой ошибок, показывает насколько модель далека/близка от/к случайной (случайная 0.5)
auc_roc_score_rf = roc_auc_score(target_val, probabilities_one_val_rfc)
# записываем значение метрик в соответствующие списки
f1_rf.append(f1_score_rf)
auc_roc_rf.append(auc_roc_score_rf)
#print (f1_score_rf, auc_roc_score_rf)
# Лучшие значения гиперпараметров для валидационной выборки
best_idx = np.argmax(f1_rf) # выбираем с максимальным значением F1-меры
print(f1_rf[best_idx], auc_roc_rf[best_idx], ParameterGrid(grid)[best_idx])
Выбререм несколько гиперпараметров логистической регрессии для насройки модели: параметр регуляризации C, dual - оптимизация прямая или двойственная, max_iter - максимальное количество итераций. Выберем алгоритм использования в задаче оптимизации solver = 'liblinear' - рекомендованный slkearn библиотекой для небольших датасетов.
# создадим словарь с гипперпараметрами и их значениями
grid = {'C': [1.0, 2.5, 5, 10, 100, 1000], 'dual': [True,False], 'max_iter': [100,110,120,130,140]} # , np.logspace(-1, 4, 10)'max_features': [2, 4, 8]
# пустой список, в который будут записываться значения метрики F1
f1_lr = []
# пустой список, в который будет записоваться значение метрики AUC-ROC
auc_roc_lr = []
# создаем объекта класса LogisticRegression
lr = LogisticRegression(random_state=12345, solver = 'liblinear') #solver = 'liblinear'
# Организуем цикл по сетке параметров.
for g in ParameterGrid(grid):
#распаковывваем словарь и передаем его элемент в функцию .set_params
lr.set_params(**g)
# запускаем обучение на обучающей выборке
lr.fit(features_upsampled, target_upsampled)
# предсказываем значения целевого признака валидационных данных
predictions_val = lr.predict(features_val)
# F1-мера - среднее гармоническое полноты и точности. Важна для правильного прогнозирования класса 1
f1_score_lr = f1_score(target_val, predictions_val)
# Посчитаем вероятность классов
probabilities_val_lr = lr.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_lr = probabilities_val_lr[:, 1]
# AUC-ROC площадь под кривой ошибок, показывает насколько модель далека/близка от/к случайной (случайная 0.5)
auc_roc_score_lr = roc_auc_score(target_val, probabilities_one_val_lr)
# записываем значение метрик в соответствующие списки
f1_lr.append(f1_score_lr)
auc_roc_lr.append(auc_roc_score_lr)
#print (f1_score_lr, auc_roc_score_lr)
# Лучшие значения гиперпараметров для валидационной выборки
best_idx = np.argmax(f1_lr) # выбираем с максимальным значением F1-меры
print(f1_lr[best_idx], auc_roc_lr[best_idx], ParameterGrid(grid)[best_idx])
Техника "upsampling" незначительно помогла с улучшением значения F1-меры для модели логистической регрессии: F1 стала 0.502 против 0.5 при применениии техники "взешивания" классов, при этом значение AUC-ROC стало немного лучше: 0.777 против 0.773
Попробуем третий вариант решения проблемы несбалансированных классов: данные с признаками, для котрых целевой признак 1 (меньший класс) оставим без изменения, а данные для котрых целевой признак 0 сократим согласно пропорции дисбаланса. Создадим функцию Downsample
features_downsampled, target_downsampled = downsample(features_train, target_train, 0.25)
target_downsampled.value_counts()
Классы примерно сбалансированы теперь.
# создадим словарь с гипперпараметрами и их значениями
grid = {'n_estimators': range(10, 210, 10), 'max_features': [2, 4, 8], 'min_samples_leaf': [2,3,4,5]}
# пустой список, в который будут записываться значения метрики F1
f1_rf = []
# пустой список, в который будет записоваться значение метрики AUC-ROC
auc_roc_rf = []
# создаем объекта класса RandomForestClassifier
rfc = RandomForestClassifier(random_state=12345)
# Организуем цикл по сетке параметров.
for g in ParameterGrid(grid):
#распаковывваем словарь и передаем его элемент в функцию .set_params
rfc.set_params(**g)
# запускаем обучение на обучающей выборке
rfc.fit(features_downsampled, target_downsampled)
# предсказываем значения целевого признака валидационных данных
predictions_val = rfc.predict(features_val)
# F1-мера - среднее гармоническое полноты и точности. Важна для правильного прогнозирования класса 1
f1_score_rf = f1_score(target_val, predictions_val)
# Посчитаем вероятность классов
probabilities_val_rfc = rfc.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_rfc = probabilities_val_rfc[:, 1]
# AUC-ROC площадь под кривой ошибок, показывает насколько модель далека/близка от/к случайной (случайная 0.5)
auc_roc_score_rf = roc_auc_score(target_val, probabilities_one_val_rfc)
# записываем значение метрик в соответствующие списки
f1_rf.append(f1_score_rf)
auc_roc_rf.append(auc_roc_score_rf)
#print (f1_score_rf, auc_roc_score_rf)
# Лучшие значения гиперпараметров для валидационной выборки
best_idx = np.argmax(f1_rf) # выбираем с максимальным значением F1-меры
print(f1_rf[best_idx], auc_roc_rf[best_idx], ParameterGrid(grid)[best_idx])
Лучшая модель "Случайного леса" обладает параметрами: n_estimators = 180, max_features = 4, min_samples_leaf = 3. При таких параметрах F1-мера равна 0.602 на валидационной выборке (при требуемых 0.59) и меньше, чем при применеии техники upsampling. AUC-ROC при этом равна 0.85, что несколько больше значения, чем с применением подхода "upsampling"
# создадим словарь с гипперпараметрами и их значениями
grid = {'C': [1.0, 2.5, 5, 10, 100, 1000], 'dual': [True,False], 'max_iter': [100,110,120,130,140]} # , np.logspace(-1, 4, 10)'max_features': [2, 4, 8]
# пустой список, в который будут записываться значения метрики F1
f1_lr = []
# пустой список, в который будет записоваться значение метрики AUC-ROC
auc_roc_lr = []
# создаем объекта класса LogisticRegression
lr = LogisticRegression(random_state=12345, solver = 'liblinear') #solver = 'liblinear'
# Организуем цикл по сетке параметров.
for g in ParameterGrid(grid):
#распаковывваем словарь и передаем его элемент в функцию .set_params
lr.set_params(**g)
# запускаем обучение на обучающей выборке
lr.fit(features_downsampled, target_downsampled)
# предсказываем значения целевого признака валидационных данных
predictions_val = lr.predict(features_val)
# F1-мера - среднее гармоническое полноты и точности. Важна для правильного прогнозирования класса 1
f1_score_lr = f1_score(target_val, predictions_val)
# Посчитаем вероятность классов
probabilities_val_lr = lr.predict_proba(features_val)
# Значения вероятностей класса «1»
probabilities_one_val_lr = probabilities_val_lr[:, 1]
# AUC-ROC площадь под кривой ошибок, показывает насколько модель далека/близка от/к случайной (случайная 0.5)
auc_roc_score_lr = roc_auc_score(target_val, probabilities_one_val_lr)
# записываем значение метрик в соответствующие списки
f1_lr.append(f1_score_lr)
auc_roc_lr.append(auc_roc_score_lr)
#print (f1_score_lr, auc_roc_score_lr)
# Лучшие значения гиперпараметров для валидационной выборки
best_idx = np.argmax(f1_lr) # выбираем с максимальным значением F1-меры
print(f1_lr[best_idx], auc_roc_lr[best_idx], ParameterGrid(grid)[best_idx])
Техника "downsampling" незначительно помогла с улучшением значения F1-меры для модели логистической регрессии: F1 стала 0.503 против 0.502 при применениии техники "взешивания" классов, при этом значение AUC-ROC стало немного лучше: 0.777 против 0.775
Проведем тестирование этой лучшей модели
rfc_best = RandomForestClassifier(random_state=12345, n_estimators = 30, max_features = 4, min_samples_leaf = 5)
# запускаем обучение на обучающей выборке
rfc_best.fit(features_upsampled, target_upsampled)
# проверка работы модели на тестовой выборке
predictions_test_rfc_best = rfc_best.predict(features_test)
# Посчитаем вероятность классов
probabilities_test_rfc_best = rfc_best.predict_proba(features_test)
# Значения вероятностей класса «1»
probabilities_one_val_rfc_best = probabilities_test_rfc_best[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_rfc_best = roc_auc_score(target_test, probabilities_one_val_rfc_best)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_test, predictions_test_rfc_best)))
print('AUC-ROC площадь под кривой ошибок: {:.3f}'.format(auc_roc_rfc_best))
df_no_tenure = df_ohe.drop('Tenure', axis=1)
df_no_tenure.info()
df_no_tenure.head(3)
#Делаем отдельные датасеты для признаков features и целевого признака target
target_no_tenure = df_no_tenure['Exited']
features_no_tenure = df_no_tenure.drop('Exited', axis=1)
# 20% данных оставляем для тестовой выборки
features_no_tenure_train_val, features_no_tenure_test, target_no_tenure_train_val, target_no_tenure_test = train_test_split(
features_no_tenure, target_no_tenure, test_size=0.20, random_state=12345)
# проверка размера тестовой выборки после разбиения данных
features_no_tenure_test.shape
# создадим две выборки обучающую и валидационную
features_no_tenure_train, features_no_tenure_val, target_no_tenure_train, target_no_tenure_val = train_test_split(
features_no_tenure_train_val, target_no_tenure_train_val, test_size=0.25, random_state=12345)
# проверка размера валидационной выборки после разбиения данных
features_no_tenure_val.shape
# проверка размера обучающей выборки после разбиения данных
features_no_tenure_train.shape
Проведем стандартизацию данных, воспользуясь методом масштабирования. Объект структуры для стандартизации данных мы уже создавали выше scaler = StandardScaler(). Поэтому будем его испотльзовать, для настройки новых обучающих данных
# настроим его на обучающих данных, т. е. вычислим среднее и дисперсию
scaler.fit(features_no_tenure_train[numeric])
# преобразуем обучающую выборку функцией transform()
features_no_tenure_train[numeric] = scaler.transform(features_no_tenure_train[numeric])
# преобразуем валидационную выборку функцией transform()
features_no_tenure_val[numeric] = scaler.transform(features_no_tenure_val[numeric])
# преобразуем тестовую выборку функцией transform()
features_no_tenure_test[numeric] = scaler.transform(features_no_tenure_test[numeric])
Так как присутствует дисбаланс классов, и выше мы увидели, что техника upsample для "Случайного леса" работает лучше всего в борьбе с этим дисбалансом, то применим ее далее
features_no_tenure_upsampled, target_no_tenure_upsampled = upsample(features_no_tenure_train, target_no_tenure_train, 4)
# проверяем как отработала функция, ихбавились от дисбаланса или нет
target_no_tenure_upsampled.value_counts()
features_no_tenure_upsampled.info()
# создадим словарь с гипперпараметрами и их значениями
grid = {'n_estimators': range(10, 210, 10), 'max_features': [2, 4, 8], 'min_samples_leaf': [2,3,4,5]}
# пустой список, в который будут записываться значения метрики F1
f1_rf = []
# пустой список, в который будет записоваться значение метрики AUC-ROC
auc_roc_rf = []
# создаем объекта класса RandomForestClassifier
rfc = RandomForestClassifier(random_state=12345)
# Организуем цикл по сетке параметров.
for g in ParameterGrid(grid):
#распаковывваем словарь и передаем его элемент в функцию .set_params
rfc.set_params(**g)
# запускаем обучение на обучающей выборке
rfc.fit(features_no_tenure_upsampled, target_no_tenure_upsampled)
# предсказываем значения целевого признака валидационных данных
predictions_no_tenure_val = rfc.predict(features_no_tenure_val)
# F1-мера - среднее гармоническое полноты и точности. Важна для правильного прогнозирования класса 1
f1_score_rf = f1_score(target_no_tenure_val, predictions_no_tenure_val)
# Посчитаем вероятность классов
probabilities_no_tenure_val = rfc.predict_proba(features_no_tenure_val)
# Значения вероятностей класса «1»
probabilities_no_tenure_one_val = probabilities_no_tenure_val[:, 1]
# AUC-ROC площадь под кривой ошибок, показывает насколько модель далека/близка от/к случайной (случайная 0.5)
auc_roc_score_rf = roc_auc_score(target_no_tenure_val, probabilities_no_tenure_one_val)
# записываем значение метрик в соответствующие списки
f1_rf.append(f1_score_rf)
auc_roc_rf.append(auc_roc_score_rf)
#print (f1_score_rf, auc_roc_score_rf)
# Лучшие значения гиперпараметров для валидационной выборки
best_idx = np.argmax(f1_rf) # выбираем с максимальным значением F1-меры
print(f1_rf[best_idx], auc_roc_rf[best_idx], ParameterGrid(grid)[best_idx])
Проведем проверку нашей модели на тестовой выборке
rfc_no_tenure_best = RandomForestClassifier(random_state=12345, n_estimators = 60, max_features = 2, min_samples_leaf = 5)
# запускаем обучение на обучающей выборке
rfc_no_tenure_best.fit(features_no_tenure_upsampled, target_no_tenure_upsampled)
# предсказываем значения на тестовой выборке
predictions_no_tenure_test_rfc_best = rfc_no_tenure_best.predict(features_no_tenure_test)
# Посчитаем вероятность классов
probabilities_no_tenure_test_rfc_best = rfc_no_tenure_best.predict_proba(features_no_tenure_test)
# Значения вероятностей класса «1»
probabilities_no_tenure_one_test_rfc_best = probabilities_no_tenure_test_rfc_best[:, 1]
#Выясним, как сильно наша модель отличается от случайной, посчитаем площадь под ROC-кривой — AUC-ROC
auc_roc_no_tenure_rfc_best = roc_auc_score(target_no_tenure_test, probabilities_no_tenure_one_test_rfc_best)
# делаем отчет по основным метрикам модели
print('Отчет по метрикам\n {}'.format(classification_report(target_no_tenure_test, predictions_no_tenure_test_rfc_best)))
print('AUC-ROC площадь под кривой ошибок: {:.3f}'.format(auc_roc_no_tenure_rfc_best))
Значение F1-меры на тестовой выборке равно 0.63, значение площади под кривой ошибок: 0.86