AI Ad Creative Effectiveness Analysis System

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1 servicesAll 1566 services
AI Ad Creative Effectiveness Analysis System
Medium
~1-2 weeks
FAQ
AI Development Areas
AI Solution Development Stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_logo-aider_0.jpg
    AIDER company logo development
    762
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    848

AI-анализ эффективности рекламных креативов

Рекламный отдел создаёт 50 вариантов баннера — какой запустить? Традиционный ответ: A/B тест. Проблема: A/B требует трафика и времени. AI-анализ предсказывает CTR и вовлечённость по визуальным характеристикам до запуска, используя накопленную историческую связь «характеристики изображения → performance».

Извлечение визуальных признаков

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

Предиктивная модель CTR

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
        ]

Ключевые инсайты из данных

По накопленной статистике рекламных платформ (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 исследования
Warm colors (hue 0-60°) +5–9% Для food, lifestyle

Сроки

Задача Срок
Модель на исторических данных клиента (500+ креативов) 3–5 недель
Полная система с API и интеграцией в creative workflow 6–10 недель
Генеративная оптимизация (AI-коррекция баннера) 10–16 недель