AI-сегментація клієнтів: кластеризація + LLM-опис для підвищення CTR

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

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

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

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

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

Уявіть: ви витрачаєте бюджет на email-розсилки, але CTR не перевищує 20%. Причина — ручна сегментація по 3-5 групах, які не відображають реальної поведінки клієнтів. ML-підхід автоматично виявляє до 20 статистично значущих кластерів на основі RFM-ознак, часових патернів і демографії. Наприклад, для інтернет-магазину побутової техніки ми виявили 12 сегментів замість 4 ручних, що підвищило CTR email-кампаній з 18% до 55%. У результаті CTR маркетингових кампаній зростає з 15-25% до 40-60%. Наш стек: Python, PyTorch, KMeans, DBSCAN, PCA, UMAP, LLM (Claude, GPT). Досвід — 30+ проєктів з ML-сегментації для e-commerce та fintech.

Проблеми, які вирішуємо

Неоптимальна кількість сегментів. Ручний вибір 3-5 груп ігнорує природну структуру даних. Ми використовуємо elbow-метод та силуетний коефіцієнт для автоматичного визначення числа кластерів (зазвичай 6-15). Метод [Silhouette] (https://en.wikipedia.org/wiki/Silhouette_(clustering)) дозволяє оцінити якість кластеризації.

Інтерпретованість чорної скриньки. Кластери KMeans без опису марні для маркетингу. Тому ми додали LLM-генерацію назв і портретів сегментів — модель отримує центроїди та статистику, повертає готовий текст природною мовою.

Масштабування на мільйони клієнтів. Кластеризація 1M записів займає ~25 хвилин, real-time призначення сегмента — 5-10 мс. Ми застосовуємо PCA для прискорення та інкрементальний перерахунок при додаванні 10%+ нових користувачів.

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

Feature engineering — ключовий етап. Ми будуємо 25+ ознак: RFM, середньоквадратичне відхилення суми замовлення, частка покупок у вихідні, нічні покупки, середній міжзамовний інтервал та його регулярність. Повний пайплайн включає 6 кроків:

  1. Збір та передобробка даних (очищення, об'єднання таблиць).
  2. Feature engineering: генерація 25+ ознак (RFM, часові, поведінкові).
  3. Вибір алгоритму кластеризації: KMeans для опуклих форм, DBSCAN для неопуклих.
  4. Навчання моделі з авто визначенням числа кластерів (elbow + silhouette).
  5. LLM-генерація описів сегментів (Claude, GPT).
  6. Розгортання REST API для real-time призначення.

Код класу CustomerSegmentation демонструє повний пайплайн.

import pandas as pd
import numpy as np
from anthropic import Anthropic
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, DBSCAN
from sklearn.decomposition import PCA
import umap

class CustomerSegmentation:
    def __init__(self, customers_df: pd.DataFrame, orders_df: pd.DataFrame):
        self.customers = customers_df
        self.orders = orders_df
        self.llm = Anthropic()
        self.scaler = StandardScaler()
        self.segments = None

    def build_rfm_features(self) -> pd.DataFrame:
        """RFM + поведінкові ознаки"""
        now = pd.Timestamp.now()

        rfm = self.orders.groupby('customer_id').agg(
            recency_days=('order_date', lambda x: (now - x.max()).days),
            frequency=('order_id', 'nunique'),
            monetary=('amount', 'sum'),
            avg_order_value=('amount', 'mean'),
            first_order_days_ago=('order_date', lambda x: (now - x.min()).days),
            order_std=('amount', 'std'),
            max_order=('amount', 'max'),
            category_diversity=('category', 'nunique'),
        ).reset_index()

        # Заповнення NaN для одиночних замовлень
        rfm['order_std'] = rfm['order_std'].fillna(0)

        # Часові патерни
        self.orders['order_hour'] = pd.to_datetime(self.orders['order_date']).dt.hour
        self.orders['order_dow'] = pd.to_datetime(self.orders['order_date']).dt.dayofweek

        time_features = self.orders.groupby('customer_id').agg(
            preferred_hour=('order_hour', lambda x: x.mode()[0]),
            weekend_ratio=('order_dow', lambda x: (x >= 5).mean()),
            night_ratio=('order_hour', lambda x: ((x >= 22) | (x < 6)).mean()),
        ).reset_index()

        # Міжзамовний інтервал
        self.orders_sorted = self.orders.sort_values(['customer_id', 'order_date'])
        self.orders_sorted['prev_order'] = self.orders_sorted.groupby('customer_id')['order_date'].shift(1)
        self.orders_sorted['days_between'] = (
            pd.to_datetime(self.orders_sorted['order_date']) -
            pd.to_datetime(self.orders_sorted['prev_order'])
        ).dt.days

        interval_features = self.orders_sorted.groupby('customer_id').agg(
            avg_days_between=('days_between', 'mean'),
            purchase_regularity=('days_between', lambda x: 1 / (x.std() + 1))
        ).reset_index()

        # Об'єднання всіх ознак
        features = rfm.merge(time_features, on='customer_id', how='left')
        features = features.merge(interval_features, on='customer_id', how='left')
        features = features.merge(self.customers[['customer_id', 'city', 'age', 'gender']], on='customer_id', how='left')

        return features

Кластеризація з оптимальною кількістю сегментів

    def find_optimal_segments(self, features_df: pd.DataFrame,
                               max_k: int = 20) -> int:
        """Метод elbow + silhouette для вибору числа кластерів"""
        from sklearn.metrics import silhouette_score

        X = features_df.select_dtypes(include='number').fillna(0)
        X_scaled = self.scaler.fit_transform(X)

        # Зниження розмірності для прискорення
        pca = PCA(n_components=min(20, X_scaled.shape[1]))
        X_pca = pca.fit_transform(X_scaled)

        inertias = []
        silhouettes = []

        for k in range(2, min(max_k + 1, len(X_pca))):
            km = KMeans(n_clusters=k, random_state=42, n_init=10)
            labels = km.fit_predict(X_pca)
            inertias.append(km.inertia_)
            if k <= 15:  # Silhouette дорогий для великих k
                silhouettes.append(silhouette_score(X_pca, labels, sample_size=2000))

        # Elbow method
        diffs = np.diff(inertias)
        diff2 = np.diff(diffs)
        elbow_k = np.argmax(diff2) + 3  # +3 через подвійний diff і зміщення

        # Перевіряємо, що silhouette підтверджує
        sil_optimal = np.argmax(silhouettes) + 2

        # Компроміс
        optimal_k = round((elbow_k + sil_optimal) / 2)
        return max(4, min(optimal_k, max_k))

    def cluster_customers(self, features_df: pd.DataFrame,
                           n_clusters: int = None) -> pd.DataFrame:
        """Кластеризація та опис сегментів"""
        numeric_features = features_df.select_dtypes(include='number').fillna(0)
        X_scaled = self.scaler.fit_transform(numeric_features)

        if n_clusters is None:
            n_clusters = self.find_optimal_segments(features_df)

        # K-Means як основний алгоритм
        km = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
        features_df['cluster'] = km.fit_predict(X_scaled)

        # UMAP для візуалізації (2D)
        reducer = umap.UMAP(n_components=2, random_state=42)
        X_2d = reducer.fit_transform(X_scaled)
        features_df['umap_x'] = X_2d[:, 0]
        features_df['umap_y'] = X_2d[:, 1]

        self.segments = features_df
        self.cluster_centers = pd.DataFrame(
            self.scaler.inverse_transform(km.cluster_centers_),
            columns=numeric_features.columns
        )

        return features_df

LLM-опис сегментів

    def describe_segments(self) -> dict[int, dict]:
        """Автоматичний опис кожного кластера через LLM"""
        if self.segments is None:
            raise ValueError("Run cluster_customers first")

        segment_descriptions = {}

        for cluster_id in self.segments['cluster'].unique():
            cluster_data = self.segments[self.segments['cluster'] == cluster_id]
            center = self.cluster_centers.iloc[cluster_id]

            # Статистика по кластеру
            stats = {
                'size': len(cluster_data),
                'pct_of_total': len(cluster_data) / len(self.segments) * 100,
                'avg_recency_days': cluster_data['recency_days'].mean(),
                'avg_frequency': cluster_data['frequency'].mean(),
                'avg_monetary': cluster_data['monetary'].mean(),
                'avg_order_value': cluster_data['avg_order_value'].mean(),
                'weekend_ratio': cluster_data.get('weekend_ratio', pd.Series([0])).mean(),
            }

            response = self.llm.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=400,
                messages=[{
                    "role": "user",
                    "content": f"""Ти маркетинговий аналітик. Опиши сегмент клієнтів за даними.

Статистика сегмента:
- Розмір: {stats['size']:,} клієнтів ({stats['pct_of_total']:.1f}% від бази)
- Середня давність покупки: {stats['avg_recency_days']:.0f} днів тому
- Середня частота: {stats['avg_frequency']:.1f} замовлень
- Середня виручка: {stats['avg_monetary']:,.0f} руб.
- Середній чек: {stats['avg_order_value']:,.0f} руб.
- Частка покупок у вихідні: {stats['weekend_ratio']:.1%}

Дай:
1. Назву сегмента (2-4 слова, наприклад "Лояльні чемпіони" або "Група ризику")
2. Опис у 2-3 реченнях — хто ці люди, їхній патерн поведінки
3. Рекомендовану маркетингову стратегію (1-2 конкретні дії)"""
                }]
            )

            text = response.content[0].text
            lines = text.strip().split('\n')

            segment_descriptions[cluster_id] = {
                'stats': stats,
                'name': lines[0].replace('1. ', '').strip() if lines else f"Segment {cluster_id}",
                'description': text,
                'cluster_id': cluster_id
            }

        return segment_descriptions

Автоматичне призначення сегментів новим клієнтам

    def assign_new_customer(self, customer_features: dict) -> dict:
        """Real-time сегментація нового клієнта"""
        feature_vector = pd.DataFrame([customer_features])
        numeric_cols = self.cluster_centers.columns.tolist()

        for col in numeric_cols:
            if col not in feature_vector.columns:
                feature_vector[col] = 0

        feature_vector_scaled = self.scaler.transform(feature_vector[numeric_cols])

        # Відстані до центрів кластерів
        from sklearn.metrics import pairwise_distances
        centers_scaled = self.scaler.transform(self.cluster_centers)
        distances = pairwise_distances(feature_vector_scaled, centers_scaled)[0]
        cluster_id = distances.argmin()
        confidence = 1 - distances[cluster_id] / distances.sum()

        return {
            'cluster_id': int(cluster_id),
            'confidence': float(confidence),
            'distance': float(distances[cluster_id])
        }

Як вибрати оптимальне число сегментів?

Ми не гадаємо на кавовій гущі. Використовуємо комбінацію elbow-методу (пошук точки перегину) та силуетного коефіцієнта. На практиці оптимальне число — від 6 до 15. Для баз понад 100K записів застосовуємо PCA з 20 компонентами — це прискорює розрахунки в 3 рази без втрати якості.

Чому LLM-опис ефективніший за ручний аналіз?

Маркетолог витрачає 2-3 дні на аналіз кожного кластера і часто помиляється в інтерпретації. LLM (ми використовуємо Claude, GPT) отримує числові центроїди і за 10 секунд генерує назву, опис та стратегію. Наприклад, сегмент з високою частотою та низькою давністю отримує ім'я "Лояльні чемпіони" та рекомендацію щодо програм лояльності. Це знижує час на сегментацію для маркетингу на 90% та підвищує точність таргетованого маркетингу.

Продуктивність на реальних даних

Розмір бази Число ознак Час кластеризації Оптимальних кластерів
10K клієнтів 25 ~30 сек 6-8
100K клієнтів 25 ~3 хв 10-15
1M клієнтів 25 ~25 хв 15-25
1M клієнтів 25 + PCA(20) ~8 хв 15-25

Для real-time призначення сегментів попередньо навчена модель скорить нового клієнта за 5-10 мс. Перерахунок сегментації повної бази — раз на тиждень або при накопиченні 10%+ нових клієнтів.

Порівняння алгоритмів кластеризації

Критерій K-Means DBSCAN
Форма кластерів Опукла Будь-яка
Чутливість до шуму Висока Низька
Необхідність числа кластерів Так Ні
Швидкість Висока Середня
Масштабованість Відмінна Добра

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

Типові помилки при сегментації - Використання лише RFM-ознак без поведінкових (час покупок, регулярність). - Вибір числа кластерів на око без об'єктивних метрик. - Ігнорування сезонності та викидів. - Відсутність інтерпретації сегментів — кластери залишаються «чорною скринькою». - Нерегулярне оновлення моделі при зміні клієнтської бази.

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

  • Feature engineering під вашу структуру даних (до 30 ознак).
  • Навчання моделей KMeans/DBSCAN з автовибором числа кластерів.
  • LLM-опис кожного сегмента українською мовою.
  • REST API для real-time призначення сегментів (latency p99 < 20 мс).
  • Документація пайплайну та коду.
  • Інтеграція з вашою CRM або email-платформою.
  • Навчання команди роботі із сегментацією.

Строки: від 2 до 8 тижнів залежно від обсягу даних та складності інтеграції. Вартість проєкту визначається після аналізу ваших даних.

Отримайте консультацію: наші інженери з 5+ років досвіду в ML аналізують вашу базу, підбирають оптимальний алгоритм. Гарантуємо підвищення CTR та економію маркетингового бюджету до 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-пайплайну під ключ.