Рекламний відділ запускає 50 банерів — який із них спрацює? A/B-тести вимагають тижнів і тисяч показів. Ми вирішуємо це до запуску: AI-система аналізує візуальні ознаки креативу та передбачає CTR з точністю до ±15% на основі історичних даних. За 5 років роботи ми впровадили аналогічні рішення для 30+ проєктів в e-commerce та fintech, гарантуючи зростання CTR на 10–30% після оптимізації. Один із клієнтів — маркетплейс одягу — витрачав значний бюджет на тестування креативів; після впровадження AI-системи вони скоротили витрати на 40% та підвищили середній CTR на 22%. Окупність системи становить 2–3 місяці.
Як AI передбачає CTR?
Система вилучає два типи ознак: семантичні через CLIP та низькорівневі візуальні (контраст, насиченість, текстове покриття, наявність облич). Навчаємо LightGBM на логарифмі CTR для нормалізації розподілу. LightGBM у 3 рази швидший за нейромережеві аналоги та дає порівнянну точність.
import torch import torch.nn.functional as F from transformers import CLIPProcessor, CLIPModel from PIL import Image import numpy as np import cv2 class CreativeFeatureExtractor: """ Мультимодальні ознаки: CLIP semantic + низькорівневі visual. """ def __init__(self): self.clip = CLIPModel.from_pretrained( 'openai/clip-vit-large-patch14' ).eval().cuda() self.processor = CLIPProcessor.from_pretrained( 'openai/clip-vit-large-patch14' ) @torch.no_grad() def extract_clip_features( self, image: Image.Image ) -> np.ndarray: inputs = self.processor(images=image, return_tensors='pt').to('cuda') emb = self.clip.get_image_features(**inputs) return F.normalize(emb, dim=-1).cpu().numpy().squeeze() def extract_visual_features(self, image: Image.Image) -> dict: """ Низькорівневі ознаки, що корелюють з CTR: - face_area_ratio: наявність обличчя (face = +18% CTR за Nielsen) - contrast: високий контраст → помітність - color_harmony: гармонійна палітра - text_coverage: скільки % займає текст - brightness_variance: візуальна складність """ img_array = np.array(image) h, w = img_array.shape[:2] features = {} # Контраст (Michelson) gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY) features['contrast_michelson'] = float( (gray.max() - gray.min()) / (gray.max() + gray.min() + 1e-8) ) # Яскравісна дисперсія features['brightness_variance'] = float(gray.std() / 128.0) # Домінантні кольори (k=5 через k-means) pixels = img_array.reshape(-1, 3).astype(np.float32) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0) _, labels, centers = cv2.kmeans( pixels, 5, None, criteria, 5, cv2.KMEANS_PP_CENTERS ) counts = np.bincount(labels.flatten(), minlength=5) dominant_color = centers[counts.argmax()] features['dominant_hue'] = float( cv2.cvtColor( dominant_color.reshape(1,1,3).astype(np.uint8), cv2.COLOR_RGB2HSV )[0,0,0] / 180.0 ) features['dominant_saturation'] = float(dominant_color.max() - dominant_color.min()) / 255.0 # Детекція облич face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) faces = face_cascade.detectMultiScale(gray, 1.1, 4) total_face_area = sum(fw * fh for _, _, fw, fh in faces) if len(faces) > 0 else 0 features['face_area_ratio'] = total_face_area / (h * w) features['has_face'] = int(len(faces) > 0) features['face_count'] = len(faces) # Частка текстових регіонів (через морфологію) _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel) features['text_coverage_estimate'] = float( (gradient > 0).mean() ) return features import pandas as pd import numpy as np import lightgbm as lgb from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler class CreativeCTRPredictor: """ Навчаємо на історичних даних: (visual_features, clip_embedding) → CTR. Цільова змінна: log(CTR) для нормалізації розподілу. """ def __init__(self): self.feature_extractor = CreativeFeatureExtractor() self.model = lgb.LGBMRegressor( n_estimators=500, learning_rate=0.05, max_depth=6, min_child_samples=20, subsample=0.8, colsample_bytree=0.8, reg_lambda=0.1 ) self.scaler = StandardScaler() def build_feature_vector(self, image: Image.Image) -> np.ndarray: visual = self.feature_extractor.extract_visual_features(image) clip_emb = self.feature_extractor.extract_clip_features(image) visual_vec = np.array(list(visual.values())) # CLIP 768-dim + візуальні ознаки ~10 dim return np.concatenate([clip_emb, visual_vec]) def predict_ctr( self, image: Image.Image ) -> dict: features = self.build_feature_vector(image) features_scaled = self.scaler.transform(features.reshape(1, -1)) log_ctr = self.model.predict(features_scaled)[0] ctr_predicted = np.exp(log_ctr) # SHAP пояснюваність — топ-3 фактори import shap explainer = shap.TreeExplainer(self.model) shap_values = explainer.shap_values(features_scaled) return { 'predicted_ctr': round(float(ctr_predicted), 4), 'ctr_percentile': None, # заповнюється з розподілу на train 'top_factors': self._top_shap_factors(shap_values[0], features) } def _top_shap_factors( self, shap_vals: np.ndarray, feature_vals: np.ndarray, top_k: int = 3 ) -> list: top_indices = np.argsort(np.abs(shap_vals))[::-1][:top_k] feature_names = ( [f'clip_{i}' for i in range(768)] + ['contrast', 'brightness_var', 'dominant_hue', 'dominant_sat', 'face_ratio', 'has_face', 'face_count', 'text_coverage'] ) return [ { 'feature': feature_names[i] if i < len(feature_names) else f'feat_{i}', 'shap_value': float(shap_vals[i]), 'direction': 'positive' if shap_vals[i] > 0 else 'negative' } for i in top_indices ] Чому LightGBM, а не DNN?
Градієнтний бустинг на табличних даних дає кращу якість, ніж нейромережі, при малому обсязі даних (тисячі креативів). LightGBM у 3 рази швидший за CatBoost на 500+ ознаках, а SHAP-інтерпретація працює «з коробки». Для продакшену ми використовуємо vLLM для інференсу CLIP та ONNX Runtime для прискорення моделі.
Що таке SHAP і як він допомагає дизайнерам?
SHAP (SHapley Additive exPlanations) присвоює кожній ознаці внесок у передбачення — аналогічно розподілу виграшу в кооперативній грі. Дизайнер бачить, що «наявність обличчя» збільшила прогнозований CTR на 0.8%, а «низький контраст» знизив на 0.4%. Це дозволяє свідомо покращувати банер, а не гадати. Ми інтегруємо SHAP-графіки в дашборд Streamlit, де можна завантажити креатив і одразу отримати топ-3 фактори.
Як ми інтегруємо систему у ваш creative workflow?
Ми розгортаємо REST API на Triton Inference Server або SageMaker. API приймає зображення та повертає predicted_ctr і top_factors. Через плагіни для Figma і Photoshop дизайнери отримують предикт прямо в інтерфейсі. Вбудована MLOps-інфраструктура (MLflow, DVC) версіонує дані та моделі, підтримує A/B-тести нових версій. Типова інтеграція займає 1–2 тижні.
Ключові інсайти з даних
За накопиченою статистикою рекламних платформ (Google, Meta):
| Візуальна ознака | Вплив на CTR | Примітка |
|---|---|---|
| Наявність обличчя (frontal) | +15–22% | Особливо для fashion, beauty |
| Висока насиченість кольору (>0.6) | +8–14% | Не працює для B2B |
| Текст < 20% площі | +10–17% | Meta обмеження 20% |
| Контраст > 0.7 | +6–11% | Помітність у стрічці |
| Обличчя дивиться на CTA | +12% | Eye tracking дослідження |
| Теплі кольори (hue 0-60°) | +5–9% | Для food, lifestyle |
Процес роботи
- Аналітика: аудит історичних креативів, збір метрик, оцінка вибірки. Ми перевіряємо, чи достатньо даних і чи є баланс за категоріями.
- Проєктування: вибір архітектури (CLIP vs EfficientNet, LightGBM vs XGBoost), опис feature pipeline. Визначаємо метрики якості: MAE, MAPE, кореляція Спірмена.
- Реалізація: навчання моделі, валідація на відкладеній вибірці (time-based split). Інтегруємо SHAP для пояснюваності.
- Тестування: A/B-експеримент на 20 креативах — порівнюємо передбачення з фактичним CTR після тижня показів.
- Деплой: розгортання API, підключення до дизайн-пайплайну через плагіни. Налаштовуємо моніторинг дрейфу даних.
Що входить у роботу
- Документація: model card, експлуатаційна інструкція, опис API.
- Вихідний код з DVC-версіонуванням даних.
- Доступ до дашборду Streamlit для тестування нових креативів.
- Навчання команди роботі з системою (2 години).
- Підтримка 3 місяці після деплою.
Строки
| Задача | Строк |
|---|---|
| Модель на історичних даних клієнта (500+ креативів) | 3–5 тижнів |
| Повна система з API та інтеграцією в creative workflow | 6–10 тижнів |
| Генеративна оптимізація (AI-корекція банера) | 10–16 тижнів |
Ми гарантуємо прозорість прогнозів: кожен передбачений CTR супроводжується топ-3 факторами від SHAP. Отримайте консультацію щодо вашого проєкту — оцінимо дані за 2 дні. Зв'яжіться з нами для оцінки ваших даних — ми проаналізуємо 100 креативів за 2 дні. Замовте пілот на 20 креативах — результат через тиждень.







