понедельник, 16 декабря 2024 г.

Соревнование на Kaggle : регрессия с машинным обучением c курсом DS на Stepik Глеба Михайлова

Новое соревновании на Kaggle, на этот раз регрессия : Regression with an Insurance Dataset (Регрессия с набором страховых данных) . Цель этого набора данных — облегчить разработку и тестирование регрессионных моделей для прогнозирования страховых премий на основе различных характеристик клиентов и деталей полиса. Страховые компании часто полагаются на основанные на данных подходы для оценки премий, принимая во внимание такие факторы, как возраст, доход, состояние здоровья и история претензий. Этот синтетический набор данных имитирует реальные сценарии, чтобы помочь практикующим специалистам практиковать проектирование признаков, очистку данных и обучение моделей. Решение будем сопровождать материалами, взятыми из курса на платформе Stepik DS Глеба Михайлова.

Используемые библиотеки :

import pandas as pd
pd.DataFrame.iteritems = pd.DataFrame.items
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error
from sklearn.metrics import root_mean_squared_log_error
from sklearn.model_selection import KFold,cross_val_score, RepeatedStratifiedKFold,StratifiedKFold
from catboost import Pool
from catboost import cv
from catboost import CatBoostRegressor
import seaborn as sns

Как обычно, данные представляют собой два файла : train.csv и test.csv. Загружаем и смотрим на обучающие данные :

df_test = pd.read_csv('test.csv')
df = pd.read_csv('train.csv')
df.head().T

01234
id01234
Age19.039.023.021.021.0
GenderFemaleFemaleMaleMaleMale
Annual Income10049.031678.025602.0141855.039651.0
Marital StatusMarriedDivorcedDivorcedMarriedSingle
Number of Dependents1.03.03.02.01.0
Education LevelBachelor'sMaster'sHigh SchoolBachelor'sBachelor's
OccupationSelf-EmployedNaNSelf-EmployedNaNSelf-Employed
Health Score22.59876115.56973147.17754910.93814420.376094
LocationUrbanRuralSuburbanRuralRural
Policy TypePremiumComprehensivePremiumBasicPremium
Previous Claims2.01.01.01.00.0
Vehicle Age17.012.014.00.08.0
Credit Score372.0694.0NaN367.0598.0
Insurance Duration5.02.03.01.04.0
Policy Start Date2023-12-23 15:21:39.1349602023-06-12 15:21:39.1115512023-09-30 15:21:39.2213862024-06-12 15:21:39.2269542021-12-01 15:21:39.252145
Customer FeedbackPoorAverageGoodPoorPoor
Smoking StatusNoYesYesYesYes
Exercise FrequencyWeeklyMonthlyWeeklyDailyWeekly
Property TypeHouseHouseHouseApartmentHouse
Premium Amount2869.01483.0567.0765.02022.0


Набор данных содержит 20 признаков с комбинацией категориальных, числовых и текстовых данных. Он включает пропущенные значения, неверные типы данных и перекошенные распределения для имитации сложностей, с которыми сталкиваются реальные наборы данных.
Целевая переменная для прогнозирования — «Premium Amount».

Описание переменных :

Age (Возраст): Возраст застрахованного лица (числовой)
Gender (Пол) : Пол застрахованного лица (Categorical: Male, Female)(Категория: Мужской, Женский)
Annual Income (Годовой доход): Годовой доход застрахованного лица (числовой, асимметричный)
Marital Status (Семейное положение): Семейное положение застрахованного лица (Categorical: Single, Married, Divorced)
(категория: холост, женат, разведен)
Number of Dependents (Количество иждивенцев): Количество иждивенцев (числовое, с пропущенными значениями)
Education Level (Уровень образования): наивысший достигнутый уровень образования (Categorical: High School, Bachelor's, Master's, PhD) (категория: средняя школа, бакалавр, магистр, доктор философии)
Occupation (Род занятий): Род занятий застрахованного лица (Categorical: Employed, Self-Employed, Unemployed) (категория: работающий по найму, самозанятый, безработный)
Health Score (Оценка состояния здоровья): оценка, отражающая состояние здоровья (числовая, асимметричная)
Location (Местоположение): Тип местоположения (Categorical: Urban, Suburban, Rural) (Категория: Город, Пригород, Сельская местность)
Policy Type (Тип полиса): Тип страхового полиса (Categorical: Basic, Comprehensive, Premium) (категория: Базовый, Комплексный, Премиум)
Previous Claims (Предыдущие заявления): Количество предыдущих заявлений (числовое, с выбросами)
Vehicle Age (Возраст транспортного средства): Возраст застрахованного транспортного средства (числовой)
Credit Score (Кредитный рейтинг): кредитный рейтинг застрахованного лица (числовой, с отсутствующими значениями)
Insurance Duration (Срок действия страхового полиса): Срок действия страхового полиса (числовой, в годах)
Premium Amount (Сумма страховой премии): целевая переменная, представляющая сумму страховой премии (числовая, асимметричная)
Policy Start Date (Дата начала действия полиса): Дата начала действия страхового полиса (Текст, неправильно отформатирован)
Customer Feedback (Отзывы клиентов): краткие отзывы клиентов (текст)
Smoking Status (Статус курения): Статус курения застрахованного лица (Categorical: Yes, No) (Категория: Да, Нет)
Exercise Frequency (Частота упражнений) : Частота упражнений (Categorical: Daily, Weekly, Monthly, Rarely) (Категория: Ежедневно, Еженедельно, Ежемесячно, Редко)
Property Type (Тип недвижимости) : Тип собственности (Categorical: House, Apartment, Condo) (Категория: Дом, Квартира, Кондоминиум)

