Прогнозирование приемлемости кредита: создание прогнозной модели

Эта статья является заключительной частью серии из четырех частей, посвященных прогнозной модели. В первых двух частях (часть-1 и часть-2) я показываю, как обрабатывать и очищать необработанные данные. Затем, в третьей части, я покажу, как выполнить некоторый исследовательский анализ данных (EDA), чтобы разобраться в данных и разработке функций. В конце обработки и EDA у нас теперь есть 30 наиболее важных функций, включая целевую переменную credit_status. В заключительной, но самой захватывающей части статьи я покажу, как начать с простой, а затем перейти к построению сложной модели с высокой точностью. В этой работе мне удалось построить модель LightGBM на основе дерева с точностью ›87%, тогда как исходная логистическая модель предсказывает с точностью 66%. В следующих разделах я описываю пошаговые процедуры для улучшения модели с помощью необходимой теории и кодов. Хорошо, давайте начнем с импорта необходимых библиотек.

Импорт библиотек

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.linear_model import LogisticRegression
import lightgbm
from sklearn.utils import resample
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_validate, KFold
from sklearn.metrics import recall_score, roc_auc_score, f1_score
from sklearn.metrics import accuracy_score, roc_auc_score, \ classification_report, confusion_matrix

Читать выбранные функции

df_selected = pd.read_csv(‘./data/df_selected.csv’)
df_selected.describe(include = 'all')

Посмотрите на соотношение классов

Прежде чем мы перейдем к чему-либо, давайте рассмотрим соотношение классов в наборе данных. Если у одного класса очень мало примеров, в то время как у другого класса / классов много примеров, мы определяем, что данные несбалансированы, и мы должны решить проблему перед созданием модели. Следующий код показывает соотношение классов используемых данных:

df_selected.loan_status.value_counts(normalize=True)
0    0.783494
1    0.216506
Name: loan_status, dtype: float64
df_selected.loan_status.value_counts()
0    358436
1     99048
Name: loan_status, dtype: int64

Очевидно, что приведенные выше результаты показывают, что набор данных несбалансирован. Класс 1 (списанный) относится к меньшинству и составляет 21,6% от всех примеров. В наборе данных больше примеров соискателей, которые вовремя выплатили ссуду, чем тех, кому были списаны средства. Это проблема для модели машинного обучения, потому что модель будет изучать свой параметр в основном на основе классов с большим количеством примеров. Следовательно, модель будет смещена в сторону большинства. Чтобы решить проблему с классом дисбаланса, мы можем использовать множество приемов, например:

(1) Назначьте вес класса (2) Используйте алгоритмы ансамбля с перекрестной проверкой (3) Повышайте дискретизацию класса меньшинства или понижайте дискретизацию класса большинства

В другом моем блоге (Три метода повышения производительности модели машинного обучения с помощью наборов данных дисбаланса) я подробно объяснил эти три метода. Я также показал, как шаг за шагом улучшить производительность модели. В этой работе я испробовал все методы и обнаружил, что повышающая дискретизация класса меньшинства улучшает обобщение модели на невидимых данных. Поэтому в этой статье я сосредоточусь только на технике передискретизации.

Повторная выборка класса меньшинства

Один из популярных методов работы с сильно несбалансированными наборами данных - это повторная выборка. Хотя этот метод во многих случаях оказался эффективным для решения проблемы несбалансированного класса, тем не менее, он имеет и свои недостатки. Например, избыточная выборка записей из класса меньшинства может привести к переобучению, в то время как удаление случайных записей из класса большинства может вызвать потерю информации. Из второстепенного класса с повышающей дискретизацией и основного класса с пониженной дискретизацией я обнаружил, что модель с повышающей дискретизацией, заряженной вне класса, лучше работает с невидимыми данными. В приведенном ниже коде показан механизм:

df_major = df_selected[df_selected.loan_status == 0]
df_minor = df_selected[df_selected.loan_status == 1]
df_minor_upsmapled = resample(df_minor, replace = True, n_samples = 358436, random_state = 2018)
df_minor_upsmapled = pd.concat([df_minor_upsmapled, df_major])
df_minor_upsmapled.loan_status.value_counts()
1    358436
0    358436
Name: loan_status, dtype: int64

В приведенном выше коде я сначала разделил классы на два фрейма данных: 1. df_major и 2. df_minor. Затем я использую df_minor, чтобы увеличить его до того же числа, что и у основного класса, то есть 358436. Обратите внимание, что я оставляю для параметра replace значение true. Если бы у меня была субдискретизация, я бы оставил для параметра замены значение false. Наконец, я объединяю второстепенный класс с повышенной дискретизацией с основным классом. Посмотрите на классы функции credit_status, теперь они явно сбалансированы. Я бы порекомендовал итеративно пробовать разные техники, пока не найдете лучшую модель.

