Реализация сверточной модели в Python

Реализацию сверточных моделей на языке Python мы будем осуществлять с помощью средств, предлагаемых библиотекой Keras из TensorFlow. Указанная библиотека предлагает несколько вариантов сверточных слоев. В первую очередь, это базовые варианты сверточных слоев:

  • Conv1D
  • Conv2D
  • Conv3D

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

Объекты класса Conv1D создают ядро ​​свертки, которое сворачивается с исходными данными в одном измерении для создания тензора выходных данных. Здесь важно понять и не путать. Исходные данные сворачиваются в одном измерении, но исходные данные, подаваемые на вход нейронного слоя, должны иметь вид трехмерного тензора. Первое измерение определяет размер пакета обрабатываемых данных (batch size). Второе — измерение свертки. А в третьем измерении содержатся исходные данные для свертки.

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

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

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

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

  • filters — количество фильтров, используемых в свертке;
  • kernel_size — размер окна одномерной свертки;
  • strides — размер шага свертки;
  • padding — допускается одно из значений: "valid", "same" или "causal" (без учета регистра); "valid" — означает отсутствие отступов; "same" — приводит к равномерному дополнению нулями исходных данных для получения размера вывода равного размеру входа; "causal" — приводит к возникновению причинных (расширенных) изменений, например output[t] не зависит от input[t+1:]. Полезно при моделировании временных данных, когда модель не должна нарушать временной порядок;
  • data_format — допускается одно из значений: "channels_last" или "channels_first"; определяет в каком измерении входного тензора содержатся данные для свертки; по умолчанию используется "channels_last";
  • dilation_rate — используется для расширенной свертки и определяет скорость расширения;
  • groups — количество групп, на которые вход разделяется по оси канала; каждая группа сворачивается отдельно с помощью фильтров, на выходе получаем объединение всех результатов по оси канала;
  • activation — функция активации;
  • use_bias — указывает на необходимость использования вектора смещения;
  • kernel_initializer —  задает метод инициализации матрицы весов;
  • bias_initializer — задает метод инициализации вектора смещения;
  • kernel_regularizer — указывает на метод регуляризации матрицы весов;
  • bias_regularizerуказывает на метод регуляризации вектора смещения;
  • activity_regularizer — указывает на метод регуляризации результатов;
  • kernel_constraint — указывает функцию ограничения для матрицы весов;
  • bias_constraint — указывает функцию ограничения для вектора смещения.

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

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

К примеру, объекты класса Conv2D оперируют свертками с исходными данными в двух измерениях. При этом следует понимать, что отличие между одномерными и двухмерными сверточными слоями лежит не только в названии. Объекты двухмерного сверточного слоя ожидают на вход четырехмерный тензор. По аналогии с тензором Conv1D, первое измерение определяет размер пакета обрабатываемых данных (batch size), второе и третье — определяют измерения свертки, а в четвертом измерении содержатся исходные данные для свертки. Тут возникает справедливый вопрос: где нам взять данные для еще одного измерения? Как нам поделить наш набор исходных данных на четыре измерения? Нам нужно перевести наши исходные данные из плоской в объемную таблицу. Наиболее простое решение лежит на поверхности. Мы говорим, что глубина таблицы исходных данных равна 1. Перед объявлением двухмерного нейронного слоя изменим размерность тензора, подаваемого на вход сверточного слоя Conv2D, до четырехмерного, указав в размере четвертого измерения 1.

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

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

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

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

Параметры же класса Conv3D переходят из выше рассмотренных классов практически без изменений. Разница лишь в размерности векторов окна свертки и его шага.

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

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

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

Аналогично сверточным слоям библиотека Keras предлагает несколько вариантов классов для подвыборочного слоя. Среди них:

  • AvgPool1D — одномерное усреднение данных;
  • AvgPool2D — двухмерное усреднение данных;
  • AvgPool3D — трехмерное усреднение данных;
  • MaxPool1D — одномерное извлечение максимального значения;
  • MaxPool2D — двухмерное извлечение максимального значения;
  • MaxPool3D — трехмерное извлечение максимального значения.

Все указанные подвыборочные слои имеют одинаковый набор параметров:

  • pool_size — целое число или вектор целых чисел, определяет размер окна;
  • strides — целое число или вектор целых чисел, определяет шаг окна;
  • padding — допускается одно из значений: "valid", "same" или "causal" (без учета регистра); "valid"  — означает отсутствие отступов; "same"  —  приводит к равномерному дополнению нулями исходных данных для получения размера вывода равного размеру входа;
  • data_format — допускается одно из значений: "channels_last" или "channels_first"; определяет в каком измерении входного тензора содержатся данные для свертки; по умолчанию используется "channels_last".

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

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

