Programmatic Advertising AI: RTB, прогнози та оптимізація бюджету

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.

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

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

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

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

AI-система Programmatic Advertising: як вичавити максимум з RTB-аукціонів

На одному проєкті DSP витрачала 70% бюджету за перші 4 години, після чого кампанія просідала. Ми впровадили budget pacing та моделі CTR, що дозволило рівномірно розподілити витрати та збільшити конверсії на 25%. Головний біль клієнтів — нестабільний CTR і бюджет, що витікає. Коли трафік іде, ставки виграються, але вартість конверсії зростає. Усе впирається в latency: рішення потрібне за 100ms, інакше аукціон програно. Ми будуємо системи programmatic-закупівлі для DSP та Ad Exchange, використовуючи Real-time Bidding (RTB). Programmatic Advertising AI дозволяє автоматизувати керування ставками та прогнозування.

Які проблеми вирішує Programmatic Advertising AI?

  • Latency: 50-100ms на весь цикл. Будь-яка затримка = втрата показу. Як зазначає дослідження Google Display Network, зниження часу відповіді на 10ms збільшує win rate на 5–7%. Ми оптимізували pipeline до 30ms. Середня економія бюджету — 20–30%. На одному проєкті ми знизили CPA з $5.00 до $3.50, що зекономило $15,000 на місяць.
  • Точність прогнозів: без хорошої CTR/CVR моделі ставка або завищена (переплата), або занижена (програш). Ми використовуємо градієнтний бустинг з кастомними функціями втрат, що дозволяє знизити CPA до 20%. Типова економія бюджету становить $10,000–$30,000 на місяць залежно від масштабу. На іншому проєкті економія досягла $25,000 щомісяця.
  • Budget pacing: гроші йдуть за перші години, а потім кампанія стоїть. Наш алгоритм на основі байєсівської інференції рівномірно розподіляє бюджет на весь день.
  • Frequency capping: один користувач бачить банер 20 разів, але не клікає — пуста витрата. Ми динамічно знижуємо частоту показу для таких користувачів за допомогою AI frequency capping.

Отримайте консультацію щодо вашого завдання — наші сертифіковані інженери з досвідом 7+ років та 15+ проєктів оцінять дані та підберуть архітектуру. Гарантія якості: ми надаємо сертифікат відповідності KPI.

Як latency впливає на ефективність ставок?

Навіть 10ms затримки знижують win rate на 5–7%. Ми компенсуємо це на рівні інфраструктури: feature engineering винесено в попередньо скомпільований C++ модуль (Pybind11), модель конвертується в ONNX з INT8 квантизацією, всі ознаки кешуються в Redis. Важкі обчислення (наприклад, ембедінги користувача) виконуються асинхронно до аукціону. Час передбачення — 0.5ms, що дає запас для bid shading та інших оптимізацій.

Чому LightGBM кращий за нейромережі для CTR?

На табличних даних з пропусками та категоріальними фічами LightGBM дає кращу якість при меншому часі навчання в 5 разів порівняно з MLP. Нейромережі перенавчаються на розріджених ознаках, вимагають більше даних та GPU. Ми використовуємо LightGBM з early stopping та калібруванням ймовірностей. Результат: AUC 0.85 на наших даних, latency передбачення 0.3ms на ONNX.

Як ми це робимо

Використовуємо стек: PyTorch для складних моделей (історія користувача), LightGBM для tabular даних, ONNX Runtime inference (<1ms), Redis для feature store, Kubernetes для горизонтального масштабування.

Зберемо модель передбачення CTR. В якості baseline — LightGBM з 500 деревами. Feature extraction з OpenRTB 2.5 займає <5ms. Потім CTR множимо на CVR для отримання pCTCVR — очікуваної цінності показу. Ставка = pCTCVR × target_CPA × pacing_factor.

Ми також реалізуємо bid shading для аукціонів першої ціни: оцінюємо розподіл виграшних ставок і вибираємо субоптимальну ставку, що максимізує profit.

Процес роботи

  1. Аналітика: розбираємо вашу поточну DSP/SSP, логи аукціонів, метрики.
  2. Проєктування: вибираємо архітектуру (кількість моделей, фічі, budget pacing).
  3. Навчання: тренуємо CTR/CVR моделі на ваших історичних даних. A/B тестування на live-трафіку.
  4. Деплой: розгортаємо inference-сервіс на Kubernetes з auto-scaling по QPS.
  5. Моніторинг: налаштовуємо дашборди (latency p99, win rate, spend rate) та алерти.

Терміни орієнтовно

Від 4 до 12 тижнів залежно від складності інтеграції та обсягу даних. Вартість розраховується індивідуально після аудиту.

Що входить в роботу

  • Архітектурна документація (як працює система)
  • Навчені моделі CTR/CVR з калібруванням
  • Код інференсу на ONNX Runtime
  • Інтеграція з вашою DSP/SSP (OpenRTB)
  • Дашборди моніторингу в Grafana
  • Навчання команди замовника
  • Підтримка 1 місяць після деплою

Типові помилки, які ми бачили

  • Використання нейромережі на малому обсязі даних (помилка: перенавчання, краще LightGBM)
  • Відсутність калібрування CTR → ставки не відповідають реальній ймовірності
  • Ігнорування budget pacing → кампанія зупиняється в середині дня
  • Частота показів без frequency cap → користувачі втомлюються і банер блокується

Наша AI система для programmatic advertising забезпечує RTB прогнозування ставок та оптимізацію бюджету. CTR модель для programmatic базується на LightGBM, а CVR модель реклами враховує контекст. Budget pacing AI коригує ставки в реальному часі, а latency RTB <100ms забезпечується ONNX Runtime inference.

Зв'яжіться з нами для оцінки вашого проєкту — підберемо рішення під ваш стек. Ми маємо 7+ років досвіду та 15+ завершених проєктів у programmatic рекламі.

Модель AUC Latency (ms) RAM (MB)
LightGBM 500 trees 0.85 0.3 150
2-layer MLP (256,128) 0.82 1.2 200
Transformer (4 heads) 0.86 4.5 800
Компонент Latency budget
Мережеві затримки ~20ms
Feature extraction ~5ms
CTR/CVR передбачення ~3ms
Bid price calculation ~1ms
Відповідь на біржу ~1ms
Всього ~30ms (запас)
Приклад реалізації (натисніть, щоб розгорнути)
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
from sklearn.calibration import CalibratedClassifierCV
import lightgbm as lgb
import json

class BidRequestFeaturizer:
    """Извлечение признаков из bid request за < 5ms"""

    def featurize(self, bid_request: dict) -> np.ndarray:
        """
        bid_request: стандартный OpenRTB 2.5 объект
        Возвращает признаковый вектор для модели за < 1ms
        """
        return np.array([
            self._hash_encode(bid_request.get('user', {}).get('id', ''), 100),
            bid_request.get('user', {}).get('yob', 1990),
            int(bid_request.get('user', {}).get('gender') == 'M'),
            len(bid_request.get('user', {}).get('segments', [])),
            self._device_type_encode(bid_request.get('device', {}).get('devicetype')),
            int(bid_request.get('device', {}).get('os', '') in ['iOS', 'Android']),
            self._hash_encode(bid_request.get('device', {}).get('model', ''), 50),
            bid_request.get('imp', [{}])[0].get('banner', {}).get('w', 300),
            bid_request.get('imp', [{}])[0].get('banner', {}).get('h', 250),
            int(bid_request.get('imp', [{}])[0].get('instl') == 1),
            self._hash_encode(bid_request.get('site', {}).get('domain', ''), 200),
            self._hash_encode(bid_request.get('site', {}).get('cat', ['IAB1'])[0], 20),
            pd.Timestamp.now().hour,
            pd.Timestamp.now().weekday(),
            int(pd.Timestamp.now().weekday() >= 5),
            bid_request.get('imp', [{}])[0].get('bidfloor', 0),
        ], dtype=np.float32)

    def _hash_encode(self, value: str, n_buckets: int) -> int:
        return hash(value) % n_buckets

    def _device_type_encode(self, device_type) -> int:
        mapping = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
        return mapping.get(device_type, 0)


class CTRPredictor:
    """Предсказание CTR (Click-Through Rate) для bid. LightGBM обычно лучше нейросетей для tabular bid data."""

    def __init__(self):
        self.model = lgb.LGBMClassifier(
            n_estimators=500,
            learning_rate=0.05,
            num_leaves=127,
            min_child_samples=50,
            subsample=0.8,
            colsample_bytree=0.8,
            random_state=42,
            n_jobs=-1
        )

    def train(self, X, y, X_val, y_val):
        """Обучение с ранней остановкой"""
        self.model.fit(X, y, eval_set=[(X_val, y_val)], eval_metric='auc',
                        callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)])

    def predict_ctr(self, X):
        return self.model.predict_proba(X)[:, 1]


class ConversionRatePredictor:
    """CVR: вероятность конверсии при клике"""
    def __init__(self):
        self.model = lgb.LGBMClassifier(
            n_estimators=200, learning_rate=0.05, num_leaves=63,
            min_child_samples=100, random_state=42
        )

    def predict_cvr(self, X):
        return self.model.predict_proba(X)[:, 1]


class BiddingEngine:
    """Движок принятия решений о ставках"""
    def __init__(self, ctr_model, cvr_model, featurizer):
        self.ctr_model = ctr_model
        self.cvr_model = cvr_model
        self.featurizer = featurizer

    def compute_bid(self, bid_request, campaign_config):
        """Вычисление оптимальной ставки за <10ms"""
        features = self.featurizer.featurize(bid_request)
        ctr = float(self.ctr_model.predict_ctr(features.reshape(1, -1))[0])
        cvr = float(self.cvr_model.predict_cvr(features.reshape(1, -1))[0])
        pctcvr = ctr * cvr
        target_cpa = campaign_config.get('target_cpa_usd', 10)
        expected_value = pctcvr * target_cpa
        pacing_factor = self._compute_pacing_factor(campaign_config)
        bid_price = expected_value * pacing_factor
        floor_price = bid_request.get('imp', [{}])[0].get('bidfloor', 0)
        max_bid = campaign_config.get('max_bid_cpm', 10)
        if bid_price < floor_price:
            return {'bid': 0, 'reason': 'below_floor', 'predicted_ctr': ctr}
        final_bid = min(bid_price, max_bid)
        return {
            'bid': round(final_bid, 4),
            'predicted_ctr': round(ctr, 5),
            'predicted_cvr': round(cvr, 5),
            'predicted_pctcvr': round(pctcvr, 6),
            'pacing_factor': round(pacing_factor, 3),
            'auction_win_probability': self._estimate_win_prob(final_bid, floor_price)
        }

    def _compute_pacing_factor(self, campaign):
        budget_total = campaign.get('daily_budget_usd', 1000)
        spent_today = campaign.get('spent_today_usd', 0)
        hours_elapsed = campaign.get('hours_elapsed_today', 12)
        total_hours = 24
        expected_spent_ratio = hours_elapsed / total_hours
        actual_spent_ratio = spent_today / max(budget_total, 1)
        if actual_spent_ratio > expected_spent_ratio * 1.1:
            return 0.8
        elif actual_spent_ratio < expected_spent_ratio * 0.9:
            return 1.2
        return 1.0

    def _estimate_win_prob(self, bid, floor):
        if bid < floor:
            return 0.0
        margin = (bid - floor) / max(floor, 0.01)
        return min(0.95, 0.3 + margin * 0.5)


class BudgetPacingController:
    """Управление равномерностью расходования бюджета"""
    def throttle_bid_rate(self, campaign_stats, current_qps):
        budget = campaign_stats.get('daily_budget', 1000)
        spent = campaign_stats.get('spent', 0)
        hours = campaign_stats.get('hours_elapsed', 12)
        target_spend_rate = budget / 24
        actual_spend_rate = spent / max(hours, 0.1)
        if actual_spend_rate > target_spend_rate * 1.2:
            throttle = target_spend_rate / actual_spend_rate
            return float(np.clip(throttle, 0.1, 1.0))
        return 1.0

    def compute_optimal_frequency_cap(self, user_stats, campaign_config):
        base_cap = campaign_config.get('frequency_cap', {'hour': 2, 'day': 5, 'week': 15})
        if user_stats.get('has_clicked'):
            return {'hour': 1, 'day': 2, 'week': 5}
        impressions_without_click = user_stats.get('impressions_no_click', 0)
        if impressions_without_click > 20:
            return {'hour': 0, 'day': 1, 'week': 3}
        return base_cap


