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

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

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

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

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

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

  1. Если 20-периодная простая скользящая средняя (SMA) ниже минимума дня.
  2. Если 20-периодная SMA находится между минимумом и максимумом дня.
  3. Если 20-периодная SMA выше максимума дня.

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

Вот визуальное представление того, как сеть LSTM работала всего после нескольких минут обучения:

В следующих разделах будет рассмотрен код, необходимый для:

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

Загрузка ценовых данных

На самом деле у меня есть статья на эту тему, вы можете найти ее здесь:



Однако для удобства следует использовать следующий код:

Этот код создаст папку с именем «данные» в вашем рабочем каталоге и загрузит данные для 6 крупных технологических акций, хранящихся в списке в строке 4 кода. Не стесняйтесь изменять, чтобы добавлять или удалять тикеры🙂

Формирование входных данных для нейронной сети

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

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

FEAT_LENGTH = 30
FEAT_COLS = ['Open', 'Low', 'High', 'Close']
def time_series(df: pd.DataFrame,
                col: str,
                name: str) -> pd.DataFrame:
    '''
    Form the lagged columns for this feature
    '''
    return df.assign(**{
        f'{name}_t-{lag}': col.shift(lag)
        for lag in range(0, FEAT_LENGTH)
    })
def get_lagged_returns(df: pd.DataFrame) -> pd.DataFrame:
    '''
    For each of the feature cols, find the returns and then form the lagged
    time-series as new columns
    '''
    for col in FEAT_COLS:
        return_col = df[col]/df[col].shift(1)-1
        df = time_series(df, return_col, f'feat_{col}_ret')
        
    return df

Подождите, почему мы используем 30 дней в качестве длины нашего временного ряда?

Что ж, мы пытаемся обнаружить закономерности на основе 20-дневной SMA; 30 дней позволяют сети узнать, что такое 20-дневная SMA с запасом места. Вы можете попробовать поработать с днями, чтобы увидеть, работает ли что-то лучше! В конце концов, с нейронными сетями небольшие пробы и ошибки могут иметь большое значение.

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

Почему мы используем процентную доходность?

Если мы поместим необработанные данные о ценах, сеть будет изо всех сил пытаться изучить масштабы. Например, цена акций TSLA в 3 раза превышает цену AMD и в 18 раз превышает цену Ford (по состоянию на 28 августа 2022 года). Сеть запутается, если в будущем увидит резко более высокую или более низкую цену. Только представьте, что вы обучаете его акциям по цене около 200 долларов, а затем просите классифицировать Berkshire Hathaway по цене 436 201 доллар!

Процентные изменения примерно соответствуют нормальному распределению и будут одинаковыми для любой акции, которую вы рассматриваете. Изменение цены TSLA на 5% будет таким же, как изменение цены AMD на 5%, но изменение доллара может быть другим. Суть в том, чтобы показать сети достаточное количество акций, чтобы она узнала, как процентные изменения связаны с вашим паттерном.

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

Обобщите, пожалуйста, как работает этот код?

Конечно, держи:

  1. get_lagged_returns(df) берет набор данных о ценах, который вы скачали с yfinance.
  2. Для каждой функции процент возврата за предыдущий день рассчитывается с использованием df[col]/df[col].shift(1)-1, .shift(1) получает данные за предыдущий день.
  3. Затем данные «транспонируются» из-за отсутствия лучшего способа их описания. Другими словами, функция time_series добавляет 30 новых столбцов, соответствующих 30 последним процентным изменениям с каждой даты. т.е. мы получим процентное изменение между сегодняшним и вчерашним днем, вчера и позавчера и так далее.

В итоге вы получите фрейм данных со 120 новыми столбцами, соответствующими 30-дневным процентным изменениям для 4 входных функций.

Мой совет — установить несколько точек останова и просмотреть набор данных после выполнения этой операции (полный код в конце этого раздела).

Хорошо, отлично, как насчет классификации?

Ах, это самая легкая часть! Мы можем использовать numpy.select:

def get_classification(df: pd.DataFrame) -> pd.DataFrame:
    '''
    Get the classifications for the LSTM network, which are as follows:
        0 = The 20 period SMA is below the low of the day
        1 = The 20 period SMA is between the low and high of the day
        2 = The 20 period SMA is above the high of the day
    '''
    
    df['ma'] = df['Close'].rolling(20).mean()
    
    conditions = [
        df['ma'] <= df['Low'],
        (df['ma'] < df['High']) & (df['ma'] > df['Low']),
        df['ma'] >= df['High'],
    ]
    
    df['classification'] = np.select(
        condlist = conditions,
        choicelist = [0, 1, 2],
    )
    
    return df

Надеюсь, в этом случае код не требует пояснений! Суть в том, что вы получите новый столбец с именем classification, который имеет значения 0, 1 или 2 (что означает каждая классификация, см. в строке документации).

Боже мой, это длинная и сложная статья.

Я тебя чувствую, так что вот случайное фото утки.

Вернемся к машинному обучению!

Это весь код вместе (обратите внимание, что элементы управления установлены как глобальные переменные в верхней части скрипта):

Подождите, что это за функция reshape_x?!

Ах, простите, чуть не забыл об этом.

