Занятие 6

Лабораторная 6. Градиентный бустинг.

In [ ]:
# используемые библиотеки
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split

import optuna
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine

import numpy as np
import pandas as pd
from catboost import CatBoostClassifier, Pool
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

import numpy as np
import pandas as pd
from catboost import CatBoostRegressor, Pool
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

1. Гиперпараметры

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

Кросс-валидация (cross-validation) — это метод оценки производительности модели машинного обучения, который позволяет избежать переобучения и получить более надежную оценку её качества. Вместо разделения данных на одну обучающую и одну тестовую выборки, как в случае с обычной проверкой на тестовых данных, кросс-валидация использует несколько различных разбиений данных для обучения и тестирования.

K-кратная кросс-валидация (K-fold cross-validation):

  1. Данные делятся на K равных частей (фолдов).
  2. Модель обучается K раз: каждый раз одна из частей используется как тестовая выборка, а остальные K-1 части — как обучающая.
  3. Результаты усредняются для получения окончательной оценки.

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

In [1]:
# Генерируем синтетические данные
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)

# Создаем модель логистической регрессии
model = LogisticRegression()

# Инициализируем K-кратную кросс-валидацию с 5 фолдами
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Выполняем кросс-валидацию
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')

# Выводим результаты
print("Оценки точности на каждом фолде:", scores)
print("Средняя точность:", scores.mean())
Оценки точности на каждом фолде: [0.855 0.855 0.875 0.865 0.865]
Средняя точность: 0.8630000000000001

Плюсы:

  1. Меньший риск переобучения : Поскольку модель тестируется на нескольких подмножествах данных, вероятность переобучения снижается.
  2. Более эффективное использование данных : Все данные используются как для обучения, так и для тестирования.
  3. Устойчивость к случайным разбиениям : Усреднение результатов по нескольким фолдам делает оценку более стабильной.

Минусы:

  1. Высокая вычислительная сложность : Требуется обучить модель несколько раз.
  2. Зависимость от размера фолдов : Если фолды слишком маленькие, оценка может быть неточной.

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

In [3]:
# Загружаем данные
data = load_wine()
X, y = data.data, data.target

# Разделяем данные на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Определяем модель
rf = RandomForestClassifier(random_state=42)

# Задаем сетку гиперпараметров для перебора
param_grid = {
    'n_estimators': [50, 100, 200],  # Количество деревьев
    'max_depth': [None, 10, 20, 30],  # Максимальная глубина дерева
    'min_samples_split': [2, 5, 10],  # Минимальное количество образцов для разделения узла
    'min_samples_leaf': [1, 2, 4]     # Минимальное количество образцов в листе
}

# Инициализируем GridSearchCV с использованием кросс-валидации
grid_search = GridSearchCV(estimator=rf, param_grid=param_grid, cv=5, scoring='accuracy', n_jobs=-1)

# Обучаем модель на обучающей выборке
grid_search.fit(X_train, y_train)

# Выводим лучшие параметры
print("Лучшие параметры:", grid_search.best_params_)
print("Лучшая точность на кросс-валидации:", grid_search.best_score_)

# Оцениваем качество модели на тестовой выборке
best_model = grid_search.best_estimator_
test_accuracy = best_model.score(X_test, y_test)
print("Точность на тестовой выборке:", test_accuracy)

# Дополнительно: оцениваем качество модели с помощью кросс-валидации на всех данных
cv_scores = cross_val_score(best_model, X, y, cv=5, scoring='accuracy')
print("Средняя точность по кросс-валидации на всех данных:", cv_scores.mean())
Лучшие параметры: {'max_depth': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 100}
Лучшая точность на кросс-валидации: 0.9785714285714286
Точность на тестовой выборке: 1.0
Средняя точность по кросс-валидации на всех данных: 0.9720634920634922

