Оптимизация логистики: оптимизация местоположения водителей доставки с помощью причинно-следственной связи
Клиентом этой недели является 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.
- Улучшение оптимизации логистики
- Соберите больше мнений бизнес-экспертов, чтобы создать более подробный причинно-следственный график.
Рекомендации
- https://arxiv.org/pdf/2107.00793.pdf
- https://en.wikipedia.org/wiki/Causal_graph
- https://en.wikipedia.org/wiki/Rubin_causal_model
- https://arxiv.org/pdf/1803.01422.pdf
- https://causalnex.readthedocs.io/en/latest/04_user_guide/04_user_guide.html
- https://causalnex.readthedocs.io/en/latest/03_tutorial/03_tutorial.html
- http://web.math.ku.dk/~peters/jonas_files/scriptChapter1-4.pdf
- http://bayes.cs.ucla.edu/WHY/why-ch1.pdf