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

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1Все 1566 услуг
Кросс-канальная оптимизация рекламного бюджета: AI, MMM и incrementality
Средний
~2-4 недели
Часто задаваемые вопросы

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

Этапы разработки AI-решения

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

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

Как 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 рабочих дня.