Изучение многоквантильной регрессии с помощью Catboost

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

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

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



Доступно продолжение этой статьи:



Краткий обзор квантильной регрессии

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

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

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

Несмотря на то, что прогноз модели оптимально соответствует данным, он не дает количественной оценки неопределенности. Например, когда x составляет около 0,4, линия наилучшего соответствия предсказывает, что y равно 3,8. Хотя верно то, что 3,8 является наиболее вероятным значением y, когда x близок к 0,4, в данных есть множество примеров, когда y намного выше или ниже 3,8. Иными словами, условное распределение целевого объекта имеет высокую изменчивость за пределами его исключения, и квантильная регрессия помогает нам это описать.

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

Чтобы лучше понять эту функцию потерь, предположим, что модель обучается для изучения 95-го квантиля целевого распределения. В первом случае рассмотрим один обучающий пример, в котором цель была 45, а модель предсказала 60 (т. е. модель переоценила цель на 15). Предположим также, что каждый обучающий пример имеет вес 1. Функция потерь, оцениваемая при этих значениях, выглядит следующим образом:

Теперь, с той же целью 45, предположим, что модель предсказала 30 (т. е. модель недооценила цель на 15). Значение функции потерь выглядит совсем иначе:

Несмотря на то, что в обоих примерах предсказание модели было отклонено на 15, функция потерь наказывала за недооценку намного больше, чем за переоценку. Поскольку изучается 95-й квантиль, функция потерь наказывает любой прогноз ниже целевого значения в 0,95 раза, а любой прогноз выше целевого значения — в 0,05 раза. Следовательно, модель «вынуждена» отдавать предпочтение завышенному прогнозу над заниженным при изучении 95-го квантиля. Это происходит при изучении любых квантилей выше медианы — противоположное верно при изучении квантилей ниже медианы. Чтобы увидеть это лучше, мы можем визуализировать функцию потерь для каждого предсказанного квантиля:

Catboost теперь расширяет эту идею, позволяя базовым деревьям решений выводить несколько квантилей на узел. Это позволяет одной модели предсказывать несколько квантилей, минимизируя новую функцию потерь:

Пример 1 — Простая линейная регрессия

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

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from catboost import CatBoostRegressor
sns.set()

# Number of training and testing examples
n = 1000

# Generate random x values between 0 and 1
x_train = np.random.rand(n)
x_test = np.random.rand(n)

# Generate random noise for the target
noise_train = np.random.normal(0, 0.3, n)
noise_test = np.random.normal(0, 0.3, n)

# Set the slope and y-intercept of the line
a, b = 2, 3

# Generate y values according to the equation y = ax + b + noise
y_train = a * x_train + b + noise_train
y_test = a * x_test + b + noise_test

Полученные данные обучения должны выглядеть примерно так:

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

# Store quantiles 0.01 through 0.99 in a list
quantiles = [q/100 for q in range(1, 100)]

# Format the quantiles as a string for Catboost
quantile_str = str(quantiles).replace('[','').replace(']','')

# Fit the multi quantile model
model = CatBoostRegressor(iterations=100,
                          loss_function=f'MultiQuantile:alpha={quantile_str}')

model.fit(x_train.reshape(-1,1), y_train)

# Make predictions on the test set
preds = model.predict(x_test.reshape(-1, 1))
preds = pd.DataFrame(preds, columns=[f'pred_{q}' for q in quantiles])

Полученные прогнозы DataFrame выглядят примерно так:

Каждая строка соответствует тестовому примеру, а каждый столбец дает прогнозируемый квантиль. Например, предсказание 10-го квантиля для первого тестового примера было 3,318624. Поскольку есть только одна входная функция, мы можем визуализировать несколько прогнозируемых квантилей, наложенных на данные тестирования:

fig, ax = plt.subplots(figsize=(10, 6))
ax.scatter(x_test, y_test)

for col in ['pred_0.05', 'pred_0.5', 'pred_0.95']:
    ax.scatter(x_test.reshape(-1,1), preds[col], alpha=0.50, label=col)

ax.legend()

Визуально 95-й и 5-й квантили хорошо справляются с учетом неопределенности данных. Более того, 50-й квантиль (то есть медиана) примерно соответствует линии наилучшего соответствия. При работе с прогнозируемыми квантилями одна метрика, которую нам часто интересно анализировать, — это покрытие. Охват — это процент целей, которые попадают между двумя желаемыми квантилями. Например, охват можно рассчитать с использованием 5-го и 95-го квантилей следующим образом:

coverage_90 = np.mean((y_test <= preds['pred_0.95']) & (y_test >= preds['pred_0.05']))*100
print(coverage_90) 
# Output: 91.4

Используя 5-й и 95-й квантили, предполагая идеальную калибровку, мы ожидаем, что охват будет 95–5 = 90%. В этом примере предсказанные квантили немного отличались, но все же были близки, что дает значение охвата 91,4%.

