Microsegment.ru
  • Главная страница
  • О проекте
  • Портфолио
  • Блог

Ручной подбор гиперпараметров модели классификации для прогноза оттока клиентов из банка

Описание проекта

Из «Бета-Банка» стали уходить клиенты. Каждый месяц. Немного, но заметно. Банковские маркетологи посчитали: сохранять текущих клиентов дешевле, чем привлекать новых.

Нужно спрогнозировать, уйдёт клиент из банка в ближайшее время или нет. Вам предоставлены исторические данные о поведении клиентов и расторжении договоров с банком.

Постройте модель с предельно большим значением F1-меры. Чтобы сдать проект успешно, нужно довести метрику до 0.59. Проверьте F1-меру на тестовой выборке самостоятельно.

Дополнительно измеряйте AUC-ROC, сравнивайте её значение с F1-мерой.

Источник данных: https://www.kaggle.com/barelydedicated/bank-customer-churn-modeling

Описание атрибутов датафрейма:

  1. RowNumber — индекс строки в данных
  2. CustomerId — уникальный идентификатор клиента
  3. Surname — фамилия
  4. CreditScore — кредитный рейтинг
  5. Geography — страна проживания
  6. Gender — пол
  7. Age — возраст
  8. Tenure — сколько лет человек является клиентом банка
  9. Balance — баланс на счёте
  10. NumOfProducts — количество продуктов банка, используемых клиентом
  11. HasCrCard — наличие кредитной карты
  12. IsActiveMember — активность клиента
  13. EstimatedSalary — предполагаемая зарплата
  14. Exited — факт ухода клиента (целевой признак)

Оглавление

  • 1  Подготовка данных
    • 1.1  Загрузка библиотек и их компонентов
    • 1.2  Анализ данных
    • 1.3  Предварительная обработка данных
    • 1.4  Итоги подготовки данных
  • 2  Исследование задачи
    • 2.1  Изучение моделей без учёта дисбаланса
      • 2.1.1  Разделение датафрейма на обучающие, валидационные и тестовые выборки
      • 2.1.2  Разделение выборок на предназначенные для «деревянных» и логистических моделей с использованием порядкового и OHE декодирования категориальных признаков
      • 2.1.3  Приведение данных к единому масштабу
      • 2.1.4  Анализ полученных выборок
      • 2.1.5  Создание функции и датафрейма для анализа метрик качества модели
      • 2.1.6  Модель решающего дерева без балансировки классов
      • 2.1.7  Модель случайного леса без балансировки классов
      • 2.1.8  Проверка на мультиколлинеарность выборок для логистических моделей
      • 2.1.9  Модель логистической регрессии без балансировки классов
      • 2.1.10  Результаты использования моделей без балансировки классов
    • 2.2  Исследование баланса классов
      • 2.2.1  Модель решающего дерева с балансировкой весов классов внутри модели
      • 2.2.2  Модель случайного леса с балансировкой весов классов внутри модели
      • 2.2.3  Модель логистической регрессии с балансировкой весов классов внутри модели
      • 2.2.4  Результаты использования моделей с балансировкой весов классов внутри модели
    • 2.3  Итоги исследования задачи
  • 3  Борьба с дисбалансом
    • 3.1  Балансировка обучающей выборкой путем увеличения количества объектов класса «1»
      • 3.1.1  Модель решающего дерева со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»
      • 3.1.2  Модель случайного леса со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»
      • 3.1.3  Модель логистической регрессии со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»
      • 3.1.4  Результаты использования моделей со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»
    • 3.2  Балансировка классов за счет уменьшения обучающей выборки класса «0»
      • 3.2.1  Модель решающего дерева со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»
      • 3.2.2  Модель случайного леса со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»
      • 3.2.3  Модель логистической регрессии со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»
    • 3.3  Сравнение показателей качества моделей с разными способами балансировки обучающих «деревянными» и логистических выборок и без балансировки
  • 4  Тестирование модели
    • 4.1  Проведение тестирования
    • 4.2  Выводы тестирования
    • 4.3  Выводы проекта
  • 5  Чек-лист готовности проекта

Подготовка данных¶

Загрузка библиотек и их компонентов¶

In [1]:
# Импорт библиотек и их модулей
import pandas as pd
import numpy as nm

# Обработка данных
import re
from sklearn.preprocessing import(
    OrdinalEncoder, 
    LabelEncoder, 
    OneHotEncoder, 
    StandardScaler
) 
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle
# Модели классификации
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
# Показатели качества моделей
from sklearn.metrics import(
    mean_absolute_error, 
    accuracy_score, 
    f1_score, 
    roc_curve,
    roc_auc_score
) 
# Визуализация графиков
import seaborn as sns
import matplotlib.pyplot as plt

Анализ данных¶

In [2]:
# Чтение данных
try:
    data = pd.read_csv('/datasets/Churn.csv')
except:
    data = pd.read_csv('datasets/Churn.csv')
In [3]:
# Вывод данных для анализа
print(data.info()) 
data.head() 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10000 entries, 0 to 9999
Data columns (total 14 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   RowNumber        10000 non-null  int64  
 1   CustomerId       10000 non-null  int64  
 2   Surname          10000 non-null  object 
 3   CreditScore      10000 non-null  int64  
 4   Geography        10000 non-null  object 
 5   Gender           10000 non-null  object 
 6   Age              10000 non-null  int64  
 7   Tenure           9091 non-null   float64
 8   Balance          10000 non-null  float64
 9   NumOfProducts    10000 non-null  int64  
 10  HasCrCard        10000 non-null  int64  
 11  IsActiveMember   10000 non-null  int64  
 12  EstimatedSalary  10000 non-null  float64
 13  Exited           10000 non-null  int64  
dtypes: float64(3), int64(8), object(3)
memory usage: 1.1+ MB
None
Out[3]:
RowNumber CustomerId Surname CreditScore Geography Gender Age Tenure Balance NumOfProducts HasCrCard IsActiveMember EstimatedSalary Exited
0 1 15634602 Hargrave 619 France Female 42 2.0 0.00 1 1 1 101348.88 1
1 2 15647311 Hill 608 Spain Female 41 1.0 83807.86 1 0 1 112542.58 0
2 3 15619304 Onio 502 France Female 42 8.0 159660.80 3 1 0 113931.57 1
3 4 15701354 Boni 699 France Female 39 1.0 0.00 2 0 0 93826.63 0
4 5 15737888 Mitchell 850 Spain Female 43 2.0 125510.82 1 1 1 79084.10 0

Выявлено:

  1. Менее 1% объектов содержат пропуски в атрибуте Tenure. Удаление этих объектов не приведёт к потере качества данных датафрейма, а все оставшиеся объекты можно использовать в машиностроению обучении.
  2. Названия атрибутов не соответствуют «змеиному стилю». Их требуется привести к нижнему регистру.
  3. Датафрейм содержит 3 категориальных признака, требующих дополнительного анализа и возможной конвертации в численные типы данных для использовании в машинное обучении.
In [4]:
data.describe()
Out[4]:
RowNumber CustomerId CreditScore Age Tenure Balance NumOfProducts HasCrCard IsActiveMember EstimatedSalary Exited
count 10000.00000 1.000000e+04 10000.000000 10000.000000 9091.000000 10000.000000 10000.000000 10000.00000 10000.000000 10000.000000 10000.000000
mean 5000.50000 1.569094e+07 650.528800 38.921800 4.997690 76485.889288 1.530200 0.70550 0.515100 100090.239881 0.203700
std 2886.89568 7.193619e+04 96.653299 10.487806 2.894723 62397.405202 0.581654 0.45584 0.499797 57510.492818 0.402769
min 1.00000 1.556570e+07 350.000000 18.000000 0.000000 0.000000 1.000000 0.00000 0.000000 11.580000 0.000000
25% 2500.75000 1.562853e+07 584.000000 32.000000 2.000000 0.000000 1.000000 0.00000 0.000000 51002.110000 0.000000
50% 5000.50000 1.569074e+07 652.000000 37.000000 5.000000 97198.540000 1.000000 1.00000 1.000000 100193.915000 0.000000
75% 7500.25000 1.575323e+07 718.000000 44.000000 7.000000 127644.240000 2.000000 1.00000 1.000000 149388.247500 0.000000
max 10000.00000 1.581569e+07 850.000000 92.000000 10.000000 250898.090000 4.000000 1.00000 1.000000 199992.480000 1.000000

Все атрибуты отличаются друг от друга размерностью. Требуется их сбалансировать для улучшения качества моделей.

In [5]:
#
attr = 'Surname'
print(attr, ':', len(data[attr].unique())) 
print(data[attr].unique()) 
print() 
# 
attr = 'Gender'
print(attr, ':', len(data[attr].unique())) 
print(data[attr].unique()) 
print() 
# 
attr = 'Geography'
print(attr, ':', len(data[attr].unique())) 
print(data[attr].unique()) 
print() 
Surname : 2932
['Hargrave' 'Hill' 'Onio' ... 'Kashiwagi' 'Aldridge' 'Burbidge']

Gender : 2
['Female' 'Male']

Geography : 3
['France' 'Spain' 'Germany']

Атрибуты Gender и Geography предлагается преобразовать методом порядкового кодирования. Атрибут Surname предлагается удалить.

Выводы из анализа данных

Выявлено:

  1. Менее 1% объектов содержат пропуски в атрибуте Tenure. Удаление этих объектов не приведёт к потере качества данных датафрейма, а все оставшиеся обёртывания можно использовать в машиностроению обучении.
  2. Названия атрибутов не соответствуют «змеиному стилю». Их требуется привести к нижнему регистру.
  3. Датафрейм содержит 3 категориальных признака. Атрибуты Gender и Geography предлагается преобразовать методом порядкового кодирования. Атрибут Surname предлагается удалить из датафрейма в силу его предполагаемой недостаточной значимости.
  4. Все атрибуты отличаются друг от друга размерностью. Требуется их сбалансировать для улучшения качества моделей.

Предварительная обработка данных¶

In [6]:
# Приведение названий атрибутов к "змеиному стилю" 
#data.columns = data.columns.str.lower() 
data.columns = [re.sub(r'(?<!^)(?=[A-Z])', '_', i).lower() for i in data.columns] 
data.columns
Out[6]:
Index(['row_number', 'customer_id', 'surname', 'credit_score', 'geography',
       'gender', 'age', 'tenure', 'balance', 'num_of_products', 'has_cr_card',
       'is_active_member', 'estimated_salary', 'exited'],
      dtype='object')
In [7]:
# Удаление объектов с отсутствующими значениями признака "tenure" 
data = data.loc[data['tenure'].notnull()]
In [10]:
# Удаление атрибута "surname"
data = data.drop('surname', axis=1) 
In [11]:
# Удаление объектов с пустыми атрибутом "tenure" 
data = data.loc[data['tenure'].notnull()] 
In [12]:
# Проверка атрибутов датафрейма после их изменения
data.info() 
<class 'pandas.core.frame.DataFrame'>
Int64Index: 9091 entries, 0 to 9998
Data columns (total 13 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   row_number        9091 non-null   int64  
 1   customer_id       9091 non-null   int64  
 2   credit_score      9091 non-null   int64  
 3   geography         9091 non-null   object 
 4   gender            9091 non-null   object 
 5   age               9091 non-null   int64  
 6   tenure            9091 non-null   float64
 7   balance           9091 non-null   float64
 8   num_of_products   9091 non-null   int64  
 9   has_cr_card       9091 non-null   int64  
 10  is_active_member  9091 non-null   int64  
 11  estimated_salary  9091 non-null   float64
 12  exited            9091 non-null   int64  
dtypes: float64(3), int64(8), object(2)
memory usage: 994.3+ KB

Выводы из предварительной обработки данных

Проведены следующие работы:

  1. Названия атрибутов приведены к «змеиному стилю».
  2. Удален признак surname.
  3. Удалены объекты с отсутствующими значениями признака tenure.

Итоги подготовки данных¶

Анализ данных выявил:

  1. Удалены менее 1% объектов, содержащих пропуски в атрибуте Tenure. Удаление этих объектов не приведёт к потере качества данных датафрейма, а все оставшиеся обёртывания можно использовать в машиностроению обучении.
  2. Названия атрибутов приведены к «змеиному стилю».
  3. Датафрейм содержит 3 категориальных признака. Атрибут Surname удален из датафрейма в силу его предполагаемой недостаточной значимости. Атрибуты Gender и Geography будут преобразованы методом порядкового кодирования после разделения датафрейма на выборки.
  4. Все атрибуты отличаются друг от друга размерностью. После разделения датафрейма на выборки все атрибуты будут сбалансированы по размерности.

Исследование задачи¶

Изучение моделей без учёта дисбаланса¶

Не смотря на то, что все параметры далее будут переведены в числовые значения, в данной задаче требуется классификация по целевому признаку. Поэтому для решения задачи подойдут модели DecisionTreeClassifier, RandomForestClassifier и LogisticRegression. Для решение НЕ подходят модели регрессии DecisionTreeRegressor, RandomForestRegressor и LinearRegression.

Разделение датафрейма на обучающие, валидационные и тестовые выборки¶

In [13]:
# Разделение данных на выборки с целевыми данными и признаками
target = data['exited'] 
features = data.drop(['exited'], axis=1) 
# Разделение выборок на обучающие и валидационные
features_train, features_test, target_train, target_test = train_test_split(
    features, 
    target, 
    test_size=.4,
    stratify=target, 
    random_state=12345
) 
features_test, features_valid, target_test, target_valid = train_test_split(
    features_test, 
    target_test, 
    test_size=.5,
    stratify=target_test, 
    random_state=12345
)

print('Размеры выборок:')
print('features.     :', features.shape)
print('target.       :', target.shape)
print('features_train:', features_train.shape)
print('target_train  :', target_train.shape)
print('features_test :', features_test.shape)
print('target_test   :', target_test.shape)
print('features_valid:', features_valid.shape)
print('target_valid  :', target_valid.shape)
print('\nfeatures_test:')
features_test.head()
Размеры выборок:
features.     : (9091, 12)
target.       : (9091,)
features_train: (5454, 12)
target_train  : (5454,)
features_test : (1818, 12)
target_test   : (1818,)
features_valid: (1819, 12)
target_valid  : (1819,)

features_test:
Out[13]:
row_number customer_id credit_score geography gender age tenure balance num_of_products has_cr_card is_active_member estimated_salary
7493 7494 15683276 610 Spain Female 37 10.0 140363.95 2 1 1 129563.86
5460 5461 15668894 661 Germany Male 41 5.0 122552.48 2 0 1 120646.40
9126 9127 15666095 753 Spain Male 51 4.0 79811.72 2 0 1 68260.27
9801 9802 15578878 569 Spain Female 30 3.0 139528.23 1 1 1 33230.37
9101 9102 15660980 597 Spain Male 38 6.0 115702.67 2 1 1 25059.05

Разделение выборок на предназначенные для «деревянных» и логистических моделей с использованием порядкового и OHE декодирования категориальных признаков¶

In [14]:
# Порядковое кодирование категориальных признаков "gender" и "geography"
# для "деревянных" моделей
encoder = OrdinalEncoder(categories='auto', handle_unknown='ignore') 
encoder.fit(features_train)

features_train_wooden = pd.DataFrame(
    encoder.transform(features_train), 
    columns=features_train.columns, 
    index=features_train.index
)
features_valid_wooden = pd.DataFrame(
    encoder.transform(features_valid), 
    columns=features_valid.columns, 
    index=features_valid.index
)
features_test_wooden = pd.DataFrame(
    encoder.transform(features_test), 
    columns=features_test.columns, 
    index=features_test.index
)

print('features_test_wooden:')
features_test_wooden.head()
features_test_wooden:
Out[14]:
row_number customer_id credit_score geography gender age tenure balance num_of_products has_cr_card is_active_member estimated_salary
7493 0.0 0.0 205.0 2.0 0.0 19.0 10.0 0.0 1.0 1.0 1.0 0.0
5460 0.0 0.0 256.0 1.0 1.0 23.0 5.0 0.0 1.0 0.0 1.0 0.0
9126 0.0 0.0 348.0 2.0 1.0 33.0 4.0 0.0 1.0 0.0 1.0 0.0
9801 0.0 0.0 164.0 2.0 0.0 12.0 3.0 0.0 0.0 1.0 1.0 0.0
9101 0.0 0.0 192.0 2.0 1.0 20.0 6.0 0.0 1.0 1.0 1.0 0.0
In [15]:
# OneHotEncoder кодирование категориальных признаков "gender" и "geography"
# для логистических моделей
def OneHotEncoder_TrainValidTestTransform(features_train, 
                                          features_valid, 
                                          features_test, 
                                          column_name
                                         ):
    ohe = OneHotEncoder(
        categories='auto', 
        #drop='first', 
        handle_unknown='ignore', 
        sparse=False
    )
    ohe.fit(pd.DataFrame(features_train[column_name]))
    # features_train
    features_train = features_train.join(
        pd.DataFrame(
            ohe.transform(pd.DataFrame(features_train[column_name])), 
            columns=ohe.get_feature_names([column_name])
        )
    )
    features_train_logistic = features_train.drop(column_name, axis=1)
    # features_valid
    features_valid = features_valid.join(
        pd.DataFrame(
            ohe.transform(pd.DataFrame(features_valid[column_name])), 
            columns=ohe.get_feature_names([column_name])
        )
    )
    features_valid_logistic = features_valid.drop(column_name, axis=1)
    # features_test
    features_test = features_test.join(
        pd.DataFrame(
            ohe.transform(pd.DataFrame(features_test[column_name])), 
            columns=ohe.get_feature_names([column_name])
        )
    )
    features_test_logistic = features_test.drop(column_name, axis=1)
    
    return features_train_logistic, features_valid_logistic, features_test_logistic

"gender"
features_train_logistic, features_valid_logistic, features_test_logistic = OneHotEncoder_TrainValidTestTransform(
    features_train, 
    features_valid, 
    features_test, 
    'gender')

"geography"
features_train_logistic, features_valid_logistic, features_test_logistic = OneHotEncoder_TrainValidTestTransform(
    features_train_logistic, 
    features_valid_logistic, 
    features_test_logistic, 
    'geography')

print('features_test_logistic:')
features_test_logistic.head()
features_test_logistic:
Out[15]:
row_number customer_id credit_score age tenure balance num_of_products has_cr_card is_active_member estimated_salary gender_Female gender_Male geography_France geography_Germany geography_Spain
7493 7494 15683276 610 37 10.0 140363.95 2 1 1 129563.86 NaN NaN NaN NaN NaN
5460 5461 15668894 661 41 5.0 122552.48 2 0 1 120646.40 NaN NaN NaN NaN NaN
9126 9127 15666095 753 51 4.0 79811.72 2 0 1 68260.27 NaN NaN NaN NaN NaN
9801 9802 15578878 569 30 3.0 139528.23 1 1 1 33230.37 NaN NaN NaN NaN NaN
9101 9102 15660980 597 38 6.0 115702.67 2 1 1 25059.05 NaN NaN NaN NaN NaN
In [16]:
# Проверка наличия разных значений аттрибутов,
# созданных путем OHE кодирования, на предмет наличия пропусков
features_test_logistic['gender_Female'].unique()
Out[16]:
array([nan,  1.,  0.])
In [17]:
# Замена пропусков в новых аттрибутах на 0
features_train_logistic = features_train_logistic.fillna(0)
features_valid_logistic = features_valid_logistic.fillna(0)
features_test_logistic = features_test_logistic.fillna(0)

features_test_logistic['gender_Female'].unique()
Out[17]:
array([0., 1.])

Приведение данных к единому масштабу¶

In [18]:
# Приведение данных к единому масштабу
scaler = StandardScaler() 
# для "деревянных" моделей
scaler.fit(features_train_wooden)
features_train_wooden = pd.DataFrame(
    scaler.transform(features_train_wooden), 
    index=features_train_wooden.index, 
    columns=features_train_wooden.columns
)
features_valid_wooden = pd.DataFrame(
    scaler.transform(features_valid_wooden), 
    index=features_valid_wooden.index, 
    columns=features_valid_wooden.columns
) 
features_test_wooden = pd.DataFrame(
    scaler.transform(features_test_wooden), 
    index=features_test_wooden.index, 
    columns=features_test_wooden.columns
) 
# для логистических моделей
scaler.fit(features_train_logistic)
features_train_logistic = pd.DataFrame(
    scaler.transform(features_train_logistic), 
    index=features_train_logistic.index, 
    columns=features_train_logistic.columns
)
features_valid_logistic = pd.DataFrame(
    scaler.transform(features_valid_logistic), 
    index=features_valid_logistic.index, 
    columns=features_valid_logistic.columns
) 
features_test_logistic = pd.DataFrame(
    scaler.transform(features_test_logistic), 
    index=features_test_logistic.index, 
    columns=features_test_logistic.columns
) 

Анализ полученных выборок¶

In [19]:
# Анализ атрибутов тестовой выборки 
# для "деревянных" моделей
print(features_test_wooden.info()) 
print() 
features_test_wooden.describe()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1818 entries, 7493 to 4661
Data columns (total 12 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   row_number        1818 non-null   float64
 1   customer_id       1818 non-null   float64
 2   credit_score      1818 non-null   float64
 3   geography         1818 non-null   float64
 4   gender            1818 non-null   float64
 5   age               1818 non-null   float64
 6   tenure            1818 non-null   float64
 7   balance           1818 non-null   float64
 8   num_of_products   1818 non-null   float64
 9   has_cr_card       1818 non-null   float64
 10  is_active_member  1818 non-null   float64
 11  estimated_salary  1818 non-null   float64
dtypes: float64(12)
memory usage: 249.2 KB
None

Out[19]:
row_number customer_id credit_score geography gender age tenure balance num_of_products has_cr_card is_active_member estimated_salary
count 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000
mean -1.731733 -1.731733 0.029663 -0.050233 0.047846 0.017180 -0.010780 -0.956339 -0.036475 -0.024180 -0.008073 -1.731495
std 0.000000 0.000000 0.995222 0.977624 0.995012 1.033504 1.011652 0.000000 0.973909 1.010950 1.000530 0.010144
min -1.731733 -1.731733 -2.561551 -0.918068 -1.089426 -2.005133 -1.748894 -0.956339 -0.910943 -1.554765 -1.036226 -1.731733
25% -1.731733 -1.731733 -0.660474 -0.918068 -1.089426 -0.661078 -1.053112 -0.956339 -0.910943 -1.554765 -1.036226 -1.731733
50% -1.731733 -1.731733 0.039373 -0.918068 0.917915 -0.181059 -0.009440 -0.956339 -0.910943 0.643184 0.965040 -1.731733
75% -1.731733 -1.731733 0.715717 0.283550 0.917915 0.490968 0.686341 -0.956339 0.789359 0.643184 0.965040 -1.731733
max -1.731733 -1.731733 2.086686 1.485169 0.917915 4.139115 1.730013 -0.956339 4.189962 0.643184 0.965040 -1.299197
In [20]:
# Анализ атрибутов тестовой выборки 
# для логистических моделей
print(features_test_logistic.info()) 
print() 
features_test_logistic.describe()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1818 entries, 7493 to 4661
Data columns (total 15 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   row_number         1818 non-null   float64
 1   customer_id        1818 non-null   float64
 2   credit_score       1818 non-null   float64
 3   age                1818 non-null   float64
 4   tenure             1818 non-null   float64
 5   balance            1818 non-null   float64
 6   num_of_products    1818 non-null   float64
 7   has_cr_card        1818 non-null   float64
 8   is_active_member   1818 non-null   float64
 9   estimated_salary   1818 non-null   float64
 10  gender_Female      1818 non-null   float64
 11  gender_Male        1818 non-null   float64
 12  geography_France   1818 non-null   float64
 13  geography_Germany  1818 non-null   float64
 14  geography_Spain    1818 non-null   float64
dtypes: float64(15)
memory usage: 291.8 KB
None

Out[20]:
row_number customer_id credit_score age tenure balance num_of_products has_cr_card is_active_member estimated_salary gender_Female gender_Male geography_France geography_Germany geography_Spain
count 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000 1818.000000
mean 0.062226 0.022237 0.029394 0.024595 -0.010780 0.008403 -0.036475 -0.024180 -0.008073 -0.015442 -0.427996 -0.437109 -0.430849 -0.266873 -0.294005
std 1.015332 0.980033 0.995982 1.043167 1.011652 0.985896 0.973909 1.010950 1.000530 0.990842 0.578798 0.646456 0.608029 0.614191 0.548372
min -1.728157 -1.740931 -3.126112 -2.004168 -1.748894 -1.222967 -0.910943 -1.554765 -1.036226 -1.730254 -0.584549 -0.648030 -0.609131 -0.404530 -0.402071
25% -0.792920 -0.817757 -0.657523 -0.660843 -1.053112 -1.222967 -0.910943 -1.554765 -1.036226 -0.858026 -0.584549 -0.648030 -0.609131 -0.404530 -0.402071
50% 0.084398 0.007659 0.040348 -0.181084 -0.009440 0.330659 -0.910943 0.643184 0.965040 -0.007500 -0.584549 -0.648030 -0.609131 -0.404530 -0.402071
75% 0.962845 0.869769 0.714783 0.490579 0.686341 0.802382 0.789359 0.643184 0.965040 0.809042 -0.584549 -0.648030 -0.609131 -0.404530 -0.402071
max 1.743543 1.713618 2.081881 5.096265 1.730013 2.323255 4.189962 0.643184 0.965040 1.740143 1.710721 1.543139 1.641683 2.472008 2.487121

Выборки подготовлены для использования в моделях. Для балансировки в дальнейшем выбоки будут дополнительно обработаны.

Создание функции и датафрейма для анализа метрик качества модели¶

In [21]:
# Функция анализ метрик качества модели
def output_of_quality_metrics(target_train, 
                              target_test, 
                              predictions_test, 
                              probabilities_test, 
                              quality_metrics, 
                              model, 
                              sample,
                              class_weight,
                              max_depth, 
                              n_estimators
                             ):
    
    predictions_train = pd.Series(target_train.median(), index=target_train.index)
    
    # Показатель ROC-AUC
    probabilities_one_test = probabilities_test[:, 1]
    fpr, tpr, thresholds = roc_curve(target_test, probabilities_one_test)

    # Построение кривой AUC
    plt.figure()
    plt.plot(fpr, tpr, linestyle='-')
    plt.plot([0, 1], [0, 1], linestyle='--')
    plt.xlim(0, 1)
    plt.ylim(0, 1)
    plt.xlabel('False Pisitive Rate')
    plt.xlabel('True Pisitive Rate')
    plt.xlabel('True Pisitive Rate')
    plt.title('ROC-кривая')
    plt.show()

    # Площадь "AUC-ROC"
    auc_roc = roc_auc_score(target_test, probabilities_one_test)
    print('AUC-ROC:', auc_roc)

    # Метрики для оценки моделей классификации (не регрессии)
    accuracy = accuracy_score(target_test, predictions_test)
    f1 = f1_score(target_test, predictions_test)
    print('Accuracy:', accuracy) 
    print('F1:', f1) 

    print() 
    # Альтернативные метрики
    result = target_test * predictions_test
    print('TP:', len(result.loc[result == 1])) 
    result = target_test + predictions_test
    print('TN:', len(result.loc[result == 0]))
    result = target_test - predictions_test
    print('FP:', len(result.loc[result == -1]))
    result = target_test - predictions_test
    print('FN:', len(result.loc[result == 1]))
    
    quality_metrics = quality_metrics.append({
        'model': model, 
        'sample': sample, 
        'auc_roc': auc_roc, 
        'accuracy': accuracy, 
        'f1': f1, 
        'class_weight': class_weight,
        'max_depth': max_depth, 
        'n_estimators': n_estimators
    }, ignore_index=True)
    
    return quality_metrics
In [22]:
# Датафрейм с результатами анализа моделей
quality_metrics = pd.DataFrame(columns=['model', 
                                        'sample', 
                                        'auc_roc', 
                                        'accuracy', 
                                        'f1', 
                                        'class_weight', 
                                        'max_depth', 
                                        'n_estimators'
                                       ]
                              )

quality_metrics
Out[22]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators

Модель решающего дерева без балансировки классов¶

In [23]:
# Функция поиска оптимальных параметров для решающего дерева
def DecisionTreeClassifier_SelectionOfParametrs(
    features_train, target_train, 
    features_test, target_test, 
    class_weight, balance_type, 
    quality_metrics
):
    # Поиск оптимальных параметров
    depth_f1 = pd.DataFrame(columns=['depth', 'f1']) 
    for depth in range(0, 30, 1):
        if depth > 0:
            model = DecisionTreeClassifier(
                class_weight=class_weight, 
                max_depth=depth, 
                random_state=12345
            )
            model.fit(features_train, target_train)
            predictions_test = model.predict(features_test)
            
            depth_f1 = depth_f1.append(pd.DataFrame({
                'depth': [depth], 
                'f1': [f1_score(target_test, predictions_test)]
            }), ignore_index=True) 
            print('max_depth:', depth)
            print('f1:', f1_score(target_test, predictions_test))
            print()
    
    # Модель решающего дерева с оптимальными параметрами
    depth = int(depth_f1.loc[depth_f1['f1'] == depth_f1['f1'].max(), 'depth'])
    model = DecisionTreeClassifier(
        max_depth=depth, 
        random_state=12345
    )
    model.fit(features_train, target_train)
    predictions_test = model.predict(features_test)
    probabilities_test = model.predict_proba(features_test)
    
    # Анализ метрик качества моделей
    quality_metrics = output_of_quality_metrics(target_train, target_test,
                                                predictions_test, probabilities_test, 
                                                quality_metrics, 
                                                'DecisionTreeClassifier', balance_type,
                                                class_weight, depth, None
                                               ) 
    print('\nmax_depth:', depth)
    
    return quality_metrics
In [24]:
# Модель решающего дерева без балансировки классов
quality_metrics = DecisionTreeClassifier_SelectionOfParametrs(
    features_train_wooden, target_train,
    features_valid_wooden, target_valid, 
    None, 'none', quality_metrics
)
max_depth: 1
f1: 0.0

max_depth: 2
f1: 0.49913941480206536

max_depth: 3
f1: 0.4795737122557726

max_depth: 4
f1: 0.47368421052631576

max_depth: 5
f1: 0.4990757855822551

max_depth: 6
f1: 0.4549019607843137

max_depth: 7
f1: 0.5536547433903577

max_depth: 8
f1: 0.4612736660929432

max_depth: 9
f1: 0.5058479532163743

max_depth: 10
f1: 0.4444444444444444

max_depth: 11
f1: 0.43467336683417085

max_depth: 12
f1: 0.39151398264223725

max_depth: 13
f1: 0.31947840260798693

max_depth: 14
f1: 0.42424242424242425

max_depth: 15
f1: 0.37965485921889197

max_depth: 16
f1: 0.32885375494071145

max_depth: 17
f1: 0.3675777568331763

max_depth: 18
f1: 0.37447698744769875

max_depth: 19
f1: 0.3460076045627377

max_depth: 20
f1: 0.3931987247608927

max_depth: 21
f1: 0.40585774058577406

max_depth: 22
f1: 0.36221837088388215

max_depth: 23
f1: 0.34383954154727797

max_depth: 24
f1: 0.34383954154727797

max_depth: 25
f1: 0.34383954154727797

max_depth: 26
f1: 0.34383954154727797

max_depth: 27
f1: 0.34383954154727797

max_depth: 28
f1: 0.34383954154727797

max_depth: 29
f1: 0.34383954154727797

AUC-ROC: 0.7024737159535972
Accuracy: 0.8422210005497526
F1: 0.5536547433903577

TP: 178
TN: 1354
FP: 94
FN: 193

max_depth: 7

Модель случайного леса без балансировки классов¶

In [25]:
# Функция поиска оптимального параметра n_estimators случайного леса
def RandomForestClassifier_SelectionOfParametrs_NEstimatorsItrator(
    features_train, target_train, 
    features_test, target_test, 
    class_weight, depth, n1, n2, n3
):
    n_estimators_f1 = pd.DataFrame(columns=['n_estimators', 'f1']) 
    for n in range(n1, n2, n3):
        model = RandomForestClassifier(
            class_weight=class_weight, 
            n_estimators=n, 
            max_depth=depth, 
            random_state=12345
        )
        model.fit(features_train, target_train)
        predictions_test = model.predict(features_test)
        
        n_estimators_f1 = n_estimators_f1.append(pd.DataFrame({
            'n_estimators': [n], 
            'f1': [f1_score(target_test, predictions_test)]
        }), ignore_index=True) 
        print('n_estimators:', n)
        print('f1:', f1_score(target_test, predictions_test))
        print()
    
    result = n_estimators_f1.loc[n_estimators_f1['f1'] == n_estimators_f1['f1'].max(), 'n_estimators']
    #print(result)
    if len(result) > 1:
        result = int(result.loc[result == result.max()])
    else:
        result = int(result)
    
    return result
In [26]:
# Функция поиска оптимальных параметров для случайного леса
def RandomForestClassifier_SelectionOfParametrs(
    features_train, target_train, 
    features_test, target_test, 
    class_weight, balance_type, quality_metrics
):
    # Поиск max_depth
    depth_f1 = pd.DataFrame(columns=['depth', 'f1']) 
    for depth in range(1, 30, 1):
        model = RandomForestClassifier(
            class_weight=class_weight, 
            n_estimators=20, 
            max_depth=depth, 
            random_state=12345
        )
        model.fit(features_train, target_train)
        predictions_test = model.predict(features_test)
        
        depth_f1 = depth_f1.append(pd.DataFrame({
            'depth': [depth], 
            'f1': [f1_score(target_test, predictions_test)]
        }), ignore_index=True)
        print('max_depth:', depth)
        print('f1:', f1_score(target_test, predictions_test))
        print()
    
    depth = int(depth_f1.loc[depth_f1['f1'] == depth_f1['f1'].max(), 'depth'])
    
    # Поиск n_estimators крупными шагами
    n_estimators = RandomForestClassifier_SelectionOfParametrs_NEstimatorsItrator(
        features_train, target_train, 
        features_test, target_test, 
        class_weight, depth, 50, 252, 10
    )
    
    # Модель случайного леса с оптимальными параметрами
    model = RandomForestClassifier(
        n_estimators=n_estimators, 
        max_depth=depth, 
        random_state=12345
    )
    model.fit(features_train, target_train)
    predictions_test = model.predict(features_test)
    probabilities_test = model.predict_proba(features_test)

    # Анализ метрик качества моделей
    quality_metrics = output_of_quality_metrics(target_train, target_test, 
                                                predictions_test, probabilities_test, 
                                                quality_metrics, 
                                                'RandomForestClassifier', balance_type, 
                                                class_weight, depth, n_estimators
                                               )
    print('\nmax_depth:', depth)
    print('n_estimators:', n_estimators)
    
    return quality_metrics
In [27]:
# Модель случайного леса без балансировки классов
quality_metrics = RandomForestClassifier_SelectionOfParametrs(
    features_train_wooden, target_train, 
    features_valid_wooden, target_valid, 
    None, 'none', quality_metrics
)
max_depth: 1
f1: 0.0

max_depth: 2
f1: 0.10714285714285715

max_depth: 3
f1: 0.17031630170316303

max_depth: 4
f1: 0.389937106918239

max_depth: 5
f1: 0.4244897959183674

max_depth: 6
f1: 0.47924528301886793

max_depth: 7
f1: 0.48393194706994325

max_depth: 8
f1: 0.5446009389671361

max_depth: 9
f1: 0.5277777777777778

max_depth: 10
f1: 0.5116279069767441

max_depth: 11
f1: 0.43043478260869567

max_depth: 12
f1: 0.48575305291723203

max_depth: 13
f1: 0.38337182448036955

max_depth: 14
f1: 0.4021024967148489

max_depth: 15
f1: 0.4502762430939226

max_depth: 16
f1: 0.39124087591240886

max_depth: 17
f1: 0.4987012987012987

max_depth: 18
f1: 0.519893899204244

max_depth: 19
f1: 0.4135021097046414

max_depth: 20
f1: 0.46036585365853655

max_depth: 21
f1: 0.46713286713286717

max_depth: 22
f1: 0.45108695652173914

max_depth: 23
f1: 0.47058823529411764

max_depth: 24
f1: 0.4509283819628647

max_depth: 25
f1: 0.4862023653088041

max_depth: 26
f1: 0.46965699208443273

max_depth: 27
f1: 0.46965699208443273

max_depth: 28
f1: 0.46965699208443273

max_depth: 29
f1: 0.46965699208443273

n_estimators: 50
f1: 0.4936479128856625

n_estimators: 60
f1: 0.5159010600706714

n_estimators: 70
f1: 0.5185185185185185

n_estimators: 80
f1: 0.532399299474606

n_estimators: 90
f1: 0.5228070175438596

n_estimators: 100
f1: 0.51985559566787

n_estimators: 110
f1: 0.5178571428571428

n_estimators: 120
f1: 0.5240641711229945

n_estimators: 130
f1: 0.5186500888099467

n_estimators: 140
f1: 0.5222024866785079

n_estimators: 150
f1: 0.5202108963093146

n_estimators: 160
f1: 0.5283687943262411

n_estimators: 170
f1: 0.5222024866785079

n_estimators: 180
f1: 0.5186500888099467

n_estimators: 190
f1: 0.5098743267504487

n_estimators: 200
f1: 0.5179856115107913

n_estimators: 210
f1: 0.5197132616487455

n_estimators: 220
f1: 0.5197132616487455

n_estimators: 230
f1: 0.5242369838420108

n_estimators: 240
f1: 0.5215827338129496

n_estimators: 250
f1: 0.516245487364621

AUC-ROC: 0.8082921326562523
Accuracy: 0.8532160527762507
F1: 0.532399299474606

TP: 152
TN: 1400
FP: 48
FN: 219

max_depth: 8
n_estimators: 80

Проверка на мультиколлинеарность выборок для логистических моделей¶

In [28]:
# Проверка на мультиколлинеарность
# тренировочных выборок для логистических моделей
sns.heatmap(features_train_logistic.join(target_train).corr()) 
features_train_logistic.join(target_train).corr()
Out[28]:
row_number customer_id credit_score age tenure balance num_of_products has_cr_card is_active_member estimated_salary gender_Female gender_Male geography_France geography_Germany geography_Spain exited
row_number 1.000000 0.005898 0.005712 0.007946 -0.011205 0.007941 0.027162 0.002995 0.001504 -0.010913 -0.460272 -0.497288 -0.477358 -0.303382 -0.317613 -0.000772
customer_id 0.005898 1.000000 0.015712 0.008529 -0.027959 -0.001875 -0.006575 -0.014382 0.019827 0.008934 -0.019446 0.017972 -0.014917 -0.001245 0.019618 -0.013277
credit_score 0.005712 0.015712 1.000000 -0.006208 0.008835 0.006499 0.024159 0.005841 0.034242 0.000839 0.000568 0.011944 -0.009777 0.014734 0.014214 -0.016024
age 0.007946 0.008529 -0.006208 1.000000 -0.011521 0.021097 -0.026788 -0.030624 0.083881 -0.017381 -0.007526 0.001229 0.017243 -0.009995 -0.019946 0.281278
tenure -0.011205 -0.027959 0.008835 -0.011521 1.000000 0.006254 -0.009793 0.039019 -0.027909 0.028676 0.008976 0.001430 0.017221 -0.011342 0.002470 -0.005569
balance 0.007941 -0.001875 0.006499 0.021097 0.006254 1.000000 -0.285302 -0.016341 0.002558 -0.000291 -0.006299 -0.003205 -0.017244 -0.007464 0.017476 0.106216
num_of_products 0.027162 -0.006575 0.024159 -0.026788 -0.009793 -0.285302 1.000000 0.008951 0.005011 0.017998 -0.011565 -0.010363 -0.002647 -0.004415 -0.020389 -0.043919
has_cr_card 0.002995 -0.014382 0.005841 -0.030624 0.039019 -0.016341 0.008951 1.000000 -0.002109 -0.005875 -0.002348 0.010606 0.013534 -0.023826 0.017588 -0.014600
is_active_member 0.001504 0.019827 0.034242 0.083881 -0.027909 0.002558 0.005011 -0.002109 1.000000 -0.020044 0.014988 -0.021859 0.003096 0.004073 -0.018021 -0.147341
estimated_salary -0.010913 0.008934 0.000839 -0.017381 0.028676 -0.000291 0.017998 -0.005875 -0.020044 1.000000 0.005538 0.008935 0.003077 0.012168 0.002582 0.006600
gender_Female -0.460272 -0.019446 0.000568 -0.007526 0.008976 -0.006299 -0.011565 -0.002348 0.014988 0.005538 1.000000 -0.378805 0.293737 0.192073 0.189316 -0.013787
gender_Male -0.497288 0.017972 0.011944 0.001229 0.001430 -0.003205 -0.010363 0.010606 -0.021859 0.008935 -0.378805 1.000000 0.319642 0.215141 0.215352 0.013093
geography_France -0.477358 -0.014917 -0.009777 0.017243 0.017221 -0.017244 -0.002647 0.013534 0.003096 0.003077 0.293737 0.319642 1.000000 -0.246412 -0.244914 0.006210
geography_Germany -0.303382 -0.001245 0.014734 -0.009995 -0.011342 -0.007464 -0.004415 -0.023826 0.004073 0.012168 0.192073 0.215141 -0.246412 1.000000 -0.162650 -0.001808
geography_Spain -0.317613 0.019618 0.014214 -0.019946 0.002470 0.017476 -0.020389 0.017588 -0.018021 0.002582 0.189316 0.215352 -0.244914 -0.162650 1.000000 -0.006246
exited -0.000772 -0.013277 -0.016024 0.281278 -0.005569 0.106216 -0.043919 -0.014600 -0.147341 0.006600 -0.013787 0.013093 0.006210 -0.001808 -0.006246 1.000000

В обучающей выборке целевой показатель exited имеет максимальную Положительную зависть с атрибутом age, а отрицательную с is_active_member и чуть меньшую с gender. Все аттрибуты не достаточно коррелируют между собой для того, чтобы их исключать из выборки.

In [29]:
# Проверка на мультиколлинеарность
# тестовых выборок
sns.heatmap(features_valid_logistic.join(target_valid).corr()) 
features_valid_logistic.join(target_valid).corr()
Out[29]:
row_number customer_id credit_score age tenure balance num_of_products has_cr_card is_active_member estimated_salary gender_Female gender_Male geography_France geography_Germany geography_Spain exited
row_number 1.000000 -0.004687 -0.021238 -0.022875 -0.028467 -0.007524 -0.049267 -0.017128 0.026583 0.032802 -0.428833 -0.472871 -0.462637 -0.310754 -0.296507 -0.060179
customer_id -0.004687 1.000000 -0.019811 0.012808 -0.006061 -0.009157 0.045024 -0.064256 -0.009686 0.010066 -0.009566 0.013437 0.003232 -0.016932 0.019044 0.003639
credit_score -0.021238 -0.019811 1.000000 -0.013809 -0.008568 -0.024891 0.006992 -0.011907 0.030364 0.017494 0.050082 -0.018401 0.029116 0.030111 -0.031060 -0.040755
age -0.022875 0.012808 -0.013809 1.000000 -0.010143 0.054375 -0.050313 -0.010073 0.090122 0.003412 -0.006570 0.039086 -0.008604 -0.010327 0.071635 0.280302
tenure -0.028467 -0.006061 -0.008568 -0.010143 1.000000 -0.036796 0.061575 0.017838 -0.016184 -0.013928 0.031951 -0.010001 0.011865 0.018325 -0.006511 -0.051494
balance -0.007524 -0.009157 -0.024891 0.054375 -0.036796 1.000000 -0.309930 -0.022514 -0.036199 0.042752 0.031474 -0.003727 0.002136 0.020740 0.013885 0.127003
num_of_products -0.049267 0.045024 0.006992 -0.050313 0.061575 -0.309930 1.000000 -0.007011 0.031366 -0.003514 -0.014212 0.028259 0.002495 0.000553 0.017730 -0.085182
has_cr_card -0.017128 -0.064256 -0.011907 -0.010073 0.017838 -0.022514 -0.007011 1.000000 -0.027230 0.008695 0.017583 0.027662 0.021351 -0.001293 0.035147 0.005733
is_active_member 0.026583 -0.009686 0.030364 0.090122 -0.016184 -0.036199 0.031366 -0.027230 1.000000 -0.022181 -0.012466 -0.032888 -0.018167 -0.033954 -0.004539 -0.148687
estimated_salary 0.032802 0.010066 0.017494 0.003412 -0.013928 0.042752 -0.003514 0.008695 -0.022181 1.000000 -0.018907 0.009388 -0.004455 -0.002596 -0.003195 0.040447
gender_Female -0.428833 -0.009566 0.050082 -0.006570 0.031951 0.031474 -0.014212 0.017583 -0.012466 -0.018907 1.000000 -0.101855 0.425875 0.351241 0.253830 0.004754
gender_Male -0.472871 0.013437 -0.018401 0.039086 -0.010001 -0.003727 0.028259 0.027662 -0.032888 0.009388 -0.101855 1.000000 0.498107 0.258706 0.338808 0.037867
geography_France -0.462637 0.003232 0.029116 -0.008604 0.011865 0.002136 0.002495 0.021351 -0.018167 -0.004455 0.425875 0.498107 1.000000 -0.071101 -0.069711 0.035337
geography_Germany -0.310754 -0.016932 0.030111 -0.010327 0.018325 0.020740 0.000553 -0.001293 -0.033954 -0.002596 0.351241 0.258706 -0.071101 1.000000 -0.045695 0.003171
geography_Spain -0.296507 0.019044 -0.031060 0.071635 -0.006511 0.013885 0.017730 0.035147 -0.004539 -0.003195 0.253830 0.338808 -0.069711 -0.045695 1.000000 0.007349
exited -0.060179 0.003639 -0.040755 0.280302 -0.051494 0.127003 -0.085182 0.005733 -0.148687 0.040447 0.004754 0.037867 0.035337 0.003171 0.007349 1.000000

В тестовой выборке, как и в целевой, показатель exited имеет максимальную положительную зависть с атрибутом age, а отрицательную с is_active_member и чуть меньшую с gender. Все аттрибуты не достаточно коррелируют между собой для того, чтобы их исключать из выборки.

Модель логистической регрессии без балансировки классов¶

In [30]:
# Функция поиска оптимальных параметров для логистической регрессии
def LogisticRegression_SelectionOfParametrs(
    features_train, target_train, 
    features_test, target_test, 
    class_weight, balance_type, quality_metrics
):
    # Пробное обучение методом логистической регрессии
    model = LogisticRegression(
        solver='liblinear', 
        class_weight=class_weight, 
        random_state=12345
    ) 
    model.fit(features_train, target_train)
    predictions_test = model.predict(features_test)
    probabilities_test = model.predict_proba(features_test)

    # Анализ метрик качества моделей
    quality_metrics = output_of_quality_metrics(target_train, target_test, 
                                                predictions_test, probabilities_test, 
                                                quality_metrics, 'LogisticRegression', balance_type, 
                                                class_weight, None, None
                                               )
    return quality_metrics
In [31]:
# Модель логистической регрессии без балансировки классов
quality_metrics = LogisticRegression_SelectionOfParametrs(
    features_train_logistic, target_train, 
    features_valid_logistic, target_valid, 
    None, 'none', quality_metrics
)
AUC-ROC: 0.7442182543819154
Accuracy: 0.8042880703683343
F1: 0.25523012552301255

TP: 61
TN: 1402
FP: 46
FN: 310

Результаты использования моделей без балансировки классов¶

In [32]:
# Таблица изменения значений показателей качества моделей
quality_metrics.sort_values('f1', ascending=False)
Out[32]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
0 DecisionTreeClassifier none 0.702474 0.842221 0.553655 None 7 None
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
2 LogisticRegression none 0.744218 0.804288 0.255230 None None None

Среди моделей с несбалансированными выборками лидирует DecisionTreeClassifier.

Исследование баланса классов¶

In [33]:
print('Уникальные классы целевого признака:') 
print(target_train.unique()) 
print() 
print('Количество объектов каждого класса целевого признака:') 
for i in target_train.unique():
    print(f'Класс "{i}" =', len(target_train.loc[target_train == i])) 
Уникальные классы целевого признака:
[0 1]

Количество объектов каждого класса целевого признака:
Класс "0" = 4342
Класс "1" = 1112
In [34]:
print('Отношение класса "1." к классу "0." целевого признака:') 
class_ratio = len(target_train.loc[target_train == 0]) / len(target_train.loc[target_train == 1]) 
class_ratio
Отношение класса "1." к классу "0." целевого признака:
Out[34]:
3.9046762589928057

Котчество объектов класса 0 целевого признака превышает количество объектов класса 1 в 3.9046762589928057 раз.

Модель решающего дерева с балансировкой весов классов внутри модели¶

In [35]:
# Модель решающего дерева с балансировкой весов классов внутри модели
quality_metrics = DecisionTreeClassifier_SelectionOfParametrs(
    features_train_wooden, target_train, 
    features_valid_wooden, target_valid, 
    'balanced', 'balance', quality_metrics
)
max_depth: 1
f1: 0.4771033013844515

max_depth: 2
f1: 0.49688149688149696

max_depth: 3
f1: 0.49688149688149696

max_depth: 4
f1: 0.5203045685279187

max_depth: 5
f1: 0.5340453938584779

max_depth: 6
f1: 0.5047318611987381

max_depth: 7
f1: 0.48648648648648657

max_depth: 8
f1: 0.310580204778157

max_depth: 9
f1: 0.31124807395993837

max_depth: 10
f1: 0.3560311284046693

max_depth: 11
f1: 0.32882414151925077

max_depth: 12
f1: 0.3127035830618893

max_depth: 13
f1: 0.328125

max_depth: 14
f1: 0.32026143790849676

max_depth: 15
f1: 0.2953020134228188

max_depth: 16
f1: 0.31171786120591577

max_depth: 17
f1: 0.31358885017421606

max_depth: 18
f1: 0.3180778032036613

max_depth: 19
f1: 0.30624263839811544

max_depth: 20
f1: 0.30568720379146924

max_depth: 21
f1: 0.27175208581644816

max_depth: 22
f1: 0.30492196878751504

max_depth: 23
f1: 0.30419161676646705

max_depth: 24
f1: 0.30419161676646705

max_depth: 25
f1: 0.30419161676646705

max_depth: 26
f1: 0.30419161676646705

max_depth: 27
f1: 0.30419161676646705

max_depth: 28
f1: 0.30419161676646705

max_depth: 29
f1: 0.30419161676646705

AUC-ROC: 0.7995887998689521
Accuracy: 0.8510170423309511
F1: 0.4990757855822551

TP: 135
TN: 1413
FP: 35
FN: 236

max_depth: 5

Модель случайного леса с балансировкой весов классов внутри модели¶

In [36]:
# Модель случайного леса с балансировкой весов классов внутри модели
quality_metrics = RandomForestClassifier_SelectionOfParametrs(
    features_train_wooden, target_train,
    features_valid_wooden, target_valid, 
    'balanced', 'balance', quality_metrics
)
max_depth: 1
f1: 0.5258323765786452

max_depth: 2
f1: 0.5110619469026548

max_depth: 3
f1: 0.5714285714285715

max_depth: 4
f1: 0.5399568034557235

max_depth: 5
f1: 0.5841121495327102

max_depth: 6
f1: 0.5645390070921986

max_depth: 7
f1: 0.5469613259668508

max_depth: 8
f1: 0.45619834710743806

max_depth: 9
f1: 0.3090128755364807

max_depth: 10
f1: 0.3858267716535433

max_depth: 11
f1: 0.2672413793103448

max_depth: 12
f1: 0.22119815668202764

max_depth: 13
f1: 0.17966903073286053

max_depth: 14
f1: 0.19134396355353075

max_depth: 15
f1: 0.2945054945054945

max_depth: 16
f1: 0.24713958810068654

max_depth: 17
f1: 0.06683804627249358

max_depth: 18
f1: 0.18571428571428572

max_depth: 19
f1: 0.17433414043583534

max_depth: 20
f1: 0.2901785714285714

max_depth: 21
f1: 0.1141439205955335

max_depth: 22
f1: 0.16097560975609757

max_depth: 23
f1: 0.19811320754716982

max_depth: 24
f1: 0.28764044943820233

max_depth: 25
f1: 0.2630385487528345

max_depth: 26
f1: 0.2602739726027397

max_depth: 27
f1: 0.2602739726027397

max_depth: 28
f1: 0.2602739726027397

max_depth: 29
f1: 0.2602739726027397

n_estimators: 50
f1: 0.5876543209876544

n_estimators: 60
f1: 0.57465495608532

n_estimators: 70
f1: 0.5761006289308176

n_estimators: 80
f1: 0.5789473684210527

n_estimators: 90
f1: 0.5768742058449808

n_estimators: 100
f1: 0.5772151898734177

n_estimators: 110
f1: 0.5692695214105794

n_estimators: 120
f1: 0.5760598503740648

n_estimators: 130
f1: 0.581772784019975

n_estimators: 140
f1: 0.5760598503740648

n_estimators: 150
f1: 0.5743073047858943

n_estimators: 160
f1: 0.5678073510773131

n_estimators: 170
f1: 0.5670886075949367

n_estimators: 180
f1: 0.569974554707379

n_estimators: 190
f1: 0.5641677255400254

n_estimators: 200
f1: 0.565891472868217

n_estimators: 210
f1: 0.5695876288659794

n_estimators: 220
f1: 0.5636363636363636

n_estimators: 230
f1: 0.5692108667529107

n_estimators: 240
f1: 0.565891472868217

n_estimators: 250
f1: 0.5673575129533678

AUC-ROC: 0.8214434632395645
Accuracy: 0.8449697636063771
F1: 0.4337349397590362

TP: 108
TN: 1429
FP: 19
FN: 263

max_depth: 5
n_estimators: 50

Модель логистической регрессии с балансировкой весов классов внутри модели¶

In [37]:
# Модель логистической регрессии с балансировкой весов классов внутри модели
quality_metrics = LogisticRegression_SelectionOfParametrs(
    features_train_logistic, target_train, 
    features_valid_logistic, target_valid, 
    'balanced', 'balance', quality_metrics
)
AUC-ROC: 0.7483823770308707
Accuracy: 0.685541506322155
F1: 0.47329650092081027

TP: 257
TN: 990
FP: 458
FN: 114

Результаты использования моделей с балансировкой весов классов внутри модели¶

In [38]:
# Таблица изменения значений показателей 
# качества всех уже протестированных моделей

quality_metrics.sort_values('f1', ascending=False)
Out[38]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
0 DecisionTreeClassifier none 0.702474 0.842221 0.553655 None 7 None
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
3 DecisionTreeClassifier balance 0.799589 0.851017 0.499076 balanced 5 None
5 LogisticRegression balance 0.748382 0.685542 0.473297 balanced None None
4 RandomForestClassifier balance 0.821443 0.844970 0.433735 balanced 5 50
2 LogisticRegression none 0.744218 0.804288 0.255230 None None None

Использование параметра class_weight='balanced', балансирующей веса классов, в модели логистической регрессии привело к следющим изменениям показателей качества модели:

  1. Две из трех моделей без балансировки весов классов имеют лучшее значение показателя F1. Лидер DecisionTreeClassifier со значением F1 равным 0.553655.
  2. Максимальное значение параметра AUC-ROC равное 0.821443 у сбалансированной модели RandomForestClassifier.
  3. Максимальное значение показателя Accuracy равное 0.853216 у несбалансированной модели RandomForestClassifier.

Итоги исследования задачи¶

Разные модели в сбалансированном и несбалансированном состоянии показали разные значения настолько, что требуется долнительная проверка эффективности сбалансированных моделей.

Борьба с дисбалансом¶

Для борьбы с дисбалансом будет применено два подхода с использование в каждом по 3 модели. В рамках первого подхода будет увеличен размер выборки за счет увеличения количества объектов меньшего класса 1 целевого показателя в 3.9046762589928057 раз. Множитель был получен в предыдущем разделе и сохранен в переменной class_ratio. В рамках второго подхода будет уменьшен размер выборки за счет уменьшения количества объектов класса большего класса 0 целевого показателя в 3.9046762589928057 раз.

С целью достижения результата будет создана универсальная функция для изменения размера выборок.

In [39]:
# Универсальная функция изменения размера выборки
def changesizasample(features, target, class_change, change_type, class_ratio):
    # fetures - аттрибуты
    # target - целевой признак
    # class_change - класс (0, 1), размер которого требуется изменить
    # change_type - тип изменения выборки True (увеличение), False (уменьшение)
    # fraction - множитель для изменения
    
    features_zeros = features[target == 0]
    features_ones = features[target == 1]
    target_zeros = target[target == 0]
    target_ones = target[target == 1]
    
    if change_type == True:
        if class_change == 0:
            features_downloadsample = pd.concat([features_zeros] * int(class_ratio) + [features_ones])
            target_downloadsample = pd.concat([target_zeros] * int(class_ratio) + [target_ones])
        if class_change == 1:
            features_downloadsample = pd.concat([features_zeros] + [features_ones] * int(class_ratio))
            target_downloadsample = pd.concat([target_zeros] + [target_ones] * int(class_ratio))
    if change_type == False:
        if class_change == 0:
            features_zeros = features_zeros.sample(frac=class_ratio, random_state=12345)
            target_zeros = target_zeros.sample(frac=class_ratio, random_state=12345)
        if class_change == 1:
            features_ones = features_ones.sample(frac=class_ratio, random_state=12345)
            target_ones = target_ones.sample(frac=class_ratio, random_state=12345)
        features_downloadsample = pd.concat([features_zeros] + [features_ones])
        target_downloadsample = pd.concat([target_zeros] + [target_ones])
    
    features_downloadsample, target_downloadsample = shuffle(
        features_downloadsample, target_downloadsample, random_state=12345
    )
    
    return features_downloadsample, target_downloadsample

Балансировка обучающей выборкой путем увеличения количества объектов класса «1»¶

В рамках этого подхода будет увеличен размер выборки за счет увеличения количества объектов меньшего класса 1 целевого показателя в 3.9046762589928057 раз.

In [40]:
# Увеличение обучающей выборки за счет объектов класса "1" для "деревянных" моделей
features_train_changesizasample, target_train_changesizasample = changesizasample(
    features_train_wooden, 
    target_train, 
    1,              # класс целевой обучающей выборки
    True,           # увеличение выборки
    class_ratio     # кратность изменения (множитель/делитель)
)

print('Изменение размера выборок:')
print(features_train_wooden.shape, '->', features_train_changesizasample.shape)
print(target_train.shape, '->', target_train_changesizasample.shape)
Изменение размера выборок:
(5454, 12) -> (7678, 12)
(5454,) -> (7678,)

Размер обучающей выборки увеличен с 5454 до 7678 объектов за счет объектов класса 1.

Модель решающего дерева со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»¶

In [41]:
# Модель решающего дерева со сблансированной обучающей выборкой, 
# полученной путем увеличения количества объектов класса "1"
quality_metrics = DecisionTreeClassifier_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample, 
    features_valid_wooden, target_valid, 
    None, 'up_sample', quality_metrics
)
max_depth: 1
f1: 0.4771033013844515

max_depth: 2
f1: 0.49688149688149696

max_depth: 3
f1: 0.5266362252663623

max_depth: 4
f1: 0.5220883534136546

max_depth: 5
f1: 0.5584905660377358

max_depth: 6
f1: 0.5307017543859649

max_depth: 7
f1: 0.500669344042838

max_depth: 8
f1: 0.3313840155945419

max_depth: 9
f1: 0.3465491923641703

max_depth: 10
f1: 0.3421633554083885

max_depth: 11
f1: 0.3361344537815126

max_depth: 12
f1: 0.3031624863685932

max_depth: 13
f1: 0.34346103038309117

max_depth: 14
f1: 0.3880952380952381

max_depth: 15
f1: 0.28538011695906434

max_depth: 16
f1: 0.37249283667621774

max_depth: 17
f1: 0.35268346111719606

max_depth: 18
f1: 0.3076923076923077

max_depth: 19
f1: 0.3367003367003367

max_depth: 20
f1: 0.3462686567164179

max_depth: 21
f1: 0.30861244019138756

max_depth: 22
f1: 0.36444444444444446

max_depth: 23
f1: 0.36444444444444446

max_depth: 24
f1: 0.36444444444444446

max_depth: 25
f1: 0.36444444444444446

max_depth: 26
f1: 0.36444444444444446

max_depth: 27
f1: 0.36444444444444446

max_depth: 28
f1: 0.36444444444444446

max_depth: 29
f1: 0.36444444444444446

AUC-ROC: 0.7736630876680913
Accuracy: 0.8070368334249588
F1: 0.5584905660377358

TP: 222
TN: 1246
FP: 202
FN: 149

max_depth: 5

Модель случайного леса со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»¶

In [42]:
# Модель случайного леса со сблансированной обучающей выборкой, 
# полученной путем увеличения количества объектов класса "1"
quality_metrics = RandomForestClassifier_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample,
    features_valid_wooden, target_valid, 
    None, 'up_sample', quality_metrics
)
max_depth: 1
f1: 0.19093078758949883

max_depth: 2
f1: 0.546218487394958

max_depth: 3
f1: 0.5482625482625482

max_depth: 4
f1: 0.556701030927835

max_depth: 5
f1: 0.557840616966581

max_depth: 6
f1: 0.5904255319148937

max_depth: 7
f1: 0.5045317220543807

max_depth: 8
f1: 0.5395232120451693

max_depth: 9
f1: 0.5260347129506008

max_depth: 10
f1: 0.28169014084507044

max_depth: 11
f1: 0.47213114754098356

max_depth: 12
f1: 0.47027027027027024

max_depth: 13
f1: 0.3058823529411765

max_depth: 14
f1: 0.4860335195530726

max_depth: 15
f1: 0.2007042253521127

max_depth: 16
f1: 0.4027777777777778

max_depth: 17
f1: 0.4514003294892916

max_depth: 18
f1: 0.3966101694915255

max_depth: 19
f1: 0.43956043956043955

max_depth: 20
f1: 0.4354587869362364

max_depth: 21
f1: 0.42928452579034937

max_depth: 22
f1: 0.30268199233716475

max_depth: 23
f1: 0.36684303350970016

max_depth: 24
f1: 0.35251798561151076

max_depth: 25
f1: 0.36524822695035464

max_depth: 26
f1: 0.38095238095238093

max_depth: 27
f1: 0.39583333333333337

max_depth: 28
f1: 0.39583333333333337

max_depth: 29
f1: 0.39583333333333337

n_estimators: 50
f1: 0.5730824891461649

n_estimators: 60
f1: 0.5616045845272206

n_estimators: 70
f1: 0.5785813630041725

n_estimators: 80
f1: 0.5854341736694678

n_estimators: 90
f1: 0.577524893314367

n_estimators: 100
f1: 0.5677233429394813

n_estimators: 110
f1: 0.5647743813682679

n_estimators: 120
f1: 0.5623188405797103

n_estimators: 130
f1: 0.5589519650655022

n_estimators: 140
f1: 0.5647743813682679

n_estimators: 150
f1: 0.5643994211287988

n_estimators: 160
f1: 0.5628571428571428

n_estimators: 170
f1: 0.5602240896358543

n_estimators: 180
f1: 0.5702364394993045

n_estimators: 190
f1: 0.5643153526970955

n_estimators: 200
f1: 0.5764546684709068

n_estimators: 210
f1: 0.574496644295302

n_estimators: 220
f1: 0.561307901907357

n_estimators: 230
f1: 0.5632653061224488

n_estimators: 240
f1: 0.5636856368563686

n_estimators: 250
f1: 0.5641025641025642

AUC-ROC: 0.8320296793793093
Accuracy: 0.8372732270478285
F1: 0.5854341736694678

TP: 209
TN: 1314
FP: 134
FN: 162

max_depth: 6
n_estimators: 80

Модель логистической регрессии со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»¶

In [43]:
# Увеличение обучающей выборки за счет объектов класса "1" для логистических" моделей
features_train_changesizasample, target_train_changesizasample = changesizasample(
    features_train_logistic, 
    target_train, 
    1,              # класс целевой обучающей выборки
    True,           # увеличение выборки
    class_ratio     # кратность изменения (множитель/делитель)
)

print('Изменение размера выборок:')
print(features_train_logistic.shape, '->', features_train_changesizasample.shape)
print(target_train.shape, '->', target_train_changesizasample.shape)
Изменение размера выборок:
(5454, 15) -> (7678, 15)
(5454,) -> (7678,)
In [44]:
# Модель логистической регрессии со сблансированной обучающей выборкой, 
# полученной путем увеличения количества объектов класса "1"
quality_metrics = LogisticRegression_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample, 
    features_valid_logistic, target_valid, 
    None, 'up_sample', quality_metrics
)
AUC-ROC: 0.747809042307635
Accuracy: 0.7416162726772952
F1: 0.48237885462555063

TP: 219
TN: 1130
FP: 318
FN: 152

Результаты использования моделей со сблансированной обучающей выборкой, полученной путем увеличения количества объектов класса «1»¶

In [45]:
# Результаты использования моделей со сблансированной обучающей выборкой, 
# полученной путем увеличения количества объектов класса "1"

quality_metrics.sort_values('f1', ascending=False)
Out[45]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
7 RandomForestClassifier up_sample 0.832030 0.837273 0.585434 None 6 80
6 DecisionTreeClassifier up_sample 0.773663 0.807037 0.558491 None 5 None
0 DecisionTreeClassifier none 0.702474 0.842221 0.553655 None 7 None
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
3 DecisionTreeClassifier balance 0.799589 0.851017 0.499076 balanced 5 None
8 LogisticRegression up_sample 0.747809 0.741616 0.482379 None None None
5 LogisticRegression balance 0.748382 0.685542 0.473297 balanced None None
4 RandomForestClassifier balance 0.821443 0.844970 0.433735 balanced 5 50
2 LogisticRegression none 0.744218 0.804288 0.255230 None None None

Балансировка классов за счет уменьшения обучающей выборки класса «0»¶

В рамках этого подхода будет уменьшен размер выборки за счет уменьшения количества объектов класса большего класса 0 целевого показателя в 3.9046762589928057 раз.

In [46]:
# Балансировка классов за счет уменьшения обучающей выборки класса "0"
features_train_changesizasample, target_train_changesizasample = changesizasample(
    features_train_wooden, 
    target_train, 
    0,              # класс целевой обучающей выборки
    False,          # уменьшение выборки
    class_ratio/100 # кратность изменения (множитель/делитель)
)

print('Изменение размера выборок:')
print(features_train_wooden.shape, '->', features_train_changesizasample.shape)
print(target_train.shape, '->', target_train_changesizasample.shape)
Изменение размера выборок:
(5454, 12) -> (1282, 12)
(5454,) -> (1282,)

Размер обучающей выборки уменьшен с 5454 до 1282 объектов за счет объектов класса 0.

Модель решающего дерева со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»¶

In [47]:
# Модель решающего дерева со сблансированной обучающей выборкой, 
# полученной путем уменьшения количества объектов класса "0"
quality_metrics = DecisionTreeClassifier_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample, 
    features_valid_wooden, target_valid, 
    None, 'down_sample', quality_metrics
)
max_depth: 1
f1: 0.33881278538812787

max_depth: 2
f1: 0.33881278538812787

max_depth: 3
f1: 0.398406374501992

max_depth: 4
f1: 0.3150912106135987

max_depth: 5
f1: 0.4414029084687767

max_depth: 6
f1: 0.4218040233614536

max_depth: 7
f1: 0.342668863261944

max_depth: 8
f1: 0.3871338311315336

max_depth: 9
f1: 0.362657091561939

max_depth: 10
f1: 0.3701863354037267

max_depth: 11
f1: 0.3186119873817035

max_depth: 12
f1: 0.37016949152542367

max_depth: 13
f1: 0.37016949152542367

max_depth: 14
f1: 0.37016949152542367

max_depth: 15
f1: 0.37016949152542367

max_depth: 16
f1: 0.37016949152542367

max_depth: 17
f1: 0.37016949152542367

max_depth: 18
f1: 0.37016949152542367

max_depth: 19
f1: 0.37016949152542367

max_depth: 20
f1: 0.37016949152542367

max_depth: 21
f1: 0.37016949152542367

max_depth: 22
f1: 0.37016949152542367

max_depth: 23
f1: 0.37016949152542367

max_depth: 24
f1: 0.37016949152542367

max_depth: 25
f1: 0.37016949152542367

max_depth: 26
f1: 0.37016949152542367

max_depth: 27
f1: 0.37016949152542367

max_depth: 28
f1: 0.37016949152542367

max_depth: 29
f1: 0.37016949152542367

AUC-ROC: 0.6727357001384939
Accuracy: 0.6410115448048378
F1: 0.4414029084687767

TP: 258
TN: 908
FP: 540
FN: 113

max_depth: 5

Модель случайного леса со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»¶

In [48]:
# Модель случайного леса со сблансированной обучающей выборкой, 
# полученной путем уменьшения количества объектов класса "0"
quality_metrics = RandomForestClassifier_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample,
    features_valid_wooden, target_valid, 
    None, 'down_sample', quality_metrics
)
max_depth: 1
f1: 0.33881278538812787

max_depth: 2
f1: 0.33881278538812787

max_depth: 3
f1: 0.3699360341151386

max_depth: 4
f1: 0.3830016137708446

max_depth: 5
f1: 0.4225352112676057

max_depth: 6
f1: 0.3807439824945295

max_depth: 7
f1: 0.41290322580645156

max_depth: 8
f1: 0.3970189701897019

max_depth: 9
f1: 0.41963578780680927

max_depth: 10
f1: 0.3775351014040561

max_depth: 11
f1: 0.33097345132743367

max_depth: 12
f1: 0.3131067961165048