Плюсы:

  1. Простота реализации : Алгоритм простой и понятный.
  2. Полный перебор : Все возможные комбинации гиперпараметров проверяются, что гарантирует нахождение оптимального решения (если сетка достаточно плотная).

Минусы:

  1. Высокая вычислительная сложность : Если количество гиперпараметров велико или их диапазоны широки, число комбинаций может стать огромным.
  2. Неэффективность : Многие комбинации могут быть неинтересными или бессмысленными, но они всё равно проверяются.

1.3 Optuna

Optuna — это современная библиотека для подбора гиперпараметров, которая использует Bayesian Optimization и другие продвинутые методы для эффективного поиска оптимальных значений гиперпараметров. В отличие от Grid Search или Randomized Search, которые перебирают значения из заранее заданных сеток или случайно выбирают их, Optuna адаптивно находит лучшие комбинации гиперпараметров, учитывая предыдущие результаты.

  1. Optuna использует алгоритмы, такие как Tree-structured Parzen Estimator (TPE) и Bayesian Optimization, что делает её более эффективной, особенно для сложных задач.
  2. Optuna может автоматически прерывать вычисления для неперспективных наборов гиперпараметров (early stopping).
  3. Optuna легко интегрируется с популярными фреймворками машинного обучения, такими как scikit-learn, TensorFlow, PyTorch и др.
  4. Optuna поддерживает параллельный поиск гиперпараметров, что позволяет использовать несколько ядер процессора или даже распределенные системы.

Дан датасет, со следующими признаками (скачать файл Loan.csv).

  1. ApplicationDate: Loan application date
  2. Age: Applicant's age
  3. AnnualIncome: Yearly income
  4. CreditScore: Creditworthiness score
  5. EmploymentStatus: Job situation
  6. EducationLevel: Highest education attained
  7. Experience: Work experience
  8. LoanAmount: Requested loan size
  9. LoanDuration: Loan repayment period
  10. MaritalStatus: Applicant's marital state
  11. NumberOfDependents: Number of dependents
  12. HomeOwnershipStatus: Homeownership type
  13. MonthlyDebtPayments: Monthly debt obligations
  14. CreditCardUtilizationRate: Credit card usage percentage
  15. NumberOfOpenCreditLines: Active credit lines
  16. NumberOfCreditInquiries: Credit checks count
  17. DebtToIncomeRatio: Debt to income proportion
  18. BankruptcyHistory: Bankruptcy records
  19. LoanPurpose: Reason for loan
  20. PreviousLoanDefaults: Prior loan defaults
  21. PaymentHistory: Past payment behavior
  22. LengthOfCreditHistory: Credit history duration
  23. SavingsAccountBalance: Savings account amount
  24. CheckingAccountBalance: Checking account funds
  25. TotalAssets: Total owned assets
  26. TotalLiabilities: Total owed debts
  27. MonthlyIncome: Income per month
  28. UtilityBillsPaymentHistory: Utility payment record
  29. JobTenure: Job duration
  30. NetWorth: Total financial worth
  31. BaseInterestRate: Starting interest rate
  32. InterestRate: Applied interest rate
  33. MonthlyLoanPayment: Monthly loan payment
  34. TotalDebtToIncomeRatio: Total debt against income

Также датсет содержит информацию:

  1. LoanApproved: Loan approval status - будет ли кредит одобрен (для задачи классификации).
  2. RiskScore: Risk assessment score - оценка риска (для задачи регрессии).

Задача 1 (3 балла)

Давайте решим задачу бинарной классификации (будет выдан кредит или нет).

Не забудьте удалить столбец RiskScore!

  1. Разбейте выборку на обучающую и тестовую.
  2. Подберите оптимальные гиперпараметры с помощью кросс-валидации и optuna на обучающей выборке.
  3. Расчитайте F1-score на тестовой выборке.
In [ ]:

2. Catboost

2.1 CatboostClassifier

