Использование сети кодировщика-декодера LSTM для обнаружения аномалий

Что такое обнаружение аномалий?

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

Что такое автоэнкодер?

Автоэнкодер берет более объемные входные данные и кодирует их в небольшие векторы. Давайте попробуем лучше понять это с помощью графика.

Например, если вы вводите изображение размером 512 × 512 в автоэнкодер, то входное изображение будет постепенно уменьшаться, и вся информация, содержащаяся в изображении, будет захвачена как скрытый вектор. Эти векторы захватывают всю информацию, содержащуюся во входном изображении. Когда мы декодируем его обратно, важно обучение, потому что вы тренируетесь на входном изображении, чтобы получить то же самое изображение обратно, и мы используем обученные веса для обратного декодирования этого изображения. Причина, по которой мы это делаем, состоит в том, чтобы свести к минимуму потери при реконструкции.

В предыдущей статье Долговременная кратковременная память (LSTM) для анализа настроений я объяснил архитектуру LSTM. Я, однако, предоставлю объяснение, когда это будет необходимо или возможно.

Структура автоэнкодера LSTM

model = Sequential()
model.add(LSTM(128, activation='relu', input_shape=(trainX.shape[1], trainX.shape[2]), return_sequences=True))
model.add(LSTM(64, activation='relu' return_sequences=False))
model.add(RepeatVector(trainX.shape[1]))
model.add(LSTM(64, activation='relu' return_sequences=False))
model.add(LSTM(128, activation='relu' return_sequences=False))
model.add(TimeDistributed(Dense(trainX.shape[2])))
model.compile(optimizer='adam', loss='mse')

Как видно из приведенной выше структуры кода, он находится в зеркальном состоянии. Первая половина включается в кодер, а вторая половина - в декодер. В примере, который мы собираемся пройти, я разделил данные на 30 временных шагов и 1 функцию. Причина, по которой я выбрал только одну функцию, заключается в том, что мы собираемся обнаружить аномалию в биржевых данных. Поэтому нам будет нужна только цена закрытия каждого дня.

Теперь давайте рассмотрим структуру модели с входной функцией. Сначала он переходит в первый слой, который выводит форму 30, потому что я определил количество временных шагов как 30 и 128 функций. Во втором слое мы определили состояние возврата как False, поэтому форма будет представлять собой только один вектор размером 64. Проще говоря, мы закодировали размер изображения входного изображения в векторный размер 1 × 64. . На третьем уровне мы определили RepeatVector, который является мостом между кодировщиком и декодером. Следующие два слоя определяются для декодера, и это как бы противоположно слоям кодировщика. Наконец, слой TimeDistributed, который внутри создает вектор, который имеет равное количество объектов из предыдущего слоя.

Теперь, когда я объяснил структуру модели, давайте перейдем к коду и посмотрим, как мы можем использовать этот автоэнкодер LSTM для обнаружения аномалий.

В этом руководстве я использовал открытый набор данных, который вы можете скачать здесь. (Вы можете настроить период времени по своему выбору)

Требуемые зависимости

Давайте импортируем все библиотеки глубокого обучения, которые нам понадобятся на протяжении всей реализации.

import numpy as np
from keras.models import Sequential, Model
from keras.layers import LSTM, Input, Dropout, Dense, RepeatVector, TimeDistributed

import pandas as pd
from matplotlib import pyplot as plt
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import seaborn as sns

Вышеупомянутые библиотеки состоят из базовых инструментов анализа данных и интеллектуального анализа данных, таких как NumPy, Pandas, Seaborn и matplotlib, а также библиотеки Keras для построения модели LSTM.

Чтение набора данных

dataset = pd.read_csv('dataset.csv', parse_dates=['date'], index_col='date')
dataset.head()
--Output--

date        close
1986-01-02  209.59
1986-01-03  210.88
1986-01-06  210.65
1986-01-07  213.80
1986-01-08  207.97

Прочитав набор данных, я создал фрейм данных, который включает только дату и стоимость закрытия (мы сосредоточены на стоимости закрытия, потому что это конец стоимости акций). Когда вы прочитаете набор данных, вы заметите, что дата находится в форме объекта, и я преобразовал ее в формат даты и времени, передав столбец даты параметру parse_dates. Давайте посмотрим, как выглядит акция, изобразив ее на графике.

