Прогноз на акции с использованием CNN

Что такое CNN?

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

В основе CNN лежит операция, называемая сверткой. Итак, что же такое свертка, особенно в области обработки изображений?

Что такое свертки?

Возьмем простой пример свертки двух 1D массивов. Предположим, что массивы a = [1, 2, 3, 4, 5] и b = [0, 1, -1, 2, -2]. Тогда свертка a и b, обозначаемая как (a * b), представляет собой другой массив, i-йэлемент которого (индексация начинается с 0) задается выражением:

, что в данном случае оказывается [0, 1, 1, 3, 3, 3, -3, 2, -10].

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

Каждая цветовая единица изображения называется пикселем. Каждый пиксель можно рассматривать как вектор, содержащий 3 значения, для красного, зеленого и синего соответственно, каждое значение варьируется от 0 до 255.

Когда мы свертываем изображение с 2D-ядром (в идеале n x n квадратов), мы проходим по каждому пикселю и в каждом раунде сопоставляем текущий пиксель как центральный элемент ядра, а его непосредственных соседей — с соответствующими элементами матрицы.

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

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

Назад к CNNs

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

Затем элементы карты признаков пропускаются через функцию активации, обычно ReLU (Rectified Linear Unit). Это вносит нелинейность и позволяет сети изучать и моделировать сложные данные, указывая, какие элементы карты признаков должны быть активированы. Функция ReLU определена как max(0, x), которая активирует только те элементы, которые строго больше 0.

Теперь для обычных изображений, с которыми мы сталкиваемся (с разрешением 300 PPI), карты признаков будут ОГРОМНЫМИ, с миллионами (буквально) элементов в карте признаков. Мы хотели бы как-то обобщить этот огромный объем информации в нечто компактное. Это можно сделать с помощью процесса, называемого объединением. При этом фильтр проходит по каждому разделу изображения (не проходя ни по одному из них дважды, и, таким образом, двигаясь по шагам, называемым «шагами») и суммирует информацию определенным образом. Процесс объединения помогает уменьшить размерность задачи. Мы могли бы иметь:

  1. MaxPooling: Из каждой секции, через которую проходит фильтр, мы берем только максимальное значение. Это работает довольно хорошо, когда ReLU возвращает много нулей, так что несколько положительных записей становятся определяющими характеристиками конкретного раздела.
  2. Средний пул: Это работает, как указано выше, но из каждого сечения мы берем среднее значение.

3. Глобальный пул: возвращает среднее или максимальное значение из всей карты объектов. Обычно используется ближе к концу процесса CNN для оценки знаний нейронной сети.

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

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

РАБОТА МОДЕЛИ КАК МЫ ИСПОЛЬЗУЕМ CNN ДЛЯ ПРОГНОЗИРОВАНИЯ СИГНАЛОВ

Идея довольно проста: рассчитать несколько технических индикаторов с разной длиной периода (описано ниже), суммируя общее количество характеристик 116. Затем уменьшите количество объектов, взяв среди них 81 лучший объект, а затем преобразуйте 81 новый объект в изображения 9×9. Обозначьте данные как покупка/продажа/удержание на основе алгоритма (мы обсудим далее). Затем обучите сверточную нейронную сеть, как и любую другую задачу классификации изображений.

Мы разобьем нашу модель на 10 частей:

  1. Извлечение признаков
  2. Проектирование и выбор элементов
  3. Стратегия маркировки
  4. Важность признака в прогнозе
  5. Создание образа
  6. Архитектура сверточной нейронной сети
  7. Обучение, оценка моделей и результаты
  8. Причина плохой работы в матрице Confusion
  9. Работа с несбалансированными классами
  10. Окончательная производительность модели
  11. Ссылки

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

Извлечение признаков:

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

Источник данных: Обработка данных начинается со сбора исторических данных о ценах на акции. Здесь мы использовали данные о цене акций Google, которые мы взяли у Alpha Vintage.

Технические индикаторы: Для создания характеристик нашей модели мы использовали различные индикаторы с разными временными периодами. С помощью этого метода мы создали около 106 объектов, а сложив все эти OHLC и Volume и Adjusted Close, мы получим в общей сложности 112 объектов.

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

Скользящее среднее для списка чисел похоже на среднее арифметическое, но вместо того, чтобы вычислять среднее значение всех чисел, мы вычисляем среднее значение первых n чисел (n называется размером окна или периодом времени), а затем перемещаем (или сдвигаем) окно на 1 индекс, таким образом, исключая первый элемент и включая элемент n+1 и вычисляя их среднее значение. Этот процесс продолжается. Вот пример, подтверждающий это:

Это пример SMA на окне размером 6.

SMA первых шести элементов показана оранжевым цветом.

Проектирование и выбор элементов

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

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

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

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

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

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

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

Стратегия маркировки :

Стратегия маркировки для нашей модели заключается в том, что мы в основном использовали окно в 11 дней по цене закрытия, что мы делаем, так это то, что если среднее значение является максимальным в пределах окна, то мы помечаем середину дня как «продажа» или, если среднее число является минимальным, то мы помечаем день как «покупать», иначе помечаем как «держать». Идея состоит в том, чтобы покупать на впадине и продавать на гребнях в течение любого 11-дневного окна.Вот его непосредственная реализация:windowSize = 11
def labl(df = df, windowSize =11):
labels = []
values = []
for i in range(len(df.close) — windowSize):
mx= df.close[i]
mn df.close[i]
mxIndex, mnIndex = i, i
for j in range(i + 1, i + windowSize + 1):
if df.close[j] > mx:
mx = df.close [j]
mxIndex = j
if df.close[j] < mn:
mn = df.close[j]
mnIndex = j

if mnIndex == i + 11:
labels.append(1)
values.append(i + 11)
elif mxIndex == i + 11:
labels.append(0)
values.append(i + 11)
else:
labels.append(2)
values.append(i + 11)
return labels, values

После добавления всех этих признаков и маркировки наши данные выглядят примерно так:

Важность признака в прогнозировании:

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

СОЗДАНИЕ ОБРАЗА:

На данный момент у нас есть табличные данные из 20 признаков, нам нужно преобразовать их в изображение 5 x 4 , для этого в основном мы преобразуем наши данные из кадра данных pandas в массив numpy, а затем преобразуем этот массив в 5,4, реализация которого находится здесь:from imblearn.over_sampling import RandomOverSampler
from sklearn.preprocessing import StandardScaler

X = d[d.columns[0:20]]
Y = d[‘labeldd’]

# TRANSFORMING DATA
scaler = StandardScaler()
X = scaler.fit_transform(X)

#SPLITTING THE DATA
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2)

#REPLACING THE nan VALUE WITH THE MEAN OF X_TRAIN, X_TEST
X_train = np.nan_to_num(X_train, nan=X_train.mean())
X_test = np.nan_to_num(X_test, nan=X_test.mean())
X_a = np.array(X_train)
X_b = np.array(X_test)

Вот как выглядят изображения:

Архитектура сверточной нейронной сети:

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

def simple_model():
model = Sequential()
model.add(Conv2D (32, 5, 5, padding=’same’, input_shape=(1,5,4), activation=’relu’))
model.add(MaxPooling2D (pool_size=(2,2), padding=’same’))
model.add(Dense (10, activation=’relu’))
model.add(Dropout (0.1))
model.add(Flatten())
model.add(Dense (10, activation=’relu’))
model.add(Dense (3, activation=’softmax’))
model.compile(loss=’ categorical_crossentropy’, optimizer=’Adam’, metrics=[‘accuracy’])
return model

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

  • Conv2D (32 фильтра, 5×5): обнаруживает объекты с помощью 32 фильтров в сетке 5×5.
  • MaxPooling2D (2×2): уменьшает размеры данных, сохраняя при этом важные функции.
  • Плотный (10 нейронов, ReLu): Связный слой для представления сложных объектов.
  • Выпадение (0,1): предотвращает переобучение, случайным образом отключая 10% нейронов.
  • Сведение (Flatten): Изменяет форму данных для полносвязных слоев.
  • Плотность (10 нейронов, ReLu): Дальнейшая обработка высокоуровневых признаков.
  • Dense (3 neurons, softmax): Выходной слой для вероятностей классов.
  • Compile (categorical_crossentropy, Adam): Подготавливает модель к обучению с потерей и оптимизатором

Вот краткое описание нашей модели:

Обучение, оценка модели и результат :

Поскольку мы разделили наши данные на обучение и тестирование, где 80% являются обучением, а остальное — тестовыми данными, теперь мы обучим нашу модель с помощью обучающего набора данных и оценим ее с помощью тестового набора данных, мы установили наши эпохи равными 20 с размером пакета 32, вот его реализация:

А вот и матрица несоответствий:

Как видите, производительность модели при предсказании 2, т.е. удержания, хорошая, но в случае 0 и 1 она хуже, поэтому в чем причина такой производительности и как ее решить, мы рассмотрим далее.

Причина плохой работы в матрице Confusion:

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

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

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

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

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

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

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

Работа с несбалансированными классами:

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

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

Пересчета: Либо избыточная дискретизация класса меньшинства (дублирующиеся экземпляры), либо недостаточная дискретизация класса большинства (удаление экземпляров), чтобы сбалансировать распределение классов.

Функция взвешенных потерь: Измените функцию потерь, чтобы назначить более высокие веса классу меньшинства во время обучения. Это может помочь модели уделять больше внимания классу меньшинства и уменьшить предвзятое отношение к классу большинства.

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

Генерация синтетических данных: используйте такие методы, как Synthetic Minority Over-Sampling Technique (SMOTE), для создания синтетических экземпляров для класса меньшинств, помогая сбалансировать распределение данных.

В нашей модели мы использовали Random Over Sampling для наших данных, кратко о том, как работает случайная избыточная выборка:

  1. Определите класс меньшинства: определите, в каком классе меньше выборок по сравнению с другими классами.
  2. Случайное дублирование образцов: случайный выбор образцов из класса меньшинства и создание их дубликатов. Это делается для того, чтобы увеличить количество образцов в классе меньшинства до тех пор, пока не будет достигнут желаемый баланс.
  3. Настройка распределения классов: Random Over Sampler продолжает случайным образом дублировать выборки из класса меньшинства до тех пор, пока его размер не приблизится к размеру класса большинства. Это помогает создать более сбалансированный набор данных.

Вот как мы его применили, мы использовали для этого библиотеку ‘imblearn’:from imblearn.over_sampling import RandomOverSampler
from sklearn.preprocessing import StandardScaler

X = d[d.columns [0:20]]
Y = d[‘labeldd’]

# TRANSFORMING DATA
scaler =StandardScaler()
X = scaler.fit_transform(X)

#APPLIED RANDOM OVER SAMPLER
os = RandomOverSampler()
Xs, Ys = os.fit_resample (X, Y)

#SPLITTING THE DATA
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(Xs, Ys, test_size=0.2)

#REPLACING THE nan VALUE WITH THE MEAN OF X_TRAIN, X_TEST
X_train = np.nan_to_num(X_train, nan=X_train.mean())
X_test = np.nan_to_num(X_test, nan=X_test.mean())
X_a = np.array(X_train)
x_b = np.array(X_test)

После выполнения Random Over Sampler наши данные выглядят так, как показано на рисунке (1), и, как вы можете видеть, количество классов 0,1,2 одинаково, следовательно, это поможет в улучшении производительности, так как наша модель будет получать выборки данных с метками 0,1, и это предоставит нашей модели более разнообразный диапазон данных для обучения и, следовательно, повысит точность и производительность

Окончательная производительность модели

Теперь мы обучим нашу модель на этих измененных данных и посмотрим, как она работает,

Модель CNN аналогична с щелевым изменением количества нейронов в плотном слое.

Вот модель:def CNN_model():
model = Sequential())
model.add(Conv2D (32,5,5, padding=’same’, input_shape=(1,5, 4), activation=’relu’))
model.add(MaxPooling2D(pool_size=(2,2), padding=’same’))
model.add(Dense (200, activation=’relu’))
model.add(Dropout (0.2))
model.add(Flatten())
model.add(Dense (200, activation=’ relu’))
model.add(Dense (3, activation=’softmax’))
model.compile(loss=’categorical_crossentropy’, optimizer=’Adam’, metrics=[‘accuracy’])
return model

И резюме:

Теперь заключительная часть, т.е. оценка нашей модели, мы обучаем нашу модель с помощью 500 эпох (имеется в виду одна итерация по всему обучающему набору данных в процессе обучения модели)history = modeld.fit(X_a, y_train, epochs 500, batch_size=32, verbose=1) loss, accuracy = modeld.evaluate(X_b, y_test, verbose=1)
print(«Test acc:», (accuracy*100))

И после 500 итераций итоговая точность нашей модели выходит в 93%

Теперь основной параметр матрицы несоответствий:

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

ССЫЛКИ:

  1. «Алгоритмическая финансовая торговля с глубокими сверточными нейронными сетями: преобразование временных рядов в изображения», Омер Берат Сезер, Мурат Озбайоглу.

Иточник