В бизнесе именно неопределенность стоит у истоков всех проблем по оптимизации, данные же расположились у истоков решений всех этих проблем. В эпоху расцвета анализа данных и искусственного интеллекта это уже не новость, а прописная истина. Тем не менее существует целый ряд проблем, где данных либо крайне мало, либо нет вовсе. Когда речь заходит о системах управления доходностью (RMS), всегда подразумевается B2C, где много клиентов, а значит должно быть много данных. А как при этом быть с B2B, где количество клиентов за год может запросто исчисляться десятками или даже единицами?

Давайте представим обычного путешественника: для него существует только B2C — он платит и получает услугу. Отрасль путешествий представляет собой целую экосистему компаний: авиа и железнодорожные компании, каршеринг, туристические агентства, отели, IT-компании, дизайн-студии и десятки других участников, занятых самыми разными аспектами отрасли. Путешественник видит контент о своем предстоящем пути на своем телефоне благодаря слаженной работе целого ряда компаний, которым удалось наладить отношения, заключить контракты и договоры друг с другом. Можно смело заявить, что экосистемы компаний, где применение RMS является общепринятой практикой, в целом будут гораздо эффективнее остальных. Тем не менее для целого ряда компаний, особенно в секторе B2B, это все еще остается серьезной проблемой.

Необходим универсальный принцип для разработки любых RMS — и таким принципом является теорема Байеса. Теорема Байеса обладает большим потенциалом и помогает более глубоко понять целый ряд проблем и угроз, которые не связаны с управлением доходностью напрямую, но напрямую влияют на успешность и жизнеспособность бизнеса.

История развития RM

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

Формально управление доходностью как дисциплина возникла в 1972 году, когда авиакомпания British Overseas Airways начала экспериментировать с дифференциальными тарифами на одни и те же места: просто начала предлагать скидки, чтобы стимулировать спрос на места, которые в противном случае летали бы пустыми. При этом по-настоящему на важность этой дисциплины обратили внимание после событий, связанных с авиакомпанией American Airlines.

К середине 1970-х Совет гражданской авиации США позволил чартерным авиакомпаниям оказывать свои услуги на квазирегулярной основе — по тарифам, которые оказались намного дешевле тарифов действительно регулярных авиакомпаний. Новые реалии обернулись не просто жесткой конкуренцией, а самой настоящей угрозой существованию для множества компаний.

В 1976 году старший вице-президент American по маркетингу Боб Крэндалл созвал экстренный совет. Начался мозговой штурм, с целью понять, что необходимо сделать, чтобы компании кардинальным образом снизить издержки и наконец начать конкурировать с новыми игроками. Кто-то заметил (о,сюрприз!), что их самолеты летают полупустыми — миллионы пустых кресел, ежегодно. На утро после этого пришло полное осознание того, что проблема дохода у компании намного серьезнее проблемы затрат.

Первоначальное решение, представленное год спустя, называлось "Super Saver" (Супер-экономные тарифы). Изначально было решено, что доля новых тарифов будет составлять 30% мест на каждом рейсе. Однако быстро выяснилось, что проблему таким образом не решить: единая квота работает совершенно по-разному для разных маршрутов, дней недели и времени суток. Хотя все бы обязательно сработало, если бы величина спроса всегда была фиксированной, но оказалось, что она меняется. Следующее решение оказалось более чем разумным: для мониторинга и прогнозирования спроса были созданы огромные базы данных и разработано специальное ПО. Аналитики компании научились учитывать отклонения спроса и стали более точно распределять места со скидкой. Боб Крэндал назвал весь этот процесс "Управление доходностью". Благодаря этой новой дисциплине American получила сотни миллионов долларов дополнительного дохода, а конкурентная угроза полностью исчезла.

В последующие годы American продолжила инвестировать в возможности прогнозирования, управления доходностью и оптимального распределения ресурсов. Казалось, что все хорошо и что хуже, чем было, уже точно не будет. Однако в начале 1980-х умеренная рецессия и очередная дерегуляция со стороны Совета гражданской авиации снова создали конкурентную угрозу — еще более серьезную, чем предыдущая. Бюджетные авиакомпании, такие как PEOPLExpress, получили возможность (а самое главное — оказались способными) взимать за перелет плату меньше, чем в супер-экономных тарифах American.

Ежемесячно PEOPLExpress сообщала о впечатляющем рекордном росте трафика и доходов — на тот момент ни одна компания на планете еще не достигала уровня доходов в 1 миллиард долларов с такой скоростью. Боб Крэндал тоже не сидел на месте — к тому времени он уже стал президентом American и окончательно убедился в том, что управление доходностью — это не просто очередная дисциплина для ботаников, а самое настоящее оружие против конкурентов. Благодаря Бобу авиакомпания постоянно инвестировала в исследования миллионы долларов и обрела инновационные возможности, которым дали весьма подходящее имя — DINAMO (Dynamic Inventory Optimization and Maintenance Optimizer — динамическая оптимизация запасов и оптимизатор обслуживания).

В январе 85-го American объявила о тарифах Ultimate Super Saver, которые были дешевле даже, чем у PEOPLExpress. Эти тарифы не подлежали возврату, ограничивались предварительной покупкой и жестко контролировались прогнозами спроса. Новые невиданные скидки использовались только с целью обойти конкурентов, а DINAMO тщательно нацеливала их только на "лишние" места, которые в любом другом случае летели бы пустыми. Можно подумать, что такая тактика конкурирования равносильна работе в убыток, но нет: в течение года доходы American увеличились на 15%, а прибыль — на 48%. Еще через год PEOPLExpress исчезла как компания. Боб Крэндал в очередной раз доказал, что управление доходностью — это экономическое оружие: если у тебя есть DINAMO за миллионы долларов, то можно "динамить" конкурентов, даже если они зарабатывают миллиарды.

В 1992 году дополнительный доход American, обеспеченный управлением доходностью за последние три года, оценивался в 1,4 миллиарда долларов. Хотя множество других авиакомпаний также развивали и использовали внутри себя технологии управления доходностью, триумф American был самым заметным и дал мощный толчок к внедрению этой дисциплины в самых разных отраслях.

Следом за гражданской авиацией последовал гостиничный бизнес. В середине 1980-х Билл Марриот-младший услышал об управлении доходностью непосредственно от самого Боба Крэндала на случайной встрече. У Marriott International было много тех же проблем, что и у авиакомпаний: "скоропортящиеся" запасы, предварительное бронирование, конкуренция с более дешевыми отелями и большой дисбаланс спроса и предложения. Перенесение принципов управления доходностью из авиации показалось очень простым, но очень быстро выяснилось, что отели — это совсем не самолеты и что идентичным подходом проблему не решить. В авиации управление самолетами является централизованным, а в отельном бизнесе каждый отель уникален и требует отдельного внимания. Вскоре выяснилось, что отличий еще больше: например, в самолете использование кресел определяется продолжительностью маршрута, а в отелях продолжительность бронирования номеров может быть совершенно разной.

Билл Марриот-младший решил не экономить и создал отдельную организацию, которая занималась управлением доходностью. В скором времени эта организация оказалась способна давать ежедневные прогнозы спроса и делать на их основе рекомендации по стоимости для каждого из 160 тысяч номеров, чем обеспечили себе крепкие конкурентные преимущества. К середине 90-х управление доходностью в Marriot приносило от 150 до 200 миллионов долларов дополнительного годового дохода и стало неотделимой частью рыночной стратегии.

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

История внедрения управления доходностью в B2B является не менее интересной. Перевозчик United Parcl Service (UPS) с момента своего "скромного" основания в 1907 году росла, по мнению своего основателя, за счет двух факторов:

  1. Лозунг: «Лучший сервис и самые низкие тарифы».

  2. Маниакальная борьба с издержками: "всевидящее око" отдела контроля затрат определяло абсолютно все, начиная от количества шагов водителя до машины и заканчивая последовательностью ключей в его связке.

Основным своим конкурентом UPS считала почтовую службу США. Однако после очередной дерегуляции в виде закона об автомобильных перевозчиках в 1980 году таковым стал FedEx, который перевозил часть грузов по воздуху. Разумеется, многие старые клиенты тут же начали требовать скидок. И вот тут в UPS поняли две важные вещи:

  1. Каким бы маниакальным не было решение проблем с затратами — оно все равно не решает проблем с доходами.

  2. У них (внезапно!) нет даже маркетингового отдела.

Первоначально скидки ограничивались только теми клиентами, которые могли доказать, что у них есть достаточный объем для снижения стоимости обслуживания, но конкуренция все сильнее набирала обороты. Интересно, что никаких альтернатив, кроме скидок, на тот момент даже не рассматривалось: именно они считались единственным эффективным оружием в конкурентной борьбе. Поскольку руководство требовало успеха, то отдел ценообразования начал разрешать "продажникам" скидки на сотни миллионов долларов, а цены начали падать с пугающим ускорением.

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

Рик Кампана, который был назначен вице-президентом UPS по маркетингу, понял, что самостоятельно, а самое главное "дешево и сердито" справиться с задачей не получится. После этого он собрал группу из бывших экспертов по авиаотрасли и внешних консультантов с обширным опытом работы в авиакомпаниях. После анализа исторических данных по совершенным сделкам они сформулировали проблему в виде модели индивидуального отклика на предложение и смогли понять, как прогнозировать вероятность выигрыша в разных ценовых категориях. Хорошая система должна иметь имя, и они назвали ее системой целевого планирования. Благодаря ей они получили возможность прогнозировать результаты любого предложения, но самое главное — четко понимать, в каких ситуациях они могут легко получить прибыль большую, чем у конкурентов, а в каких ситуациях для заключения контракта необходимо предложить более значительные скидки.

По прошествии года после внедрения системы целевого планирования UPS сообщила об увеличении прибыли более чем на 100 миллионов долларов.

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

Совокупный прирост прибыли от использования управления доходностью по всему миру за последние десятилетия можно смело оценить в сотни миллиардов долларов, хотя, возможно, речь идет о куда больших суммах, ведь помимо перечисленных компаний управление доходностью стало активно использоваться в многотриллионной сфере финансовых услуг. Ввиду своей "секретности" игроки данной сферы, как правило, не трубят об успехах внедрения RMS — хотя очевидно, что эти успехи есть и могут запросто исчисляться триллионами долларов.

Годовой оборот любого глобального рынка, например рынка travel-услуг, может составлять сотни миллиардов и даже триллионы долларов. Любой глобальный рынок делится на сегменты, а участники каждого сегмента воспринимают RMS как оружие в конкурентной борьбе. Обратившись к истории, мы можем видеть, что отставание в используемых технологиях может оказаться фатальной ошибкой для отдельных компаний. Вполне логично предположить, что вероятность потери доли рынка для некоторой совокупности компаний далеко не нулевая.

Моралью истории тоже не удивить: можно сколько угодно думать, что у тебя все нормально, все под контролем и что ты точно знаешь, что делаешь, но при этом потом внезапно обнаружить себя в ситуации, в которой оказалась American Airlines — когда из-за упущенной единственной и элементарной гипотезы можно потерять абсолютно все.

На возможную угрозу можно посмотреть и под другим углом: допустим, у нас есть две авиакомпании с одинаковыми мощностями и технологиями RMS. Что будет, если одна из них находится в экосистеме компаний, в которой культура развития и внедрения RMS более развита? Скорее всего, у нее будет более дешевое и качественное техническое обслуживание, страхование, кредитование, реклама и т.д. Авиакомпания, которая не имеет никакого конкурентного преимущества по отношению к другой, получит его за счет всех остальных компаний, с которыми она связана.

Правильная интерпретация неопределенности

Прежде чем перейти к каким-то примерам, важно разобраться с тем, что из себя представляет неопределенность. Большинство людей, кто так или иначе знаком с теорией вероятностей, воспринимает неопределенность именно как вероятность. Вероятность очень удобна для оценки возможности наступления событий, особенно, если события происходят с некоторой частотой:

  • Если монетка выпала 10 раз орлом вверх, значит и в 11-й раз вероятнее всего появится орел.

  • Если 10 дней подряд идет дождь, то и в 11-й день вероятнее всего будет наблюдаться дождь.

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

Фреквентистская неопределенность возникает лишь потому, что наблюдения производятся только над частью всех возможных событий — то есть над выборкой, а не генеральной совокупностью. Например, если подбросить монетку всего 100 раз, то вероятность выпадения орла может заключаться в довольно широком диапазоне, например [0.45, 0.55]. Если подбросить ее бесконечное количество раз, то вероятность уже будет равна строго определенному числу, например, 0.5, если монетка честная.

В пользу фреквентизма как характерной черты для человеческого разума часто приводят и другой аргумент — невозможность оценки вероятности наступления событий, которые не могут происходить с некоторой частотой. Именно поэтому с точки зрения фреквентизма крайне сложно ответить на такие вопросы:

  • Какова вероятность того что акции Google завтра обесценятся?

  • Какова вероятность того, что сборная ЮАР по футболу выиграет чемпионат мира?

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

  1. Неопределенность — это мера информации. Как и любая мера, она должна выражаться количественно.

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

  3. Неопределенность означает наличие некоторого множества альтернатив. Почему так популярны фильмы на основе комиксов Marvel или DC? Зрителям известно, что вероятность выживания супергероев в тех условиях, в которые они попадают, намного больше, чем у обычного человека — но при этом она не равна 100%. В самом деле, если бы заранее было известно, что все герои выживут (отсутствие каких-то альтернатив), то эти фильмы просто незачем было бы смотреть.

  4. Неопределенность напрямую связана с качеством информации. Информация может быть в разной степени ценной, достоверной, полной, ясной и актуальной. Чем лучше качество информации — тем меньше неопределенность.

  5. Любые риски неизбежно связаны с неопределенностью. Если бы это было не так, то ни о какой математической теории управления рисками не было бы и речи.

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

  7. Неопределенность является естественным ограничением эффективности (оптимальности) принимаемых решений. В среде, где неопределенность является естественным фактором, стохастическое программирование не является естественным методом решения задач оптимизации.

Так какова же вероятность того, что акции Google завтра обесценятся? А сколько бы вы на это поставили? И какая ставка вас устроит: 1:50, 1:1000 или 1:1000000? Скорее всего, если бы речь об этом шла до появления ChatGPT, то последний вариант мог бы показаться наиболее логичным. Сейчас появление каких-то новых прорывных технологий от OpenAI или какой-нибудь другой компании (Google в том числе) не кажется чем-то фантастическим. Если речь идет конкретно о завтра, то ставка 1:1000 могла бы быть вполне разумной. А если бы речь зашла не о завтрашнем дне, а о "в течение года", то ставка 1:50 стала бы еще приемлемей.

Ставки лучше всего у всех ассоциируются со спортом. Сколько бы вы поставили на то, что сборная ЮАР выиграет чемпионат мира по футболу? Очевидно, что немного — в таком случае 1 доллар к 500 кажется разумной ставкой. А как вам ставка 1 доллар к 10^{9}? Можем ли мы быть уверены, в том что вероятность победы сборной ЮАР настолько мала? Вдруг прямо сейчас в ЮАР решили развивать спорт в соответствии с мировыми стандартами и лучшими практиками? Нам это неизвестно, но такое допущение не так далеко от реальности и вполне имеет место быть.

1 доллар кажется ерундой — его можно поставить на самый невероятный исход без всякого сожаления. А если ставки приходится делать сотнями и тысячами ежедневно? Именно так и делают букмекерские конторы, причем исторический опыт показывает, что это весьма прибыльное занятие. При этом оно вряд ли было бы таким прибыльным, если бы ставки брались "с потолка" — в этом деле всегда были нужны информация, опыт и логика. Сейчас же букмекерство — это прикладная математическая статистика в чистом виде. При этом так называемые капперы (профессиональные спортивные аналитики) и бетторы ("ставочники") тоже никуда не делись. Если ввести в поисковик запрос "байесовский вывод и ставки на спорт", можно обнаружить массу примеров того, как теорема Байеса используется для анализа возможных исходов спортивных игр. Все это может показаться чем-то околонаучным, однако Ави Пфеффер (родоначальник вероятностного программирования и создатель Figaro — одного из лучших пакетов для байесовского вывода) в своей книге "Вероятностное программирование на практике" показывает, что спортивные ситуации могут быть математически формализованы и подвергаться серьезному анализу.

Ставка на некоторый исход — это объективный способ количественного выражения неопределенности, который удовлетворяет всем ее вышеперечисленным свойствам. Более того, ставки на исходы — это такая же интуитивно-понятная мера, как и частота появления исходов. Как часто вы делаете ставки, даже не осознавая этого? Например, находясь в родном городе, вы можете быть уверены в том, что, если отправитесь в аэропорт за полтора часа до вылета, то точно не опоздаете на самолет. Такую уверенность можно выразить большой ставкой, например, в 200 долларов. Возможность опоздания, конечно же, не стоит исключать, но вы уверены, что это практически невозможно — поэтому поставили бы на это всего 5 долларов.

Вероятнее всего, вы и правда не опоздаете, но как посчитать эту вероятность? Благодаря сделанной ставке это очень просто: пусть H_{0} — гипотеза, которая состоит в том, что вы не опоздаете, а H_{1} — в том, что опоздаете. С помощью нехитрых вычислений уверенность в том, что вы не опоздаете, выразится в виде привычной вероятности — P(H_{0}), которая будет практически равна 1 (\approx 0,98). Очевидно, что в незнакомом городе ваша ставка на то, что вы не опоздаете, вполне могла бы оказаться менее скромной, скажем, 5 к 1, тогда и вероятность успеха тоже окажется ниже: P(H_{0}) \approx 0,83.

Снижение вероятности успеха может побудить добавить еще 15-20 минут к запланированным полутора часам на дорогу. С другой стороны, если есть сервис, который позволяет в незнакомом городе отслеживать пробки на дорогах и прокладывать кратчайшие по времени маршруты, то это могло бы положительно повлиять на вашу уверенность и увеличить P(H_{0}). Это, в свою очередь, может побудить уменьшить планируемое время на дорогу до аэропорта.

В этом и заключается байесовский вывод:

  1. На основе имеющейся информации выдвигаются гипотезы.

  2. Ставки позволяют подсчитать вероятность и количественно оценить, насколько верна каждая из выдвинутых гипотез.

  3. При поступлении новой информации выполняется перерасчет этих вероятностей.

Байесовизм естественен для человека, как и фреквентизм. Задумайтесь, сколько раз в день вы делаете ставки не осознавая этого: выбор дня недели для утренней пробежки, выбор ужина и фильма для вечернего просмотра. Каждый раз, когда перед нами возникает несколько альтернатив, мы механически делаем ставки на каждую из них.

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

Цена как результат конкуренции идей

Теорема Байеса выглядит следующим образом:

P(H|D) = \frac{P(H) \times P(D|H)}{P(D)},

где:

  • P(H|D) — апостериорная вероятность. Она показывает, как информация (или данные), обозначенная символом D, влияет на вероятность того, что гипотеза H верна.

  • P(H) — априорная вероятность (она же вероятность гипотезы, она же априорное убеждение). Показывает вероятность гипотезы H до появления информации D.

  • P(D|H) — правдоподобие. Показывает, какова вероятность возникновения полученной информации D в предположении, что гипотеза H верна.

  • P(D) — вероятность возникновения полученной информации (вероятность данных). Показывает, с какой вероятностью могла появиться полученная информация независимо от каких-либо гипотез.

Теорема Байеса — это замечательный способ рассуждений. Благодаря ей мы можем моделировать любые ситуации, в которых есть неопределенность, например, продажи. Единственная серьезная проблема заключается в том, что, когда речь заходит об абстрактных представлениях, мы, как правило, не можем ей пользоваться из-за невозможности вычисления P(D) — вероятности возникновения полученной информации. Это может показаться проблемой, но на самом деле P(D) нормирует конечный результат так, чтобы он находился в диапазоне от 0 до 1. В продажах нас часто волнует не конкретное значение апостериорной вероятности, а именно сравнение относительной силы двух разных гипотез. По этой причине теорему Байеса часто записывают в простой пропорциональной форме:

P(H|D) \propto P(H) \times P(D | H).

Если имеется две гипотезы H_{1} и H_{2} относительно одной и той же информации, то для каждой из них теорема Байеса запишется с одинаковым знаменателем:

P(H_{1}|D) = \frac{P(H_{1}) \times P(D|H_{1})}{P(D)}

и

P(H_{2}|D) = \frac{P(H_{2}) \times P(D|H_{2})}{P(D)}.

Даже если мы не можем вычислить P(D), то при делении P(H_{1}|D) на P(H_{2}|D) выяснится, что это вовсе не нужно, поскольку P(D) все равно сократится и останется очень простое, но глубокое по своей сути выражение:

\frac{P(H_{1}|D)}{P(H_{2}|D)} = \frac{P(H_{1}) \times P(D|H_{1})}{P(H_{2}) \times P(D|H_{2})}.

Во-первых, это выражение позволяет выяснить, во сколько раз гипотеза H_{1} объясняет полученную информацию лучше, чем гипотеза H_{2}. Во-вторых — и это самое главное — она позволяет узнать, сколько нужно информации, чтобы кто-то изменил свою уверенность в гипотезе H_{2} и принял гипотезу H_{1}.

Как все это связано с продажами — в особенности с B2B, где может быть очень мало данных? Предположим, что с одной стороны стола находится продавец, который продает нечто уникальное, чего раньше еще никогда не было: инновационный IT-стартап, технологическая линия для невероятно-сложного производства или даже метод синтеза адамантия. С другой стороны стола покупатель, который заинтересован в приобретении этого актива. Пусть продавец уверен в том, что 5 миллионов долларов — это справедливая цена для его актива, а покупатель в том, что справедливая цена равняется 4 миллионам долларов.

Очевиднее всего разница во мнениях заключается в разнице между информацией D_{1}, которой обладает продавец, и информацией D_{2}, которой обладает покупатель. Что это за информация? Проще всего представить ее как множество фактов, говорящих о цености актива и позволяющих аргументированно увеличивать или снижать его стоимость. Пусть PosFact_{i} — это некоторый факт, который говорит в пользу увеличения стоимости, а NegFact_{j} — факт, снижающий стоимость. Продавец заинтересован в поиске наибольшего количества PosFact, покупатель же, наоборот, заинтересован в длинном списке NegFact, что вполне логично.

Покупатель знает, что цена в 4 миллиона для него наиболее приемлема и готов поставить на то, что он купит актив по данной цене довольно большую ставку, например, 10:1. Цена в 4.1 миллиона для него менее привлекательна, и на покупку по этой цене он ставит чуть меньшую ставку — 9:2. Цена продавца в 5 миллионов для него абсолютно неприемлема, и на покупку по этой цене он ставит всего 1:20. У продавца противоположная ситуация со ставками: он ставит 10:1 на то, что продаст только за 5 миллионов, 9:2 — что за 4.9, и всего 1:20 на то, что продаст за 4. Зная ставки, мы можем легко рассчитать априорные вероятности покупки или продажи актива, как для покупателя, так и для продавца. Пусть они выглядят следующим образом:

Python
# Импортируем все необходимое
import numpy as np
np.random.seed(42)
import pandas as pd
from scipy.stats import uniform, betabinom, randint, poisson, gamma, norm, bernoulli, binom, beta
import pymc3 as pm
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from pylab import rcParams
rcParams['figure.figsize'] = 7, 4
rcParams['figure.dpi'] = 140
%config InlineBackend.figure_format = 'png'
import seaborn as sns
sns.set(style="whitegrid")
import arviz as az

Python
a_s = np.hstack((np.r_[10:0:-1], [1]))
b_s = np.hstack((np.r_[1:11], [20]))

a_b = np.hstack(([1], np.r_[1:11]))
b_b = np.hstack(([20], np.r_[10:0:-1]))

p_b = a_b / (a_b + b_b)
p_s = a_s / (a_s + b_s)
plt.plot(np.linspace(4, 5, 11), p_b, 'bo-', label='seller')
plt.plot(np.linspace(4, 5, 11), p_s, 'ro-', label='buyer')
plt.legend()
plt.xlabel('Price (M$)')
plt.title('Bets on buying and selling an asset');

Имея предположения о ставках, но не имея при этом никакой другой информации, мы все равно можем сделать полезные выводы. Обозначим через H^{(S)}_{0} гипотезу покупателя, состоящую в том, что 4 миллиона — это наилучшая цена, а через H^{(B)}_{0} — аналогичную гипотезу продавца. Выясним, чему будет равно отношение вероятностей данных гипотез:

\frac{P(H^{(S)}_{0})}{P(H^{(B)}_{0})} = \frac{10}{11} : \frac{1}{21} = \frac{210}{11} \approx 19

Данное соотношение записывается как O(H^{(S)}_{0} : H^{(B)}_{0}) и называется априорным шансом H^{(S)}_{0}. На первый взгляд, априорные шансы могут показаться абсолютной бессмыслицей, так как они рассматривают отношение вероятностей двух гипотез до поступления какой-бы то ни было информации. Однако такое отношение позволяет заметить относительную убежденность в той или иной гипотезе — например, риск-менеджер может предположить, что для бизнеса есть всего три угрозы: первая — конкуренты, вторая — состояние экономики, третья — инопланетяне. Априорные шансы позволяют выяснить относительную веру в ту или иную гипотезу и ранжировать их в порядке приоритета или даже отбрасывать самые неправдоподобные из них.

Вычисленное выше значение O(H^{(S)}_{0} : H^{(B)}_{0}) = 19 говорит о том, что покупатель уверен в том, что цена 4 миллиона долларов является справедливой гораздо сильнее, чем продавец, а именно в 19 раз — с учетом сделанных ими ставок. Для покупателя же ситуация будет противоположной — он будет в 19 раз сильнее уверен в обратном. На графике априорные шансы для всех соответствующих гипотез будут выглядеть следующим образом:

Python
plt.plot(np.linspace(4, 5, 11), p_s / p_b, 'bo-', label='O - seller')
plt.plot(np.linspace(4, 5, 11), p_b / p_s, 'ro-', label='O - buyer')
plt.xlabel('Price (M$)')
plt.legend()
plt.title('Prior odds');

Чем глубже "пропасть" на графике — тем сложнее будут идти переговоры за столом, ведь высоту каждого ее края можно еще интерпретировать как степень безразличия к цене.

Априорные шансы полезны для оценки представлений о ситуации до появления информации. Как быть тогда, когда переговоры начались и стали появляться первые факты, говорящие в пользу снижения или увеличения стоимости актива? В качестве простой аналогии можно привести простой пример с монеткой: допустим, до подбрасывания монетки мы уверены в том, что вероятность выпадения орла составляет всего 0.001. Как нам следует поступить, если после первого подбрасывания выпадет орел? Либо нам очень повезло, потому что вероятность увидеть орла с такой вероятностью, даже при сотне подбрасываний составит всего около 0.1, либо что-то не так с нашей изначальной гипотезой, и ее нужно менять.

Во время переговоров относительно цены происходит нечто подобное. Покупатель ставит 10:1 на то, что купит актив за 4 миллиона, но, если продавец приведет положительный факт для актива, который позволяет аргументированно увеличить стоимость актива на 100k, то покупателю придется так или иначе пересмотреть свои ставки относительно всех своих гипотез. Если покупатель рационален, то он так и поступит. Точно так же придется поступить и продавцу при появлении отрицательного факта, снижающего стоимость.

Допустим, торги начинаются с 4-х миллионов. В этой точке покупатель уверен в том, что появление отрицательного факта имеет вероятность P(\mathrm{NegFact}|H^{(S)}_{0})=10/11, а положительного:

P(\mathrm{PosFact}|H^{(S)}_{0})=1 - P(\mathrm{NegFact}|H^{(S)}_{0})= \frac{1}{11}.

У продавца в этой точке будет противоположная ситуация:

P(\mathrm{NegFact}|H^{(B)}_{0})=1/21

и

P(\mathrm{PosFact}|H^{(B)}_{0})=1 - P(\mathrm{NegFact}|H^{(B)}_{0})= \frac{20}{21}.

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

\frac{P(\mathrm{PosFact}|H^{(S)}_{0})}{P(\mathrm{PosFact}|H^{(B)}_{0})} = \frac{1}{11} : \frac{20}{21} = \frac{21}{220} \approx 0.1

Что означает данное соотношение? Продавец и покупатель собираются спорить о своей вере в справедливую стоимость актива — именно поэтому они заинтересованы в сборе доказательств, а именно фактов, говорящих либо в пользу H^{(S)}, либо в пользу H^{(B)}. Спор о справедливости цены — это выяснение того, насколько хорошо каждая гипотеза объясняет представленную информацию. Если стороны переговоров рациональны, то только информация может заставить кого-то изменить свое мнение.

Соотношение правдоподобий называется коэффициентом Байеса и записывается как \Lambda(H^{(S)}_{0} : H^{(B)}_{0} | D_{0}), а его значение показывает, во сколько раз рассматриваемая гипотеза объясняет данные лучше, чем другая, в предположении, что каждая из гипотез равновероятна (P(H^{(S)}_{0})=P(H^{(B)}_{0})). Значение 0.1 говорит о том, что гипотеза покупателя H^{(S)}_{0} объясняет единственный положительный факт в 10 раз хуже, чем гипотеза продавца H^{(B)}_{0}. Это неудивительно, поскольку и продавец, и покупатель заняли довольно радикальные позиции относительно приемлемой для себя цены.

Произведение априорных шансов на коэффициент Байеса позволяет вычислить апостериорные шансы:

O(H^{(S)}_{0} : H^{(B)}_{0} | D_{0}) = O(H^{(S)}_{0} : H^{(B)}_{0}) \times \Lambda(H^{(S)}_{0} : H^{(B)}_{0} | D_{0}) = 19 \times 0.1 \approx 2.

Апостериорные шансы показывают, во сколько раз одна гипотеза объясняет данные лучше, чем другая. Несмотря на то, что H^{(S)}_{0} объясняет единственный положительный факт в 10 раз хуже, чем H^{(B)}_{0}, апостериорные шансы показывают, что из-за того, что покупатель поставил на справедливость цены в 4 миллиона гораздо больше, чем продавец, его гипотеза все равно объясняет данные в два раза лучше. Данных на данный момент мало — если их будет больше, то ситуация может очень быстро измениться.

Если апостериорные шансы равны или близки к единице, то ни одна из гипотез не имеет преимуществ. Это значит, что для опровержения гипотезы будет достаточно даже небольшого количества информации. Например, если к представленному положительному факту добавить еще один отрицательный, то O(H^{(S)}_{0} : H^{(B)}_{0} | D=\left\{1p, 1n\right\}) = 3.5. Однако, если в самом начале торгов предоставить сразу два положительных факта, то O(H^{(S)}_{0} : H^{(B)}_{0} | D=\left\{2p, 0n\right\}) = 0.17. Гораздо сложнее опровергнуть гипотезу, если апостериорные шансы >100 или >150 — в этом случае гипотеза, скорее всего, имеет неопровержимые доказательства.

Конечно, нам хотелось бы понять, как именно должны меняться ставки покупателя и продавца, но мы не можем сделать это из-за невозможности вычислить вероятность возникновения информации P(D). Ставку можно воспринимать как дробь a:b, пусть положительный или отрицательный факт будет изменять числитель этой дроби на какое-то фиксированное значение \Delta, пусть \Delta=1. Предположим, что покупатель предоставил 8 отрицательных фактов, а продавец — только 6 положительных. После полученной информации вероятности гипотез будут выглядеть следующим образом:

Python
a_s = np.hstack((np.r_[10:0:-1], [1]))
a_s_post = np.hstack((np.r_[10:0:-1], [1])) + 6
b_s = np.hstack((np.r_[1:11], [20]))

a_b = np.hstack(([1], np.r_[1:11]))
a_b_post = np.hstack(([1], np.r_[1:11])) + 8
b_b = np.hstack(([20], np.r_[10:0:-1]))

p_b_post = a_b_post / (a_b_post + b_b)
p_b = a_b / (a_b + b_b)
p_s_post = a_s_post / (a_s_post + b_s)
p_s = a_s / (a_s + b_s)
plt.plot(np.linspace(4, 5, 11), p_b, 'bo-', alpha=0.4, label='seller "before"')
plt.plot(np.linspace(4, 5, 11), p_b_post, 'bo-', label='seller "after"')
plt.plot(np.linspace(4, 5, 11), p_s, 'ro-', alpha=0.4, label='buyer "before"')
plt.plot(np.linspace(4, 5, 11), p_s_post, 'ro-', label='buyer "after"')
plt.ylim(0, 1)
plt.xlabel('Price (M$)')
plt.legend()
plt.title('Probabilities of hypotheses before and after receiving information');

Данный график показывает, как продавец и покупатель оценивают свои шансы на продажу и покупку актива по определенной цене после предоставления всех имеющихся на руках фактов. Разумеется, если продавец или покупатель заинтересованы в сделке, то каждый из них уже может руководствоваться полученными графиками. Например, если продавец заинтересован в том, чтобы продать товар с высокой вероятностью, близкой к 1, то придется уступить. Если он совсем не хочет уступать, то ему следует быть готовым к тому, что его актив не будет куплен с вероятностью 0.75. В какой-то мере данные графики можно интерпретировать как спрос и предложение.

Если же оба будут вести себя в соответствии со своими ставками, то вероятность успешной сделки будет равна произведению соответствующих вероятностей на вышеприведенном графике:

Python
a_s_post = np.hstack((np.r_[10:0:-1], [1])) + 6
b_s = np.hstack((np.r_[1:11], [20]))

a_b_post = np.hstack(([1], np.r_[1:11])) + 8
b_b = np.hstack(([20], np.r_[10:0:-1]))

p_b_post = a_b_post / (a_b_post + b_b)
p_s_post = a_s_post / (a_s_post + b_s)

price = np.linspace(4, 5, 11)
cog_s = price * p_s_post
cog_b = price * p_b_post

plt.plot(price, p_s_post * p_b_post, 'bo-', label='all')

plt.xlabel('Price (M$)')
plt.legend()
plt.title('The influence of price on the probability of a transaction');

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

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

Представленная модель позволяет понять, что будет, если продавец один, а покупателей становится больше одного. Самое главное — она позволяет понять, что будет, если продавцов также становится несколько.

Планирование продаж в байесовской парадигме

Основная критика Байесовской парадигмы связана с тем, что человек, работающий с неопределенностью, может привнести в модель свой субъективный опыт. Однако, субъективизм следует воспринимать как "опыт" + "логика" — и в таком сочетании они могут оказаться решающим фактором успеха. Рассмотрим сначала пример для модели B2B, чтобы убедиться в этом.

Как говорилось в самом начале, в модели B2B очень мало данных. Пусть у нас есть некоторая компания-продавец и всего десять компаний-клиентов. В качестве актива будет выступать некоторый, весьма дорогой IT-продукт, помогающий клиентам развивать свой бизнес. Какой может быть стоимость использования такого продукта? Самый тривиальный способ ее вычисления — это посчитать издержки и прибавить к ним маржу. Маржа для продукта, который действительно помогает развивать бизнес, может быть очень большой, но в то же время у нее есть какой-то логический и психологический потолок. Если все клиенты отличаются по маржинальности, то и отношение к цене у них так же будет разным.

Формирование единой цены для всех клиентов — не самая лучшая идея, но даже такое решение может быть оптимизировано. Единственное, что для этого нужно — это дополнительная информация. Допустим, нам известно, что у компании следующий набор клиентов: 2 крупные компании, 3 средних и 5 небольших. У нас нет возможности узнать, как могут выглядеть кривые спроса для каждой из этих групп, но мы можем предположить, как будет меняться вероятность приобретения продукта компаниями в зависимости от его цены. Если ежемесячная стоимость использования продукта для большой компании будет составлять всего 1% от ее прибыли, то она купит его с большой вероятностью, но для маленькой компании такая стоимость может составлять 5 или даже 10% — из-за чего она наверняка откажется от его использования.

Дополнительная информация позволила перейти от простого "гадания" по поводу лучшей цены к формализуемой задаче оптимизации. Предположения могут быть подкреплены достоверной информацией — мы могли бы узнать о прибыли компаний, посчитать, какой прирост к прибыли обеспечит предлагаемый продукт, а затем строить более объективные кривые отношения к цене не для групп, а уже для каждой отдельной компании. Это действительно позволит принять наиболее оптимальное решение.

У нас есть хорошая гипотеза, состоящая в том, что компании-клиенты разные, следовательно, единая цена для всех — это не лучшая идея. Выдвинем еще одну гипотезу: компании разные, следовательно, и потребности у них тоже разные. Если предлагаемый продукт состоит из набора сервисов, каждый из которых востребован клиентами в разной степени и зависит от величины компании, то почему бы не представить продукт в виде трех разных наборов этих самых сервисов. Скажем, назвать их: "Базовый", "Средний" и "Профессиональный", — и постараться подобрать оптимальную стоимость для каждого из них? Такое решение окажется еще оптимальнее. Если такая стратегия выбрана на раннем этапе создания продукта, то она еще и окажется полезной для оптимального распределения ресурсов и приведет к наискорейшему появлению продукта на рынке.

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

Казалось бы, что на этом уже можно остановиться, но можно пойти еще дальше и достичь персонального подхода. Разделять продукт на отдельные наборы сервисов — это гораздо лучше, чем продавать монолитный продукт с единой ценой. При этом кого-то из клиентов может смутить то, что ему придется пользоваться набором, который называется "Базовый". Клиенты, тоже люди, которым также присущ субъективизм — а это значит, что слово "базовый" может быть кем-то ассоциировано со словом "бесперспективный". Такой клиент может выбрать другого продавца лишь только потому, что ему, как он посчитает, продадут "успешную" перспективу. Не зря говорят, что управление доходами — это продажа продукта клиенту в правильное время по правильной цене и, что не менее важно, в правильной упаковке. Мы можем задуматься над более привлекательными названиями для каждого набора сервисов, но какими бы ни были эти названия, доля клиентов будет видеть в нем сначала классирование, а потом дискриминацию. Гипотеза довольно спорная, но она имеет место быть, и необходимо понять ее вероятность.

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

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

Байесовская парадигма и управление доходностью в B2C

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

Рассмотрим способы конкуренции двух авиакомпаний, предварительно разобравшись, как априорная вероятность влияет на апостериорную. Проще всего продемонстрировать это с помощью вероятностного программирования. Допустим, у нас есть следующие данные:

Python
D = np.array([1, 1, 1, 0, 5, 2, 3, 3, 3, 2, 1, 2, 3, 1, 2, 1, 0, 3, 2, 1])

Пусть эти данные показывают, сколько единиц товара Y продавалось ежедневно по некоторой фиксированной цене. Очевидно, что перед нами случайная величина, которая имеет распределение Пуассона:

Y \sim P(\lambda).

Вот только \lambda — это среднее количество продаж, которое нам неизвестно. Самое простое, что можно сделать, — это найти выборочное среднее и оценить его возможные отклонения с помощью метода бутстрап:

Python
means = np.array([np.mean(D[np.random.randint(0, 20, 20)]) for _ in range(5000)])
sns.kdeplot(means, label=r'pdf($\lambda$)')
plt.axvline(np.mean(D), c='b', ls='--', label=r'$\hat{\lambda}$ = E(D)')
plt.axvspan(*np.percentile(means, [5, 95]), alpha=0.2, label='90% ci')
plt.legend()
plt.title(r'epdf($\lambda$) inference from data');

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

Наилучшей оценкой \lambda является выборочное среднее. Раз это наилучшая оценка, то полагаем, что Y \sim P(\lambda=1.85) — и "дело в шляпе". Распределение количества ежедневных продаж будет выглядеть следующим образом:

Python
y = np.arange(0, 8)
p_y = poisson.pmf(y, mu=1.85)
plt.stem(y, p_y)
plt.xlabel('Number of sales')
plt.ylabel('Probability')
plt.title('Distribution of the number of daily sales');

Давайте предположим, что истинное значение \lambda равняется 2.8 (в 1.5 раза больше выборочного):

Python
sns.kdeplot(means, label=r'pdf($\lambda$)')
plt.axvline(np.mean(D), c='b', ls='--', label=r'$\hat{\lambda}$ = E(D)')
plt.axvspan(*np.percentile(means, [5, 95]), alpha=0.2, label='90% ci')

means_hyp = [poisson.rvs(mu=2.8, size=20).mean() for _ in range(5000)]
sns.kdeplot(means_hyp, color='C1', label=r'$\lambda$ = 2.8')
plt.axvline(np.mean(means_hyp), c='C1', ls='--')
plt.legend()
plt.title('Possible deviations of sample and hypothetical values');

Есть небольшая вероятность совершения очень большой ошибки: можно принять \lambda = \hat{\lambda} = 1.85, когда в реальности \lambda = 2.8. Причем pdf отклонений выборочного среднего даже не содержит намека о том, что \lambda может быть равна 2.8. Как будто что-то не так: здесь имеет место быть сразу целое множество противоречивых гипотез. Во фреквентистской статистике от этой неопределенности никуда не деться. Например, если посчитать 95% доверительный интервал для \lambda "как положено", то мы получим промежуток от 0.2 до 7, что гораздо хуже, чем бутстрап, и что не дает никакого представления о вероятности возможных отклонений.

Что насчет байесовского вывода? Обычно его никогда не сравнивают с бутстрапом — из-за этого гораздо интереснее посмотреть на его возможности. Допустим, у нас нет вообще никакой уверенности по поводу возможных значений \lambda, и мы думаем, что она может принять любое значение из интервала [0.2, 7] с равной вероятностью. Как повлияют данные на такое предположение?

Python
with pm.Model() as model:
    mu = pm.Uniform("mu", lower=0.2, upper=7)
    obs = pm.Poisson("obs", mu=mu, observed=D)
    trace = pm.sample(1000, return_inferencedata=False)
sns.kdeplot(means, label=r'pdf($\lambda$)')
plt.axvline(np.mean(D), c='b', ls='--', label=r'$\hat{\lambda}$ = E(D)')

sns.kdeplot(means_hyp, color='C1', label=r'$\lambda$ = 2.8')
plt.axvline(np.mean(means_hyp), c='C1', ls='--')

sns.kdeplot(trace['mu'], color='C2', label=r'Posterior')
plt.axvline(np.mean(trace['mu']), c='C2', ls='--')

plt.legend()
plt.title(r'Psterior pdf($\lambda$) (uniform prior pdf)');

Ситуация как-то кардинально не улучшилась по сравнению с бутстрапом. Однако в данном случае мы учли, что у нас имеется некоторая дополнительная информация о параметре \lambda — точнее то, что этой информации нет вообще. И, если раньше мы были в состоянии абсолютной неопределенности по отношению к значению \lambda, то теперь мы ее уменьшили. Причем апостериорное распределение несколько шире pdf, полученной бутстрапом. Это может показаться сущей мелочью, но множество противоречивых гипотез действительно уменьшилось. Теперь неопределенность выражается апостериорным распределением, которое учитывает более широкий диапазон возможных значений \lambda. Раньше эта неопределенность вообще выражалась абсолютным отсутствием хоть каких-либо предположений, которое мы выразили как равномерное распределение U(0.2, 7).

В байесовской статистике мы можем учитывать самые разные представления о неопределенности параметров. Допустим, некто, кому можно безоговорочно доверять, утверждает, что истинное значение \lambda с большой долей вероятности находится где-то по середине интервала [1.75, 2.2]. Что делать с этой информацией? Давайте попробуем ее учесть, так же, как учитывали U(0.5, 6):

Python
with pm.Model() as model:
    mu = pm.Normal("mu", mu=1.85, sigma=0.1)
    obs = pm.Poisson("obs", mu=mu, observed=D)
    trace = pm.sample(1000, return_inferencedata=False)
sns.kdeplot(means, label=r'pdf($\lambda$)')
plt.axvline(np.mean(D), c='b', ls='--', label=r'$\hat{\lambda}$ = E(D)')

sns.kdeplot(trace['mu'], color='C2', label=r'Posterior')
plt.axvline(np.mean(trace['mu']), c='C2', ls='--')

plt.legend()
plt.title(r'Psterior pdf($\lambda$) (normal prior pdf)');

Дополнительная информация позволила сузить доверительный интервал. Именно это является большим преимуществом байесовской статистики — благодаря дополнительной информации можно обходиться меньшим количеством данных. Некто, кто сказал нам, что \lambda \in [1.75, 2.2], может оказаться ведущим аналитиком компании, который обладает дополнительной и достоверной информацией. С другой стороны, дополнительная информация может быть статистикой за предыдущий год.

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

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

Python
for flight in range(1, 6):
    for period in range(1, 5):
        x, y = period + flight - 0.9, flight + 0.2
        text_before = '$d_{' + str(flight) + ',' + str(period) + '}$'
        text_after = '$\hat{d}_{' + str(flight) + ',' + str(period) + '}$'
        if x < 5:
            plt.text(x, y, text_before, size=13, 
                    bbox=dict(boxstyle="square",
                       ec=(0.5, 0.5, 1),
                       fc=(0.9, 0.9, 1),
                       ))
        else:
            plt.text(x, y, text_after, size=13, 
                    bbox=dict(boxstyle="square",
                       ec=(1., 0.5, 0.5),
                       fc=(1., 0.9, 0.9),
                       ))
plt.axvline(4.89, color='k', ls='--', lw=1)

plt.xlim(0, 9)
plt.ylim(0.5, 6)

plt.title('Presentation of flight ticket sales')
plt.xlabel('Time')
plt.ylabel('Flight number');

Билеты продаются сразу на некоторое число рейсов, осуществляемых по одному и тому же маршруту, но в разные даты. Пусть период продаж билетов на каждый рейс разбит на подпериоды одинаковой длины, внутри которых устанавливается фиксированная цена билета. В приведенной выше схеме индекс i обозначает номер рейса, а индекс j — номер подпериода. Мы можем обозначить стоимость билетов в каждом периоде следующим образом:

\mathrm{Price}_{i,1} = \mathrm{Price}_{1},\mathrm{Price}_{i,2} = \mathrm{Price}_{2},\mathrm{Price}_{i,3} = \mathrm{Price}_{3},\mathrm{Price}_{i,4} = \mathrm{Price}_{4}.

Пусть спрос в каждом из подпериодов выглядит следующим образом:

Python
def demand_gen(price, a, b, c):
    return a * np.exp(-b * price) + c

price = np.linspace(1, 11)
demand_1 = demand_gen(price, a=11, b=0.5, c=15)
demand_2 = demand_gen(price, a=13, b=0.4, c=15)
demand_3 = demand_gen(price, a=15, b=0.3, c=15)
demand_4 = demand_gen(price, a=17, b=0.2, c=15)

plt.plot(price, demand_1, c='C0', label=r'$T_{i, 1}$')
plt.plot(2, demand_gen(2, a=11, b=0.5, c=15), 'C0o',
         mec='w', label=r'$\mathrm{Price}_{1}=2$')
plt.plot(price, demand_2, c='C1', label=r'$T_{i, 2}$')
plt.plot(4, demand_gen(4, a=13, b=0.4, c=15), 'C1o',
         mec='w', label=r'$\mathrm{Price}_{2}=4$')
plt.plot(price, demand_3, c='C2', label=r'$T_{i, 3}$')
plt.plot(6, demand_gen(6, a=15, b=0.3, c=15), 'C2o',
         mec='w', label=r'$\mathrm{Price}_{3}=6$')
plt.plot(price, demand_4, c='C3', label=r'$T_{i, 4}$')
plt.plot(10, demand_gen(10, a=17, b=0.2, c=15), 'C3o',
         mec='w', label=r'$\mathrm{Price}_{4}=10$')
plt.legend()
plt.title('Demand curves')
plt.xlabel('Price')
plt.ylabel('Demand value');

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

Python
t = np.array([0, 0, 0, 0, 1, 1, 1, 2, 2, 3])
x = np.array([2, 2, 2, 2, 4, 4, 4, 6, 6, 10])

h_1_true, h_2_true = 2, 11
h_3_true, h_4_true = -0.1, 0.5
h_5_true = 15

l = (h_1_true * t + h_2_true) * np.exp(-(h_3_true * t + h_4_true) * x) + h_5_true

data = poisson.rvs(mu=l)

plt.plot(x, data, 'rs', label='Observations')
plt.plot(x, l, 'bo-', label=r'Real $\lambda$')
plt.legend()
plt.title('Observed Sales')
plt.xlabel('Price')
plt.ylabel('Demand');
print('d_i_1 =', data[:4])
print('d_i_2 =', data[4:7])
print('d_i_3 =', data[7:9])
print('d_i_4 =', data[9:10])
d_i_1 = [18 15 21 15]
d_i_2 = [26 24 10]
d_i_3 = [16 15]
d_i_4 = [12]

Данных очень мало:

  • d_{i, 1} = [22, 14, 20, 24];

  • d_{i, 2} = [13, 15, 17];

  • d_{i, 3} = [19, 15];

  • d_{i, 4} = [17].

Возможно ли оценить распределение спроса при заданных ценах в каждом из подпериодов на основе такого скудного количества информации? Попробуем выяснить:

Python
with pm.Model() as model:
    slope = pm.Normal('Slope', 0, sd=3)
    bias = pm.Normal('Bias', 20, sd=3)
    
    likelihood = pm.Poisson('Demand', mu=bias + slope * t, observed=data)

    trace = pm.sample(2000)

Взглянем на результат:

Python
fig, ax = plt.subplots(1, 2, figsize=(12, 5))

T = np.array([0, 1, 2, 3])
trace_data = trace['Bias'] + trace['Slope'] * T.reshape(-1, 1)

for i in range(1000):
    line = trace['Bias'][i] + trace['Slope'][i] * T
    ax[0].plot(T, line, c='m', alpha=0.05)
ax[0].set_xticks(T)

price = np.array([2, 4, 6, 10])
real_line = (h_1_true * T + h_2_true) * np.exp(-(h_3_true * T + h_4_true) * price) + h_5_true

ax[0].plot(T, real_line, 'o-', color='red', label='Real')
ax[0].plot(T, trace_data.mean(axis=1), 'o-', color='navy', label='Model')
ax[0].legend()
ax[0].set_xlabel('Sub-period')
ax[0].set_ylabel('Demand')

sns.violinplot(trace_data.T, color='cyan', split=True, ax=ax[1])
ax[1].plot(T, real_line, 'o-', c='red', label='Real')
ax[1].legend()
ax[1].set_xlabel('Sub-period')

fig.suptitle('Estimation of demand distribution in each sub-period');

В данном случае мы воспользовались самым простым линейным приближением:

\mathrm{Demand}_{i, j} \sim \mathrm{Poisson}(\lambda_{j} = at_{j} + b).

В какой-то мере это даже может показаться невероятным, особенно, если учесть объем информации, который был использован. Следует учитывать, что оценки могут быть не так хороши, как на приведенном графике. Однако в данном случае нас интересует вовсе не матожидание полученных оценок распределений спроса в каждом из подпериодов, а сами распределения. В данном случае мы получили оценки распределений \lambda_{j}, которые можно использовать в стохастическом программировании, что позволит учитывать не только какие-то наиболее ожидаемые значения спроса, но еще и все возможные его отклонения. Распределения — это и есть необходимое нам представление неопределенности.

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

Python
t_1 = np.array([0, 0, 0, 0, 0, 0, 0])
t_2 = np.array([1, 1, 1, 1, 1, 1])
t_3 = np.array([2, 2, 2, 2, 2])
t_4 = np.array([3, 3, 3, 3])

x_1 = np.array([1, 2, 3, 1.5, 2, 2, 2])
x_2 = np.array([3, 4, 4.5, 5, 4, 4])
x_3 = np.array([6, 5.5, 7, 6, 6.5])
x_4 = np.array([8.5, 10, 10.5, 11])

h_1_true, h_2_true = 2, 11
h_3_true, h_4_true = -0.1, 0.5
h_5_true = 15

def lambda_true(t, x, h1, h2, h3, h4, h5):
    return (h1 * t + h2) * np.exp(-(h3 * t + h4) * x) + h5

l_1 = lambda_true(t_1, x_1, 2, 11, -0.1, 0.5, 15)
l_2 = lambda_true(t_2, x_2, 2, 11, -0.1, 0.5, 15)
l_3 = lambda_true(t_3, x_3, 2, 11, -0.1, 0.5, 15)
l_4 = lambda_true(t_4, x_4, 2, 11, -0.1, 0.5, 15)

data_1 = poisson.rvs(mu=l_1)
data_2 = poisson.rvs(mu=l_2)
data_3 = poisson.rvs(mu=l_3)
data_4 = poisson.rvs(mu=l_4)

plt.figure(figsize=(12, 4))
plt.plot(x_1, data_1, 'C0x', label='Observations in $T_{i, 1}$')
plt.plot(np.sort(x_1), l_1[np.argsort(x_1)], 'C0--', label=r'Real $\lambda$ in $T_{i, 1}$')

plt.plot(x_2, data_2, 'C1x', label='Observations in $T_{i, 2}$')
plt.plot(np.sort(x_2), l_2[np.argsort(x_2)], 'C1--', label=r'Real $\lambda$ in $T_{i, 2}$')

plt.plot(x_3, data_3, 'C2x', label='Observations in $T_{i, 3}$')
plt.plot(np.sort(x_3), l_3[np.argsort(x_3)], 'C2--', label=r'Real $\lambda$ in $T_{i, 3}$')

plt.plot(x_4, data_4, 'C3x', label='Observations in $T_{i, 4}$')
plt.plot(np.sort(x_4), l_4[np.argsort(x_4)], 'C3--', label=r'Real $\lambda$ in $T_{i, 4}$')

plt.xlim(0.5, 15)
plt.legend()
plt.title('Observed Sales')
plt.xlabel('Price')
plt.ylabel('Demand');

Снова воспользуемся простым линейным приближением:

\mathrm{Demand}(i, j, \mathrm{Price}) \sim \mathrm{Poisson}(\lambda_{i, j} = a_{i, j}\mathrm{Price} + b_{i, j}).

Сделаем вывод и взглянем на то, что получилось:

Python
with pm.Model() as model:
    a = pm.Uniform('a', lower=-2, upper=0)
    b = pm.Normal('b', 22, sd=3)
    likelihood = pm.Poisson('Demand', mu=a * x_1 + b, observed=data_1)
    trace_1 = pm.sample(draws=3000, tune=2000)
    
a_1 = trace_1['a'].mean()
b_1 = trace_1['b'].mean()

with pm.Model() as model:
    a = pm.Uniform('a', lower=-2, upper=0)
    b = pm.Normal('b', 22, sd=3)
    likelihood = pm.Poisson('Demand', mu=a * x_2 + b, observed=data_2)
    trace_2 = pm.sample(draws=3000, tune=2000)
    
a_2 = trace_2['a'].mean()
b_2 = trace_2['b'].mean()

with pm.Model() as model:
    a = pm.Uniform('a', lower=-2, upper=0)
    b = pm.Normal('b', 22, sd=3)
    likelihood = pm.Poisson('Demand', mu=a * x_3 + b, observed=data_3)
    trace_3 = pm.sample(draws=3000, tune=2000)
    
a_3 = trace_3['a'].mean()
b_3 = trace_3['b'].mean()

with pm.Model() as model:
    a = pm.Uniform('a', lower=-2, upper=0)
    b = pm.Normal('b', 22, sd=3)
    likelihood = pm.Poisson('Demand', mu=a * x_4 + b, observed=data_4)
    trace_4 = pm.sample(draws=3000, tune=2000)
    
a_4 = trace_4['a'].mean()
b_4 = trace_4['b'].mean()

print(a_1, a_2, a_3, a_4)
pplt.figure(figsize=(12, 4))
for i in range(1000):
    a, b = trace_1['a'][i], trace_1['b'][i]
    line = a * x_1 + b
    plt.plot(x_1, line, c='cyan', alpha=0.02)
plt.plot(x_1, data_1, 'x', c='darkblue', label='Observations in $T_{i, 1}$')
plt.plot(x_1, a_1 * x_1 + b_1, c='k')
plt.plot(np.sort(x_1), l_1[np.argsort(x_1)], c='darkblue', label=r'Real $\lambda$ in $T_{i, 1}$')

for i in range(1000):
    a, b = trace_2['a'][i], trace_2['b'][i]
    line = a * x_2 + b
    plt.plot(x_2, line, c='bisque', alpha=0.02)
plt.plot(x_2, data_2, 'x', c='darkorange', label='Observations in $T_{i, 2}$')
plt.plot(x_2, a_2 * x_2 + b_2, c='k')
plt.plot(np.sort(x_2), l_2[np.argsort(x_2)], c='darkorange', label=r'Real $\lambda$ in $T_{i, 2}$')

for i in range(1000):
    a, b = trace_3['a'][i], trace_3['b'][i]
    line = a * x_3 + b
    plt.plot(x_3, line, c='aquamarine', alpha=0.02)
plt.plot(x_3, data_3, 'x', c='darkgreen', label='Observations in $T_{i, 3}$')
plt.plot(x_3, a_3 * x_3 + b_3, c='k')
plt.plot(np.sort(x_3), l_3[np.argsort(x_3)], c='darkgreen', label=r'Real $\lambda$ in $T_{i, 3}$')

for i in range(1000):
    a, b = trace_4['a'][i], trace_4['b'][i]
    line = a * x_4 + b
    plt.plot(x_4, line, c='lightpink', alpha=0.02)
plt.plot(x_4, data_4, 'x', c='darkred', label='Observations in $T_{i, 4}$')
plt.plot(np.sort(x_4), l_4[np.argsort(x_4)], c='darkred', label=r'Real $\lambda$ in $T_{i, 4}$')
plt.plot(x_4, a_4 * x_4 + b_4, c='k', label='Model')

plt.xlim(0.5, 15)
plt.legend()
plt.title('Estimation of demand curves in each sub-period')
plt.xlabel('Price')
plt.ylabel('Demand');
rint(b_1, b_2, b_3, b_4)
plt.figure(figsize=(12, 4))
for i in range(1000):
    a, b = trace_1['a'][i], trace_1['b'][i]
    line = a * x_1 + b
    plt.plot(x_1, line, c='cyan', alpha=0.02)
plt.plot(x_1, data_1, 'x', c='darkblue', label='Observations in $T_{i, 1}$')
plt.plot(x_1, a_1 * x_1 + b_1, c='k')
plt.plot(np.sort(x_1), l_1[np.argsort(x_1)], c='darkblue', label=r'Real $\lambda$ in $T_{i, 1}$')

for i in range(1000):
    a, b = trace_2['a'][i], trace_2['b'][i]
    line = a * x_2 + b
    plt.plot(x_2, line, c='bisque', alpha=0.02)
plt.plot(x_2, data_2, 'x', c='darkorange', label='Observations in $T_{i, 2}$')
plt.plot(x_2, a_2 * x_2 + b_2, c='k')
plt.plot(np.sort(x_2), l_2[np.argsort(x_2)], c='darkorange', label=r'Real $\lambda$ in $T_{i, 2}$')

for i in range(1000):
    a, b = trace_3['a'][i], trace_3['b'][i]
    line = a * x_3 + b
    plt.plot(x_3, line, c='aquamarine', alpha=0.02)
plt.plot(x_3, data_3, 'x', c='darkgreen', label='Observations in $T_{i, 3}$')
plt.plot(x_3, a_3 * x_3 + b_3, c='k')
plt.plot(np.sort(x_3), l_3[np.argsort(x_3)], c='darkgreen', label=r'Real $\lambda$ in $T_{i, 3}$')

for i in range(1000):
    a, b = trace_4['a'][i], trace_4['b'][i]
    line = a * x_4 + b
    plt.plot(x_4, line, c='lightpink', alpha=0.02)
plt.plot(x_4, data_4, 'x', c='darkred', label='Observations in $T_{i, 4}$')
plt.plot(np.sort(x_4), l_4[np.argsort(x_4)], c='darkred', label=r'Real $\lambda$ in $T_{i, 4}$')
plt.plot(x_4, a_4 * x_4 + b_4, c='k', label='Model')

plt.xlim(0.5, 15)
plt.legend()
plt.title('Estimation of demand curves in each sub-period')
plt.xlabel('Price')
plt.ylabel('Demand');

В очередной раз следует отметить, что апостериорные распределения представляют единственную ценность на этом графике — только они позволяют найти оптимальные цены, квоты (пределы продаж) и тип нестинга. Возможно ли добиться того же самого с помощью бутстрапа? Нет. Будь данных даже несколько больше, они всегда будут обладать структурой, которая не позволит рационально генерировать из них выборки. Из-за невозможности адекватной оценки распределений параметров и появляются всевозможные простые эвристики.

Под эвристиками в данном случае понимаются алгоритмы, в которых относительно случайности делаются некоторые допущения. Например, в методах прогнозирования BP (Booking Profile) и расчета оптимальных пределов продаж EMSR (Expected Marginal Seat Revenue) спрос сугубо нормальный, а правила выполнения расчетов настолько простые, что их можно выполнить даже без калькулятора. Эти алгоритмы не могли появиться другими, просто потому, что в те времена не было достаточных вычислительных ресурсов. Однако, практика использования серьезных допущений в алгоритмах используется по сей день.

Чем же тогда вероятностное программирование лучше всех остальных методов? Сначала спрос моделировался как:

\mathrm{Demand}_{i, j} \sim \mathrm{Poisson}(\lambda_{j} = at_{j} + b).

Потом чуть сложнее:

\mathrm{Demand}(i, j, \mathrm{Price}) \sim \mathrm{Poisson}(\lambda_{i, j} = a_{i, j}\mathrm{Price} + b_{i, j}).

Все равно недостаточно близко к реальности. Что будет, если данных станет больше? Чем больше данных, тем сложнее модели, которые можно строить. Модель может включать как номер подпериода, так и стоимость следующим образом:

\mathrm{Demand}(i, j, \mathrm{Price}) \sim \mathrm{Poisson}(\lambda_{i, j} = (\theta_{1} t_{j} + \theta_{2}) \times e ^{(\theta_{3} t_{j} + \theta_{4})\times \mathrm{Price}} + \theta_{5}).

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

Давайте рассмотрим простую модель конкуренции: допустим, есть две авиакомпании, которые перевозят пассажиров по одному и тому же маршруту, используя при этом одинаковые типы самолетов. Если пассажиропоток по этому маршруту начал расти, то одна из авиакомпаний может решить заменить используемый тип самолета на самолет с большей емкостью, что позволит ей снизить стоимость билетов и добиться большего конкурентного преимущества. Перед авиакомпанией встает важный вопрос: каким должно быть \Delta \mathrm{Price} — снижение цены?

Цена должна целиком опираться на данные о спросе. Однако проблема заключается в том, что пассажиры пользуются услугами двух авиакомпаний, а не одной — то есть имеют альтернативу. Это, в свою очередь, значит, что они делают выбор на основе функции полезности, которая у каждого пассажира своя. Рассмотрим пример для детального понимания: допустим, вы пользуетесь услугами одной из авиакомпаний очень давно, участвуете в программе лояльности, накопили много миль и имеете только положительный опыт — может получиться так, что вы даже не узнаете, что компания конкурент снизила стоимость. Даже если и узнаете, то \Delta \mathrm{Price} — разница в 15-30 долларов все равно не переубедит вас сменить перевозчика. С другой стороны, для обычного среднестатистического студента разница даже в 5 долларов может оказаться решающей.

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

P(\mathrm{Transition} = 1 | \Delta \mathrm{Price}) = \frac{1}{1 + e^{\beta_{0}+\beta_{1}\Delta \mathrm{Price}}}.

В данном случае предполагается, что вероятность зависит от разницы цен и определяется всего двумя параметрами. Преимущество снижения цены будет наиболее оптимальным, если как можно быстрее и точнее узнать значения параметров \beta_{0} и \beta_{1}. Априорное распределение здесь может оказаться очень кстати. Самый простой способ его построения — это постараться сегментировать клиентов конкурента, прикинуть количество клиентов в каждом сегменте и предположить, как каждый из них будет относиться к изменению цены. Разумные оценки можно получить даже из собственного предыдущего опыта конкурирования. Еще один способ заключается в анализе сторонних данных, если их можно найти: например, авиакомпания может состоять в альянсе и запросить данные о похожих ситуациях у его членов.

Другой авиакомпании тоже придется снижать стоимость билетов, но из-за функции предпочтения у каждого клиента ей вовсе не придется делать эту стоимость равной или ниже стоимости конкурента. В то же время это снижение не будет оптимальным, если не знать \beta_{0} и \beta_{1}. Даже если использовать байесовский подход, то без дополнительной информации для априорного распределения придется придерживаться индифферентного подхода. Это значит, что разумные оценки для \beta_{0} и \beta_{1} могут быть получены позже конкурента, и потери все равно окажутся больше, чем могли бы быть.

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

"Какой должна быть величина скидки?" — снова вопрос моделирования и оценки параметров созданной модели. Увеличение количества клиентов в зависимости от размера скидки может быть представлено простой линейной регрессией. Повторно возникает вопрос наискорейшего и правильного оценивания параметров с помощью априорного распределения. Разумным источником информации может послужить демографический и экономический анализ населения, а сторонняя информация может позволить определить вспомогательную зависимость между социально-экономическими показателями и влиянием размера скидки на продажи.

Целенаправленные скидки и специальные тарифы — это понимание клиентской базы. Помимо сегментирования важно понимать, что стратегии поведения у разных сегментов тоже разные: например, пассажиры могут искать лучшие предложения как вертикально — билеты от одной авиакомпании на разные даты, так и горизонтально — на одну дату у разных авиакомпаний. Есть те, кто купит билет прямо сейчас, но есть и те, кто уже знает, что цена может быть очень волатильной, и будет либо ждать снижения, либо купит возвратный билет. Для пассажиров всегда есть некоторый риск: если не купить заранее, то потом билетов может просто не оказаться, либо они будут стоить дороже, чем рассчитывалось. Кто-то предпочитает страховать такой риск заблаговременной покупкой и готов заплатить за это чуть больше.

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

Конкуренция в Байесовской парадигме

Байесовская парадигма имеет гораздо больший потенциал, чем можно подумать. Рассмотрим модель конкуренции между двумя авиакомпаниями чуть подробнее, чтобы продемонстрировать это.

Допустим, есть некоторый авиамаршрут со случайным пассажиропотоком, средняя величина которого равна 200 пассажиров и две авиакомпании A_{1} и A_{2}, которые делят его пополам, используя самолеты одинаковой емкости для перевозки v_{1} = v_{2} = 100. Если самолеты абсолютно идентичны в плане себестоимости полетов, то для авиакомпаний становится выгодным именно равновесие, а не конкуренция. Если \mathrm{Price} — это стоимость авиабилетов, которая равна 1500 у.е., а C — это себестоимость одного полета, которая составляет 140000 у.е., то прибыль каждой авиакомпании будет составлять 10000 у.е.:

\mathrm{Profit}_{1} = \mathrm{Profit}_{2} = \mathrm{Price} \times d - C_{1} = 1500 \times 100 - 140000 = 10000,

где d — это спрос, который у каждой авиакомпании в среднем равен емкости самолета. Для каждой авиакомпании очевидно, что 20k у.е. лучше, чем 10k, но рациональная конкуренция подразумевает наличие преимущества у одной авиакомпании по отношению к другой в виде некоторых ресурсов. Что будет, если у одной авиакомпании такое преимущество появится?

Например, авиакомпания A_{2} может заменить исходный самолет на самолет большей емкости: теперь v_{2} равняется 120-ти пассажирам, а себестоимость C_{2} рейса на таком самолете теперь равна 145000. Если не изменять цены авиабилетов, то никакого преимущетсва не будет, потому что в этом случае прибыль первой авиакомпании останется неизменной:

\mathrm{Profit}_{1} = 1500 \times 100 - 140000 = 10000,

а прибыль второй авиакомпании существенно снизится:

\mathrm{Profit}_{2} = 1500 \times 100 - 145000 = 5000.

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

P(\mathrm{Transition} = 1 | \Delta) = \frac{1}{1 + e^{-2 \frac{\Delta - \beta_{0}}{\beta_{1}}}},

где \beta_{1} — задает смещение кривой по горизонтали, \beta_{2} — определяет крутизну ее наклона, а \Delta — это разность цен, выраженная в процентах. Пусть \beta_{1} = 7, а \beta_{2} = 2, тогда кривая будет выглядеть следующим образом:

Python
x = np.linspace(0, 20, 500)

def p_tr(x, h1=7, h2=2):
    '''
    h1 - bias;
    h2 - slope steepness
    '''
    return 1 / (1 + np.exp(-2*((x - h1)/h2)))
    

plt.plot(x, p_tr(x), lw=3)
x_opt = x[np.argmin(np.abs(0.2 - p_tr(x)))]
plt.axvline(x_opt, c = 'r', lw=3, label=r'$\Delta^{*}$' + f' = {x_opt:.3f}%')
plt.legend()
plt.xlabel(r'$\Delta^{*}$ (%)')
plt.ylabel('p', rotation=0)
plt.title('Discount Sensitivity Curve');

Красная вертикальная линия показывает величину оптимальной скидки для A_{2}. Оптимальность в данном случае означает, что скидка позволяет "переманить" 20% клиентов у A_{1}, а значит — добиться того, чтобы значение спроса d_{2} стало равным емкости самолета v_{2}. Поскольку величина общего спроса в среднем равна общей емкости самолетов, то дальнейшее увеличение скидки хоть и приведет к дальнейшему "переманиванию" клиентов, но их просто будет некуда сажать. Это, в свою очередь, приведет к тому, что билеты на рейсы A_{2} будут исчезать из продажи раньше, чем билеты A_{1}. Отсутствие билетов конкурента при наличии спроса предоставит для A_{1} возможность продавать оставшиеся билеты по завышенной цене и уменьшить негативный эффект от больших скидок A_{2}.

Если A_{2} снизит стоимость билетов на 5.61%, то доходы авиакомпаний существенно изменятся:

\mathrm{Profit}_{1} = 1500 \times 80 - 140000 = -20000,\mathrm{Profit}_{2} = (1 - 0.0561) \times 1500 \times 120 - 145000 = 24902.

Несмотря на простоту этой арифметики, можно сделать несколько выводов. Во-первых, после замены самолета у A_{2} даже без снижения ею цены доходы снизились не так сильно, как можно было бы ожидать — по крайней мере они не стали отрицательными. Объединение задач RMS и EMS (Enterprise Management Systems) — то есть задач управления доходностью с операционными задачами, например, задачей расстановки флота, — может дать преимущество на "ровном месте". Это значит, что умение решать сложные задачи само по себе является источником множества преимуществ. Во-вторых, A_{2} может воспользоваться своим преимуществом оптимально только в том случае, если она способна быстро и точно установить параметры кривой чувствительности к скидкам. Это еще раз говорит в пользу использования методов байесовской статистики.

A_{1} стала терпеть большие убытки, но это еще не конец конкурентной борьбы. Поскольку рейсы A_{1} стали убыточными, ей необходимо брать откуда-то средства для покрытия текущих издержек — это могут быть либо собственные накопления, либо займы в банке, либо что-то еще. Назовем этот источник средств фондом F. Например, если рейсы по данному маршруту выполняются раз в неделю, а объем фонда F_{1} составляет 200k у.е., то авиакомпания сможет продержаться всего 10 недель. Если она не примет никаких ответных действий, после этого срока самолет больше не сможет выполнять рейсы. Конкурентная борьба закончится поражением, либо когда одна из сторон полностью исчерпает свой фонд, либо когда одна из сторон сама предпочтет выйти из борьбы, тем самым сохранив какую-то часть своего фонда.

Чтобы избежать убытков авиакомпании A_{1}, достаточно снизить стоимость вслед за A_{2}. Исходя из кривой чувствительности к скидкам, она также может сделать это оптимально:

Python
price_a2 = (1 - 0.0561) * 1500     # стоимость билета A_2
price_a1 = np.linspace(1500, price_a2 - 10)    # стоимость билета A_1
x_a1 = 100 * (price_a1 - price_a2) / price_a1    # скидка A_1
# прибыль А_1:
profit_a1 = (1 - p_tr(x_a1))* 100 * price_a1 - 140000 
plt.plot(price_a1, profit_a1, lw=3)
plt.axvline(price_a2, c='g', lw=3, label=r'$A_{2}$: price' + f' = {price_a2:.0f}')
price_a1_opt = price_a1[np.argmax(profit_a1)]
plt.axvline(price_a1_opt, c='r', lw=3, label=r'$A_{1}$: price$^{*}$' + f' = {price_a1_opt:.0f}')
plt.ylabel('Income (c.u.)')
plt.xlabel('Price (c.u.)')
plt.legend()
plt.title(r'Dependence of $A_{1}$ income on price');

Как видим, из-за нелинейного характера кривой чувствительности к скидкам, авиакомпании A_{1} вовсе необязательно снижать стоимость билетов до уровня, установленного авиакомпанией A_{2}. Разницы цен в 2.48% достаточно для того, чтобы переманить 19 из 20 пассажиров обратно, т.е. практически всех.

\mathrm{Profit}_{1} = 1452 \times 99 - 140000 \approx 3621,\mathrm{Profit}_{2} = 1416 \times 101 - 145000 = -1984.

A_{2} снова может снизить стоимость, на этот раз она может сделать ее на 5.57% меньше, чем цена, установленная A_{1}, то есть 1369 у.е.:

\mathrm{Profit}_{1} = 1452 \times 80 - 140000 = -23840,\mathrm{Profit}_{2} = 1369 \times 120 - 145000 = 19291.

В ответ A_{1} снова придется снизить цену до 1402 у.е. — так, чтобы разность составляла всего 2.4%. Это снова позволит переманить обратно 19 пассажиров из 20, но после этого прибыли будут выглядеть уже следующим образом:

\mathrm{Profit}_{1} = 1403 \times 99 - 140000 = -1103,\mathrm{Profit}_{2} = 1369 \times 101 - 145000 = -6731.

Теперь настал по-настоящему острый момент конкурентной борьбы: A_{1} снизила цену, чтобы уменьшить убытки, но такого оптимального ответа оказалось недостаточно. В то же время убытки A_{2} оказались очень велики — намного больше, чем у A_{1}, и ей нужно решить, что делать дальше. Теперь успех авиакомпаний зависит от объема фонда каждой из них, но и тут не все так просто, как хотелось бы.

Важно отметить, что чувствительность к скидкам обладает собственной эластичностью по цене. Например, если рейсы авиакомпании A_{1} выполняются по более удобному расписанию, то по мере того, как стоимость билетов будет снижаться все сильнее, пассажиры будут предпочитать летать рейсами A_{1} гораздо чаще — даже несмотря на большую скидку от A_{2}. Параметры \beta_{1} и \beta_{2} должны иметь некоторую обратно пропорциональную зависимость от \mathrm{Price}, а это значит, что каждое действие авиакомпаний влияет на спрос и что это влияние снова нужно быстро анализировать. Также это значит, что преимущество в конкурентной борьбе определяется не только объемом фонда, но еще и тем, кто сделал первый ход — ведь на определенном этапе оптимальное снижение цены может оказаться хоть и действительно оптимальным, но абсурдным по своей величине.

Если последний ход остается за A_{2} и чувствительность к скидкам действительно эластична по цене, то влияние размера скидки на прибыли авиакомпаний может запросто выглядеть следующим образом:

Python
x = np.linspace(0, 25, 600)
p_trans = p_tr(x, h1=22, h2=3)
d_a1 = (1 - p_trans) * 99
d_a1[d_a1<80] = 80
d_a2 = 101 + p_trans * 99
d_a2[d_a2 > 120] = 120
prof_a1 = 1403 * d_a1 - 140000
prof_a2 = (1 - x / 100) * 1403 * d_a2 - 145000

plt.plot(x, prof_a1, c='C3', lw=3, label='A1')
plt.plot(x, prof_a2, c='C4', lw=3, label='A2')

plt.axvline(x[0], ls='--', c='C2', label=r'$\Delta^{*}_{1} = 0$')
plt.plot(x[0], prof_a1[0], 'ko')
plt.plot(x[0], prof_a2[0], 'ko')

x_opt_2 = np.argmax(np.diff(prof_a2)) + 1
plt.axvline(x[x_opt_2], ls='--', c='C1', label=r'$\Delta^{*}_{2}=$' + f'{x[x_opt_2]:.2f}%')
plt.plot(x[x_opt_2], prof_a1[x_opt_2], 'ko')
plt.plot(x[x_opt_2], prof_a2[x_opt_2], 'ko')
plt.ylabel('Income (c.u.)')
plt.xlabel(r'$\Delta$ (%)')
plt.legend(loc='lower left')
plt.title('Dependence of airline revenues on price changes');

Действие A_{2} можно рассматривать как последний ход — ведь если она решится снизить цены на 20%, то это приведет к сильному увеличению убытков A_{1}. Из-за эластичности кривой чувствительности к скидкам ответное действие A_{1} может уже не привести к приемлемому для нее уровню убытков. Теперь оптимальное действие A_{2} зависит от величины ее фонда F_{2} и того, что она знает о величине фонда F_{1}, которым располагает A_{1}. Если записать, что F_{1} = k F_{2}, то можно легко выяснить, как необходимое для победы значение k будет зависеть от \Delta:

Python
delta = x[x <= x[x_opt_2]]

K = []
k = np.linspace(0, 20, 500)
for i in range(len(delta)):
    K.append(k[np.argmin(np.abs(k * prof_a1[i] / prof_a2[i] - 1))])

plt.plot(delta, K, lw=3)
plt.axhline(0, c='k')
plt.axhline(1, ls='--', c='0.6')

plt.ylabel(r'$k$', rotation=0)
plt.xlabel(r'$\Delta$ (%)');

Из графика следует: если фонд F_{2} в три раза больше, чем F_{1}, то можно вообще не предпринимать никаких действий по дальнейшему снижению цены — в этом случае, несмотря на то, что убытки A_{2} больше, чем убытки A_{1}, фонд F_{1} закончится быстрее, чем F_{2}. Самое интересное в данном графике то, что A_{2} может выиграть в конкурентной борьбе, даже если ее фонд F_{2} будет меньше, чем у A_{1}, причем в несколько раз. Естественно, выигрыш A_{2} в этом случае будет связан с большим риском, ведь для этого придется очень сильно снизить цену, но это все еще возможно.

Кооперация вместо конкуренции

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

Пусть авиакомпаниям A_{1} и A_{2} необходимо найти оптимальный план продаж для трех предстоящих рейсов. Емкость самолетов на всех рейсах одинакова и составляет 100 мест, пассажиропоток в предстоящий период составляет 800 путешественников.

Вероятность покупки билета по некоторой цене подчиняется закону N(100, 15^{2}):

Python
d_price = np.linspace(50, 150, 300)
d_val = norm.pdf(d_price, loc=100, scale=15)
plt.plot(d_price, d_val)
plt.title('pdf of purchasing a ticket at a certain price',
          fontsize=10);

Предпочтения путешественников конкретных рейсов выражаются следующими вероятностями:

  • P(\mathrm{Flight} = 1) = 0.2 — низкий спрос на 1-й рейс;

  • P(\mathrm{Flight} = 2) = 0.5 — высокий спрос на 2-й рейс;

  • P(\mathrm{Flight} = 3) = 0.3 — средний спрос на 3-й рейс.

Пусть пассажиры придерживаются 4-х разных стратегий и делятся на следующие типы:

  • 1 — безразличны как к цене, так и информации о количестве доступных мест, но не номеру рейса. Они всегда покупают билет только на необходимый рейс только у той авиакомпании, которую выбрали, даже если у нее стоимость билета больше, чем у другой авиакомпании. Их оптимальная стратегия — улететь необходимым рейсом при любых условиях.

  • 2 — безразличны к информации о количестве доступных мест, но не цене. Они купят билет только на выбранный ими рейс, но купят его у авиакомпании с наименьшей стоимостью.

  • 3 — предпочитают страховать риск остаться без билета. Если они видят, что количество доступных билетов на выбранный ими рейс невелико, то они его купят с большей вероятностью.

  • 4 — предпочитают экономить и при выборе номера рейса больше руководствуются низкой ценой и большим количеством доступных мест.

Принадлежность пассажира к определенному типу задается следующими вероятностями:

  • P(\mathrm{TravelerType} = 1) = 0.1;

  • P(\mathrm{TravelerType} = 2) = 0.15;

  • P(\mathrm{TravelerType} = 3) = 0.25;

  • P(\mathrm{TravelerType} = 4) = 0.5.

Каждый тип при этом обладает рядом особенностей.

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

  • P(\mathrm{A = A_{1} | TravelerType} = 1) = 0.7;

  • P(\mathrm{A = A_{2} | TravelerType} = 1) = 0.3.

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

Python
x = np.linspace(-20, 20, 500)

def p_tr(x, h1=-0.5, h2=7):
    return 1 / (1 + np.exp(-2*((x - h1)/h2)))
    

plt.plot(x, p_tr(x))

plt.xlabel(r'$\Delta$ (%)')
plt.ylabel('p', rotation=0)
plt.title(r'Dependence of the probability of choosing the $A_{1}$' +\
          '\non the price difference', fontsize=10);

Причем предпочтения у данного типа пассажиров также немного смещены в сторону первой авиакомпании.

Вероятность покупки билета у пассажиров третьего типа определяется количеством средств, которыми они располагают:

P(\mathrm{Purchase} = 1 | \mathrm{TravelerType} = 3) = \int_{\mathrm{Price}}^{+\infty} \frac {1}{15 {\sqrt {2\pi }}}e^{-{\frac {1}{2}}\left({\frac {x-100 }{15}}\right)^{2}}.

Данная вероятность увеличивается на следующую величину:

\Delta P = k(1 - P(\mathrm{Purchase} = 1 | \mathrm{TravelerType} = 3)),

где коэффициент k зависит от количества доступных мест a:

Python
x = np.linspace(0, 15)
y = np.exp(-0.2*x)
plt.plot(x, y, 'C4-')
x = np.r_[0:15]
y = np.exp(-0.2*x)
plt.plot(x, y, 'C4o')
plt.xlabel(r'$a$')
plt.ylabel('k', rotation=0)
plt.title(r'Dependence of $k$ on $a$', fontsize=10);

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

Вероятность покупки билета для пассажиров четвертого типа также определяется законом распределения N(100, 15^{2}):

P(\mathrm{Purchase} = 1 | \mathrm{TravelerType} = 3) = \int_{\mathrm{Price}}^{+\infty} \frac {1}{15 {\sqrt {2\pi }}}e^{-{\frac {1}{2}}\left({\frac {x-100 }{15}}\right)^{2}}.

Данная вероятность увеличивается на величину:

\Delta P = r(1 - P(\mathrm{Purchase} = 1 | \mathrm{TravelerType} = 3)),

где коэффициент r зависит от количества доступных мест следующим образом:

Python
x = np.linspace(0, 15)
y = 1 - np.exp(-0.3*x)
plt.plot(x, y, 'C4-')
x = np.r_[0:15]
y = 1 - np.exp(-0.3*x)
plt.plot(x, y, 'C4o')
plt.ylabel('r', rotation=0)
plt.title(r'Dependence of $k$ on $a$', fontsize=10);

Такая зависимость дает надежду на то, что цена может со временем уменьшиться. Чем меньше стоимость и чем больше доступно мест — тем больше для них смысла ждать, что цена упадет.

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

Как обычно, сделаем для моделирования ряд функций и вспомогательных скриптов:

Python
# Функция, моделирующая процесс продажи одного билета:
def selling(prices_a1, prices_a2, avail_a1, avail_a2):
    n_flight = np.random.choice([0, 1, 2], size=1, p=[0.2, 0.5, 0.3])[0]
    traveler_type = np.random.choice([0, 1, 2, 3], size=1, p=[0.1, 0.15, 0.25, 0.5])[0]

    if traveler_type == 0:
        n_airline = bernoulli.rvs(p=0.7)        
        if n_airline == 0:
            return n_airline, n_flight, prices_a1[n_flight]
        else:
            return n_airline, n_flight, prices_a2[n_flight]

    if traveler_type == 1:
        discount = 100 * (prices_a1[n_flight] - prices_a2[n_flight]) / prices_a1[n_flight]
        p_a1 = p_tr(discount)
        n_airline = bernoulli.rvs(p=p_a1)
        if n_airline == 0:
            return n_airline, n_flight, prices_a1[n_flight]
        else:
            return n_airline, n_flight, prices_a2[n_flight]

    if traveler_type == 2:
        p_purchase_a1 = 1 - norm.cdf(prices_a1, loc=100, scale=15)
        k = norm.cdf(prices_a1, loc=100, scale=15) * np.exp(-0.2 * avail_a1)
        p_purchase_a1 = k + p_purchase_a1
        n_flight_a1 = np.random.choice([0, 1, 2], p=p_purchase_a1/np.sum(p_purchase_a1))
        p_purchase_a1 = p_purchase_a1[n_flight_a1]
        
        p_purchase_a2 = 1 - norm.cdf(prices_a2, loc=100, scale=15)
        k = norm.cdf(prices_a2, loc=100, scale=15) * np.exp(-0.2 * avail_a2)
        p_purchase_a2 = k + p_purchase_a2
        n_flight_a2 = np.random.choice([0, 1, 2], p=p_purchase_a2/np.sum(p_purchase_a2))
        p_purchase_a2 = p_purchase_a2[n_flight_a2]
        
        if p_purchase_a1 == p_purchase_a2:
            n_airline = bernoulli.rvs(p=0.5)
            if n_airline == 0:
                if bernoulli.rvs(p=p_purchase_a1) == 1:
                    return 0, n_flight_a1, prices_a1[n_flight_a1]
            else:
                if bernoulli.rvs(p=p_purchase_a2) == 1:
                    return 1, n_flight_a2, prices_a2[n_flight_a2]
        elif p_purchase_a1 > p_purchase_a2:
            if bernoulli.rvs(p=p_purchase_a1) == 1:
                return 0, n_flight_a1, prices_a1[n_flight_a1]
        else:
            if bernoulli.rvs(p=p_purchase_a2) == 1:
                return 1, n_flight_a2, prices_a2[n_flight_a2]

    if traveler_type == 3:
        p_purchase_a1 = 1 - norm.cdf(prices_a1, loc=100, scale=15)
        k = norm.cdf(prices_a1, loc=100, scale=15) * (1 - np.exp(-0.3 * avail_a1))
        p_purchase_a1 = k + p_purchase_a1
        n_flight_a1 = np.random.choice([0, 1, 2], p=p_purchase_a1/np.sum(p_purchase_a1))
        p_purchase_a1 = p_purchase_a1[n_flight_a1]
        
        p_purchase_a2 = 1 - norm.cdf(prices_a2, loc=100, scale=15)
        k = norm.cdf(prices_a2, loc=100, scale=15) * (1 - np.exp(-0.3 * avail_a1))
        p_purchase_a2 = k + p_purchase_a2
        #print(p_purchase_a2)
        n_flight_a2 = np.random.choice([0, 1, 2], p=p_purchase_a2/np.sum(p_purchase_a2))
        p_purchase_a2 = p_purchase_a2[n_flight_a2]
        
        if p_purchase_a1 == p_purchase_a2:
            n_airline = bernoulli.rvs(p=0.5)
            if n_airline == 0:
                if bernoulli.rvs(p=p_purchase_a1) == 1:
                    return 0, n_flight_a1, prices_a1[n_flight_a1]
            else:
                if bernoulli.rvs(p=p_purchase_a2) == 1:
                    return 1, n_flight_a2, prices_a2[n_flight_a2]
        elif p_purchase_a1 > p_purchase_a2:
            if bernoulli.rvs(p=p_purchase_a1) == 1:
                return 0, n_flight_a1, prices_a1[n_flight_a1]
        else:
            if bernoulli.rvs(p=p_purchase_a2) == 1:
                return 1, n_flight_a2, prices_a2[n_flight_a2]

# Функция вычисляющая среднюю прибыль:
def f(p1, p2, a1, a2, n_iter):
    res_profit = []
    
    for i in range(n_iter):
        data = [selling(p1, p2, a1, a2) for _ in range(800)]
        data = np.array([row for row in data if row != None])
        data_a1 = data[data[:, 0] == 0]
        data_a2 = data[data[:, 0] == 1]
    
        occupancy_a1 = np.unique(data_a1[:, 1], return_counts=True)[1]
        occupancy_a2 = np.unique(data_a2[:, 1], return_counts=True)[1]
        res_profit_a1 = np.sum(p1 * np.clip(occupancy_a1, 0, 100))
        res_profit_a2 = np.sum(p2 * np.clip(occupancy_a2, 0, 100))
        res_profit.append(res_profit_a1 + res_profit_a2)
    return(np.mean(res_profit))

# Софтмакс Тейлора
def tsm(z):
    numerator = 1 + z + 0.5*z**2
    denominator = np.sum(1 + z + 0.5*z**2)
    return numerator / denominator

# Функция, вычисляющая вероятностное распределение цен
# по выборке наилучших кандидатов:
def hist_ps(res_vals):
    bins = np.r_[0:16]
    h = tsm(np.histogram(res_vals, bins=bins)[0])
    return h

# Функция генерирующая вектор цен на основе 
# заданного распределения:
def rand_ps(h):
    indices = np.r_[1:16]
    vec = np.random.choice(indices, size=1, p=h)[0]
    return vec

# Скрипт выполняющий оптимизацию методом кросс-энтропии:
'''
mu_01, mu_11, mu_21 = 100, 100, 100
mu_02, mu_12, mu_22 = 100, 100, 100

std_01, std_11, std_21 = 10, 10, 10
std_02, std_12, std_22 = 10, 10, 10

h_01 = np.array([1/15]*15)
h_11 = np.array([1/15]*15)
h_21 = np.array([1/15]*15)
h_02 = np.array([1/15]*15)
h_12 = np.array([1/15]*15)
h_22 = np.array([1/15]*15)

N = 50
for i in range(25):
    p01 = norm.rvs(loc=mu_01, scale=std_01, size = N)
    p11 = norm.rvs(loc=mu_11, scale=std_11, size = N)
    p21 = norm.rvs(loc=mu_21, scale=std_21, size = N)
    p1 = np.vstack((p01, p11, p21)).T
    
    p02 = norm.rvs(loc=mu_02, scale=std_02, size = N)
    p12 = norm.rvs(loc=mu_12, scale=std_12, size = N)
    p22 = norm.rvs(loc=mu_22, scale=std_22, size = N)
    p2 = np.vstack((p02, p12, p22)).T
    
    a_01 = np.array([rand_ps(h_01) for _ in range(N)])
    a_11 = np.array([rand_ps(h_11) for _ in range(N)])
    a_21 = np.array([rand_ps(h_21) for _ in range(N)])
    a1 = np.vstack((a_01, a_11, a_21)).T
    
    a_02 = np.array([rand_ps(h_02) for _ in range(N)])
    a_12 = np.array([rand_ps(h_12) for _ in range(N)])
    a_22 = np.array([rand_ps(h_22) for _ in range(N)])
    a2 = np.vstack((a_02, a_12, a_22)).T
    
    R = np.array([f(p1[i], p2[i], a1[i], a2[i], 5) for i in range(N)])
    idx = np.argsort(R)[::-1][:10]
    
    mu_01, mu_11, mu_21 = np.mean(p01[idx]), np.mean(p11[idx]), np.mean(p21[idx])
    mu_02, mu_12, mu_22 = np.mean(p02[idx]), np.mean(p12[idx]), np.mean(p22[idx])
    std_01, std_11, std_21 = np.std(p01[idx]), np.std(p11[idx]), np.std(p21[idx])
    std_02, std_12, std_22 = np.std(p02[idx]), np.std(p12[idx]), np.std(p22[idx])
    
    h_01 = hist_ps(a_01[idx])
    h_11 = hist_ps(a_11[idx])
    h_21 = hist_ps(a_21[idx])
    h_02 = hist_ps(a_02[idx])
    h_12 = hist_ps(a_12[idx])
    h_22 = hist_ps(a_22[idx])
''';

В результате оптимизации получаем следующее:

Python
prices_a1 = np.array([115, 115, 107.5])
prices_a2 = np.array([116, 117, 113.5])
avail_a1 = np.array([15, 11, 13])
avail_a2 = np.array([8, 2, 3])

  • Цены на рейсы A_{1} — [115, 115, 107.5].

  • Цены на рейсы A_{2} — [116, 117, 113.5].

  • Информация о количестве доступных мест на рейсы A_{1} — [15, 11, 13].

  • Информация о количестве доступных мест на рейсы A_{2} — [8, 2, 3].

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

  • A_{1} — взяла на себя четвертый тип пассажиров, которые предпочитают ждать лучшие цены. Она публикует информацию о том, что доступных билетов много, и смогла подобрать для них наилучшие цены.

  • A_{2} — стала ориентироваться на тех, кто предпочитает страховать свой риск. Она публикует информацию о том, что билетов мало, и также нашла для них оптимальную стоимость.

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

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

Средний суммарный доход авиакомпаний составляет 67100 у.е.:

Python
res_oc_a1 = []
res_oc_a2 = []
res_profit_a1 = []
res_profit_a2 = []

for i in range(100):
    data = [selling(prices_a1, prices_a2, avail_a1, avail_a2) for _ in range(800)]
    data = np.array([row for row in data if row != None])
    data_a1 = data[data[:, 0] == 0]
    data_a2 = data[data[:, 0] == 1]

    occupancy_a1 = np.unique(data_a1[:, 1], return_counts=True)[1]
    occupancy_a2 = np.unique(data_a2[:, 1], return_counts=True)[1]
    res_oc_a1.append(occupancy_a1)
    res_oc_a2.append(occupancy_a2)
    res_profit_a1.append(prices_a1 * np.clip(occupancy_a1, 0, 100))
    res_profit_a2.append(prices_a2 * np.clip(occupancy_a2, 0, 100))

res_oc_a1 = np.array(res_oc_a1)
res_oc_a2 = np.array(res_oc_a2)
res_profit_a1 = np.array(res_profit_a1)
res_profit_a2 = np.array(res_profit_a2)

np.mean(res_profit_a1.sum(axis=1) + res_profit_a2.sum(axis=1))
66899.575

Если взглянуть на kde доходов авиакомпаний, то можно заметить некоторую асимметрию — одна авиакомпания зарабатывает больше другой:

Python
fig, ax = plt.subplots(1, 3, figsize=(9, 3), layout="constrained")
sns.kdeplot(res_profit_a1.sum(axis=1), ax=ax[0])
ax[0].axvline(np.mean(res_profit_a1.sum(axis=1)), c='C3')
sns.kdeplot(res_profit_a2.sum(axis=1), ax=ax[1])
ax[1].axvline(np.mean(res_profit_a2.sum(axis=1)), c='C3')
sns.kdeplot(res_profit_a1.sum(axis=1) + res_profit_a2.sum(axis=1), ax=ax[2])
ax[2].axvline(np.mean(res_profit_a1.sum(axis=1) + res_profit_a2.sum(axis=1)), c='C3')

ax[0].set_title('Revenues of the A1', fontsize=10)
ax[1].set_title('Revenues of the A2', fontsize=10)
ax[1].set_ylabel('')
ax[2].set_title('Total income', fontsize=10)
ax[2].set_ylabel('');

Попытки как-то уравнять доходы, так же, как и конкуренция, приведут к снижений общей суммарной прибыли. Кооперация действительно может быть выгоднее конкуренции, и в этом нет ничего удивительного, поскольку авиакомпании делят между собой общий спрос. Это разделение тоже может быть оптимальным и отсылает нас к задаче об оптимальном распределении ресурсов, в данном случае — нагрузки, и задаче о справедливом (оптимальном) разрезании торта.

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

В данной модели предполагается, что лишь небольшая часть спроса безразлична к информации о количестве доступных мест — всего 25%. Сколько на самом деле таких пассажиров — можно выяснить только на основе данных. Если предположить, что таких пассажиров гораздо больше, то положительный эффект от манипулирования спросом посредством информации о количестве доступных мест будет значительно уступать эффекту от оптимальной ценовой политики.

Любопытно то, что доход от первого рейса в обеих авиакомпаниях сопоставим с доходами от других рейсов или даже чуть выше, хотя спрос на него составляет всего 20% от общего пассажиропотока:

Python
fig, ax = plt.subplots(1, 2, figsize=(7, 3), layout="constrained")
ax[0].bar(x=[1, 2, 3], height=res_profit_a1.mean(axis=0), color=['C1', 'C2', 'C3'], label='1')
ax[0].set_xlabel('flight number')
ax[0].set_ylabel('income')
ax[0].set_title('A1')

ax[1].bar(x=[1, 2, 3], height=res_profit_a2.mean(axis=0), color=['C1', 'C2', 'C3'], label='1')
ax[1].set_xlabel('flight number')
ax[1].set_ylabel('income')
ax[1].set_title('A2');

Добиться этого удалось с помощью информации о количестве доступных мест. Для первого рейса каждой авиакомпании количество доступных мест должно принимать максимально возможное значение. Чтобы "перебросить" часть спроса на первый рейс, обеим авиакомпаниям вовсе не пришлось снижать количество доступных мест для 2-го и 3-го рейса вместе — это сделала только A_{2}. Изначально могло показаться, что это должны были сделать обе авиакомпании.

Рациональный прогноз без умения решать подобные задачи может действительно приводить к тому, что обе авиакомпании будут вступать в сговор и делать то, что им только лишь кажется оптимальным. Учитывая, что спрос на первый рейс кажется очень небольшим (20%), то идея полного изъятия из продажи билетов на какое-то время на 2-й и 3-й рейс может показаться еще более разумной. Если взглянуть на заполняемость рейсов, то идея может показаться единственно верной, а не просто разумной:

Python
fig, ax = plt.subplots(2, 3, figsize=(9, 3), layout="constrained")
sns.kdeplot(res_oc_a1[:, 0], ax=ax[0][0])
sns.kdeplot(res_oc_a1[:, 1], ax=ax[0][1])
sns.kdeplot(res_oc_a1[:, 2], ax=ax[0][2])
ax[0][0].set_ylabel('A1', rotation=0, fontsize=10, labelpad=10)
ax[0][1].set_ylabel('')
ax[0][2].set_ylabel('')

ax[0][0].set_title('Flight №1', fontsize=10)
ax[0][1].set_title('Flight №2', fontsize=10)
ax[0][2].set_title('Flight №3', fontsize=10)

sns.kdeplot(res_oc_a2[:, 0], ax=ax[1][0])
sns.kdeplot(res_oc_a2[:, 1], ax=ax[1][1])
sns.kdeplot(res_oc_a2[:, 2], ax=ax[1][2])
ax[1][0].set_ylabel('A2', rotation=0, fontsize=10, labelpad=10)
ax[1][1].set_ylabel('')
ax[1][2].set_ylabel('')

for a in ax.flatten():
    a.axvline(100, c='C3');

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

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

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

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

Приведенная выше модель показывает как преимущества кооперации, так и преимущества объединения задач RMS и EMS (Enterprise Management Systems). Сейчас мировая гражданская авиация делает большую ставку на массовое использование узкофюзеляжных самолетов. Появление в парке значительного количества самолетов такого типа как раз и направлено на то, чтобы более эффективно подстраиваться под спрос за счет цены и расстановки флота по маршрутам.

Данные и понимание потребителя

Управление доходностью основывается на том, как клиент воспринимает ценность товара или услуги. Можно ли обойтись без данных, чтобы понять клиентов? Парадоксально, но да! Байесовскую статистику часто критикуют за субъективизм, но не стоит забывать, что для нас субъективизм — это опыт + знания + логика. Благодаря им априорные убеждения могут оказаться очень близкими к реальности.

Клиентов проще всего понять с помощью функции полезности, но мы попробуем убить двух зайцев сразу: выясним чем и как руководствуются путешественники при покупке авиабилетов, а заодно адаптируем это представление для оптимизации планирования продаж. Пусть некий путешественник намерен лететь из пункта A в пункт B: он открывает метапоисковик и в первую очередь обращает свое внимание на \mathrm{Price} — стоимость билета. Предположим, что функция полезности, которой руководствуется путешественник, имеет следующий вид:

U(\mathrm{Price}) =  1 - \frac{1}{1 + e^{-2 \frac{\mathrm{Price} - \theta_{1}}{\theta_{2}}}}.

Это уже хорошо знакомая нам сигмоида. Если \theta_{1} будет равна 10, а \theta_{2} равна 5, то график функции будет таким:

Python
def U(price, theta_1, theta_2):
    return -1 / (1 + np.exp(-2*(price - theta_1)/theta_2)) + 1

theta_1, theta_2 = 100, 10

price = np.linspace(80, 130, 500)
y = U(price, theta_1, theta_2)
plt.plot(price, y, lw=3, c='C0')

plt.axvline(95, ls='--', c='C1', label='Price = 95 c.u.')
plt.plot(95, U(95, theta_1, theta_2), 'C1o', ms=7, markeredgecolor='w')
plt.axvline(100, ls='--', c='C2', label='Price = 100 c.u.')
plt.plot(100, U(100, theta_1, theta_2), 'C2o', ms=7, markeredgecolor='w')
plt.axvline(110, ls='--', c='C3', label='Price = 110 c.u.')
plt.plot(110, U(110, theta_1, theta_2), 'C3o', ms=7, markeredgecolor='w')

plt.xlabel('Price (c.u.)')
plt.ylabel('U(Price)')
plt.legend()
plt.title('Dependence of ticket utility on price');

Данная функция примечательна тем, что после некоторых значений полезность достигает определенных пределов, после которых ее значение не меняется. Если путешественник располагает суммой, близкой к 100 долларам, то билеты, которые стоят 5 или 25 долларов, будут иметь очень большую, но практически одинаковую для него полезность. Также билеты, которые стоят 150 или 500 долларов, будут иметь пренебрежимо малую, хоть и отличную друг от друга полезность. Естественно, путешественник хочет максимизировать свою функцию полезности: если он увидит три разных предложения с ценами 95, 100 и 110 долларов, то выберет то, что дешевле.

Значения U(\mathrm{Price}) находятся в диапазоне от 0 до 1 — такое свойство вовсе не является обязательным, но оно оказывается очень полезным, так как позволяет интерпретировать полезность как вероятность. Если событие \mathrm{Purchase} = 1 обозначает покупку билета, а путешественник видит только одно предложение по цене \mathrm{Price}, то:

P(\mathrm{Purchase} = 1 | \mathrm{Price}) = U(\mathrm{Price}).

Если i — это номер предложения, которое пробегает от 1 до n, и путешественник видит несколько предложений с ценами \mathrm{Price}_{1}, \mathrm{Price}_{2}, ..., \mathrm{Price}_{n}, то вероятность покупки билета по i-му предложению будет равна:

P_{i}(\mathrm{Purchase} = 1 | \mathrm{NumOffer} = i, \mathrm{Price}_{i}) = \frac{U(\mathrm{Price}_{i})}{\sum_{i = 1}^{n}U(\mathrm{Price}_{i})}.

Говоря о вероятности, мы полагаем, что стратегия поведения для каждого путешественника известна, но является случайной, поскольку мы не можем обладать всей необходимой информацией. Из-за этого в модели и возникает неопределенность. С некоторыми оговорками представленную функцию полезности можно воспринимать как кривую спроса — для этого достаточно принять, что индивидуальное значение параметров \theta_{1} и \theta_{2} в функции полезности становится матожиданием соответствующих параметров для разных путешественников — то есть E(\theta_{1}) и E(\theta_{2}). Теперь можно легко перейти к задаче ценообразования, которая в обобщенной форме может быть записана в виде уже знакомой простой целевой функции:

\mathrm{Profit} = \mathrm{Price} \times U(\mathrm{Price}) \to \underset{\mathrm{Price}}{\mathrm{max}}
Python
price = np.linspace(60, 130, 500)
y = price * U(price, theta_1, theta_2)
plt.plot(price, y)
plt.xlabel('Price (c.u.)')
plt.ylabel('Profit')
plt.title('Dependence of profit on price');

Можно пойти еще дальше и предположить: функция полезности зависит не только от цены, но и от оставшегося до вылета времени t. Обычно считается, что по мере приближения даты вылета спрос увеличивается и многие пассажиры готовы платить больше за возможность перелета. В этом случае параметры \theta_{1} и \theta_{2} должны быть зависимыми от времени. Пусть \theta_{1} и \theta_{2} зависят от времени следующим образом:

\theta_{1} = a_{1}t + b_{1} = -2t + 120,\theta_{2} = a_{2}t + b_{2} = -t + 20

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

def U(price, t):
    theta_1 = -2*t + 120
    theta_2 = -t + 20
    return -1 / (1 + np.exp(-2*(price - theta_1)/theta_2)) + 1



price = np.linspace(80, 150, 500)

plt.plot(price, U(price, t=10), lw=3, c='C1', label=r'$t = 10$')
plt.plot(price, U(price, t=5), lw=3, c='C2', label=r'$t = 5$')
plt.plot(price, U(price, t=1), lw=3, c='C3', label=r'$t = 1$')



plt.xlabel('Price (c.u.)')
plt.ylabel('U(Price)')
plt.legend()
plt.title('The influence of time on the utility function');
Python

Присутствие в модели такой переменной, как время, и наличие рационального прогноза делает модель пригодной для оптимизации динамического ценообразования. Помимо этой очевидной пользы, она пригодна для объяснения причин роста или падения трафика. Обычно в планировании продаж речь идет об альтернативах в рамках одной категории: например, сейчас мы рассматриваем только авиабилеты с разной стоимостью. Однако может случиться так, что, помимо самолета, из A в B можно добраться на поезде или на автобусе, и эти предложения тоже могут рассматриваться при формировании предпочтений. Допустим, у нас есть простое предположение: все путешественники воспринимают полезность поездки на автобусе одинаково и считают, что она не выше 0.2.

Python
def U(price, t):
    theta_1 = -2*t + 120
    theta_2 = -t + 20
    return -1 / (1 + np.exp(-2*(price - theta_1)/theta_2)) + 1

price = np.linspace(80, 150, 500)

plt.axvline(110, ls='--')

plt.plot(price, U(price, t=10), lw=3, c='C1', label=r'$t = 10$')
plt.plot(110, U(110, t=10), 'C1o', ms=7, markeredgecolor='w')
plt.plot(price, U(price, t=5), lw=3, c='C2', label=r'$t = 5$')
plt.plot(110, U(110, t=5), 'C2o', ms=7, markeredgecolor='w')
plt.plot(price, U(price, t=1), lw=3, c='C3', label=r'$t = 1$')
plt.plot(110, U(110, t=1), 'C3o', ms=7, markeredgecolor='w')

plt.axhspan(0, 0.2, color='r', alpha=0.2)

plt.xlabel('Price (c.u.)')
plt.ylabel('U(Price, t)')
plt.legend()
plt.title('Preference change area');

График демонстрирует: если в определенное время показать путешественнику неправильную цену, то он с большой долей вероятности предпочтет поездку на автобусе вместо перелета. Если цена очень волатильна и часто оказывается в "красной" зоне, то у путешественника может появиться стратегия выжидания лучшей цены, но слишком большие отклонения могут оказаться триггерами к смене предпочтений той или иной категории транспорта.

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

  • Если в периоде от 14 до 7 дней до даты вылета продано 90% мест от емкости самолета (или от квоты подкласса), то увеличить стоимость на 40%, если продано 80% мест — увеличить на 20%; ...; если продано всего 40% мест — снизить стоимость билета на 10%; если продано 30% мест — снизить стоимость на 15%; если продано 20% мест — снизить на 20%.

  • Если в периоде от 7 до 2 дней до даты вылета продано всего 70% мест от емкости самолета (или от квоты подкласса), то снизить стоимость билета на 10% и так далее.

Такой алгоритм может быть задан в виде простой таблицы условий, которая называется сценарием. Сценариев может быть много, и можно выбирать наиболее подходящие. Каждая ячейка в таком сценарии может восприниматься как прогноз величины спроса на следующий период продаж, полученный на основе только что прошедшего периода. В краткосрочной перспективе и при условии стационарности спроса такой алгоритм вполне жизнеспособен. Однако спрос не обязан быть стационарным: его эластичность по цене с течением времени может меняться самым разным образом. Если в каком-то периоде продано билетов больше обычного, то это вовсе не гарантирует, что в следующем периоде спрос будет еще больше. Однако, согласно сценарию, цена все равно будет увеличена.

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

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

В предыдущем примере было показано, что с помощью информации о количестве доступных мест можно управлять продажами и даже добиться роста прибыли. Однако в модели из предыдущего примера не было времени — хотя очевидно, что оно также влияет на ощущение дефицита. Пусть t, как и прежде, — это оставшееся время до вылета, а q — это информация о количестве доступных мест. Тогда ощущение дефицита можно выразить в виде следующей функции:

D(t, q) = \kappa \left ( 1 - \frac{1}{1 + e^{-2 \frac{t - \theta_{3}}{\theta_{4}}} + e^{-2 \frac{q - \theta_{5}}{\theta_{6}}}} \right ).

Взглянем на функцию, чтобы убедиться в том, что она является подходящей. Пусть \kappa = 10, \theta_{3} = 1, \theta_{4} = 2, \theta_{5} = 3 и \theta_{6} = 4, тогда график функции будет выглядеть следующим образом:

Python
x = np.linspace(1, 15, 50)
y = np.linspace(1, 15, 50)
X, Y = np.meshgrid(x, y)
Z = 10 * (1 - 1 / (1 + np.exp(-2*(X - 1)/2) + np.exp(-2*(Y - 3)/4)))

fig, ax = plt.subplots(figsize=(5, 4))
CS = ax.contour(X, Y, Z)
ax.clabel(CS, inline=True, fontsize=10)
PM = ax.pcolormesh(X, Y, Z, cmap='cool')
ax.set_title('Feeling of scarcity')
ax.set_ylabel('q', rotation=0)
ax.set_xlabel('t')
fig.colorbar(PM);

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

Ощущение дефицита заставляет путешественника переоценить предложение, что также легко продемонстрировать: пусть D(t, q) влияет на функцию полезности следующим образом:

U(\mathrm{Price}, t, q) =  1 - \frac{1}{1 + D(t, q) \cdot e^{-2 \frac{\mathrm{Price} - \theta_{1}}{\theta_{2}}}}.

Это означает, что U(\mathrm{Price}, t, q) будет смещаться вправо, не изменяя крутизны своего наклона:

Python
def U(price, t, q, k=10, th_3=1, th_4=2, th_5=3, th_6=4):
    theta_1 = -2*t + 120
    theta_2 = -t + 20
    D = k * (1 - 1 / (1 + np.exp(-2*(t - th_3)/th_4) + np.exp(-2*(q - th_5)/th_6)))
    return -1 / (1 + D * np.exp(-2*(price - theta_1)/theta_2)) + 1

price = np.linspace(80, 160, 500)

plt.plot(price, U(price, t=5, q=10), c='C0', ls='--', label='t = 5, q = 10')
price_1 = 115
u_1, u_2 = U(price_1, t=5, q=10), U(price_1, t=5, q=2)
plt.axvline(price_1, color='0.6', ls='--')
plt.plot(price_1, u_1, 'C3o', ms=7, markeredgecolor='w')
plt.plot(price_1, u_2, 'C0o', ms=7, markeredgecolor='w')

plt.plot(price, U(price, t=5, q=2), c='C0', label='t = 5, q = 2')
price_2 = 140
u_1, u_2 = U(price_2, t=5, q=10), U(price_2, t=5, q=2)
plt.axvline(price_2, color='0.6', ls='--')
plt.plot(price_2, u_1, 'C3o', ms=7, markeredgecolor='w')
plt.plot(price_2, u_2, 'C3o', ms=7, markeredgecolor='w')

plt.axhspan(0, 0.2, color='r', alpha=0.2)

plt.legend()
plt.xlabel('Price (c.u.)')
plt.ylabel('U(Price, t, q)')
plt.title('The influence of the perception of scarcity\non the utility function');

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

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

  • "Осталось 2 места";

  • "Осталось 2 места по этой цене";

  • "Всего осталось 15 мест, доступно 2 билета по этой цене".

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

Пожалуй, единственный существенный недостаток в полученной функции полезности заключается в том, что ощущение дефицита может заставить путешественника выбирать те рейсы, на которые осталось меньше всего билетов, а не рейсы с нужной датой вылета. Если обозначить \Delta \tau период времени между необходимой путешественнику датой вылета и датой вылета рейса, то полезность рейса R(\Delta \tau) может быть оценена следующим образом:

R(\Delta \tau) =  e^{-\frac{1}{2} \left ( \frac{\Delta \tau}{\theta_{7}} \right )^{2}}.

Это обычная колоколообразная функция, которая при \theta_{7} = 3 будет выглядеть следующим образом:

Python
x = np.linspace(-10, 10, 500)
sigma = 3
y = np.exp(-0.5 * (x / sigma)**2)
plt.plot(x, y)
plt.xlabel(r'$\Delta \tau$')
plt.ylabel(r'$R(\Delta \tau)$')
plt.title('Usefulness of the flight');

График показывает: чем сильнее отличие даты вылета рейса от необходимой путешественнику, тем меньше он оценивает его полезность. Добавить R(\Delta \tau) в функцию полезности довольно просто:

U(\mathrm{Price}, t, q, \Delta \tau) =  1 - \frac{1}{1 + R(\Delta \tau) \cdot D(t, q) \cdot e^{-2 \frac{\mathrm{Price} - \theta_{1}}{\theta_{2}}}}.

Чем меньше R(\Delta \tau), тем сильнее функция U(\mathrm{Price}, t, q, \Delta \tau) будет сдвигаться влево.

Итак, мы получили функцию полезности для нашего путешественника. Что с ней делать и для чего она вообще может пригодиться? Вспомним, что управление доходностью — это как раз и есть понимание того, как клиент воспринимает ценность товара или услуги. Полученная модель логична, ведь она учитывает наши знания и опыт. Благодаря ей можно легко понять, во что обойдутся неверные решения, почему самолеты летают полупустыми, почему у конкурента дела идут лучше, чем у нас. Модель нельзя назвать идеальной, как и большинство моделей в других науках. Цель моделирования же состоит в том, чтобы понять более сложные системы и процессы. С помощью полученной функции полезности можно легко объяснить сходство пассажиропотоков с потоками жидкости, которые текут интенсивнее там, где меньше всего препятствий. Именно это позволяет решать задачи более сложного операционного характера, например, оптимального расширения маршрутной сети за счет интерлайн и код-шер соглашений.

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

Вероятностная взаимосвязь задач RMS и EMS

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

Рассмотрим простую задачу. Раз в неделю по рейсу A-B летает всего один самолет с емкостью, равной сотне мест: V = 100. Интенсивность общего недельного пассажиропотока D составляет 500 человек — под ним понимается совокупное количество людей, которые проявляют интерес к приобретению билета. Например, это может быть общая недельная посещаемость метапоисковиков и сайта авиакомпании для проверки стоимости билета. Казалось бы, что к такой информации очень легко получить доступ, но многие компании до сих пор видят только продажи, а не всех потенциальных покупателей. Поскольку вероятность того, что какому-то жителю в городе A или в городе B понадобится совершить полет, очень мала, то пассажиропоток можно смоделировать распределением Пуассона со средней интенсивностью \lambda, равной 500:

D \sim \mathrm{Poisson}(\lambda = 500).

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

U(\mathrm{Price}) =  1 - \frac{1}{1 + e^{-2 \frac{\mathrm{Price} - \theta_{1}}{\theta_{2}}}}.

Пусть \theta_{1} =100, а \theta_{2} = 50, тогда функция полезности будет выглядеть следующим образом:

Python
def U(price, theta_1, theta_2):
    return -1 / (1 + np.exp(-2*(price - theta_1)/theta_2)) + 1

theta_1, theta_2 = 100, 50

price = np.linspace(80, 150, 500)
y = U(price, theta_1, theta_2)
plt.plot(price, y, lw=3, c='C0');

Данная кривая показывает, какова вероятность покупки билета по определенной цене:

P(\mathrm{Purchase}=1 | \mathrm{Price}) = U(\mathrm{Price}).

Случайная величина \mathrm{Purchase} может быть описана распределением Бернулли:

\mathrm{Purchase} \sim \mathrm{Bernoulli}(p = U(\mathrm{Price})).

Поскольку известна средняя интенсивность, то количество купленных билетов \mathrm{Q} на рейс может быть описано с помощью биномиального распределения:

\mathrm{Q} \sim \mathrm{Bin}(n = \lambda, p = U(\mathrm{Price})),

которое при больших n хорошо приближается нормальным распределением:

\mathrm{Bin}(n = \lambda, p = U(\mathrm{Price})) \approx N(np, np(1 - p)).

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

Средний доход от рейса будет вычисляться по довольно простой формуле:

E(\mathrm{Income}|\mathrm{Price}) = \mathrm{Price} \int_{-\infty}^{V} Q \cdot \varphi(Q | \mathrm{Price})dQ + \mathrm{Price} \cdot V \int_{V}^{+\infty} \varphi(Q | \mathrm{Price})dQ,

где \varphi(Q | \mathrm{Price}) — это условная pdf нормального распределения. Необходимо найти такое значение \mathrm{Price}^{*}, при котором значение функции будет максимальным:

Python
def e_profit(flow, buy_prob, Q, price):
    mu, sigma = flow * buy_prob, (flow * buy_prob * (1 - buy_prob))**0.5
    x =  np.linspace(0, 200, 3000)
    y = norm.pdf(x, mu, sigma)
    p_over = 1 - norm.cdf(Q, mu, sigma)
    X, Y = x[x<Q], y[x<Q]
    e_prof_less = price * np.trapz(X * Y, X)
    res_e_profit = e_prof_less + Q * price * p_over

    return res_e_profit

prices = np.linspace(120, 140, 100)
buy_probs = U(prices, theta_1=100, theta_2=50)
#plt.plot(prices, buy_probs);

profits = np.array([e_profit(flow=500, buy_prob=buy_probs[i], Q=100,
                             price=prices[i]) for i in range(len(prices))])

plt.plot(prices, profits, 'C0', lw=2)
opt_price = prices[np.argmax(profits)]
plt.plot(opt_price, profits.max(), 'bo', markeredgecolor='w', zorder=10)
plt.axvline(prices[np.argmax(profits)], c='C0', ls='--', label=f'Price*={opt_price:.1f}')
plt.axhline(12500, color='C3', lw=2, alpha=0.6)
plt.legend()
plt.ylabel('Income')
plt.xlabel('Price')
plt.title('Dependence of average income on price');

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

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

Python
fig, ax = plt.subplots(1, 2, figsize=(9, 3))

prices = np.linspace(120, 140, 300)
buy_probs = U(prices, theta_1=100, theta_2=50)

profits = np.array([e_profit(flow=500, buy_prob=buy_probs[i], Q=100,
                             price=prices[i]) for i in range(len(prices))])

ax[0].plot(prices, profits, 'C0', lw=2)
opt_price = prices[np.argmax(profits)]
ax[0].plot(opt_price, profits.max(), 'bo', markeredgecolor='w', zorder=10)
buy_probs = U(prices, theta_1=100, theta_2=50)
ax[0].axhline(12500, color='C3', lw=2, alpha=0.6, zorder=10)

profit_max = []
flow = np.arange(400, 505, 5)
for f in flow:
    profits = np.array([e_profit(flow=f, buy_prob=buy_probs[i], Q=100,
                                 price=prices[i]) for i in range(len(prices))])
    ax[0].plot(prices[profits>11900], profits[profits>11900], c='0.7', zorder=1)
    ax[0].plot(prices[np.argmax(profits)], profits.max(), 'C0o', markeredgecolor='w')
    profit_max.append(profits.max())

ax[0].set_ylim(11900, 13300)
ax[0].set_ylabel('Income')
ax[0].set_xlabel('Price')
ax[1].axhline(12500, color='C3', lw=2, alpha=0.6, zorder=10)
ax[1].plot(flow, profit_max, 'C0o-', ms=6, markeredgecolor='w')
ax[1].set_xlabel('Passenger flow')

fig.suptitle('Dependence of average income on price and passenger flow');

Рейс перестает быть рентабельным, только когда пассажиропоток падает ниже 415 человек в неделю.

С точки зрения рационального продавца в представленном примере нет ничего удивительного: средний доход может быть максимизирован с помощью правильной цены, но если пассажиропоток упадет на 17-18 процентов, то рейс станет нерентабельным. Даже странно предполагать, что подобную ситуацию можно воспринимать как-то по другому. А что, если кто-то и правда воспринимает ее совсем иначе? Как подобная ситуация может выглядеть с точки зрения иррационального продавца?

Для ответа на эти вопросы необходимо понять, какие основные характерные черты свойственны для иррациональных продавцов авиабилетов.

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

Во-вторых, можно предположить: если продавец знает и понимает то, как спрос реагирует на стоимость, то ценообразование должно быть хоть как-то детерминировано. Для представленной модели оптимальная цена только одна. Единственный смысл коррекции цены заключается только в уменьшении дисперсии среднего дохода и заполняемости самолета. Сдвинуть среднюю прибыль с помощью корректировки цен, увы, никак не получится. Это значит, что и изменения цен должны начинаться в каком-то промежутке времени, а дисперсия изменений цены также должна находиться в разумных пределах. Однако зачастую динамика цен может не очень сильно отличаться от такого:

Python
bad_prices = uniform.rvs(loc=130, scale=30, size=10)
N = poisson.rvs(mu=500)
var_idx = np.sort(np.random.randint(0, N, size=9))
num_show = np.diff(np.hstack(([0], var_idx, [N])))
prices = np.hstack([[bad_prices[i]] * num_show[i] for i in range(10)])
plt.figure(figsize=(8, 2))
plt.plot(prices)
plt.ylabel('Price')
plt.xlabel('Price display number')
plt.title('Random price change');

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

В-четвертых, иррациональный продавец должен в какой-то мере руководствоваться эмоциями. Если при повышении цены эмоциональный продавец не видит ожидаемой реакции спроса на стоимость, то это становится причиной новых ошибок. Можно увеличить стоимость, чтобы ограничить скорость заполняемости самолета, но, если цена слишком высока и если продержать ее слишком долго, то скорость заполняемости может оказаться слишком маленькой. Чтобы наверстать упущенное в дальнейшем придется сильно снизить стоимость. Самое сложное для человека — это хладнокровное следование стратегии, даже если кажется, что что-то идет не так, как должно.

Предположим, что в нашей модели иррациональный продавец может менять стоимость всего 10 раз на протяжении всего периода продаж. Каждое изменение цены является случайным и подчиняется равномерному распределению. Область значений распределения будет соответствовать высоким ценам, например:

\mathrm{Price} \sim U(a=130, b=140).

Такой продавец будет думать, что доход зависит только от пассажиропотока, поскольку все изменения цены, с его точки зрения, и так направлены на максимальный выжим дохода. Хотя в гораздо большей мере его доход будет зависеть от того, как сильно его цена отличается от оптимальной и как часто она изменяется. Если предположить, что левая граница интервала возможных значений цен неизменна и составляет 130 у.е., то с увеличением правой границы b доход будет меняться следующим образом:

Python
def bad_profit(flow, a, b):
    N = poisson.rvs(mu=flow)
    var_idx = np.sort(np.random.randint(0, N, size=9))
    num_show = np.diff(np.hstack(([0], var_idx, [N])))
    bad_prices = uniform.rvs(loc=a, scale=b, size=10)
    prices = np.hstack([[bad_prices[i]] * num_show[i] for i in range(10)])
    buys = bernoulli.rvs(p=U(prices, theta_1=100, theta_2=50))
    pays = buys * prices
    profit = np.sum(pays[np.cumsum(buys) < 100])
    return profit

def e_bad_profit(flow, b, s_size):
    return np.mean([bad_profit(flow, 130, b) for _ in range(s_size)])

b = np.arange(0, 25)
res_profit = [e_bad_profit(500, b_i, s_size=1000) for b_i in b]
plt.plot(b+130, res_profit, 'C0o-', ms=5, markeredgecolor='w')

plt.ylabel('Income')
plt.xlabel('b - upper limit of the price range [130, b]')
plt.title('Dependence of average income on the range of random price changes');

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

Python
fig, ax = plt.subplots(1, 2, figsize=(9, 3))

prices = np.linspace(120, 140, 300)
buy_probs = U(prices, theta_1=100, theta_2=50)

profits = np.array([e_profit(flow=500, buy_prob=buy_probs[i], Q=100,
                             price=prices[i]) for i in range(len(prices))])

ax[0].plot(prices, profits, 'C0', lw=2)
opt_price = prices[np.argmax(profits)]
ax[0].plot(opt_price, profits.max(), 'bo', markeredgecolor='w', zorder=10)
buy_probs = U(prices, theta_1=100, theta_2=50)
ax[0].axhline(12500, color='C3', lw=2, alpha=0.6, zorder=10)

profit_max = []
price_opt = []
flow = np.arange(400, 505, 5)
for f in flow:
    profits = np.array([e_profit(flow=f, buy_prob=buy_probs[i], Q=100,
                                 price=prices[i]) for i in range(len(prices))])
    ax[0].plot(prices[profits>11900], profits[profits>11900], c='0.7', zorder=1)
    
    price_opt.append(prices[np.argmax(profits)])
    profit_max.append(profits.max())

ax[0].plot(price_opt, profit_max, 'C0o', markeredgecolor='w',
               label='Optimized')

profits_b5 = np.array([e_bad_profit(f, 5, 1000) for f in flow])
profits_b10 = np.array([e_bad_profit(f, 10, 1000) for f in flow])
profits_b15 = np.array([e_bad_profit(f, 15, 1000) for f in flow])
ax[0].plot(np.full_like(profits_b5, 132.5), profits_b5, 'C1o', markeredgecolor='w',
           label='U(130, 135)')
ax[0].plot(np.full_like(profits_b10, 135), profits_b10, 'C2o', markeredgecolor='w',
           label='U(130, 140)')
ax[0].plot(np.full_like(profits_b15, 137.5), profits_b15, 'C4o', markeredgecolor='w',
           label='U(130, 145)')
ax[0].legend()
ax[0].set_ylabel('Income')
ax[0].set_xlabel('Price')

ax[1].axhline(12500, color='C3', lw=2, alpha=0.6, zorder=10)
ax[1].plot(flow, profit_max, 'C0o-', ms=6, markeredgecolor='w',
           label='Optimized')
ax[1].plot(flow, profits_b5, 'C1o-', ms=6, markeredgecolor='w',
           label='U(130, 135)')
ax[1].plot(flow, profits_b10, 'C2o-', ms=6, markeredgecolor='w',
           label='U(130, 140)')
ax[1].plot(flow, profits_b15, 'C4o-', ms=6, markeredgecolor='w',
           label='U(130, 145)')
ax[1].legend()
ax[1].set_xlabel('Passenger flow')

fig.suptitle('Dependence of average income on price and passenger flow');

Казалось бы, что пример не заслуживает рассмотрения, ведь вывод из него очевиден: E(\mathrm{Income} | \mathrm{Price}, \mathrm{Flow}) лучше, чем E(\mathrm{Income} | \mathrm{Flow}). Как всегда, не все так просто. Условное матожидание E(\mathrm{Income} | \mathrm{Price}, \mathrm{Flow}), показывает, что доход зависит от независимого параметра \mathrm{Flow} — пассажиропотока и управляемого параметра \mathrm{Price}, с помощью которого авиакомпания может реагировать на изменения \mathrm{Flow}. Условное матожидание E(\mathrm{Income} | \mathrm{Flow}) означает ригидность авиакомпании, а именно отсутствие у нее каких бы то ни было способностей реагировать на изменения окружающей среды.

Сейчас это кажется каким-то безумием, но на самом деле это вполне обыденное и даже в какой-то мере нормальное явление. Если задуматься об истории развития задачи по расстановке флота, то можно заметить, что по мере движения от FAM к ODFAM, а от нее к IFAM, каждый раз добавлялись все новые управляющие факторы, которые до этого просто не учитывались в силу самых разных и непреодолимых причин.

  • В FAM спрос, с точки зрения математической статистики, является очень простой величиной — это просто среднее арифметическое значений за какой-то конкретный период.

  • В ODFAM начал учитываться не просто общий спрос, а спрос на конкретные продукты (цена + направление).

  • В IFAM спрос начал рассматриваться как пассажиропотоки, которые могут влиять друг на друга.

Модели развиваются по мере развития возможностей науки и технологий. Иррациональность — это когда причины неиспользования возможностей являются преодолимыми, но они не преодолеваются.

Ригидность компании может стать причиной крайне плохих решений. Например, большинство операционных задач, таких как расстановка флота по маршрутам или формирование цепочек рейсов, с точки зрения исследования операций содержат средний доход, прибыль или средние убытки в качестве исходных данных. Авиакомпания, уверенная в том, что ее средняя прибыль зависит только от пассажиропотока, может запросто закрывать некоторые рейсы или даже сокращать задействованный флот воздушных судов, когда для этого нет никаких причин.

Ригидность — это главная причина таких феноменов, как иррациональная экономия и иррациональная оптимизация, которые распространены настолько, что нередко становятся предметом научных исследований. Если авиакомпания решила сократить задействованный флот воздушных судов, то она может счесть вполне разумным использовать незадействованную часть флота в качестве источника "бесплатных" запчастей. Ригидность — это не просто причина плохих решений, это еще и основной симптом отсутствия долгосрочной стратегии. Стратегия — это как минимум простое понимание того, что будущее есть и оно неминуемо наступит и что будущее зависит от прошлого, но его реализация возможна только через настоящее. Отсутствие долгосрочной стратегии означает, что есть только условное "сейчас" или "сегодня". А если так, то в нижеследующей цепочке событий нет ничего иррационального:

  • Сокращаем задействованный флот — потому что сейчас это выгодно.

  • Пускаем незадействованный флот на запчасти — потому что сейчас это выгодно.

  • Продаем остатки авиакомпании конкурентам за бесценок — потому что сейчас это выгодно.

Использование E(\mathrm{Income} | \mathrm{Flow}) означает, что действительность воспринимается как нечто случайное и абсолютно недетерминированное. Если так, то зачем предпринимать что-то осмысленное? Представленный пример — это крайне простая модель, которая абсолютно непригодна для прикладного применения. Однако, постойте, ведь та же RMS на основе сценариев — это не выдумка. Такую RMS можно найти и приобрести прямо сейчас. Это означает, что те, кто создал, и те, кто использует такую RMS, действительно воспринимают спрос именно так, как в представленном примере. Для них спрос — это стационарный случайный процесс. Более того, можно показать: если спрос стационарен, то использование подобных сценариев более чем рационально. Есть всего одна оптимальная цена, которая приводит к оптимальной средней прибыли, а сценарии с корректирующими ценами необходимы для того, чтобы уменьшить дисперсию прибыли и заполняемости. В реальном мире это — практически полная, эталонная иррациональность.

Часто плохие решения подаются и воспринимаются как ответ на обстоятельства непреодолимой трудности. Настоящая причина кроется же в банальном непонимании элементарных причин этих обстоятельств. Вспомним исторический экскурс из начала статьи: влиятельные люди в крупной авиакомпании долгое время воспринимали перевозку миллионов пустых кресел как нечто нормальное, но это было почти полвека назад. Сейчас такое точно невозможно, ведь сейчас у нас есть продвинутые математические методы и ПО. Очевидно, что действительность и правда может быть непреодолимо трудной, но такое оправдание было бы более-менее приемлемым также и полвека назад.

Первые работы о функциях полезности и предпочтениях, необходимые для понимания клиентов, относятся уже к 1920-годам. Знаменитая работа Джона Нэша о трудностях переговоров, пригодная для понимания поведения клиентов, увидела свет в 1950-м году. В 50-х годах появились первые работы по теории игр и исследованию операций, пригодные для понимания игр с природой — силой, на которую зачастую списываются все промахи. Как видите, оправдания "дремучести" было сложно найти даже полвека назад. Сейчас поиск оправданий, выставляющих плохие решения в хорошем свете, это практически невыполнимая задача, ведь у нас действительно есть продвинутые математические методы и ПО.

Необходимое для объединения RMS и EMS

Рассмотренный выше пример хоть и кажется элементарным, но именно он используется, чтобы продемонстрировать авиакомпаниям главное преимущество той или иной RMS. Сердце любой RMS — это прогнозный "движок". Достаточно просто показать, что есть гипотетическое реальное значение спроса \lambda, есть \Delta \lambda_{A} \sim N(0, \sigma_{A}) — ошибка прогноза авиакомпании, а есть вот \Delta \lambda_{\mathrm{RMS}} \sim N(0, \sigma_{\mathrm{RMS}}) — ошибка предлагаемой RMS. Поскольку \sigma_{A} < \sigma_{\mathrm{RMS}}, значит RMS нужно срочно приобретать. Для большей убедительности можно даже нарисовать графики подобные вышеприведенным. Какие еще могут быть варианты?

Предположим, авиакомпания приобретает RMS с лучшими прогнозными возможностями, которая устанавливает наилучшие цены по каждому продукту (подклассу), пределы продаж (квоты) и типы нестинга по каждому рейсу. Однако, что будет делать RMS, если спрос по некоторым рейсам начнет меняться очень сильно? Она точно так же продолжит находить наилучшие цены, пределы и нестинг. Она не покажет, какие самолеты на какие рейсы необходимо переназначить и каким образом это лучше всего сделать.

Наилучшей точкой соприкосновения задач RMS и EMS можно считать следующую целевую функцию:

\sum_{i \in L}\sum_{k \in K} c_{k, i}^{'} f_{k, i} + \sum_{p \in P}\sum_{r \in P} \left ( \mathrm{Fare}_{p}^{'} - b_{p}^{r} \mathrm{Fare}_{r}^{'} \right ) t_{p}^{r} \to \mathrm{min}.

Это так называемая задача IFAM (Itinerary-based Fleet Assignment Model — задача расстановки флота на основе маршрутов), которая лучше всего помогает понять, что же на самом деле нужно от RMS и с какими проблемами на самом деле сталкиваются авиакомпании.

Рассмотрим два маршрута A \to B \to C и A \to D \to C, составляющих множество P и индексируемых либо буквой p либо буквой r. Каждый маршрут состоит из сегментов: A \to B, B \to C, A \to D, D \to C. Допустим, спрос на маршрут A \to B \to C начал расти, а на маршрут A \to D \to C, наоборот, падать. Обозначим первый маршрут индексом p, а второй — индексом r. Очевидно дисбаланс спроса приводит к убыткам, особенно, если множество типов самолетов K состоит всего из одного элемента. По маршруту p — убытки от неудовлетворенного спроса, а по маршруту r — убытки от пустых кресел. Также очевидно: если скорректировать цены билетов \mathrm{Fare}_{p} и \mathrm{Fare}_{r} на p и r, то можно добиться того, что какая-то часть пассажиров предпочтет маршрут r вместо p.

Изначально значение \mathrm{Fare}_{p} равнялось 105 у.е., а \mathrm{Fare}_{r} равнялось 95 у.е. Если скорректированная цена \mathrm{Fare}_{p}^{'} теперь будет равняться 115 у.е., а \mathrm{Fare}_{r}^{'} будет равна 85 у.е. то какое-то количество пассажиров t_{p}^{r} предпочтет не лететь по маршруту p и будет перенаправлено в r. Даже если точно ответить на этот вопрос, то так или иначе перенаправить из p в r ровно столько, сколько необходимо, вряд ли получится. Лишь какая-то доля b_{p}^{r} от t_{p}^{r} действительно будет перенаправлена (0 \leqslant b_{p}^{r} \leqslant 1).

Чтобы проще понять модель IFAM, можно провести аналогию между пассажиропотоками и реальной жидкостью, а маршруты представить в виде системы водостоков. Если диаметр трубы p не позволяет какому-то объему жидкости пройти через него в заданный промежуток времени, то можно взять емкость, слить в нее лишний объем жидкости t_{p}^{r} и отнести эту емкость к трубе r. Какая-то часть жидкости во время переноса емкости обязательно прольется. Чтобы добиться успеха, необходимо, чтобы RMS могла прогнозировать, какая часть жидкости прольется мимо — (1 - b_{p}^{r})t_{p}^{r}, а какую часть жидкости удастся донести — b_{p}^{r}t_{p}^{r}.

Взглянем на второе слагаемое в представленной целевой функции отдельно:

\sum_{p \in P}\sum_{r \in P} \left ( \mathrm{Fare}_{p}^{'} - b_{p}^{r} \mathrm{Fare}_{r}^{'} \right ) t_{p}^{r}.

Это выражение как раз и представляет собой PMM-расширение и позволяет найти самое оптимальное распределение пассажиров по маршрутам. В то же время нетрудно догадаться, что сама PMM тоже может быть расширена. Во-первых, оптимизация может выполняться не на уровне маршрутов, а на уровне продуктов (подклассов бронирования), которые характеризуются как ценой, так и направлением. Например, маршрут p может быть представлен как минимум двумя продуктами: один подешевле, который продается сразу с момента начала продаж, другой — подороже, который продается незадолго до вылета. Это необходимо просто потому, что RMS действительно способны прогнозировать спрос на такие продукты.

Во-вторых, для расширения PMM необходимо, чтобы RMS могла отвечать не только на вопрос "Что будет?", но еще и на вопрос "Что будет, если?". Например, какими будут величины t_{p}^{r} и b_{p}^{r}, если \mathrm{Fare}_{p}^{'} — \mathrm{Fare}_{r}^{'} = \Delta? Такие вопросы так или иначе отсылают нас к функциям полезности пассажиров, с помощью которых они формируют свои предпочтения. Чем лучше наше представление о таких функциях, тем сложнее могут быть вопросы. Например, как t_{p}^{r} и b_{p}^{r} зависят от дельты цен и времени стыковки рейсов?

В-третьих, RMS должна уметь строить модель дискретного выбора пассажира. Представленные маршруты состоят из сегментов, на каждом из которых может летать более одного самолета. Самолеты могут быть как разного типа, так и принадлежать разным авиакомпаниям. Значит у пассажиров может быть более двух альтернатив — случай, в котором обычные биномиальные регрессионные модели не работают. Если обычная биномиальная регрессия помогает разобраться в том, как устроена нечестная монетка, то есть, как некие факторы влияют на выбор одной из двух альтернатив, то при наличии n альтернатив необходимо разобраться в том, как устроен нечестный кубик с n гранями — точнее в том, от чего именно зависит его нечестность.

Отдельно стоит задуматься о том, почему какая-то часть пассажиропотоков разливается мимо. В качестве альтернатив у пассажиров всегда есть другие категории транспорта, либо, в крайнем и самом плохом случае, альтернатива отказаться от путешествия. Если убрать PMM из IFAM, то получится обычная FAM модель, в которой минимизируются убытки, связанные только с c_{k, i} — затратами на использование самолета типа k на сегменте i. В данном случае считается, что действия авиакомпании никак не влияют на величину пассажиропотоков, и в лучшем случае используется случайное распределение спроса. FAM активно используется для оценки эффективности расписания: сокращение или добавление рейса — обычное действие.

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

Апострофы рядом с некоторыми величинами означают, что эти величины являются скорректированными — старые значения не рассматриваются вовсе. Отдельного упоминания заслуживает величина c_{k, i}^{'} — в данном случае рассматривается сумма затрат трех типов, связанных с:

  • аэропортовыми сборами, обслуживанием самолета и т.п.;

  • количеством пассажиров: дополнительный керосин, обработка багажа и т.п.;

  • неудовлетворением спроса и перевозкой пустых кресел.

Последний пункт кажется необязательным и вроде бы учитывается в PMM, но на самом деле он напрямую связан с фиксированной емкостью самолетов и учитывает потери, которые зависят от общего объема пассажиропотоков по некоторому маршруту. Так, например, слишком большое изменение цены может привести к тому, что значение b_{p}^{r}t_{p}^{r} окажется слишком большим, из-за чего на одном рейсе может оказаться слишком много пустых кресел, а на другом рейсе спрос будет значительно превосходить емкость самолета.

Подход к оценке c_{k, i}^{'} в IFAM — это крайне примечательная особенность. Пассажиропотоки оказывают нагрузку на маршрутную сеть авиакомпании, а оценка c_{k, i}^{'} позволяет справляться с ней более эффективно. Если по какому-то сегменту летает более одного самолета, то во многом благодаря c_{k, i}^{'} IFAM может использоваться для оценки общей согласованности назначений, которая характерна для конкурирующих авиакомпаний. Вообще, для нарушения согласованности вовсе необязательно, чтобы авиакомпании имели общие сегменты. Например, в случае представленных выше двух маршрутов: A \to B \to C и A \to D \to C, сегменты A \to B и A \to D могут принадлежать маршрутной сети одной авиакомпании, а сегменты B \to C и D \to C — другой авиакомпании. Изолированное рассмотрение сегментов общих частей маршрутов неминуемо приводит к несогласованным действиям, даже если обе авиакомпании используют IFAM, они хуже справляются с нагрузкой, значит получают намного меньше прибыли, чем могли бы.

Намного меньше прибыли значит, что цифры составляют порядка десятков процентов. Оценки эффективности внедрения моделей ODFAM в некоторых российских авиакомпаниях составляют порядка 30%, хотя эта она, в отличие от IFAM, производит оптимизацию доходов только на уровне продуктов без учета перенаправления пассажиропотоков. Оценки внедрения IFAM в некоторых зарубежных авиакомпаниях составляют порядка 25%, но это вовсе не означает, что эффективность IFAM на 5% хуже, чем ODFAM, — это означает, что 25% дополнительной прибыли появились после внедрения IFAM этими авиакомпаниями, но после того, как они уже использовали ODFAM. Прирост прибыли от внедрения IFAM поверх FAM можно запросто оценить в 55-60%.

60% — это очень много! Однако даже эта цифра не учитывает все потери от несогласованных действий авиакомпаний. Учитывая количество общих сегментов и городов в маршрутных сетях авиакомпаний, можно смело предположить, что упускается еще не менее 15-20% потенциальной прибыли. Цифры кажутся нереалистичными, ведь если бы это было действительно так, то кто-нибудь уже давно бы все это заметил и внедрил все необходимое. В очередной раз обратимся к исторической справке из начала статьи: в самых разных бизнесах колоссальные проблемы с доходностью не замечались и считались нормой гораздо дольше, чем следовало бы. Еще лучше — вспомним про ставки. Сколько бы вы поставили на то, что сейчас авиакомпании недополучают 70-80% потенциальной прибыли, если бы не были знакомы с историей появления и развития RMS? Наверняка, ставка была бы совсем небольшой.

Уберизация

Внедрение IFAM и расширение PMM требует от RMS наличия не только прогнозного "движка", но еще и дополнительного продвинутого аналитического функционала. Решение проблемы согласованности невозможно без централизованного подхода к принятию решений. Централизованный подход всегда вызывает много опасений, но здесь достаточно вспомнить причины появления таких бизнес-моделей, как Uber или Airbnb. Сначала они позволили заменить посредников на более дешевую цифровую платформу, затем занялись снижением общей нагрузки на перевозчиков. Благодаря анализу данных, машинному обучению и оптимизации извозчики сейчас зарабатывают намного больше, перевозя гораздо больше пассажиров, при этом сами пассажиры платят значительно меньше. А ведь несколько десятилетий назад такое казалось невозможным.

Централизованный подход к снижению нагрузки — это вовсе не какой-то коварный план по захвату власти над авиакомпаниями. Если вдуматься, то это замечательный способ избежать основных минусов конкуренции и кооперации. Конкуренция приводит к несогласованности действий, а кооперация, хоть и позволяет добиться определенного уровня согласованности, но чревата такими рисками, как: утрата полного контроля над стратегическими решениями, сговор в политике ценообразования, конфликты интересов при координации совместных действий. Централизованный подход позволит авиакомпаниям зарабатывать намного больше и сконцентрироваться на конкуренции в сфере качества сервиса, программ лояльности и тарифной политики. Сконцентрироваться на самом главном и самом прибыльном факторе — положительном опыте пассажиров.

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

Денежные потоки, так же, как и пассажиропотоки, можно рассматривать как жидкость, поток которой тем сильнее, чем меньше преград и ограничений на ее пути. Уберизация наиболее вероятна там, где таких преград и ограничений слишком много, например, в трэвел-отрасли. И если она неизбежна, то что страшнее: ее приход изнутри или снаружи?

Заключение

Трансформации рынков неизбежны. Иногда они могут происходить из-за дерегуляции, но иногда катализатором изменений может послужить и банальная цифровизация. Управление доходностью как дисциплина зародилась в гражданской авиации, и первые ее уверенные шаги начались именно в отрасли путешествий. Важно отметить, что первые предпосылки уберизации также зародились именно в гражданской авиации. Глобальные распределительные системы (GDS) стали прообразом электронной коммерции и позволили управлять большими объемами продаж, задействуя гораздо меньшее количество сотрудников. В какой-то мере фиксированные цены казались чем-то нормальным, просто потому что не было какого-то простого и быстрого способа их изменения. GDS обеспечили легкий доступ к ценам и позволили собирать данные и принимать на их основе еще больше обоснованных решений.

GDS убедительно доказали, что централизованный подход к управлению ценой оказался на порядки эффективнее. Раз так, то почему бы не развить эту идею дальше? Эта идея действительно развивается: например, IATA давно и всерьез рассматривает идею внедрения блокчейна в отрасль гражданских авиаперевозок. Блокчейн — это не просто способ уменьшения количества посредников. Блокчейн опирается на децентрализованный подход, но в то же время это решение позволяет централизованно управлять рынком только посредством рыночных механизмов спроса и предложения.

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

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

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

В конечном счете для посредников, так же, как и для компаний, действует единый закон естественного отбора — выживает вовсе не сильнейший, а тот, кто быстрее адаптируется к изменяющимся условиям. Новые реалии и изменения правил всегда несут угрозу потерь, но элементарные принципы теоремы Байеса позволяют выигрывать от них гораздо больше. Если ты предвидишь изменения и их неизбежность, а твои конкуренты нет, то почему бы не сделать эти изменения частью конкурентной стратегии?

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

Комментарии (1)


  1. abcdsash
    07.07.2024 06:11

    временная локальность и пространственная локальность - вот!

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

    Пространственная - если что то происходит в этом месте, то есть большая вероятность, что это же будет происходить и в окрестностях этого же места...

    Определения очень сильно применяют при предсказании поведения процессов в ОС... на них можно строить именно вероятностные прогнозы.