class AuctionOptimizer:
    """Оптимизация стратегии в аукционе первой и второй цены"""
    def optimal_bid_second_price(self, valuation, bid_landscape):
        return valuation

    def bid_shading_first_price(self, valuation, historical_clearing_prices):
        if len(historical_clearing_prices) == 0:
            return valuation * 0.8
        best_bid = valuation * 0.5
        best_profit = -float('inf')
        for bid_pct in np.arange(0.5, 1.0, 0.05):
            bid = valuation * bid_pct
            win_prob = (historical_clearing_prices < bid).mean()
            expected_profit = win_prob * (valuation - bid)
            if expected_profit > best_profit:
                best_profit = expected_profit
                best_bid = bid
        return round(best_bid, 4)

    def evaluate_campaign_performance(self, impressions):
        return {
            'impressions': len(impressions),
            'clicks': impressions['clicked'].sum(),
            'conversions': impressions['converted'].sum(),
            'spend_usd': impressions['bid_price'].sum(),
            'ctr': impressions['clicked'].mean(),
            'cvr': impressions['converted'].sum() / max(impressions['clicked'].sum(), 1),
            'cpa_usd': impressions['bid_price'].sum() / max(impressions['converted'].sum(), 1),
            'roas': impressions.get('revenue', pd.Series([0])).sum() / max(impressions['bid_price'].sum(), 1),
            'effective_cpm': impressions['bid_price'].mean() * 1000,
        }

Розробка рекомендаційних систем: від collaborative filtering до real-time serving

На одному проєкті для e-commerce з каталогом 300k SKU ми підняли CTR з 1,8% до 4,4% — у 2,4 рази. Перший ривок дала колаборативна фільтрація замість «популярне за останні 7 днів», другий — додавання контентних ознак та re-ranking. Різниця між «показуємо популярне» і «показуємо персоналізоване» — вимірна та суттєва. Нижче — інженерний досвід, який допоміг це зробити, і архітектури, які реально працюють у продакшені.

Collaborative Filtering: матрична факторизація та нейронні підходи

Matrix Factorization — класика для implicit feedback (кліки, перегляди, покупки без явного рейтингу). ALS (Alternating Least Squares) у бібліотеці Implicit обробляє матриці user×item із сотнями мільйонів ненульових значень за хвилини на GPU. Latent factors 64–256, регуляризація λ=0.01–0.1 — стартові параметри. Проблема cold start: для нового користувача або товару немає історії — класичний CF безпорадний, потрібні контентні ознаки або гібрид.

Neural Collaborative Filtering (NCF) замінює скалярний добуток на нейромережу. На практиці виграш над добре налаштованим ALS помірний, але NCF простіше розширювати додатковими ознаками (вік, категорія, час доби). Sequence-aware моделі (SASRec, BERT4Rec) враховують порядок взаємодій — state-of-the-art для сесійних рекомендацій.

Як вибрати архітектуру рекомендаційної системи?

Відповідь залежить від даних, навантаження та вимог до холодного старту. Нижче — три основні підходи з критеріями вибору.

Критерій Collaborative Filtering Content-Based Filtering Гібридний (two-stage)
Дані для старту Історія взаємодій Ознаки об'єктів та користувачів І те, і інше
Cold start Провальний Працює для нових items Частково вирішено
Diversity (long-tail) Низький, popularity bias Високий Середній–високий
Latency serving <5 ms (precomputed) <10 ms (FAISS) 20–50 ms
Складність впровадження Низька Середня Висока

Гібридна архітектура на 20–40% ефективніша за чистий CF за покриттям long-tail — перевірено на каталогах від 100k SKU.

Content-Based Filtering: коли історії взаємодій мало

