- Определяем константы
- Механизм описания структуры создаваемой нейронной сети
- Базовый класс нейронной сети и организация процессов прямого и обратного проходов
- Динамический массив хранения нейронных слоёв
Механизм описания структуры создаваемой нейронной сети
Мы уже определились, что будем строить универсальный конструктор для удобного создания нейронных сетей различной конфигурации. Следовательно, нам нужен некий механизм (интерфейс) для возможности передачи конфигурации модели для построения. Давайте подумаем, какую информацию нам нужно получить от пользователя для однозначного понимания, какую нейронную сеть предполагается построить.
Прежде всего нам нужно понимать, сколько слоев нейронов будет в нашей сети. Таких слоев должно быть как минимум два: входной слой исходных данных и выходной слой результатов. Дополнительно новая нейронная сеть может включать различное количество скрытых слоев. Их количество может быть различно, и мы не будем сейчас их ограничивать.
Для создания каждого слоя нейронной сети нам нужно знать количество нейронов в данном слое. Следовательно, помимо количества нейронных слоев пользователь должен указать количество нейронов в каждом слое.
Теперь давайте вспомним, что в предыдущем разделе мы определи константы для нескольких типов нейронных слоев, которые будут отличаться по типу нейронов. Чтобы понимать, какой именно слой хочет создать пользователь, нужно получить эту исходную информацию. Значит, пользователь должен иметь возможность ее указать для каждого создаваемого слоя.
Кроме того, мы рассматривали различные варианты функций активации. Какую из них нужно использовать при создании нейронов?
При создании универсального инструмента мы должны предоставить возможность выбора функции активации пользователю. Следовательно, добавляем функцию активации в список параметров для получения от пользователя.
Возникает еще один вопрос: будут ли все нейроны одного слоя использовать одну функцию активации? Или же будут варианты использования различных функций активации в рамках одного слоя? Я предлагаю остановиться на первом варианте, когда все нейроны одного слоя используют одну функцию активации.
Поясню свою позицию. Обсуждая приемы повышения сходимости нейронных сетей и, в частности, нормализацию данных, мы говорили о важности сопоставимости данных на входе нейронного слоя. Использование же различных функций активации с высокой долей вероятности приведет к дисбалансу данных. Это связано с природой самих функций активации. Вспомните, сигмоида возвращает данные в диапазоне от 0 до 1. Область значений гиперболического тангенса лежит в диапазоне от −1 до 1. А ReLU может вернуть значения от 0 до +∞. Очевидно, что разные функции активации дадут сильно отличающиеся значения и лишь затруднят обучение и эксплуатацию нейронной сети.
Кроме того, с технической стороны тоже есть плюсы использования одной функции активации для всего нейронного слоя. В таком случае мы можем ограничиться одним целочисленным значением для хранения кода активации нейронов в слое независимо от количества нейронов. В то время как для хранения индивидуальных функций активации нам бы пришлось создавать целый вектор значений размером в число нейронов слоя.
Следующее, что нам потребуется знать при создании архитектуры нейронной сети, — это метод оптимизации весовых коэффициентов. Помните, в главе «Методы оптимизации нейронных сетей» мы рассмотрели шесть методов оптимизации. В предыдущей главе мы задали перечисление для их идентификации. Теперь можно воспользоваться этим перечислением и предоставить пользователю выбрать один из них.
Почему нам важно знать метод оптимизации сейчас, на стадии создания нейронной сети, а не ее обучения? Тут все очень просто. Разные методы оптимизации требуют разное количество объектов для хранения информации, поэтому при создании нейронной сети нужно создать все необходимые объекты. При этом так как у нас есть технические ограничения по памяти вычислительной машины, мы должны рационально ее использовать и не создавать лишние объекты.
При создании таких слоев как нормализация и Dropout нам потребуется некоторая специфическая информация. Для нормализации нам потребуется размер выборки нормализации (batch), а для Dropout нужно указать вероятность «выкидывания» нейронов в процессе обучения.
Забегая немного вперед, скажу о том, что для некоторых типов нейронных слоев нам еще потребуются размер входного и выходного окна. А также размер шага от начала одного входного окна до начала следующего окна.
Чтобы облегчить пользователю процесс создания последовательно идущих одинаковых слоев, добавим еще параметр для указания такой последовательности.
Таким образом, у нас набрался десяток параметров, которые пользователь должен указать для каждого слоя. Добавим сюда же общее количество слоев для создания в нейронной сети. Все это мы хотим получить от пользователя перед созданием нейронной сети. Мы не будем сильно усложнять процесс передачи данных, и для описания одного нейронного слоя создадим класс CLayerDescription с элементами для хранения указанных параметров.
class CLayerDescription : public CObject |
Обратите внимание, что создаваемый класс наследуется от класса CObject, который является базовым классом для всех объектов в MQL5. Это небольшой момент, который мы будет эксплуатировать немного позже.
Конструктор класса мы не будем чем-либо усложнять, а зададим лишь некоторые значения по умолчанию. Здесь вы можете использовать любые свои значения. Я же рекомендую указать наиболее часто используемые параметры. Это облегчит вам в последующем их указание в коде программы.
CLayerDescription::CLayerDescription(void) : type(defNeuronBase), |
Теперь вернемся к тому, почему важно было наследоваться от CObject. Здесь все довольно просто и прозаично: мы создали объект для описания одного нейронного слоя, но не всей нейронной сети. Мы еще не указали общее количество слоев и их последовательность.
Я решил не усложнять процесс и воспользоваться классом CArrayObj из стандартной библиотеки MQL5. Это класс динамического массива для хранения указателей на объекты CObject и их наследников. Следовательно, мы можем записать в него наши объекты описания нейронного слоя. Таким образом мы решаем вопрос с контейнером для хранения и передачи информации о нейронных сетях. Последовательность нейронных слоев будет соответствовать последовательности сохраненных описаний от входного слоя с нулевым индексом до выходного слоя.
На мой взгляд, это довольно простой и интуитивно понятный способ описания структуры нейронной сети. Но каждый читатель может воспользоваться своими наработками.