00:28 Введение
00:43 Роберт А. Хайнлайн
03:03 История до настоящего момента
06:52 Выбор парадигм
08:20 Статический анализ
18:26 Массивное программирование
28:08 Совместимость аппаратного обеспечения
35:38 Вероятностное программирование
40:53 Дифференцируемое программирование
55:12 Версионирование кода и данных
01:00:01 Безопасное программирование
01:05:37 В заключение: инструменты имеют значение и в цепях поставок
01:06:40 Предстоящая лекция и вопросы аудитории
Описание
В то время как основная теория управления цепями поставок с трудом утверждается в крупных компаниях, один инструмент – а именно Microsoft Excel – добился значительных успехов в эксплуатации. Повторная реализация числовых рецептов основной теории управления цепями поставок с помощью таблиц тривиальна, однако на практике, несмотря на осведомлённость о теории, это не произошло. Мы демонстрируем, что таблицы одержали победу, приняв программные парадигмы, которые оказались более эффективными для достижения результатов в цепях поставок.
Полная транскрипция
Всем привет, добро пожаловать на серию лекций по цепям поставок. Я Жоанн Верморель, и сегодня я представлю мою четвёртую лекцию: Программные парадигмы для цепей поставок.
Когда меня спрашивают: «Мистер Верморель, какие области знаний о цепях поставок представляют для вас наибольший интерес?», один из моих основных ответов обычно — программные парадигмы. И затем, не слишком часто, но достаточно часто, реакция собеседника такова: «Программные парадигмы, мистер Верморель? О чем вы, черт возьми, вообще говорите? Как это может быть хоть как-то связано с решаемой задачей?» Такие реакции, разумеется, встречаются нечасто, но когда они происходят, они неизменно напоминают мне эту невероятную цитату Роберта А. Хайнлайна, которого считают деканом писателей научной фантастики.
У Хайнлайна есть потрясающая цитата о компетентном человеке, которая подчеркивает важность профессионализма в различных областях, особенно в цепях поставок, где у нас возникают крайне сложные проблемы. Эти проблемы почти так же сложны, как сама жизнь, и я считаю, что действительно стоит уделить время изучению идеи программных парадигм, поскольку они могут принести огромную пользу вашей цепочке поставок.
До сих пор, в первой лекции мы увидели, что проблемы цепей поставок являются крайне сложными. Тот, кто говорит об оптимальных решениях, упускает суть; ничего даже близкого к оптимальности не существует. Во второй лекции я изложил Манифест количественной оптимизации цепи поставок, видение, включающее пять ключевых требований для достижения величия в управлении цепями поставок. Эти требования сами по себе недостаточны, но их нельзя обойти, если вы хотите добиться успеха.
В третьей лекции я обсуждал поставку программных продуктов в контексте оптимизации цепей поставок. Я отстаивал точку зрения, что оптимизация цепи поставок требует надлежащего подхода к программному продукту в капиталистическом ключе, но такого продукта невозможно найти на полке. Существует слишком много разнообразия, и задачи, с которыми сталкиваются, значительно превосходят возможности современных технологий. Поэтому, по необходимости, это будет нечто совершенно индивидуальное. Таким образом, если программный продукт разрабатывается специально для компании или определённой цепи поставок, встает вопрос: какие инструменты вообще подходят для его создания. Это подводит меня к сегодняшней теме: правильный инструмент начинается с правильных программных парадигм, потому что нам всё равно придётся программировать этот продукт.
До сих пор нам требуются программные возможности для решения задачи оптимизации, которые не следует путать с управленческой стороной. Как мы видели на моей предыдущей лекции, Microsoft Excel до сих пор является победителем. От очень маленьких до очень крупных компаний он вездесущ и используется повсеместно. Даже в компаниях, инвестировавших миллионы долларов в суперсовременные системы, Excel по-прежнему правит, и почему? Потому что у него есть нужные программные свойства. Он очень выразителен, гибок, доступен и поддерживаем. Однако Excel не является конечной целью. Я твёрдо верю, что мы можем сделать гораздо больше, но нам нужны правильные инструменты, мышление, инсайты и программные парадигмы.
Программные парадигмы могут показаться аудитории чересчур абстрактными, но на самом деле это область изучения, которая интенсивно исследуется последние пять десятилетий. В этой области выполнено огромное количество работ. Хотя она не широко известна широкой аудитории, существуют целые библиотеки, наполненные высококачественными трудами, выполненными множеством людей. Итак, сегодня я собираюсь представить серию из семи парадигм, принятых в Lokad. Мы не изобретали ни одну из этих идей; мы заимствовали их у тех, кто их изобрёл до нас. Все эти парадигмы были реализованы в программном продукте Lokad, и после почти десятилетия эксплуатации Lokad в производстве с использованием этих парадигм, я считаю, что они были абсолютно критичны для нашего операционного успеха.
Давайте рассмотрим этот список с точки зрения статического анализа. Проблема здесь заключается в сложности. Как справиться со сложностью в цепях поставок? Вам предстоит иметь дело с корпоративными системами, содержащими сотни таблиц, каждая из которых имеет десятки полей. Если рассматривать такую задачу, как пополнение запасов на складе, необходимо учитывать так много факторов. У вас могут быть минимальные объемы заказа, ценовые скидки, прогнозы спроса, прогнозы сроков поставки, а также всевозможные возвраты. Может возникнуть ограничение по площади витрины, ограничения по пропускной способности приёмки и сроки годности, из-за которых некоторые партии товара становятся устаревшими. В итоге приходится учитывать огромное количество нюансов. В цепях поставок идея «быстро двигаться и ломать всё» просто не подходит. Если вы по ошибке закажете товаров на миллион долларов, которые вам вовсе не нужны, это обернётся крайне дорогостоящей ошибкой. Вы не можете позволить себе, чтобы бизнес-приложение управляло вашей цепью поставок, принимая рутинные решения, а в случае ошибки стоимость могла бы достигать миллионов. Нам необходимо иметь систему с чрезвычайно высокой степенью корректности по замыслу. Мы не хотим обнаруживать ошибки уже в производстве. Это совершенно отличается от обычного программного обеспечения, где сбой — не такая уж проблема.
Когда речь заходит об оптимизации цепей поставок, это не обычная проблема. Если вы случайно передадите поставщику огромный, неверный заказ, вы не сможете просто позвонить им через неделю и сказать: «Ой, моя ошибка, забудьте, что мы это заказывали». Такие ошибки будут стоить огромных денег. Статический анализ называется так потому, что он позволяет анализировать программу без её выполнения. Идея заключается в том, что у вас есть программа, написанная с использованием операторов, ключевых слов и всего прочего, и уже без её запуска можно понять, если в ней присутствуют проблемы, которые почти наверняка негативно повлияют на ваше производство, особенно на производство в цепях поставок. Ответ — да. Эти методы действительно существуют, и они реализованы, и являются чрезвычайно ценными.
Чтобы привести пример, взгляните на скриншот Envision на экране. Envision — это предметно-ориентированный язык программирования, разработанный Lokad почти десятилетие и предназначенный для прогностической оптимизации цепей поставок. То, что вы видите, — это скриншот редактора кода Envision, веб-приложения, позволяющего редактировать код онлайн. Синтаксис этого языка сильно заимствован у Python. На этом небольшом скриншоте, состоящем всего из четырех строк, я иллюстрирую идею, что если вы пишете обширную логику для пополнения запасов на складе и вводите некоторые экономические переменные, например, ценовые скидки, то при логическом анализе программы становится очевидно, что эти ценовые скидки никак не влияют на конечные результаты, которые определяют количество для пополнения. Здесь имеется явная проблема: вы ввели важную переменную — ценовые скидки, которая, однако, не оказывает никакого влияния на конечный результат. Таким образом, возникает проблема, которую можно обнаружить с помощью статического анализа. Если в коде появляются переменные, не влияющие на выходные данные программы, они не имеют никакого смысла. В этом случае у нас два варианта: либо эти переменные являются мёртвым кодом, и программа не должна компилироваться (их следует удалить, чтобы уменьшить сложность и не накапливать избыточную сложность), либо это была реальная ошибка, и существует важная экономическая переменная, которую следовало учитывать в расчётах, но вы её упустили из-за отвлечения или по другой причине.
Статический анализ абсолютно необходим для достижения любой степени корректности по замыслу. Речь идёт о том, чтобы исправлять ошибки на этапе компиляции, когда вы пишете код, ещё до работы с данными. Если проблемы возникают при выполнении, скорее всего, они проявятся ночью, когда ночной пакет запускает пополнение запасов на складе. Программа может работать в нерабочие часы, когда за ней никто не следит, и вы не хотите, чтобы она выходила из строя, когда никого нет. Ошибка должна проявляться в момент написания кода.
Статический анализ имеет множество целей. Например, в Lokad мы используем статический анализ для визуального редактирования дашбордов. WYSIWYG означает «то, что видишь, то и получаешь». Представьте, что вы создаёте дашборд для отчётности с линейными графиками, столбчатыми диаграммами, таблицами, цветами и различными эффектами оформления. Вы хотите иметь возможность делать это визуально, а не настраивать стиль дашборда через код, поскольку это очень неудобно. Все настройки, которые вы реализовали, будут вновь внедрены в сам код, и это осуществляется с помощью статического анализа.
Ещё один аспект в Lokad, который может быть не столь важен для всей цепочки поставок, но определённо критичен для реализации проекта, заключался в работе с языком программирования под названием Envision, который мы разрабатываем. Мы знали с первого дня, почти десять лет назад, что ошибки будут совершаться. У нас не было хрустального шара, чтобы с самого начала обладать идеальным видением. Вопрос заключался в том, как обеспечить возможность исправления ошибок дизайна в самом языке программирования настолько удобно, насколько это возможно. Здесь Python стал для меня предостережением.
Python, который не является новым языком, был впервые выпущен в 1991 году, почти 30 лет назад. Миграция с Python 2 на Python 3 заняла у всего сообщества почти десятилетие, и это был настоящий кошмар, чрезвычайно болезненный процесс для компаний, участвовавших в этой миграции. Моё впечатление было таково, что у самого языка не хватало конструкций. Он не был спроектирован так, чтобы можно было легко мигрировать программы с одной версии языка на другую. Это было действительно чрезвычайно сложно сделать полностью автоматически, и всё потому, что Python не был разработан с учётом статического анализа. Когда у вас есть язык программирования для цепей поставок, вам действительно нужен тот, который обладает высоким качеством статического анализа, поскольку ваши программы будут работать долгие годы. Цепочки поставок не могут позволить себе сказать: «Подождите три месяца, мы просто переписываем код. Подождите, кавалерия уже на подходе.» Это всё равно что чинить поезд, когда он мчится по рельсам на полной скорости, и вы хотите починить двигатель, пока поезд в движении. Вот так выглядит ремонт системы цепей поставок, находящейся в эксплуатации. У вас нет роскоши просто приостановить систему; она никогда не останавливается.
Вторая парадигма — массивное программирование. Мы хотим держать сложность под контролем, так как это повторяющаяся тема в цепях поставок. Нам нужна такая логика, в которой отсутствуют определённые виды программных ошибок. Например, когда используются циклы или условные операторы, написанные явно программистами, это открывает возможность для целых классов очень трудных проблем. Становится чрезвычайно сложно, когда люди могут просто писать произвольные циклы для гарантии определённого времени расчёта. Хотя это может показаться нишевой проблемой, в оптимизации цепей поставок ситуация обстоит иначе.
На практике, предположим, что у вас есть розничная сеть. В полночь все продажи по всей сети полностью консолидируются, и данные передаются в некую систему оптимизации. Эта система имеет ровно 60-минутное окно для проведения прогнозирования, оптимизации запасов и принятия решений по перераспределению для каждого магазина сети. После завершения работы результаты передаются в систему управления складом, чтобы начали готовить все отгрузки. Грузовики, возможно, начнут загружаться в 5:00 утра, а к 9:00 магазины откроются с уже полученными и выставленными на полки товарами.
Однако, у вас очень жесткие временные рамки, и если ваши расчеты выйдут за пределы этого 60-минутного окна, вы ставите под угрозу выполнение всей цепочки поставок. Вы не хотите узнать в процессе работы, сколько времени что-то занимает. Если у вас есть циклы, в которых люди могут решить, сколько итераций они проведут, будет очень сложно доказать длительность ваших расчетов. Имейте в виду, что речь идет об оптимизации цепочки поставок. У вас нет роскоши проводить внутреннюю проверку и перепроверять всё. Иногда, из-за пандемии, некоторые страны закрываются, в то время как другие открываются весьма хаотично, обычно с уведомлением за 24 часа. Вам нужно действовать быстро.
Таким образом, программирование массивов — это идея, что вы можете работать непосредственно с массивами. Если мы посмотрим на фрагмент кода, представленный здесь, это код Envision, специального языка (DSL) компании Lokad. Чтобы понять, что происходит, нужно осознавать, что когда я пишу “orders.amounts”, это означает переменную, а “orders” фактически является таблицей в понимании реляционной базы данных, как таблица в вашей базе данных. Например, здесь, в первой строке, “amounts” будет столбцом в этой таблице. На первой строке я буквально говорю: для каждой строки таблицы orders я просто возьму значение из столбца “quantity”, умножу его на “price” и таким образом получу третий столбец, который генерируется динамически — “amount”.
Кстати, современный термин для программирования массивов ныне также называется программированием с использованием data frame. Эта область изучения достаточно древняя; её история насчитывает три-четыре десятилетия, может быть, даже четыре-пять. Раньше это называли программированием массивов, хотя сейчас люди обычно больше знакомы с понятием data frames. На второй строке мы выполняем фильтрацию, как в SQL. Мы фильтруем даты, и оказывается, что таблица orders содержит дату. Она подлежит фильтрации, и я говорю “date that is greater than today minus 365” — то есть, даты за последние 365 дней. Мы сохраняем данные за прошлый год, а затем пишем “products.soldLastYear = SUM(orders.amount)”.
Интересно, что между products и orders существует то, что мы называем естественным соединением (natural join). Почему? Потому что каждая строка заказа связана с одним и только одним товаром, а один товар может быть связан с нулем или большим числом строк заказов. В такой конфигурации вы можете прямо сказать: “Я хочу вычислить что-то на уровне продукта, что является просто суммой того, что происходит на уровне заказов”, и именно это делается на строке девять. Вы могли заметить, что синтаксис весьма минималистичен; здесь отсутствует множество случайных или технических деталей. Я бы сказал, что этот код почти полностью свободен от случайных моментов, когда речь идет о программировании data frame. Затем, на строках 10, 11 и 12, мы просто выводим таблицу на дисплей нашего дашборда, что удобно: “LIST(PRODUCTS)”, а затем “TO(products)”.
Преимуществ программирования массивов для цепочек поставок много. Во-первых, оно устраняет целые классы проблем. У вас не возникнут ошибки с индексами “off-by-one” в массивах. Параллелизация и даже распределение вычислений становится гораздо проще. Это весьма интересно, так как означает, что вы можете написать программу, которая будет выполняться не на одной локальной машине, а сразу на флоте машин в облаке, и, кстати, именно это реализовано в Lokad. Такая автоматическая параллелизация имеет чрезвычайно большое значение.
Видите ли, когда вы занимаетесь оптимизацией цепочек поставок, ваши типичные схемы использования вычислительных ресурсов крайне прерывисты. Если вернуться к примеру, который я приводил о 60-минутном окне для розничных сетей при пополнении магазинов, это означает, что в течение одного часа в сутки вам необходима вычислительная мощность для выполнения всех расчетов, а остальные 23 часа остаются практически неиспользованными. Поэтому вам нужна программа, которая, когда вы запустите ее, распределится на многих машинах, а затем, как только работа завершится, освободит все эти машины, чтобы они могли выполнять другие задачи. Альтернативой было бы держать множество машин, за которые вы платите весь день, но используете их лишь 5% времени, что крайне неэффективно.
Эта идея, позволяющая быстро и предсказуемо распределяться по множеству машин, а затем освобождать вычислительную мощность, требует облачной инфраструктуры в многоарендной конфигурации и ряда других решений, над которыми работает Lokad. Но прежде всего, это требует содействия со стороны самого языка программирования. Это невозможно осуществить на обычном универсальном языке программирования, таком как Python, потому что сам язык не настроен на такой умный и эффективный подход. Это не просто набор трюков; речь идет буквально о том, чтобы уменьшить затраты на IT-оборудование в 20 раз, значительно ускорить выполнение расчетов и устранить целые классы потенциальных ошибок в вашей цепочке поставок. Это меняет правила игры.
Программирование массивов уже существует во многих аспектах, например, в NumPy и pandas для Python, которые так популярны среди data scientist. Но вопрос, который я хочу вам задать, заключается в следующем: если это настолько важно и полезно, почему же эти инструменты не являются гражданами первого класса самого языка? Если всё, что вы делаете, заключается в использовании NumPy, то NumPy должен становиться гражданином первого класса. Я бы сказал, что можно пойти дальше, чем NumPy. NumPy — это в основном программирование массивов на одной машине, но почему бы не реализовать программирование массивов на флоте машин? Это намного мощнее и более адекватно, когда у вас есть облако с доступными вычислительными ресурсами.
Итак, что станет узким местом в оптимизации цепочек поставок? Существует поговорка Голдрэта: “Любое улучшение, достигнутое за пределами узкого места в цепочке поставок, — иллюзия”, и я полностью согласен с этим утверждением. Реалистично говоря, когда речь заходит об оптимизации цепочек поставок, узким местом будут люди, а точнее, специалисты по цепочным поставкам, которых, к сожалению для Lokad и моих клиентов, не найти за углом.
Узкое место — это именно специалисты по цепочкам поставок, те люди, которые умеют создавать числовые рецепты, учитывающие все стратегии компании, поведение конкурентов и превращающие всю эту разведданную информацию в нечто механическое, что можно масштабировать. Трагедия наивного подхода к науке о данных, когда я начинал свой PhD (который, кстати, так и не завершился), заключалась в том, что я видел, как все в лаборатории буквально занимались наукой о данных. Большинство людей писали код для какой-то продвинутой модели машинного обучения, нажимали Enter и начинали ждать. Если у вас большой набор данных, скажем, 5-10 гигабайт, это не работает в реальном времени. В результате лаборатория была наполнена людьми, которые писали несколько строк кода, нажимали Enter, а затем уходили за чашкой кофе или просматривали что-то онлайн. Продуктивность была чрезвычайно низкой.
Когда я основал собственную компанию, я понимал, что не хочу в итоге платить за целую армию суперумных людей, которые проводят большую часть дня, попивая кофе и ожидая завершения программ, чтобы получить результаты и двигаться дальше. Теоретически они могут параллельно запускать множество задач и проводить эксперименты, но на практике я такого никогда не наблюдал. Когда вы заняты поиском решения проблемы, вы хотите проверить свою гипотезу и получить результат, чтобы продолжить работу. Очень сложно заниматься серьезными техническими вопросами, пытаясь одновременно следовать нескольким интеллектуальным направлениям.
Однако была и положительная сторона. Специалисты по данным, а теперь и специалисты по цепочкам поставок в Lokad, не пишут тысячу строк кода и затем говорят: “пожалуйста, запустите”. Обычно они добавляют две строки к скрипту, который уже насчитывает тысячу строк, и просят запустить скрипт. Этот скрипт тестируется на тех же данных, что и несколько минут назад. Логика почти идентична, за исключением этих двух строк. Так как же можно обрабатывать терабайты данных за считанные секунды, а не за несколько минут? Ответ прост: если при предыдущем запуске скрипта вы зафиксировали все промежуточные этапы вычислений и сохранили их на накопитель (обычно это твердотельные накопители — SSD), которые очень дешевы, быстры и удобны.
В следующий раз, когда вы запустите программу, система заметит, что скрипт почти тот же самый. Она выполнит сравнение (diff) и увидит, что с точки зрения вычислительного графа он почти идентичен, за исключением нескольких мелких моментов. Что касается данных, они обычно совпадают на 100%. Иногда вносятся незначительные изменения, но почти ничего. Система автоматически определит, что требуется вычислить лишь несколько вещей, и вы получите результат за считанные секунды. Это может значительно улучшить продуктивность вашего специалиста по цепочкам поставок. Вы можете перейти от людей, которые нажимают Enter и ждут 20 минут, к тем, кто нажимает Enter и получает результат через 5 или 10 секунд, чтобы двигаться дальше.
Я говорю о том, что может показаться крайне незначительным, но на деле имеет 10-кратный эффект на продуктивность. Это огромно. Итак, мы используем хитрый трюк, который Lokad не изобретал. Мы заменяем один сырой вычислительный ресурс — вычислительную мощность — на другой: память и хранилище. У нас есть базовые вычислительные ресурсы: вычисления, память (как оперативная, так и постоянная) и пропускная способность. Это основные ресурсы, за которые вы платите при аренде вычислительной мощности в облаке. Вы можете действительно заменить один ресурс другим, а цель — получить наибольшую отдачу за ваши деньги.
Когда люди говорят, что вам следует использовать вычисления в оперативной памяти, я бы сказал, что это ерунда. Если вы говорите “вычисления в памяти”, это означает, что вы делаете акцент на одном ресурсе за счет остальных. Но нет, здесь есть компромиссы, и интересное заключается в том, что можно иметь язык программирования и среду, делающие реализацию этих компромиссов и взглядов проще. В обычном языке общего назначения это возможно, но его нужно реализовывать вручную. Это означает, что человек, занимающийся этим, должен быть профессиональным программистом. Специалист по цепочкам поставок не будет выполнять эти низкоуровневые операции с базовыми вычислительными ресурсами вашей платформы. Это должно быть реализовано на уровне самого языка программирования.
Теперь давайте поговорим о вероятностном программировании. Во второй лекции, где я представил видение количественной цепочки поставок, моим первым требованием было рассмотреть все возможные будущие. Технический ответ на это требование — probabilistic forecasting. Вы работаете с будущим, где присутствуют вероятности. Все варианты будущего возможны, но они не все равновероятны. Вам нужна алгебра, позволяющая проводить вычисления с неопределенностью. Одну из главных своих критик в адрес Excel я приписываю тому, что в нем чрезвычайно сложно представить неопределенность в электронной таблице, независимо от того, используете ли вы Excel или какой-либо современный облачный аналог.
В этом небольшом фрагменте я иллюстрирую алгебру случайных величин, которая является встроенной функцией Envision. На первой строке я генерирую дискретное распределение Пуассона со средним значением 2 и сохраняю его в переменной X. Затем я делаю то же самое для другого распределения Пуассона, Y. После этого я вычисляю Z как произведение X и Y. Эта операция, умножение случайных величин, может показаться весьма эксцентричной. Зачем вообще нужна такая операция с точки зрения цепочки поставок? Позвольте привести пример.
Предположим, вы работаете на рынке послепродажного обслуживания в автомобильной промышленности и продаете тормозные колодки. Люди не покупают тормозные колодки поштучно; они покупают либо две, либо четыре. Таким образом, если вы хотите составить прогноз, вам, возможно, придется предсказать вероятности того, что клиенты придут купить определенный тип тормозных колодок. Это будет ваша первая случайная величина, задающая вероятность наблюдения нуля, одного, двух, трех, четырех и так далее единиц спроса на тормозные колодки. Затем у вас будет другое распределение, отражающее, покупают ли люди две или четыре тормозные колодки. Возможно, соотношение будет 50–50, или, может быть, 10 процентов покупают две и 90 процентов — четыре. Дело в том, что у вас есть эти два аспекта, и если вы хотите узнать фактическое общее потребление тормозных колодок, вам нужно умножить вероятность того, что клиент придет, на распределение вероятностей покупки либо двух, либо четырех. Таким образом, вам требуется перемножение этих двух неопределенных величин.
Здесь я предполагаю, что две случайные величины независимы. Кстати, умножение случайных величин в математике известно как дискретная свертка. Вы можете увидеть на скриншоте дашборд, сгенерированный Envision. В первых трех строках я провожу вычисления по случайной алгебре, а затем, на строках четыре, пять и шесть, вывожу результаты на веб-страницу — дашборд, созданный скриптом. Я отображаю значения A1, B2, например, как в сетке Excel. Дашборды Lokad организованы аналогично таблицам Excel, с колонками B, C и так далее, и строками 1, 2, 3, 4, 5.
Вы можете заметить, что дискретная свертка Z имеет весьма странный, крайне заостренный вид, который очень часто встречается в цепочках поставок, когда люди покупают упаковки, лоты или несколько единиц сразу. В такой ситуации обычно лучше декомпозировать источники событий, связанных с лотом или упаковкой. Вам нужен язык программирования, который предоставляет такие возможности как функции первого класса. Именно об этом и говорит вероятностное программирование, и именно так мы реализовали его в Envision.
Итак, давайте обсудим дифференцируемое программирование. Я должен сделать оговорку: я не ожидаю, что аудитория действительно поймёт, что происходит, и приношу за это извинения. Дело не в том, что вам не хватает интеллекта, а в том, что эта тема заслуживает целой серии лекций. Фактически, если вы посмотрите план предстоящих лекций, то увидите целую серию, посвящённую дифференцируемому программированию. Я собираюсь идти очень быстро, и это будет довольно загадочно, так что прошу прощения заранее.
Перейдём к проблеме цепочки поставок, которая нас здесь интересует, а именно к каннибализации и замещению. Эти проблемы очень интересны, и именно здесь прогнозирование временных рядов — повсеместное явление — терпит самые жестокие неудачи. Почему? Потому что часто ко мне обращаются клиенты или потенциальные заказчики с вопросом, можем ли мы, например, сделать прогноз за 13 недель вперёд для определённых товаров, таких как рюкзаки. Я отвечу, что да, можем, но, очевидно, если мы берём один рюкзак и хотим спрогнозировать спрос на этот товар, то он в значительной мере зависит от того, что происходит с вашими другими рюкзаками. Если у вас только один рюкзак, возможно, весь спрос на рюкзаки сконцентрируется на этом единственном продукте. Если же вы представите 10 различных вариантов, то, конечно, произойдёт масса каннибализации. Вы не умножите общий объём продаж в 10 раз только потому, что увеличили число позиций в 10 раз. Так что, очевидно, происходит каннибализация и замещение. Эти явления широко распространены в цепочках поставок.
Как анализировать каннибализацию или замещение? Метод, который мы используем в Lokad, и я не утверждаю, что это единственный способ, но он определённо работает, — это, как правило, анализ графа, который соединяет клиентов и продукты. Почему так? Потому что каннибализация происходит, когда продукты конкурируют друг с другом за одних и тех же клиентов. Каннибализация — это буквально отражение того, что у клиента есть потребность, но у него есть предпочтения, и он выбирает один продукт из набора товаров, соответствующих его вкусам, выбирая только один. Вот суть каннибализации.
Если вы хотите это проанализировать, вам нужно изучить не временные ряды продаж, потому что эта информация изначально не отражается. Вам нужно анализировать граф, который соединяет исторические транзакции между клиентами и продуктами. Оказывается, что в большинстве бизнесов эти данные легко доступны. Для электронной коммерции это само собой разумеется. Каждую единицу, которую вы продаёте, можно отследить по клиенту. В B2B — то же самое. Даже в B2C в розничной торговле в наши дни большинство сетей имеют программы лояльности, благодаря которым известно двузначное число клиентов, приходящих с их картами, так что вы знаете, кто что покупает. Не для 100% трафика, но это не так важно. Если у вас 10% и более исторических транзакций, где известны пары клиент-продукт, этого достаточно для такого анализа.
В этом относительно небольшом примере я подробно расскажу об анализе аффинности между клиентами и продуктами. Это буквально базовый шаг, который необходимо сделать для проведения любого анализа каннибализации. Давайте посмотрим, что происходит в этом коде.
Со строк с первой по пятую всё очень обыденно; я просто читаю плоский файл, содержащий историю транзакций. Я читаю CSV-файл, который имеет четыре столбца: дату, продукт, клиента и количество. Что-то совершенно базовое. Я даже не использую все эти столбцы, а делаю пример чуть более конкретным. В истории транзакций я предполагаю, что все клиенты известны. Так что, всё очень просто; я просто считываю данные из таблицы.
Затем, в строках семь и восемь, я просто создаю таблицы для продуктов и клиентов. В реальной производственной среде, как правило, я не создавал бы эти таблицы, а считывал их из других плоских файлов. Я хотел сделать пример максимально простым, поэтому просто извлекаю таблицу продуктов из наблюдаемых в истории транзакций товаров, и делаю то же самое для клиентов. Видите, это просто трюк для сохранения простоты.
Теперь, строки 10, 11 и 12 касаются латинских пространств, и это станет немного более запутанным. Сначала, в строке 10, я создаю таблицу из 64 строк. Эта таблица ничего не содержит; она определяется лишь фактом наличия 64 строк, и всё. Это как заполнитель, тривиальная таблица с множеством строк и без столбцов. Просто так она не очень полезна. Затем «P» — это по сути декартово произведение, математическая операция, создающая все пары. Это таблица, в которой есть одна строка для каждой строки таблицы продуктов и каждой строки таблицы «L». Таким образом, эта таблица «P» имеет на 64 строки больше, чем таблица продуктов, и то же самое я делаю для клиентов. Я просто расширяю эти таблицы с помощью этого дополнительного измерения, которым является таблица «L».
Это станет моей опорой для моих латинских пространств, именно то, чему я собираюсь научиться. Я хочу научиться определять для каждого продукта латинское пространство — вектор из 64 значений, и для каждого клиента — латинское пространство из 64 значений. Если я хочу узнать аффинность между клиентом и продуктом, мне просто нужно вычислить скалярное произведение между ними. Скалярное произведение — это покомпонентное умножение всех элементов этих двух векторов, за которым следует их суммирование. Это может звучать очень технически, но на самом деле это просто покомпонентное умножение плюс суммирование — вот и всё.
Эти латинские пространства — просто модный жаргон для обозначения пространства с параметрами, которые несколько выдуманы, именно того, чему я хочу научиться. Вся магия дифференцируемого программирования происходит всего в пяти строках, с 14 по 18. У меня есть ключевое слово “autodiff” и “transactions”, которое указывает, что это таблица наблюдений, таблица интересных данных. Я буду обрабатывать эту таблицу построчно, чтобы реализовать процесс обучения. В этом блоке я объявляю набор параметров. Параметры — это те величины, которые вы хотите изучить, как числа, но вы пока не знаете их значений. Они будут инициализированы случайным образом, с помощью случайных чисел.
Я ввожу переменные “X”, “X*” и “Y”. Я не буду вдаваться в подробности того, что именно делает “X*”; возможно, об этом будет речь в вопросах. Я возвращаю выражение, которое является моей функцией потерь, то есть суммой. Суть коллаборативной фильтрации или разложения матрицы заключается в том, что вы хотите обучить латинские пространства, которые соответствуют всем связям в вашем двудольном графе. Я знаю, что это звучит немного технически, но с точки зрения цепочки поставок, мы делаем нечто очень простое. Мы изучаем аффинность между продуктами и клиентами.
Я знаю, что это может показаться совершенно непонятным, но оставайтесь со мной, и будут ещё лекции, где я дам более вдумчивое введение в эту тему. Всё это сделано в пяти строках, и это поистине замечательно. Когда я говорю о пяти строках, я не обманываю, говоря: “Смотрите, всего пять строк, но на самом деле я вызываю стороннюю библиотеку колоссальной сложности, где скрыта вся логика.” Нет, нет, нет. Здесь, в этом примере, нет никакой магии машинного обучения, кроме двух ключевых слов “autodiff” и “params”. “Autodiff” используется для определения блока, в котором происходит дифференцируемое программирование, и, кстати, это блок, в котором я могу программировать что угодно, то есть я буквально могу внедрить нашу программу. Затем я использую “params” для объявления моих параметров, и на этом всё. Видите, никакой загадочной магии нет; нет библиотеки на миллион строк, выполняющей всю работу в фоновом режиме. Всё, что вам нужно знать, буквально представлено на этом экране, и вот в чём разница между парадигмой программирования и библиотекой. Парадигма программирования даёт доступ к, казалось бы, невероятно сложным возможностям, таким как проведение анализа каннибализации с помощью всего нескольких строк кода, без прибегания к массивным сторонним библиотекам, инкапсулирующим сложность. Это превосходит проблему, делая её намного проще, так что можно решить, казалось бы, сверхсложную задачу, всего в нескольких строках.
Теперь несколько слов о том, как работает дифференцируемое программирование. Существует два подхода. Один из них — автоматическое дифференцирование. Для тех из вас, кто имел удовольствие получать инженерное образование, известно два способа вычисления производных. Существует символическая производная: например, если у вас есть x в квадрате, вы берёте производную по x, и получается 2x. Это символическая производная. Затем существует численная производная: если у вас есть функция f(x), которую вы хотите дифференцировать, то f’(x) ≈ (f(x + ε) - f(x))/ε. Это численное дифференцирование. Ни один из этих методов не подходит для того, что мы пытаемся сделать здесь. Символическое дифференцирование усложняется, так как ваша производная может оказаться программой, гораздо более сложной, чем исходная программа. Численное дифференцирование является численно нестабильным, поэтому у вас будет масса проблем с численной стабильностью.
Автоматическое дифференцирование — это фантастическая идея, возникшая в 70-х годах и заново открытая мировым сообществом за последнее десятилетие. Это идея о том, что можно вычислить производную произвольной компьютерной программы, что просто поражает воображение. Ещё более поразительно то, что программа, являющаяся производной, имеет ту же вычислительную сложность, что и исходная программа, что удивительно. Дифференцируемое программирование — это всего лишь сочетание автоматического дифференцирования и параметров, которые вы хотите изучить.
Так как же происходит обучение? Когда у вас появляется производная, это означает, что можно передавать градиенты назад, и с помощью стохастического градиентного спуска можно вносить небольшие корректировки в значения параметров. Постепенно, через многие итерации стохастического градиентного спуска, посредством настройки этих параметров вы придёте к значениям, которые имеют смысл и достигают того, чему вы хотите научиться или оптимизировать.
Дифференцируемое программирование может использоваться для задач обучения, как в примере, который я привожу, когда я хочу изучить аффинность между моими клиентами и продуктами. Оно также может применяться для задач численной оптимизации, таких как оптимизация с учётом ограничений, и является весьма масштабируемой парадигмой. Как видите, этот аспект был придан первостепенное значение в Envision. Кстати, в плане синтаксиса Envision ещё остаётся несколько доработок, так что не ожидайте, что всё будет именно так; мы всё ещё уточняем некоторые моменты. Но суть остаётся. Я не собираюсь обсуждать мелкие детали тех аспектов, которые ещё эволюционируют.
Теперь перейдём к другой проблеме, связанной с готовностью вашего производства. Обычно в оптимизации цепочек поставок вы сталкиваетесь с Heisenbug’ами. Что такое Heisenbug? Это досадная ошибка, когда происходит оптимизация, и она даёт бесполезные результаты. Например, у вас происходил пакетный расчёт для пополнения запасов ночью, а утром вы обнаруживаете, что некоторые из этих результатов были абсурдны, что привело к дорогостоящим ошибкам. Вы не хотите, чтобы проблема повторялась, поэтому перезапускаете процесс. Однако при повторном запуске проблема исчезает. Вы не можете воспроизвести ошибку, и Heisenbug не проявляется.
Это может показаться странным исключением, но в первые несколько лет Lokad мы неоднократно сталкивались с такими проблемами. Я видел, как многие инициативы в области управления цепочками поставок, особенно в сфере науки о данных, проваливались из-за неустранённых Heisenbug’ов. Ошибки возникали в производстве, их пытались воспроизвести локально, но не удавалось, поэтому проблемы так и оставались нерешёнными. После пары месяцев в режиме паники весь проект обычно тихо закрывался, и люди возвращались к использованию Excel-таблиц.
Если вы хотите добиться полной воспроизводимости вашей логики, вам нужно версионировать и код, и данные. Большинство из присутствующих, кто является разработчиками или специалистами по данным, наверняка знакомы с идеей версионирования кода. Однако вы также хотите версионировать все данные, чтобы при выполнении вашей программы вы точно знали, какая версия кода и данных используется. Возможно, вы не сможете воспроизвести проблему на следующий день, потому что данные изменились из-за новых транзакций или иных факторов, поэтому условия, приведшие к ошибке изначально, уже отсутствуют.
Вы хотите быть уверены, что ваша программная среда может точно воспроизвести логику и данные так, как они были в производстве в определённый момент времени. Это требует полного версионирования всего. Опять же, для этого язык программирования и стек должны сотрудничать. Это достижимо даже без того, чтобы парадигма программирования была центральной в вашем стеке, но тогда специалист по цепочкам поставок должен быть чрезвычайно внимателен ко всем своим действиям и способам программирования. В противном случае он не сможет воспроизвести свои результаты. Это накладывает огромное давление на плечи специалистов по цепочкам поставок, которые и так находятся под значительным давлением со стороны самих цепочек поставок. Вы не хотите, чтобы эти профессионалы сталкивались со случайной сложностью, например, с невозможностью воспроизвести свои собственные результаты. В Lokad мы называем это “машиной времени”, где можно воспроизвести всё в любой точке прошлого.
Будьте осторожны, дело не только в том, чтобы воспроизвести то, что произошло прошлой ночью. Иногда вы обнаруживаете ошибку намного позже. Например, если вы размещаете заказ у поставщика с трёхмесячным сроком исполнения, то через три месяца можете обнаружить, что заказ был бессмысленным. Вам нужно вернуться назад на три месяца, к тому моменту, когда вы создали этот фиктивный заказ, чтобы понять, в чём заключалась проблема. Речь идёт не только о версионировании нескольких последних часов работы; на самом деле, это подразумевает наличие полной истории исполнения за последний год.
Ещё одна проблема — рост числа программ-вымогателей и кибератак на цепочки поставок. Эти атаки наносят колоссальные разрушения и могут быть очень дорогостоящими. При реализации программных решений необходимо учитывать, не делаете ли вы свою компанию и цепочку поставок более уязвимыми к кибератакам и другим рискам. С этой точки зрения Excel и Python не являются идеальными. Эти компоненты программируемы, что означает, что они могут содержать множество уязвимостей безопасности.
Если у вас есть команда специалистов по данным или специалистов по цепочке поставок, занимающихся задачами оптимизации цепочки поставок, они не могут позволить себе тщательный, итеративный процесс рецензирования кода, который является обычной практикой в индустрии программного обеспечения. Если тариф изменится за одну ночь или склад затопят, вам нужен быстрый отклик. Вы не можете тратить недели на составление спецификаций кода, его рецензирование и тому подобное. Проблема в том, что вы предоставляете возможности программирования людям, которые по своей сути могут нанести компании вред случайно. Это может быть еще хуже, если среди сотрудников окажется умышленно вредоносный человек, но даже если оставить это в стороне, у вас все равно остается риск случайного раскрытия внутренней части ИТ-систем. Помните, что системы оптимизации цепочки поставок по определению имеют доступ к огромному объему данных по всей компании. Эти данные являются не только активом, но и потенциальным пассивом.
То, что вам нужно — это парадигма программирования, способствующая безопасному программированию. Вам нужен такой язык программирования, в котором существует целый класс действий, которые вы не можете выполнить. Например, зачем нужен язык, способный выполнять системные вызовы для оптимизации цепочки поставок? Python может выполнять системные вызовы, как и Excel. Но зачем вообще вам нужна программируемая система с такими возможностями? Это как купить оружие, чтобы затем самому себе навредить.
Вам нужно нечто, где отсутствуют целые классы или функции, потому что они не требуются для оптимизации цепочки поставок. Если такие функции присутствуют, они становятся огромным пассивом. Если вы вводите возможности программирования без инструментов, обеспечивающих безопасное программирование по своей сути, вы увеличиваете риск кибератак и программ-вымогателей, что только усугубляет ситуацию.
Конечно, всегда можно компенсировать это увеличением численности команды по кибербезопасности, но это очень затратно и не идеально в условиях срочных задач в цепочке поставок. Вам нужно действовать быстро и безопасно, без времени на стандартные процессы, проверки и утверждения. Вам также нужно безопасное программирование, которое исключает рутинные проблемы, такие как ошибки null-ссылки, исчерпание памяти, неправильные циклы и побочные эффекты.
В заключение, инструментарий имеет значение. Существует поговорка: “Не бери меч на перестрелку с оружием”. Вам нужны правильные инструменты и парадигмы программирования, а не просто те, которые вы изучали в университете. Вам нужно что-то профессиональное и пригодное для производства, чтобы удовлетворить потребности вашей цепочки поставок. Хотя вы можете добиться некоторых результатов с посредственными инструментами, они не будут выдающимися. Фантастический музыкант может создать музыку, используя всего лишь ложку, но с правильным инструментом он сможет добиться гораздо большего.
Теперь перейдем к вопросам. Обратите внимание, что задержка составляет около 20 секунд, поэтому между видеотрансляцией и моим чтением ваших вопросов наблюдается небольшая задержка.
Вопрос: Что насчет динамического программирования с точки зрения операционных исследований?
Динамическое программирование, несмотря на название, не является парадигмой программирования. Это скорее алгоритмическая техника. Суть в том, что если вы хотите выполнить алгоритмическую задачу или решить определённую проблему, вам часто приходится повторять одну и ту же подоперацию. Динамическое программирование — это конкретный пример обмена между пространством и временем, о котором я упоминал ранее: вы вкладываете немного больше в память, чтобы сэкономить время на вычислениях. Это одна из самых ранних алгоритмических техник, зародившаяся в 60-70-х годах. Она хороша, но название несколько неудачное, поскольку в ней нет ничего по-настоящему динамичного, и она вовсе не про программирование. Для меня, несмотря на название, это не парадигма программирования, а скорее специфическая алгоритмическая техника.
Вопрос: Йоханнес, не могли бы вы порекомендовать несколько справочных книг, которые должен иметь каждый хороший инженер по цепочке поставок? К сожалению, я новичок в этой области, и моя текущая специализация — наука о данных и системная инженерия.
У меня сложилось весьма противоречивое мнение относительно существующей литературы. В своей первой лекции я представил две книги, которые, по моему мнению, являются вершиной академических исследований в области цепочки поставок. Если вам хочется прочитать две книги, можете взять их. Однако у меня постоянно возникают проблемы с книгами, которые я читал ранее. По сути, одни авторы представляют собой коллекции игрушечных числовых рецептов для идеализированных цепочек поставок, и я считаю, что такие книги не рассматривают цепочку поставок под правильным углом, полностью упуская из виду, что это – запутанная проблема. Существует обширная техническая литература с уравнениями, алгоритмами, теоремами и доказательствами, но, на мой взгляд, она полностью теряет суть вопроса.
Затем есть другой стиль книг по управлению цепочками поставок, который больше характерен для консультантов. Эти книги легко узнать по тому, что они используют спортивные аналогии каждые две страницы. В таких книгах можно встретить всевозможные упрощенные схемы, вроде вариантов SWOT-анализа (сильные стороны, слабости, возможности, угрозы) в формате 2x2, которые я считаю некачественными методами рассуждения. Проблема этих книг в том, что они, как правило, лучше понимают, что управление цепочками поставок – это крайне сложное предприятие. Они гораздо лучше осознают, что это игра, в которой участвуют люди, и в которой могут происходить самые странные вещи, требующие смекалки. Я отдаю им за это должное. Но проблема этих книг, часто написанных консультантами или профессорами управленческих школ, заключается в том, что они не дают конкретных практических рекомендаций. Основное их послание сводится к “станьте лучшим лидером”, “будьте умнее”, “имейте больше энергии”, и для меня это не приводит к конкретным действиям, которые могли бы превратиться во что-то действительно ценное, как программное обеспечение.
Итак, я возвращаюсь к первой лекции: читайте эти две книги, если хотите, но я не уверен, что это время потрачено с пользой. Хорошо знать, что писали другие. Что касается книг в стиле консультантов, моим фаворитом, вероятно, является работа Катаны, которую я не упоминал в первой лекции. Не всё так плохо; некоторые авторы действительно обладают талантом, даже если их стиль более консультативный. Вы можете ознакомиться с работой Катаны; у него есть книга о динамических цепочках поставок. Я приведу эту книгу в списке литературы.
Вопрос: Как вы осуществляете параллелизацию при решении задач каннибализации или выборе ассортимента, если проблему не так просто распараллелить?
Почему её не так легко распараллелить? Стохастический градиентный спуск довольно просто параллелизировать. Вы можете выполнять стохастические шаги градиентного спуска в случайном порядке, и их можно выполнять одновременно. Так что я считаю, что всё, что основано на стохастическом градиентном спуске, довольно просто распараллеливается.
При решении задачи каннибализации гораздо сложнее справиться с другим видом параллелизации, а именно с определением порядка выполнения. Если я запущу один продукт первым и на его основе составлю прогноз, а затем добавлю другой продукт, это изменит общую картину. Ответ кроется в том, чтобы рассматривать всю картину одновременно. Вы не говорите: “Сначала я представляю этот продукт и делаю прогноз; затем представляю другой продукт и переделываю прогноз, корректируя первый”. Вы делаете всё одновременно, все элементы вместе. Вам нужны дополнительные парадигмы программирования. Те парадигмы, которые я представил сегодня, могут в этом значительно помочь.
Когда речь заходит об ассортименте, такого рода проблемы не создают значительных трудностей для параллелизации. То же самое касается и мировых розничных сетей, где вы хотите оптимизировать ассортимент для всех магазинов одновременно. Вы можете проводить расчеты для всех магазинов параллельно. Последовательная оптимизация, когда вы сначала оптимизируете ассортимент для одного магазина, а затем переходите к следующему, — это неверный подход. Лучше оптимизировать сеть параллельно, передав всю информацию, а затем повторить процесс. Существует множество техник, и инструментарий может значительно упростить этот процесс.
Вопрос: Используете ли вы подход с графовыми базами данных?
Нет, не в техническом, каноническом смысле. На рынке существует множество графовых баз данных, которые представляют большой интерес. Но то, что мы используем внутри Lokad, представляет собой полную вертикальную интеграцию через единый, монолитный компиляторский стек, который полностью устраняет все традиционные элементы, присутствующие в классическом стеке. Именно так мы достигаем очень высокой вычислительной производительности, почти на уровне железа. Не потому, что мы фантастически умные программисты, а потому, что устранили практически все традиционные слои. Lokad буквально не использует базу данных вообще. У нас есть компилятор, который берет на себя всё, вплоть до организации структур данных для постоянного хранения. Это несколько странно, но работает гораздо эффективнее, и таким образом вы лучше используете тот факт, что компилируете скрипт для целого парка машин в облаке. Ваша целевая платформа с точки зрения оборудования — это не одна машина, а целый кластер.
Вопрос: Каково ваше мнение о Power BI, который также запускает Python-коды и связанные алгоритмы, такие как градиентный спуск, жадные алгоритмы и т.д.?
Проблема, которая у меня возникает с любыми решениями, связанными с бизнес-аналитикой, в числе которых и Power BI, заключается в том, что у него есть парадигма, которую я считаю неподходящей для цепочки поставок. Вы видите все проблемы как гиперкуб, где есть измерения, которые можно только нарезать и перемешивать. В основе лежит проблема выразительности, которая крайне ограничена. Когда вы используете Power BI с Python, потому что возможности выражения гиперкуба очень слабы, чтобы восстановить выразительность, вы добавляете Python внутрь. Однако, помните, что я уже упоминал о слоях: проклятием современного корпоративного программного обеспечения является наличие слишком большого количества слоев. Каждый добавленный слой приводит к неэффективности и ошибкам. Если вы используете Power BI в сочетании с Python, у вас получится слишком много уровней. Таким образом, сначала у вас есть Power BI, который работает поверх других систем, то есть у вас уже присутствует несколько систем до его внедрения. Затем работает Power BI, а поверх него — Python. Но действует ли Python самостоятельно? Скорее всего, вы будете использовать библиотеки Python, такие как Pandas или NumPy. В итоге у вас формируется множество слоев внутри Python, и их может оказаться десятки. Ошибки могут возникнуть на любом из этих уровней, так что ситуация обернется настоящим кошмаром.
Я не верю в решения, при которых в итоге формируется огромное количество слоев. Существует шутка, что в C++ любую проблему можно решить, добавив еще один уровень косвенности, включая проблему слишком большого количества уровней косвенности. Конечно, это утверждение несколько нелепо, но я глубоко не согласен с подходом, при котором у продукта неадекватное базовое проектирование, и вместо того чтобы бороться с проблемой непосредственно, накладываются дополнительные слои, в то время как фундамент остается шатким. Это не путь к успеху, и у вас получится низкая производительность, постоянные проблемы с ошибками, которые никогда не будут решены, а с точки зрения поддержки — просто рецепт для кошмара.
Вопрос: Как можно внедрить результаты анализа коллаборативной фильтрации в алгоритм прогнозирования спроса для каждого продукта, например, для рюкзаков?
Извините, но эту тему я рассмотрю на следующей лекции. Краткий ответ — не стоит интегрировать их в существующий алгоритм прогнозирования. Вам нужно создать решение, которое будет гораздо более органично интегрировано. Вы не объединяете их и не возвращаетесь к старым методам прогнозирования; вместо этого вы просто отказываетесь от старого подхода и переходите к чему-то радикально новому, что использует эти данные. Но об этом я расскажу на последующих лекциях. На сегодня это было бы слишком.
Полагаю, на этом лекция заканчивается. Большое спасибо всем за внимание. Следующая лекция состоится в среду, 6 января, в то же время, в тот же день недели. Я беру рождественские каникулы, так что желаю всем счастливого Рождества и Нового года. Мы продолжим нашу серию лекций в следующем году. Большое спасибо.