Характеристики данных

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

Для машинного обучения будем использовать библиотеку CatBoost. CatBoost  - 
это библиотека, разработанная в Яндексе и представленная в июле 2017 года.
Глеб Михайлов считает ее одной из лучших библиотек для машинного обучения и
в своем курсе предлагает своим слушателем не тратить время на другие библиотеки
и начать сразу с нее.

Для удобного ознакомления с данными определим несколько полезных функций (код функций взят из примеров кода этого соревнования на kaggle с небольшими доработками):

Функция выводит основную информацию по признакам

def unique_info(df):
    columns = []
    coltypes=[]
    unique_values = []
    unique_counts = []
    isnull_counts = []
    MAX_LIST_LENGTH = 8

    for col in df.columns:
        columns.append(col)
        coltypes.append(df[col].dtypes)
        unique_vals = df[col].unique()
        null_counts = df[col].isnull().sum()
        unique_values.append(list(unique_vals))  
        unique_counts.append(len(unique_vals))   
        isnull_counts.append(null_counts)   # 

    def truncate_list(lst, max_length=MAX_LIST_LENGTH):
        if len(lst) > max_length:
            return lst[:max_length] + ['...']  
        return lst
    
    unique_info_df = pd.DataFrame({
        'Column': columns,
        'Type': coltypes,
        'Unique Values': unique_values,
        'Total Unique Values': unique_counts,
        'Total Null Values': isnull_counts
    })

    unique_info_df['Unique Values'] = unique_info_df['Unique Values'].apply(lambda x: truncate_list(x))    
    return unique_info_df.sort_values(['Type','Column'], ascending=[False, True])


Графическое представление распределения пропущенных значений по признакам

def plotMissingValues(df, title, color):
    missingRatio = df.isnull().sum() / len(df) * 100
    missingDf = pd.DataFrame({'column': missingRatio.index, 'missingRatio': missingRatio.values})
    
    plt.figure(figsize=(15, 6))
    plt.grid(True)
    ax = sns.barplot(x='column', y='missingRatio', data=missingDf, color=color)
    
    plt.xticks(rotation=45, ha='right', fontsize = 14)
    plt.xlabel('')
    
    plt.title(title, fontsize = 20)
    
    plt.yticks(range(0, 41, 10), fontsize = 14)
    plt.ylabel('Missing Values ratio(%)', fontsize = 12)
    
    for p in ax.patches:
        height = p.get_height()
        ax.text(p.get_x() + p.get_width() / 2.,
                height + 0.8,
                '{:.1f}%'.format(height),
                ha="center")
    
    plt.tight_layout()
    plt.show()

Переведем 'Policy Start Date' в более привычный формат, у меня получилась несколько сложно, но главное - результат

df['Policy Start Date'] = df['Policy Start Date'].astype('datetime64[ns]')
df['Policy Start Date'] = df['Policy Start Date'].dt.strftime('%d-%m-%Y')
df['Policy Start Date'] = df['Policy Start Date'].astype('datetime64[ns]')
df_test['Policy Start Date'] = df_test['Policy Start Date'].astype('datetime64[ns]')
df_test['Policy Start Date'] = df_test['Policy Start Date'].dt.strftime('%d-%m-%Y')
df_test['Policy Start Date'] = df_test['Policy Start Date'].astype('datetime64[ns]')


Определим рабочие списки признаков

features_drop = ["id"]  # этот признак будет удален

Отбираем итоговый набор признаков для использования моделью

X_col = [i for i in df.columns if (i not in y_col and i not in features_drop)]
cat_col = [c for c in df[X_col].columns if df[c].dtypes=='O']
num_col = [c for c in df[X_col].columns if df[c].dtypes!='O']
y_col = ['Premium Amount']

print(f"features :\n {X_col}")
print(f"cat_features :\n { cat_col}")
print(f"num_features :\n {num_col}")
print(f"targets :\n {y_col}")

features :
 ['Age', 'Gender', 'Annual Income', 'Marital Status', 'Number of Dependents', 'Education Level', 'Occupation', 'Health Score', 'Location', 'Policy Type', 'Previous Claims', 'Vehicle Age', 'Credit Score', 'Insurance Duration', 'Policy Start Date', 'Customer Feedback', 'Smoking Status', 'Exercise Frequency', 'Property Type']
