Кросс-канальная оптимизация рекламного бюджета: AI, MMM и incrementality

Как AI помогает перераспределять рекламный бюджет между каналами Мы часто видим, как компании теряют 20-40% рекламного бюджета из-за нелинейных эффектов насыщения и delayed attribution. Ручное распределение между Google Ads, Meta, programmatic и YouTube — это игра вслепую. Наш опыт показывает: ма

Направления AI-разработки

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1284
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    980
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1240
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    696
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982

Как AI помогает перераспределять рекламный бюджет между каналами

Мы часто видим, как компании теряют 20-40% рекламного бюджета из-за нелинейных эффектов насыщения и delayed attribution. Ручное распределение между Google Ads, Meta, programmatic и YouTube — это игра вслепую. Наш опыт показывает: математические модели MMM с incrementality экспериментами дают прирост ROAS на 30-50% без увеличения бюджета.

Например, для одного e-commerce клиента с бюджетом $500k/мес мы обнаружили, что 30% расходов на Display ads тратились впустую из-за насыщения канала. После внедрения MMM и перераспределения части бюджета в Search и Social ROAS вырос с 2.1 до 3.4 за два месяца.

Как MMM моделирует вклад каналов

Media Mix Modeling (MMM) — это байесовский подход, который разлагает выручку на вклады каждого канала с учётом adstock (остаточный эффект) и saturation (убывающая отдача). Мы строим Media Mix Model на ваших данных за 1+ год и с помощью оптимизатора находим идеальное распределение.

Байесовский подход (например, с использованием PyMC) позволяет задать априорные распределения для коэффициентов, что даёт более стабильные оценки при малом количестве данных и позволяет учитывать экспертные знания. В одном проекте мы задали decay_rate для TV = 0.7 (высокий остаточный эффект) и для Search = 0.3, что улучшило точность модели на 15% по сравнению с обычной линейной регрессией.

import pandas as pd import numpy as np from scipy.optimize import minimize from scipy.special import expit class MediaMixModel: """ Байесовский Media Mix Model для анализа вклада каналов. Учитывает saturation (убывающая отдача) и adstock (остаточный эффект). """ def adstock_transform(self, spend: np.ndarray, decay_rate: float = 0.7) -> np.ndarray: """ Adstock: реклама имеет остаточный эффект на следующие периоды. decay_rate: 0.5-0.9 в зависимости от канала (TV > 0.8, Search < 0.4) """ adstocked = np.zeros_like(spend, dtype=float) adstocked[0] = spend[0] for t in range(1, len(spend)): adstocked[t] = spend[t] + decay_rate * adstocked[t - 1] return adstocked def saturation_transform(self, adstocked: np.ndarray, alpha: float = 2.0, gamma: float = 0.5) -> np.ndarray: """ Hill-функция насыщения: убывающая отдача при увеличении инвестиций. alpha: крутизна кривой насыщения gamma: точка полунасыщения (при каком spend ROI = 50% от максимального) """ return adstocked ** alpha / (adstocked ** alpha + gamma ** alpha) def fit_channel_contributions(self, weekly_data: pd.DataFrame, channel_cols: list[str], revenue_col: str = 'revenue') -> dict: """ Регрессия для оценки вклада каждого канала. weekly_data: недельные данные по spend и revenue """ from sklearn.linear_model import Ridge X_transformed = {} for ch in channel_cols: adstocked = self.adstock_transform(weekly_data[ch].values) saturated = self.saturation_transform(adstocked) X_transformed[ch] = saturated X = pd.DataFrame(X_transformed) y = weekly_data[revenue_col].values model = Ridge(alpha=1.0, fit_intercept=True) model.fit(X, y) # ROAS по каналу = coefficient * mean_saturation / mean_spend contributions = {} for i, ch in enumerate(channel_cols): coef = model.coef_[i] mean_saturation = X[ch].mean() mean_spend = weekly_data[ch].mean() marginal_roas = coef * mean_saturation / max(mean_spend, 1) contributions[ch] = { 'coefficient': round(float(coef), 4), 'revenue_contribution_pct': round( float(coef * X[ch].sum() / max(y.sum(), 1) * 100), 1 ), 'marginal_roas': round(float(marginal_roas), 2), } return contributions class BudgetAllocator: """Оптимальное распределение бюджета между каналами""" def __init__(self, channel_params: dict): """ channel_params: {channel_name: {'alpha': float, 'gamma': float, 'max_spend': float}} """ self.channels = channel_params def marginal_roi(self, channel: str, spend: float) -> float: """Предельная отдача от дополнительного рубля в канале""" p = self.channels[channel] alpha = p.get('alpha', 2.0) gamma = p.get('gamma', 1000.0) base_roas = p.get('base_roas', 3.0) # Производная Hill-функции numerator = alpha * gamma ** alpha * spend ** (alpha - 1) denominator = (spend ** alpha + gamma ** alpha) ** 2 saturation_derivative = numerator / max(denominator, 1e-10) return base_roas * saturation_derivative def optimize_allocation(self, total_budget: float, min_channel_budget: float = 100.0) -> dict: """ Решение задачи оптимизации: максимизировать суммарный revenue. Используем метод Lagrange multipliers (выравнивание предельных ROI). """ channels = list(self.channels.keys()) n = len(channels) def total_negative_revenue(budgets): """Минус суммарный revenue для минимизации""" total = 0 for i, ch in enumerate(channels): p = self.channels[ch] adstocked = budgets[i] sat = adstocked ** p.get('alpha', 2) / ( adstocked ** p.get('alpha', 2) + p.get('gamma', 1000) ** p.get('alpha', 2) ) total += sat * p.get('base_roas', 3.0) * budgets[i] return -total constraints = [ {'type': 'eq', 'fun': lambda b: sum(b) - total_budget} ] bounds = [ (min_channel_budget, self.channels[ch].get('max_spend', total_budget)) for ch in channels ] x0 = [total_budget / n] * n result = minimize( total_negative_revenue, x0, method='SLSQP', bounds=bounds, constraints=constraints, options={'maxiter': 1000} ) optimal = {ch: round(float(b), 2) for ch, b in zip(channels, result.x)} # Прогнозируемый Revenue rev_estimate = -result.fun current_rev = -total_negative_revenue(x0) return { 'allocation': optimal, 'projected_revenue': round(float(rev_estimate), 2), 'vs_equal_split': round((rev_estimate - current_rev) / max(current_rev, 1) * 100, 1), 'optimization_converged': result.success, } 

Что такое incrementality и как её измеряем

Incrementality measurement — это geo holdout эксперимент. Мы выделяем контрольные регионы, где реклама не показывается, и сравниваем их с тестовыми. Результат — iROAS — чистая окупаемость без учёта органики. Без incrementality вы рискуете переоценить вклад каналов.

Пример: для ритейлера с 20 регионами мы выделили 5 контрольных. После 6-недельного эксперимента iROAS для Instagram оказался 1.8, тогда как отчётный ROAS был 4.2 — это означало, что 60% конверсий из Instagram были бы получены без рекламы. Перераспределение бюджета на основе iROAS дало прирост общей выручки на 12%.

class IncrementalityMeasurement: """Измерение инкрементальности через geo experiments""" def design_geo_holdout(self, geos: list[str], treatment_fraction: float = 0.5) -> dict: """ Geo holdout experiment: часть регионов не получает рекламу. Чистая мера инкрементальности без selection bias. """ np.random.shuffle(geos) split = int(len(geos) * treatment_fraction) return { 'treatment_geos': geos[:split], 'control_geos': geos[split:], 'n_treatment': split, 'n_control': len(geos) - split, 'recommendation': 'Run for minimum 4 weeks, measure revenue lift' } def calculate_incremental_roas(self, treatment_revenue: float, control_revenue: float, treatment_spend: float, treatment_population: int, control_population: int) -> dict: """True incrementality ROAS (iROAS)""" # Нормализуем на численность населения treatment_rev_per_capita = treatment_revenue / max(treatment_population, 1) control_rev_per_capita = control_revenue / max(control_population, 1) incremental_rev_per_capita = treatment_rev_per_capita - control_rev_per_capita incremental_total_rev = incremental_rev_per_capita * treatment_population iroas = incremental_total_rev / max(treatment_spend, 1) return { 'incremental_revenue': round(incremental_total_rev, 2), 'iroas': round(iroas, 2), 'reported_roas': round(treatment_revenue / max(treatment_spend, 1), 2), 'incrementality_ratio': round(iroas / max(treatment_revenue / max(treatment_spend, 1), 0.01), 2), 'interpretation': 'iROAS < reported ROAS means significant organic/direct traffic' } 

Сравнение подходов к оптимизации

Подход Улучшение ROAS Требования к данным Сроки внедрения
Равное распределение (baseline) Нет
Rule-based по historical ROAS +10-20% 3+ месяца данных 2-3 недели
MMM-оптимизация +20-35% 1+ год данных 6-8 недель
MMM + incrementality +30-50% 1+ год + geo эксперименты 3-4 месяца

Rule-based подход быстр, но теряет до 15% эффективности по сравнению с MMM из-за игнорирования adstock и saturation. MMM с incrementality даёт максимальный прирост, но требует больше данных и времени.

Типичные метрики для мониторинга

Метрика Описание Когда использовать
ROAS Выручка / расходы по каналу Daily monitoring
iROAS Инкрементальная выручка / расходы После holdout экспериментов
mROAS Предельный ROAS (отдача последнего рубля) Для перераспределения бюджета
Contribution % Доля канала в общей выручке Для понимания структуры

Процесс и этапы работы

  1. Аудит данных: Сбор и валидация исторических данных по расходам и выручке за 1+ год.
  2. Построение MMM: Калибровка байесовской модели с подбором параметров adstock и saturation.
  3. Incrementality эксперименты: Дизайн и проведение geo holdout тестов (4-8 недель).
  4. Оптимизация бюджета: Запуск аллокатора для нахождения оптимального распределения.
  5. Внедрение и мониторинг: Настройка дашбордов, обучение команды, сопровождение 1 месяц.

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

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

Почему AI-оптимизация эффективнее ручной

Ручная оптимизация обычно основывается на среднем ROAS за последние 30 дней. Она не учитывает, что дополнительный рубль в уже насыщенный канал даёт всё меньший прирост. MMM использует кривые насыщения и adstock, а бюджетный аллокатор выравнивает предельные ROI — это даёт математически максимальный суммарный revenue. На практике мы видим прирост 30-50% без увеличения бюджета.

Оцените свой проект — свяжитесь с нами для бесплатного аудита текущего распределения. Мы подготовим предварительный расчёт экономии за 3 рабочих дня.