В этой статье будет продемонстрировано применение трёх ML алгоритмов (Isolation Forest, CatBoost, Autoencoder) к решению задачи детекции подозрительных событий в активности пользователей.
Описание задачи
Представьте себе инфраструктуру крупной компании, где хранятся миллионы файлов. Сотрудники постоянно взаимодействуют с ними: читают, изменяют, создают новые. В этом непрерывном потоке событий крайне сложно вручную заметить признаки потенциальной угрозы — будь то инсайдер, копирующий данные, или вирус, массово шифрующий файлы.
Машинное обучение позволяет автоматизировать поиск таких аномалий. В качестве исследуемых методов были выбраны три модели, построенные на разных принципах: изолирующий лес (Isolation Forest), градиентный бустинг (CatBoost) и нейросетевой автоэнкодер. Задача — оценить их способность детектировать аномалии на многомерных временных рядах и выявить сильные стороны каждого подхода.
Данные
Исходные данные для исследования — обезличенный лог действий пользователей с файловым хранилищем. Каждая запись содержит тип операции (чтение, создание, изменение, удаление), идентификатор пользователя и временную метку.
Для перехода к формату, пригодному для машинного обучения, выполняется агрегация. Временная ось разбивается на равные интервалы. Для каждого интервала подсчитывается количество событий по каждому типу операции. В результате формируется многомерный временной ряд, где каждый временной отрезок представлен вектором признаков: интенсивность чтений, интенсивность записей и так далее.

Задача моделей — выявить временные отрезки, характеристики которых значимо отклоняются от нормального поведения. Такие отклонения могут быть точечными (отдельный всплеск активности) или групповыми (устойчивое изменение паттерна на некотором промежутке) и рассматриваться как потенциальные индикаторы угроз.

Unsupervised подходы
Перед тем как начать погружение в модели, важный нюанс - в исходных данных не было разметки на аномальное / нормальное событие. Поэтому был выбраны модели, которые смогут в пуле событий отличать нормальное поведение от подозрительного.
Выявление выбросов с помощью изолирующего леса
from sklearn.ensemble import IsolationForest
Основная идея алгоритма – сила случайности. Оказалось, что построение деревьев, где каждый узел - это случайное разделение данных, способно быстро отделить аномальные наблюдения от основного набора. Действительно, аномалии - это редкие наблюдения, признаки которых значительно отличаются от основного набора. Поэтому при случайном разрезании признакового пространства аномалии быстрее изолируются - попадают в лист, где больше нет соседей.
В отличие от случайного леса (random forest), изолирующий лес работает без учителя. Алгоритм не фокусируется на нормальном поведении и не запоминает специально как выглядят аномалии. Вместо этого модель учится отделять (изолировать) странные, непохожие на других, наблюдения от основной массы событий.
Пару слов о том, как устроен алгоритм. IForest – это ансамбль решателей, каждый из которых строит своё дерево бинарной классификации, разделяя признак случайным образом. Процесс повторяется рекурсивно до тех пор, пока в каждом листе не окажется ровно по одному объекту. Для каждого наблюдения считается расстояние пути от его листа до корня дерева. Затем считается среднее расстояние по результатам построения всех деревьев. Наконец, наблюдения с наименьшими расстояниями будут считаться аномалиями.

