Служба такси, такая как Uber/Lyft, начинает свою работу в новом городе. Разработайте систему для прогнозирования базовых тарифов для различных пар пунктов отправления и назначения в пределах города.

Тип проблемы машинного обучения

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

Каковы особенности?

Ниже приведены некоторые функции, которые могут быть полезны для прогнозирования базовых тарифов:

  • Расстояние от источника до места назначения
  • Среднее время, затрачиваемое на путешествие от источника до места назначения
  • Среднее количество людей, путешествующих из пункта отправления в пункт назначения в день
  • Среднее количество общественного транспорта из пункта отправления в пункт назначения
  • Средняя стоимость проезда на человека в общественном транспорте между пунктом отправления и пунктом назначения
  • Страна, которой принадлежит город
  • Количество такси (уже работающих или планирующих запустить) по маршруту
  • Тип поездки (совместная или частная)
  • Цены на топливо в городе.

… и так далее.

Как получить данные для обучения и тестирования, как получить метки?

Храните данные о совершенных поездках в больших базах данных, таких как Hadoop или Hive, со следующими метаданными о поездках:

(ride_id, user_id, driver_id, src_lat, src_long, dst_lat, dst_long, город, страна, расстояние, продолжительность, ride_type, base_fare, всплеск_множителя, total_fare)

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

Мы можем предположить, что ячейка QuadTree имеет максимум K мест захвата/отвода, т. е. мы не разделяем ячейку дальше, если она содержит меньше, чем равно K мест захвата/отвода, иначе делим ячейку на 4 квадранта.

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

Например, если у нас есть таблица (широта, долгота, grid_id)

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

SELECT
(SELECT grid_id FROM grid WHERE latitude=src_lat AND longitude=src_long) as source,
(SELECT grid_id FROM grid WHERE latitude=dst_lat AND longitude=dst_long) as destination,
AVG(dist) FROM trips WHERE city IN (<nearby cities>) GROUP BY source, destination

Точно так же мы можем найти среднее время, затрачиваемое на отправку и пункт назначения, количество поездок между источником и пунктом назначения.

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

SELECT
(SELECT grid_id FROM grid WHERE latitude=src_lat AND longitude=src_long) as source,
(SELECT grid_id FROM grid WHERE latitude=dst_lat AND longitude=dst_long) as destination,
AVG(base_fare) as base_fare FROM trips WHERE city IN (<nearby cities>) GROUP BY source, destination

Мы можем создавать задания Spark для выполнения этих запросов в Hive.

Мы можем получить данные об общественном транспорте с государственных веб-сайтов.

Обратите внимание, что полученные данные об общественном транспорте, скорее всего, будут получены в то же время, когда служба такси уже работала в городе, но наша цель — оценить данные об общественном транспорте до того, как они будут введены в действие в городе. Таким образом, количество общественного транспорта и людей, путешествующих на общественном транспорте, будет ниже фактического. Мы можем умножить на какой-то коэффициент в диапазоне 1,1–1,3, т.е. на 10–30 % меньше фактического.

Какие шаги предварительной обработки функции необходимы?

  • Работа с коррелированными функциями. Обратите внимание, что расстояние и продолжительность поездки могут коррелировать при условии сходных дорожных условий, но в целом это не так, поскольку дорожные условия очень динамичны и зависят от внешних факторов. Мы можем использовать PCA для объединения коррелированных признаков.
  • Обработка отсутствующих значений и NaN. Пропустите все поездки, в которых отсутствует параметр base_fare. Недостающие расстояния можно рассчитать, используя широту и долготу. Недостающую продолжительность можно вычислить, выполнив расстояние/среднюю скорость.
  • Обнаружение выбросов . Некоторые поездки могут занять очень много времени из-за дорожных условий или большого расстояния из-за того, что водитель едет в объезд. Мы можем опустить поездки, в которых затраченное время или расстояние выходят за пределы среднего значения + 2*sd среди всех поездок для одних и тех же пар исходной и конечной точек.
  • Масштабирование объектов . Значения расстояний или длительности имеют разные масштабы. Мы можем нормализовать значения функции от 0 до 1, выполнив (x-min(x))/(max(x)-min(x)) где x — значение функции, а min(x) — минимальное значение функции. по поездкам.

Как вычислить представления функций? Обрабатывать функции высокой мощности?

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

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

D1 ≤ D2 ≤ D3 …. ≤ Dn

Затем, если размер ведра равен K, первый K, то есть от D1 до Dk, попадает в ведро 1, затем от Dk+1 до D2k переходит в ведро 2 и так далее. Таким образом, вместо n значений признаков у нас есть n/K значений признаков:

D’1, D’2 , … D’n/K

где D’1 = (D1+…+Dk)/K и так далее.

Для категориальных признаков (таких как страна и тип поездки) мы можем сделать одно горячее кодирование. Поскольку количество стран может быть около 200, векторы могут быть очень редкими.

Одной из стратегий является группирование. Подсчитайте частоту поездок по стране.

Пусть страна и частоты поездок, отсортированные по убыванию, будут:

(C1, F1) (C2, F2) … (Cn, Fn), где F1 ≥ F2 ≥ …

Затем, начиная с 1-й страны, складываем частоты до соотношения (F1+F2+…+Fk)/(F1+F2+…+Fn) ≥ 0,9, где k ‹ n.

Присвойте метки 1 C1, 2 C2, …, k Ck и k+1 всем странам от Ck+1 до Cn. Что у нас есть только k стран вместо n.

Другая стратегия заключается в использовании метода Хеширования.

Сгенерируйте K случайных перестановок от 0 до m-1, где «m» — количество стран. Для каждой перестановки будет один индекс, где значение равно 1, а все остальные индексы равны 0 (поскольку это горячее кодирование). Новое значение является значением записи перестановки для ненулевого индекса.

Хэширование вектора горячего кодирования длиной 9 в хэш длины 3.

Объедините числовые векторы, а также векторы одноразового кодирования для каждой поездки.

Где хранить представления объектов?

Функции могут быть сохранены в базе данных для сохранения. Мы можем использовать базу данных NoSQL, такую ​​​​как Cassandra, для хранения значений функций.

Таблица функций: (trip_id, feature_id, feature_name, значение)

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

Как обучить модель, используя функции?

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

Ниже приведены шаги для обучения модели:

  • Разделите набор данных на обучение, тестирование и проверку (обычно 80–20).
  • Выполните настройку гиперпараметров (константы регуляризации и т. д.), используя перекрестную проверку поиска по сетке (K-fold).
  • Мы можем использовать функцию потерь RMSE для обучения модели, а также для оптимизации результатов поиска в сетке. RMSE = сумма (истинный тариф — прогнозируемый тариф)² по всем данным обучения.
  • Лучшие гиперпараметры выбираются на основе минимальных потерь данных проверки.
  • Окончательная модель обучается на всех обучающих данных с лучшими гиперпараметрами.

Предполагая N обучающих примеров и M функций.

L = sum_i(1toN) (true_i — pred_i)² + reg*sum_i(1toM) w_i²
pred_i = sum_i(1toM) w_i*feature_i

Как оценить модель в офлайне? Метрики?

потеря среднеквадратичной ошибки

Как сохранить модель и вес модели, архитектуру и т.д.?

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

‹bucket_name›/‹дата›/‹версия›

Как тренироваться, когда размер обучающих данных слишком велик для одной машины?

Есть несколько способов, которыми это может быть достигнуто.

Генераторы Python

Вместо того, чтобы загружать все обучающие данные в память, загружайте партии, скажем, по 100 за раз, используя генераторы Python. После обработки пакета удалите его из памяти. Недостатком является то, что будет много дискового ввода-вывода, что может увеличить время обучения.

Режим канала S3

Загрузите данные обучения в S3 вместо Hive, а затем используйте режим Pipe во время обучения с Sagemaker. Данные напрямую передаются алгоритму обучения, а не сохраняются на локальном диске, а затем загружаются в память. Здесь у нас есть сетевой ввод-вывод, который может замедлить обучение.

Распределенное обучение (совместное использование параметров)

  • Разделите набор обучающих данных на нескольких узлах/компьютерах.
  • Скопируйте набор тестовых данных на все машины.
  • Создайте таблицу в Cassandra, чтобы отслеживать изученные веса. (model_id, веса, потери, last_updated_timestamp)
  • Создайте еще одну таблицу для отслеживания выполнения перекрестной проверки. (model_id, machine_id, fold_num, hyperparameter_hash, гиперпараметры, validation_loss)
  • В каждой машине разделите тренировочный набор на K равных частей, чтобы K сложить CV.
  • Перед запуском задания настройки CV гиперпапарметра обновите таблицу гиперпараметров данными model_id, machine_id, fold_num, hyperparameters. Гиперпараметры представляют собой строку JSON и одинаковы для всех fold_num, соответствующих model_id.
  • После обнаружения потери проверки для настройки гиперпараметра обновите столбец validation_loss, соответствующий model_id, machine_id, fold_num и гиперпараметрам в Cassandra.
  • Чтобы найти лучшие гиперпараметры для модели, выполните
