Рубрики
Без рубрики

Могу ли я оценить кредиты лучше, чем londingClub?

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

Писать мою нейронную сеть против корпоративного эталона

  1. Введение
  2. Ключевые правила
  3. Тестовая метрика
  4. Поворот londingClub
  5. Моя очередь
  6. Победа!

Введение

В случае, если вы пропустили это, я построил нейронную сеть для прогнозирования риска кредита используя Общественный набор данных от LondingClub. . Тогда я построил Публичные API служить прогнозам модели. Это хорошее и все, но … как Хорошо моя модель?

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

import joblib

prev_notebook_folder = "../input/building-a-neural-network-to-predict-loan-risk/"
loans = joblib.load(prev_notebook_folder + "loans_for_eval.joblib")
loans.shape
(1110171, 70)
loans.head()
5.91 36 месяцев C DEC-2015. консолидация долгов 3600.0 C4. 0.0 0 55000.0 10+ лет 0.0 675.0 ИПОТЕКА 2400.0 4429.08 178050.0 148.0 7746.0 1.0 13734.0
16.06 36 месяцев C DEC-2015. малый бизнес 24700.0 C1. 0.0 1 65000.0 10+ лет 1.0 715.0 ИПОТЕКА 79300.0 29530.08 314017.0 192.0 39475.0 1.0 24667.0
10.78 60 месяцев B DEC-2015. home_improvement. 20000.0 B4. 0.0 2 63000.0 10+ лет 0.0 695.0 ИПОТЕКА 6200.0 25959.60 218418.0 184.0 18696.0 1.0 14877.0
25.37 60 месяцев F DEC-2015. major_purchase. 10400.0 F1. 0.0 4 104433.0 3 года 1.0 695.0 ИПОТЕКА 20300.0 17394.60 439570.0 210.0 95768.0 1.0 88097.0
10.20 36 месяцев C DEC-2015. консолидация долгов 11950.0 C3. 0.0 5 34000.0 4 года 0.0 690.0 АРЕНДА 9400.0 14586.48 16900.0 338.0 12798.0 1.0 4000.0
5 rows × 70 columns

Этот пост был адаптирован из ноутбука Jupyter, кстати, так что если вы хотите следовать в своем собственном ноутбуке, идите вперед и вилку Kaggle или Github Действительно

Ключевые правила

Это будет чистая бой – моя модель не будет использовать какие-либо данные LondingClub, не имел доступа к тому, что они рассчитывают оценку кредита (включая саму оценку).

Я собираюсь сортировать набор данных хронологически (используя Column espient_D Столбец, месяц и год, который был выдан кредит) и разделил его на две части. Первые 80% я буду использовать для обучения моей модели конкуренции, и я сравним производительность на последних 20%.

from sklearn.model_selection import train_test_split

loans["date"] = loans["issue_d"].astype("datetime64[ns]")
loans.sort_values("date", axis="index", inplace=True, kind="mergesort")

train, test = train_test_split(loans, test_size=0.2, shuffle=False)
train, test = train.copy(), test.copy()
print(f"The test set contains {len(test):,} loans.")
The test set contains 222,035 loans.

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

Я должен дать кредит Майкла Wurm, кстати, за идея По сравнению с производительностью моей модели к оценкам кредитов LeondingClub, но мой подход довольно разный. Я не пытаюсь имитировать выступление инвестиционного портфеля; Я просто оцениваю, насколько хорошо мои прогнозы простого риска сравнивать.

Тестовая метрика

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

LondingClub сделает плита сначала. Я пойму все свои кредиты из тестового набора, посчитайте их и рассчитать их среднее fraction_recovered Отказ Это среднее значение будет метрикой, моя модель должна победить.

Тогда я буду тренировать свою модель на тренировке, используя то же самое Трубопровод и параметры Я поселился в моей последней ноутбуке. Как только он обучен, я буду использовать его, чтобы сделать прогнозы на тестовом наборе, а затем соберите количество главных прогнозов, равных количеству кредитов класса LondingClub. Наконец, я рассчитаю то же среднее значение fraction_recovered На этом подмножестве, и мы будем победителем!

Поворот londingClub

from statistics import mean

lc_grade_a = test[test["grade"] == "A"]
print(f"LendingClub gave {len(lc_grade_a):,} loans in the test set an A grade.")

print("\nAverage `fraction_recovered` on LendingClub's grade A loans:")
print(round(mean(lc_grade_a["fraction_recovered"]), 5))
LendingClub gave 38,779 loans in the test set an A grade.

Average `fraction_recovered` on LendingClub's grade A loans:
0.96021

Это довольно высокий процент. Я немного нервничаю.

Моя очередь

Во-первых, я скопирую за мой Run_Pipeline Функция из Мой предыдущий ноутбук :

from sklearn.model_selection import train_test_split
from sklearn_pandas import DataFrameMapper
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, StandardScaler
from tensorflow.keras import Sequential, Input
from tensorflow.keras.layers import Dense, Dropout


def run_pipeline(
    data,
    onehot_cols,
    ordinal_cols,
    batch_size,
    validate=True,
):
    X = data.drop(columns=["fraction_recovered"])
    y = data["fraction_recovered"]
    X_train, X_valid, y_train, y_valid = (
        train_test_split(X, y, test_size=0.2, random_state=0)
        if validate
        else (X, None, y, None)
    )

    transformer = DataFrameMapper(
        [
            (onehot_cols, OneHotEncoder(drop="if_binary")),
            (
                list(ordinal_cols.keys()),
                OrdinalEncoder(categories=list(ordinal_cols.values())),
            ),
        ],
        default=StandardScaler(),
    )

    X_train = transformer.fit_transform(X_train)
    X_valid = transformer.transform(X_valid) if validate else None

    input_nodes = X_train.shape[1]
    output_nodes = 1

    model = Sequential()
    model.add(Input((input_nodes,)))
    model.add(Dense(64, activation="relu"))
    model.add(Dropout(0.3, seed=0))
    model.add(Dense(32, activation="relu"))
    model.add(Dropout(0.3, seed=1))
    model.add(Dense(16, activation="relu"))
    model.add(Dropout(0.3, seed=2))
    model.add(Dense(output_nodes))
    model.compile(optimizer="adam", loss="mean_squared_logarithmic_error")

    history = model.fit(
        X_train,
        y_train,
        batch_size=batch_size,
        epochs=100,
        validation_data=(X_valid, y_valid) if validate else None,
        verbose=2,
    )

    return history.history, model, transformer


onehot_cols = ["term", "application_type", "home_ownership", "purpose"]
ordinal_cols = {
    "emp_length": [
        "< 1 year",
        "1 year",
        "2 years",
        "3 years",
        "4 years",
        "5 years",
        "6 years",
        "7 years",
        "8 years",
        "9 years",
        "10+ years",
    ]
}

Теперь на момент истины:

# Train the model
_, model, transformer = run_pipeline(
    train.drop(columns=["issue_d", "date", "grade", "sub_grade", "expected_return"]),
    onehot_cols,
    ordinal_cols,
    batch_size=128,
    validate=False,
)

# Make predictions
X_test = transformer.transform(
    test.drop(
        columns=[
            "fraction_recovered",
            "issue_d",
            "date",
            "grade",
            "sub_grade",
            "expected_return",
        ]
    )
)
test["model_predictions"] = model.predict(X_test)

# Gather top predictions
test_sorted = test.sort_values("model_predictions", axis="index", ascending=False)
ty_grade_a = test_sorted.iloc[0:len(lc_grade_a)]

# Display results
print("\nAverage `fraction_recovered` on Ty's grade A loans:")
print(format(mean(ty_grade_a["fraction_recovered"]), ".5f"))
Epoch 1/100
6939/6939 - 13s - loss: 0.0249
Epoch 2/100
6939/6939 - 13s - loss: 0.0204
Epoch 3/100
6939/6939 - 13s - loss: 0.0202
Epoch 4/100
6939/6939 - 13s - loss: 0.0202
Epoch 5/100
6939/6939 - 13s - loss: 0.0202
Epoch 6/100
6939/6939 - 14s - loss: 0.0201
Epoch 7/100
6939/6939 - 14s - loss: 0.0201
Epoch 8/100
6939/6939 - 14s - loss: 0.0201
Epoch 9/100
6939/6939 - 13s - loss: 0.0201
Epoch 10/100
6939/6939 - 12s - loss: 0.0201
Epoch 11/100
6939/6939 - 13s - loss: 0.0201
Epoch 12/100
6939/6939 - 13s - loss: 0.0201
Epoch 13/100
6939/6939 - 13s - loss: 0.0201
Epoch 14/100
6939/6939 - 13s - loss: 0.0201
Epoch 15/100
6939/6939 - 12s - loss: 0.0201
Epoch 16/100
6939/6939 - 12s - loss: 0.0201
Epoch 17/100
6939/6939 - 13s - loss: 0.0200
Epoch 18/100
6939/6939 - 13s - loss: 0.0200
Epoch 19/100
6939/6939 - 13s - loss: 0.0200
Epoch 20/100
6939/6939 - 14s - loss: 0.0200
Epoch 21/100
6939/6939 - 13s - loss: 0.0200
Epoch 22/100
6939/6939 - 13s - loss: 0.0200
Epoch 23/100
6939/6939 - 12s - loss: 0.0200
Epoch 24/100
6939/6939 - 12s - loss: 0.0200
Epoch 25/100
6939/6939 - 12s - loss: 0.0200
Epoch 26/100
6939/6939 - 13s - loss: 0.0200
Epoch 27/100
6939/6939 - 13s - loss: 0.0200
Epoch 28/100
6939/6939 - 13s - loss: 0.0200
Epoch 29/100
6939/6939 - 13s - loss: 0.0200
Epoch 30/100
6939/6939 - 13s - loss: 0.0200
Epoch 31/100
6939/6939 - 15s - loss: 0.0200
Epoch 32/100
6939/6939 - 13s - loss: 0.0200
Epoch 33/100
6939/6939 - 12s - loss: 0.0200
Epoch 34/100
6939/6939 - 13s - loss: 0.0200
Epoch 35/100
6939/6939 - 13s - loss: 0.0200
Epoch 36/100
6939/6939 - 13s - loss: 0.0200
Epoch 37/100
6939/6939 - 13s - loss: 0.0200
Epoch 38/100
6939/6939 - 13s - loss: 0.0200
Epoch 39/100
6939/6939 - 13s - loss: 0.0200
Epoch 40/100
6939/6939 - 13s - loss: 0.0200
Epoch 41/100
6939/6939 - 13s - loss: 0.0200
Epoch 42/100
6939/6939 - 13s - loss: 0.0200
Epoch 43/100
6939/6939 - 14s - loss: 0.0200
Epoch 44/100
6939/6939 - 13s - loss: 0.0200
Epoch 45/100
6939/6939 - 13s - loss: 0.0200
Epoch 46/100
6939/6939 - 13s - loss: 0.0200
Epoch 47/100
6939/6939 - 13s - loss: 0.0200
Epoch 48/100
6939/6939 - 13s - loss: 0.0200
Epoch 49/100
6939/6939 - 13s - loss: 0.0200
Epoch 50/100
6939/6939 - 13s - loss: 0.0200
Epoch 51/100
6939/6939 - 13s - loss: 0.0200
Epoch 52/100
6939/6939 - 13s - loss: 0.0200
Epoch 53/100
6939/6939 - 13s - loss: 0.0200
Epoch 54/100
6939/6939 - 14s - loss: 0.0200
Epoch 55/100
6939/6939 - 14s - loss: 0.0200
Epoch 56/100
6939/6939 - 13s - loss: 0.0200
Epoch 57/100
6939/6939 - 13s - loss: 0.0200
Epoch 58/100
6939/6939 - 13s - loss: 0.0200
Epoch 59/100
6939/6939 - 13s - loss: 0.0200
Epoch 60/100
6939/6939 - 13s - loss: 0.0200
Epoch 61/100
6939/6939 - 13s - loss: 0.0200
Epoch 62/100
6939/6939 - 13s - loss: 0.0200
Epoch 63/100
6939/6939 - 13s - loss: 0.0200
Epoch 64/100
6939/6939 - 13s - loss: 0.0200
Epoch 65/100
6939/6939 - 12s - loss: 0.0200
Epoch 66/100
6939/6939 - 13s - loss: 0.0200
Epoch 67/100
6939/6939 - 14s - loss: 0.0200
Epoch 68/100
6939/6939 - 13s - loss: 0.0200
Epoch 69/100
6939/6939 - 13s - loss: 0.0200
Epoch 70/100
6939/6939 - 13s - loss: 0.0200
Epoch 71/100
6939/6939 - 13s - loss: 0.0200
Epoch 72/100
6939/6939 - 13s - loss: 0.0200
Epoch 73/100
6939/6939 - 13s - loss: 0.0200
Epoch 74/100
6939/6939 - 13s - loss: 0.0200
Epoch 75/100
6939/6939 - 13s - loss: 0.0200
Epoch 76/100
6939/6939 - 13s - loss: 0.0200
Epoch 77/100
6939/6939 - 13s - loss: 0.0200
Epoch 78/100
6939/6939 - 13s - loss: 0.0200
Epoch 79/100
6939/6939 - 14s - loss: 0.0200
Epoch 80/100
6939/6939 - 13s - loss: 0.0200
Epoch 81/100
6939/6939 - 13s - loss: 0.0200
Epoch 82/100
6939/6939 - 13s - loss: 0.0200
Epoch 83/100
6939/6939 - 13s - loss: 0.0200
Epoch 84/100
6939/6939 - 12s - loss: 0.0200
Epoch 85/100
6939/6939 - 13s - loss: 0.0200
Epoch 86/100
6939/6939 - 13s - loss: 0.0200
Epoch 87/100
6939/6939 - 13s - loss: 0.0200
Epoch 88/100
6939/6939 - 13s - loss: 0.0200
Epoch 89/100
6939/6939 - 13s - loss: 0.0200
Epoch 90/100
6939/6939 - 13s - loss: 0.0200
Epoch 91/100
6939/6939 - 14s - loss: 0.0200
Epoch 92/100
6939/6939 - 13s - loss: 0.0200
Epoch 93/100
6939/6939 - 13s - loss: 0.0200
Epoch 94/100
6939/6939 - 13s - loss: 0.0200
Epoch 95/100
6939/6939 - 13s - loss: 0.0200
Epoch 96/100
6939/6939 - 13s - loss: 0.0200
Epoch 97/100
6939/6939 - 13s - loss: 0.0200
Epoch 98/100
6939/6939 - 13s - loss: 0.0200
Epoch 99/100
6939/6939 - 13s - loss: 0.0200
Epoch 100/100
6939/6939 - 13s - loss: 0.0200

Average `fraction_recovered` on Ty's grade A loans:
0.96166

Победа!

Феве, это был близкий! Моя победа может быть слишком маленькой, чтобы быть статистически значимым, но эй, прохладно видеть, что я могу отставать от лучших и ярких и ярких.

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

Оригинал: “https://dev.to/tymick/can-i-grade-loans-better-than-lendingclub-19jd”