Эксперимент 2026-05-26: anomaly detection по NDVI-кривым
Постановка
Найти поля-выбросы среди 20 рисовых чеков Темрюкского района по их сезонным NDVI-кривым. Использовать сразу несколько независимых методов и проверить согласованность – это даёт устойчивость к выбору алгоритма.
Дизайн
Три метода, на одних и тех же данных:
Метод 1: Pointwise z-score
Для каждой даты считаем медиану и MAD (median absolute deviation) NDVI по 20 полям. Для каждого поля считаем z = (ndvi_f - median_все_поля) / MAD. Пиксельный порог |z| > 2. По полю агрегируем:
anomaly_z_count– сколько раз поле выходило за порог;anomaly_z_total– сумма абс. z-значений за весь период.
Сильная сторона: показывает не только «насколько», но и «когда» происходила аномалия. Слабость: чувствителен к шуму единичных дат.
Метод 2: IsolationForest
На тех же 18 фичах, что у LightGBM в дне 6. contamination=0.15 (3 аномалии из 20 ожидаемых). n_estimators=200, random_state=42. Чем ниже decision_function – тем аномальнее. Инвертируем знак для интерпретации «высокий score = аномалия».
Сильная сторона: учитывает многомерную структуру (форма кривой через 10 NDVI-фичей сразу). Слабость: чёрный ящик, объяснение через permutation тяжёлое.
Метод 3: L2-distance от медианной кривой
Сглаженная NDVI-кривая поля (rolling 21d, daily grid 361 точка) сравнивается с медианной кривой по 20 полям через L2-норму. Одно число – общее «расстояние от стаи».
Сильная сторона: интерпретируется как геометрия в пространстве сезонных профилей. Слабость: один скаляр, не различает причину расхождения.
Объединение
Каждое поле получает 3 ранга (1 = самый аномальный). Финальная метрика mean_rank = mean(rank_z, rank_iso, rank_l2). Top-K=3 по этому рангу – финальный список выбросов.
Результаты
Top-3 аномалий
| field_id | mean_rank | rank_z | rank_iso | rank_l2 | z | count | iso_score | L2 | |
|---|---|---|---|---|---|---|---|---|---|
| F004 | 1.00 | 1 | 1 | 1 | 164 | 0.066 | 2.88 | ||
| F005 | 2.33 | 2 | 2 | 3 | 197 | 0.056 | 2.51 | ||
| F017 | 3.33 | 3 | 5 | 2 | 207 | -0.022 | 2.59 |
F004 – абсолютный лидер во всех трёх методах. F005 и F017 уверенно входят в top-3 у двух методов из трёх.
Согласованность методов (Spearman ρ по 20 рангам)
| Z-total | IsoForest | L2 | |
|---|---|---|---|
| anomaly_z_total | 1.00 | 0.70 | 0.96 |
| isoforest_score | 0.70 | 1.00 | 0.79 |
| l2_from_median | 0.96 | 0.79 | 1.00 |
Высокая корреляция Z ↔ L2 (0.96) – ожидаемо: оба строятся над одним сглаженным сигналом. IsolationForest даёт ρ=0.70-0.79 – независимый сигнал, но направление то же. Top-3 по объединённому рангу согласуется со всеми тремя методами – это и есть устойчивый сигнал.
Интерпретация выбросов
| field_id | NDVI peak | doy_peak | yield (синтетический) | Природа аномалии |
|---|---|---|---|---|
| F004 | 0.77 | 187 (05 июля) | 63.11 ц/га | Максимум по всем метрикам. Высокая биомасса, ранний и устойчивый пик. Это «лучшее» поле. |
| F005 | 0.45 | 141 (20 мая) | 47.58 ц/га | Минимум урожая. Низкая биомасса, рано «упал» в воду. Подозрение на пересев или другую культуру в ротации. |
| F017 | 0.61 | 134 (13 мая) | 57.09 ц/га | Сдвинутый сезон. Самый ранний пик из всех 20 полей. На heatmap виден зимний минимум вместе со всеми, но весной пик опережает остальных на 2-3 недели. Скорее всего раннее затопление чека. |
Anomaly-детектор находит и положительные, и отрицательные выбросы – это важно для агро-страхования: «вот поле, которое существенно отличается от соседей в любую сторону, посмотрите внимательнее».
Важное ограничение (для портфолио)
В индустриальной задаче «исторический коридор» строится из multi-year данных по тому же полю: 5 лет NDVI 2020-2024 даёт коридор p10-p90 на каждый календарный день. Поле сравнивается со своей же историей.
В нашем pet-проекте данные за один год, поэтому коридор построен по соседним полям того же года – это слабый proxy. Соседние поля могут отличаться сортом, датой затопления, фазой ротации (рис → пшеница → пар). Этот выбор зафиксирован честно в коде и в этом отчёте.
При расширении датасета на 5 лет (data/processed/ndvi_series.csv структурно к этому готов – только нужны больше запросов к STAC API) пересчитать тривиально: то же pointwise_z, но median и MAD строятся по 5 годам на каждый календарный день, а не по 20 полям одного года.
Что доказано
- Три независимых метода сходятся. Spearman ρ от 0.70 до 0.96 между парами. Top-3 одинаков для всех. Это признак устойчивого сигнала, а не артефакта одного алгоритма.
- Heatmap z-score читается как агрономический документ. Видны не только «какие поля», но и «когда» отклонялись от группы.
- Anomaly score интегрируется с моделью прогноза. Если поле в top-K по anomaly, его прогноз LightGBM (или линейной модели) нужно сопровождать предупреждением «out-of-distribution» – это валидный downstream-сигнал для UI Дня 8.
- Подход осознанно прост. Использован минимум алгоритмов, всё интерпретируется. На реальных multi-year данных можно добавить более сложные методы (DTW, autoencoder, prophet-residuals) – но они доказывают себя только когда у baseline-подходов кончается воздух.
Что дальше
- Multi-year коридор: заменить «20 полей одного года» на «5 лет одного поля». Это снимет главное ограничение текущего проекта.
- Per-date alerts: не только итоговый ранг, но и алерты конкретных дат («24 июля поле F011 ушло за p90 – проверьте полив»). Структура z-matrix к этому готова.
-
Стат-тест значимости: перейти от порога z >2 к p-value через permutation test или GEV для экстремумов. - Интеграция с UI (день 8): на folium-карте подсвечивать top-K красным border, в popup показывать график «поле vs коридор» прямо при клике.
Артефакты
data/processed/fields_anomaly.csv– сводная таблица 20 полей × 8 колонок (счётчики, score, ранги, флаг is_anomaly_top_k);data/processed/fields_anomaly_z_matrix.csv– z-score 361 день × 20 полей;-
data/preview/anomaly_heatmap.png– z-score heatmap, поля отсортированы по суммеz ; data/preview/anomaly_top_curves.png– top-3 кривые vs коридор p10-p90, p25-p75 и медиана;data/preview/anomaly_consistency.png– три pairwise scatter согласованности методов;data/preview/anomaly_map.html– folium-карта со спутниковой подложкой Esri, окраска по rank, top-3 выделены чёрной рамкой.