Стандартизированные данные

В этом разделе я изменяю масштаб данных, удаляя среднее значение каждой выборки, а затем делю на стандартное отклонение. Нулевое среднее и единичное стандартное отклонение помогают ускорить оптимизацию модели. Я использовал метод Scikit-learn StandardScaler. Перед этим я разделил набор данных на обучающую и тестовую части. Следующий код не требует пояснений:

X = df_minor_upsmapled.drop('loan_status', axis = 1)
Y = df_minor_upsmapled.loan_status
xtrain, xtest, ytrain, ytest = train_test_split(X, Y, test_size=0.25, random_state=0)
mms = StandardScaler()
mms.fit(xtrain)
xtrain_scaled = mms.transform(xtrain)

Теперь, когда наши данные готовы, я перехожу к следующему шагу: построению моделей. Как я уже говорил ранее, использование простого алгоритма, такого как логистическая регрессия, помогает упростить задачу и служит эталоном для сложных моделей.

Модель логистической регрессии (LR)

Логистическая регрессия - это метод моделирования, заимствованный из статистики. Это первый алгоритм, который следует начинать и продолжать работать над сложной моделью. Этот алгоритм относительно прост и легко реализуем, поэтому я всегда сначала начинаю с этого метода и записываю производительность модели для будущего тестирования сложных моделей. Это помогает мне легко и интуитивно двигаться вперед. Хорошо, давайте посмотрим, как может работать логистическая регрессия:

logisticRegr = LogisticRegression()
logisticRegr.fit(xtrain_scaled, ytrain)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False)

В приведенном выше коде я обучаю модель с параметрами LR по умолчанию. Ниже я стандартизирую тестовые данные, используя те же среднее значение параметров стандартизации и стандартное отклонение, которые используются в обучающих данных, а затем предсказываю тестовые данные.

xtest_scaled = mms.transform(xtest)
lr_pred = logisticRegr.predict(xtest_scaled)

Чтобы увидеть производительность на тестовых данных, я написал функцию под названием «Assessment_model», которая печатает различные критерии оценки: 1) точность, 2) оценку ROC-AUC, 3) матрицу путаницы и 4) подробный отчет о классификации.

Наконец, давайте посмотрим, как работает логистическая регрессия:

evaluate_model(ytest, lr_pred)
Accuracy of the model: 0.66409066053633

Classification report: 
             precision    recall  f1-score   support

          0       0.66      0.68      0.67     89877
          1       0.67      0.65      0.66     89341

avg / total       0.66      0.66      0.66    179218


Confusion matrix: 
[[60846 29031]
 [31170 58171]]

Приведенный выше прогноз - неплохое начало, но не впечатляет. Точность модели немного выше случайного предположения. Мы видим, что простейшая модель дает точность 66%. Следовательно, нам предстоит еще многое сделать, чтобы создать эффективную модель. Мы должны выбрать лучший алгоритм и настроить его гиперпараметры таким образом, чтобы модель превосходила модель логистической регрессии.

Если мы посмотрим на уникальное значение функции, используя приведенный ниже код, мы увидим, что почти 60% функций являются категориальными, а остальные - непрерывными. Посмотрите на блокнот GitHub jupyter для отображения.

features = pd.DataFrame([], columns = ['feature_name', 'unique_values'])
for i, col in enumerate(df_selected.columns):
    features.loc[i] = [col, len(np.unique(df_selected[col]))]
features.sort_values('unique_values')

Выбор подходящей модели - еще одна проблема для специалиста по данным. Иногда даже опытный специалист по данным не может сказать, какой алгоритм будет работать лучше всего, прежде чем пробовать другие алгоритмы. В нашем окончательном наборе данных почти 60% наших функций являются категориальными. В этом случае лучше выбрать древовидную модель. Тем не менее, это очень непредсказуемо. Если древовидные алгоритмы работают не очень хорошо, мы можем попробовать другой алгоритм, например нейронную сеть. Тем не менее, в этом проекте я бы сначала попробовал методы ансамбля на основе дерева: алгоритмы суммирования (случайный лес) и повышения (LightGBM). Хорошо, давайте начнем со случайного леса.

Модель случайного леса (RF)

Случайный лес - это гибкий и простой в использовании алгоритм ансамбля машинного обучения. Алгоритм настолько легкий и эффективный, что даже без настройки гиперпараметров может дать отличный результат. Это также один из наиболее часто используемых алгоритмов из-за его простоты и того факта, что его можно использовать как для задач классификации, так и для задач регрессии. Подробности метода можно найти на веб-странице Scikit-Sklearn или в этом сообщении в блоге: https://towardsdatascience.com/the-random-forest-algorithm-d457d499ffcd. Приступим к моделированию:

В приведенной выше функции я сначала определяю гиперпараметры. Важными гиперпараметрами случайного леса являются количество оценок и максимальная глубина дерева. Я предпочитаю итеративно находить оптимальные гиперпараметры. В этом случае я сначала начинаю с небольшого количества оценщиков, а затем медленно увеличиваю их. Я считаю, что этот ручной процесс более эффективен и интуитивно понятен, чем использование GridSearchCV или RandomSearch. Существует еще один метод, известный как байесовская оптимизация гиперпараметров, который можно использовать для поиска подходящего набора гиперпараметров. Метод кажется более действенным и действенным. В моем следующем проекте я бы попробовал его в другом проекте. Более подробную информацию о технике можно найти в сообщении блога Уильяма Кёрсена Концептуальное объяснение байесовской оптимизации гиперпараметров для машинного обучения. Как я уже говорил, я начинаю с небольшого числа с наиболее важным гиперпараметром, и как только я нахожу оптимальное значение, я начинаю со следующего важного гиперпараметра и так далее.

Хорошо, пора посмотреть на производительность случайного леса на тестовых данных:

evaluate_model(ytest, rfpred, rfpred_proba)
ROC-AUC score of the model: 0.8054282761077389
Accuracy of the model: 0.7304177035788816

Classification report: 
             precision    recall  f1-score   support

          0       0.75      0.69      0.72     89877
          1       0.71      0.77      0.74     89341

avg / total       0.73      0.73      0.73    179218


Confusion matrix: 
[[61972 27905]
 [20409 68932]]

Ух ты, радомский лес лучше. Точность логистической регрессии составляет почти 11%, что является большим достижением и доказывает, что древовидные модели хорошо работают с категориальными данными.

Поскольку дальнейшая настройка гиперпараметров не улучшает производительность модели, я прекратил работу над моделью случайного леса. Я пробовал использовать другие гиперпараметры и увеличивать n_estimators, но это не помогло. Эти трудности помогли мне решить использовать дерево с градиентным усилением. Так как я не планирую продвигаться дальше со случайным лесом, давайте проверим надежность модели. Один из способов узнать о перекрестной проверке.

Перекрестная проверка модели случайного леса

Перекрестная проверка - один из эффективных способов оценки модели и ее обобщающей способности с использованием независимого набора данных на практике. Если производительность модели на разных складках одинакова, то можно сказать, что модель надежна и работает хорошо. Далее мы проверяем надежность RF-модели с помощью метода перекрестной проверки Scikit-Sklearn:

scoring = [‘accuracy’, ‘recall’, ‘roc_auc’, ‘f1’]
scores = cross_validate(rf, X = xtrain_scaled, y = ytrain, scoring = scoring, cv = 10, return_train_score = False, verbose = 10, n_jobs= -1)

Как вы можете видеть в приведенном выше коде, я использую четыре различных показателя оценки («точность», «отзыв», «roc_auc», «f1»), чтобы судить об обобщении модели. Посмотрим на результат:

scores
{'fit_time': array([519.01635194, 519.08185387, 518.91476393, 514.854949  ,
        491.86662292, 491.60002613, 492.63204885, 491.53150296,
        296.78954768, 297.04013801]),
 'score_time': array([10.45862293, 10.56293011, 10.65213013, 10.40467215, 10.21855617,
        10.32528472, 10.14684105, 10.33355498,  6.49080539,  6.19442701]),
 'test_accuracy': array([0.72979206, 0.7310196 , 0.73124279, 0.73332589, 0.73371648,
        0.72999163, 0.7343625 , 0.72937785, 0.73207477, 0.73201399]),
 'test_recall': array([0.76737272, 0.77194352, 0.77324415, 0.77840951, 0.77421033,
        0.77122896, 0.77728641, 0.77037422, 0.77174923, 0.77271545]),
 'test_roc_auc': array([0.80477504, 0.80651739, 0.80334897, 0.8090473 , 0.80837787,
        0.80377023, 0.80829186, 0.80413098, 0.80798087, 0.8082303 ]),
 'test_f1': array([0.73977216, 0.74178689, 0.74226804, 0.74502063, 0.74427079,
        0.74087428, 0.74548241, 0.74022496, 0.74248735, 0.74268672])}

В приведенном выше фрагменте кода распечатайте результаты перекрестной проверки. Если вы внимательно посмотрите на различные метрики оценки, вы увидите, что модель стабильно работает во всех складках, а значит, является надежной. Давайте посчитаем более конкретные показатели, такие как среднее значение и дисперсия:

print('F1 score# (1) mean: {} (2)variance: {}'.format(np.mean(scores['test_f1']), np.var(scores['test_f1'])))
print('Recall score# (1) mean: {} (2)variance: {}'.format(np.mean(scores['test_recall']), np.var(scores['test_recall'])))
print('Accuracy score# (1) mean: {} (2)variance: {}'.format(np.mean(scores['test_accuracy']), np.var(scores['test_accuracy'])))
### mean and variance of the merics:
F1 score# (1) mean: 0.7424874224946193 (2)variance: 3.4239691671447294e-06
Recall score# (1) mean: 0.7728534498486367 (2)variance: 9.340496661280428e-06
Accuracy score# (1) mean: 0.7316917565649772 (2)variance: 2.6660110240196636e-06

Приятно видеть, что все оценочные показатели имеют очень низкую дисперсию, что еще раз подтверждает надежность модели. Несмотря на то, что модель крепкая, я пока не доволен. Нам нужно улучшить производительность модели. Затем я бы попробовал алгоритм на основе дерева с градиентным усилением. Существует множество древовидных алгоритмов с усилением градиентов, таких как XGBoost, LightGBM, CataBoost и т. Д. Я считаю, что LightGBM быстрее и хорошо работает с категориальными данными.

Модель LightGBM

В приведенном выше коде я сначала вручную нахожу оптимальные гиперпараметры, аналогично тому, как это делал в модели случайного леса. Я считаю, что наиболее важными параметрами в модели являются n_estimators и max_depth. Давайте посмотрим на прогноз и производительность модели на тестовых данных:

### Performance report
ROC-AUC score of the model: 0.9586191656898193
Accuracy of the model: 0.890546708477943

Classification report: 
             precision    recall  f1-score   support

          0       0.93      0.85      0.89     89877
          1       0.86      0.94      0.90     89341

avg / total       0.89      0.89      0.89    179218


Confusion matrix: 
[[75963 13914]
 [ 5702 83639]]

Отчет о производительности кажется многообещающим. Точность подскочила на 35% по логистической регрессии и на 23% по модели случайного леса. На этом я останавливаюсь, оптимизируя другие гиперпараметры. На поиск двух гиперпараметров у меня ушло около 4 часов. Теперь я сосредотачиваюсь на надежности модели, используя ту же методику перекрестной проверки.

Если вы посмотрите на результат ниже, вы увидите, что LightGBM стабильно работает в разных тренировочных пакетах, что мы и хотели построить. Мы здесь.

--------------- cv: 1 --------------------
ROC-AUC score of the model: 0.9483670270426987
Accuracy of the model: 0.8754963684890869

Classification report: 
             precision    recall  f1-score   support

          0       0.91      0.83      0.87     53472
          1       0.84      0.92      0.88     54059

avg / total       0.88      0.88      0.88    107531


Confusion matrix: 
[[44284  9188]
 [ 4200 49859]]
--------------- cv: 2 --------------------
ROC-AUC score of the model: 0.9478880521895745
Accuracy of the model: 0.8756730617217361

Classification report: 
             precision    recall  f1-score   support

          0       0.92      0.83      0.87     53861
          1       0.84      0.92      0.88     53670

avg / total       0.88      0.88      0.88    107531


Confusion matrix: 
[[44633  9228]
 [ 4141 49529]]

--------------- cv: 3 --------------------
ROC-AUC score of the model: 0.9490374658331387
Accuracy of the model: 0.8780909691158829

Classification report: 
             precision    recall  f1-score   support

          0       0.92      0.83      0.87     53504
          1       0.85      0.92      0.88     54027

avg / total       0.88      0.88      0.88    107531


Confusion matrix: 
[[44457  9047]
 [ 4062 49965]]
--------------- cv: 4 --------------------
ROC-AUC score of the model: 0.9490402272662238
Accuracy of the model: 0.8781188680473538

Classification report: 
             precision    recall  f1-score   support

          0       0.92      0.83      0.87     53958
          1       0.85      0.92      0.88     53573

avg / total       0.88      0.88      0.88    107531


Confusion matrix: 
[[44916  9042]
 [ 4064 49509]]
--------------- cv: 5 --------------------
ROC-AUC score of the model: 0.9486846347287888
Accuracy of the model: 0.8762112898725937

Classification report: 
             precision    recall  f1-score   support

          0       0.91      0.83      0.87     53764
          1       0.84      0.92      0.88     53766

avg / total       0.88      0.88      0.88    107530


Confusion matrix: 
[[44671  9093]
 [ 4218 49548]]

В этом проекте я ничего не говорил о переобучении. Надеюсь, я напишу еще один блог по этой теме.

Большое спасибо за чтение. Надеюсь, вам понравилась вся серия. Полный код можно найти на Github. Я хотел бы получить известие от вас. Если вы обнаружите какую-либо ошибку, которую я допустил в коде или где-либо еще, пожалуйста, напишите комментарий. Если хотите, можете связаться со мной:

Email: [email protected]
LinkedIn: https://www.linkedin.com/in/sabber-ahamed/
Github: https://github.com/msahamed
Medium: https://medium.com/@sabber/