max_depth: 13
f1: 0.34974533106960953

max_depth: 14
f1: 0.2860192102454643

max_depth: 15
f1: 0.28688524590163933

max_depth: 16
f1: 0.28260869565217395

max_depth: 17
f1: 0.2835332606324973

max_depth: 18
f1: 0.2835332606324973

max_depth: 19
f1: 0.2835332606324973

max_depth: 20
f1: 0.2835332606324973

max_depth: 21
f1: 0.2835332606324973

max_depth: 22
f1: 0.2835332606324973

max_depth: 23
f1: 0.2835332606324973

max_depth: 24
f1: 0.2835332606324973

max_depth: 25
f1: 0.2835332606324973

max_depth: 26
f1: 0.2835332606324973

max_depth: 27
f1: 0.2835332606324973

max_depth: 28
f1: 0.2835332606324973

max_depth: 29
f1: 0.2835332606324973

n_estimators: 50
f1: 0.4423337856173677

n_estimators: 60
f1: 0.4512110726643599

n_estimators: 70
f1: 0.4298469387755101

n_estimators: 80
f1: 0.42156268927922463

n_estimators: 90
f1: 0.44010416666666663

n_estimators: 100
f1: 0.42412935323383083

n_estimators: 110
f1: 0.4306709265175719

n_estimators: 120
f1: 0.42454602379461487

n_estimators: 130
f1: 0.42848061029879214

n_estimators: 140
f1: 0.43305573350416404

n_estimators: 150
f1: 0.43147208121827413

n_estimators: 160
f1: 0.42785445420326224

n_estimators: 170
f1: 0.4328453214513049

n_estimators: 180
f1: 0.42794210195091253

n_estimators: 190
f1: 0.4256378344741755

n_estimators: 200
f1: 0.4259028642590287

n_estimators: 210
f1: 0.4235294117647059

n_estimators: 220
f1: 0.4217687074829933

n_estimators: 230
f1: 0.41982864137086906

n_estimators: 240
f1: 0.4197681513117755

n_estimators: 250
f1: 0.4185478950579622

AUC-ROC: 0.7694552203243435
Accuracy: 0.5640461792193513
F1: 0.4512110726643599

TP: 326
TN: 700
FP: 748
FN: 45

max_depth: 5
n_estimators: 60

Модель логистической регрессии со сблансированной обучающей выборкой, полученной путем уменьшения количества объектов класса «0»¶

In [49]:
# Уменьшение обучающей выборки за счет объектов класса "0" для логистических моделей
features_train_changesizasample, target_train_changesizasample = changesizasample(
    features_train_logistic, 
    target_train, 
    0,              # класс целевой обучающей выборки
    False,          # уменьшение выборки
    class_ratio/100 # кратность изменения (множитель/делитель)
)