CatBoostClassifier — это мощный алгоритм машинного обучения из библиотеки CatBoost , разработанной компанией Yandex. Он является частью фреймворка CatBoost, который специализируется на обработке категориальных признаков без необходимости их предварительного кодирования (например, с помощью One-Hot Encoding). CatBoost использует градиентный бустинг над деревьями решений и показывает высокую производительность на задачах классификации. Основные особенности CatboostClassifier:

  1. Автоматическая обработка категориальных признаков : CatBoost может работать с категориальными признаками напрямую.
  2. Снижение переобучения : CatBoost использует методы, такие как перестановочный тест и порядковое целевое кодирование.
  3. Высокая скорость обучения : Библиотека оптимизирована для быстрого обучения даже на больших объемах данных.
  4. Поддержка многозадачности : CatBoost может обучаться на нескольких задачах одновременно.
  5. Встроенная кросс-валидация и подбор гиперпараметров .
In [17]:
# Генерация синтетического датасета
np.random.seed(42)

# Числовые признаки
num_feature_1 = np.random.normal(5, 2, 1000)  # Признак 1: нормальное распределение
num_feature_2 = np.random.uniform(0, 10, 1000)  # Признак 2: равномерное распределение

# Категориальные признаки
cat_feature_1 = np.random.choice(['A', 'B', 'C'], size=1000)  # Категориальный признак 1
cat_feature_2 = np.random.choice(['X', 'Y'], size=1000)       # Категориальный признак 2

# Целевая переменная (зависимость от признаков)
target = []
for i in range(1000):
    if num_feature_1[i] > 6 and cat_feature_1[i] in ['A', 'B']:  # Правило для класса 1
        target.append(1)
    elif num_feature_2[i] < 5 and cat_feature_2[i] == 'X':      # Правило для класса 1
        target.append(1)
    else:
        target.append(0)

# Создание DataFrame
data = pd.DataFrame({
    'NumFeature1': num_feature_1,
    'NumFeature2': num_feature_2,
    'CatFeature1': cat_feature_1,
    'CatFeature2': cat_feature_2,
    'Target': target
})

# Разделение на признаки и целевую переменную
X = data.drop(columns=['Target'])
y = data['Target']

# Указание категориальных признаков
categorical_features = ['CatFeature1', 'CatFeature2']

# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Создание объектов Pool для CatBoost
train_pool = Pool(data=X_train, label=y_train, cat_features=categorical_features)
test_pool = Pool(data=X_test, label=y_test, cat_features=categorical_features)

# Инициализация модели CatBoostClassifier
model = CatBoostClassifier(
    iterations=100,        # Количество итераций (деревьев)
    depth=6,               # Максимальная глубина дерева
    learning_rate=0.1,     # Скорость обучения
    loss_function='Logloss',  # Функция потерь для бинарной классификации
    verbose=10             # Вывод информации каждые 10 итераций
)

# Обучение модели
model.fit(train_pool)

# Предсказания на тестовой выборке
predictions = model.predict(test_pool)

# Оценка качества модели
accuracy = accuracy_score(y_test, predictions)
print(f"Точность модели: {accuracy:.4f}")

# Важность признаков
feature_importances = model.get_feature_importance()
for feature, importance in zip(X.columns, feature_importances):
    print(f"{feature}: {importance:.4f}")
0:	learn: 0.6271690	total: 1.15ms	remaining: 114ms
10:	learn: 0.3004846	total: 10.9ms	remaining: 88.1ms
20:	learn: 0.1571604	total: 17.7ms	remaining: 66.5ms
30:	learn: 0.0947066	total: 23.1ms	remaining: 51.3ms
40:	learn: 0.0657784	total: 28.1ms	remaining: 40.5ms
50:	learn: 0.0494676	total: 32.6ms	remaining: 31.3ms
60:	learn: 0.0405709	total: 36.7ms	remaining: 23.5ms
70:	learn: 0.0350401	total: 40.8ms	remaining: 16.7ms
80:	learn: 0.0304118	total: 45.1ms	remaining: 10.6ms
90:	learn: 0.0280984	total: 49ms	remaining: 4.85ms
99:	learn: 0.0266610	total: 52.2ms	remaining: 0us
Точность модели: 1.0000
NumFeature1: 28.3813
NumFeature2: 27.5961
CatFeature1: 11.2484
CatFeature2: 32.7742

