Модель машинного обучения на временных рядях для оценки качества связи

Необходимо сделать модель машинного обучения на временных рядях для оценки качества связи.

Датасет собирался на протяжении 14 дней (15.01.2016-28.01.2016) и содержит в себе 7026706 строк и 6 колонок: Dest. IP, Last hop IP, LQ, NLQ, Cost. В данный датасет я добавил колонку с датой для всех строк и убрал столбцы Dest. IP, Last hop IP, NLQ, Cost. В итоге у меня получился след. датасет.

введите сюда описание изображения

Дальше я построил график зависимости значения LQ от даты.

введите сюда описание изображения

И сделал прогноз по последнему наблюдению (24 часа): 0.9105833333333333

Код на данный момент:

import pandas as pd
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from glob import glob
from datetime import datetime, time
from pandas.plotting import autocorrelation_plot
from statsmodels.graphics.tsaplots import plot_acf

#Формирование датасета
files = glob("/content/newDataset/*.tsv")
dataset = pd.concat([pd.read_csv(f, sep="\t", skiprows=1) for f in files], 
           ignore_index=True)

n_samples = 7026706 
dt_from = pd.to_datetime("2016-01-15 00:00:00.000000")
dt_to = pd.to_datetime("2016-01-28 23:59:59.999999")
dataset["DateTime"] = pd.date_range(dt_from, dt_to, periods=n_samples)
dataset['LQ'] = dataset.LQ.astype('float64')
#df = pd.DataFrame(dataset)
del dataset['Dest. IP']
del dataset['Last hop IP']
del dataset['NLQ']
del dataset['Cost']
#dataset.drop(['Dest. IP', 'Last hop IP', 'NLQ', 'Cost'], axis=1)
print(dataset)

#График
def plot_series(time, series, format="-", start=0, end=None, label=None):
    plt.plot(time[start:end], series[start:end], format, label=label)
    plt.xlabel("DateTime")
    plt.ylabel("LQ")
    if label:
        plt.legend(fontsize=14)
    plt.grid(True)

def trend(time, slope=0):
    return slope * time

time = dataset['DateTime']
series = dataset['LQ']
plt.figure(figsize=(30, 6))
#plot_series(time, series)
#plt.show()
dataset.set_index("DateTime")["LQ"].rolling(window=600000).mean().plot()

#прогноз по последнему наблюдению
def moving_average(series, n):
    return np.average(series[-n:])

moving_average(dataset.LQ, 24)

Поскольку не силён в машинном обучении, то:

  1. Надо ли мне делать сглаживание временного ряда?
  2. Какую модель можно использовать для данной задачи?
  3. Правильно ли я понимаю, что выбросов выбросов нет и ряд является стационарным?

Файлы проекта.

График зависимости LQ от даты/времени и графики корреляции FBProphet


Ответы (4 шт):

Автор решения: passant
  1. Ваш исходный ряд имеет весьма специфический вид - фрагмент его на рисунке внизу. По нему качество связи можно оценивать по разным показателям. Один из них - изменение среднего значения за период. (Например - за секунду, минуту, день) Непонятно зачем вы делали скользящее среднее, если для указанной операции вам надо было просто усреднять по периоду.

  2. Полученный таким образом ряд необходимо проверить на стационарность например, по критерию Dickey-Fuller или KPSS. Оба есть в пакете statsmodels. Скорее всего ваш ряд окажется нестационарным.

  3. Наличие сезонности тоже определяется не на глаз, а - например - через анализ автокорреляций.

  4. Далее надо анализировать ваш ряд и строить прогнозы. Я бы прошелся по классическому списку (с поправкой на результаты пунктов 2 и 3) - Локальная регрессия, Экспоненциальное среднее, Хольта, Хольта-Винтерса, ARMA, ARIMA, SARIMA.

  5. После чего можно делать и анализ.

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

→ Ссылка
Автор решения: MaxU