print('Изменение размера выборок:')
print(features_train_wooden.shape, '->', features_train_changesizasample.shape)
print(target_train.shape, '->', target_train_changesizasample.shape)
Изменение размера выборок:
(5454, 12) -> (1282, 15)
(5454,) -> (1282,)
In [50]:
# Модель логистической регрессии со сблансированной обучающей выборкой, 
# полученной путем уменьшения количества объектов класса "0"
quality_metrics = LogisticRegression_SelectionOfParametrs(
    features_train_changesizasample, target_train_changesizasample, 
    features_valid_logistic, target_valid, 
    None, 'down_sample', quality_metrics
)
AUC-ROC: 0.743058554600825
Accuracy: 0.2886201209455745
F1: 0.36003956478733923

TP: 364
TN: 161
FP: 1287
FN: 7

Сравнение показателей качества моделей с разными способами балансировки обучающих «деревянными» и логистических выборок и без балансировки¶

In [51]:
# Показатели качества моделей с разными способами балансировки 
# обучающих "деревянными" и логистических выборок и без нее
quality_metrics.sort_values('f1', ascending=False)
Out[51]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
7 RandomForestClassifier up_sample 0.832030 0.837273 0.585434 None 6 80
6 DecisionTreeClassifier up_sample 0.773663 0.807037 0.558491 None 5 None
0 DecisionTreeClassifier none 0.702474 0.842221 0.553655 None 7 None
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
3 DecisionTreeClassifier balance 0.799589 0.851017 0.499076 balanced 5 None
8 LogisticRegression up_sample 0.747809 0.741616 0.482379 None None None
5 LogisticRegression balance 0.748382 0.685542 0.473297 balanced None None
10 RandomForestClassifier down_sample 0.769455 0.564046 0.451211 None 5 60
9 DecisionTreeClassifier down_sample 0.672736 0.641012 0.441403 None 5 None
4 RandomForestClassifier balance 0.821443 0.844970 0.433735 balanced 5 50
11 LogisticRegression down_sample 0.743059 0.288620 0.360040 None None None
2 LogisticRegression none 0.744218 0.804288 0.255230 None None None
In [52]:
print('Лучшее значение показателей "F1", "Accuracy", "AUC-ROC":')
quality_metrics.loc[
    (quality_metrics['f1'] == quality_metrics['f1'].max()) |
    (quality_metrics['accuracy'] == quality_metrics['accuracy'].max()) |
    (quality_metrics['auc_roc'] == quality_metrics['auc_roc'].max())
]
Лучшее значение показателей "F1", "Accuracy", "AUC-ROC":
Out[52]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
7 RandomForestClassifier up_sample 0.832030 0.837273 0.585434 None 6 80

