AI Architect Trainer Відкрити інтерактивний трек

ГоловнаАрхітектура корпоративної аналітики

Інтелект рішень і виявлення аномалій

Архітектура систем підтримки рішень, базові лінії та ізоляційний ліс, економіка хибних спрацювань і замикання циклу «сигнал → рішення → результат».

Востаннє переглянуто: 2026-09-04 · In English

У цьому напрямі

Четвертий шар корпоративної інтелектуальної архітектури не виробляє даних — він виробляє рішення. Цей модуль показує, чому дашборд аномалій не є системою підтримки рішень; як насправді оцінює точку ізоляційний ліс і чому його параметр contamination — це план завантаження вашої команди, а не гіперпараметр моделі; та як обирати робочий поріг у грошах, а не у F1. Наскрізна теза одна: детектор, результати рішень якого ніхто не записує, не має ані міток для навчання, ані доказів власної цінності.

Від сигналу до дії: анатомія системи підтримки рішень

E7.1

Дашборд не є системою підтримки рішень. Дашборд показує стан; СППР веде конкретну людину від сигналу до дії й записує, що з цього вийшло. Різниця не косметична: без записаного результату система ніколи не отримає міток, а без міток жодна модель усередині неї не здатна навчатися й жоден аналітик не доведе її окупність.

Чотири підсистеми класичної СППР — і що кожна означає в сучасній архітектурі:

Рішення — це об'єкт першого класу. Мінімальний контракт запису: ідентифікатор сутності, тригер (яка модель і яка її версія), оцінка й чинний на той момент поріг, знімок доказів, обраний варіант дії, автор, час, клас оборотності та — обов'язково — поле 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% менше хибних спрацювань при незмінній повноті.

Антипатерн

«Дашборд аномалій» без власника: сповіщення не має ані відповідального, ані терміну, ані поля результату, а успіх системи вимірюють кількістю виявлених аномалій. Такий контур не може виміряти жодного хибнонегативного випадку і через рік не має чим обґрунтувати власний бюджет.

Базові лінії та ізоляційний ліс: щодо чого ви вимірюєте відхилення

E7.2

Аномалія — це порушення очікування, а не велике число. Тому перше архітектурне рішення стосується не алгоритму, а бази порівняння: глобальний розподіл, конкретна сутність чи група однолітків. Платіж на 40 000 € аномальний для роздрібного клієнта і абсолютно нормальний для оптовика; глобальний поріг «сума > 10 000» — це не детектор, а фільтр, який щодня генерує ту саму сотню відомих імен.

Три класи аномалій вимагають трьох різних механізмів:

Ізоляційний ліс: чому він працює. Замість описувати «норму», алгоритм ізолює точку випадковими розрізами за випадковими ознаками. Аномалії відтинаються за кілька розрізів, тож середня довжина шляху 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

E7.3

Точність 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 не інваріантна до базової частоти. Той самий клас помилки — перенавчання лише на розібраних сповіщеннях: модель оптимізує згоду сама з собою, а зона під порогом ніколи не перевіряється.

Джерела, з яких виведено напрям

  1. A Look Toward the Future Decision Suppor
  2. A Framework to Assess Intelligent Decisi
  3. A conceptualization and general architec
  4. A Mobile Emergency Triage Decision Suppo
  5. A Comparative Study of Anomaly Detection
  6. A Revealing Large Scale Evaluation of Un
  7. Graph Anomaly Detection with Graph Neural Networks
  8. A Systematic Review on Anomaly Detection
  9. A Review of Anomaly based Intrusion Dete
  10. A Survey on Anomaly Based Network Intrus
  11. A Review of Machine Learning based Anoma

Пройти інтерактивно

У кожного напряму є питання, картки з інтервальним повторенням і облік прогресу. Для них потрібен акаунт — безкоштовний і на одну хвилину.

Відкрити інтерактивний трек Створити безкоштовний акаунт

Далі в цьому треку