Условие
В Booking.com появилась задача: автоматически предлагать пользователям «хорошие сделки» (good value deals) среди отелей, в том числе тех, у которых ещё нет рейтинга. Как вы её решите?
Решение
Подход
Это композитная задача: (а) оценить «истинную цену» отеля и (б) сравнить фактическую цену с ожидаемой. Хорошая сделка = большая отрицательная разница.
1. Целевая переменная.
- Прокси для качества — медианная цена «похожих» отелей на ту же дату/город/тип номера.
- Альтернативно — историческая средняя цена этого же отеля (для отелей без рейтинга — мало данных).
2. Признаки отеля.
- Локация (район, расстояние до достопримечательностей).
- Тип (отель/апартаменты/хостел), число звёзд, количество номеров.
- Удобства (Wi-Fi, завтрак, бассейн) — sparse one-hot.
- Внешние сигналы: средние цены конкурентов на эту ночь, сезонность, события в городе.
3. Модель «ожидаемой цены».
- Бейзлайн: median price по схожим отелям (collaborative filtering для cold start).
- Основная: GBM (LightGBM/XGBoost) с регрессией на
log(price). - Эмбеддинги соседних отелей + цена через kNN — хорошо работает для cold start.
4. Метрика хорошей сделки.
deal_score = (expected_price − actual_price) / expected_price.- Порог:
deal_score > 15%→ «good value». Подбирается по бизнесу.
5. Cold start (нет рейтинга, мало данных).
- Берём ближайших соседей по локации и типу.
- Используем content-based признаки, а не history.
- Маркируем как «new property — рейтинг скоро появится» — пользователи реагируют лучше, чем на «безрейтинговый» без объяснений.
6. Валидация.
- Offline: MAE / MAPE по предсказанной цене на отложенной по времени выборке.
- Online: A/B-тест по бизнес-метрикам (CTR на deal-плашку, конверсия в бронь, контрибуция этих бронирований в общую выручку).
# Эскиз бейзлайна
import lightgbm as lgb
import numpy as np
# y = log(price), чтобы регрессия не страдала от длинного хвоста
model = lgb.LGBMRegressor(objective="regression", n_estimators=500)
model.fit(X_train, np.log1p(y_train), eval_set=[(X_val, np.log1p(y_val))])
expected = np.expm1(model.predict(X))
deal_score = (expected - actual_price) / expected
is_good_deal = deal_score > 0.15Подводные камни
- Adverse selection. Если ожидаемая цена строится на конверсиях, отель с занижением может попадать в «good deals» снова и снова — модель закрепляет смещение. Регуляризуйте: ограничивайте долю «deals» в выдаче.
- Двойной счёт сезонности. Сравнивать с медианой города на ту же ночь — обязательно; иначе все летние Анапы будут «плохими сделками» к зимним.
- Налоги и комиссии. «Цена» бывает разная: со сборами, без, с завтраком. Сравниваем comparable.
- Cold start без content-features ломается. Для новых отелей крайне важно завести признаки локации/типа в первый же день.
Эталонный ответ
Регрессия на «ожидаемую цену» по похожим отелям/датам; deal_score = (expected − actual) / expected; порог настраивается A/B-тестом. Для отелей без истории — content-based / kNN-cold-start.