Анализ показателей качества выявил наилучшую комбинацию модели и выборки. Это модель RandomForestClassifier с параметрами n_estimators=8, max_depth=6, random_state=12345 с использованием сбалансированной обучающей выборки с увеличенным в 3 раза количеством объектов класса 1. У этой комбинации самое лучшее значение показателя F1, равного 0.585434, и AUC-ROC, равного 0.832030. Только значение показателя Accuracy незначительно уступает этой же модели, но без балансировки.

Тестирование модели¶

Проведение тестирования¶

In [53]:
# Увеличение обучающей выборки за счет объектов класса "1" для "деревянных" моделей
features_train_changesizasample, target_train_changesizasample = changesizasample(
    features_train_wooden, 
    target_train, 
    1,              # класс целевой обучающей выборки
    True,           # увеличение выборки
    class_ratio     # кратность изменения (множитель/делитель)
)

print(features_train_changesizasample.shape)
print(target_train_changesizasample.shape)
(7678, 12)
(7678,)
In [54]:
# Параметры модели
depth_f1 = int(quality_metrics.loc[
    quality_metrics['f1'] == quality_metrics['f1'].max(), 
    'max_depth'
])
n_estimators_f1 = int(quality_metrics.loc[
    quality_metrics['f1'] == quality_metrics['f1'].max(), 
    'n_estimators'
])

# Создание, обучение и предсказание модели
model = RandomForestClassifier(
    class_weight=None, 
    n_estimators=n_estimators_f1, 
    max_depth=depth_f1, 
    random_state=12345
)
model.fit(features_train_changesizasample, target_train_changesizasample)
predictions_test = model.predict(features_test_wooden)
probabilities_test = model.predict_proba(features_test_wooden)

# Анализ метрик качества моделей
quality_metrics = output_of_quality_metrics(target_train, target_test, 
                                            predictions_test, probabilities_test, 
                                            quality_metrics, 
                                            'RandomForestClassifier', 'up_sample_test', 
                                            None, depth_f1, n_estimators_f1
                                           )

# Параметры модели
print('\nmax_depth:', depth_f1)
print('n_estimators:', n_estimators_f1)
AUC-ROC: 0.8533791448801032
Accuracy: 0.845984598459846
F1: 0.6195652173913043

TP: 228
TN: 1310
FP: 137
FN: 143

max_depth: 6
n_estimators: 80

Значение показателя F1 равно 0.6195652173913043, что больше требуемого 0.59, следовательно, цель проекта достигнута. Кривая ROC сильно выгнута вврех и значение показателя AUC-ROC равно 0.8533791448801032. Это означает, что модель существенно отличается от случайной в лучшую сторону.

Выводы тестирования¶

В тестировании использована модель RandomForestClassifier с параметрами n_estimators=80, max_depth=6, random_state=12345 и class_weight=None с использованием сбалансированной обучающей выборки с увеличенным в 3 раза количеством объектов класса 1. По результатам её использования:

  1. Показатель F1 равен 0.6195652173913043, что больше требуемого 0.59.
  2. Кривая ROC сильно выгнута вврех. Это означает, что модель существенно отличается от случайной в лучшую сторону.
  3. Значение показателя AUC-ROC стремиться к идеальному, оно равно 0.8533791448801032. Значение хоть и не является идальным, оно значительно больше случайного значения, равного 0.5.

Все задачи проекта выполнены.

Выводы проекта¶

В рамках данного проекта проведены следующие работы:

  1. Подготовлены данные. В том числе, загружены библиотеки и их компоненты, проанализированы и предварительно обработаны данные.
  2. Исследована задача проекта. Изучены модели без учёта дисбаланс. Подобраны параметров моделей решающего дерева, случайного леса и логистической регрессии для улучшения их качества. Исследован баланс классов.
  3. Проведена борьба с дисбалансом классов за счет увеличения обучающей выборки класса «1», а также за счет уменьшения обучающей выборки класса «0». Сравнены показатели качества моделей с разными способами балансировки обучающей выборки и без балансировки.
  4. Протестирована выбранная модель и приведены выводы тестирования.

Рзультаты проведённой работы в виде анализа метрик качества всех моделей со сбалансированными разным способом классами в выборках и без балансировки представлены в следующей таблице.

In [55]:
# Рзультаты анализа метрик качества 
# всех моделей с разными выборками
quality_metrics
Out[55]:
model sample auc_roc accuracy f1 class_weight max_depth n_estimators
0 DecisionTreeClassifier none 0.702474 0.842221 0.553655 None 7 None
1 RandomForestClassifier none 0.808292 0.853216 0.532399 None 8 80
2 LogisticRegression none 0.744218 0.804288 0.255230 None None None
3 DecisionTreeClassifier balance 0.799589 0.851017 0.499076 balanced 5 None
4 RandomForestClassifier balance 0.821443 0.844970 0.433735 balanced 5 50
5 LogisticRegression balance 0.748382 0.685542 0.473297 balanced None None
6 DecisionTreeClassifier up_sample 0.773663 0.807037 0.558491 None 5 None
7 RandomForestClassifier up_sample 0.832030 0.837273 0.585434 None 6 80
8 LogisticRegression up_sample 0.747809 0.741616 0.482379 None None None
9 DecisionTreeClassifier down_sample 0.672736 0.641012 0.441403 None 5 None
10 RandomForestClassifier down_sample 0.769455 0.564046 0.451211 None 5 60
11 LogisticRegression down_sample 0.743059 0.288620 0.360040 None None None
12 RandomForestClassifier up_sample_test 0.853379 0.845985 0.619565 None 6 80

Чек-лист готовности проекта¶

Поставьте ‘x’ в выполненных пунктах. Далее нажмите Shift+Enter.

  • [x] Jupyter Notebook открыт
  • [x] Весь код выполняется без ошибок
  • [x] Ячейки с кодом расположены в порядке исполнения
  • [x] Выполнен шаг 1: данные подготовлены
  • [x] Выполнен шаг 2: задача исследована
    • [x] Исследован баланс классов
    • [x] Изучены модели без учёта дисбаланса
    • [x] Написаны выводы по результатам исследования
  • [x] Выполнен шаг 3: учтён дисбаланс
    • [x] Применено несколько способов борьбы с дисбалансом
    • [x] Написаны выводы по результатам исследования
  • [x] Выполнен шаг 4: проведено тестирование
  • [x] Удалось достичь F1-меры не менее 0.59
  • [x] Исследована метрика AUC-ROC

Политика конфиденциальности

Продолжая использовать данный сайт вы подтверждаете свое согласие с условиями его политики конфиденциальности. Подробнее…




Администрация и владельцы данного информационного ресурса не несут ответственности за возможные последствия, связанные с использованием информации, размещенной на нем.


Все права защищены. При копировании материалов сайта обязательно указывать ссылку на © Microsegment.ru (2020-2025)