Давайте теперь проанализируем все прогнозируемое распределение, которое выводит модель. Напомним, линия наилучшего соответствия y = 2x + 3. Следовательно, для любых входных данных x среднее значение прогнозируемого распределения должно быть около 2x + 3. Аналогичным образом, поскольку к данным был добавлен случайный гауссовский шум со стандартным отклонением 0,3, стандартное отклонение предсказанного распределения должно быть около 0,3. Давайте проверим это:

# Give the model a new input of x = 0.4
x = np.array([0.4])

# We expect the mean of this array to be about 2*0.4 + 3 = 3.8
# We expect the standard deviation of this array to be about 0.30
y_pred = model.predict(x.reshape(-1, 1))

mu = np.mean(y_pred)
sigma = np.std(y_pred)
print(mu) # Output: 3.836147287742427
print(sigma) # Output: 0.3283984093786787

# Plot the predicted distribution
fig, ax = plt.subplots(figsize=(10, 6))
_ = ax.hist(y_pred.reshape(-1,1), density=True)
ax.set_xlabel('$y$')
ax.set_title(f'Predicted Distribution $P(y|x=4)$, $\mu$ = {round(mu, 3)}, $\sigma$ = {round(sigma, 3)}')

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

Пример 2. Нелинейная регрессия с переменным шумом

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

# Create regions of the domain that have variable noise
bounds = [(-10, -8), (-5, -4), (-4, -3), (-3, -1), (-1, 1), (1, 3), (3, 4), (4, 5), (8, 10)]
scales = [18, 15, 8, 11, 1, 2, 9, 16, 19]

x_train = np.array([])
x_test = np.array([])
y_train = np.array([])
y_test = np.array([])

for b, scale in zip(bounds, scales):

    # Randomly select the number of samples in each region 
    n = np.random.randint(low=100, high = 200)

    # Generate values of the domain between b[0] and b[1]
    x_curr = np.linspace(b[0], b[1], n)

    # For even scales, noise comes from an exponential distribution
    if scale % 2 == 0:

        noise_train = np.random.exponential(scale=scale, size=n)
        noise_test = np.random.exponential(scale=scale, size=n)

    # For odd scales, noise comes from a normal distribution
    else:

        noise_train = np.random.normal(scale=scale, size=n)
        noise_test = np.random.normal(scale=scale, size=n)

    # Create training and testing sets
    y_curr_train = x_curr**2  + noise_train
    y_curr_test = x_curr**2  + noise_test

    x_train = np.concatenate([x_train, x_curr])
    x_test = np.concatenate([x_test, x_curr])
    y_train = np.concatenate([y_train, y_curr_train])
    y_test = np.concatenate([y_test, y_curr_test])

Полученные данные обучения выглядят следующим образом:

Мы подгоним регрессор Catboost так же, как в примере 1, и визуализируем прогнозы на тестовом наборе:

model = CatBoostRegressor(iterations=300,
                          loss_function=f'MultiQuantile:alpha={quantile_str}')

model.fit(x_train.reshape(-1,1), y_train)

preds = model.predict(x_test.reshape(-1, 1))
preds = pd.DataFrame(preds, columns=[f'pred_{q}' for q in quantiles])

fig, ax = plt.subplots(figsize=(10, 6))
ax.scatter(x_test, y_test)

for col in ['pred_0.05', 'pred_0.5', 'pred_0.95']:

    quantile = int(float(col.split('_')[-1])*100)
    label_name = f'Predicted Quantile {quantile}'
    ax.scatter(x_test.reshape(-1,1), preds[col], alpha=0.50, label=label_name)

ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Testing Data for Example 2 with Predicted Quantiles')
ax.legend()

При визуальном осмотре модель хорошо охарактеризовала эту нелинейную гетероскедастическую зависимость. Обратите внимание, как около x = 0 три предсказанных квантиля сходятся к одному значению. Это связано с тем, что в области вблизи x = 0 почти нет шума — любая модель, которая правильно предсказывает условное распределение вероятностей в этой области, должна предсказывать небольшую дисперсию. И наоборот, когда x находится в диапазоне от 7,5 до 10,0, предсказанные квантили разнесены гораздо дальше из-за собственного шума в регионе. Покрытие 90% можно рассчитать, как и раньше:

coverage_90 = np.mean((y_test <= preds['pred_0.95']) & (y_test >= preds['pred_0.05']))*100
print(coverage_90) 
# Output: 90.506

При использовании 5-го и 95-го квантилей при идеальной калибровке ожидаемый охват составляет 95–5 = 90 %. В этом примере предсказанные квантили были даже лучше, чем в примере 1, давая охват 90,506%.

Наконец, давайте посмотрим на несколько входных данных и их соответствующее прогнозируемое распределение.

Приведенное выше распределение хорошо справляется с захватом целевого значения с относительно небольшой дисперсией. Этого следует ожидать, поскольку целевые значения в обучающих данных, когда x = 0, имеют небольшой шум. Сравните это с прогнозируемым целевым распределением при x = -8,56:

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

Последние мысли

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

Стать участником: https://harrisonfhoffman.medium.com/membership

Купи мне кофе: https://www.buymeacoffee.com/HarrisonfhU

Рекомендации

  1. Функции потерь Catboost —https://catboost.ai/en/docs/concepts/loss-functions-regression#MultiQuantile