Авторы: Ясалапу Шива Саи Теджа (руководитель исследования), Брайан Онг Лимин, Чжан Лунхао, Лин Сюаньчан, Абхишек Баладжи.

Что приносит настоящие деньги, регрессоры Random Forest или сети LSTM?

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

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

Итак, что же такое классификатор случайного леса? Чтобы попытаться проиллюстрировать концепцию алгоритма случайного леса, представьте, что вы решили купить новую машину. У вас есть некоторое представление о функциях, которые вы хотите видеть в своем автомобиле, но вы знаете, что ваши друзья (которые хорошо вас знают) могут подсказать вам лучшие идеи. Например, первый друг советует вам модель автомобиля после того, как расспросит вас о ваших положительных и отрицательных впечатлениях от предыдущей поездки на автомобиле. Таким образом, ваш друг предлагает рекомендации, основанные на некоторых правилах. Точно так же другие друзья задают вам разные вопросы и впоследствии дают вам разные рекомендации. В конце концов, вы выбираете автомобиль после того, как усредните все рекомендации, которые вам дали. Таким образом, случайный лес предоставляет простой в реализации и гибкий алгоритм машинного обучения, способный выдавать вполне надежный результат.

Случаи использования случайного леса можно найти в самых разных областях: здравоохранение, финансы и т. д. Например, в мире банковского дела случайный лес может моделировать вероятность невыплаты ежемесячного платежа заемщиком.

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

Чтобы дать вам аналогию того, как работает этот алгоритм, попробуйте произнести первые 10 букв алфавита. Легко, верно? Теперь попробуйте прочитать те же буквы в обратном порядке. Тяжело не правда ли. Это интуиция, стоящая за моделью LSTM. Наш мозг запрограммирован на последовательное запоминание алфавита, поэтому мы расшифровываем, какая буква идет следующей, основываясь на том, какая буква уже была произнесена. Эта память о предыдущей букве хранится в компоненте узла LSTM, называемом скрытым состоянием, который является хранилищем исторической информации, полезной для прогнозирования последовательных данных. Однако то, что я только что описал вам в этом абзаце, — это рекуррентная нейронная сеть (RNN), а не совсем модель LSTM.

Модель LSTM предназначена для решения распространенной проблемы, от которой страдает обычная RNN, — исчезающего градиента. По сути, это означает, что по мере того, как это скрытое состояние хранит все больше и больше исторических данных, вес, придаваемый более старым данным, «исчезает» при прогнозировании. Это приводит к потере ценных данных об исторических тенденциях, что ставит под угрозу точность наших прогнозов. Чтобы обойти эту проблему, в архитектуру LSTM включен компонент «неизменяемое скрытое состояние», в котором хранится полная информация относительно старых данных, так что ценные тренды от этого не теряются. Это позволяет модели LSTM работать исключительно хорошо с точки зрения включения идей из старых тенденций в данные.

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

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

  1. Экспоненциальная скользящая средняя (EMA)

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

2. Индекс относительной силы (RSI)

RSI — это индикатор импульса, который учитывает изменения как цены, так и объема. Это помогает прогнозировать будущие ценовые тенденции, отслеживая перепроданность или перекупленность акций. Учитывая, как фондовые рынки управляются силой спроса и предложения, RSI часто считается эффективным индикатором ценового тренда. Как показано на графике выше, он отображается в виде осциллятора, который колеблется между верхней и нижней границей, обычно установленной на отметках 70 и 30. Любое колебание за пределами верхней границы или ниже нижней границы указывает на перекупленность или перепроданность соответственно.

3. Тренд объемной цены (VPT)

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

Подготовка данных для прогнозирования

import yfinance as yf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import pandas_ta as ta
print(‘Packages successfully imported’)

Это пакеты, которые мы будем использовать для нашего сравнительного анализа моделей RF Regressors и LSTM. Мы будем использовать API yfinance для нашего набора данных, который включает в себя стандартные показатели цен на акции, такие как цена открытия, цена закрытия, объем и скорректированное закрытие. Чтобы включить наши технические индикаторы, мы будем использовать библиотеку pandas_ta, которая поставляется со встроенными функциями технических индикаторов, упрощающих вычислительную задачу.

