120. Обнаружение аномалий: вибрация и предиктивное обслуживание
Разделы 116–119 учили модель различать известные состояния: норму, дисбаланс, подшипник. На заводе так бывает редко: записей с дефектами почти нет, а дефект, который случится, может оказаться незнакомым. Честнее задать другой вопрос — «похоже ли это на норму?» — и учить модель только на исправной работе. Раздел сравнивает четыре таких способа на общей модели нормы и один — на своей норме каждого прибора, выученной сразу после установки, и показывает, почему второй подход здесь выигрывает с огромным запасом.
Данные — генератор раздела 118, признаки — раздела 117, опыты
в образе stm-ml:1.
Как это называется в документации
Оригинал Перевод anomaly (novelty) detection обнаружение аномалий (нового) one-class learning обучение на одном классе — только норме predictive maintenance обслуживание по состоянию, предиктивное anomaly score оценка аномальности z-score z-оценка: отклонение в долях разброса Mahalanobis distance расстояние Махаланобиса: с учётом связи признаков k-means k-средних: норма как несколько центров autoencoder, reconstruction error автокодировщик, ошибка восстановления baseline своя норма прибора Welford's algorithm алгоритм Уэлфорда: среднее и разброс на лету false alarm rate доля ложных тревог detection rate доля найденных дефектов threshold, percentile порог, процентиль concept drift дрейф: норма меняется со временем
120.1 Норма вместо классов
Модель нормы отвечает не названием дефекта, а числом — оценкой аномальности: чем дальше окно от всего, что модель видела, тем больше. Решение — сравнение с порогом. Порог выбирают по исправным окнам: например, так, чтобы 1 % из них оказались выше. Это и есть ожидаемая доля ложных тревог; всё, что выше порога на работающем приборе, — повод посмотреть на мотор.
Опыт устроен как жизнь прибора. Модель нормы учится на 1200 окнах 30 исправных моторов. Проверка — 60 приборов, каждый на своём моторе: первые 20 окон мотор исправен, потом у 20 приборов развивается дисбаланс, у 20 — дефект подшипника, а 20 так и работают исправно. Всего 400 окон с дисбалансом, 400 с подшипником и 1200 исправных.
120.2 z-оценка
Самое простое: для каждого признака среднее и разброс по норме, оценка — сумма квадратов отклонений в долях разброса. Четыре признака раздела 117 — восемь чисел модели. Способ предполагает, что признаки независимы и распределены колоколом; для вибрации это верно лишь отчасти, но как первая линия он работает везде.
120.3 Махаланобис, k-средних, автокодировщик
Расстояние Махаланобиса учитывает связь признаков: у исправного мотора СКЗ и пик-фактор меняются согласованно, и окно, где они разошлись, подозрительнее, чем сумма отдельных отклонений. Модель — среднее и обратная матрица ковариации, 4 + 16 чисел.
k-средних описывает норму несколькими центрами — когда у исправного мотора есть несколько режимов (холостой ход, нагрузка). Оценка — квадрат расстояния до ближайшего центра.
Автокодировщик — нейросеть, которая учится восстанавливать свой вход через узкое горло. Здесь вход — весь спектр, 64 бина, горло — 8 нейронов, 1096 параметров:
ae = MLPRegressor(hidden_layer_sizes=(8,), max_iter=3000, random_state=120).fit((XS - smu) / ssd, (XS - smu) / ssd)
Норму он восстанавливает хорошо, а незнакомый спектр — плохо; оценка — средний квадрат ошибки восстановления.
120.4 Своя норма прибора
Главная помеха раздела 118 — у каждого мотора своя сила вибрации, а у каждого крепления своя чувствительность. Общая модель нормы вынуждена считать нормой всё это разнообразие, и дисбаланс одного мотора оказывается внутри нормы другого. Выход: пусть каждый прибор выучит норму своего мотора сам — первые 10 окон (2,5 секунды) после установки, пока мотор исправен:
m, s = F[:10].mean(axis=0), F[:10].std(axis=0) + 1e-6
return (((F[10:] - m) / s) ** 2).sum(axis=1)
Та же z-оценка, только среднее и разброс — свои. Порог по-прежнему выбирается один на все приборы — по исправным окнам обучающих моторов, каждый со своей базой.
120.5 Что вышло
Ложные тревоги на исправных окнах / найдено окон с дисбалансом из 400:
| Способ | z-оценка | Махаланобис | k-средних, 4 центра | автокодировщик 64 → 8 → 64 | своя норма |
|---|---|---|---|---|---|
| Порог 5 % | 67 / 106 | 85 / 140 | 138 / 113 | 66 / 74 | 49 / 400 |
| Порог 1 % | 49 / 45 | 43 / 106 | 60 / 87 | 14 / 37 | 9 / 400 |
| Порог 0,1 % | 31 / 40 | 16 / 40 | 26 / 40 | 0 / 8 | 3 / 400 |
Ложные тревоги считаются из 1200 исправных окон у общих моделей и из 600 у своей нормы (первые 10 окон каждого прибора ушли на базу).
Подшипник найден во всех 400 окнах всеми способами при всех порогах: новая частота в спектре выдаёт себя сама. Трудный случай — дисбаланс, то есть тот же спектр, только сильнее. Общие модели находят от 8 до 140 окон из 400: усилившаяся вибрация одного мотора для них всё ещё укладывается в норму всех моторов. Своя норма находит все 400 при любом пороге. На рисунке — один такой прибор: оценка общей модели после появления дисбаланса поднялась до 9–14 и не дошла до порога, своя — до сотен (порог 25,0 против 14,8 у общей).
И ещё одно: общие модели дают больше ложных тревог, чем обещал порог, — у z-оценки при пороге 1 % это 49 окон из 1200, вчетверо больше. Порог выбран по обучающим моторам, а проверочные — другие. Своя норма и здесь ближе к обещанию: 9 из 600.
120.6 Своя норма на приборе: алгоритм Уэлфорда
Хранить 10 окон, чтобы потом посчитать среднее и разброс, не нужно: алгоритм Уэлфорда обновляет их по одному окну, без потери точности, которой страдает формула «среднее квадратов минус квадрат среднего»:
void base_add(baseline_t *b, const float f[N_FEAT])
{
int i;
b->n++;
for (i = 0; i < N_FEAT; i++) {
float d = f[i] - b->mean[i];
b->mean[i] += d / (float)b->n;
b->m2[i] += d * (f[i] - b->mean[i]);
}
}
Оценка окна — та же сумма квадратов:
float sd = sqrtf(b->m2[i] / (float)b->n) + 1e-6f;
float z = (f[i] - b->mean[i]) / sd;
s += z * z;
Прогон на окнах двух приборов: тревоги C и numpy совпали во всех 60 окнах, оценки расходятся на десятки миллионных — float против double. armclang: три функции — 390 байт на F407 и 512 на F103 (без FPU, не считая библиотеки плавающей точки); состояние — 36 байт: счётчик, четыре средних и четыре суммы.
120.7 Когда учить норму
База учится после установки и после каждого ремонта — по команде наладчика, а не сама собой. Если прибор ставят на уже больной мотор, он выучит болезнь как норму; от этого страхует общая модель, которая работает рядом и ловит то, что сильно выделяется у всех моторов (подшипник в опыте). Два уровня — общий и свой — обычная схема промышленных систем.
Норма медленно меняется: износ, температура, сезон. Если базу подтягивать к новым окнам, прибор привыкнет и к медленно растущему дефекту. Поэтому базу не обновляют на ходу, а переучивают по команде и хранят историю оценок: плавный рост оценки за недели — самый ценный сигнал предиктивного обслуживания.
120.8 Порог и тревога
Порог по процентилю — только начало. На приборе тревогу поднимают не по одному окну, а по нескольким подряд: при 1 % ложных на окно и правиле «3 окна из 5» ложная тревога случается примерно в тысячу раз реже (если окна независимы), а настоящий дефект держится в каждом окне и проходит правило с задержкой всего в три окна — столько аномальных окон правилу нужно. Кроме тревоги полезно отдавать наверх саму оценку: по ней видно, насколько далеко ушёл мотор, а не только «да или нет».
120.9 Какой способ выбрать
- Своя норма на z-оценке — первым делом, если прибор стоит на одном моторе: 36 байт, никакого обучения на компьютере, лучший результат.
- Махаланобис — когда признаки заметно связаны; 20 чисел, та же арифметика.
- k-средних — когда у нормы несколько режимов работы.
- Автокодировщик — когда признаков нет и работать приходится со спектром; тысяча параметров, квантуется как в разделе 119.
120.10 Грабли
Прибор выучил дефект как норму. База снята на уже больном моторе. Учить базу после ремонта и держать рядом общую модель.
Ложных тревог больше, чем обещал порог. Порог выбран на других моторах. Своя норма или порог по первым часам работы прибора.
Тревога от одного окна. Удар, переходный процесс, пуск. Правило «несколько окон подряд».
Прибор привык к растущему дефекту. База подтягивается к новым окнам. Переучивать только по команде, хранить историю оценок.
Разброс признака почти нулевой — оценка улетает. Признак в базе не менялся. Минимальный разброс (здесь 1e−6) или нижняя граница по шуму датчика.
120.11 Итоги
- Модель нормы отвечает оценкой, решение — порог по исправным окнам.
- Новая частота (подшипник) видна любому способу: 400 из 400.
- Усиление той же вибрации (дисбаланс) общая норма почти не видит — от 8 до 140 из 400; своя норма прибора — 400 из 400.
- Своя норма — алгоритм Уэлфорда: 36 байт состояния, 390 байт кода на F407.
- База — по команде после установки и ремонта; ценнее всего плавный рост оценки за недели.
Полные исходники
anom120.c — своя норма прибора: среднее и разброс по Уэлфорду и z-оценка каждого окна.
/* Раздел 120: своя норма прибора — среднее и разброс признаков по алгоритму Уэлфорда,
* затем z-оценка каждого окна. С -DML_HOST main() сверяет оценки и тревоги с numpy. */
#include <math.h>
#include "model120.h"
typedef struct {
unsigned n;
float mean[N_FEAT], m2[N_FEAT];
} baseline_t;
void base_reset(baseline_t *b)
{
int i;
b->n = 0;
for (i = 0; i < N_FEAT; i++)
b->mean[i] = b->m2[i] = 0.0f;
}
/* Одно окно нормы: среднее и сумма квадратов отклонений обновляются без хранения окон. */
void base_add(baseline_t *b, const float f[N_FEAT])
{
int i;
b->n++;
for (i = 0; i < N_FEAT; i++) {
float d = f[i] - b->mean[i];
b->mean[i] += d / (float)b->n;
b->m2[i] += d * (f[i] - b->mean[i]);
}
}
/* Оценка окна: сумма квадратов отклонений в долях разброса нормы. */
float base_score(const baseline_t *b, const float f[N_FEAT])
{
float s = 0.0f;
int i;
for (i = 0; i < N_FEAT; i++) {
float sd = sqrtf(b->m2[i] / (float)b->n) + 1e-6f;
float z = (f[i] - b->mean[i]) / sd;
s += z * z;
}
return s;
}
#ifdef ML_HOST
#include <stdio.h>
int main(void)
{
int d, w, k = 0, alarms_same = 0, n = 0, alarms = 0;
float worst = 0.0f;
for (d = 0; d < N_DEV; d++) {
baseline_t b;
base_reset(&b);
for (w = 0; w < N_BASE; w++)
base_add(&b, &WIN[(d * N_WIN + w) * N_FEAT]);
for (w = N_BASE; w < N_WIN; w++, k++) {
float s = base_score(&b, &WIN[(d * N_WIN + w) * N_FEAT]);
float rel = fabsf(s - REF[k]) / (REF[k] + 1.0f);
if (rel > worst)
worst = rel;
alarms_same += (s > THRESH) == (REF[k] > THRESH);
alarms += s > THRESH;
n++;
}
}
printf("alarms_same=%d,%d\nalarms=%d\nworst_ppm=%d\n", alarms_same, n, alarms, (int)(worst * 1e6f + 0.5f));
return 0;
}
#else
volatile float g_feat[N_FEAT];
volatile int g_alarm;
void SystemInit(void) {}
int main(void)
{
baseline_t b;
float f[N_FEAT];
int i, w;
base_reset(&b);
for (w = 0;; w++) {
for (i = 0; i < N_FEAT; i++)
f[i] = g_feat[i];
if (w < N_BASE)
base_add(&b, f);
else
g_alarm = base_score(&b, f) > THRESH;
}
}
#endif
Дальше: раздел 121 «Классика против ML» — фильтр Калмана и оценка заряда батареи: когда физическая модель лучше обученной.