Как уже сказали в соседнем ответе - вместо применения скользящего среднего можно сделать обычный downsampling, т.е. сгруппировать временной ряд, например, по N-минут и взять среднее для группы. Это значительно уменьшит вашу выборку и ускорит обучение модели.

Ваш ряд имеет явные признаки дневной сезонности - смотрите второй график. Чтобы говорить о других видах сезонности - нужно иметь данные за более длинный период времени. Для более менее толковых прогнозов нужно иметь данные хотя бы за один год.

Практический пример (используем FBProphet):

import numpy as np
import pandas as pd
from pathlib import Path
from fbprophet import Prophet
import matplotlib.pyplot as plt
import matplotlib

# %matplotlib

### читаем данные
data_dir = Path(r"D:\download\data")

df = (pd
      .concat([pd.read_csv(f, sep="\t", skiprows=1, usecols=["LQ"])
               for f in data_dir.glob("*.tsv")], 
              ignore_index=True)
      .rename(columns={"LQ": "y"}))
dt_from = pd.to_datetime("2016-01-15 00:00:00.000000")
dt_to = pd.to_datetime("2016-01-28 23:59:59.999999")
df.insert(0, "ds", pd.date_range(dt_from, dt_to, periods=len(df)))

### усредняем данные до минуты (resampling -> 1 min. frequency)
data = df.set_index("ds").resample("1T").mean().reset_index()

### разбиваем данные не обучающую и тестовые выборки
train_test_split = int(0.7 * len(data))
train, test = np.split(data, [train_test_split])


### строим модель
model = Prophet(daily_seasonality=True)
model.fit(train)

forecast = model.predict(test)

ax = data.set_index("ds")["y"].plot(figsize=(20, 8))
forecast.set_index("ds")["yhat"].plot(ax=ax)
ax.legend(["Ground truth", "Predicted"])

введите сюда описание изображения

### разложение временного ряда на компоненты и визуализация
# c:\temp\forecast_comps.png
fig_comps = model.plot_components(forecast)

введите сюда описание изображения

PS если временной ряд будет содержать больше данных (например 1+ лет), то стоит добавить недельную и месячную сезонности и информацию о праздниках.

→ Ссылка
Автор решения: dom t

Для предсказания цены:

# создем модель LSTM с Bidirectional слоем, будем использовать оптимизатор adam
model = Sequential()
# model.add(BatchNormalization()) 
model.add(
    Bidirectional(LSTM(50, activation='relu', input_shape=(X_train.shape[1],
    X_train.shape[2]))))
model.add(Dropout(0.001))
model.add(Dense(1))
model.compile(optimizer='adam', loss='mse')
# сохраняем процесс обучения модели для того чтобы выбрать лучшую эпоху
callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath='mymodel.keras',
        # Путь по которому нужно сохранить модель
        # Два параметра ниже значат что мы перезапишем
        # текущий чекпоинт в том и только в том случае, когда
        # улучится значение `val_loss`.
        save_best_only=True,
        monitor='val_loss',
        verbose=100)
]
# обучаем и записываем историю модели для дальнейшего построения графиков
history = model.fit(
    X_train, y_train, epochs=10, batch_size=64,
    callbacks=callbacks, validation_data=(X_val, y_val))
model2 = keras.models.load_model('mymodel.keras')
y_pred =  model2.predict(test_df2)
→ Ссылка
Автор решения: Aribama
new_df1 = df.iloc[0:10000]
adf_test = adfuller(new_df1['CLOSE'].dropna())
print(f'ADF Statistic: {adf_test[0]}')
print(f'p-value: {adf_test[1]}')

if adf_test[1] < 0.05:
    print("Ряд стационарен")
else:
    print("Ряд НЕ стационарен, требуется преобразование (дифференцирование)")

Если требуется дифференцирование:

new_df2['Close_diff'] = new_df2['CLOSE'] - new_df2['CLOSE'].shift(1)

Затем еще раз проверить на стационарность

→ Ссылка