Замовте розробку AI-системи на базі графових нейронних мереж (GNN)

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1564 послуг
Замовте розробку AI-системи на базі графових нейронних мереж (GNN)
Складний
від 1 тижня до 3 місяців
Часті запитання

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

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

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

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

Ми розробляємо AI-системи на базі графових нейронних мереж (GNN) для задач, де важливі зв'язки між об'єктами. Коли табличні дані втрачають контекст, граф його зберігає. Наприклад, у виявленні шахрайства GNN аналізує ланцюжки транзакцій, а не окремі операції. Наша команда має 5+ років досвіду в GNN, реалізувала понад 30 проєктів для FinTech, e-Commerce та біоінформатики. Замовте розробку GNN-системи під ключ: ми підберемо архітектуру під ваші дані та забезпечимо продакшн-готовність. Зв'яжіться з нами для консультації та оцінки графової структури. Бюджет типового проєкту — від $10 000 до $50 000 залежно від складності.

Чому GNN перевершують класичні ML-моделі на графових даних?

Традиційні моделі (GBDT, лінійна регресія) працюють з векторами ознак, ігноруючи топологію. GNN оперує не лише ознаками вузлів, але й структурою — які вузли зв'язані, яка сила зв'язків, які типи ребер. Завдяки message passing, після K ітерацій кожен вузол «бачить» своїх сусідів на відстані K. Це дає приріст AUC на 5–15% у задачах класифікації вузлів та передбачення зв'язків порівняно з MLP або CatBoost.

Практичний приклад: у задачі виявлення шахрайства стандартна градієнтна модель дивиться на ознаки однієї транзакції — суму, час, геолокацію. GNN додає до цього контекст: як даний акаунт пов'язаний з іншими, чи були серед сусідів раніше заблоковані об'єкти, наскільки щільна навколо нього мережа підозрілих транзакцій. Саме цей мережевий контекст дозволяє виявляти скоординовані схеми шахрайства, непомітні для точкового аналізу. У наших проєктах перехід з CatBoost на GraphSAGE на фінансових даних давав покращення recall@5% FPR на 12–18 процентних пунктів — це в 2-3 рази краще за логістичну регресію.

Теоретична база та ключові архітектури

Основна ідея GNN — message passing: кожен вузол агрегує інформацію від своїх сусідів. Після K ітерацій вузол «бачить» K-hop neighbourhood.

Формула агрегації (GraphSAGE):

h_v^(k) = σ(W · CONCAT(h_v^(k-1), AGG({h_u^(k-1), u ∈ N(v)})))

Ключові архітектури (з посиланнями на оригінальні роботи):

Архітектура Агрегація Застосування Особливості
GCN (Kipf & Welling, ICLR 2017) Spectral conv Класифікація вузлів Transductive
GraphSAGE (Hamilton et al., NeurIPS 2017) Mean/LSTM/Max Великі графи Inductive
GAT (Veličković et al., ICLR 2018) Attention Неоднорідні графи Зважені ребра
GIN (Xu et al., ICLR 2019) Sum (найпотужніший) Ізоморфізм графів Максимальна виразність
RGCN (Schlichtkrull et al., ESWC 2018) Relation-specific Knowledge graphs Різні типи ребер

Реалізація GCN з PyTorch Geometric

Показати код реалізації GCN, GraphSAGE, GAT
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv, SAGEConv, GATConv, global_mean_pool
from torch_geometric.data import Data, DataLoader
import numpy as np
import pandas as pd

class GraphConvNet(nn.Module):
    """
    GCN для класифікації/регресії на графі.
    Підходить для: fraud detection, рекомендацій, молекул.
    """

    def __init__(self, node_features: int,
                  hidden_channels: int = 64,
                  output_dim: int = 1,
                  num_layers: int = 3,
                  dropout: float = 0.3):
        super().__init__()

        self.convs = nn.ModuleList()
        self.bns = nn.ModuleList()

        # Вхідний шар
        self.convs.append(GCNConv(node_features, hidden_channels))
        self.bns.append(nn.BatchNorm1d(hidden_channels))

        # Приховані шари
        for _ in range(num_layers - 2):
            self.convs.append(GCNConv(hidden_channels, hidden_channels))
            self.bns.append(nn.BatchNorm1d(hidden_channels))

        # Вихідний шар
        self.convs.append(GCNConv(hidden_channels, hidden_channels))
        self.bns.append(nn.BatchNorm1d(hidden_channels))

        self.dropout = dropout
        self.classifier = nn.Linear(hidden_channels, output_dim)

    def forward(self, x: torch.Tensor,
                edge_index: torch.Tensor,
                batch: torch.Tensor = None) -> torch.Tensor:
        """
        x: (N, node_features) — матриця ознак вузлів
        edge_index: (2, E) — список ребер у COO форматі
        batch: (N,) — приналежність вузлів до графів (для батчингу)
        """
        for conv, bn in zip(self.convs, self.bns):
            x = conv(x, edge_index)
            x = bn(x)
            x = F.relu(x)
            x = F.dropout(x, p=self.dropout, training=self.training)

        # Graph-level readout (для задач на рівні графа)
        if batch is not None:
            x = global_mean_pool(x, batch)

        return self.classifier(x)


class GraphSAGEEncoder(nn.Module):
    """
    GraphSAGE для inductive learning (працює на нових вузлах без перенавчання).
    Використовується для великих графів: соціальні мережі, транзакції.
    """

    def __init__(self, in_channels: int, hidden_channels: int, out_channels: int,
                  num_layers: int = 3, aggr: str = 'mean'):
        super().__init__()
        self.convs = nn.ModuleList()

        self.convs.append(SAGEConv(in_channels, hidden_channels, aggr=aggr))
        for _ in range(num_layers - 2):
            self.convs.append(SAGEConv(hidden_channels, hidden_channels, aggr=aggr))
        self.convs.append(SAGEConv(hidden_channels, out_channels, aggr=aggr))

    def forward(self, x, edge_index):
        for i, conv in enumerate(self.convs):
            x = conv(x, edge_index)
            if i < len(self.convs) - 1:
                x = F.relu(x)
                x = F.dropout(x, p=0.2, training=self.training)
        return x

    def encode(self, x, edge_index):
        """L2-нормалізовані ембеддінги для downstream задач"""
        out = self.forward(x, edge_index)
        return F.normalize(out, p=2, dim=-1)


class GATNetwork(nn.Module):
    """
    Graph Attention Network: зважена агрегація сусідів.
    Attention ваги показують «важливість» кожного сусіда.
    """

    def __init__(self, in_channels: int, hidden_channels: int,
                  out_channels: int, num_heads: int = 8):
        super().__init__()

        self.conv1 = GATConv(in_channels, hidden_channels,
                              heads=num_heads, dropout=0.6)
        self.conv2 = GATConv(hidden_channels * num_heads, out_channels,
                              heads=1, concat=False, dropout=0.6)

    def forward(self, x, edge_index):
        x = F.dropout(x, p=0.6, training=self.training)
        x = F.elu(self.conv1(x, edge_index))
        x = F.dropout(x, p=0.6, training=self.training)
        return self.conv2(x, edge_index)

Побудова графа з табличних даних

Показати код побудови графа
class GraphBuilder:
    """Конвертація табличних даних у граф для GNN"""

    def build_user_item_graph(self, interactions: pd.DataFrame,
                               user_features: pd.DataFrame,
                               item_features: pd.DataFrame) -> Data:
        """
        Дводольний граф користувач-товар для рекомендацій.
        interactions: user_id, item_id, rating/count
        """
        # Маппінг ID в індекси вузлів
        user_ids = interactions['user_id'].unique()
        item_ids = interactions['item_id'].unique()
        n_users = len(user_ids)

        user_idx = {uid: i for i, uid in enumerate(user_ids)}
        item_idx = {iid: i + n_users for i, iid in enumerate(item_ids)}

        # Ребра: користувач → товар
        src = interactions['user_id'].map(user_idx).values
        dst = interactions['item_id'].map(item_idx).values

        # Двонаправлений граф (типово для GNN)
        edge_index = torch.tensor(
            np.vstack([
                np.concatenate([src, dst]),
                np.concatenate([dst, src])
            ]),
            dtype=torch.long
        )

        # Матриця ознак вузлів
        # Користувачі: embedding + поведінкові ознаки
        user_feat_matrix = user_features.set_index('user_id').reindex(user_ids).fillna(0).values
        # Товари: embedding + характеристики
        item_feat_matrix = item_features.set_index('item_id').reindex(item_ids).fillna(0).values

        # Вирівнюємо розмірності
        max_dim = max(user_feat_matrix.shape[1], item_feat_matrix.shape[1])
        user_feat_padded = np.pad(user_feat_matrix, ((0, 0), (0, max_dim - user_feat_matrix.shape[1])))
        item_feat_padded = np.pad(item_feat_matrix, ((0, 0), (0, max_dim - item_feat_matrix.shape[1])))

        x = torch.tensor(
            np.vstack([user_feat_padded, item_feat_padded]),
            dtype=torch.float
        )

        # Ваги ребер (наприклад, рейтинг)
        edge_attr = torch.tensor(
            np.concatenate([
                interactions['rating'].values,
                interactions['rating'].values  # Дзеркальні ребра
            ]),
            dtype=torch.float
        ).unsqueeze(1)

        return Data(
            x=x,
            edge_index=edge_index,
            edge_attr=edge_attr,
            n_users=n_users
        )

    def build_transaction_graph(self, transactions: pd.DataFrame) -> Data:
        """
        Граф транзакцій для fraud detection.
        Вузли: акаунти, карти, IP-адреси, мерчанти.
        Ребра: транзакції між ними.
        """
        # Унікальні сутності
        accounts = transactions['account_id'].unique()
        merchants = transactions['merchant_id'].unique()
        n_accounts = len(accounts)

        acc_idx = {a: i for i, a in enumerate(accounts)}
        mer_idx = {m: i + n_accounts for i, m in enumerate(merchants)}

        src = transactions['account_id'].map(acc_idx).values
        dst = transactions['merchant_id'].map(mer_idx).values

        edge_index = torch.tensor([
            np.concatenate([src, dst]),
            np.concatenate([dst, src])
        ], dtype=torch.long)

        # Ознаки транзакцій як атрибути ребер
        edge_attr = torch.tensor(
            transactions[['amount', 'hour_of_day', 'is_international']].values,
            dtype=torch.float
        )
        edge_attr = torch.cat([edge_attr, edge_attr], dim=0)  # Дублюємо для дзеркальних ребер

        # Мітки: fraud = 1
        if 'is_fraud' in transactions.columns:
            y = torch.tensor(transactions['is_fraud'].values, dtype=torch.long)
        else:
            y = None

        return Data(
            x=torch.zeros(n_accounts + len(merchants), 16),  # placeholder features
            edge_index=edge_index,
            edge_attr=edge_attr,
            y=y
        )

Навчання та оцінка GNN

Показати код навчання
class GNNTrainer:
    """Pipeline навчання GNN"""

    def __init__(self, model: nn.Module, device: str = 'cuda'):
        self.model = model.to(device)
        self.device = device
        self.optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

    def train_epoch(self, data: Data, mask: torch.Tensor = None) -> float:
        """Одна епоха для node classification"""
        self.model.train()
        self.optimizer.zero_grad()

        data = data.to(self.device)
        out = self.model(data.x, data.edge_index)

        if mask is not None:
            loss = F.cross_entropy(out[mask], data.y[mask])
        else:
            loss = F.cross_entropy(out, data.y)

        loss.backward()
        self.optimizer.step()
        return float(loss)

    def evaluate(self, data: Data, mask: torch.Tensor) -> dict:
        """Оцінка якості передбачень"""
        self.model.eval()
        with torch.no_grad():
            out = self.model(data.x.to(self.device), data.edge_index.to(self.device))
            pred = out[mask].argmax(dim=-1).cpu()
            true = data.y[mask].cpu()

        from sklearn.metrics import accuracy_score, f1_score, roc_auc_score
        probs = torch.softmax(out[mask], dim=-1)[:, 1].cpu().numpy()

        return {
            'accuracy': accuracy_score(true, pred),
            'f1_macro': f1_score(true, pred, average='macro'),
            'auc': roc_auc_score(true, probs) if len(np.unique(true)) > 1 else 0.5
        }

    def train(self, data: Data,
               n_epochs: int = 200,
               train_mask: torch.Tensor = None,
               val_mask: torch.Tensor = None) -> dict:
        """Повний цикл навчання з early stopping"""
        best_val_auc = 0
        patience, patience_counter = 20, 0
        history = {'train_loss': [], 'val_auc': []}

        for epoch in range(n_epochs):
            loss = self.train_epoch(data, train_mask)
            history['train_loss'].append(loss)

            if val_mask is not None and epoch % 5 == 0:
                metrics = self.evaluate(data, val_mask)
                history['val_auc'].append(metrics['auc'])

                if metrics['auc'] > best_val_auc:
                    best_val_auc = metrics['auc']
                    patience_counter = 0
                    torch.save(self.model.state_dict(), 'best_gnn_model.pt')
                else:
                    patience_counter += 1
                    if patience_counter >= patience:
                        print(f"Early stopping at epoch {epoch}")
                        break

        return {'best_val_auc': best_val_auc, 'history': history}

Як масштабувати GNN на великі графи?

Стандартний GNN не масштабується на графи з мільйонами вузлів — повна матриця суміжності не поміщається в пам'ять. Наприклад, граф соціальної мережі може мати 10⁷ вузлів та 10⁸ ребер. Рішення:

  • GraphSAGE з mini-batch: семплювання K сусідів замість всіх. PyG підтримує через NeighborLoader з параметром num_neighbors=[25, 10]. Це дозволяє обробляти графи з 10⁶ вузлів на одному GPU.
  • Cluster-GCN: розбиття графа на кластери, навчання всередині кластерів.
  • GraphSAINT: випадкове семплювання підграфів з importance sampling.
Показати код завантажувача
from torch_geometric.loader import NeighborLoader

def create_scalable_dataloader(data: Data, batch_size: int = 1024) -> NeighborLoader:
    """Mini-batch завантажувач для великих графів"""
    return NeighborLoader(
        data,
        num_neighbors=[25, 10, 5],  # Сусіди для 3 hop
        batch_size=batch_size,
        input_nodes=data.train_mask,
        shuffle=True,
        num_workers=4
    )

Область застосування та бенчмарки

Показати таблицю бенчмарків
Задача Датасет Архітектура AUC/Accuracy
Fraud detection фінанс. транзакції (10⁶ вузлів) GraphSAGE AUC 0.93-0.97
Рекомендації Amazon LightGCN NDCG@20 0.045
Соціальний спам Twitter GAT F1 0.89
Молекулярні властивості ZINC GIN MAE 0.163
Дорожній трафік METR-LA Diffusion GCN RMSE 2.37

GNN перевершують традиційні методи лише тоді, коли структура графа несе інформацію. Якщо відносини між об'єктами випадкові — звичайний GBDT або MLP покаже порівнянний результат з меншою складністю.

Перед стартом проєкту ми проводимо аудит: чи має ваша доменна область значущу структуру графа, чи достатньо даних для навчання, чи реалістичний цільовий AUC. Якщо GNN не дає переваги, чесно скажемо про це і запропонуємо простішу модель. Такий підхід зберігає бюджет клієнта та підвищує довгострокову довіру до рішення.

Що входить в роботу (покроковий план)

  1. Аналіз графової структури та вибір архітектури (GCN, GraphSAGE, GAT, GIN).
  2. Побудова пайплайну даних: конвертація таблиць у графи, нормалізація ознак.
  3. Навчання та підбір гіперпараметрів з валідацією (early stopping, крос-валідація).
  4. Розгортання моделі через Triton Inference Server або ONNX Runtime.
  5. Документація model card, API-специфікація та керівництво користувача.
  6. Підтримка після впровадження: моніторинг дрейфу даних, донавчання.

Ми гарантуємо якість результату — всі рішення проходять тестування на ваших даних до фіксації. Оцінимо ваш проєкт за 2 – 3 робочих дні: надішліть опис задачі та приблизні обсяги графа (число вузлів, ребер, задача). Зв'яжіться з нами, щоб обговорити задачу.

Розробка рекомендаційних систем: від 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.