Это небольшой независимый практический проект, данный руководителем, когда я впервые пришел в компанию. Что касается прогнозирования временных рядов, сценарий относительно прост. Весь процесс реализован всего более чем в 100 строк кода, но в нем есть много подводных камней. процесс завершения, и я чувствую, что это необходимо.Поделись (ту) и поделись (сао). Полный код и примеры данных находятся у меня на github (только вставленная часть статьи):
GitHub.com/scarlet через в…
1. Предпосылки
На платформе компании есть разные API для внутренних и внешних вызовов.Эти API выполняют разные функции, такие как запрос учетных записей, выпуск выпусков, захват красных конвертов и т. д. Журнал будет фиксировать, сколько раз в минуту происходит обращение к апи, то есть апи будет иметь 1440 записей в сутки (1440 минут), а ежедневные данные будут подключаться для наблюдения, что чем-то похоже на смысл биржевых трендов . Я хочу предсказать ситуацию с доступом к трафику в день N + 1 с помощью исторических данных за предыдущие дни N. Прогнозируемое значение используется в качестве разумной ссылки для сравнения в реальном времени между новым днем и фактическим значением. Когда существует большое расхождение между фактическим трафиком и прогнозируемым значением, считается, что имеет место аномальный доступ, и срабатывает тревога.
2. Исследование данных
Я положил образец данных в папку данных, взгляните на размер и структуру данных.
data = pd.read_csv(filename)
print('size: ',data.shape)
print(data.head())
data.png
Размер данных:
Всего 10080 записей, то есть 10080 минут, семь дней данных.
Значение поля:
дата: время в минутах
count: количество обращений к API за эту минуту
Нарисуйте картинку, чтобы увидеть тенденцию последовательности: (Некоторые методы рисования и другие классы исследования размещены в файле test_stationarity.py, в том числе графики временных рядов, графики скользящих средних, если вам интересно, вы можете попробовать их сами).
def draw_ts(timeseries):
timeseries.plot()
plt.show()
data = pd.read_csv(path)
data = data.set_index('date')
data.index = pd.to_datetime(data.index)
ts = data['count']
draw_ts(ts)
последовательность.png
Глядя на эту плохую картинку, те точки, которые упали до 0, - это первые ямы, с которыми я столкнулся.Я начал делать это, как только получил эти данные. После долгого метания я обнаружил, что те баллы, которые упали до 0, были вызваны отсутствием данных, а студенты ETL автоматически заполняли нулями, а связь запаздывала (TДT).
Заполните пробел, заполните пропущенное значение средним значением до и после значений и посмотрите еще раз:
Последовательность заполнена пропущенными значениями.png
Установлено, что эти данные обладают следующими характеристиками, которые следует учитывать при построении модели и предварительной обработке данных:
1. Это периодический временной ряд, и значение регулярно колеблется ежедневно, API на рисунке более активен днем и вечером каждый день, и редко утром и ранним утром. Перед моделированием необходимо выполнить декомпозицию.
2. Моя вторая яма: данные сами по себе не гладкие, и есть много внезапных и внезапных падений, что не способствует прогнозированию.В конце концов, модель должна изучить нормальную последовательность, чтобы дать объективное суждение о неизвестных данных, иначе появится. Частые ложные срабатывания делали атмосферу очень неловкой (´Д`), так что пришлось ее сглаживать.
3. Это всего лишь диаграмма последовательности API, и есть большая разница в форме разных API, ведь выполняемые функции разные, и как адаптировать модель к разным формам API - это тоже проблема, которая необходимо учитывать.
3. Предварительная обработка
3.1 Разделите набор обучающих тестов
Данные за первые шесть дней используются для обучения, а седьмой день используется в качестве тестового набора.
class ModelDecomp(object):
def __init__(self, file, test_size=1440):
self.ts = self.read_data(file)
self.test_size = test_size
self.train_size = len(self.ts) - self.test_size
self.train = self.ts[:len(self.ts)-test_size]
self.test = self.ts[-test_size:]
3.2 Сглаживание обучающих данных
Для устранения заусенцев данных можно использовать метод скользящего среднего.Я не использовал его здесь, т.к. пытался найти, что для моих данных скользящее среднее не может сделать данные гладкими после обработки.Метод, который я использовал здесь, очень просто, но эффект все равно Неплохой: принять значение изменения между каждой точкой и предыдущей точкой как новую последовательность, сбрить здесь выбросы, то есть значения с запредельными изменениями, и заполнить их средним значением значение данных до и после, обратите внимание, что могут быть непрерывные большие изменения.
def _diff_smooth(self, ts):
dif = ts.diff().dropna() # 差分序列
td = dif.describe() # 描述性统计得到:min,25%,50%,75%,max值
high = td['75%'] + 1.5 * (td['75%'] - td['25%']) # 定义高点阈值,1.5倍四分位距之外
low = td['25%'] - 1.5 * (td['75%'] - td['25%']) # 定义低点阈值,同上
# 变化幅度超过阈值的点的索引
forbid_index = dif[(dif > high) | (dif < low)].index
i = 0
while i < len(forbid_index) - 1:
n = 1 # 发现连续多少个点变化幅度过大,大部分只有单个点
start = forbid_index[i] # 异常点的起始索引
while forbid_index[i+n] == start + timedelta(minutes=n):
n += 1
i += n - 1
end = forbid_index[i] # 异常点的结束索引
# 用前后值的中间值均匀填充
value = np.linspace(ts[start - timedelta(minutes=1)], ts[end + timedelta(minutes=1)], n)
ts[start: end] = value
i += 1
self.train = self._diff_smooth(self.train)
draw_ts(self.train)
Сглаженные тренировочные данные:
сглаженная тренировочная последовательность.png
3.3 Периодически разлагайте обучающие данные
Используя инструментарий statsmodels:
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(self.ts, freq=freq, two_sided=False)
# self.ts:时间序列,series类型;
# freq:周期,这里为1440分钟,即一天;
# two_sided:观察下图2、4行图,左边空了一段,如果设为True,则会出现左右两边都空出来的情况,False保证序列在最后的时间也有数据,方便预测。
self.trend = decomposition.trend
self.seasonal = decomposition.seasonal
self.residual = decomposition.resid
decomposition.plot()
plt.show()
взорванный вид.png
Первая строка наблюдения: исходные данные, вторая линия тренда: разложенная часть тренда, третья сезонная строка: периодическая часть, последний остаток: остаточная часть. Я использую декомпозицию аддитивной модели сезонного_разложения, то есть наблюдаемое = тренд + сезонный + остаток, а также есть модель умножения. При моделировании, только для части обучения и прогнозирования тренда, как обработать результаты прогнозирования тренда в разумный конечный результат? Разумеется, снова делается дополнение, о чем будет подробно написано позже.
4. Модель
4.1 Обучение
Часть декомпозированного тренда обучается только с моделью arima:
def trend_model(self, order):
self.trend.dropna(inplace=True)
train = self.trend[:len(self.trend)-self.test_size]
#arima的训练参数order =(p,d,q),具体意义查看官方文档,调参过程略。
self.trend_model = ARIMA(train, order).fit(disp=-1, method='css')
4.2 Прогноз
После прогнозирования данных тренда периодические данные добавляются в качестве окончательного результата прогнозирования, но, что более важно, мы хотим получить не конкретное значение, а разумный диапазон.Когда реальные данные превышают этот диапазон, будет подан сигнал тревоги. Интервал ошибки устанавливается из только что разложенных остаточных данных:
d = self.residual.describe()
delta = d['75%'] - d['25%']
self.low_error, self.high_error = (d['25%'] - 1 * delta, d['75%'] + 1 * delta)
Предскажите и завершите окончательный процесс сложения, чтобы получить прогнозируемое значение седьмого дня, то есть высокий и низкий доверительный интервал:
def predict_new(self):
'''
预测新数据
'''
#续接train,生成长度为n的时间索引,赋给预测序列
n = self.test_size
self.pred_time_index= pd.date_range(start=self.train.index[-1], periods=n+1, freq='1min')[1:]
self.trend_pred= self.trend_model.forecast(n)[0]
self.add_season()
def add_season(self):
'''
为预测出的趋势数据添加周期数据和残差数据
'''
self.train_season = self.seasonal[:self.train_size]
values = []
low_conf_values = []
high_conf_values = []
for i, t in enumerate(self.pred_time_index):
trend_part = self.trend_pred[i]
# 相同时间点的周期数据均值
season_part = self.train_season[
self.train_season.index.time == t.time()
].mean()
# 趋势 + 周期 + 误差界限
predict = trend_part + season_part
low_bound = trend_part + season_part + self.low_error
high_bound = trend_part + season_part + self.high_error
values.append(predict)
low_conf_values.append(low_bound)
high_conf_values.append(high_bound)
# 得到预测值,误差上界和下界
self.final_pred = pd.Series(values, index=self.pred_time_index, name='predict')
self.low_conf = pd.Series(low_conf_values, index=self.pred_time_index, name='low_conf')
self.high_conf = pd.Series(high_conf_values, index=self.pred_time_index, name='high_conf')
4.3 Оценка:
Сделайте прогноз на седьмой день, индекс оценки - это среднеквадратическая ошибка rmse, разница между сравнением чертежей и истинным значением:
md = ModelDecomp(file=filename, test_size=1440)
md.decomp(freq=1440)
md.trend_model(order=(1, 1, 3)) # arima模型的参数order
md.predict_new()
pred = md.final_pred
test = md.test
plt.subplot(211)
plt.plot(md.ts) # 平滑过的训练数据加未做处理的测试数据
plt.title(filename.split('.')[0])
plt.subplot(212)
pred.plot(color='blue', label='Predict') # 预测值
test.plot(color='red', label='Original') # 真实值
md.low_conf.plot(color='grey', label='low') # 低置信区间
md.high_conf.plot(color='grey', label='high') # 高置信区间
plt.legend(loc='best')
plt.title('RMSE: %.4f' % np.sqrt(sum((pred.values - test.values) ** 2) / test.size))
plt.tight_layout()
plt.show()
результат предсказания.png
Видно, что среднеквадратическая ошибка составляет 462,8, что по-прежнему составляет порядка нескольких тысяч по сравнению с исходными данными. Две точки мутации в тестовых данных также превышают доверительный интервал, и о них можно точно сообщить.
5. Заключение
Как упоминалось выше, существуют огромные различия между различными формами API. В этой статье показана только одна. В этом проекте я также столкнулся с другими формами последовательностей. Некоторые из них имеют явные восходящие или нисходящие тенденции, некоторые начинаются относительно ровно, а затем начинают растут..., но все они являются типичными периодическими временными рядами. Его основная идея очень проста: сделать хорошую работу по декомпозиции, восстановить результаты прогноза и установить доверительный интервал. Конкретная операция может быть скорректирована в соответствии с конкретным бизнес-логика Всем удачного моделирования :-D.