# Создание модели перцептрона с тремя скрытыми слоями и регуляризацией
model1 = keras.Sequential([keras.Input(shape=inputs),
                           keras.layers.Dense(40, activation=tf.nn.swish, 
                   kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
                   kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
                   kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

Такая модель насчитывает 9802 параметра. Ниже на скриншоте приведена структура созданной нами нейронной сети. В первой колонке таблицы указаны имя и тип нейронного слоя, во второй — размерность тензора результатов каждого слоя. Обратите внимание, что первое измерение не задано, вместо размера указано None. Это означает, что данное измерение строго не задано и может быть переменной длины. Данное измерение задается размером пакета данных batch size. В третьей колонке указано количество параметров в матрице весов каждого слоя.

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

Обратите внимание: несмотря на то, что мы переносим данные в трехмерный тензор, в параметрах слоя Reshape мы указываем два измерения. Это связано с тем, что первое измерение тензора является переменным и задается размером пакета исходных данных batch size.

Структура перцептрона

Структура перцептрона

И еще один момент. В векторе размерности, передаваемом в параметрах класса Reshape, в первом измерении стоит −1. Это указывает классу самостоятельно рассчитать размер данного измерения исходя из размера тензора исходных данных и указанных размерностей других измерений.

# Добавляем в модель 1D сверточный слой
model2 = keras.Sequential([keras.Input(shape=inputs),
                           # Переформатируем тензор в трехмерный.
    # Указываем 2 измерения, т.к. 3-е измерение определяется размером пакета
                           keras.layers.Reshape((-1,4)), 
                           # Сверточный слой с 8-ю фильтрами
                           keras.layers.Conv1D(8,1,1,activation=tf.nn.swish,
             kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)),

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

                           # Подвыборочный слой
                           keras.layers.MaxPooling1D(2,strides=1),                         
                  # Переформатируем тензор в двухмерный для полносвязных слоев
                           keras.layers.Flatten(),
                           keras.layers.Dense(40, activation=tf.nn.swish,
               kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
               kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
               kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

Обратите внимание. В исходных данных каждая свеча описана четырьмя значениями. Использование восьми фильтров увеличивает размерность обрабатываемого тензора. В итоге модель с одномерным сверточным слоем содержит уже 15922 параметра.

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

Структура нейронной сети с одномерным сверточным слоем

Структура нейронной сети с одномерным сверточным слоем

С такими параметрами первое измерение (измерение времени) уменьшится на два элемента в результате проведения операций свертки. Второе измерение (измерение индикаторов) останется неизменным, так как окно свертки и его шаг в данном измерении равно 1. В то же время у нас увеличится третье измерение — оно станет равным количеству фильтров. Напомню, что до операции свертки третье измерение было рано 1. В результате всех итераций количество параметров сети увеличилось до 50794. Структура новой нейронной сети приведена ниже. Как можно заметь, сверточный слой имеет только 32 параметра. Такой рост количества параметров сети вызван увеличением размера тензора после операции свертки по причинам, указанным выше. Это видно по количеству параметров в первом после свертки полносвязном слое.

# Заменяем в модели сверточный слой на двухмерный
model3 = keras.Sequential([keras.Input(shape=inputs),
                           # Переформатируем тензор в четырехмерный.
         #Указываем 3 измерения, т.к. 4-е измерение определяется размером пакета
                           keras.layers.Reshape((-1,4,1)), 
                           # Сверточный слой с 8 фильтрами
                           keras.layers.Conv2D(8,(3,1),1,activation=tf.nn.swish,
                 kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)),
                           # Подвыборочный слой
                           keras.layers.MaxPooling2D((2,1),strides=1),                         
                    # Переформатируем тензор в двухмерный для полносвязных слоев
                           keras.layers.Flatten(),
                           keras.layers.Dense(40, activation=tf.nn.swish,
                 kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
                 kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(40, activation=tf.nn.swish,
                 kernel_regularizer=keras.regularizers.l1_l2(l1=1e-7, l2=1e-5)), 
                           keras.layers.Dense(targerts, activation=tf.nn.tanh) 
                         ])

Структура нейронной сети с двухмерным сверточным слоем

Структура нейронной сети с двухмерным сверточным слоем

В остальном наш скрипт останется без изменений. С результатами работы скрипта познакомимся в следующем разделе.