В данной статье рассматриваются основные требования к реализации нейронных сетей, описываются возможности языка Java по созданию компонентов нейронных сетей. Также приводится анализ и сравнение уже существующих решений для данного языка и производится выбор технологий для конкретных категорий задач.
Ключевые слова: нейронные сети, Java, Neuroph, Deeplearning4j
В данный момент большую популярность в задачах обработки данных получают нейронные сети. Нейронная сеть представляет собой высокопараллельную динамическую систему с топологией направленного графа, которая может получать выходную информацию путем реакции ее состояния на выходные воздействия [1]. Нейронные сети решают широкий спектр задач: классификация объектов, выявление закономерностей, распознавание образов, прогнозирование, аппроксимация, сжатие данных.
Универсальность и востребованность нейронных сетей порождает задачу их реализации на различных языках программирования с использованием различных стеков технологий, что должно позволить использовать продвинутые технологии в любой сфере от мобильных приложений до серверных. Одним из универсальных языков программирования является Java. На данном языке возможно создавать как ПО для использования в крупных промышленных системах (Java EE), так и приложения для платформы Android.
Для реализации нейронной сети на любом языке программирования требуются несколько составляющих:
‒ непосредственное описание объектов нейронной сети, таких как нейрон, связь, слой, входы и выходы сети;
‒ представление в конструкциях языка архитектуры сети: связи между слоями, их количество и тип;
‒ программные функции, реализующие алгоритмы обучения сети, которые могут позволить задать количество эпох обучения, установить целевую погрешность;
‒ сохранение и загрузка параметров обученной сети.
При создании приложения можно использовать различные подходы. Первый предполагает кодирование всех узлов нейронной сети и дальнейшее их использование. Второй заключается в использовании готовых библиотек и требует программирования только самой архитектуры сети из ее составляющих. Подробнее рассмотрим каждый из вариантов.
Java является объектно-ориентированным языком. В данном случае каждый элемент нейронной сети может быть отображен в свой класс [2]. В случае кодирования всех узлов без использования библиотек необходимо создать класс для каждого из них. Далее описываются основные узлы, которые необходимо реализовать.
Нейрон — вычислительная единица, которая получает информацию, передает ее дальше и производит над ней простые вычисления. Нейроны можно поделить на три группы: входные, скрытые и выходные [3]. В рамках языка Java возможно организовать абстрактный класс, который описывает нейрон, его входы и выходы, а затем создать наследников, которые будут выполнять роли входных, скрытых и выходных нейронов.
Синапс — связь между нейронами. Синапс описывается всего одним параметром — весом. Данные веса используются при передаче сигнала между нейронами. Логику синапса можно внести в класс нейрона. Таким образом один объект будет одновременно отвечать и за передачу сигнала, и за использование весовых коэффициентов.
Для функционирования нейронной сети необходимо использование функции активации, которая нормализует данные, а также фактически является логическим ядром нейрона: она позволяет получить выходные значения сигнала по входному. В реальных нейронных сетях чаще всего используются следующие функции активации:
‒ линейная функция ;
‒ гиперболический тангенс ;
‒ сигмоидная функция .
Начиная с Java версии 1.8 стало возможно объявлять лямбда-функции. Функции такого вида можно передавать как аргументы другой функции, но, самое главное, они могут являться полем класса. В примере далее показывается объявление простейшей линейной функции.
Function
Подобный подход позволяет менять поведение нейрона изменяя одно поле класса. Так как количество функций активации ограничено, задачей становится создать объект-перечисление enum, который будет реализовывать каждую из функций и предоставлять к ней доступ.
Нейронные сети зачастую состоят из большого количества слоев. В данном случае достаточной абстракцией является массив или список из нейронов. После создания необходимых нейронов они группируются в такие структуры и формируют архитектуру нейронной сети.
После создания нейронной сети необходимо провести ее обучение. Обучение производится на тренировочных наборах (сетах) — последовательностях данных, которыми может оперировать нейронная сеть. Обработка тренировочного сета может проходить в несколько этапов — эпох. На каждой из эпох нейронная сеть обрабатывает полный набор данных. Целью обучения является снижение ошибки. Ошибка — величина отражающая расхождение между ожидаемыми и полученными результатами. Ошибка вычисляется каждую эпоху и при успешном обучении должна становиться меньше.
Для вычисления ошибки используются различные способы:
‒ Mean Squared Error (MSE) ;
‒ Root MSE;
‒ Arctan .
Каждый из способов имеет свои особенности, но при этом они реализуют одинаковую логику: чем ближе выходной вектор к ожидаемому, тем меньше погрешность. Данные функции можно реализовать аналогично функциям активации.
Обучение на различных наборах по эпохам реализуется с помощью цикла for. При этом важно учитывать порядок вложенности: нейронная сеть тренируется на полном наборе данных, и только после этого возможен переход к следующей эпохе [4].
// Верный подход
for (int i = 0; i < maxEpoch; i++) {
for (int j = 0; j < trainSetLength; j++) {
// Обучение сети
}
}
// Неверный подход
for (int i = 0; i < trainSetLength; i++) {
for (int j = 0; j < maxEpoch; j++) {
// Обучение сети
}
}
Программирование всех элементов сети самостоятельно имеет смысл, когда требуется особенное поведение, дополнительная оптимизация, в нейронной сети используется небольшое количество элементов. Подобный подход позволяет уменьшить размер итогового исполняемого файла, так как в него входят только действительно требующиеся структуры и функции, но увеличивает сроки разработки ПО. В случае ограниченности ресурсов времени целесообразно использовать библиотеки. Далее приводится описание основных возможностей Neuroph и Deeplearning4j (DL4J) — библиотек, позволяющих упростить программирование нейронных сетей.
Neuroph — легковесный фреймворк для языка Java. Он позволяет создавать сети с традиционными архитектурами:
‒ однослойная нейронная сеть (Adaline);
‒ перцептрон;
‒ многослойный перцептрон с обратным распространением;
‒ сеть Хопфилда;
‒ сеть Кохонена;
‒ cеть радиально-базисных функций.
Neuroph состоит из двух компонентов: логики нейронных сетей и графического приложения Neuroph Studio, которое позволяет конфигурировать сеть без написания кода [5]. В графическом режиме требуется выбрать тип сети, ее параметры, задать обучающую выборку. После этого становится доступным просмотр структуры получившейся сети, появляется возможность проверки качества обучения сети.
Рис. 1. Представление графа сети в Neuroph Studio
Помимо графического интерфейса программисту предоставляется доступ к классам библиотеки. Конфигурирование сети происходит в объектно-ориентированном стиле. В следующем примере формируется тренировочный набор данных для сети, на котором тренируется сеть, представляющая собой простейший перцептрон с двумя входами и одним выходом.
// создание набора для реализации функции логического И
DataSet trainingSet = new DataSet(2, 1);
trainingSet.addRow(new DataSetRow(new double[]{0, 0}, new double[]{0}));
trainingSet.addRow(new DataSetRow(new double[]{0, 1}, new double[]{0}));
trainingSet.addRow(new DataSetRow(new double[]{1, 0}, new double[]{0}));
trainingSet.addRow(new DataSetRow(new double[]{1, 1}, new double[]{1}));
// создание НС типа перцептрон
NeuralNetwork myPerceptron = new Perceptron(2, 1);
// обучение на наборе
myPerceptron.learn(trainingSet);
// сохранение обученной сети в файл
myPerceptron.save("mySamplePerceptron.nnet");
Использование Neuroph Studio может упростить обучение теории нейронных сетей, а библиотека Neuroph позволяет легко конфигурировать требуемую архитектуру сети. Данную библиотеку можно расширять за счет добавления новых типов сетей и способов обучения.
Deeplearning4j — библиотека, реализующая глубокое обучение. Глубокое обучение — отдельный класс задач, в который входит, например, распознавание образов на изображениях. Для глубокого обучения требуется больше вычислительных ресурсов, чем для классических нейронных сетей. В отличие от Neuroph сложные вычисления в DL4J осуществляются отдельным модулем, который работает вне зависимости от JVM, что позволяет ускорить работу библиотеки. Вычисления могут также производиться на распределенных ресурсах и на графических ускорителях [6].
DL4J позволяет создавать сверточные нейронные сети, что важно при обработке изображений. Все конфигурирование производится с помощью программного кода. В следующем примере показано определение сети LeNet. LeNet — это классическая сверточная сеть, которая состоит из 5 слоев.
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.layer(1, maxPool("maxpool1", new int[]{2,2}))
.layer(2, conv5x5("cnn2", 100, new int[]{5, 5}, new int[]{1, 1}, 0))
.layer(3, maxPool("maxool2", new int[]{2,2}))
.layer(4, new DenseLayer.Builder().nOut(500).build())
.layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(numLabels)
.activation(Activation.SOFTMAX)
.build())
.backprop(true).pretrain(false)
.setInputType(InputType.convolutional(height, width, channels))
.build();
DL4J использует сверточные, полносвязные и пулинговые слои, что позволяет реализовать практически любую архитектуру нейронной сети для глубокого обучения.
В данной статье были проанализированы три подхода к реализации нейронных сетей. Каждый из подходов имеет свои преимущества и сферы применения. Программирование с нуля хорошо ложится на объектную модель и позволяет учесть особенности конкретной сети, чтобы использовать только те элементы, которые необходимы. Neuroph представляет графический интерфейс и упрощает создание архитектуры сети, давая возможность реализовать большинство известных типов нейронных сетей. Deeplearning4j позволяет решать задачи глубокого обучения, а также оптимизирует вычисления за счет использования распределенных ресурсов. Основой для выбора подхода является задача, которую необходимо решить.
Литература:
- Галушкин А. И. Нейронные сети: Основы теории. — М.: Горячая Линия — Телеком, 2012. — 496 с.
- Васильев А. Н. Java. Объектно-ориентированное программирование. — СПб.: Питер, 2011. — 400 с.
- Тархов Д. А. Нейросетевые модели и алгоритмы. Справочник. — М.: Радиотехника, 2014. — 352 с.
- Рашитов Э. Э., Стоякова К. Л., Ибраев Р. Р. Модель математической нейронной сети // Молодой ученый. — 2017. — № 15 (149). — С. 77–80.
- Documentation // Java Neural Network Framework Neuroph. URL: http://neuroph.sourceforge.net/documentation.html (дата обращения: 25.04.2017).
- Deeplearning4j: Open-source, Distributed Deep Learning for the JVM // Deep Learning for Java. URL: https://deeplearning4j.org/ (дата обращения: 22.04.2017).