Головна › Архітектура корпоративної аналітики
Інтелект рішень і виявлення аномалій
Архітектура систем підтримки рішень, базові лінії та ізоляційний ліс, економіка хибних спрацювань і замикання циклу «сигнал → рішення → результат».
У цьому напрямі
Четвертий шар корпоративної інтелектуальної архітектури не виробляє даних — він виробляє рішення. Цей модуль показує, чому дашборд аномалій не є системою підтримки рішень; як насправді оцінює точку ізоляційний ліс і чому його параметр contamination — це план завантаження вашої команди, а не гіперпараметр моделі; та як обирати робочий поріг у грошах, а не у F1. Наскрізна теза одна: детектор, результати рішень якого ніхто не записує, не має ані міток для навчання, ані доказів власної цінності.
Від сигналу до дії: анатомія системи підтримки рішень
Дашборд не є системою підтримки рішень. Дашборд показує стан; СППР веде конкретну людину від сигналу до дії й записує, що з цього вийшло. Різниця не косметична: без записаного результату система ніколи не отримає міток, а без міток жодна модель усередині неї не здатна навчатися й жоден аналітик не доведе її окупність.
Чотири підсистеми класичної СППР — і що кожна означає в сучасній архітектурі:
- Дані — не рядки таблиць, а розв'язані сутності онтології: «цей платіж», «цей контрагент», «цей вузол мережі». Аномалія, прив'язана до
row_id, а не до сутності, не має адресата і не переживає злиття дублікатів. - Моделі — детектори, правила, симулятори. Модель дає оцінку, а не вирок.
- Знання — пороги, регламенти, права доступу, обмеження мети. Тут живе бізнес-політика, зокрема головне обмеження шару: скільки сповіщень команда фізично здатна розібрати за зміну.
- Діалог — інтерфейс рішення: чому спрацювало, які докази, які варіанти дій, хто власник і до якого терміну.
Рішення — це об'єкт першого класу. Мінімальний контракт запису: ідентифікатор сутності, тригер (яка модель і яка її версія), оцінка й чинний на той момент поріг, знімок доказів, обраний варіант дії, автор, час, клас оборотності та — обов'язково — поле outcome, що дописується пізніше. Саме воно замикає цикл виявлення → рішення → дія → спостереження і перетворює операційну систему на джерело навчальних даних. Відхилене сповіщення — теж рішення: саме відмови становлять більшість негативних міток, і саме їх найчастіше не пишуть.
Бюджет затримки рішення. Архітектуру визначає не «потужність моделі», а час, що лишається між сигналом і моментом, коли дія ще щось змінює. Авторизація картки — сотні мілісекунд; розслідування мережевого інциденту — хвилини; перегляд кредитного ліміту — доба. Один і той самий детектор у цих трьох режимах живе у трьох різних місцях: у синхронному шляху транзакції, у потоковому конвеєрі, у нічному пакеті. Спроба обслужити всі три одним синхронним викликом — найдорожча помилка цього шару; правильний хід — винести важкі агрегати у feature store, лишивши на синхронному шляху лише оцінювання.
Затримка мітки. Істина майже ніколи не відома одразу: підтверджене шахрайство приходить чарджбеком через 30–60 днів, наслідок клінічного рішення — через тижні. Тому навчальна вибірка завжди відстає від продакшену. Зберігайте дві мітки часу: коли подія сталася і коли ви про неї дізналися. Різати навчальну вибірку треба за другою — інакше модель непомітно навчається на інформації, якої в момент оцінювання не існувало.
-- The decision as a first-class object: the record that closes the loop.
-- EVERY alert that reaches a human lands here, including the dismissed ones.
CREATE TABLE decision (
decision_id BIGSERIAL PRIMARY KEY,
entity_urn TEXT NOT NULL, -- ontology object, NOT a source row id
detector TEXT NOT NULL, -- 'iforest_payments'
detector_ver TEXT NOT NULL, -- exact model version that fired
score DOUBLE PRECISION NOT NULL,
threshold DOUBLE PRECISION NOT NULL, -- threshold IN FORCE at fire time
evidence JSONB NOT NULL, -- top features + baseline snapshot
action TEXT NOT NULL
CHECK (action IN ('block','hold','ignore','escalate')),
reversible BOOLEAN NOT NULL, -- gates what may auto-execute
decided_by TEXT NOT NULL, -- analyst id, or 'auto'
decided_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Outcome arrives LATER. Two timestamps, never one:
-- when the truth happened, and when we found out.
CREATE TABLE decision_outcome (
decision_id BIGINT PRIMARY KEY REFERENCES decision(decision_id),
label TEXT NOT NULL CHECK (label IN ('tp','fp','fn','unknown')),
loss_amount NUMERIC(14,2),
occurred_at TIMESTAMPTZ NOT NULL,
known_at TIMESTAMPTZ NOT NULL -- label lag = known_at - decided_at
);
-- Training sets are cut on known_at, never on occurred_at: cutting on
-- occurred_at leaks labels that did not exist when the score was produced.
CREATE VIEW trainable_decisions AS
SELECT d.*, o.label, o.loss_amount,
o.known_at - d.decided_at AS label_lag
FROM decision d
JOIN decision_outcome o USING (decision_id)
WHERE o.known_at <= now() - INTERVAL '1 day'
AND o.label <> 'unknown';
На практиці
Європейський платіжний провайдер писав у сховище лише ескальовані сповіщення — 18% від усіх. Решта 82% (відхилення аналітиків) не залишали сліду, тож навчальна вибірка складалася майже виключно з позитивів. Після додавання рядка рішення зaction='ignore' кількість розмічених прикладів зросла з 6 000 до 41 000 за квартал, а перенавчений детектор дав на 34% менше хибних спрацювань при незмінній повноті.Антипатерн
«Дашборд аномалій» без власника: сповіщення не має ані відповідального, ані терміну, ані поля результату, а успіх системи вимірюють кількістю виявлених аномалій. Такий контур не може виміряти жодного хибнонегативного випадку і через рік не має чим обґрунтувати власний бюджет.Базові лінії та ізоляційний ліс: щодо чого ви вимірюєте відхилення
Аномалія — це порушення очікування, а не велике число. Тому перше архітектурне рішення стосується не алгоритму, а бази порівняння: глобальний розподіл, конкретна сутність чи група однолітків. Платіж на 40 000 € аномальний для роздрібного клієнта і абсолютно нормальний для оптовика; глобальний поріг «сума > 10 000» — це не детектор, а фільтр, який щодня генерує ту саму сотню відомих імен.
Три класи аномалій вимагають трьох різних механізмів:
- Точкові — окреме спостереження далеке від решти. Робочий базовий рівень — робастний z-показник на медіані та MAD:
0.6745·(x−med)/MAD, а не середнє та σ. Причина проста: викиди самі роздувають σ і тим маскують себе, а медіана й MAD мають точку зламу 50%. - Контекстуальні — значення нормальне взагалі, але не в цьому контексті: 300 запитів о третій ночі в неділю. Потрібне зняття сезонності (день тижня × година) до оцінювання, інакше детектор щопонеділка о 9:00 повідомляє вам про початок робочого тижня.
- Колективні — жодна точка не є викидом, аномальна сама послідовність або підграф: 200 переказів по 900 € (структурування). Точкові методи тут безсилі за побудовою; потрібні віконні агрегати за сутністю або графові моделі над резолюцією сутностей.
Ізоляційний ліс: чому він працює. Замість описувати «норму», алгоритм ізолює точку випадковими розрізами за випадковими ознаками. Аномалії відтинаються за кілька розрізів, тож середня довжина шляху E(h(x)) коротка; оцінка s = 2^(−E(h(x))/c(n)) нормує її в (0,1). Практичні наслідки: складність ~O(n·log n), відсутність потреби в мітках, стійкість у високій вимірності — і контрінтуїтивна деталь: підвибірка 256 точок на дерево працює краще за повний набір, бо великі підвибірки посилюють ефект затоплення.
Contamination — не гіперпараметр, а рішення про штат. Цей параметр задає частку подій, які ви оголосите аномальними, тобто безпосередньо визначає довжину черги на розбір. Лишити типове значення 0.1 на потоці 480 000 подій на добу означає замовити 48 000 сповіщень команді, здатній розібрати 120. Правильний шлях зворотний: візьміть денну спроможність, поділіть на обсяг потоку і візьміть відповідний квантиль розподілу оцінок.
Маскування та затоплення. Кілька схожих аномалій поруч ховають одна одну (masking); нормальні точки біля щільного кластера аномалій отримують високі оцінки (swamping). Обидва ефекти послаблюються підвибіркою та базовими лініями за сутністю — і жодного з них не видно на агрегованій метриці, лише на розборі конкретних кейсів.
# Per-entity baseline + isolation forest, with the alert threshold taken from
# the team's review capacity rather than from a default contamination value.
import numpy as np
from sklearn.ensemble import IsolationForest
REVIEWS_PER_DAY = 120 # analyst capacity -- the real binding constraint
EVENTS_PER_DAY = 480_000
def robust_z(g):
med = g.median()
mad = (g - med).abs().median()
return 0.6745 * (g - med) / (mad if mad > 0 else 1e-9) # MAD, not sigma
# Point deviation, measured against the counterparty's OWN history:
df['amt_z'] = df.groupby('counterparty_urn')['amount'].transform(robust_z)
# Contextual deviation: same hour, same weekday, same counterparty.
df['ctx_z'] = (df.groupby(['counterparty_urn', 'dow', 'hour'])['req_count']
.transform(robust_z))
feats = df[['amt_z', 'ctx_z', 'velocity_1h', 'new_device', 'geo_jump']].values
# max_samples=256 is the published sweet spot: larger subsamples increase
# swamping and cost, they do not improve recall.
iso = IsolationForest(n_estimators=200, max_samples=256,
contamination='auto', random_state=7).fit(feats)
score = -iso.score_samples(feats) # higher = more anomalous (a RANK,
# not a calibrated probability)
# Threshold = capacity quantile, NOT a guessed contamination rate.
alert_rate = REVIEWS_PER_DAY / EVENTS_PER_DAY # 0.00025
threshold = float(np.quantile(score, 1.0 - alert_rate))
df['alert'] = score >= threshold
assert df['alert'].sum() <= REVIEWS_PER_DAY * 1.2, 'review queue will overflow'
На практиці
Оператор CDN виявляв сплески трафіку глобальним правилом «середнє + 3σ» і отримував близько 4 100 сповіщень на добу. Перехід на базову лінію за тенантом (медіана/MAD) зі зняттям сезонності «день тижня × година» і поріг за квантилем спроможності знизив потік до 118 сповіщень на добу, зберігши виявлення 7 із 8 відомих інцидентів. Середній час розбору одного сповіщення впав із 6,2 до 1,1 хвилини, бо доказ спрацювання став локальним і зрозумілим.Антипатерн
Порогування важкохвостої метрики через середнє і 3σ, а потім здивування, що детектор «нічого не бачить»: кілька великих викидів роздули σ настільки, що поріг проліз вище за них самих. Другий бік тієї ж помилки — лишитиcontamination=0.1 у продакшені й отримати чергу, що фізично не може бути розібрана.Економіка хибних спрацювань: поріг обирає гаманець, а не F1
Точність 99,9% на розрідженій задачі — порожня цифра. При поширеності шахрайства 0,1% детектор, який завжди відповідає «норма», має accuracy 99,9%. Єдина величина, що має значення для команди розбору, — прогностична цінність позитивного результату (PPV, вона ж precision), і вона визначається базовою частотою події не менше, ніж якістю моделі.
Арифметика, яку варто вміти рахувати усно. Мільйон транзакцій, поширеність 0,1% → 1 000 шахрайських. Детектор із чутливістю 90% і часткою хибнопозитивних 1% дає 900 істинних спрацювань і 9 990 хибних. Precision = 900 / 10 890 ≈ 8%: аналітик відкриває дванадцять справ, аби знайти одну. Щоб дійти до 50% precision за тієї ж чутливості, FPR має впасти приблизно до 0,09% — удесятеро. Саме тому на цьому шарі оптимізують специфічність, а не чутливість, і саме тому метрика, виміряна на збалансованій тестовій вибірці 1:1, не переноситься у продакшен узагалі.
Поріг обирає економіка. Порогом ви призначаєте ціну: E[cost](τ) = FN(τ)·L + FP(τ)·C, де L — середні втрати від пропуску, C — вартість розбору одного сповіщення (хвилини аналітика плюс тертя для клієнта). F1 неявно припускає L = C, що майже ніколи не так: у AML пропуск коштує тисячі, а розбір — одиниці; у медичному тріажі співвідношення протилежне. Оптимальний поріг — мінімум цієї кривої, і його треба перераховувати щоразу, коли змінюються L, C або штат.
Друга межа — спроможність. Навіть економічно оптимальний поріг марний, якщо черга росте швидше, ніж її розбирають: alerts/day ≤ analysts × shifts × 60 / minutes_per_alert. Перевищення не означає «знайдемо більше» — воно означає зростання часу до реакції, тобто фактичне падіння повноти, якого офлайн-метрика не показує.
Зворотний зв'язок отруює вибірку. Мітки надходять лише з розібраних сповіщень, тобто з-над порога. Модель, перенавчена винятково на них, дедалі впевненіше повторює власні минулі рішення, а зона під порогом стає сліпою плямою. Протиотрута коштує грошей і є обов'язковою: постійна випадкова аудит-вибірка 1–2% подій нижче порога, які розбирають попри низьку оцінку. Це єдиний спосіб виміряти хибнонегативні й отримати незміщену повноту.
Нова версія виходить у тінь. Претендент рахує оцінки на живому потоці, не породжуючи сповіщень; порівняння — за precision@k на однаковому бюджеті сповіщень, а не за AUC. Перемикати можна лише після того, як минув період затримки міток і результати за той самий проміжок стали відомі.
# Choosing the operating point by expected cost under a capacity constraint,
# not by argmax F1. Scores come from the shadow-scored live stream.
import numpy as np
LOSS_PER_FN = 850.0 # EUR, measured average uncaught loss
COST_PER_FP = 7.5 # EUR, 6 analyst-minutes + customer friction
CAPACITY = 120 # alerts/day the team can actually clear
DAILY_EVENTS = 480_000
def operating_point(scores, labels):
best = None
for tau in np.quantile(scores, np.linspace(0.90, 0.99999, 400)):
flag = scores >= tau
tp = int((flag & (labels == 1)).sum())
fp = int((flag & (labels == 0)).sum())
fn = int((~flag & (labels == 1)).sum())
alerts_day = float(flag.mean()) * DAILY_EVENTS
if alerts_day > CAPACITY: # hard constraint, tested FIRST
continue # an unreviewable alert is not detection
cost = fn * LOSS_PER_FN + fp * COST_PER_FP
if best is None or cost < best['cost']:
best = {'tau': float(tau), 'cost': cost,
'precision': tp / max(tp + fp, 1),
'recall': tp / max(tp + fn, 1),
'alerts_day': alerts_day}
return best
# Unbiased recall needs labels from BELOW the line. Keep a permanent random
# audit sample of non-alerted events in the review queue -- forever, not once.
audit_mask = np.random.rand(len(scores)) < 0.01
# NEVER report precision measured on a rebalanced test set: precision is not
# prevalence-invariant, and 1:1 down-sampling inflates it by ~100x here.
На практиці
Банк середнього розміру налаштував AML-детектор за максимумом F1 і отримав 2 400 сповіщень на добу проти спроможності 300. Медіанний час до розбору сягнув 9 днів, і фактична повнота впала до 31% при офлайн-повноті 88%. Перекалібрування заL = 850 € та C = 7,5 € з обмеженням у 300 сповіщень дало 290 сповіщень на добу, precision зросла з 4% до 19%, а кількість підтверджених повідомлень про підозрілі операції — на 22%.Антипатерн
Звітувати precision, виміряну на тестовій вибірці зі штучно збалансованими класами: 92% у звіті перетворюються на 8% у продакшені, бо precision не інваріантна до базової частоти. Той самий клас помилки — перенавчання лише на розібраних сповіщеннях: модель оптимізує згоду сама з собою, а зона під порогом ніколи не перевіряється.Джерела, з яких виведено напрям
- A Look Toward the Future Decision Suppor
- A Framework to Assess Intelligent Decisi
- A conceptualization and general architec
- A Mobile Emergency Triage Decision Suppo
- A Comparative Study of Anomaly Detection
- A Revealing Large Scale Evaluation of Un
- Graph Anomaly Detection with Graph Neural Networks
- A Systematic Review on Anomaly Detection
- A Review of Anomaly based Intrusion Dete
- A Survey on Anomaly Based Network Intrus
- A Review of Machine Learning based Anoma
Пройти інтерактивно
У кожного напряму є питання, картки з інтервальним повторенням і облік прогресу. Для них потрібен акаунт — безкоштовний і на одну хвилину.
Відкрити інтерактивний трек Створити безкоштовний акаунт