Вступление

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

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

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

Деловое понимание

Раз в несколько дней Starbucks рассылает предложения пользователям мобильного приложения. Предложение может быть просто рекламой напитка или фактическим предложением, таким как скидка или BOGO (купите один, получите один бесплатно). Некоторые пользователи могут не получать никаких предложений в течение определенных недель.

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

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

У каждого предложения есть уровень сложности, а также срок действия до истечения срока действия предложения. Например, предложение BOGO с уровнем сложности 7 может быть действительным только в течение 5 дней. Это означает, что если клиент потратит 7 или более долларов до истечения срока действия предложения, он получит продукт на 7 долларов бесплатно. Информационные предложения имеют уровень сложности 0, так как они не требуют совершения покупок. У них также есть срок действия, даже если эти объявления просто предоставляют информацию о продукте; Например, если информационное предложение действует 7 дней, мы можем предположить, что покупатель ощущает влияние предложения в течение 7 дней после получения объявления.

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

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

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

Понимание данных

В этом разделе мы бегло рассмотрим данные, чтобы получить общее представление обо всем, что доступно нам в форме данных.

Данные содержатся в трех файлах:

  • портфолио.json - содержит идентификаторы предложений и метаданные о каждом предложении (продолжительность, тип и т. д.)
  • profile.json - демографические данные по каждому покупателю
  • transcript.json - записи транзакций, полученных предложений, просмотренных предложений и завершенных предложений

Вот схема и объяснение каждой переменной в файлах:

портфолио.json

  • id (строка) - id предложения
  • offer_type (string) - тип предложения, т. е. BOGO, скидка, информационная
  • сложность (int) - минимум, необходимый для завершения предложения
  • reward (int) - награда за выполнение предложения
  • duration (int) - время открытия оферты в днях
  • каналы (список строк)

profile.json

  • age (int) - возраст покупателя
  • стала_member_on (int) - дата, когда клиент создал учетную запись приложения
  • пол (str) - пол клиента (обратите внимание, что некоторые записи содержат «O» вместо M или F)
  • id (str) - идентификатор клиента
  • доход (float) - доход клиента

transcript.json

  • событие (str) - описание записи (т.е. транзакция, полученное предложение, просмотренное предложение и т. д.)
  • person (str) - идентификатор клиента
  • time (int) - время в часах с начала теста. Данные начинаются в момент времени t = 0
  • value - (dict of strings) - либо идентификатор предложения, либо сумма транзакции в зависимости от записи

Вот обзор данных:

  • Есть 10 предложений, и часть этих предложений отправлена ​​17000 клиентам.
  • Срок действия предложения составляет от 3 до 10 дней.
  • Уровень сложности предложений варьируется от 0 до 20. Самое простое предложение имеет порог суммы покупки 5, а самое сложное предложение имеет порог суммы покупки 20.
  • События, связанные с предложениями, и данные о транзакциях доступны в течение 30 дней.
  • Последнее членство было в июле 2018 года, что указывает на то, что данные датируются 1 годом.

Подготовка данных

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

Очистка данных

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

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

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

портфолио

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

профиль

  • Сопоставьте уникальные целочисленные идентификаторы каждому человеку.
  • Удалите профили для лиц в возрасте 118 лет, поскольку это возраст по умолчанию для лиц, у которых отсутствуют данные о поле и доходе.
  • Преобразуйте формат «стать участником» в формат даты.
  • Создайте новый столбец «members_duration» из «стать участником», поскольку столбец «стал_частником_в» не очень полезен в его текущем формате. Мы рассчитаем продолжительность членства в днях, взяв за основу самую последнюю дату «вступления в членство».
  • Отбросьте столбец «Стать участником».

стенограмма

Процесс очистки немного сложнее для данных расшифровки. Вот первые шаги:

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

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

Построим график сумм транзакций.

Большинство покупок составляют менее 100 долларов. Однако существует множество крупных транзакций, максимальная сумма которых достигает 1062 долларов США. Из данных портфолио мы знаем, что самое сложное предложение требует, чтобы клиент потратил 20 долларов и более. Таким образом, транзакции на большие суммы, которые намного превышают 20 долларов США, вероятно, являются групповыми заказами и не подпадают под действие рекламных предложений. Для целей нашего анализа и моделирования мы будем отмечать эти транзакции как выбросы, используя максимальное пороговое значение из межквартильного диапазона. Если в течение периода предложения у клиента будет одна или несколько резко отклоняющихся транзакций, мы не будем рассматривать это как эффективное предложение для клиента.

Затем мы объединим все наборы данных в единый фрейм данных, выполнив следующие шаги:

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

Выявление затронутых клиентов

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

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

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

В объединенных данных эти категории будут сопоставлены со значениями 0, 1, 2 и 3 соответственно.

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

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

Анализ данных

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

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

Категориальные функции

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

Тип предложения

  • Похоже, клиенты лучше откликнулись на скидки, чем на предложения BOGO.

Идентификатор предложения

  • Исходя из соотношения откликов клиентов, предложения с идентификаторами 7,6 и 4, соответственно, являются тремя лучшими предложениями. Вот подробности этих предложений.

7. Потратьте 10 долларов в течение 10 дней и получите скидку 2 доллара.

6. Потратьте 7 долларов в течение 7 дней и получите скидку 3 доллара.

4. Потратьте 5 долларов в течение 7 дней и получите продукт на 5 долларов бесплатно.

  • Интересно отметить, что BOGO предлагает тот же уровень сложности (идентификаторы предложения 1 и 2), что и идентификатор предложения 7, и даже более высокий коэффициент вознаграждения имел более низкий уровень успеха. Похоже, клиенты предпочитали покупать продукт со скидкой, чем получать дополнительный продукт бесплатно.

Сложность

  • Лучше всего откликнулись предложения с уровнем сложности 7.
  • Для предложения с наивысшим уровнем сложности большинство предложений не были просмотрены заказчиком. Однако нет четкой корреляции между уровнем сложности и соотношением ответов.

Продолжительность

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

Пол

  • У женщин соотношение ответов лучше, чем у их коллег-мужчин.
  • Хотя клиентов, принадлежащих к «другому» полу, было сравнительно меньше, их ответ был лучше, чем у мужчин.

Числовые характеристики

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

Общая потраченная сумма

  • Клиенты, на которых повлияли предложения, в среднем тратили значительно больше в час, чем те, кто не реагировал. Это ожидаемые строки.
  • Общая потраченная сумма почти всегда была меньше 200 долларов.
  • Предложения с идентификаторами 5 и 10, которые имели уровень сложности 20 и 10 соответственно, имели немного большую общую потраченную сумму.
  • Кажется, есть две широкие категории. Те, кто заполнил предложения (категории 1 и 2), и те, кто не выполнил (категории 0 и 3).
  • 0 и 1 имеют одинаковое распределение общих потраченных сумм, как и сегменты 2 и 3.
  • И нет четкой тенденции к разграничению просматриваемых и непросмотренных сегментов друг от друга. Посмотрим, сохраняется ли эта тенденция для других атрибутов клиента, таких как возраст, доход и продолжительность членства.

Доход

  • Большинство предложений было отправлено клиентам с доходом от 40 до 80 тысяч.
  • По всем предложениям подверженный влиянию сегмент относится к группе с более высоким доходом, чем их не подверженные влиянию коллеги. Можно с уверенностью предположить, что клиенты, принадлежащие к группам с более высоким доходом, с большей вероятностью откликнутся на рекламные предложения, независимо от разнообразия предложений.
  • Как и «общая потраченная сумма», сегменты 1 и 2 имеют сходство, как и сегменты 0 и 3. Сходство менее глубокое, чем то, что мы видели для «общей потраченной суммы».
  • В отличие от «общей потраченной суммы», нет четкого разделения между завершенными и незавершенными сегментами.

Возраст

  • Большинство предложений было отправлено 40-70-летним клиентам.
  • Разделение между разными сегментами не так сильно в «возрастном» распределении, как в отношении дохода и общей потраченной суммы. Однако категория 3, по-видимому, всегда принадлежит к более низкой возрастной группе по сравнению с остальными категориями. Самые молодые участники с большей вероятностью не будут просматривать и / или не заполнять предложения.

Срок участия

  • Большинство предложений было отправлено клиентам с менее чем 2-летним членством.
  • В среднем клиенты, которые откликнулись на предложения, были участниками дольше, чем те, кто этого не сделал.
  • Продолжительность членства хорошо разделяет разные сегменты. Это важная функция для определения вероятности ответа клиента на предложение.
  • Кроме того, тенденция, наблюдаемая в «сумме, потраченной в час», кажется, более или менее сохраняется и здесь, то есть категории 1 и 2 имеют сходство. Как и категории 0 и 3. Однако категория 3 имеет более широкое распределение членства, чем категория 0.

Создание функции

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

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

Коэффициент сложности

Соотношение сложности создается путем деления сложности предложения на продолжительность.

  • Предложения с более низким коэффициентом сложности, похоже, получают лучший отклик.

Группы дохода

  • Коэффициент отклика был наилучшим для клиентов с уровнем дохода 100 000+, за которыми следовали клиенты с уровнем дохода 80–100 000.
  • Положительная корреляция между доходом и откликом клиентов очевидна из приведенной выше визуализации.
  • Мы можем с уверенностью сделать вывод, что люди с доходом более 60 тысяч с большей вероятностью откликнутся на предложения.

Продолжительность членства

  • Клиенты с членством менее 1 года были наименее отзывчивыми клиентами.
  • Самые отзывчивые клиенты - это те, кто был членом от 1 до 2,5 лет (365–900 дней), за ними следуют клиенты с 2,5–4 годами членства.
  • Достигнув максимума примерно через 1–2,5 года членства, количество откликов постоянно снижается с увеличением продолжительности членства.

Возрастные группы

  • Лучшее соотношение ответов было для клиентов в возрастной группе 80+, за которыми следовали клиенты в возрастной группе 61–80 лет.
  • Между возрастом и реакцией клиента существует положительная корреляция. У пожилых клиентов коэффициент отклика выше, чем у их более молодых коллег.
  • Вероятно, можно с уверенностью сделать вывод, что клиенты старше 40 лет с большей вероятностью откликнутся на предложения.

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

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

Выберите функции для прогнозной модели

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

Также из раздела анализа данных можно с уверенностью предположить, что нет четкой тенденции, которая отделяет категорию 2 от других категорий. Хотя у категорий 1 и 2 есть общие черты, мы не можем объединить их, потому что в категории 2 могут быть ценные клиенты, которые в среднем тратят больше, чем обычные клиенты. Отправка предложений этим клиентам не имеет большого смысла для бизнеса. Мы не можем объединить категорию 2 с группой «не подверженных влиянию», потому что между этими двумя группами нет никакого сходства.

Однако мы можем объединить категории 0 и 3, поскольку они имеют схожие черты, и нормально не отправлять предложения клиентам, которые не просматривали предложение и, скорее всего, не ответят даже после просмотра.

Наивный предсказатель

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

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

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

Подготовьте данные для моделирования

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

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

Первоначальная оценка модели

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

  • Логистическая регрессия
  • Ada Boost
  • Случайный лес
  • Легкая ГБМ

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

Вот результат:

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

Важность функции и объяснение модели

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

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

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

Пример объяснения прогноза по строке данных проверки:

Границы принятия решений, кажется, более или менее подтверждают интуицию, которую мы выработали на этапе EDA.

Настройка производительности

В этом разделе мы попытаемся дополнительно улучшить оценку модели, настроив параметры модели LightGBM с помощью RandomizedSearchCV.

  • num_leaves: большое значение num_leaves помогает повысить точность, но может привести к чрезмерной подгонке. Чтобы этого не произошло, мы должны позволить ему быть меньше 2 ^ (max_depth).
  • min_data_in_leaf: его оптимальное значение зависит от количества обучающих выборок и num_leaves. Установка большого значения поможет избежать слишком глубокого роста дерева, но может привести к недостаточной подгонке. На практике установки сотен или тысяч достаточно для большого набора данных.
  • max_depth: max_depth используется для явного ограничения глубины дерева.
  • boosting_type: «дротик» обычно дает лучшую точность

Мы могли оптимизировать модель, чтобы получить лучший результат (0,80) по сравнению с неоптимизированной моделью (0,79) с параметрами по умолчанию. И если мы сравним с наивным предсказателем, использование этой модели может значительно повысить наши шансы на успешное предложение, чем просто случайное распределение предложений.

Заключение

Размышления:

  • Очистка данных стенограммы была одним из самых сложных аспектов этого проекта.
  • Уровень сложности и размер вознаграждения предложений, а также продолжительность членства, доход и возраст клиентов являются наиболее важными характеристиками, которые определяют шансы на успех предложения.
  • Тип предложения со скидкой, большая продолжительность, меньший уровень сложности - вот некоторые из атрибутов предложений, которые увеличивают вероятность успешного предложения.
  • Некоторые клиенты считают, что вероятность отклика повышается: более высокий доход (›60 тыс.), Более длительное членство (от 1 до 2,5 лет), женский пол и более высокий возраст (› 40 лет).

Возможности для улучшения:

  • Есть еще возможности для дальнейшего улучшения характеристик модели. Мы можем просто попробовать другие высокопроизводительные модели, такие как XGboost, Catboost, чтобы увидеть, будут ли они лучше, чем LightGBM. Другая стратегия, обычно используемая для повышения производительности модели, - это наложение нескольких моделей вместе. Кроме того, поскольку это несбалансированный набор данных, другой вариант, который мы, безусловно, можем изучить, - это SMOTE - избыточная выборка данных из класса меньшинства.
  • Использование данных транзакции для прогнозирования ответов клиентов может привести к утечке данных. Однако исторические данные, относящиеся к суммам покупок клиента, могут помочь нам лучше спрогнозировать реакцию клиента на предложение. Например, если средние расходы клиента намного больше, чем у постоянного клиента, посылать им предложения с низкой сложностью не имеет большого смысла с точки зрения бизнеса. Мы бы предпочли послать им предложения высокой сложности. Кроме того, по текущим данным трудно судить, оказали ли информационные предложения какое-либо влияние. Сравнение исторической средней нормы расходов клиента со средней скоростью расходов в период предложения помогло бы нам определить эффект информационных предложений.
  • Есть много случаев, когда клиенты воспользовались предложением, не просматривая его. С точки зрения бизнеса, в идеале мы не хотели бы отправлять такие предложения. В ходе нашего анализа мы не смогли найти атрибуты, которые могут помочь спрогнозировать этот сегмент клиентов. Однако можно было бы сделать предложения на основе купонов. Таким образом, только клиенты, желающие воспользоваться предложениями, будут заполнять предложения.
  • Поскольку не все предложения были отправлены всем пользователям, мы можем создать набор данных, сопоставив все предложения со всеми клиентами, а затем спрогнозировать ответы клиентов на каждое из предложений. Если клиент, скорее всего, ответит на несколько предложений, нам нужно выбрать только те, которые приносят максимальный доход, например, предложения с наивысшей сложностью или наименьшим соотношением вознаграждения.

Полный код этой реализации доступен здесь.