Изучение многоквантильной регрессии с помощью 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
Рекомендации
- Функции потерь Catboost —https://catboost.ai/en/docs/concepts/loss-functions-regression#MultiQuantile