Прогнозирование приемлемости кредита: создание прогнозной модели
Эта статья является заключительной частью серии из четырех частей, посвященных прогнозной модели. В первых двух частях (часть-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/