Оптимизация логистики: оптимизация местоположения водителей доставки с помощью причинно-следственной связи



Клиентом этой недели является ZDelivery. Работайте со своими данными, чтобы помочь ему
понять основные причины невыполненных запросов и разработать решения, которые рекомендуют местонахождение водителей, что увеличивает долю выполненных заказов. Поскольку водителям платят в зависимости от количества принятых ими запросов, ваше решение поможет бизнесу ZDelivery повысить удовлетворенность клиентов и расширить бизнес.

Обзор и цель

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

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

Однако причинность относительно быстро нарушается даже в простых физических
системах. Вернемся к бильярдному столу. Мы ударяем по мячу 1, который попадает в мяч 2,
который попадает в мяч 3, и так далее. Зная точную траекторию мяча 1, мы можем вычислить точные траектории всех последующих мячей. Однако, учитывая очень незначительное отклонение фактической траектории мяча 1 от траектории, которую мы используем в наших расчетах, наш прогноз для мяча 2 будет немного отличаться, наш прогноз для мяча 3 будет еще дальше, а наш прогноз для мяча 5 может быть совсем выключен. Учитывая небольшое количество шума в системе, которое всегда имеет место, мы ничего не можем сказать о траектории мяча 5: мы не имеем представления о причинно-следственной связи между тем, как мы ударили мяч 1, и траекторией мяча 5.

О данных

Для этого проекта доступны два набора данных.

Первый содержит информацию о поездках

  • Идентификатор поездки: представляет собой уникальный идентификационный номер
    текущей поездки.
  • Начало поездки: комбинация широты и долготы в формате (LAT, LNG)
    , которая содержит GPS-координаты начала поездки.
  • Назначение поездки. Комбинация широты и долготы в формате (LAT,
    LNG), которая содержит GPS-координаты пункта назначения поездки.
  • Время начала поездки. Это временная метка времени начала
    поездки.
  • Время окончания поездки. Это временная метка времени окончания
    поездки.

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

  • ID: уникальный идентификационный номер для каждой строки в наборе данных.
  • order_id: идентификационный номер для транслируемых поездок.
  • driver_id: идентификационный номер водителей.
  • driver_action: представляет действия водителей, будь то «принятие» или
    «отклонение».
  • широта: Широта водителя во время трансляции поездки.
  • lng: долгота водителя во время трансляции поездки.

Исследовательский анализ данных

Некоторые извлечения функций

  • Будний/выходной день

  • Праздничный день

  • Расстояние между пунктом отправления и пунктом назначения

  • Скорость (км/ч)

Причинное обучение



Объединение двух наборов данных на основе идентификатора поездки

  • Получить широту и долготу водителя.
preprocessed_trip_df['driver_lat'] = driver_lat
preprocessed_trip_df['driver_lng'] = driver_lng
  • Рассчитать расстояние между точкой отправления и координатами водителя
driver_lat_coordinates = preprocessed_trip_df['driver_lat'].tolist()
driver_lng_coordinates = preprocessed_trip_df['driver_lng'].tolist()
ending_coordinates = []
for i in range(len(driver_lat_coordinates)):
    ending_coordinates.append(f'{driver_lat_coordinates[i]},{driver_lng_coordinates[i]}')
start_coordinates = preprocessed_trip_df['Trip Origin'].tolist()
  • Вычисление невыполненных заказов
preprocessed_trip_df_with_drivers['is_fulfilled'] = preprocessed_trip_df_with_drivers['driver_distance'].apply(
    lambda x: 1 if x !=-2 else 0)
preprocessed_trip_df_with_drivers[preprocessed_trip_df_with_drivers['is_fulfilled']==0].shape
(609, 14)

Окончательная структура кадра данных после удаления некоторых нежелательных столбцов.

  • Масштабирование и нормализация числовых данных
from sklearn import preprocessing
from causalnex.structure.notears import from_pandas
x = struct_data.values #returns a numpy array
min_max_scaler = preprocessing.MinMaxScaler()
x_scaled = min_max_scaler.fit_transform(x)
df = pd.DataFrame(x_scaled)

sm = from_pandas(df)
{'is_weekend': 0, 'is_holiday': 1, 'distance': 2, 'speed': 3, 'driver_distance': 4, 'is_fulfilled': 5}
  • Визуализировать
viz = plot_structure(
    sm,
    graph_attributes={"scale": "0.5"},
    all_node_attributes=NODE_STYLE.WEAK,
    all_edge_attributes=EDGE_STYLE.WEAK,
)
Image(viz.draw(format='png'))

  • Удалить ребра ниже порогового значения
sm.remove_edges_below_threshold(0.8)

  • Правильные отношения