dow_reduced = [‘AAPL’, ‘BA’, ‘CSCO’, ‘GS’, ‘HD’, ‘INTC’, ‘JNJ’, ‘KO’, ‘DIS’, ‘V’] # Stocks to analyse
stocks_df = yf.download(tickers = dow_reduced, period = ‘10y’, interval = ‘1d’, group_by=’ticker’, threads=True)
stocks_df.head()
# Including a columns for EMA, RSI and PVT
for ticker in dow_reduced:
    stocks_df[ticker, ‘EMA’] = ta.ema(close=stocks_df[ticker, ‘Adj Close’], length=30) # Taking a 30-day moving average
    stocks_df[ticker, ‘RSI’] = ta.rsi(close=stocks_df[ticker, ‘Adj Close’]) # Calculating the Relative strength for each stock
    stocks_df[ticker, ‘PVT’] = ta.pvt(close = stocks_df[ticker, ‘Adj Close’], volume = stocks_df[ticker, ‘Volume’]) # Calculating the price-volume trend for each stock

После получения необходимых данных (данные за 10 лет по 10 акциям) мы рассчитываем значения технических индикаторов для каждой акции. Данные по первой анализируемой акции (Apple) таковы. (Этот кадр данных получается после некоторых шагов проецирования, чтобы получить только интересующие нас столбцы).

Затем, чтобы шаг прогнозирования работал, мы включаем новый столбец под названием «Следующее скорректированное закрытие», который, по сути, является скорректированной ценой закрытия для следующей строки данных (на следующий день). Скорректированная цена следующего дня становится целевой переменной для прогнозирования наших моделей.

# Performing some data processing
def process_data(df, ticker_list):
    for ticker in ticker_list:
        df[ticker, ‘Next Adj Close’] = df[ticker, ‘Adj Close’]
        df[ticker, ‘Next Adj Close’] = df[ticker, ‘Next Adj Close’].shift(-1)
        # Drop columns that are NaN
        df = df.dropna()
    return df

С помощью этой функции наши окончательные преобразованные данные должны выглядеть так:

Пример случайного леса

Чтобы начать построение нашей регрессионной модели Random Forest, мы импортируем класс RandomForestRegressor из модуля ансамбля библиотеки Scikit-Learn. Мы также импортируем функцию train_test_split из sklearn.model_selection, чтобы разделить наш набор данных на обучающие и тестовые подмножества. После завершения предварительной обработки данных создание первой итерации нашей модели становится простым и требует всего несколько строк кода.

Во-первых, мы инициализируем объект RandomForestRegressor и присваиваем аргументу n_estimators произвольное значение 1000, которое представляет количество деревьев в лесу. Затем мы обучаем наш RandomForestRegressor с данными обучения. На этом первая итерация нашей модели завершена, и теперь мы можем протестировать нашу модель, используя тестовые данные, используя метод прогнозирования RandomForestRegressor.

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
# Split data into training and test sets
x_train, x_test, y_train, y_test = train_test_split(df, target, test_size=0.3)
# First run of random forest regressor
rf = RandomForestRegressor(n_estimators=1000)
rf.fit(x_train, y_train)
y_pred = rf.predict(x_test)

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

print(rf.get_params())

Примерное представление о качестве прогноза нашей модели можно получить с помощью некоторых функций, доступных в модуле sklearn.metrics. В этом случае мы решили использовать показатель среднеквадратичной ошибки (MSE). С данными о запасах Intel наша модель Random Forest показала себя относительно хорошо с MSE примерно 0,645.

from sklearn import metrics
print(f”MSE of initial RF model for {ticker}:{metrics.mean_squared_error(y_test, y_pred)}”)

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

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

