Оптимизация маркетингового бюджета с помощью нелинейного программирования
Image by author
💰 Распределение маркетингового бюджета
В эпоху цифрового маркетинга компании сталкиваются с задачей распределения своего маркетингового бюджета по различным каналам для максимизации продаж.
Однако по мере расширения охвата эти фирмы неизбежно сталкиваются с проблемой убывающей отдачи — явлением, при котором дополнительные инвестиции в маркетинговый канал приносят прогрессивно меньшее увеличение конверсий. Именно здесь вступает в игру концепция распределения маркетингового бюджета, добавляя ещё один уровень сложности во весь процесс.
В этой статье мы рассмотрим потенциал нелинейного программирования, в частности коническую оптимизацию (или конусное программирование), в качестве инструмента для распределения маркетингового бюджета. Используя эту передовую математическую технику, мы стремимся оптимизировать распределение маркетингового бюджета по различным платформам для извлечения максимальной ценности и максимально возможного ROI.
Код доступен на GitHub и Google Colab.
💰 Распределение маркетингового бюджета
Распределение маркетингового бюджета является важнейшим аспектом любой рекламной кампании, требующим от бизнеса стратегического распределения своих ресурсов по различным каналам. Цель — максимизировать эффективность маркетинговых усилий и достичь максимально возможного возврата инвестиций (ROI).
Для решения этой задачи необходимо учесть три ключевых компонента:
- Атрибуция: как мы можем связать события конверсии с конкретными кампаниями?
- Оценка эффективности: как мы можем предсказать эффективность кампании на основе выделенного ей бюджета?
- Оптимизация: как мы можем распределить бюджеты по различным кампаниям для максимизации ROI?
🔗 1. Атрибуция: связь конверсий с кампаниями
Атрибуция — это процесс определения, какие кампании ответственны за конверсию клиентов. Некоторые каналы, такие как VK или AdWords, могут напрямую заявлять о конверсиях. Однако существуют различные модели атрибуции, включая:
- Первое касание
- Последнее касание
- Мультитач
- Временное затухание
- Позиционно-зависимая
Системы атрибуции не лишены проблем, основными из которых являются:
- Задержка: время, необходимое для измерения эффективности рекламы и точного атрибутирования конверсий
- Окно атрибуции: компромисс между использованием короткого и длинного окна для атрибутирования конверсий
Например, DoorDash использовала систему атрибуции с несколькими днями последнего касания. Проблема, с которой они столкнулись, заключалась в необходимости ждать несколько дней для измерения эффективности своих объявлений, что оказалось слишком длительным сроком с учётом быстрых изменений на их рынке.
🔮 2. Оценка эффективности: прогнозирование успеха кампании
Оценка эффективности предполагает создание модели, которая может предсказать успех маркетинговой кампании на основе её распределения бюджета. Здесь успех может быть определён с помощью различных ключевых показателей эффективности (KPI), таких как:
- Лиды
- Стоимость за лид (CPL)
- Пожизненная ценность клиента (CLV)
- Стоимость привлечения клиента (CAC)
Традиционно для оценки эффективности использовались линейные модели. Однако они предполагают, что маркетинговые каналы не демонстрируют убывающую отдачу, что часто не так. Для получения нетривиальных решений линейные модели обычно включают несколько ограничений и решаются с помощью линейного программирования (LP).
В реальности кривые отклика в моделировании маркетингового микса часто имеют разные формы, такие как:
- Линейная (редко)
- Вогнутая (часто, что указывает на убывающую отдачу)
- Выпуклая (редко)
- S-образная (редко)
Эти формы отражают убывающую отдачу от маркетинговых расходов или различную эффективность разных каналов при разных уровнях бюджета. Например, инвестирование большего количества денег в канал может сначала приносить более высокую отдачу (выпуклая), но после определённого момента каждый дополнительный доллар может генерировать всё меньше и меньше дополнительной отдачи (становится вогнутой), создавая S-образную кривую в целом.
Чтобы учесть внутреннюю нелинейность проблемы распределения маркетингового бюджета, необходим более сложный подход. Именно здесь на помощь приходит нелинейное программирование, в частности коническая оптимизация.
🔄 3. Оптимизация: нелинейная оптимизация с CVXPY
Нелинейное программирование, также известное как нелинейная оптимизация, — это метод, используемый для решения задач оптимизации, в которых целевая функция, ограничения или и то, и другое являются нелинейными. Простыми словами, это процесс поиска оптимального решения (максимизации или минимизации) для системы, управляемой набором нелинейных уравнений.
В этом примере мы смоделируем доходность для каждого маркетингового канала (кривую отклика) с использованием натурального логарифма следующим образом:
Два предыдущих шага атрибуции и оценки эффективности аппроксимируют значения αᵢ и βᵢ для каждого канала i. Давайте рассмотрим простой пример с тремя каналами:
Наблюдаемый в этих значениях шум типичен для задач распределения маркетингового бюджета. Обратите внимание, что значения альфа отрицательные; это можно интерпретировать как начальную стоимость взаимодействия с новым маркетинговым каналом.
Мы можем построить кривые отклика каждого маркетингового канала с помощью matplotlib.
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(0)
TOTAL_BUDGET = 100_000
alphas = np.array([-9453.72, -8312.84, -7371.33])
betas = np.array([8256.21, 7764.20, 7953.36])
x = np.linspace(1, TOTAL_BUDGET, TOTAL_BUDGET)
fig = plt.figure(figsize=(10, 5), dpi=300)
plt.plot(x, alphas[0] + betas[0] * np.log(x), color='red', label='Google Ads')
plt.plot(x, alphas[1] + betas[1] * np.log(x), color='blue', label='VK Ads')
plt.plot(x, alphas[2] + betas[2] * np.log(x), color='green', label='Twitter Ads')
plt.xlabel('Бюджет ($)')
plt.ylabel('Доходность ($)')
plt.legend()
plt.show()
Как найти наилучшие значения для каждой кривой отклика? Самое простое решение состоит из жадного алгоритма, который случайным образом выбирает значения и оценивает результат. Наша задача оптимизации может быть описана следующим образом:
def greedy_optimization(TOTAL_BUDGET, alphas, betas, num_iterations=1_000):
google_budget = VK_budget = twitter_budget = TOTAL_BUDGET / 3
obj = alphas[0] + betas[0] * np.log(google_budget) + alphas[1] + betas[1] * np.log(VK_budget) + alphas[2] + betas[2] * np.log(twitter_budget)
for _ in range(num_iterations):
random_allocation = np.random.dirichlet(np.ones(3)) * TOTAL_BUDGET
google_budget_new, VK_budget_new, twitter_budget_new = random_allocation
new_obj = alphas[0] + betas[0] * np.log(google_budget_new) + alphas[1] + betas[1] * np.log(VK_budget_new) + alphas[2] + betas[2] * np.log(twitter_budget_new)
if new_obj > obj:
google_budget, VK_budget, twitter_budget = google_budget_new, VK_budget_new, twitter_budget_new
obj = new_obj
return (google_budget, VK_budget, twitter_budget), obj
Давайте запустим его и посмотрим на приближённое решение, которое он нашёл:
(best_google, best_VK, best_twitter), obj = greedy_optimization(TOTAL_BUDGET, alphas, betas)
print('='*59 + 'n' + ' '*24 + 'Solution' + ' '*24 + 'n' + '='*59)
print(f'Returns = ${round(obj):,}n')
print('Marketing allocation:')
print(f' - Google Ads = ${round(best_google):,}')
print(f' - VK Ads = ${round(best_VK):,}')
print(f' - Twitter Ads = ${round(best_twitter):,}')
После выполнения наших расчётов мы обнаруживаем, что наш общий доход составляет 224 533 доллара. Вы можете задаться вопросом, можно ли улучшить его, настроив нашу модель более тщательно или запустив больше итераций.
Этот вид гарантии — именно то, для чего нелинейное программирование приходит на помощь: оно может вывести наилучшее возможное решение, также называемое оптимальным решением. Помимо этого подавляющего преимущества, оно также работает быстрее.
Для решения задачи распределения маркетингового бюджета с помощью нелинейного программирования мы будем использовать библиотеку CVXPY, которая поддерживает коническую оптимизацию благодаря специализированным решателям, таким как ECOS, MOSEK (метод внутренней точки) и SCS (метод первого порядка). В этом примере мы будем использовать открытый решатель ECOS для поиска оптимального решения.
Давайте настроим задачу оптимизации:
- Наши переменные решения — это (положительные) бюджеты для каждого канала
- Наше ограничение состоит в том, что сумма всех бюджетов не должна превышать общий бюджет
- Наша цель — максимизировать общий доход, который представляет собой сумму доходов для каждого канала
import cvxpy as cp
google = cp.Variable(pos=True)
VK = cp.Variable(pos=True)
twitter = cp.Variable(pos=True)
constraint = [google + VK + twitter <= TOTAL_BUDGET]
obj = cp.Maximize(alphas[0] + betas[0] * cp.log(google)
+ alphas[1] + betas[1] * cp.log(VK)
+ alphas[2] + betas[2] * cp.log(twitter))
Наконец, мы вызываем решатель ECOS, чтобы найти оптимальные распределения бюджета и отображаем результаты.
prob = cp.Problem(obj, constraint)
prob.solve(solver='ECOS', verbose=False)
print('='*59 + 'n' + ' '*24 + 'Solution' + ' '*24 + 'n' + '='*59)
print(f'Status = {prob.status}')
print(f'Returns = ${round(prob.value):,}n')
print('Marketing allocation:')
print(f' - Google Ads = ${round(google.value):,}')
print(f' - VK Ads = ${round(VK.value):,}')
print(f' - Twitter Ads = ${round(twitter.value):,}')
Оптимальное распределение, найденное решателем, составляет 34 439 долларов для Google Ads, 32 386 долларов для VK Ads и 33 175 долларов для YouTube, при общем доходе в 224 540 долларов! Это на 7 долларов больше, чем вернул жадный алгоритм (224 533 доллара).
Имейте в виду, что это распределение максимизирует доход на основе наших кривых отклика: правильное моделирование этих кривых имеет решающее значение для эффективной оптимизации бюджета.
Давайте визуализируем это оптимальное распределение поверх предыдущих кривых отклика.
fig = plt.figure(figsize=(10, 5), dpi=300)
plt.plot(x, alphas[0] + betas[0] * np.log(x), color='red', label='Google Ads')
plt.plot(x, alphas[1] + betas[1] * np.log(x), color='blue', label='VK Ads')
plt.plot(x, alphas[2] + betas[2] * np.log(x), color='green', label='Twitter Ads')
plt.scatter([google.value, VK.value, twitter.value],
[alphas[0] + betas[0] * np.log(google.value),
alphas[1] + betas[1] * np.log(VK.value),
alphas[2] + betas[2] * np.log(twitter.value)],
marker="+", color='black', zorder=10)
plt.xlabel('Бюджет ($)')
plt.ylabel('Доходность ($)')
plt.legend()
plt.show()
Но действительно ли это оптимально? Мы можем провести быструю проверку здравого смысла, запустив жадный алгоритм для разного количества итераций. Это покажет нам разницу между этими двумя подходами.
Давайте запустим его для 20 различных чисел итераций между 1 и 1 000 000.
best_obj_list = []
num_iterations_range = np.logspace(0, 6, 20).astype(int)
for num_iterations in num_iterations_range:
_, best_obj = greedy_optimization(TOTAL_BUDGET, alphas, betas, num_iterations)
best_obj_list.append(best_obj)
Мы можем теперь построить полученный список с помощью matplotlib и сравнить его с оптимальным решением:
plt.figure(figsize=(10, 5), dpi=300)
plt.ticklabel_format(useOffset=False)
plt.plot(num_iterations_range, best_obj_list, label='Жадный алгоритм')
plt.axhline(y=prob.value, color='r', linestyle='--', label='Оптимальное решение (CVXPY)')
plt.xlabel('Количество итераций')
plt.xticks(num_iterations_range)
plt.xscale("log")
plt.ylabel('Лучший доход ($)')
plt.title('Лучший доход, найденный жадным алгоритмом для разного количества итераций')
plt.legend()
plt.show()
Мы наблюдаем, что жадный алгоритм работает относительно хорошо, когда ему дано большое количество итераций. Однако, несмотря на миллион попыток, он не находит оптимального распределения, которое приносит доход в 224 540,1500 долларов. Наилучшее значение без округления, которое он смог достичь, составляет 224 540,1489 долларов.
Кроме того, существует значительная разница в плане вычислительной скорости между двумя подходами. Нелинейная модель программирования определила оптимальное решение за 22,3 миллисекунды. В отличие от этого жадный алгоритм занял 30 секунд, чтобы запустить свои 1 миллион итераций и найти почти оптимальное решение.
Заключение
Нелинейное программирование предлагает мощный подход к решению задачи распределения маркетингового бюджета. Моделируя убывающую отдачу каждого маркетингового канала с помощью нелинейных функций и используя библиотеку CVXPY, мы можем найти оптимальное распределение ресурсов, которое максимизирует продажи.
По мере развития маркетингового ландшафта и увеличения количества каналов методы оптимизации, такие как нелинейное программирование, могут помочь компаниям принимать более обоснованные решения о своих маркетинговых инвестициях. Хотя эта статья представляет собой отправную точку, существует множество более продвинутых методов и моделей для изучения. Продолжайте учиться и экспериментировать, чтобы найти наилучший подход для вашего бизнеса.
Ссылки
Если вы хотите узнать больше о распределении маркетингового бюджета, я рекомендую следующие ресурсы:
- Введение в линейное программирование на Python
- Целочисленное и линейное программирование на Python
