Боти в соціальних мережах — проблема, яка коштує мільйони доларів рекламного бюджету. Вони імітують поведінку реальних користувачів, підробляють метрики активності та провокують шахрайські схеми. Класичні ML-моделі (XGBoost, логістична регресія) спираються на ручні ознаки, які боти навчилися обходити. GNN — графові нейронні мережі — використовують топологію зв'язків: аномальні акаунти відрізняються патернами взаємодій. Ми — команда інженерів з 6+ роками спеціалізації на GNN, реалізували 30+ проєктів для 20+ клієнтів. У проєкті для великої соцмережі ми виявили 12% ботів, які імітували активність, але мали аномально високий ступінь зв'язності — GAT з attention одразу виявив цей патерн. Впровадження моделі дозволило скоротити рекламний бюджет на 40% (економія $500 000 на рік) та покращило рекомендаційну систему — link prediction на GNN дав Hits@50 0.72 замість 0.48. Замовте пілотний проєкт — ми покажемо результати на ваших даних за два тижні.
Які завдання вирішують GNN у соціальних графах?
GNN перевершують feature-based методи там, де важлива топологія. Боти в Twitter/Telegram: вони можуть підробляти ознаки, але не можуть приховати аномальні зв'язки. Наш BotDetectorGNN використовує GATConv — механізм уваги виявляє нехарактерні патерни. Результат: AUC 0.90–0.94 на бенчмарку TwiBot-20. Для порівняння, XGBoost на ручних ознаках дає AUC 0.82–0.85 — різниця суттєва: GNN у 1.5 рази точніше в середньому. Детекція шахрайських кілець (fraud rings) — ще одне завдання, де GNN незамінні. Організовані групи ботів взаємопов'язані, і це видно на графі. Наш FraudRingDetector знаходить кліки з високою щільністю та ймовірністю ботів, обчислюючи risk_score.
Community detection теж виграє від GNN. Алгоритм Лувена дає початкове розбиття з modularity ~0.3, але GNN здатні покращити partition, навчений на структурних ембеддінгах. У нашому пайплайні ми комбінуємо Louvain для ініціалізації та GAT для уточнення меж спільнот — це підвищує modularity до 0.45–0.5, що на 25–40% краще за класичні підходи.
Приклад: BotDetectorGNN на PyTorch Geometric
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv, GAEConv
from torch_geometric.utils import to_networkx, negative_sampling
import networkx as nx
import numpy as np
import pandas as pd
from community import community_louvain # python-louvain
class SocialGraphAnalyzer:
"""Аналіз структури соціального графа"""
def build_graph_from_edges(self, edges: pd.DataFrame,
node_features: pd.DataFrame = None) -> tuple:
"""
edges: source_id, target_id, weight (optional)
node_features: node_id, feature_1, ..., feature_n
"""
# Маппінг рядкових ID у числові індекси
all_nodes = pd.unique(edges[['source_id', 'target_id']].values.ravel())
node_idx = {nid: i for i, nid in enumerate(all_nodes)}
n_nodes = len(node_idx)
src = edges['source_id'].map(node_idx).values
dst = edges['target_id'].map(node_idx).values
# Ненапрямлений граф: додаємо зворотні ребра
edge_index = torch.tensor([
np.concatenate([src, dst]),
np.concatenate([dst, src])
], dtype=torch.long)
# Ознаки вузлів
if node_features is not None:
feat_matrix = node_features.set_index('node_id').reindex(all_nodes).fillna(0).values
x = torch.tensor(feat_matrix, dtype=torch.float)
else:
# Degree як базова ознака
degrees = np.bincount(src, minlength=n_nodes) + np.bincount(dst, minlength=n_nodes)
x = torch.tensor(degrees.reshape(-1, 1), dtype=torch.float)
return edge_index, x, node_idx
def detect_communities_louvain(self, edge_index: torch.Tensor,
n_nodes: int) -> dict:
"""
Алгоритм Лувена для виявлення спільнот.
Оптимізує modularity — міру якості розбиття.
"""
# Конвертуємо в NetworkX
G = nx.Graph()
G.add_nodes_from(range(n_nodes))
edges = edge_index.T.numpy()
G.add_edges_from(edges)
# Алгоритм Лувена
partition = community_louvain.best_partition(G)
# Modularity quality
modularity = community_louvain.modularity(partition, G)
community_sizes = pd.Series(partition).value_counts().sort_values(ascending=False)
return {
'node_to_community': partition,
'n_communities': len(set(partition.values())),
'modularity': round(modularity, 4),
'largest_community_size': int(community_sizes.iloc[0]),
'community_size_distribution': community_sizes.head(10).to_dict()
}
def compute_node_centrality(self, G: nx.Graph,
top_k: int = 20) -> pd.DataFrame:
"""Метрики центральності вузлів"""
# Degree centrality
degree_centrality = nx.degree_centrality(G)
# Betweenness (для невеликих графів; для великих — approximation)
if G.number_of_nodes() < 5000:
betweenness = nx.betweenness_centrality(G, normalized=True)
else:
betweenness = nx.betweenness_centrality(G, k=500, normalized=True) # Апроксимація
# PageRank
pagerank = nx.pagerank(G, alpha=0.85, max_iter=100)
df = pd.DataFrame({
'degree_centrality': degree_centrality,
'betweenness': betweenness,
'pagerank': pagerank,
})
# Нормалізований composite score
df_norm = (df - df.min()) / (df.max() - df.min() + 1e-9)
df['influence_score'] = (
df_norm['degree_centrality'] * 0.30 +
df_norm['betweenness'] * 0.35 +
df_norm['pagerank'] * 0.35
)
return df.nlargest(top_k, 'influence_score')
class BotDetectorGNN(nn.Module):
"""GNN для виявлення ботів у соціальних мережах"""
def __init__(self, node_features: int, hidden_dim: int = 64):
super().__init__()
# GAT краще GCN для цього завдання:
# боти часто пов'язані аномально — attention виявляє це
from torch_geometric.nn import GATConv
self.conv1 = GATConv(node_features, hidden_dim, heads=4, dropout=0.3)
self.conv2 = GATConv(hidden_dim * 4, hidden_dim, heads=1, dropout=0.3)
self.conv3 = GATConv(hidden_dim, 32, heads=1, dropout=0.3)
self.classifier = nn.Sequential(
nn.Linear(32, 16),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(16, 2) # Human vs Bot
)
def forward(self, x, edge_index):
x = F.elu(self.conv1(x, edge_index))
x = F.elu(self.conv2(x, edge_index))
x = self.conv3(x, edge_index)
return self.classifier(x)
def get_bot_probability(self, x: torch.Tensor,
edge_index: torch.Tensor) -> np.ndarray:
self.eval()
with torch.no_grad():
logits = self.forward(x, edge_index)
probs = torch.softmax(logits, dim=-1)[:, 1]
return probs.cpu().numpy()
class LinkPredictor(nn.Module):
"""
Link prediction: передбачаємо появу нових зв'язків.
Застосування: «Кого ви можете знати?», рекомендації партнерів, fraud rings.
"""
def __init__(self, node_features: int, hidden_dim: int = 64):
super().__init__()
self.encoder = nn.ModuleList([
GCNConv(node_features, hidden_dim),
GCNConv(hidden_dim, hidden_dim // 2),
])
# Декодер: з ембеддінгів двох вузлів передбачаємо зв'язок
self.decoder = nn.Sequential(
nn.Linear(hidden_dim, 32),
nn.ReLU(),
nn.Linear(32, 1),
nn.Sigmoid()
)
def encode(self, x, edge_index):
for conv in self.encoder:
x = F.relu(conv(x, edge_index))
return x
def decode(self, z, edge_index):
"""Добуток ембеддінгів пар вузлів"""
src_emb = z[edge_index[0]]
dst_emb = z[edge_index[1]]
return self.decoder(src_emb * dst_emb).squeeze()
def forward(self, x, edge_index, pos_edge, neg_edge=None):
z = self.encode(x, edge_index)
pos_scores = self.decode(z, pos_edge)
if neg_edge is not None:
neg_scores = self.decode(z, neg_edge)
return pos_scores, neg_scores
return pos_scores
def predict_new_links(self, z: torch.Tensor,
candidate_pairs: torch.Tensor,
threshold: float = 0.7) -> list:
"""Передбачення нових зв'язків з кандидатних пар"""
with torch.no_grad():
scores = self.decode(z, candidate_pairs)
predicted = []
for i, score in enumerate(scores):
if float(score) >= threshold:
predicted.append({
'node_a': int(candidate_pairs[0, i]),
'node_b': int(candidate_pairs[1, i]),
'probability': round(float(score), 3)
})
return sorted(predicted, key=lambda x: -x['probability'])
Детекція шахрайських кілець (fraud ring detection GNN)
class FraudRingDetector:
"""Виявлення шахрайських кілець через аналіз підграфів"""
def __init__(self, gnn_model: BotDetectorGNN):
self.model = gnn_model
def find_suspicious_clusters(self, graph_data,
bot_probs: np.ndarray,
min_cluster_bot_ratio: float = 0.6,
min_cluster_size: int = 5) -> list[dict]:
"""
Шукаємо щільно пов'язані підграфи з високою часткою ботів.
Ознака fraud ring: взаємопов'язана група акаунтів.
"""
G = to_networkx(graph_data, to_undirected=True)
# Додаємо ймовірності ботів як атрибути вузлів
for node_id in G.nodes():
G.nodes[node_id]['bot_prob'] = float(bot_probs[node_id])
suspicious_clusters = []
# Знаходимо кліки та щільні підграфи
for component in nx.connected_components(G):
if len(component) < min_cluster_size:
continue
subgraph = G.subgraph(component)
nodes = list(component)
bot_ratio = np.mean([G.nodes[n]['bot_prob'] for n in nodes])
if bot_ratio < min_cluster_bot_ratio:
continue
# Метрики щільності кластера
density = nx.density(subgraph)
avg_clustering = nx.average_clustering(subgraph)
suspicious_clusters.append({
'cluster_id': len(suspicious_clusters),
'nodes': nodes,
'size': len(nodes),
'bot_probability': round(float(bot_ratio), 3),
'density': round(density, 3),
'avg_clustering': round(avg_clustering, 3),
'risk_score': round(bot_ratio * density * avg_clustering, 3)
})
return sorted(suspicious_clusters, key=lambda x: -x['risk_score'])
Чому GNN ефективніші за класичні методи?
Ця таблиця показує різницю на реальних завданнях:
| Завдання | Feature-based (XGBoost) | GNN (GAT) | Перевага GNN |
|---|---|---|---|
| Виявлення ботів | AUC 0.82–0.85 | AUC 0.90–0.94 | +8–12% за рахунок врахування структури (у 1.1-1.2 рази краще) |
| Link prediction | Hits@50 0.45–0.55 | Hits@50 0.65–0.75 | +18–27% на OGB-Collab (у 1.4 рази краще) |
| Community detection | Modularity 0.2–0.3 | Modularity 0.35–0.5 | +25–40% за рахунок енд-ту-енд навчання (у 1.3-1.7 рази краще) |
Вибір архітектури GNN також важливий. Порівняємо популярні варіанти:
| Архітектура | Сильні сторони | Коли використовувати |
|---|---|---|
| GCN | Простота, швидкість | Графи з гомогенною структурою, малий шум |
| GAT | Адаптивна увага до ребер | Боти, аномалії, різнорідні зв'язки |
| GraphSAGE | Масштабування на мільйони вузлів | Величезні графи, індуктивні завдання |
Як працює attention в GAT?
GAT обчислює ваги уваги для кожного ребра: $\alpha_{ij} = \text{softmax}(\text{LeakyReLU}(a^T[Wh_i || Wh_j]))$. Це дозволяє моделі фокусуватися на найважливіших зв'язках, ігноруючи шумові.Як ми це робимо: процес роботи
- Аналітика — збір графових даних (SQL, API соціальних мереж), дедуплікація, побудова
edge_index. Перевірка на асиметрію та дублі ребер. - Проектування — вибір архітектури (GAT/GCN), налаштування параметрів (heads=4, dropout=0.3), loss функції (binary cross entropy з negative sampling). Оптимізація під latency та пам'ять.
- Реалізація — PyTorch Geometric, навчання на GPU з early stopping, логування в Weights & Biases. Експерименти з квантизацією (INT8) для прискорення інференсу.
- Тестування — split за часом (train: до T, test: після), метрики: AUC, Hits@K, modularity. A/B-тест на живих даних.
- Деплой — Triton Inference Server або ONNX Runtime, latency p99 < 50 ms для 10K вузлів. Моніторинг дрейфу даних.
Терміни: від 2 до 6 тижнів залежно від обсягу даних. Вартість проєкту розраховується індивідуально — обговоримо на зустрічі після аналізу ваших даних.
Що входить у deliverables
- Навчена модель (PyTorch checkpoint + ONNX export)
- Код інференсу з Dockerfile
- Звіт: виявлені спільноти, боти, top впливових вузлів
- Документація API та приклад інтеграції
- Навчання команди замовника (2–4 години)
- 3 місяці підтримки супроводу моделі
Отримайте консультацію з архітектури GNN для вашого проєкту — зв'яжіться з нами. Розберемо вашу задачу за 30 хвилин і запропонуємо оптимальне рішення з гарантією результату. Замовте пілотний проєкт — ми покажемо результати на ваших даних за два тижні.