distance ---> is_fulfilled
distance ---> is_fulfilled
driver_distance ---> is_fulfilled

  • Неверные отношения

distance ---> is_weekend
driver_distance ---> is_weekend

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

  • Переименовать столбцы
df = df.rename(columns={0: "is_weekend", 1: "is_holiday", 2: "distance", 3: "speed",4: "driver_distance", 5: "is_fulfilled"}, errors="raise")
sm = from_pandas(df, tabu_edges=[("driver_distance", "is_weekend"),("distance", "is_weekend")], w_threshold=0.8)

sm.add_edge("is_weekend", "is_fulfilled")
sm.add_edge("is_holiday", "is_fulfilled")

Подбор условного распределения байесовской сети

from causalnex.network import BayesianNetwork

bn = BayesianNetwork(sm)

Подготовка дискретизированных данных

  • Количество категориальных признаков
df_c = preprocessed_trip_df_with_drivers.copy()
df_c['is_weekend'] = df_c['is_weekend'].apply(lambda x: 'weekday' if x == 0 else "weekend")
df_c['is_holiday'] = df_c['is_holiday'].apply(lambda x: 'holiday' if x !=0 else "no-holiday")
df_c['is_fulfilled'] = df_c['is_fulfilled'].apply(lambda x: 'fulfilled' if x !=0 else "unfulfilled")

  • Дискретизация числовых признаков
from causalnex.discretiser import Discretiser
discretised_data = df_c.copy()
discretised_data["distance"] = Discretiser(method="fixed",
        numeric_split_points=[1,100]).transform(discretised_data["distance"].values)
discretised_data["distance"].value_counts()
# output
1    26039
0      390
2        7
discretised_data["driver_distance"] = Discretiser(method="fixed",
        numeric_split_points=[1,100]).transform(discretised_data["driver_distance"].values)
discretised_data["driver_distance"].value_counts()
#output
1    17361
0     9073
2        2
discretised_data["speed"] = Discretiser(method="fixed",
        numeric_split_points=[1,100]).transform(discretised_data["speed"].values)
discretised_data["speed"].value_counts()
#output
1    24515
2     1681
0      240
  • Создание меток для числовых функций
distance_map = {
    0: "short-distance",
    1: "medium-distance",
    2: "long-distance"
}
speed_map = {
    0: "slow-speed",
    1: "medium-speed",
    2: "fast-speed"
}
discretised_data["distance"] = discretised_data["distance"].map(distance_map)
discretised_data["driver_distance"] = discretised_data["driver_distance"].map(distance_map)
discretised_data["speed"] = discretised_data["speed"].map(speed_map)

Поезд / тестовый сплит

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

from sklearn.model_selection import train_test_split

train, test = train_test_split(discretised_data, train_size=0.9, test_size=0.1, random_state=7)

Модель Вероятность

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

bn = bn.fit_cpds(train, method="BayesianEstimator", bayes_prior="K2")
bn.cpds["is_fulfilled"]
  • Предсказывать состояние по входным данным

Метод прогнозирования BayesianNetwork позволяет нам делать прогнозы на основе данных с использованием изученной байесовской сети. Например, мы хотим предсказать, будет ли поездка выполнена или нет на основе входных данных. Представьте, что у нас есть данные о поездках, которые выглядят так:

discretised_data.loc[57, discretised_data.columns != 'is_fulfilled']
#output
is_weekend                 weekday
is_holiday              no-holiday
distance           medium-distance
speed                 medium-speed
driver_distance    medium-distance
predictions = bn.predict(discretised_data, "is_fulfilled")
print(f"The prediction is '{predictions.loc[57, 'is_fulfilled_prediction']}'")
#output
The prediction is 'fulfilled'

Моделирование

В этом блокноте я реализовал 4 модели машинного обучения, ниже постараюсь поделиться результатами.

Случайный лес

Нормализовать

norm_df = preprocessed_trip_df_with_drivers.copy()
x = norm_df.values #returns a numpy array
min_max_scaler = preprocessing.MinMaxScaler()
x_scaled = min_max_scaler.fit_transform(x)
df = pd.DataFrame(x_scaled)

Подходит

from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestRegressor
X = df.drop(columns=['is_fulfilled'], axis=1)
y = df['is_fulfilled']
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=123)
pipeline = Pipeline(steps=[
    ('regressor', RandomForestRegressor(n_estimators=12, random_state=42))
])
model = pipeline.fit(X_train, y_train)

Подтвердить

val_accuracy = model.score(X_test, y_test)
val_accuracy
#output
1.0
  • Мы видим, что точность проверки равна 1,0, что означает переоснащение модели.