Приведенный выше график содержит визуальное представление цен на акции с 1970 по 2020 год.

Разделение и нормализация набора данных

Я разделил набор данных на 95 % в обучающем наборе и 5 % в тестовом наборе.

train_size = int(len(data) * 0.95)
test_size = len(data) - train_size
train, test = data.iloc[0:train_size], data.iloc[train_size:len(data)]

Как видно из приведенного выше кода, я разделил набор данных на наборы данных для обучения и тестирования.

scaler = StandardScaler()
scaler = scaler.fit(train[['close']])

train['close'] = scaler.transform(train[['close']])
test['close'] = scaler.transform(test[['close']])

Я использовал StandardScaler для нормализации набора данных. Который стандартизирует функцию, вычитая среднее значение, а затем масштабируя до единичной дисперсии.

sequence = 30  
def get_sequences(x, y, sequence=1):    
    x_val = []    
    y_val = []    
 
    for i in range(len(x)-sequence):               
        x_val.append(x.iloc[i:(i+sequence)].values)
        y_val.append(y.iloc[i+sequence]) 
    return np.array(x_val), np.array(y_val) 

trainX, trainY = get_sequences(train[['close']], train['close'], sequence)
testX, testY = get_sequences(test[['close']], test['close'], sequence)

Как видно из приведенного выше фрагмента кода, я определил функцию, которая разбивает последовательность временных рядов на разные входные последовательности. Размер последовательности влияет на то, как мы обучаем модель для повышения точности.

Присоединяйтесь к 16 000 своих коллег на Еженедельном глубоком обучении, чтобы узнавать о последних продуктах, приобретениях, технологиях, подробностях и многом другом.

Создание модели и определение порогового значения

Я объяснил структуру LSTM Autoencoder в начале этой статьи. После экспериментов с различными структурами моделей я решил использовать следующую структуру модели:

model = Sequential()
model.add(LSTM(128, input_shape=(trainX.shape[1], trainX.shape[2])))
model.add(Dropout(rate=0.2))
model.add(RepeatVector(trainX.shape[1]))
model.add(LSTM(128, return_sequences=True))
model.add(Dropout(rate=0.2))
model.add(TimeDistributed(Dense(trainX.shape[2])))
model.compile(optimizer='adam', loss='mae')
model.summary()
--Output--
Model: "sequential" _________________________________________________________________  Layer (type)                Output Shape              Param #    =================================================================  lstm (LSTM)                 (None, 64)                16896                                                                         dropout (Dropout)           (None, 64)                0                                                                             repeat_vector (RepeatVector)  (None, 30, 64)           0                                                                                                                                             lstm_1 (LSTM)               (None, 30, 64)            33024                                                                         dropout_1 (Dropout)         (None, 30, 64)            0                                                                             time_distributed (TimeDistr  (None, 30, 1)            65          ibuted)                                                                                                                            ================================================================= Total params: 49,985
Trainable params: 49,985 
Non-trainable params: 0 _________________________________________________________________

Кроме того, я добавил слой отсева, который выполняет регуляризацию для преодоления переобучения.

history = model.fit(trainX, trainY, epochs=10, batch_size=32, validation_split=0.1, shuffle=False)

Поскольку LSTM работает медленно, обучение модели может занять до 10–15 минут. Давайте посмотрим, как выглядит убыток, построив его на графике:

plt.plot(history.history['loss'], label='train')
plt.plot(history.history['val_loss'], label='test')
plt.legend();

Вы можете определить аномалию как любую точку, где ошибка реконструкции велика. Мы можем определить значение и назвать значения за пределами определенных значений аномалиями. Рассчитав среднюю абсолютную ошибку, мы можем определить предел указанного значения.

X_train_pred = model.predict(trainX)
train_mae_loss = np.mean(np.abs(X_train_pred - trainX), axis=1)
sns.distplot(train_mae_loss, bins=50, kde=True);

Как видно из приведенного выше графика, большая часть средней абсолютной ошибки находится в диапазоне 0–0,2, но достигает 0,8. Таким образом, мы можем определить допустимое пороговое значение где-то от 0,6 до 0,8, и я выбрал пороговое значение равным 0,65.