Используя метод get_params, мы видим, что RandomForestRegressor определяется многими гиперпараметрами, такими как n_estimators, bootstrap, max_leaf_nodes и min_impurity_decrease. Попытка оптимизировать каждый гиперпараметр займет много времени. Следовательно, с целью достижения баланса между производительностью модели и эффективностью во время выполнения мы собрали меньший список из шести гиперпараметров для настройки, изучая опыт аналогичных проектов, доступных в Интернете. Это n_estimators, max_features, max_depth, min_samples_split, min_samples_leaf и bootstrap. Мы определяем сетку поиска по диапазонам допустимых значений в словаре следующим образом:

from sklearn.model_selection import RandomizedSearchCV
# n_estimators: Number of trees in random forest
# max_features: Number of features to consider at every split
# max_depth: Maximum number of levels in tree
# min_samples_split: Minimum number of samples required to split a node
# min_samples_leaf: Minimum number of samples required at each leaf node
# bootstrap: Method of selecting samples for training each tree
# Create the search grid
search_grid = {
‘n_estimators’: [int(x) for x in np.linspace(start = 100, stop = 2000, num = 10)],
‘max_features’: [‘auto’, ‘sqrt’],
‘max_depth’: [int(x) for x in np.linspace(10, 100, num = 10)] + [None],
‘min_samples_split’: [2, 5, 10],
‘min_samples_leaf’: [1, 2, 4],
‘bootstrap’: [True, False] }

Базовая модель rf_tuned сначала создается с использованием RandomizedSearchCV. Одним из важных передаваемых аргументов является n_iter, который определяет количество параметров, которые необходимо выбрать. Согласно документации scikit-learn, это фактор, который компенсирует время выполнения качеством решения для машинного обучения. Еще один передаваемый ключевой аргумент — cv, указывающий тип используемой перекрестной проверки. В этом проекте мы будем использовать по умолчанию 10 образцов-кандидатов и 5-кратную перекрестную проверку. Кроме того, мы установили аргумент n_jobs равным -1, чтобы использовать все процессоры и ускорить обработку.

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

# Random sampling parameters, searching across 30 different parameter settings,
# using 3-fold cross validation, and use all cores for maximum processing speed
rf_tuned = RandomizedSearchCV(
estimator = RandomForestRegressor(), param_distributions=random_grid, n_iter=10, cv=None,
verbose=2, random_state=42, n_jobs=-1)
# Fit new model and predict
rf_tuned.fit(x_train, y_train)
print(rf_tuned.best_params_)

После настройки обновленный оценщик может быть вызван через атрибут best_estimator_. Мы можем продолжить использовать функцию прогнозирования в этом экземпляре RandomizedSearchCV, чтобы получить новый набор прогнозов (который должен быть более высокого качества, чем предыдущий). Чтобы проверить это, мы еще раз используем MSE как способ измерения качества прогнозов. Как оказалось, настройка гиперпараметров нашего регрессора случайного леса помогла получить более точные прогнозы на основе критериев MSE. Настроенная модель зафиксировала MSE примерно 0,595, используя тот же набор данных о запасах Intel.

new_y_pred = rf_tuned.best_estimator_.predict(x_test)
print(f”MSE of hyperparameter-tuned RF model for {ticker}: {metrics.mean_squared_error(y_test, new_y_pred)}”)
data = {‘Adj Close’: y_test, ‘Predicted Adj Close’: y_pred, ‘Predicted Adj Close (tuned)’: new_y_pred}
results = pd.DataFrame(data = data)
results[“Diff”] = results[“Predicted Adj Close”] — results[“Adj Close”]
results[“Diff (tuned)”] = results[“Predicted Adj Close (tuned)”] — results[“Adj Close”]
results

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

Чтобы лучше понять, как прогнозируемые цены, сгенерированные обеими моделями регрессии случайного леса, сравниваются с фактическими скорректированными ценами закрытия, различные данные можно объединить во фрейм данных. Мы также создаем 2 новых столбца, Diff и Diff (настроено). Первое относится к разнице между прогнозируемой скорректированной ценой закрытия и фактической скорректированной ценой закрытия INTC с использованием нашей исходной модели, тогда как второе относится к соответствующей разнице с использованием модели с настройкой гиперпараметров.