Логистическая регрессия

Подходит

from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
results = cross_validate(estimator=model,
    X=X,
    y=y,
    cv=5,
    scoring=scoring,
    return_train_score=True, return_estimator=True)

Результат модели

{'Training Accuracy scores': array([0.97697182, 0.97692562, 0.97697291, 0.97697291, 0.97697291]),
 'Mean Training Accuracy': 97.69632320376157,
 'Training Precision scores': array([0.97697182, 0.97692562, 0.97697291, 0.97697291, 0.97697291]),
 'Mean Training Precision': 0.9769632320376156,
 'Training Recall scores': array([1., 1., 1., 1., 1.]),
 'Mean Training Recall': 1.0,
 'Training F1 scores': array([0.98835179, 0.98832815, 0.98835235, 0.98835235, 0.98835235]),
 'Mean Training F1 Score': 0.9883473967699015,
 'Validation Accuracy scores': array([0.9769289 , 0.97711368, 0.97692453, 0.97692453, 0.97692453]),
 'Mean Validation Accuracy': 97.696323325532,
 'Validation Precision scores': array([0.9769289 , 0.97711368, 0.97692453, 0.97692453, 0.97692453]),
 'Mean Validation Precision': 0.9769632332553201,
 'Validation Recall scores': array([1., 1., 1., 1., 1.]),
 'Mean Validation Recall': 1.0,
 'Validation F1 scores': array([0.98832983, 0.98842438, 0.98832759, 0.98832759, 0.98832759]),
 'Mean Validation F1 Score': 0.9883473960193727,
 'Coefficients': []}

Логистическая регрессия не является переоснащением, но мы все еще не можем быть уверены из-за невыполненных поездок, охватывающих очень небольшую часть набора данных. У нас нет сопоставимых выборок по невыполненным заказам, которые составляют всего 0,023% выборки.

Результаты остальных моделей можно найти в блокноте.



Логистическая оптимизация



Время и место

  • Предположим, что день начинается в время 0 и заканчивается через T часов. В определенное время T будет транслироваться поездка t в T, где диски расположены в месте Лд.

Поездки

  • Осталось совершить T рейсов. Каждая поездка t = { 1, 2,...., T} имеет уровень приоритета 𝝅t (если применяется приоритет, если нет, уровень приоритета будет 1) и требует pt минут для завершения.
  • Каждая поездка t должна быть полностью завершена к ct времени.
  • Пусть расстояние между пунктом отправления и пунктом назначения каждой поездки будет tod.
  • Пусть место каждой поездки задано Lt = {1, 2,..., N}.

Драйверы

  • Есть K водителей, доступных для работы. Каждый водитель находится в Ok = {1, 2,...,N} для k = {1,2,...,K}.
  • Каждый водитель находится на расстоянии d от транслируемого заказа.
  • Каждый водитель может принять только 1 заказ за одну поездку t

Рассылаемые заказы

  • На каждую поездку передается b заказов b = {1, 2,....,B}, где B – общее количество доступных поблизости водителей.

Проблема компании

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

Ограничения

Извлеченные уроки и заключение

  • Создание причинно-следственных диаграмм.
  • Выведите причинно-следственный график из данных наблюдений, а затем подтвердите график.
  • Построение пайплайнов моделей и оркестровка
  • Как вы можете видеть, все 3 модели, кроме LogisticRegression, имеют точность 1, что является признаком переобучения.
  • Даже в случае модели логистической регрессии мы не можем быть на 100% уверены в том, что модель была точной, потому что у нас нет сопоставимых выборок для невыполненных заказов, которые составляют всего 0,023% выборки.
  • Однако мы можем сделать вывод, что модели ML переоснащены с большей долей.
  • Логистическая оптимизация

Планы на будущее

  • Применение обоснованных вмешательств и визуализация результатов.
  • Создайте день из жизни визуализаций водителей ZDelivery.
  • Улучшение оптимизации логистики
  • Соберите больше мнений бизнес-экспертов, чтобы создать более подробный причинно-следственный график.

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

  1. https://arxiv.org/pdf/2107.00793.pdf
  2. https://en.wikipedia.org/wiki/Causal_graph
  3. https://en.wikipedia.org/wiki/Rubin_causal_model
  4. https://arxiv.org/pdf/1803.01422.pdf
  5. https://causalnex.readthedocs.io/en/latest/04_user_guide/04_user_guide.html
  6. https://causalnex.readthedocs.io/en/latest/03_tutorial/03_tutorial.html
  7. http://web.math.ku.dk/~peters/jonas_files/scriptChapter1-4.pdf
  8. http://bayes.cs.ucla.edu/WHY/why-ch1.pdf