Обнаружение риска дефолта кредитной карты с помощью машинного обучения



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



Для набора данных я нашел отличный ресурс от Kaggle, где создатель сопоставил данные из Международного института информационных технологий Бангалора, чтобы получить больше информации о транзакциях неплательщиков по кредитным картам. Но сначала мне нужно будет пройти EDA и убедиться, что мой набор данных готов к машинному обучению.

Часть I: ЭДА

1. Определение моей цели

Приблизительно взглянув на набор данных, я обнаружил, что форма (307511, 121). Довольно много данных с множеством атрибутов. Вот фрагмент описания атрибутов:

Мне нужно сначала определить мою целевую переменную. К счастью, создатель набора данных был достаточно любезен, чтобы указать целевую переменную, которая называется «TARGET», разбивая клиентов на два сегмента:

1. Клиенты, у которых могут возникнуть трудности с выплатой кредита и риск неплатежа.

0: клиенты, у которых нет проблем с выплатой кредита.

Это именно то, что я пытаюсь исследовать в этом проекте.

2. Обращение к отсутствующим значениям

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

def missing(df):
total = df.isnull().sum()
percent = (df.isnull().sum()/df.isnull().count()*100)
unique = df.nunique()
datatypes = df.dtypes
return pd.concat([total, percent, unique, datatypes],
axis=1,
keys=['Total', 'Missing_Percent', 'Unique', 'Data_Type']).sort_values(by="Missing_Percent", ascending=False)

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

3. EDA на основе типов данных

Мы видим, что данные можно разделить на три части в зависимости от типов данных: объект, целое число и число с плавающей запятой. Из-за различной природы этих типов данных я буду выполнять EDA для каждого типа данных и в конце соберу все вместе.

объекты

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

int64

К счастью, у меня нет пропущенных значений для типов данных int64, вот распределение всех значений int64.

поплавок64

Следуя той же процедуре для объектов, я заполнил недостающие значения с помощью

floatVal = data.select_dtypes("float64").interpolate(method ='linear', limit_direction ='forward')

и у меня остался этот дистрибутив:

4. Собираем вещи вместе

Теперь я успешно обработал все три типа данных, пришло время объединить их в новый фрейм данных и проверить наличие интересных корреляций между моими данными.

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

Часть II: Машинное обучение

Первоначальный вид

Теперь наши данные готовы к машинному обучению, пришло время применить несколько простых моделей к нашему набору данных и посмотреть, как они работают. Чтобы ускорить процесс, я написал функцию, которая позволяет нам одновременно тестировать несколько моделей в наборе данных. Для начала я буду использовать DummyClassifier(), LogisticRegression(), DecisionTreeClassifier() и GaussianNB().

def model_lot(X_train, X_test, Y_train, Y_test):
collection = [DummyClassifier(),LogisticRegression(),DecisionTreeClassifier(),GaussianNB()]
comparison = pd.DataFrame([])
row_index = 0
for model in collection:
predicted = model.fit(X_train, Y_train).predict(X_test)
comparison.loc[row_index,'Model Name'] = model.__class__.__name__
comparison.loc[row_index, 'Train Accuracy'] = accuracy_score(Y_train,model.predict(X_train))
comparison.loc[row_index, 'Test Accuracy'] = accuracy_score(Y_test,predicted)
comparison.loc[row_index, 'Precision'] = precision_score(Y_test, predicted)
comparison.loc[row_index, 'Recall'] = recall_score(Y_test, predicted)
comparison.loc[row_index, 'F1 score'] = f1_score(Y_test, predicted)
row_index += 1
return comparison.sort_values(by="Precision",ascending=False)

Вот производительность наших моделей:

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

Случайная избыточная выборка

Случайная избыточная выборка — это способ сбалансировать класс меньшинства путем случайного дублирования примеров в классе меньшинства. Поскольку информация только дублируется, общая производительность модели будет только повышена.

oversample = RandomOverSampler(sampling_strategy='minority')
X_train_over, Y_train_over = oversample.fit_resample(X_train, Y_train)

Давайте посмотрим на результат Random Over Sampling:

По сравнению с первоначальными результатами с использованием несбалансированных обучающих данных мы наблюдаем повышение точности моделей и, что более важно, также улучшились оценки точности, полноты и F-меры. Однако они не так высоки, как хотелось бы. Теперь я буду использовать более сложные модели, чтобы улучшить наши результаты.

Сложные модели: классификатор голосования и RandomForestClassifer

Используя ту же логику, что и наши более простые модели, я написал функцию, которая одновременно реализует эти две модели.

def model_complex(model_list):
collection = model_list
comparison = pd.DataFrame([])
row_index = 0
for model in collection:
predicted = model.predict(X_test)
comparison.loc[row_index,'Model Name'] = model.__class__.__name__
comparison.loc[row_index, 'Train Accuracy'] = accuracy_score(Y_train,model.predict(X_train))
comparison.loc[row_index, 'Test Accuracy'] = accuracy_score(Y_test,predicted)
comparison.loc[row_index, 'Precision'] = precision_score(Y_test, predicted)
comparison.loc[row_index, 'Recall'] = recall_score(Y_test, predicted)
comparison.loc[row_index, 'F1 score'] = f1_score(Y_test, predicted)
row_index += 1
return comparison.sort_values(by="Precision",ascending=False)

А вот результат производительности для этих двух моделей

Как видно из результатов, RandomForestClassifier имеет самую высокую точность и показатель точности из всех моделей, которые мы использовали. Его звездная производительность имеет смысл, поскольку RandomForest лучше всего подходит для наборов данных со смесью категориальных и числовых функций и может делать точные прогнозы, несмотря на большое количество шума в наборах данных. Сильные стороны RandomForest прекрасно устраняют недостатки моего текущего набора данных (много зашумленных данных и смесь числовых и категориальных функций). В результате я буду использовать RandomForestClassifier в качестве модели для перехода. Теперь мне нужно будет настроить гиперпараметры для достижения наилучшего результата.

Часть III: Настройка гиперпараметров

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

Выше приведена формула для трех важных показателей: Precision, Recall и Accuracy. Ранее я установил, что точность не будет подходящей метрикой для нашей оценки из-за несбалансированного характера набора данных. Цель моей модели — свести к минимуму убытки банков от дефолтов клиентов. В результате наиболее важной задачей для моей модели является выявление тех, кто подвержен риску дефолта, который будет отмечен как 1 (истинно положительный), и в то же время свести к минимуму тех, кто подвержен риску дефолта, но отмечен как иначе (ложноотрицательный). Имея это в виду, становится ясно, что отзыв является самым важным показателем при оценке производительности из-за характера нашей проблемы. Итак, приступим!

Метод 1: RandomizedSearchCV

Случайный поиск — это метод, при котором используются случайные комбинации гиперпараметров для поиска наилучшего решения для построенной модели. Недостатком случайного поиска является то, что он дает высокую дисперсию во время вычислений. Так как выбор параметров совершенно случайный; и поскольку для выборки этих комбинаций не используется разум, удача играет свою роль.

n_estimators = [int(x) for x in np.linspace(start = 100, stop = 1000, num=10)]
max_features = ["auto","sqrt"]
max_depth = [int(x) for x in np.linspace(start = 10, stop = 50, num=9)]
min_samples_split = [2,5,10]
min_samples_leaf = [1,2,4]
bootstrap = [True,False]
ccp_alpha = [x for x in np.arange(start = 0, stop = 0.03, step=0.005)]
random_grid = {'n_estimators': n_estimators,
'max_features': max_features,
'max_depth': max_depth,
'min_samples_split': min_samples_split,
'min_samples_leaf': min_samples_leaf,
'bootstrap': bootstrap,
"ccp_alpha":ccp_alpha}
rf_new = RandomForestClassifier()
random = RandomizedSearchCV(estimator=rf_new,
param_distributions=random_grid,
n_iter=5,
cv=3,
random_state=2020,
verbose=2,
n_jobs=-1,
scoring="precision")
random.fit(X_train_over,Y_train_over)

RandomizedSearch оказался крайне медленным из-за большого размера моего обучающего набора данных (я запускал его 8 часов и до сих пор не закончил настройку). В результате я буду использовать Optuna, оптимизатор с открытым исходным кодом, чтобы более эффективно настраивать мою модель.

Способ 2: Оптуна

Optuna — это автоматизированная программная среда для оптимизации гиперпараметров, специально разработанная для задач, основанных на машинном обучении. В нем подчеркивается авторитетный пользовательский API с подходом «определение за запуском». К преимуществам Optuna относятся: эффективная реализация стратегий поиска и обрезки, а также интегрированные модули для научного обучения.

import optuna
def objective(trial):
n_estimators = trial.suggest_int('n_estimators', 20, 200)
max_depth = int(trial.suggest_float('max_depth', 10, 50, log=True))
clf = RandomForestClassifier(n_estimators=n_estimators,
max_depth=max_depth)
predicted = clf.fit(X_train_over, Y_train_over).predict(X_test)
return recall_score(Y_test, predicted)
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=10)
trial = study.best_trial
print('Recall: {}'.format(trial.value))
print("Best hyperparameters: {}".format(trial.params))

После запуска 10 испытаний Optuna вернула лучшие гиперпараметры: {‘n_estimators’: 158, ‘max_depth’: 11,207186966546672} с оценкой отзыва 0,59, что является значительным улучшением по сравнению с состоянием до настройки. Вот сравнение показателей производительности до и после настройки:

Здесь мы видим, что показатель отзыва увеличился на 8657%, а точность и воспроизводимость снизились на 23% и 64% соответственно. Это огромное увеличение отзыва и снижение точности и точности означает, что модель стала намного более чувствительной, помечая клиентов как не имеющих права на кредиты и транзакции, и это компромисс, на который я готов пойти. Ведь наша цель в этом проекте — минимизировать потери банков от неплатежей клиентов.

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

Часть IV: Заключение

Забрать

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

Что нужно улучшить

Главный недостаток этого проекта — процесс тюнинга. Из-за большого набора данных даже Optuna немного тормозила при тестировании разных гиперпараметров, поэтому мне пришлось ограничить количество испытаний до 10 (после чего программа просто выключается). В результате этой ограниченной настройки у меня остался приличный, но не идеальный результат. С точки зрения банка, относительно низкий показатель точности означает, что модель регулярно помечает подходящих клиентов как неподходящих, что усложняет клиентам получение кредита, и из-за этого банк может потерять клиентов. Чтобы решить эту проблему, я мог бы: 1. попытаться дополнительно оптимизировать свой набор данных, оставив только часть наиболее важных функций, или настроить Optuna для лучшего соответствия моим данным; 2. попытаться приобрести больше вычислительной мощности. С большим количеством испытаний и дополнительной настройкой гиперпараметров я смог бы в конечном итоге получить модель с высокой точностью и высоким показателем полноты.

Еще одна вещь, которую следует улучшить, — это принять во внимание предыдущее_приложение (которое также представлено в наборе данных). Сопоставление предыдущей истории приложений с текущим приложением дало бы более интересные результаты.