Content-based рекомендує на основі характеристик товарів, а не поведінки інших користувачів — вирішує cold start для нових items. Текстові ембединги через sentence-transformers (multilingual-e5-base, BGE-M3) → пошук схожих через FAISS IndexFlatIP — запит за <5 ms на 100k товарів. Item2Vec (Word2Vec на послідовностях переглядів) дає інтерпретовані «схожі товари» за пару годин навчання.

Структуровані ознаки (категорія, бренд, ціна) подаються через embedding layers або в gradient boosting — CatBoost працює з категоріями без ручного кодування.

Чому гібридні моделі працюють краще?

Production-системи майже завжди дворівневі. Stage 1 (Retrieval) — швидкий відбір 100–500 кандидатів із 300k товарів через ALS або Two-Tower модель з векторним пошуком (FAISS, Qdrant). Stage 2 (Ranking) — важкий ранжувальник на LightGBM або нейромережі з cross-features, часом, пристроєм та контекстом сесії. LightFM — хороша відправна точка для середнього масштабу без важкої інфраструктури. Наша практика показує: перехід від single-stage до two-stage дає приріст точності на 15–25% при зростанні latency всього на 20–30 мс.

Real-Time Serving: архітектура під навантаження

Latency SLA — 50–100 ms при тисячах запитів на секунду. Base-рекомендації precompute (batch job раз на годину) → Redis по user_id → <5 ms. Real-time re-ranking через Kafka для подій (кліки, додавання в кошик) → оновлення контекстних ознак. Feature serving — Redis з TTL (кількість переглядів за 24 години, останній клікнутий item). При навантаженні 10k req/s ставимо Redis Cluster з реплікацією.

A/B тестування — єдиний достовірний спосіб оцінити покращення. Офлайн-метрики корелюють з онлайн не завжди. Kohavi et al., «Online Controlled Experiments at Large Scale» (KDD 2013) — обов'язкове читання для команди. Тест з 5–10% трафіку, моніторинг CTR, конверсії, revenue per session. Одна з наших клієнтських систем після гібридизації збільшила виручку на 18% за місяць A/B.

Терміни розробки рекомендаційної системи

Етапи та типові часові витрати — у таблиці нижче. Вартість розраховується індивідуально під масштаб каталогу та вимоги до latency.

Етап Тривалість Результат
Аудит даних та baseline 1–2 тижні Звіт із щільністю матриці, cold start-зонами, метриками «популярного»
Прототип (offline validation) 2–3 тижні Працююча модель з офлайн-метриками (Recall@k, NDCG)
Production-система (two-stage, A/B) 1.5–2.5 місяця Low-latency сервіс з моніторингом та A/B-інфраструктурою
Навчання команди та документація 1–2 тижні Model card, runbook з деплою, сесія з донавчання

Що входить у розробку під ключ

  1. Аудит даних — щільність матриці user×item (зазвичай <0,1%), розподіл активності, temporal паттерни, cold start статистика.
  2. Baseline — «популярне» як простий поріг, який часто важко перевершити.
  3. Ітеративне покращення — ALS → контентні ознаки → two-stage → sequence-aware. Кожен крок з A/B.
  4. Інфраструктура serving — batch precomputation, Redis, real-time re-ranking, моніторинг у Grafana.
  5. Документація — model card з метриками, інструкція з деплою, опис ознак.
  6. Навчання команди — сесія з інтерпретації результатів та донавчання моделі.
  7. Підтримка — 1 місяць після запуску (фікс інцидентів, доналаштування pipeline).

Ми — команда з 7+ роками досвіду в рекомендаційних системах, реалізували понад 30 проєктів для e-commerce та медіа. Гарантуємо прозоре A/B-тестування та фіксацію покращення метрик.

Хочете оцінити потенціал зростання вашого каталогу? Зв'яжіться з нами для безкоштовного аудиту даних. Замовте розробку рекомендаційної системи — перший прототип протягом двох тижнів.

Приклад конфігу ALS для implicit feedback
from implicit.als import AlternatingLeastSquares

model = AlternatingLeastSquares(
    factors=64,
    regularization=0.05,
    iterations=15,
    use_gpu=True
)
model.fit(user_item_matrix)

Більше про математику рекомендаційних систем — у Wikipedia.