Задача 2 (3 балла)

Решите предыдущую задачу, используя CatboostClassifier.

In [ ]:

2.2 CatboostRegressor

CatBoostRegressor — это алгоритм регрессии из библиотеки CatBoost , разработанной компанией Yandex. Он является частью фреймворка CatBoost, который специализируется на градиентном бустинге над деревьями решений и поддерживает как числовые, так и категориальные признаки без необходимости их предварительного кодирования (например, One-Hot Encoding). CatBoostRegressor используется для решения задач регрессии, где целевая переменная является непрерывной. Основные особенности CatBoostRegressor:

  1. Автоматическая обработка категориальных признаков : CatBoost может работать с категориальными признаками напрямую.
  2. Снижение переобучения : CatBoost использует методы, такие как перестановочный тест и порядковое целевое кодирование.
  3. Высокая скорость обучения : Библиотека оптимизирована для быстрого обучения даже на больших объемах данных.
  4. Поддержка многозадачности : CatBoost может обучаться на нескольких задачах одновременно.
  5. Встроенная кросс-валидация и подбор гиперпараметров .
In [18]:
# Генерация синтетического датасета
np.random.seed(42)

# Числовые признаки
area = np.random.uniform(50, 200, 1000)  # Площадь дома (в квадратных метрах)
num_rooms = np.random.randint(1, 6, 1000)  # Количество комнат
age = np.random.randint(0, 50, 1000)  # Возраст дома (в годах)

# Категориальные признаки
location = np.random.choice(['City', 'Suburb', 'Rural'], size=1000)  # Местоположение

# Целевая переменная (зависимость от признаков)
price = 50000 + area * 1000 - age * 500 + np.random.normal(0, 10000, 1000)  # Цена дома

# Создание DataFrame
data = pd.DataFrame({
    'Area': area,
    'NumRooms': num_rooms,
    'Age': age,
    'Location': location,
    'Price': price
})

# Разделение на признаки и целевую переменную
X = data.drop(columns=['Price'])
y = data['Price']

# Указание категориальных признаков
categorical_features = ['Location']

# Разделение данных на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Создание объектов Pool для CatBoost
train_pool = Pool(data=X_train, label=y_train, cat_features=categorical_features)
test_pool = Pool(data=X_test, label=y_test, cat_features=categorical_features)

# Инициализация модели CatBoostRegressor
model = CatBoostRegressor(
    iterations=100,        # Количество итераций (деревьев)
    depth=6,               # Максимальная глубина дерева
    learning_rate=0.1,     # Скорость обучения
    loss_function='RMSE',  # Функция потерь для регрессии (Root Mean Squared Error)
    verbose=10             # Вывод информации каждые 10 итераций
)

# Обучение модели
model.fit(train_pool)

# Предсказания на тестовой выборке
predictions = model.predict(test_pool)

# Оценка качества модели
mse = mean_squared_error(y_test, predictions)
rmse = np.sqrt(mse)
print(f"RMSE: {rmse:.2f}")

# Важность признаков
feature_importances = model.get_feature_importance()
for feature, importance in zip(X.columns, feature_importances):
    print(f"{feature}: {importance:.4f}")
