image

Сейчас все очень много говорят про искусственный интеллект и его применение во всех сферах работы компании. Однако есть некоторые области, где еще с давних времён главенствует один вид модели, так называемый «белый ящик» — логистическая регрессия. Одна из таких областей – банковский кредитный скоринг.

Для этого есть несколько причин:

  • Коэффициенты регрессии можно легко объяснить в отличие от «черных ящиков» вроде бустинга, куда может входить более 500 переменных
  • Машинное обучение всё еще не вызывает доверия у менеджмента из-за сложности в интерпретации моделей
  • Существуют неписанные требования регулятора к интепретируемости моделей: в любой момент, например, Центробанк может попросить объяснения — почему было отказано в кредите заемщику
  • Компании используют внешние data mining программы (например, rapid miner, SAS Enterprise Miner, STATISTICA или любой другой пакет), которые позволяют быстро научиться строить модели, даже не имея навыков программирования

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

В этом посте мы расскажем о том, как при построении скоринга мы отказались от внешних data mining пакетов в пользу open source решения в виде Python, увеличили скорость разработки в несколько раз, а также улучшили качество всех моделей.

Процесс построения скоринга


Классический процесс построения скоринговых моделей на регрессии выглядит так:

image

Он может меняться от компании к компании, но главные этапы остаются постоянными. Нам всегда необходимо производить биннинг переменных (в отличие от парадигмы машинного обучения, где в большинстве случаев нужно лишь категориальное кодирование), их отсев по Information Value (IV), и ручную выгрузку всех коэффициентов и бинов для последующей интеграции в DSL.
Такой подход к построению скоринговых карт отлично работал в 90-е, однако технологии классических data mining пакетов сильно устарели и не позволяют использовать новые методики, такие как, например, L2-регуляризация в регрессии, которые позволяют значительно улучшить качество моделей.

В один момент в качестве исследования мы решили воспроизвести все этапы, которые аналитики делают при построении скоринга, дополнить их знаниями Data Scientist’ов, а также максимально автоматизировать весь процесс.

Улучшение в Python


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

Первым делом необходим сбор данных и генерация переменных – этот этап составляет значительную часть работы аналитиков.

В Python загрузить из базы собранные данные можно с помощью pymysql.

Код для загрузки из базы
def con():
    conn = pymysql.connect(
        host='10.100.10.100',
        port=3306,
        user='******* ',
        password='*****',
        db='mysql')
    return conn;

df = pd.read_sql('''
SELECT  * 
FROM idf_ru.data_for_scoring
''', con=con())


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

Подготовка данных
def filling(df):
    cat_vars = df.select_dtypes(include=[object]).columns
    num_vars = df.select_dtypes(include=[np.number]).columns
    df[cat_vars] = df[cat_vars].fillna('_MISSING_')
    df[num_vars] = df[num_vars].fillna(np.nan)
    return df

def replace_not_frequent(df, cols, perc_min=5, value_to_replace = "_ELSE_"):
        else_df = pd.DataFrame(columns=['var', 'list'])
        for i in cols:
            if i != 'date_requested' and i != 'credit_id':
                t = df[i].value_counts(normalize=True)
                q = list(t[t.values < perc_min/100].index)
                if q:
                    else_df = else_df.append(pd.DataFrame([[i, q]], columns=['var', 'list']))
                df.loc[df[i].value_counts(normalize=True)[df[i]].values < perc_min/100, i] =value_to_replace
        else_df = else_df.set_index('var')
        return df, else_df

cat_vars = df.select_dtypes(include=[object]).columns
df = filling(df)

df, else_df = replace_not_frequent_2(df, cat_vars)

df.drop(['credit_id', 'target_value', 'bor_credit_id', 'bchg_credit_id', 'last_credit_id', 'bcacr_credit_id',  'bor_bonuses_got' ], axis=1, inplace=True)

df_train, df_test, y_train, y_test = train_test_split(df, y, test_size=0.33, stratify=df.y, random_state=42)


Теперь начинается самой важный этап в скоринге для регресии – необходимо написать WOE-binning для числовых и категориальных переменных. В открытом доступе мы не нашли хороших и подходящих для нас вариантов и решили написать сами. За основу числового биннинга взяли эту статью 2017 года, а также эту, категориальный написали сами с нуля. Результаты получились впечатляющими (Gini на тесте поднимался на 3-5 по сравнению с алгоритмами биннинга внешних data mining программ).

После этого можно посмотреть на графиках или таблицах (которые мы потом запишем в excel), как переменные разбились по группам и проверить монотонность:

image

image

Отрисовка графиков бинов
def plot_bin(ev, for_excel=False):
    ind = np.arange(len(ev.index)) 
    width = 0.35
    fig, ax1 = plt.subplots(figsize=(10, 7))
    ax2 = ax1.twinx()
    p1 = ax1.bar(ind, ev['NONEVENT'], width, color=(24/254, 192/254, 196/254))
    p2 = ax1.bar(ind, ev['EVENT'], width, bottom=ev['NONEVENT'], color=(246/254, 115/254, 109/254))

    ax1.set_ylabel('Event Distribution', fontsize=15)
    ax2.set_ylabel('WOE', fontsize=15)

    plt.title(list(ev.VAR_NAME)[0], fontsize=20) 
    ax2.plot(ind, ev['WOE'], marker='o', color='blue')
    # Legend
    plt.legend((p2[0], p1[0]), ('bad', 'good'), loc='best', fontsize=10)

    #Set xticklabels
    q = list()
    for i in range(len(ev)):
        try:
            mn = str(round(ev.MIN_VALUE[i], 2))
            mx = str(round(ev.MAX_VALUE[i], 2))
        except:
            mn = str((ev.MIN_VALUE[i]))
            mx = str((ev.MAX_VALUE[i]))
        q.append(mn + '-' + mx)

    plt.xticks(ind, q, rotation='vertical')
    for tick in ax1.get_xticklabels():
        tick.set_rotation(60)
    plt.savefig('{}.png'.format(ev.VAR_NAME[0]), dpi=500, bbox_inches = 'tight')
    plt.show() 

def plot_all_bins(iv_df):
    for i in [x.replace('WOE_','') for x in X_train.columns]:
        ev = iv_df[iv_df.VAR_NAME==i]
        ev.reset_index(inplace=True)
        plot_bin(ev)     


Отдельно была написана функция для ручного биннинга, которая полезна, например, в случае с переменной «версия ОС», где все телефоны на Android и iOS были сгруппированы вручную.

Функция ручного биннинга
def adjust_binning(df, bins_dict):
    for i in range(len(bins_dict)):
        key = list(bins_dict.keys())[i]
        if type(list(bins_dict.values())[i])==dict:
            df[key] = df[key].map(list(bins_dict.values())[i])
        else:
            #Categories labels
            categories = list()
            for j in range(len(list(bins_dict.values())[i])):
                if j == 0:
                    categories.append('<'+ str(list(bins_dict.values())[i][j]))
                    try:                        
                        categories.append('(' + str(list(bins_dict.values())[i][j]) +'; '+ str(list(bins_dict.values())[i][j+1]) + ']')
                    except:                       
                        categories.append('(' + str(list(bins_dict.values())[i][j]))
                elif j==len(list(bins_dict.values())[i])-1:
                    categories.append(str(list(bins_dict.values())[i][j]) +'>')
                else:
                    categories.append('(' + str(list(bins_dict.values())[i][j]) +'; '+ str(list(bins_dict.values())[i][j+1]) + ']')
            
            values = [df[key].min()] + list(bins_dict.values())[i]  + [df[key].max()]        
            df[key + '_bins'] = pd.cut(df[key], values, include_lowest=True, labels=categories).astype(object).fillna('_MISSING_').astype(str)
            df[key] = df[key + '_bins']#.map(df.groupby(key + '_bins')[key].agg('median'))
            df.drop([key + '_bins'], axis=1, inplace=True)
    return df

bins_dict = {    
   'equi_delinquencyDays': [ 200,400,600]
    'loan_purpose': {'medicine':'1_group',
                    'repair':'1_group',
                    'helpFriend':'2_group'}
}
 
df = adjust_binning(df, bins_dict)


Следующим этапом идёт отбор переменных по Information Value. Стандартным значением является кат офф 0.1 (все переменные ниже не имеют хорошей предсказательной силы).

После осуществлялась проверка на корреляцию. Из двух коррелирующих переменных нужно удалить ту, у которой IV меньше. Кат офф по удалению был взят 0.75.

image

Удаление корреляций
def delete_correlated_features(df, cut_off=0.75, exclude = []):
    # Create correlation matrix
    corr_matrix = df.corr().abs()

    # Select upper triangle of correlation matrix
    upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool))
    
    # Plotting All correlations
    f, ax = plt.subplots(figsize=(15, 10))
    plt.title('All correlations', fontsize=20)
    sns.heatmap(X_train.corr(), annot=True)
    
    # Plotting highly correlated
    try:
        f, ax = plt.subplots(figsize=(15, 10))
        plt.title('High correlated', fontsize=20)
        sns.heatmap(corr_matrix[(corr_matrix>cut_off) & (corr_matrix!=1)].dropna(axis=0, how='all').dropna(axis=1, how='all'), annot=True, linewidths=.5)
    except:
        print ('No highly correlated features found')
        
    # Find index of feature columns with correlation greater than cut_off
    to_drop = [column for column in upper.columns if any(upper[column] > cut_off)]
    to_drop = [column for column in to_drop if column not in exclude]
    print ('Dropped columns:', to_drop, '\n')
    df2 = df.drop(to_drop, axis=1)
    print ('Features left after correlation check: {}'.format(len(df.columns)-len(to_drop)), '\n')    
   
    print ('Not dropped columns:', list(df2.columns), '\n')
    
    # Plotting final correlations
    f, ax = plt.subplots(figsize=(15, 10))
    plt.title('Final correlations', fontsize=20)
    sns.heatmap(df2.corr(), annot=True)
    plt.show()
    
    return df2 


Помимо отбора по IV мы добавили рекурсивный поиск оптимального количества переменных методом RFE из sklearn.
Как мы видим на графике – после 13 переменных качество не изменяется, а значит лишние можно удалить. Для регрессии более 15 переменных в скоринге считается плохим тоном, что в большинстве случаев исправляется с помощью RFE.

image
RFE
def RFE_feature_selection(clf_lr, X, y):
    rfecv = RFECV(estimator=clf_lr, step=1, cv=StratifiedKFold(5), verbose=0, scoring='roc_auc')
    rfecv.fit(X, y)

    print("Optimal number of features : %d" % rfecv.n_features_)

    # Plot number of features VS. cross-validation scores
    f, ax = plt.subplots(figsize=(14, 9))
    plt.xlabel("Number of features selected")
    plt.ylabel("Cross validation score (nb of correct classifications)")
    plt.plot(range(1, len(rfecv.grid_scores_) + 1), rfecv.grid_scores_)
    plt.show()
    mask = rfecv.get_support()
    X = X.ix[:, mask]
    return X


Далее строилась регрессия и оценивались её метрики на кросс-валидации и тестовой выборке. Обычно все смотрят на коэффициент Gini (хорошая статья про него тут).

image

Результаты моделирования
def plot_score(clf, X_test, y_test, feat_to_show=30, is_normalize=False, cut_off=0.5):
    #cm = confusion_matrix(pd.Series(clf.predict_proba(X_test)[:,1]).apply(lambda x: 1 if x>cut_off else 0), y_test)
    print ('ROC_AUC:  ', round(roc_auc_score(y_test, clf.predict_proba(X_test)[:,1]), 3))
    print ('Gini:     ', round(2*roc_auc_score(y_test, clf.predict_proba(X_test)[:,1]) - 1, 3))
    print ('F1_score: ', round(f1_score(y_test, clf.predict(X_test)), 3))
    print ('Log_loss: ', round(log_loss(y_test, clf.predict(X_test)), 3))
    
    print ('\n')
    print ('Classification_report: \n', classification_report(pd.Series(clf.predict_proba(X_test)[:,1]).apply(lambda x: 1 if x>cut_off else 0), y_test))
    skplt.metrics.plot_confusion_matrix(y_test, pd.Series(clf.predict_proba(X_test)[:,1]).apply(lambda x: 1 if x>cut_off else 0), title="Confusion Matrix",
                    normalize=is_normalize,figsize=(8,8),text_fontsize='large')
    display(eli5.show_weights(clf, top=20, feature_names = list(X_test.columns)))

clf_lr = LogisticRegressionCV(random_state=1, cv=7)
clf_lr.fit(X_train, y_train)

plot_score(clf_lr, X_test, y_test, cut_off=0.5)


Когда мы удостоверились в том, что качество модели нас устраивает, необходимо записать все результаты (коэффициенты регрессии, группы бинов, графики стабильности Gini и переменных и т.д.) в excel. Для этого удобно использовать xlsxwriter, который может работать как с данными, так и с картинками.

Примеры листов экселя:

image

image

Запись в excel
 #WRITING
writer = pd.ExcelWriter('PDL_Score_20180815-3.xlsx', engine='xlsxwriter')

workbook  = writer.book
worksheet = workbook.add_worksheet('Sample information')
bold = workbook.add_format({'bold': True})
percent_fmt = workbook.add_format({'num_format': '0.00%'})

worksheet.set_column('A:A', 20)
worksheet.set_column('B:B', 15)
worksheet.set_column('C:C', 10)

# Sample
worksheet.write('A2', 'Sample conditions', bold)
worksheet.write('A3', 1)
worksheet.write('A4', 2)
worksheet.write('A5', 3)
worksheet.write('A6', 4)

# Model
worksheet.write('A8', 'Model development', bold)

worksheet.write('A9', 1)
#labels
worksheet.write('C8', 'Bads')
worksheet.write('D8', 'Goods')
worksheet.write('B9', 'Train')
worksheet.write('B10', 'Valid')
worksheet.write('B11', 'Total')

# goods and bads
worksheet.write('C9', y_train.value_counts()[1])
worksheet.write('C10', y_test.value_counts()[1])
worksheet.write('D9', y_train.value_counts()[0])
worksheet.write('D10', y_test.value_counts()[0])
worksheet.write('C11', y.value_counts()[1])
worksheet.write('D11', y.value_counts()[0])

# NPL
worksheet.write('A13', 2)
worksheet.write('B13', 'NPL')
worksheet.write('C13', (y.value_counts()[1]/(y.value_counts()[1]+y.value_counts()[0])), percent_fmt)

worksheet.write('A16', 3)
worksheet.write('C15', 'Gini')
worksheet.write('B16', 'Train')
worksheet.write('B17', 'Valid')
worksheet.write('B18', 'CV Scores')
worksheet.write('C18', str([round(sc, 2) for sc in scores]))

worksheet.write('C16', round(2*roc_auc_score(y_train, clf_lr.predict_proba(X_train)[:,1]) - 1, 3))
worksheet.write('C17', round(2*roc_auc_score(y_test, clf_lr.predict_proba(X_test)[:,1]) - 1, 3))

# Regreesion coefs
feat.to_excel(writer, sheet_name='Regression coefficients', index=False)
worksheet2 = writer.sheets['Regression coefficients']
worksheet2.set_column('A:A', 15)
worksheet2.set_column('B:B', 50)

#WOE

ivs[['VAR_NAME', 'Variable range', 'WOE', 'COUNT', 'WOE_group']].to_excel(writer, sheet_name='WOE', index=False)
worksheet3 = writer.sheets['WOE']
worksheet3.set_column('A:A', 50)
worksheet3.set_column('B:B', 60)
worksheet3.set_column('C:C', 30)
worksheet3.set_column('D:D', 20)
worksheet3.set_column('E:E', 12)
for num, i in enumerate([x.replace('WOE_','') for x in X_train.columns]):
        ev = iv_df[iv_df.VAR_NAME==i]
        ev.reset_index(inplace=True)
        worksheet3.insert_image('G{}'.format(num*34+1), '{}.png'.format(i))

df3.to_excel(writer, sheet_name='Data', index=False)

table.to_excel(writer, sheet_name='Scores by buckets', header = True, index = True)
worksheet4 = writer.sheets['Scores by buckets']
worksheet4.set_column('A:A', 20)
worksheet4.insert_image('J1', 'score_distribution.png')
Ginis.to_excel(writer, sheet_name='Gini distribution', header = True, index = True)
worksheet5 = writer.sheets['Gini distribution']
worksheet5.insert_image('E1', 'gini_stability.png')
writer.save()


Итоговый excel в конце еще раз смотрится менеджментом, после чего отдаётся в IT для встраивания модели в продакшен.

Итог


Как мы увидели, почти все этапы скоринга можно автоматизировать так, чтобы аналитикам не нужны были навыки программирования для построения моделей. В нашем случае, после создания данного фреймворка от аналитика требуется лишь собрать данные и указать несколько параметров (указать целевую переменную, какие колонки удалить, минимальное количество бинов, коэффициент отсечения для корреляции переменных и т.д), после чего можно запустить скрипт на python, который построит модель и выдаст excel с нужными результатами.
Конечно же, иногда приходится исправлять код под нужды конкретного проекта, и одной кнопкой запуска скрипта при моделировании не обойдешься, однако даже сейчас мы видим качество лучше, чем у применяемых на рынке data mining пакетов благодаря таким техникам как оптимальный и монотонный биннинг, проверка на корреляцию, RFE, регуляризированная версия регрессии и т.д.

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

Комментарии (19)


  1. lornemalvo
    24.08.2018 12:51
    +1

    Не пробовали использовать elastic net регуляризацию для выбора переменных? См. ссылки 1 и 2 ниже.

    Я использую не Python, а R, но вроде в scikit-learn должна быть встроена elastic net регуляризация для логистической регрессии.
    В любом случае, есть библиотека glmnet (см. ссылку 3), где реализована логистическая регрессия с кросс-валидацией и elastic net регуляризацией. По утверждению авторов, это еще и одна из самых быстрых реализаций (ссылка 4).

    Ссылки:
    1. en.wikipedia.org/wiki/Elastic_net_regularization
    2. web.stanford.edu/~hastie/Papers/B67.2%20(2005)%20301-320%20Zou%20&%20Hastie.pdf
    3. glmnet-python.readthedocs.io/en/latest/glmnet_vignette.html
    4. web.stanford.edu/~hastie/Papers/glmnet.pdf


    1. Distem Автор
      24.08.2018 12:54

      Привет! Elastic Net пробовали использовать, она не встроена в обычную регрессию, но доступна в sklearn.linear_model.ElasticNet. В последней версии мы прогоняем разные версии регрессии, и выбирается наилучшая из них. Но у версии в sklearn есть минус — она не выдает вероятности (нет метода predict_proba)


      1. Arseny_Info
        24.08.2018 17:34

        Неудивительно, что в sklearn.linear_model.ElasticNet нет метода
        predict_proba, ведь это регрессор, а не классификатор в отличие от стандартного логрега. Впрочем, написать ElasticNet с нуля совсем не сложно — нужно только добавить кусок к лоссу в обычной логистической регрессии.


        1. lornemalvo
          24.08.2018 19:16

          Написать elastic net или lasso (L1) с нуля сложнее, чем ridge (L2), потому что там есть часть с коэффициентами по модулю, который не дифференцируется в нуле. Да и незачем писать с нуля, если уже есть glmnet, в которой реализована логистическая и другие регрессии с elastic net, lasso и ridge регуляризацией и k-fold кросс-валидацией.


  1. lornemalvo
    24.08.2018 13:08

    Знакома проблема черного/белого ящика, когда нужно строить объяснимую для бизнеса модель, хоть и работаю с другой отраслью (ритейл).
    Для увеличения точности может помочь стакинг, когда результаты одного или нескольких «белых ящиков» подаются на вход другого «белого ящика». Если модель можно разложить на понятные компоненты. Например, в спросе на товары можно выделить сезонность, веса дней недель, эластичность по цене и т. д.


  1. Voila2000
    24.08.2018 13:30

    Вы пишите «а также улучшили качество всех моделей».
    А по какому критерию вы оцениваете качество скоринговых моделей?
    Такой подход работает с любыми типами заемщиков?


    1. Distem Автор
      24.08.2018 13:31

      Качество оцениваем с помощью коэффициента Gini на тестовой выборке. И как раз благодаря методикам, описанным в статье он возрастает.
      Второй вопрос не совсем понял.


      1. Kordamon
        24.08.2018 20:03

        А вы пробовали оценивать эффективность в рублях? Типа «Средний доход от хорошего клиента — Цена привлечения — Потери от плохого — Упущенная выгода»


  1. Andriljo
    24.08.2018 16:15

    Вы используете Базель в качестве методологии расчета скоринг моделей? Но вы так же писали об том, что порой ML эффективнее в плане работы с категориями, но не продемонстрировали это. Так же вы используете бининг и WoE для отбора, причем понимаю это и для категорий и для чисел. Почему б не посмотреть в сторону методов ML для кодирования категорий и отбора признаков?


    1. Distem Автор
      24.08.2018 18:53

      В этой статье мы рассказываем только про логистическую регрессию и все процессы классического кредитного скоринга — то есть без применения машинного обучения. WoE
      Биннинг не лучше кодирования для ML моделей, однако именно он даёт регрессии такую стабильность и интерпретируемость, за которые её любят в банках.


  1. Yo1
    24.08.2018 16:43

    модель выгружается в эксель !? или это просто репортик? какой инструмент в продукции запускает модель?


    1. Distem Автор
      24.08.2018 19:14

      Ответил чуть ниже


  1. Kordamon
    24.08.2018 18:35

    Спасибо, очень интересно!
    Скажите:
    1) Самый важный вопрос: насколько тако вариант регресии с самописным бинингом проигрывает ML моделям по Gini?
    2) Как вы эту модель деплоите потом? У вас там на Проде Питон стоит или надо весь набор препроцесинга и модель заново кодить в Java?
    3) Если для деплоя используется PMML, то как вы туда добавляете бины?


    1. Distem Автор
      24.08.2018 19:13

      1. В среднем ML модели выигрывают около 10-15 gini, однако они используют 100+ переменных вместо 10 в регрессии и чуть менее стабильны при изменении, например, потока клиентов.
      2. В excel выгружаются бины и коэффициенты регрессии. В нашем случае (это не всегда так и зависит от компании) дальше создается DSL-скрипт — при попадание клиента в такой-то бин ему присвается такое-то WOE. После чего скрипт используется в Java.


  1. Kordamon
    24.08.2018 19:56

    На вашем потоке 10п Gini это некислые деньги! И руководство даже не использует ML модели параллельно?

    А предобработка? Ну там, кросс-переменные, нормализация, вот эта логика для заполнения пропусков — как она попадает на Продакшн?

    upd: не туда ответил, это коммент к комменту habr.com/company/idfinance/blog/421091/#comment_19030713


    1. Distem Автор
      27.08.2018 10:27

      ML модели используются как раз в большинстве случаев. Для них используется Docker, в котором находится скрипт на питоне.
      А в случае описанном в статье нормализация и замена пропусков как раз решается WOE биннингом.


      1. Kordamon
        27.08.2018 12:28

        То есть у вас Продакшн скоринг в части ML работает на Питоне? Вот это и правда редкий случай, уважаю! :)
        И какой поток входящих запросов держит один инстанс Докера, если не секрет?


  1. Gewissta
    25.08.2018 16:09

    в общем, бегло пробежал, какие замечания
    Биннинг может помочь улучшить качество модели, потому что помогает лучше описать взаимосвязь между предиктором и зависимой переменной, но этого же можно добиться за счет преобразований (квадратный корень, кубический корень, свернутый корень, арксинусное простое или удвоенное, сплайны, ну или Бокс-Кокс для ленивых). Про недостатки биннинга хорошо написано у Харрелла biostat.mc.vanderbilt.edu/wiki/Main/CatContinuous Повысить стабильность позволяет не биннинг, а соблюдение трех предпосылок – нормальность (отсюда делаем преобразования, максимизирующие нормальность, хоть это и не так критично, как для линейной регрессии, но решение будет более стабильным), приведение переменных к единому масштабу (стандартизация) и регуляризация. Эластичная сеть есть в классе H2OGeneralizedLinearEstimator библиотеки h2o. Там логрег – частный случай GLM. Глянуть, как работает, можно здесь
    github.com/Gewissta/Learning_Pandas_russian_translation/blob/master/Notebooks/%D0%9F%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5%204_%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%80%20%D0%BF%D1%80%D0%B5%D0%B4%D0%B2%D0%B0%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9%20%D0%BF%D0%BE%D0%B4%D0%B3%D0%BE%D1%82%D0%BE%D0%B2%D0%BA%D0%B8%20%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85%20%D0%B2%20pandas.ipynb
    Дополнительное удобство – можно задавать парные взаимодействия.
    Выкидывать ручками коррелирующие переменные – тоже сомнительная затея, лучше сделать агрегаты по возможности или отдать это на откуп регуляризации, как это делает в H2O lambda_search — поиск оптимального значения штрафного коэффициента при переборе alpha, задающего соотношение l1 и l2-штрафов, ну и там гранулярность настраиваем с помощью nlambdas и lambda_min_ratio.
    RFE и BorutaPy действительно помогают, но тут надо помнить, что помимо логрега надо тюнить еще и лес, с помощью которого отбираем фичи. Общее правило – брать не сильно глубокие деревья или спарсивать признаки на самых верхних уровнях, поскольку наиболее важные у нас будут в наиболее верхних уровнях.
    По поводу машинного обучения. И опять-таки оно помогает нашей старушке логистической регрессии. Можно строить ансамбли CHAID и логистической регрессии. Можно построить лес, извлечь наиболее полезные правила (наиболее часто встречающиеся, дающие наименьшую ошибку) и подать на вход логрегу. Делается в пакетах R inTrees и RandomForestExplainer. Это называют моделью «мастер-ассистент», мастером выступает логистическая регрессия, ассистент, ищущий полезные фичи, — лес или бустинг. Разумеется, делаем это, когда основной feature engineering, обогащение внешними данными выполнены.


    1. Kordamon
      25.08.2018 19:29

      Биннинг может помочь улучшить качество модели, потому что помогает лучше описать взаимосвязь между предиктором и зависимой переменной, но этого же можно добиться за счет преобразований (квадратный корень, кубический корень, свернутый корень, арксинусное простое или удвоенное, сплайны, ну или Бокс-Кокс для ленивых).


      Самое интересное в бининге для таких задач, как банковский скоринг, это не стабильность (тут альтернатив и правда много), а то, что эксперт может добавить в модель свои знания. Возраст — классический случай. 99% что пик WOE по возрасту это косяк в данных или непредставительная выборка. Тогда аналитик накладывает ограничение неубывания WOE на бины — Gini падает на тренинговом множестве, но растет на тесте (а даже если и не растет на тесте, то стабильность модели все равно повышается).
      ИМХО другого такого способа прямо выразить знания эксперта в модель в DS просто нет.