В предыдущем посте мы обсудили несколько способов прогнозирования финансовых временных рядов: как нормализовать данные, делать прогноз в форме реального значения или двоичной переменной и как бороться с переобучением на сильно зашумленных данных. Но что мы пропустили (намеренно), так это то, что в нашем файле .csv с ценами содержится гораздо больше данных, которые мы можем использовать. В последнем посте использовались только цены закрытия с некоторой трансформацией, но что может произойти, если мы будем рассматривать также максимальные, минимальные, цены открытия и объем каждого исторического дня? Это приводит нас к работе с многомерными, например многомерный временной ряд, где на каждой временной отметке у нас есть более одной переменной - в нашем случае мы будем работать со всем кортежем OHLCV.
Другие сообщения:
- Простое прогнозирование временных рядов (и сделанные ошибки)
- Корректное прогнозирование одномерных временных рядов + бэктестинг
- Многомерное прогнозирование временных рядов
- Прогнозирование волатильности и нестандартные убытки
- Многозадачное и мультимодальное обучение
- Оптимизация гиперпараметров
- Улучшение классических стратегий с помощью нейронных сетей
- Вероятностное программирование и пиропрогнозы
В этой статье мы увидим, как предварительно обрабатывать многомерные временные ряды, в частности, что делать с каждым измерением, как определять и обучать нейронную сеть на таких данных, и сравним результаты с тем, что было в предыдущем посте.
Как всегда, можно сразу перейти к коду.
Подготовка данных
Чтобы лучше понять, что такое многомерный временной ряд, давайте вспомним, как выглядят изображения, которые на самом деле также имеют не только два измерения (высоту и ширину), но и «глубину», представляющую цветовые каналы:
В случае временных рядов наше изображение просто 1D (график, который мы обычно видим на графике), а роль каналов играют разные значения - цены открытия, максимумы, минимумы, цены закрытия и объем операций. Вы также можете подумать об этом с другой точки зрения - на любой временной отметке наш временной ряд представлен не одним значением, а вектором (цены открытия, максимума, минимума, цены закрытия и объем каждого дня), а метафорой с images более полезен, чтобы понять, почему мы будем применять сверточные нейронные сети к этой проблеме сегодня.
Один из наиболее важных моментов, связанных с многомерными временными рядами - измерения могут поступать из разных источников, иметь разную природу, могут быть полностью некоррелированными и иметь разное распределение, поэтому мы должны нормализовать их независимо! Воспользуемся уродливым, но более-менее адекватным трюком из прошлого поста:
Нам не нужно предсказывать какое-то точное значение, поэтому ожидаемое значение и дисперсия в будущем для нас не очень интересны - нам просто нужно спрогнозировать движение вверх или вниз. Вот почему мы рискуем и нормализуем наши 30-дневные окна только с помощью их среднего значения и дисперсии (нормализация z-показателя), предполагая, что только в течение одного временного окна они не сильно меняются и не затрагивают информацию из будущего.
Но мы собираемся нормализовать каждое измерение временного окна независимо:
for i in range(0, len(data_original), STEP): try: o = openp[i:i+WINDOW] h = highp[i:i+WINDOW] l = lowp[i:i+WINDOW] c = closep[i:i+WINDOW] v = volumep[i:i+WINDOW] o = (np.array(o) - np.mean(o)) / np.std(o) h = (np.array(h) - np.mean(h)) / np.std(h) l = (np.array(l) - np.mean(l)) / np.std(l) c = (np.array(c) - np.mean(c)) / np.std(c) v = (np.array(v) - np.mean(v)) / np.std(v)
Но поскольку мы хотим спрогнозировать движение цены вверх или вниз на следующий день, нам нужно рассмотреть изменение одного измерения:
x_i = closep[i:i+WINDOW] y_i = closep[i+WINDOW+FORECAST] last_close = x_i[-1] next_close = y_i if last_close < next_close: y_i = [1, 0] else: y_i = [0, 1]
Итак, данные, на которых мы будем обучаться, - это временные окна, как и раньше, 30 дней, но теперь каждый день мы будем считать все данные OHLCV правильно нормализованными, чтобы предсказать направление движения цены закрытия. Полный код для подготовки данных и обучения нейронной сети можно найти здесь.
Архитектура нейронной сети
Как я уже упоминал ранее, я хотел бы использовать CNN в качестве классификатора. В основном я выбираю его из-за гибкости и интерпретируемости гиперпараметров (сверточное ядро, размер понижающей дискретизации и т. Д.) И производительности, аналогичной RNN, лучше, чем MLP, с гораздо более быстрым обучением.
Код нашей сети на сегодня выглядит так:
model = Sequential() model.add(Convolution1D(input_shape = (WINDOW, EMB_SIZE), nb_filter=16, filter_length=4, border_mode='same')) model.add(BatchNormalization()) model.add(LeakyReLU()) model.add(Dropout(0.5)) model.add(Convolution1D(nb_filter=8, filter_length=4, border_mode='same')) model.add(BatchNormalization()) model.add(LeakyReLU()) model.add(Dropout(0.5)) model.add(Flatten()) model.add(Dense(64)) model.add(BatchNormalization()) model.add(LeakyReLU()) model.add(Dense(2)) model.add(Activation('softmax'))
Единственное отличие архитектуры от самого первого поста - это изменение переменной EMB_SIZE на 5 в нашем случае.
Тренировочный процесс
Скомпилируем модель:
opt = Nadam(lr=0.002) reduce_lr = ReduceLROnPlateau(monitor='val_acc', factor=0.9, patience=30, min_lr=0.000001, verbose=1) checkpointer = ModelCheckpoint(filepath="model.hdf5", verbose=1, save_best_only=True) model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy']) history = model.fit(X_train, Y_train, nb_epoch = 100, batch_size = 128, verbose=1, validation_data=(X_test, Y_test), callbacks=[reduce_lr, checkpointer], shuffle=True)
И проверим работоспособность:
Из графиков ясно видно, что сеть обучена адекватно (для очень зашумленных данных), потеря обучающей выборки уменьшалась со временем, а точность - увеличивалась. И, что самое главное, по сравнению с одномерными временными рядами из предыдущего поста мы улучшили производительность с 58% до почти 65% точности!
Чтобы проверить переоснащение, мы также можем построить матрицу путаницы:
from sklearn.metrics import classification_report from sklearn.metrics import confusion_matrix model.load_weights("model.hdf5") pred = model.predict(np.array(X_test)) C = confusion_matrix([np.argmax(y) for y in Y_test], [np.argmax(y) for y in pred]) print C / C.astype(np.float).sum(axis=1)
и мы получим:
[[ 0.75510204 0.24489796] [ 0.46938776 0.53061224]]
который показывает, что мы прогнозируем движение «ВВЕРХ» с точностью 75% и «ВНИЗ» с точностью 53%, и эти результаты, конечно, могут быть сбалансированы для тестового набора данных.
А что насчет регресса?
Вместо того, чтобы предсказывать двоичную переменную, мы можем предсказать реальное значение - доходность на следующий день или цену закрытия. В наших предыдущих экспериментах нам не удалось добиться хороших результатов.
К сожалению, для возвратов он по-прежнему работает плохо:
Для предсказания значения цены закрытия ситуация не лучше:
Я все еще пытаюсь решить проблему регрессии в финансовых данных по-разному (например, настраиваемые функции потерь). Если у вас есть предложения, я хотел бы обсудить их в комментариях или в личке.
Выводы
Мы обсудили общий конвейер подготовки и нормализации данных в случае многомерных временных рядов, обучили им CNN и можем сообщить о значительном (+ 7%) улучшении проблемы классификации - прогнозирование того, пойдет ли цена акций. вверх или вниз на следующий день. Не забудьте проверить полный код и запустить его на своей машине!
Между тем мы все еще можем констатировать, что проблема регрессии все еще слишком сложна для нас и мы будем работать над ней позже, выбирая правильные метрики потерь и функции активации.
В следующем посте я хотел бы представить концепцию мультимодального обучения, и мы будем использовать параметры не только из нашего файла .csv с кортежами OHLCV, но и гораздо более интересных вещей.
Будьте на связи!
P.S.
Следите за мной также в Facebook, чтобы увидеть статьи AI, которые слишком короткие для Medium, Instagram для личных вещей и Linkedin!