Изолирующий лес можно применять как ко всему многомерному ряду, как и к каждому признаку отдельно. В одномерном случае модель анализирует всплеск только относительно такой же активности в прошлом. Многомерный вариант ещё учитывает взаимосвязь признаков в рамках одного момента времени, но снижает интерпретируемость – модель не сможет рассказать о природе найденной аномалии.
Адаптация алгоритма к временным рядам
Учёт изменчивости данных: Выбор оптимального временного диапазона для формирования выборки позволяет адаптироваться к естественным изменениям в динамике активности (расширение штата, временное увеличение рабочей нагрузки) без потери качества при анализе гигантских датасетов.
Дополнительные признаки: Для сохранения структуры временного ряда добавляем лаговые признаки - количество событий за предыдущие 3, 5, 10 минут. Далее мы проверим с помощью SHAP вклад каждого признака в общую оценку аномальности объекта.
data[‘prev_10min’] = (data.rolling(window=‘10min’, min_periods=1)[‘events’].sum()).astype(int)
data[‘prev_5min’] = (data.rolling(window=‘5min’, min_periods=1)[‘events’].sum()).astype(int)
data[‘prev_3min’] = (data.rolling(window=‘3min’, min_periods=1)[‘events’].sum()).astype(int)
Подбор гиперпараметров
N_estimators: чем больше число решателей, тем точнее и затратнее по вычислительным ресурсам. Надо искать оптимальное значение.
Constamination: доля объектов, которую модель относит к аномалиям.
Max_samples: количество объектов в случайной выборке для каждого дерева (по умолчанию 256 – это максимальное значение).
Bootstrap: выборку входных наблюдений можно формировать с возвращением или без. Лучше без (Bootstrap=False), чтобы увидеть большее число объектов.
if_model = IsolationForest(n_estimators=100, contamination=0.05, bootstrap=False, random_state=RAND)
Метрики качества
Степень уверенности модели: Встроенный метод decision_function позволяет получить количественную оценку аномальности объекта (anomaly score), которая связана со средней длиной пути от корня дерева до листа, в котором оказался объект. В реализации sklearn эта оценка нормализована: её среднее значение на выборке равно нулю. С учётом нормировки anomaly score изменяется в диапазоне [-0.5, 0.5]. Отрицательная оценка указывает на аномалии, положительная - на нормальные объекты. Абсолютная величина оценки показывает, насколько модель уверена в своём решении: чем больше модуль, тем больше уверенность. Например, аномалии, в которых модель уверена больше всего будут иметь anomaly_score < -0.25.
data[‘anomaly’] = np.where(if_model.fit_predict(data[[‘events’, ‘prev_3min’, ‘prev_5min’, ‘prev_10min’]])==1, 0, 1)
data[‘anomaly’] = np.where(if_model.decision_function(data[[‘events’, ‘prev_3min’, ‘prev_5min’, ‘prev_10min’]])>-0.25, 0, 1)
После того, как мы определились с выборкой, признаками, гиперпараметрами и критерием принадлежности объекта к аномальному классу, мы готовы отобразить решение. Для создания интерактивных подвижных графиков с временной шкалой используем библиотеку plotly.

Интерпретируемость: Важность признаков можно оценить с помощью интерпретатора SHAP (shap.TreeExplainer). В нашем случае получилось, что лаговый признак с агрегацией в 10 минут наиболее значим в оценке - исключение этого признака уменьшает decision_function.

Отличительные характеристики модели
Удобство настройки модели вследствие сравнительно малого числа гиперпараметров.
Высокая скорость выдачи ответа. Сложность алгоритма O(n log(n)) позволяет обрабатывать большие наборы данных в режиме реального времени
Вычислительная экономичность - достаточно CPU. Модель не требует предварительного обучения, не хранит в памяти веса и все исходные данные. Анализ происходит 'налету' со своевременных освобождением памяти. Отсюда вытекает следующее достоинство - простота архитектуры конечного решения
Прогнозирование временного ряда с помощью CatBoost
from catboost import CatBoostRegressor
Для этого подхода поиска аномалий мы, напротив, сконцентрировались на "нормальных" данных. Если мы знаем предыдущую активность, то можно обучить ML модель прогнозировать её динамику наперёд. Так как модель видела преимущественно только нормальные данные, то и предсказания будут отражать типовое значение. Аномалий же будет считаться наблюдение, где действительное значение сильно отклоняется от прогноза.
Алгоритм поиска аномалий работает по двухэтапной схеме:
этап прогнозирования: модель, изучив исторические данные (например, активность за предыдущий месяц), учится предсказывать ожидаемое число операций на следующем шаге
этап детектирования: сравниваем предсказание модели с фактическим значением. Если разница между ними превышает некоторый порог - найдена аномалия. Чем выше anomaly_score, тем подозрительнее наблюдение.
expected_value = model.predict()
anomaly_score = abs(actual_value - expected_value)
Способ выбора обучающей и тестовой выборки
Для прогнозирования временных рядов важна временная зависимость и порядок в данных. Для работы непрерывной работы модели используется схема скользящего обучения: в каждый новый день модель обучается на на всём доступном историческом пуле данных, чтобы поддерживать актуальность прогнозов.
Обучающая выборка (3 недели) — период типичной активности. На этих данных модель изучает маркеры, которые описывают нормальную активность.
Валидационная выборка (1 неделя) — следующий за обучением период без аномалий, на котором подбираются гиперпараметры модели.
Тестовая выборка (2 аномальных дня) — здесь мы проверяем, сможет ли модель, обученная на «спокойных» данных, заметить отклонения.

Новые признаки для временных рядов
В случае изолирующего леса добавление новых признаков помогало сохранить временную зависимость между наблюдениями. В случае прогнозирования тоже важно расширить признаковое пространство одномерного временного ряда, но чтобы дать модели больше контекста о наблюдаемом событии.
Создадим дополнительные признаки: временные, лаговые и скользящие статистики. Важный нюанс: лаговые признаки и скользящие статистики считаются отдельно для обучающей и валидационной выборок, чтобы избежать утечки данных в прошлое. Иначе, если модель будет подсматривать в будущее, то её ответы будут крайне недостоверны.
временные признаки: день недели и номер часа
скользящие статистики (mean, std, quantile = 0.8) за предыдущие 3 и 7 дней: чтобы модель понимала общий тренд и изменчивость данных.
лаговые признаки: сумма событий за предыдущий час, 30, 10, 5 и 3 минуты: чтобы уловить краткосрочные тенденции
#скользящие статистики
data[‘week_mean’] = round(data.rolling(window=‘7d’, min_periods=1)[‘events’].mean(), 2)
data[‘week_std’] = round(data.rolling(window=‘7d’, min_periods=1)[‘events’].std(), 2)
data[‘week_q80’] = round(data.rolling(window=‘7d’, min_periods=1)[‘events’].quantile(q=0.8), 2)
#лаговые признаки
data[‘prev_hour’] = (data.rolling(window=‘1h’, min_periods=1)[‘events’].sum()).astype(int)
data[‘prev_30min’] = (data.rolling(window=‘30min’, min_periods=1)[‘events’].sum()).astype(int)
Вот так, с помощью расширение набора признаков простой момент времени превращается в многофакторный портрет. Теперь мы можем задать модели вопрос: "Какое число операций следует ожидать в пятницу в 9 утра, если за последний час их было 10, а скользящее среднее за неделю составляет 15?"
Метрики для оценки качества прогноза
Особенно информативны для текущей задачи будут следующие регрессионные метрики:
MAE — среднее абсолютное отклонение предсказания от истинного значения — самая понятная метрика оценки на сколько событий ошибается модель
R2 — коэффициент детерминации говорит о том, насколько хорошо модель предсказывает данные по сравнению с простым предсказанием средним значением (R2 = 1 — идеальный прогноз, R2 = 0 — прогноз не лучше среднего)
MAPE — относительная ошибка, оценивающее соотношение разницы к действительному значению. Эта метрика оценивает насколько модель сохраняет разряд для предсказанных чисел. Эта метрика дополняет MAE. Например, MAE = 1 при действительном значении = 10, это много или мало? MAPE покажет, что относительная ошибка = 10%. Однако, MAPE неустойчива на данных, близких к нулю (предсказание = 2 операции при ожидании = 1 даст ошибку в 100% при малом mae = 1)
WAPE — взвешенная относительная ошибка. Более надёжная альтернатива MAPE, так как меньше штрафует модель за ошибки в области с низкой активностью. WAPE нормирует общую ошибку на общую сумму фактических значений.
Конечные метрики модели на валидационной выборке будем определять путём усреднения по всем дням
Выбор модели - почему бустинг?
Наконец, после подготовке всей фактуры, приступаем к выбору самой модели. В сердце нашего подхода - бустинг CatBoost. Но почему он?
Бустинг - это ансамбль решающих деревьев, которые обучаются последовательно. Каждое новое дерево учится на ошибках предыдущего, постепенно увеличивая точность предсказания всего алгоритма.
Временные ряды редко подчиняются простым линейным законам. Их поведение - это смесь суточных и недельных циклов, трендов и случайных шумов. Деревья в составе бустинга идеально подходят для того, чтобы видеть нелинейные паттерны. Вместе они создают гибкую и точную функцию прогнозирования.
Baseline модели
cb = CatBoostRegressor(allow_writing_files=False, random_state=RAND, eval_metric=“MAE”)
Средние метрики на валидационной выборке:
model | MAE | R2 | MAPE_% | WAPE_% |
CatBoost_baseline | 1.88 | 0.73 | 73.25 | 39.48 |

Подбор гиперпараметров
С помощью optuna получаем набор лучших гиперпараметров:
params = {‘border_count’: 32,
‘bootstrap_type’: ‘MVS’,
‘iterations’: 1000,
‘l2_leaf_reg’: 15,
‘grow_policy’: ‘Depthwise’,
‘depth’: 9,
‘learning_rate’: 0.4,
‘random_state’: RAND}
model | MAE | R2 | MAPE_% | WAPE_% |
CatBoost_baseline | 1.88 | 0.73 | 73.25 | 39.48 |
CatBoost_tuning | 0.2 | 0.91 | 3.8 | 3.8 |

Предсказание аномалий
После того как мы получили модель, способную предсказывать нормальное изменение временного ряда. Подадим на вход тестовый день с всплесками до 100 операций в минуту. Заметное ухудшение метрик сразу даёт знать об аномальной активности, а оценка по графику позволяет невооружённым взглядом увидеть аномалии.
MAE | R2 | MAPE_% | WAPE_% | |
test_with_anomalies | 30.7 | 0.49 | 635.48 | 51.66 |


Отличительные характеристики модели
Интерпретируемость степени аномальности события (как разница действительного значения и прогноза) делают прогноз интуитивно понятным пользователям
Большая чувствительность к аномалиям (по сравнению с изолирующим лесом) - способность отловить маркеры аномалии заранее за счёт обучения на нормальной динамике
Необходимость в дополнительной инфраструктуре для дооучения бустинга для качественного мониторинга аномалий в динамике
Поиск аномалий по ошибке реконструкции с помощью нейронных сетей - автокодировщиков
from tensorflow.keras import Model
Традиционные методы машинного обучения (леса, бустинг) требуют от нас ручного конструирования признаков: мы сами должны придумать какие характеристики (лаговые, обобщающие признаки) могут указывать на аномалию. А что если делегировать алгоритму не только прогноз, но и само создание признаков?
Нейронные сети зарекомендовали себя способностью автоматически выявлять неочевидные зависимости в данных. На вход можно подать временной ряд (гетерогенный набор событий) и модель сама построит признаки для обучения. Такой подход идеально подходит для ранней детекции сложных аномалий.
Нейронные сети - автокодировщики
Среди нейросетевых подходов особое место занимают автокодировщики - нейронные сети, работающие без учителя. Для них не требуется размечать данные ('аномалия' / 'норма') - а размечать пришлось бы большой объём данных, что необходимо для правильной работы всех нейросетевых алгоритмов.
Принцип работы автокодировщика напоминает сжатие файла. Bottleneck архитектура позволяет сети фокусироваться на ключевых свойствах нормального поведения и видеть аномальные зависимости.
Энкодер последовательно сжимает входной сигнал через слои с уменьшающимся числом нейронов, извлекая самую суть - латентное представление набора данных.
Бутылочное горлышко - самый узкий слой. Его малая размерность заставляет сеть отбрасывать шум и запоминать только ключевые паттерны нормального поведения.
Декодер выполняет обратную операцию - из сжатого представления он пытается восстановить исходный сигнал

Обучение автокодировщика заключается в требовании повторить на выходе входной набор данных - количество событий с разными операциями. Разница между входом и выходом называется ошибкой реконструкции.
Обучение происходит на данных, где подавляющее большинство примеров нормальные. Сеть становится специалистом по кодированию и декодированию именно нормальных паттернов. Когда на вход обученном автокодировщику подаётся аномалия (редкий отличительный объект), сеть не может правильно восстановить её - возникает большая ошибка реконструкции. Таким образом, порог ошибки реконструкции становится маркером аномальности. Если ошибка для нового наблюдения превышает установленный порог (рассчитанный на 99-м процентиле обучающей выборки), перед нами кандидат в аномалии.
Архитектура автокодировщика для временных рядов
Так как динамика активности это временной ряд, где события следуют в определённом порядке и косвенно связанны между собой циклами, то строительными блоками автокодировщика будут LSTM слои. Так обеспечивается долгосрочная память модели, способность помнить предыдущий набор действий и связывать события недельной давности (например, привычное увеличение рабочей активности по понедельникам).
На входе для каждого события имеем 8 признаков (= количеству типов операций, в которых ищем аномалии). Распределение количества нейронов по слоям: 8-12-6-3-6-12-8, функция активации для скрытых слоёв = relu.
Первый скрытый слой намеренно содержит больше нейронов, чем входных признаков. Такое расширение признакового пространства добавляет шум для устранения переобучения.

Отличительные характеристики модели
Высокая точность и способность видеть неочевидные зависимости в многомерных данных
Требовательность к объёму и составу данных, а также к наличию достаточного количества вычислительных ресурсов для настройки модели и повторного обучения
Сложность интерпретации результатов (нейронные сети сами конструируют признаки и их ответ сродни чёрному ящику)
Заключение
Как вы можете заметить все три модели показали приблизительно одинаковые результаты на тестовых данных.
Учитывая достаточную точность изолирующего леса, вместе с тем его скорость прогноза и отсутствие необходимости в поддержании цикла обучения модели, именно эта модель легла в основу real‑time системы мониторинга аномалий.
Для вашего удобства подготовили код реализации моделей в jupyter notebook — посмотреть можно на github.
