Почему никто не объясняет аномалии во временных рядах?
Недавно встретились с коллегой за кружкой кофе, без всякой рабочей повестки. Он занимается Data Science, я — backend‑разработкой. Разговор как‑то незаметно, как это обычно бывает, свернул в сторону обсуждения рабочих нюансов, а самый обычный вопрос про очередной график в дашборде — закончился идеей проекта, который в итоге вырос в open‑source фреймворк.
В какой‑то момент он сказал фразу, которая неожиданно зацепила:
«Мы легко и быстро умеем находить аномалии. Но потом аналитик всё равно часами выясняет почему они произошли.»
Чем больше я об этом думал, тем яснее понимал: я никогда не видел хорошего инструмента, который автоматизировал бы именно эту часть работы. Не поиск аномалии, не прогнозирование, не построение модели — а поиск ответа на вопрос: почему график изменился?
Так появился WhyTrend.
Мы умеем находить аномалии, но не умеем их объяснять
Экосистема анализа временных рядов сегодня выглядит очень зрелой. Есть десятки библиотек для прогнозирования (Prophet, ARIMA/SARIMA из statsmodels), отличные инструменты для обнаружения аномалий (PyOD, Isolation Forest), алгоритмы поиска точек изменения тренда (Ruptures, ADTK). Хочешь понять, есть ли выброс — пожалуйста, таких библиотек навалом. Нужно определить момент смены режима работы системы? Тоже не проблема, я сам когда‑то использовал парочку таких на предыдущем проекте. А один знакомый аналитик как‑то рассказывал, что прогноз продаж на следующий месяц у него занимает от силы час: берёт всё тот же Prophet, гоняет по историческим данным — и задача, которая раньше казалась нетривиальной, для него давно рутина.
Но дальше почти всегда происходит одна и та же история. Алгоритм сообщает: «26 января обнаружена аномалия» — и всё. На этом автоматизация заканчивается, и начинается работа человека.
Что происходит после того, как найден всплеск
Типичная ситуация: маркетолог замечает, что количество поисковых запросов выросло почти втрое. Или продуктовый аналитик видит необычный скачок регистраций. Или инженер открывает Grafana и видит всплеск ошибок.
Первый вопрос всегда один и тот же — почему?
И дальше начинается процесс, знакомый практически каждому аналитику: Google, новости, Reddit, Hacker News, GitHub, Wikipedia, календарь релизов, Slack. Знаю по себе — когда в проде внезапно скачут ошибки, сам не раз убивал вечер на то же самое: чейнджлог, Slack‑переписка с теми, кто недавно деплоил, гугл на предмет «не упал ли у кого сторонний сервис». Потом кто‑нибудь вспоминает: «Подождите… кажется, в тот день вышла новая версия» или «Это же был инцидент у облачного провайдера, весь интернет тогда лежал». Сам поиск причины иногда занимает в десять раз больше времени, чем обнаружение самой аномалии.
И почти всегда этот процесс выглядит одинаково: ищем события вокруг определённой даты, пытаемся понять, какие из них связаны с нашей метрикой, отбрасываем нерелевантные, формулируем гипотезу, подкладываем ссылки, пишем объяснение. То есть каждый раз вручную собираем один и тот же пайплайн.
Странный пробел в экосистеме
Когда я начал смотреть, какие инструменты уже существуют, выяснилась интересная вещь: практически каждая часть этой задачи уже кем‑то решена. Есть библиотеки для временных рядов (Prophet, statsmodels), для поиска аномалий (PyOD), для ранжирования документов (BM25, sentence‑transformers), поисковые API (Google News API, Bing News Search), большие языковые модели (GPT, Claude, локальные модели через Ollama), инструменты для генерации отчётов (Jinja2, ReportLab). Но между ними нет слоя, который отвечал бы за самое очевидное действие после обнаружения аномалии — попытку её объяснить. Причём не в формате «нейросеть так решила», а в формате, который можно показать человеку:
Интерес к DeepSeek резко вырос 26 января 2025 года.
Вероятные причины: опубликованы результаты DeepSeek R1; несколько крупных публикаций на Hacker News; появились первые массовые обзоры модели; интерес усилился после обсуждения в сообществе.
Со ссылками, с уровнем уверенности, с возможностью проверить каждое утверждение. Это уже не просто ответ модели — это гипотеза, подкреплённая источниками. Именно такого инструмента мне найти не удалось.
Чем это отличается от SHAP и explainable AI
Разве тема объяснимости не существует давно? Существует — но здесь важное различие. Большинство методов explainable AI отвечают на вопрос «почему модель приняла именно такое решение»: какие признаки сильнее всего повлияли на классификацию, почему модель дала определённый прогноз. Это объяснение поведения модели.
Меня интересовал другой вопрос: не почему модель нашла аномалию, а почему сама аномалия произошла в реальном мире. Это две совершенно разные задачи. Google Trends показывает резкий рост интереса к продукту — алгоритм легко определит точку изменения, но пользователю гораздо важнее узнать не то, что Z‑score превысил порог, а что произошло за пределами графика: вышел релиз, компания представила продукт, крупный блогер выпустил обзор, проект попал на главную Hacker News. Именно внешний контекст превращает набор чисел в объяснимую историю.
Здесь же стоит вскользь упомянуть PyRCA — открытую библиотеку от Salesforce для Root Cause Analysis, тоже вроде бы про «почему». Но это «почему» другое: PyRCA строит граф зависимостей между сервисами и ищет первопричину сбоя внутри вашей инфраструктуры — по метрикам, логам, топологии микросервисов. WhyTrend смотрит в противоположную сторону, наружу, туда, где никакого графа зависимостей не существует в принципе, потому что причина лежит не в вашем коде, а в реальном мире.
Как сложился пазл
После нескольких обсуждений стало понятно, что задача неплохо раскладывается на независимые этапы: сначала ищем аномалию, потом определяем интересующий временной интервал, собираем события вокруг этой даты, отбираем те, что действительно относятся к нашей метрике, и только после этого просим языковую модель написать связное объяснение.
Так родилась идея WhyTrend — не очередной библиотеки для поиска аномалий и не очередной LLM‑обёртки, а фреймворка, который собирает весь этот процесс в единый конвейер, где каждый этап можно заменить своим: другой алгоритм обнаружения, другой источник данных, другую модель, другой способ ранжирования.
Забавно, но на код ушло меньше времени, чем на обдумывание. Где‑то неделю мы просто по фану обсуждали архитектуру в свободное время — как раскладывать конвейер на этапы, что должно быть плагином, а что нет. И только после этого ещё неделю я потратил, чтобы довести эту идею до рабочего MVP. Это стало возможным во многом потому, что почти все «тяжёлые» части — BM25, работу с LLM, статистику — я не писал с нуля, а брал готовыми библиотеками. Неделя ушла не на алгоритмы, а на архитектуру вокруг них. Как именно это устроено внутри и почему это архитектурно фреймворк, а не библиотека — отдельная большая тема, о ней я расскажу во второй части. Здесь же хочу показать, что получилось на практике.
Проверяем идею на реальных данных
Для первого эксперимента хотелось взять данные, которые легко получить, легко проверить вручную и понятны любому читателю. Выбор пал на Google Trends: это классический временной ряд, а всплески интереса там почти всегда связаны с реальными событиями — значит, можно проверить не только работу алгоритма, но и качество найденного контекста.
Пример: DeepSeek в Google Trends
На уровне кода всё выглядит просто:
from whytrend import (
Pipeline, GoogleTrends, ZScoreDetector,
BM25Ranker, OllamaExplainer,
)
from whytrend.collectors import HackerNewsCollector, WikipediaCollector
pipeline = (
Pipeline(window_days=7)
.add_source(GoogleTrends(
keyword="DeepSeek",
timeframe="2024-06-01 2025-07-01",
))
.add_detector(ZScoreDetector(threshold=1.5))
.add_collector(HackerNewsCollector(max_results=8))
.add_collector(WikipediaCollector(max_results=5))
.add_ranker(BM25Ranker(top_k=5))
.add_explainer(OllamaExplainer(model="qwen2.5-coder:14b"))
)
report = pipeline.run()Но внутри происходит довольно много работы:
Google Trends → получение временного ряда → поиск аномалий → определение временного окна → сбор внешнего контекста → ранжирование документов → формирование промпта → LLM → Markdown / JSON Report
Каждый этап можно заменить — именно поэтому хотелось сделать не готовый сервис, а конструктор подобных пайплайнов.
В качестве первого примера я взял запрос DeepSeek: история развития проекта хорошо документирована, почти каждый значимый скачок интереса сопровождался публикациями и обсуждениями, что позволяет проверить результат вручную. После загрузки данных детектор нашёл сразу два всплеска подряд — оба вокруг 26 января 2025 года, с уверенностью 0.80 и 0.85. Сам по себе график не говорит ни о чём — мы знаем только одно: в эти дни интерес резко вырос, причём дважды. Дальше начинается самое интересное.
Вот реальный вывод одного прогона — без купюр, только почистил ссылки от артефактов копипаста:
$ python analyzer.py
Running pipeline for Google Trends: 'DeepSeek'...
(Fetching trends + HN + Wikipedia — may take 10-30 seconds)
============================================================
EXECUTIVE SUMMARY
============================================================
The spike in value for DeepSeek on January 26, 2025, is likely due to
the announcement of promising results from the DeepSeek R1 project.
--- Anomaly 1 ---
Confidence: 0.80
Top causes:
• Promising results from DeepSeek R1 for code (1.00)
https://simonwillison.net/2025/Jan/27/llamacpp-pr/
• Run DeepSeek R1 Dynamic 1.58-bit (0.42)
https://unsloth.ai/blog/deepseekr1-dynamic
--- Anomaly 2 ---
Confidence: 0.85
Top causes:
• Promising results from DeepSeek R1 for code (1.00)
• Run DeepSeek R1 Dynamic 1.58-bit (1.00)
• An analysis of DeepSeek’s R1-Zero and R1 (0.61)
https://arcprize.org/blog/r1-zero-r1-results-analysis
Saved: /home/.../Projects/whytrend-demo/deepseek_report.mdСобираем контекст
После обнаружения аномалии WhyTrend вычисляет временное окно — три дня до события, сам день события и три дня после, итого семь дней (ровно наш window_days=7 из кода выше). Именно в этих пределах начинают работать Collectors — в этом прогоне их два, HackerNewsCollector и WikipediaCollector. Любопытная деталь из реального вывода выше: все причины, которые в итоге попали в отчёт, пришли из Hacker News — Wikipedia‑коллектор тоже отработал, но среди его результатов не нашлось ничего настолько релевантного, чтобы попасть в топ. Это нормально: не каждый источник обязан выстрелить в каждом конкретном случае, и на этом этапе система ещё не знает заранее, какой источник даст полезный материал — она просто собирает потенциальные доказательства отовсюду, куда дотянулась.
Зачем нужен Ranker
Если просто передать все найденные документы языковой модели, результат получится посредственным: во‑первых, это дорого, во‑вторых, большая часть найденного может вообще не иметь отношения к аномалии. Поэтому следующим этапом работает ранжирование — здесь используются BM25, эмбеддинги или любые другие алгоритмы. Задача простая: из десятков найденных материалов выбрать несколько наиболее релевантных.
Документ | Relevance |
|---|---|
Promising results from DeepSeek R1 for code | 1.00 |
Run DeepSeek R1 Dynamic 1.58-bit | 1.00 |
An analysis of DeepSeek's R1-Zero and R1 | 0.61 |
Это реальные цифры из отчёта выше (Anomaly 2) — никакого приукрашивания, BM25 действительно оценил эти три материала как основных кандидатов, а остальное отсеял.
Только после этого формируется промпт для LLM — и это, пожалуй, самое важное отличие WhyTrend от идеи «давайте просто спросим ChatGPT»: модель получает не пустой график, а уже подготовленный контекст.
Почему нельзя было просто спросить LLM напрямую
Первая мысль была очевидной: есть график, есть языковая модель — давайте просто спросим её, почему произошёл этот всплеск. Получилось плохо. Не потому что модели слабые, а потому что они не знают контекст: для LLM график — это просто набор чисел. Она не знает, что в этот день состоялся релиз, не знает про новости, про публикацию на Hacker News, про обсуждение в Reddit. Если попросить её сразу придумать объяснение, она почти неизбежно начинает фантазировать.
Поэтому LLM в WhyTrend не ищет причины — они уже найдены и отранжированы предыдущими этапами. Её задача — объединить найденные факты, убрать повторы, сформулировать краткое объяснение и сохранить ссылки на источники. В реальном выводе выше это как раз видно: Executive Summary — не выдумка модели, а прямое следствие трёх причин из таблицы, которые ей передали в промпте вместе с их relevance и ссылками.
Важно не само объяснение, а то, почему модель пришла именно к такому выводу — поэтому отчёт всегда содержит список использованных источников с указанием relevance для каждого, как в примере выше. Пользователь видит не только красивый текст, но и все факты, на которых он построен, и при желании может открыть и проверить каждую ссылку. Если объяснение нельзя проверить — это не объяснение, а просто ещё одно мнение языковой модели.
Что это дало на практике
Самым неожиданным открытием стало не качество найденных объяснений, а скорость. Раньше подобный разбор занимал минимум полчаса, даже если хорошо знать предметную область: открыть график, посмотреть дату, поискать новости, прочитать обсуждения, проверить релизы, собрать ссылки, написать резюме. В прогоне выше весь пайплайн — от запроса в Google Trends до готового markdown‑отчёта — уложился в 10–30 секунд, большая часть которых ушла на сетевые запросы к Hacker News и Wikipedia, а не на саму LLM. Не потому, что LLM стала умнее, а потому что ей больше не приходится работать вслепую: вместо «угадай, почему произошёл всплеск» она получает уже собранную картину происходящего.
WhyTrend не пытается заменить аналитика — он избавляет его от самой скучной части исследования: поиска и первичной систематизации контекста. Он не проводит Root Cause Analysis в привычном DevOps‑смысле, не читает логи и не знает внутреннего устройства вашей системы — его задача скромнее: собрать внешний контекст, отфильтровать шум и подготовить первый черновик объяснения. Окончательное решение остаётся за человеком.
Что дальше
Сейчас WhyTrend — это MVP, и Google Trends здесь лишь частный случай. Дальше интересно проверить фреймворк на любых временных рядах, где причина всплеска лежит вовне: количество регистраций, продажи, упоминания бренда. В планах — больше источников (Google News, Reddit, GitHub Releases), другие алгоритмы поиска точек изменения (Ruptures, Bayesian Online Change Point Detection), CLI для запуска анализа одной командой.
О том, как устроен сам фреймворк внутри — почему это именно framework, а не библиотека, почему компоненты ничего не знают друг о друге, почему BM25 обошёл эмбеддинги и почему LLM оказалась не в центре, а на периферии архитектуры — я подробно расскажу во второй части.
Исходный код проекта — на GitHub. Начать можно буквально с нескольких строк кода или с собственного CSV‑файла. Если у вас есть идея нового источника данных, алгоритма ранжирования или детектора аномалий — pull request будет даже интереснее, чем ещё одна ⭐ в репозитории.
Спасибо, что дочитали.