SELECT hyperparameters 
FROM 
(SELECT model_id, hyperparameters, AVG(validation_loss) as loss GROUP BY model_id, hyperparameter_hash)
HAVING loss=MIN(loss)
  • Перед каждой эпохой извлекайте «веса» из таблицы весов и запускайте градиентный спуск на этом наборе весов.
  • Найдя обновленные веса и потери на обучающих данных в одной эпохе, отправьте обновленные веса с потерями Кассандре. Если временная метка больше, чем last_updated_timestamp, то веса и потери перезаписываются в таблице весов, иначе они игнорируются.

Как внедрить коды логического вывода в производство?

  • Создайте конечные точки Flask для логических выводов.
  • В конструкторе загрузите артефакты модели из корзины S3 выше и инициализируйте класс модели и классы преобразования объектов.
  • Чтобы сделать Flask многопоточным, используйте Gunicorn поверх Flask.
  • Используйте Github+Jenkins для создания конвейера CI/CD.
  • Всякий раз, когда в Github создается PR для кодов логического вывода, запускайте сборку Jenkins, т. е. запускайте любые модульные и интеграционные тестовые случаи, и, если все тестовые случаи пройдены, затем собирайте образ docker и развертывайте образ докера в кластере Kubernetes.
  • Используйте как минимум 3 реплики для модуля, в котором работает служба Docker, чтобы обеспечить балансировку нагрузки.

Как контролировать модели в производстве?

Настройте ведение журнала и возможность наблюдения с помощью Datadog или Cloudwatch (при использовании AWS).

Вот некоторые показатели производительности, которые мы можем отслеживать:

  • Количество ошибок 5xx за последние 5 минут.
  • Задержка P99 каждые 5 минут.
  • Количество запросов в секунду.
  • Загрузка ЦП на разных узлах, на которых запущена служба логического вывода.
  • Использование памяти на разных узлах, на которых запущена служба логического вывода.
  • Количество исключений за последние 5 минут.

Метрики модели, которые мы должны отслеживать:

  • Дрейф данных. Для каждого признака предварительно вычислите среднее значение и дисперсию в наборе обучающих данных. В рабочей среде вычисляйте среднее значение и дисперсию значений признаков в потоковом режиме, а через каждые 5 минут вычисляйте Расхождение KL между обучающими и производственными значениями.

Как сделать онлайн-оценку?

Использование A/B-тестирования с различными онлайн-метриками, такими как: количество бронирований, количество отмен после отображения цены в приложении, количество удалений приложения и т. д.

Мы можем использовать критерий хи-квадрат или Z-тест, чтобы проверить, действительно ли наша модель работает или нет.

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

Разделите набор пользователей в городе на 2 равные группы, одна группа получает base_fares с нулевой гипотезой (model0), а другая группа с указанной выше моделью ML (model1).

Z-тест:

В течение следующих 30 дней вычисляйте количество бронирований каждый день по обеим моделям. Пусть u0 будет средним числом бронирований от model0 и u1 от model1. Точно так же пусть v0 будет отклонением от model0 и v1 от model1.

Оценка Z = (u1-u0)/(sqrt((v0+v1)/30))

Если Z-оценка намного выше 0, то наша модель1 превосходит модель0. Чтобы получить уверенность, мы можем найти p-значение.

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

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

Таким образом, у нас будет 4 разных счетчика:

  • Количество пользователей, которые забронировали, увидев цену от model0
  • Количество пользователей, которые не бронировали, увидев цену от model0
  • Количество пользователей, которые забронировали номер, увидев цену от model1
  • Количество пользователей, которые не бронировали, увидев цену от model1

Количество пользователей, которые не бронировали = количество пользователей, у которых установлено приложение - количество пользователей, которые забронировали.

Мы можем вычислить показатель хи-квадрат, используя приведенные выше 4 подсчета.

Трубопровод

Обучение

Вывод