cat_features :
 ['Gender', 'Marital Status', 'Education Level', 'Occupation', 'Location', 'Policy Type', 'Customer Feedback', 'Smoking Status', 'Exercise Frequency', 'Property Type']
num_features :
 ['Age', 'Annual Income', 'Number of Dependents', 'Health Score', 'Previous Claims', 'Vehicle Age', 'Credit Score', 'Insurance Duration', 'Policy Start Date']
targets :
 ['Premium Amount']

Анализ начнем с вывода основных показателей по признакам для обучающих
данных

unique_info(df[X_col])

ColumnTypeUnique ValuesTotal Unique ValuesTotal Null Values
15Customer Feedbackobject[Poor, Average, Good, nan]477824
5Education Levelobject[Bachelor's, Master's, High School, PhD]40
17Exercise Frequencyobject[Weekly, Monthly, Daily, Rarely]40
1Genderobject[Female, Male]20
8Locationobject[Urban, Rural, Suburban]30
3Marital Statusobject[Married, Divorced, Single, nan]418529
6Occupationobject[Self-Employed, nan, Employed, Unemployed]4358075
9Policy Typeobject[Premium, Comprehensive, Basic]30
18Property Typeobject[House, Apartment, Condo]30
16Smoking Statusobject[No, Yes]20
14Policy Start Datedatetime64[ns][2023-12-23 00:00:00, 2023-12-06 00:00:00, 202...18260
0Agefloat64[19.0, 39.0, 23.0, 21.0, 29.0, 41.0, 48.0, 44....4818705
2Annual Incomefloat64[10049.0, 31678.0, 25602.0, 141855.0, 39651.0,...8859444949
12Credit Scorefloat64[372.0, 694.0, nan, 367.0, 598.0, 614.0, 807.0...551137882
7Health Scorefloat64[22.59876067181393, 15.569730989408043, 47.177...53265874076
13Insurance Durationfloat64[5.0, 2.0, 3.0, 1.0, 4.0, 6.0, 8.0, 9.0, ...]101
4Number of Dependentsfloat64[1.0, 3.0, 2.0, 0.0, 4.0, nan]6109672
10Previous Claimsfloat64[2.0, 1.0, 0.0, nan, 3.0, 4.0, 5.0, 6.0, ...]11364029
11Vehicle Agefloat64[17.0, 12.0, 14.0, 0.0, 8.0, 4.0, 11.0, 10.0, ...216

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

plotMissingValues(df[X_col], 'Missing Values Ratio of Train Set', 'cornflowerblue')

Тестовые данные

plotMissingValues(df_test[X_col], 'Missing Values Ratio of Test Set', 'cornflowerblue')




Распределение пропущенных значений в train и test практически одинаково

На этом знакомство с данными закончим и переходим непосредственно к машинному обучению по схеме Глеба Борисова.
Для обучения catboost помимо обучающей и тестовой выборки нужна валидационная выборка. Catboost обучается итерациями, то есть в прjцессе обучения он смотрит на данные много раз. С каждjй итерацией catboost все больше и больше узнает о загружаемых в него данных. Он извлекает все больше и больше зависимостей между признаками и целевой функцией. И проблема в том, что если не остановить этот процесс, то catboost просто вызубрит обучающую выборку. Он запомнит ее наизусть,
он просто загрузит ее в себя. И когда мы захотим сделать предсказание на новых данных , он не сможет этого сделать, так как он не нашел зависимостей, не нашел правила, которые применимы к признакам и цели, он просто переобучился запомнив конкретную выборку. Чтобы этого не случилось используется валидационная выборка. В начале мы разбиваем обучающие данные на обучающую и тестовую части, а потом из обучающей части выделяем кусок на валидациооную выборку.  Мы будем обучать catboost на обучающей части и на каждой итерации делать предсказание на валидационной выборке и смотреть, какая при этом у нас получается ошибка. С каждой итерацией до определенного предела эта ошибка будет уменьшаться, но 
с какого-то шага она начнет расти. Это будет обозначать, что catboost начала переобучаться. Таким образом нам надо вовремя остановить этот процесс. Поэтому валидационная выборка нам нужна обязательно и мы ее сейчас сделаем, но сначала
чтобы CatBoost работал с пропусками в категориальных показателях переведем их в строковый формат.

for feature in cat_col:
    df[feature] = df[feature].astype('str')
for feature in cat_col:
    df_test[feature] = df_test[feature].astype('str')

А теперь переходим непосредственно к разбиению данных :

train, test = train_test_split(df,train_size=0.6,random_state=42)
val, test = train_test_split(test,train_size=0.5,random_state=42)

Как видим, мы задали соотношение по обучающей, тестовой и валидационной выборки как 60% : 20% : 20%. Необходимо отметить, что валидационная выборка все жн не решает проблему новых данных, по факту мы обучаемся и оцениваем ошибку
на одних и тех же данных. Поэтому тестовая часть по-прежнему остается необходимой, так как оценка качества на ней дает нам прогноз на то, как будет вести наша модель на настоящих данных.
У catboost есть внутренняя структура данных Pool,которая содержит наши данные и целевые значения и ее использование ускоряет обучение
Важные параметры конструктора Pool() :
    data - массив numpy, фрейм данных pandas или список показателей
    label -  массив numpy, фрейм данных pandas или список с целевыми метками
    cat_features - список целых чисел, определяющих индексы данных, имеющих категориальные характеристики
    text_features -Он принимает список целых чисел, определяющих индексы данных, имеющих текстовые функции

Преобразуем наши данные в "пулы"

train_data = Pool(data=train[X_col],
                  label=train[y_col],
                  cat_features=cat_col
                 )

valid_data = Pool(data=val[X_col],
                  label=val[y_col],
                  cat_features=cat_col
                 )

test_data = Pool(data=test[X_col],
                  label=test[y_col],
                  cat_features=cat_col
                 )

В качестве метрики качества в соревновании используется RMSLE (Root Mean Squared Log Error) - среднеквадратичная логарифмическая ошибка. 
Среднеквадратическая логарифмическая ошибка (RMSLE) используется для оценки точности прогнозных моделей, особенно когда важно учитывать относительные ошибки, а не абсолютные значения ошибок. Известно, что логарифмирование приводит к сжатию исходного диапазона изменения значений переменной. Поэтому применение  целесообразно, если предсказанное и фактическое значения выходной переменной различаются на порядок и больше.
Это особенно актуально в задачах с сильно различающимися масштабами целевой переменной, где ошибки в прогнозах больших значений не должны доминировать над ошибками меньших значений.
Функция RMSLE - не является встроенной в библиотеку CatBoost. Выходом из данной ситуации служат кастомные метрики, которые пользователь сам может создать для дальнейшего использования при обучении и валидации моделей. Я нашел готовую функцию в сети по адресу https://habr.com/ru/sandbox/163469/

import math
class RMSLE(object):
    def calc_ders_range(self, approxes, targets, weights):
        assert len(approxes) == len(targets)
        if weights is not None:
            assert len(weights) == len(approxes)

        result = []
        for index in range(len(targets)):
            val = max(approxes[index], 0)
            der1 = math.log1p(targets[index]) - math.log1p(max(0, approxes[index]))
            der2 = -1 / (max(0, approxes[index]) + 1)

            if weights is not None:
                der1 *= weights[index]
                der2 *= weights[index]

            result.append((der1, der2))
        return result
class RMSLE_val(object):
    def get_final_error(self, error, weight):
        return np.sqrt(error / (weight + 1e-38))

    def is_max_optimal(self):
        return False

    def evaluate(self, approxes, target, weight):
        assert len(approxes) == 1
        assert len(target) == len(approxes[0])

        approx = approxes[0]

        error_sum = 0.0
        weight_sum = 0.0

        for i in range(len(approx)):
            w = 1.0 if weight is None else weight[i]
            weight_sum += w
            error_sum += w * ((math.log1p(max(0, approx[i])) - math.log1p(max(0, target[i])))**2)

        return error_sum, weight_sum

Для объявления модели CatBoostRegressor необходимо вызвать ее конструктор и задать, если есть желание, параметры настройки
модели. Глеб Борисов утверждает, что работа с параметрами по умолчанию в большинстве задач дает хороший результат. 
Приведем основные параметры модели CatBoost :
    loss_function : функция потерь, которую надо минимизировать при обучении
    eval_metric - валидационная метрика, используемая для обнаружения переобучения
    и ранней остановки, метрики для этих параметров могут быть разные
    early_stopping_rounds - определяет число итераций до остановки, если на их
    протяжении метрика качества не улучшалась по сравнению с оптимальной
    learning_rate – скорость обучения, которая определяет насколько быстро или
    медленно модель будет учиться. Значение по умолчанию обычно равно 0.03
    depth - глубина дерева (по умолчанию 6, максимальное значение - 16); 
l2_leaf_reg – коэффициент при члене регуляризации L2 функции потерь. Значение     по умолчанию – 3.0.
    cat_features - список наименований категориальных признаков
    iterations : количество итераций, по умолчанию 1000 
    verbose : количество итераций, после которых будет выводится информация
    об обучении
Для первого пуска зададим по минимуму, только самое необходимое

model = CatBoostRegressor(early_stopping_rounds=100,
                          loss_function=RMSLE(),
                          cat_features= cat_col,
                          verbose=100,
                          random_seed=42,
                          eval_metric=RMSLE_val())
model.fit(train_data,eval_set=valid_data)

0:	learn: 6.5065386	test: 6.5045289	best: 6.5045289 (0)	total: 3.3s	remaining: 54m 55s
100:	learn: 1.1314390	test: 1.1320231	best: 1.1320231 (100)	total: 2m 52s	remaining: 25m 36s
200:	learn: 1.0561386	test: 1.0575642	best: 1.0575642 (200)	total: 5m 47s	remaining: 23m
300:	learn: 1.0511825	test: 1.0527305	best: 1.0527305 (300)	total: 9m 8s	remaining: 21m 13s
400:	learn: 1.0498835	test: 1.0514877	best: 1.0514877 (400)	total: 12m 23s	remaining: 18m 30s
500:	learn: 1.0489920	test: 1.0506551	best: 1.0506551 (500)	total: 15m 40s	remaining: 15m 36s
600:	learn: 1.0483485	test: 1.0500931	best: 1.0500931 (600)	total: 18m 58s	remaining: 12m 35s
700:	learn: 1.0480250	test: 1.0498568	best: 1.0498568 (700)	total: 22m 3s	remaining: 9m 24s
800:	learn: 1.0478400	test: 1.0497555	best: 1.0497554 (799)	total: 25m 14s	remaining: 6m 16s
900:	learn: 1.0477276	test: 1.0497101	best: 1.0497101 (900)	total: 28m 27s	remaining: 3m 7s
999:	learn: 1.0476207	test: 1.0496546	best: 1.0496545 (997)	total: 31m 36s	remaining: 0us

bestTest = 1.049654503
bestIteration = 997

Shrink model to first 998 iterations

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

Определяем функцию для расчета ошибок MAE,MAPE и RMSLE

def error(y_true,y_pred):
    print(f'MAE : {mean_absolute_error(y_true,y_pred):.6}')
    print(f'MAPE : {mean_absolute_percentage_error(y_true,y_pred):.6}')  
    print(f'RMSLE : {root_mean_squared_log_error(y_true,y_pred):.6}')  


Смотрим ошибку на тесте

error(test['Premium Amount'],model.predict(test[X_col]))

MAE : 622.321
MAPE : 1.84804
RMSLE : 1.04768

Делаем предсказание на тестовом файле соревнования и отправляем решение

df_test['Premium Amount'] = model.predict(df_test[X_col])
df_subm=df_test[['id','Premium Amount']]


В результате получаем оценку 1.04734 и 225 место из 917
Объединяем обучающую и валидационную части и обучаем модель на этих данных

train_full = pd.concat([train,val])
train_full_data = Pool(train_full[X_col],
                       label=train_full[y_col],
                       cat_features=cat_col)

Параметры модели удобнее задавать через словарь

params = {'early_stopping_rounds':100,
          'loss_function': RMSLE,
          'cat_features': cat_col,
          'verbose':100,
          'random_seed':42,
          'eval_metric': RMSLE_val}


model = CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
model.fit(train_full_data)

0:	learn: 6.5060477	total: 3.73s	remaining: 1h 2m 4s
50:	learn: 1.8686363	total: 1m 46s	remaining: 32m 58s
100:	learn: 1.1306477	total: 3m 33s	remaining: 31m 42s
150:	learn: 1.0645019	total: 5m 13s	remaining: 29m 23s
200:	learn: 1.0554422	total: 6m 51s	remaining: 27m 15s
250:	learn: 1.0523733	total: 8m 31s	remaining: 25m 26s
300:	learn: 1.0509933	total: 10m 22s	remaining: 24m 5s
350:	learn: 1.0503251	total: 12m 17s	remaining: 22m 42s
400:	learn: 1.0497968	total: 14m 9s	remaining: 21m 9s
450:	learn: 1.0494200	total: 16m 8s	remaining: 19m 39s
500:	learn: 1.0490556	total: 17m 57s	remaining: 17m 53s
550:	learn: 1.0487775	total: 19m 57s	remaining: 16m 16s
600:	learn: 1.0485508	total: 22m 4s	remaining: 14m 39s
650:	learn: 1.0484500	total: 23m 46s	remaining: 12m 44s
700:	learn: 1.0483725	total: 25m 18s	remaining: 10m 47s
750:	learn: 1.0482868	total: 26m 50s	remaining: 8m 53s
800:	learn: 1.0482302	total: 28m 24s	remaining: 7m 3s
850:	learn: 1.0481883	total: 29m 51s	remaining: 5m 13s
900:	learn: 1.0481479	total: 31m 19s	remaining: 3m 26s
950:	learn: 1.0481167	total: 32m 46s	remaining: 1m 41s
999:	learn: 1.0480925	total: 34m 10s	remaining: 0us


Смотри ошибку на тестовой части, результат немного лучше

error(test['Premium Amount'],model.predict(test[X_col]))

MAE : 622.336
MAPE : 1.84817
RMSLE : 1.04761

Посмотрим на три важных параметра полученной модели :

keys = ['learning_rate', 'l2_leaf_reg', 'depth']
{key:value for key, value in model.get_all_params().items() if key in keys}

{'l2_leaf_reg': 3, 'depth': 6, 'learning_rate': 0.029999999329447743}

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

Параметр l2_leaf_reg  в CatBoost отвечает за управление регуляризацией L2,
применяемой специально к значениям листьев деревьев решений в ансамбле.
Регуляризация — это важнейший метод, используемый в машинном обучении для
предотвращения переобучения, которое происходит, когда модель слишком точно
соответствует обучающим данным и улавливает шум, а не общие закономерности.
В контексте CatBoost регуляризация L2 для значений листьев добавляет штрафной
член к функции потерь во время обучения. Этот штрафной член пропорционален
сложности отдельных деревьев. Увеличивая значение l2_leaf_reg, вы применяете 
более сильную регуляризацию к значениям листьев, эффективно препятствуя тому,
чтобы деревья становились слишком сложными. При установке более высокого
значения l2_leaf_reg вы вводите более сильный эффект регуляризации, который может помочь предотвратить слишком точную подгонку модели к обучающим данным. Это
может быть особенно полезно при работе с шумными или небольшими наборами
данных, поскольку снижает риск того, что модель запомнит шум и выдаст плохое
обобщение для новых, невиданных данных. Однако при настройке этого параметра
важно соблюдать баланс. Хотя более сильная регуляризация может предотвратить
переобучение, слишком высокая настройка может привести к недообучению, когда
модель становится слишком простой для захвата важных закономерностей в данных.
Поэтому рекомендуется экспериментировать с различными значениями l2_leaf_reg и
использовать такие методы, как перекрестная проверка, чтобы найти оптимальную
силу регуляризации для вашего конкретного набора данных и проблемы.

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

Для подбора наилучших параметров у catboost есть метод grid_search(), который 
проводит подбор параметров по сетке. На вход он принимает  словарь имен параметров и список значений, которые можно попробовать для этих параметров. Зададим сетку на 
рассмотренные выше три параметра :

grid = {'learning_rate': [0.02,0.06,0.1,0.15,0.2],
        'l2_leaf_reg' :  [0.5,0.7,1.0,2.0,3.0],
        'depth': [4,6,8]}

Словарь для остальных параметров, которые мы хотим определить :

params = {'cat_features': cat_col,
          'verbose':10,
          'iterations':100,
          'random_seed':42,
          }


Создаем модель и запускаем поиск параметров по нашей сетке :

model = CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
result = model.grid_search(grid, train_full_data)

Через довольно продолжительное время получаем результат 

result['params']

{'depth': 8, 'learning_rate': 0.15, 'l2_leaf_reg': 0.7}

Посмотрим, что получится с этими параметрами 

params = {'early_stopping_rounds':100,
          'cat_features': cat_col,
          'depth':8,
          'learning_rate':0.15,
          'l2_leaf_reg' :  0.7,
          'verbose':50,
          'random_seed':42,
          }

model = 
CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
model.fit(train_full_data)

0:	learn: 6.0067798	total: 3.53s	remaining: 58m 49s
50:	learn: 1.0502394	total: 2m 7s	remaining: 39m 24s
100:	learn: 1.0479063	total: 4m 6s	remaining: 36m 29s
150:	learn: 1.0471068	total: 6m 26s	remaining: 36m 14s
200:	learn: 1.0467471	total: 8m 27s	remaining: 33m 37s
250:	learn: 1.0464866	total: 10m 48s	remaining: 32m 15s
300:	learn: 1.0462733	total: 12m 56s	remaining: 30m 2s
350:	learn: 1.0460835	total: 15m 5s	remaining: 27m 53s
400:	learn: 1.0459466	total: 17m 17s	remaining: 25m 49s
450:	learn: 1.0457939	total: 19m 22s	remaining: 23m 35s
500:	learn: 1.0456488	total: 21m 39s	remaining: 21m 34s
550:	learn: 1.0455704	total: 23m 39s	remaining: 19m 16s
600:	learn: 1.0454906	total: 25m 41s	remaining: 17m 3s
650:	learn: 1.0454199	total: 27m 38s	remaining: 14m 49s
700:	learn: 1.0453671	total: 29m 29s	remaining: 12m 34s
750:	learn: 1.0453110	total: 31m 22s	remaining: 10m 24s
800:	learn: 1.0452567	total: 33m 16s	remaining: 8m 15s
850:	learn: 1.0451560	total: 35m 11s	remaining: 6m 9s
900:	learn: 1.0450988	total: 37m 6s	remaining: 4m 4s
950:	learn: 1.0450609	total: 38m 55s	remaining: 2m
999:	learn: 1.0450187	total: 40m 42s	remaining: 0us

error(test['Premium Amount'],model.predict(test[X_col]))

MAE : 621.405
MAPE : 1.84741
RMSLE : 1.04633

Делаем предсказание на тестовом файле соревнования и отправляем решение

df_test['Premium Amount'] = model.predict(df_test[X_col])
df_subm=df_test[['id','Premium Amount']]


В результате получаем оценку 1.04621 и 249 место из 1113 или
78-й процентиль.

CatBoost также позволяет нам выполнять рандомизированный поиск, который 
выполняется быстрее по сравнению с поиском по сетке, при котором проверяется
только несколько настроек параметров, а не все возможные комбинации. Мы можем
выполнить рандомизированный поиск, используя randomized_search() оценщика
CatBoost. Метод randomized_search() имеет тот же API, что и методgrid_search(), с одним дополнительным параметром с именем n_iter, который принимает целочисленные
значения, указывающие, сколько случайных комбинаций параметров нужно
попробовать. Значение этого параметра по умолчанию — 10.

Зададим параметры для поиска и запустим его

grid = {'learning_rate': [0.2,0.25,0.3],
        'l2_leaf_reg' :  [0.3,0.7,1.0],
        'depth': [6,10,12]}
params = {'cat_features': cat_col,
          'verbose':10,
          'iterations':100,
          'random_seed':42,
          }
model = CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
result = model.randomized_search(grid, train_full_data)

Смотрим на результат

print("\nBest Params : ", result['params'])

Best Params :  {'depth': 10, 'learning_rate': 0.25, 'l2_leaf_reg': 0.3}

Запускаем модель с этими параметрами в надежде на улучшения качества решения и
рейтинга в соревновании

params = {'early_stopping_rounds':50,
          'cat_features': cat_col,
          'depth':10,
          'iterations':500,
          'learning_rate':0.25,
          'l2_leaf_reg' :  0.3,
          'verbose':10,
          'random_seed':42,
          }
model = CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
model.fit(train_data,eval_set=valid_data)

0:	learn: 5.7259544	test: 5.7240037	best: 5.7240037 (0)	total: 3.47s	remaining: 28m 50s
10:	learn: 1.2628374	test: 1.2630776	best: 1.2630776 (10)	total: 33s	remaining: 24m 24s
20:	learn: 1.0532680	test: 1.0554178	best: 1.0554178 (20)	total: 1m	remaining: 22m 52s
30:	learn: 1.0485469	test: 1.0510100	best: 1.0510100 (30)	total: 1m 27s	remaining: 22m 9s
40:	learn: 1.0474204	test: 1.0503480	best: 1.0503433 (39)	total: 1m 58s	remaining: 22m 1s
50:	learn: 1.0468751	test: 1.0500678	best: 1.0500678 (50)	total: 2m 27s	remaining: 21m 37s
60:	learn: 1.0460021	test: 1.0496293	best: 1.0496293 (60)	total: 2m 55s	remaining: 21m 4s
70:	learn: 1.0453013	test: 1.0493935	best: 1.0493935 (70)	total: 3m 23s	remaining: 20m 32s
80:	learn: 1.0447310	test: 1.0492524	best: 1.0492494 (79)	total: 3m 52s	remaining: 20m 2s
90:	learn: 1.0443149	test: 1.0492188	best: 1.0492188 (90)	total: 4m 23s	remaining: 19m 43s
100:	learn: 1.0439379	test: 1.0491947	best: 1.0491947 (100)	total: 4m 51s	remaining: 19m 13s
110:	learn: 1.0435994	test: 1.0491889	best: 1.0491769 (105)	total: 5m 25s	remaining: 19m
120:	learn: 1.0434328	test: 1.0491914	best: 1.0491769 (105)	total: 5m 57s	remaining: 18m 39s
130:	learn: 1.0432775	test: 1.0491671	best: 1.0491539 (125)	total: 6m 30s	remaining: 18m 18s
140:	learn: 1.0430363	test: 1.0491940	best: 1.0491539 (125)	total: 7m 4s	remaining: 18m
150:	learn: 1.0428301	test: 1.0491976	best: 1.0491539 (125)	total: 7m 39s	remaining: 17m 41s
160:	learn: 1.0426873	test: 1.0492232	best: 1.0491539 (125)	total: 8m 13s	remaining: 17m 18s
170:	learn: 1.0425263	test: 1.0492235	best: 1.0491539 (125)	total: 8m 46s	remaining: 16m 52s
180:	learn: 1.0422626	test: 1.0491364	best: 1.0491355 (179)	total: 9m 20s	remaining: 16m 28s
190:	learn: 1.0421010	test: 1.0491389	best: 1.0491355 (179)	total: 9m 55s	remaining: 16m 3s
200:	learn: 1.0419586	test: 1.0491536	best: 1.0491332 (194)	total: 10m 33s	remaining: 15m 41s
210:	learn: 1.0417650	test: 1.0491869	best: 1.0491332 (194)	total: 11m 7s	remaining: 15m 14s
220:	learn: 1.0415442	test: 1.0491838	best: 1.0491332 (194)	total: 11m 42s	remaining: 14m 47s
230:	learn: 1.0414296	test: 1.0491898	best: 1.0491332 (194)	total: 12m 13s	remaining: 14m 14s
240:	learn: 1.0412738	test: 1.0492099	best: 1.0491332 (194)	total: 12m 46s	remaining: 13m 44s
Stopped by overfitting detector  (50 iterations wait)

bestTest = 1.049133227
bestIteration = 194

Shrink model to first 195 iterations.

Смотрим на ошибку

error(test['Premium Amount'],model.predict(test[X_col]))

MAE : 621.644
MAPE : 1.85157
RMSLE : 1.04696

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

Параметры

params = {'early_stopping_rounds':100,
          'cat_features': cat_col,
          'depth':8,
          'loss_function':RMSLE(),
          'eval_metric':RMSLE_val(),
          'learning_rate':0.15,
          'l2_leaf_reg' :  0.7,
          'verbose':50,
          'random_seed':42,
          }

Кросс-валидация от catboost

cv_data = cv(
    params = params,
    pool = train_full_data,
    fold_count=5,
    shuffle=True,
    partition_random_seed=0,
    stratified=False,
    verbose=False
)

Training on fold [0/5]

bestTest = 1.046859711
bestIteration = 455

Training on fold [1/5]

bestTest = 1.044641777
bestIteration = 626

Training on fold [2/5]

bestTest = 1.050165155
bestIteration = 844

Training on fold [3/5]

bestTest = 1.047578411
bestIteration = 579

Training on fold [4/5]

bestTest = 1.047993662
bestIteration = 995

Будем обучать пять моделей, потому что в настройках выбрали пять фолдов. Качество
будет хранится в cv_data, это датафрейм, в котором хранится информация по каждой
итерации и рассчитываются средние метрики на трейне и тесте со стандартным
отклонением. И мы можем найти итерацию на которой получилось максимальное
качество.

cv_data

iterationstest-RMSLE_val-meantest-RMSLE_val-stdtrain-RMSLE_val-meantrain-RMSLE_val-std
006.0066360.0022916.0066350.000512
115.3842940.0022975.3842820.000459
224.8154810.0022944.8154720.000406
334.2986300.0022804.2986240.000352
443.8319320.0022523.8319250.000303
..................
9959951.0474590.0019911.0446650.000760
9969961.0474590.0019911.0446650.000760
9979971.0474590.0019911.0446650.000760
9989981.0474590.0019911.0446640.000760
9999991.0474590.0019911.0446640.000760

1000 rows × 5 columns


n_iters = 
cv_data[cv_data['test-RMSLE_val-mean'] == 
cv_data['test-RMSLE_val-mean'].min()]['iterations'].values[0]
n_iters

995

Теперь запустим нашу модель задав найденное количество итераций

params = {'iterations':n_iters,
          'cat_features': cat_col,
          'depth':8,
          'learning_rate':0.15,
          'l2_leaf_reg' :  0.7,
          'verbose':50,
          'random_seed':42,
          }

model 
= CatBoostRegressor(**params,loss_function=RMSLE(),eval_metric=RMSLE_val())
model.fit(train_full_data)

0:	learn: 6.0067798	total: 3.67s	remaining: 1h 52s
50:	learn: 1.0502394	total: 2m 25s	remaining: 45m 2s
100:	learn: 1.0479063	total: 4m 53s	remaining: 43m 21s
150:	learn: 1.0471068	total: 7m 13s	remaining: 40m 22s
200:	learn: 1.0467471	total: 9m 28s	remaining: 37m 24s
250:	learn: 1.0464866	total: 11m 43s	remaining: 34m 45s
300:	learn: 1.0462733	total: 14m 3s	remaining: 32m 24s
350:	learn: 1.0460835	total: 16m 8s	remaining: 29m 37s
400:	learn: 1.0459466	total: 18m 16s	remaining: 27m 3s
450:	learn: 1.0457939	total: 20m 30s	remaining: 24m 44s
500:	learn: 1.0456488	total: 22m 37s	remaining: 22m 18s
550:	learn: 1.0455704	total: 24m 33s	remaining: 19m 47s
600:	learn: 1.0454906	total: 26m 29s	remaining: 17m 22s
650:	learn: 1.0454199	total: 28m 30s	remaining: 15m 3s
700:	learn: 1.0453671	total: 30m 27s	remaining: 12m 46s
750:	learn: 1.0453110	total: 32m 20s	remaining: 10m 30s
800:	learn: 1.0452567	total: 34m 12s	remaining: 8m 17s
850:	learn: 1.0451560	total: 36m 6s	remaining: 6m 6s
900:	learn: 1.0450988	total: 37m 57s	remaining: 3m 57s
950:	learn: 1.0450609	total: 39m 43s	remaining: 1m 50s
994:	learn: 1.0450269	total: 41m 16s	remaining: 0us

error(test['Premium Amount'],model.predict(test[X_col]))

MAE : 621.407
MAPE : 1.84738
RMSLE : 1.04633

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








 













Комментариев нет:

Отправить комментарий