0:	learn: 42886.0207994	total: 589us	remaining: 58.3ms
10:	learn: 19853.9653757	total: 4.82ms	remaining: 39ms
20:	learn: 12243.5232824	total: 8.83ms	remaining: 33.2ms
30:	learn: 10189.5012866	total: 12.4ms	remaining: 27.5ms
40:	learn: 9563.6000890	total: 16ms	remaining: 23.1ms
50:	learn: 9377.5232531	total: 18.6ms	remaining: 17.8ms
60:	learn: 9264.5780571	total: 21.2ms	remaining: 13.5ms
70:	learn: 9175.6445000	total: 24ms	remaining: 9.81ms
80:	learn: 9090.1512043	total: 27.3ms	remaining: 6.4ms
90:	learn: 9034.3517177	total: 30.8ms	remaining: 3.04ms
99:	learn: 8932.6403693	total: 33.9ms	remaining: 0us
RMSE: 10385.31
Area: 91.7645
NumRooms: 1.7094
Age: 6.2366
Location: 0.2895

Задача 3 (3 балла)

Решите предыдущую задачу, используя CatBoostRegressor. В качестве прогнозируемого значения используйте столбец RiskScore.

In [ ]:

3. Стекинг

Стекинг (stacking) — это продвинутый метод ансамблевого обучения, при котором несколько базовых моделей ("базовые оценщики") объединяются для создания более точной модели. Стекинг включает два основных этапа:

  1. Обучение базовых моделей: На этом этапе несколько различных алгоритмов машинного обучения (например, логистическая регрессия, случайный лес, градиентный бустинг и т.д.) обучаются на обучающих данных.

  2. Обучение мета-модели: Предсказания базовых моделей используются как входные данные для второй модели (мета-модели), которая учится комбинировать их предсказания для получения финального результата.

Пусть у нас есть $ M $ базовых моделей $ f_1(x), f_2(x), \dots, f_M(x) $, где каждая модель принимает на вход признаки $ x $ и выдает предсказание. Например: $$ f_i(x) = \hat{y}_i, \quad i = 1, 2, \dots, M $$

Здесь $ \hat{y}_i $ — предсказание $ i $-ой базовой модели.

Каждая базовая модель обучается на обучающем наборе данных $ D_{\text{train}} $. Для этого можно использовать любые алгоритмы машинного обучения, такие как линейная регрессия, деревья решений, градиентный бустинг и т.д.

После обучения базовых моделей мы создаем новый набор данных, где каждый объект представляет собой вектор предсказаний всех базовых моделей для данного объекта. Например, если у нас есть $ N $ объектов в обучающей выборке, то новый набор данных будет иметь размерность $ N \times M $, где $ M $ — количество базовых моделей.

Для объекта $ x_j $ новое представление будет выглядеть так: $$ z_j = [f_1(x_j), f_2(x_j), \dots, f_M(x_j)] $$

Где $ z_j $ — вектор предсказаний для $ j $-го объекта.

Мета-модель (например, линейная регрессия, решающее дерево или нейронная сеть) обучается на новом наборе данных $ Z = \{z_1, z_2, \dots, z_N\} $ и соответствующих истинных значениях целевой переменной $ y $.

Мета-модель пытается найти функцию $ g(z) $, которая минимизирует ошибку между предсказанными значениями и истинными значениями: $$ g(Z) = \arg\min_g \sum_{j=1}^N L(y_j, g(z_j)) $$

Где $ L $ — функция потерь (например, среднеквадратичная ошибка для задач регрессии или кросс-энтропия для задач классификации).

Для нового объекта $ x_{\text{new}} $:

  1. Каждая базовая модель делает свое предсказание: $ \hat{y}_1, \hat{y}_2, \dots, \hat{y}_M $.
  2. Эти предсказания формируют вектор $ z_{\text{new}} = [\hat{y}_1, \hat{y}_2, \dots, \hat{y}_M] $.
  3. Мета-модель делает финальное предсказание: $ \hat{y}_{\text{final}} = g(z_{\text{new}}) $.

Задача 4 (1 балл)

Решите предыдущую задачу используя стекинг моделей.

In [ ]: