Data-Driven атрибуція: Shapley, Markov chain та LLM для зростання ROAS

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1564 послуг
Data-Driven атрибуція: Shapley, Markov chain та LLM для зростання ROAS
Середній
~1-2 тижні
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1351
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1247
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    950
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1186
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    642
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    922

Оптимізація маркетингового бюджету за допомогою 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% за перший квартал. Замовте попередній аудит ваших даних — це безкоштовно.

Чому дата-інжиніринг визначає успіх ML-моделі

Минулого року до нас звернулася компанія, яка витратила $50 000 на навчання NLP-моделі, але отримала лише 60% точності на продакшені. Причина — data leakage через випадковий split часових даних. Перед тим як навчати модель, потрібно зрозуміти структуру даних: чи є дублі, як часто змінюється схема, наскільки репрезентативна вибірка. Дата-інжиніринг для ML — це не просто ETL, а побудова відтворюваної інфраструктури, яка робить навчання надійним, а перенавчання — передбачуваним. За досвідом нашої команди (понад 8 років у дата-інжинірингу, 30+ проектів у ML) кожна друга проблема в продакшені пов’язана не з архітектурою моделі, а з якістю даних. Замовте аудит ваших даних — оцінимо поточний пайплайн безкоштовно.

Як ETL-пайплайни для ML відрізняються від BI

ETL для аналітики та ETL для ML — різні завдання. В аналітиці важлива агрегація, у ML — індивідуальні записи з історією. В аналітиці train/val/test split не потрібен, у ML — критичний. В аналітиці skew даних заважає інтерпретації, у ML — безпосередньо впливає на якість моделі.

Інструменти. Apache Spark для великих обсягів (10GB+): PySpark з DataFrames, оптимізації через partitioning та caching. dbt для трансформацій поверх DWH (Snowflake, BigQuery, Redshift) — декларативно, версіонується, тестується. Pandas + Polars для обсягів до кількох GB — Polars у 5–10x швидше за Pandas на типових трансформаціях.

Temporal splits. Для ML важливо, що split за часом, а не випадковий. Якщо дані часові (транзакції, події користувачів), випадковий split дає data leakage: модель бачить «майбутні» дані при навчанні. Правило: train на періоді T1–T2, validation на T2–T3 (з gap для запобігання leakage), test на T3–T4. Неправильний split може коштувати 10–15% якості моделі на валідації. Temporal split best practices (scikit-learn docs)

Інкрементальні пайплайни. Модель перенавчається щотижня на нових даних. Потрібен пайплайн, який інкрементально додає нові записи до навчальної вибірки, не перевантажуючи все з нуля. Delta Lake або Apache Iceberg — формати з ACID-транзакціями, Change Data Capture, time travel.

Як уникнути training-serving skew за допомогою Feature Store

Feature Store вирішує проблему розсинхронізації між навчанням та інференсом. Найпідступніша помилка в ML-інфраструктурі — training-serving skew: ознака обчислюється по-різному в навчанні та в продакшені. Модель вчиться на «правильних» даних, а інференс отримує інші.

Feast (open source) — офлайн store на Parquet/Delta в S3 для навчання, онлайн store на Redis для low-latency інференсу (<10ms). Feature definitions як Python-код:

from feast import FeatureView, Field
from feast.types import Float32, Int64

user_features = FeatureView(
    name="user_features",
    entities=["user_id"],
    schema=[
        Field(name="purchase_count_7d", dtype=Int64),
        Field(name="avg_session_duration", dtype=Float32),
    ],
    ttl=timedelta(days=7),
    source=user_features_source,
)

Один definition використовується всюди — немає розбіжностей.

Потокові ознаки. Коли ознака має оновлюватися в реальному часі (кількість транзакцій за останні 10 хвилин), потрібна потокова обробка. Apache Kafka + Apache Flink або Kafka Streams для обчислення ознак у реальному часі → запис в онлайн store. Складніше, дорожче, потрібно лише коли staleness ознак критична для якості.

Розмітка даних: як не витратити бюджет даремно

Розмітка — найтрудомісткіша та недооцінювана частина ML-проекту. Погано розмічені дані не виправить жодна архітектура.

Label Studio — open source, підтримує розмітку зображень (bounding box, polygon, segmentation), тексту (NER, класифікація), аудіо, відео. Піднімається за 10 хвилин через Docker. Для невеликих команд — перший вибір.

Оцінка якості розмітки. Inter-annotator agreement — наскільки згодні розмітники між собою. Cohen's Kappa > 0.8 — добре, 0.6–0.8 — прийнятно, < 0.6 — завдання неоднозначне або інструкція погана. Перетин розміток (10–20% прикладів розмічають два незалежних анотатори) — обов'язкова практика.

Active learning. Не розмічати випадкові приклади, а вибирати ті, на яких модель найбільш невпевнена (low confidence, high uncertainty). Дозволяє досягти тієї ж якості при 50–70% обсягу розмітки. Modals, Prodigy, Label Studio підтримують active learning workflows. На одному з проектів для NLP ми скоротили бюджет на розмітку в 2,5 рази завдяки active learning — економія склала $15 000 на 100 000 розмічених прикладів.

Синтетичні дані. Коли реальних даних мало або отримати їх дорого. Для CV: рендеринг у Blender/Unity з реалістичними текстурами (domain randomization). Для NLP: parafrase через LLM, backtranslation. Ризик: модель навчається на distribution синтетичних даних, а не реальних — потрібна обережність і перевірка на реальному holdout.

Якість даних: валідація та моніторинг

Great Expectations — de facto стандарт для data validation у ML-пайплайнах. Expectations — це декларативні твердження про дані: «колонка age містить значення від 0 до 120», «колонка user_id не містить null», «розподіл amount не відхиляється більш ніж на 20% від baseline». Запускається в пайплайні, при провалі — блокує проходження.

Pandera — Pythonic alternative для pandas/polars DataFrames. Schema-based validation з type hints:

import pandera as pa

schema = pa.DataFrameSchema({
    "user_id": pa.Column(int, nullable=False),
    "score": pa.Column(float, pa.Check.between(0, 1)),
    "label": pa.Column(str, pa.Check.isin(["positive", "negative", "neutral"])),
})

Data freshness. Модель очікує дані за останні N днів. ETL впав, дані не оновилися — модель використовує застарілі ознаки. Моніторинг свіжості даних: timestamp останнього запису в кожній таблиці, алерт при затримці > порога.

Дедуплікація. Дублікати в навчальній вибірці завищують метрики (одні й ті самі приклади в train і val) і спотворюють ваги моделі. MinHash LSH для наближеної дедуплікації великих датасетів. Для точної — хеш за нормалізованим контентом.

Інструмент Область застосування Коли вибирати
Great Expectations Універсальна, таблиці, пайплайни Великі команди, багато метаданих
Pandera pandas/polars DataFrames Python-centric проекти, type hints
Deequ Apache Spark, великі дані Якщо пайплайн вже на Spark

Сховища та формати

Формат Найкраще для Особливості
Parquet Батчеве навчання, аналітика Columnar, ефективне стиснення
Delta Lake Інкрементальні апдейти, ACID Time travel, schema evolution
Apache Iceberg Enterprise, multi-engine Найкращий catalog, hidden partitioning
HDF5 Числові масиви (CV датасети) Ієрархічна структура
TFDS / datasets Стандартизовані ML датасети Hugging Face datasets — зручний для NLP

Для більшості ML-проектів на старті: Parquet в S3 + DVC для версіонування. Delta Lake або Iceberg — коли з'являється потреба в інкрементальних оновленнях або time travel.

Типові помилки при побудові пайплайнів

  • Пропуск перевірки свіжості даних. Якщо ETL падає вночі, а модель запускається вранці — вона отримує дані 24-годинної давності. Рішення: алерт при затримці > 30 хвилин.
  • Відсутність версіонування даних. Не можна відтворити експеримент, бо дані змінилися. DVC або Delta Lake time travel виправляють це.
  • Забувають про schema evolution. Нове поле з’являється, а пайплайн падає. Автоматичне виявлення змін схеми через Great Expectations.

Active learning дозволяє скоротити бюджет на розмітку до 50–70%. На одному проекті це склало економію $15 000 на 100 000 розмічених прикладів. Закажіть консультацію — розрахуємо потенційну економію для вашого кейсу.

Що входить у проект з дата-інжинірингу для ML

Ми надаємо повний цикл:

  • Аудит існуючих даних та пайплайнів (1 тиждень).
  • Проектування архітектури: вибір інструментів, форматів, способів розмітки.
  • Реалізація ETL/ELT пайплайну з валідацією та моніторингом.
  • Документація коду та процесів (model card, data card).
  • Навчання вашої команди роботі з пайплайном.
  • SLA на супровід та підтримку.

Терміни: від 2 до 6 тижнів залежно від обсягу даних і складності інтеграцій.

Як ми будуємо пайплайн: покроково

  1. Аудит існуючих даних. Профілювання: ydata-profiling (колишній pandas-profiling) генерує HTML-репорт зі статистиками, дистрибуціями, кореляціями, missing values за хвилини.
  2. Проектування пайплайну. Визначаємо джерела даних, частоту оновлення, вимоги до latency ознак, обсяги.
  3. Реалізація та тестування. Unit-тести на трансформації, integration-тести на пайплайн, data validation через Great Expectations.
  4. Деплой та моніторинг. Алерти на freshness, quality checks, аномалії в обсягах даних.

Чому варто довірити це нам

Ми займаємося дата-інжинірингом та ML з понад 8-річним досвідом. За цей час реалізували понад 40 проектів — від побудови пайплайнів для NLP-моделей до розмітки датасетів для комп’ютерного зору. Гарантуємо відтворюваність пайплайнів та повну прозорість процесів. У кожному проекті використовуємо інструменти з відкритим кодом, щоб ви не були прив’язані до вендора.

Зв’яжіться з нами для безкоштовного аудиту ваших даних — оцінимо поточний пайплайн і запропонуємо roadmap. Замовте побудову ML-пайплайну під ключ.