X_test_pred = model.predict(testX)
test_mae_loss = np.mean(np.abs(X_test_pred - testX), axis=1)

Я также рассчитал среднюю абсолютную ошибку для тестового набора, чтобы убедиться, что выбранное пороговое значение подходит.

Захват аномалий

Чтобы найти аномалии, нам нужно вернуться к нашему исходному набору данных и проверить, находятся ли содержащиеся данные выше или ниже определенного порога.

thres_value= 0.65
anomaly = pd.DataFrame(test[sequence:].index)
anomaly['loss'] = test_mae_loss
anomaly['threshold'] = thres_value
anomaly['anomaly'] = anomaly.loss > anomaly.threshold
anomaly['close'] = test[sequence:].close

Во-первых, я создал кадр данных для тестовой последовательности и зафиксировал всю среднюю абсолютную ошибку как ее часть. Во-вторых, я включил пороговое значение в качестве столбца в кадр данных и сравнил его с существующими близкими значениями в наборе данных и добавил логическое значение результата в качестве другого столбца. После настройки фрейма данных мы можем построить его, чтобы увидеть, как он выглядит.

plt.plot(anomaly.index, anomaly.loss, label='loss')
plt.plot(anomaly.index, anomaly.threshold, label='threshold')
plt.xticks(rotation=25)
plt.legend();

На приведенном выше графике вы можете увидеть график MAE и даты. Поскольку мы использовали тестовый набор данных, вы можете видеть, что он начинается примерно в 2017 году. С 2017 по 2018 год лишь несколько точек данных превышают определенное пороговое значение.

Давайте извлечем аномалии для фактических данных. Поскольку мы создали столбец с именем аномалии с логическими значениями, мы можем извлечь его, выбрав «Истинные» логические значения.

actual_anomalies = anomaly[anomaly.anomaly == True]
actual_anomalies.head()
--Output--
date        loss      threshold anomaly close
2018-02-06  0.723123  0.65      True    3.193456
2018-02-07  0.744779  0.65      True    3.168136
2018-02-08  0.754374  0.65      True    2.979068
2018-02-09  0.802155  0.65      True    3.051476
2018-02-12  0.811910  0.65      True    3.119939

Как видно из приведенного выше вывода, выявлено только 5 аномалий. Нанесем это на график.

plt.plot(test[sequence:].index, scaler.inverse_transform(test[sequence:].close), label='close price');
sns.scatterplot(actual_anomalies.index, scaler.inverse_transform(actual_anomalies.close), color=sns.color_palette()[3], s=52, label='anomaly')
plt.xticks(rotation=25)
plt.legend();

Как видно из приведенного выше графика, аномалии, обнаруженные в исходных данных, отмечены красными точками. Вы можете дополнительно определить, почему это произошло, обратившись к соответствующим датам в исходном наборе данных.

Исходный код:

Заключение

Это все, что касается этой статьи об обнаружении аномалий с использованием автоэнкодера LSTM. Вы можете использовать методы тонкой настройки, чтобы повысить точность модели и получить более точную информацию.

Примечание редактора. Heartbeat — это интернет-издание и сообщество, созданное участниками и посвященное предоставлению лучших образовательных ресурсов для специалистов по науке о данных, машинному обучению и глубокому обучению. Мы стремимся поддерживать и вдохновлять разработчиков и инженеров из всех слоев общества.

Независимая от редакции, Heartbeat спонсируется и публикуется Comet, платформой MLOps, которая позволяет специалистам по данным и командам машинного обучения отслеживать, сравнивать, объяснять и оптимизировать свои эксперименты. Мы платим нашим авторам и не продаем рекламу.

Если вы хотите внести свой вклад, перейдите к нашему призыву к участию. Вы также можете подписаться на получение наших еженедельных информационных бюллетеней (Еженедельник глубокого обучения и Информационный бюллетень Comet), присоединиться к нам в Slack и следить за Comet в Twitter и LinkedIn, чтобы получать ресурсы, события и многое другое, что поможет вам быстрее создавать лучшие модели машинного обучения.