Пример LSTM

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

Далее, если у вас уже есть опыт составления прогнозов по данным временных рядов, вам может показаться интуитивно понятным импортировать TimeseriesGenerator из библиотеки keras.preprocessing для объединения входных и целевых данных. Но, однако, это делается только для одномерного анализа временных рядов, когда модель делает прогнозы исключительно на основе прошлых ценовых тенденций. Поскольку мы используем множество других функций в виде технических индикаторов, наше прогнозирование является многомерным, и, следовательно, пакетная обработка выполняется немного по-другому. Мы объединяем разбиение и группировку последовательных данных в одну функцию, как показано ниже.

# Defining variables to use for the prediction step
pred_seq_len = 7 # Use past 7 days data to predict 8th day
pred_len = 1

Чтобы объяснить код того, как выполняется пакетная обработка входных данных, сначала мы запускаем цикл for, чтобы просмотреть все имеющиеся у нас обучающие данные. Мы будем создавать пакеты из 7 строк данных (pred_seq_len) и 1 предсказанного значения (pred_len). По сути, это означает, что предыдущие данные за 1 неделю будут использоваться для прогнозирования скорректированной цены закрытия 8-го дня. Данные объекта добавляются в набор train_X, а целевые значения добавляются в набор train_Y. Как только это будет сделано, очень важно проверить форму матриц train_X и train_Y, чтобы избежать ошибок во время подбора модели.

Теперь пришло время определить план нашей модели LSTM. Мы добавим в нашу нейронную сеть в общей сложности 5 слоев, три слоя LSTM со 100 узлами, плотный слой с 32 узлами и окончательный выходной плотный слой с одним выходным узлом. Мы будем использовать выпрямленную линейную единицу для . Как только это будет сделано, мы начнем готовить наши входные и целевые данные для подачи в нейронную сеть.

# Defining some useful constants
epochs = 10
batch_size = 50
# Creating the model
model = Sequential()
model.add(LSTM(100, input_shape=(train_X.shape[1:]), return_sequences=True))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(LSTM(100, input_shape=(train_X.shape[1:]), return_sequences=True))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(LSTM(100, input_shape=(train_X.shape[1:])))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(Dense(32, activation=’relu’))
model.add(Dropout(0.2))
model.add(Dense(1, ‘relu’))
# Defining the optimizer
opt = tf.keras.optimizers.Adam(lr=0.001, decay=1e-6)

Наконец, мы передаем эти списки входных данных в нашу модель LSTM и делаем прогнозы. После преобразования прогнозов обратно в их первоначальную форму «цены акций» прогнозы выглядят следующим образом, в порядке прогнозов.голова() и прогнозы.хвост().

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

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

Результаты прогнозов для моделей Random Forest Regressor и LSTM сопоставимы, несмотря на то, что модель LSTM учитывает дополнительный фактор временного ряда при прогнозировании цены акций. Следовательно, мы можем сделать вывод, что регрессор случайного леса с некоторой настройкой гиперпараметров дает результаты, сопоставимые с базовой моделью LSTM.

Кроме того, важно также применять эти модели в контексте финансового рынка.

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

Ссылки:

https://medium.com/@maryamuzakariya/project-predict-stock-prices-using-random-forest-regression-model-in-python-fbe4edf01664

https://neptune.ai/blog/hyperparameter-tuning-in-python-complete-guide

https://towardsdatascience.com/hyperparameter-tuning-the-random-forest-in-python-using-scikit-learn-28d2aa77dd74

https://scikit-learn.org/stable/

https://towardsdatascience.com/3-reasons-to-use-random-forest-over-a-neural-network-comparing-machine-learning-versus-deep-f9d65a154d89

https://builtin.com/data-science/random-forest-algorithm

https://wiki.pathmind.com/lstm