Мы собираемся обучить нейронную сеть LSTM, для этого требуется трехмерный массив в качестве входных данных формы (количество примеров, длина временного ряда, количество признаков). Функция изменения формы в основном изменяет наши 120 столбцов в трехмерный массив формы (N, 30, 4), где N — количество классифицированных примеров, которые у нас есть.

⚠️Эта функция требует, чтобы все функции были одинаковой длины! ⚠️

Формирование и обучение сети

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

Поскольку это довольно простая задача, мы будем использовать простую сеть. Он будет состоять из двух двунаправленных слоев LSTM и одного выходного слоя; мы также будем использовать слой отсева 20%, чтобы предотвратить переоснащение.

Это может быть достигнуто в tensorflow.keras с помощью:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, LSTM, Bidirectional, Dropout
def get_model(x_train: np.array) -> Sequential:
    '''
    Generate the NN model that we are going to train
    '''
    return Sequential([
        Bidirectional(
            LSTM(
                64, 
                input_shape = (x_train.shape[1], x_train.shape[2]),
                return_sequences = True,
            ),
        ),
        Dropout(0.2),
        Bidirectional(LSTM(32)),
        Dense(3, activation = 'softmax'),
    ])

Почему вы используете Керас?

Все просто, я ленивый. Я нахожу Keras интуитивно понятным и легко программируемым!

Почему двунаправленные слои?

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

Что такое выпадающий слой?

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

Что такое функция активации softmax?

Вы можете думать об этом как о превращении выходных данных сети в «вероятности» или, как я люблю говорить, «гарантии». По сути, это то, насколько модель уверена в том, что что-то принадлежит определенному классу. Конкретно, если вы получаете возврат [0,5, 0,2, 0,3], то модель на 50% «уверена», что класс равен 0; обратите внимание, что все элементы в сумме дают 1. Если вы получаете [0,9, 0,01, 0,09], то модель кричит вам, что это нулевой класс.

Могу ли я использовать больше слоев?

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

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

Как я узнаю, что это переоснащение?

Одним из простых способов является использование точности проверки. Другими словами, вы откладываете x% (обычно 10%) данных обучения для тестирования в конце каждого этапа обучения (эпохи). Если ваша точность проверки повышается с той же скоростью, что и точность обучения, то все в порядке. Если ваша точность обучения намного превосходит точность проверки, скорее всего, вы переобучаетесь.

Итак, как мне обучить сеть?

Используйте этот код в качестве отправной точки:

Несколько заметок:

  1. Для размера пакета задано значение 64. Это означает, что модель будет обрабатывать 64 выборки данных перед обновлением параметров сети. Это настраиваемый параметр, и вам следует попробовать другие размеры (размер партии обычно составляет 2ⁿ).
  2. Я установил 5 эпох, что говорит о том, что модель будет проходить весь обучающий набор 5 раз. Это значение нужно будет увеличить, если вы хотите повысить точность, но это нормально для тестирования, выполняет ли сеть свою работу.
  3. Я установил 10% точность проверки, чтобы увидеть, не переобучается ли модель (я говорил об этом ранее).

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

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

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

Рекомендуется увидеть как немасштабированную, так и масштабированную версии. Масштабированный вариант особенно удобен, чтобы быстро взглянуть и посмотреть, как работает модель, а немасштабированный показывает, на скольких примерах она была протестирована. Например, у вас может быть 100% точность в одном классе, но только в одном случае тестирования (крайний пример, конечно). Иногда я видел замечательную точность в одном классе и проверял, что это было только на 10 примерах, едва ли приличный размер выборки!

Мы можем видеть, что после пяти эпох модель правильно классифицировала, что скользящая средняя была выше цены акции в 89% случаев (класс 2), и ошибочно помечена как промежуточная скользящая средняя в 11% случаев; он никогда не смущался и не говорил, что находится ниже скользящей средней, хотя на самом деле был выше. Это было на 3142 примерах тестирования в этом классе.

Еще одна хорошая идея — построить графики свечей и визуализировать, где именно сеть ошиблась. Очень легко сказать: «Моя сеть эффективна на 90%, вау!», но знаете ли вы, почему она была неверна на этих 10%? Визуализация на графике свечей может помочь вам лучше разработать функции, чтобы выжать больше точности. Вот пример:

Мы можем видеть, что он имеет тенденцию запутываться, когда цена более плоская; возможно, обучение дольше или включение большего количества данных может помочь. Или, может быть, если бы вы сгладили временной ряд, вы могли бы помочь сети лучше обнаруживать плоские области (эти области выглядят довольно зашумленными). Кстати, я тренировал сеть на 20 эпох и точность возросла до ~95%.

Если вам интересно, как сгладить временной ряд, то, пожалуйста, ознакомьтесь со следующей статьей, которую я написал:



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

⚠️Обратите внимание, что этот код требует, чтобы код обработки данных, который я разместил выше, находился в том же рабочем каталоге, который называется «get_training_data.py» ⚠️

Подведение итогов

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



Этот сканер в настоящее время находится в стадии разработки, и потребовалось много усилий, чтобы вручную пометить десятки тысяч примеров, но пока результаты не так уж плохи!

Спасибо за прочтение, надеюсь статья вам понравилась! Пожалуйста, не стесняйтесь связаться со мной в LinkedIn — я буду рад услышать от вас! Также не стесняйтесь подписываться на меня в Твиттере.

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

Или, если вам нужен другой способ поддержать создание моего контента, вы можете

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

Сообщение от InsiderFinance

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь: