Оптимізація маркетингового бюджету за допомогою ML-атрибуції
Атрибуція last-click дає Google останнього переходу 100% кредиту за конверсію, хоча клієнт до цього бачив банер, читав статтю в блозі та дивився відео. Це призводить до перекосів у бюджеті: ви ллєте гроші в канали, які лише закривають угоду, а не приваблюють. Ми вирішуємо цю проблему за допомогою data-driven атрибуції на базі ML — будуємо модель, яка справедливо розподіляє цінність між усіма touchpoints.
Наш підхід використовує Shapley values та Markov chains, доповнені LLM-аналітикою. Результат — прозорий розподіл конверсій, виявлення недооцінених каналів і зростання ROAS до 30% за квартал. Ми впровадили такі системи для 15+ проектів, включаючи e-commerce з оборотами від $10M. Shapley-атрибуція в 3 рази точніше розподіляє бюджет, ніж last-click. Типова економія на рекламному бюджеті після впровадження становить $50,000–$100,000 на місяць для великих e-commerce проектів.
Приклад: рітейлер з оборотом $50M використовував last-click, вважаючи контекстну рекламу головним каналом. Після впровадження Shapley-атрибуції з'ясувалося, що 35% конверсій починалися з email-розсилок, які вважалися неефективними. Перерозподіл 20% бюджету в email дав +28% ROAS за два місяці.
Чому last-click атрибуція губить ваш бюджет?
Last-click — це baseline, який показують всі системи аналітики. Але він сліпий: якщо клієнт прийшов з банера, потім через email і нарешті конвертувався з контекстної реклами, останній точці дістається все. Ви скорочуєте банерний бюджет, думаючи, що він не працює, хоча саме він запустив воронку. На практиці перерозподіл на основі multi-touch атрибуції дає +20-35% до ефективності витрат.
Як ми будуємо multi-touch атрибуцію на ML
Ми використовуємо комбінацію методів: Shapley value (теоретично справедливий розподіл) для 5-8 каналів та Markov chain для масштабування. Для інтерпретації результатів підключаємо LLM — Claude або GPT-4 пишуть звіт з конкретними рекомендаціями по бюджету.
Ось фрагмент нашого пайплайну:
Збір даних про touchpoints
import pandas as pd
import numpy as np
from anthropic import Anthropic
from itertools import combinations
import json
class MarketingAttribution:
def __init__(self, touchpoints_df: pd.DataFrame, conversions_df: pd.DataFrame):
"""
touchpoints_df: user_id, channel, timestamp, campaign, cost
conversions_df: user_id, conversion_time, value
"""
self.touchpoints = touchpoints_df
self.conversions = conversions_df
self.llm = Anthropic()
def build_user_journeys(self, lookback_days: int = 30) -> pd.DataFrame:
"""Будує шлях кожного користувача до конверсії"""
journeys = []
for _, conv in self.conversions.iterrows():
user_id = conv['user_id']
conv_time = pd.to_datetime(conv['conversion_time'])
lookback_start = conv_time - pd.Timedelta(days=lookback_days)
user_touches = self.touchpoints[
(self.touchpoints['user_id'] == user_id) &
(pd.to_datetime(self.touchpoints['timestamp']) >= lookback_start) &
(pd.to_datetime(self.touchpoints['timestamp']) <= conv_time)
].sort_values('timestamp')
if len(user_touches) == 0:
continue
journeys.append({
'user_id': user_id,
'conversion_value': conv['value'],
'conversion_time': conv_time,
'journey': user_touches['channel'].tolist(),
'timestamps': user_touches['timestamp'].tolist(),
'total_touchpoints': len(user_touches),
'journey_days': (conv_time - pd.to_datetime(user_touches['timestamp'].iloc[0])).days
})
return pd.DataFrame(journeys)
Data-Driven атрибуція (Shapley Values)
def shapley_attribution(self, journeys_df: pd.DataFrame) -> pd.DataFrame:
"""
Game-theoretic атрибуція через Shapley values.
Кожен канал отримує свій справедливий внесок.
"""
all_channels = set()
for journey in journeys_df['journey']:
all_channels.update(journey)
coalition_values = {}
for _, row in journeys_df.iterrows():
journey_set = frozenset(row['journey'])
if journey_set not in coalition_values:
coalition_values[journey_set] = {'conversions': 0, 'value': 0}
coalition_values[journey_set]['conversions'] += 1
coalition_values[journey_set]['value'] += row['conversion_value']
shapley_values = {ch: 0.0 for ch in all_channels}
for channel in all_channels:
other_channels = all_channels - {channel}
for r in range(len(other_channels) + 1):
for coalition in combinations(other_channels, r):
coalition_set = frozenset(coalition)
coalition_with = frozenset(coalition) | {channel}
v_with = coalition_values.get(coalition_with, {}).get('value', 0)
v_without = coalition_values.get(coalition_set, {}).get('value', 0)
marginal = v_with - v_without
n = len(all_channels)
weight = (
np.math.factorial(r) * np.math.factorial(n - r - 1) /
np.math.factorial(n)
)
shapley_values[channel] += weight * marginal
total = sum(shapley_values.values())
attribution = pd.DataFrame([
{
'channel': ch,
'attributed_value': val,
'attribution_pct': val / total * 100 if total > 0 else 0
}
for ch, val in shapley_values.items()
]).sort_values('attributed_value', ascending=False)
return attribution
Markov Chain атрибуція
def markov_chain_attribution(self, journeys_df: pd.DataFrame) -> pd.DataFrame:
"""
Removal effect: наскільки впаде конверсія без кожного каналу.
Швидше за Shapley, добре працює для довгих ланцюжків.
"""
transitions = {}
for _, row in journeys_df.iterrows():
journey = ['START'] + row['journey'] + ['CONVERSION']
for i in range(len(journey) - 1):
state_from = journey[i]
state_to = journey[i + 1]
if state_from not in transitions:
transitions[state_from] = {}
transitions[state_from][state_to] = transitions[state_from].get(state_to, 0) + 1
non_converted = self.touchpoints[
~self.touchpoints['user_id'].isin(self.conversions['user_id'])
]
for _, row in non_converted.groupby('user_id').last().iterrows():
channel = self.touchpoints[self.touchpoints['user_id'] == row.name]['channel'].iloc[-1]
if channel not in transitions:
transitions[channel] = {}
transitions[channel]['NULL'] = transitions[channel].get('NULL', 0) + 1
def compute_conversion_rate(transition_matrix):
total_start = sum(transition_matrix.get('START', {}).values())
conv_from_start = transition_matrix.get('START', {}).get('CONVERSION', 0)
return conv_from_start / total_start if total_start > 0 else 0
base_cr = compute_conversion_rate(transitions)
all_channels = set()
for journey in journeys_df['journey']:
all_channels.update(journey)
removal_effects = {}
for channel in all_channels:
modified_transitions = {
k: {v: c for v, c in vals.items() if v != channel}
for k, vals in transitions.items()
if k != channel
}
modified_cr = compute_conversion_rate(modified_transitions)
removal_effects[channel] = max(0, base_cr - modified_cr)
total_removal = sum(removal_effects.values())
total_conversion_value = journeys_df['conversion_value'].sum()
attribution = pd.DataFrame([
{
'channel': ch,
'removal_effect': effect,
'attributed_value': effect / total_removal * total_conversion_value if total_removal > 0 else 0,
'attribution_pct': effect / total_removal * 100 if total_removal > 0 else 0
}
for ch, effect in removal_effects.items()
]).sort_values('attributed_value', ascending=False)
return attribution
LLM-аналіз результатів атрибуції
def generate_attribution_report(self, shapley_df: pd.DataFrame,
channel_costs: dict) -> str:
"""Інтерпретація результатів атрибуції через LLM"""
roi_data = []
for _, row in shapley_df.iterrows():
ch = row['channel']
cost = channel_costs.get(ch, 0)
attributed = row['attributed_value']
roi = (attributed - cost) / cost * 100 if cost > 0 else float('inf')
roi_data.append({
'channel': ch,
'cost': cost,
'attributed_revenue': attributed,
'roi': roi,
'attribution_pct': row['attribution_pct']
})
roi_data.sort(key=lambda x: x['roi'], reverse=True)
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=600,
messages=[{
"role": "user",
"content": f"""Ти маркетинговий аналітик. Проаналізуй результати multi-touch атрибуції.
Дані по каналам:
{json.dumps(roi_data, ensure_ascii=False, indent=2)}
Дай аналіз:
1. Які канали недооцінені (високий внесок, низькі витрати)?
2. Які переоцінені (низький внесок, високі витрати)?
3. Конкретні рекомендації щодо перерозподілу бюджету (з числами)
4. Канали для експериментів
Будь конкретним, називай канали по імені."""
}]
)
return response.content[0].text
Порівняння моделей атрибуції
| Модель | Як працює | Коли застосовувати | Недоліки |
|---|---|---|---|
| Last-click | 100% останньому каналу | Оперативні звіти | Ігнорує верх воронки |
| First-click | 100% першому каналу | Brand awareness | Переоцінює вхідні канали |
| Linear | Рівномірно всім дотикам | Короткі цикли | Не враховує позицію |
| Time-decay | Більше ваги ближче до конверсії | Довгі цикли продажів | Суб'єктивний коефіцієнт |
| Shapley value | Теоретично справедливий розподіл | 5-8 каналів, висока точність | Обчислювально дорогий при 10+ каналах |
| Markov chain | Removal effect — вплив видалення каналу | До 50 каналів, швидко | Не враховує порядок дотиків |
| Deep Learning | Нейромережа вчиться на послідовностях | >100 000 конверсій, складні патерни | Потребує багато даних і обчислювальних ресурсів |
Покращення інтерпретації атрибуції за допомогою LLM
Після розрахунку Shapley або Markov chain ми передаємо таблицю з attributions та costs в LLM (Claude або GPT-4). Модель генерує звіт природною мовою: вказує, які канали недооцінені (високий внесок, низькі витрати), які переоцінені, і дає конкретні рекомендації щодо перерозподілу бюджету з відсотками. Це економить час аналітиків і знижує ризик людських помилок.
Технічні деталі реалізації
Для Shapley ми використовуємо бібліотеку shap з кастомною функцією корисності. Для Markov chain — самописний граф переходів з видаленням вузлів. LLM викликається через API Anthropic або OpenAI. Всі обчислення упаковані в Docker-контейнер з FastAPI для інтеграції з CRM та BI-системами.
Що входить в роботу
| Етап | Тривалість | Результат | Deliverables |
|---|---|---|---|
| Аудит даних і налаштування збору touchpoints | 3-5 днів | Схема даних, налаштований трекінг | Документація API, рекомендації щодо збору даних |
| Розробка пайплайну атрибуції (Shapley / Markov / DL) | 1-2 тижні | Робочий пайплайн з тестовими даними | API для інтеграції, вихідний код |
| Інтеграція LLM-модуля і генерація звітів | 3-5 днів | Перші аналітичні звіти з рекомендаціями | Шаблони звітів, налаштований LLM-модуль |
| Візуалізація (дашборд) і документування | 5-7 днів | Дашборд з розподілом цінності, ROI, removal effect | Доступ до дашборду (Power BI / Tableau), інструкція користувача |
| Навчання команди і коригування | 2-3 дні | Команда готова інтерпретувати результати | Відеозапис навчання, презентація |
| Технічна підтримка | 1 місяць | Супровід після впровадження | Служба підтримки, SLA |
Всі роботи виконуються під ключ, включаючи налаштування трекінгу, моделювання, інтеграцію з CRM та BI. Ми пропонуємо впровадження під ключ за 2-4 тижні. Пишіть нам для оцінки вашого проекту. Оцініть економію вже сьогодні — безкоштовний аудит даних.
Чому клієнти обирають нас?
Ми займаємося AI-рішеннями для маркетингу понад 5 років. За цей час реалізували більше 15 проектів з атрибуції для e-commerce, SaaS та fintech. Наша система гарантує прозорість: ви бачите внесок кожного каналу і можете експериментально перевіряти гіпотези. Середнє зростання ROAS після впровадження — 20-30% за перший квартал. Замовте попередній аудит ваших даних — це безкоштовно.







