Руководство по Deeplearning4j

1. Вступление

В этой статье мы создадим простую нейронную сеть с помощью библиотеки deeplearning4j (dl4j) - современного и мощного инструмента для машинного обучения.

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

2. Что такое глубокое обучение?

Нейронные сети - это вычислительные модели, которые состоят из взаимосвязанных слоев узлов.

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

Обучение, по сути, заключается в сохранении некоторого числового состояния (весов) в узлах, что впоследствии влияет на вычисления. Обучающие примеры могут содержать элементы данных с особенностями и некоторые известные классы этих элементов (например, «этот набор 16 × 16 пикселей содержит рукописную букву« а »).

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

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

3. Настройка проекта

Чтобы использовать библиотеку, нам нужна как минимум Java 7. Кроме того, из-за некоторых собственных компонентов она работает только с 64-битной версией JVM.

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

$ java -version
java version "1.8.0__131"
Java(TM) SE Runtime Environment (build 1.8.0__131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

Во-первых, давайте добавим необходимые библиотеки в наш файл Maven pom.xml .

Мы извлечем версию библиотеки в запись свойства (для последней версии библиотек, посмотрите https://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org . deeplearning4j% 22[хранилище Maven Central]):

<properties>
    <dl4j.version>0.9.1</dl4j.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.nd4j</groupId>
        <artifactId>nd4j-native-platform</artifactId>
        <version>${dl4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.deeplearning4j</groupId>
        <artifactId>deeplearning4j-core</artifactId>
        <version>${dl4j.version}</version>
    </dependency>
</dependencies>

Обратите внимание, что зависимость nd4j-native-platform является одной из нескольких доступных реализаций.

Он опирается на собственные библиотеки, доступные для разных платформ (macOS, Windows, Linux, Android и т. Д.). Мы также могли бы переключить серверную часть на nd4j-cuda-8.0-platform , если бы мы хотели выполнять вычисления на графической карте, которая поддерживает модель программирования CUDA.

4. Подготовка данных

4.1. Подготовка файла набора данных

Мы напишем «Hello World» машинного обучения - классификацию набора данных цветов https://en.wikipedia.org/wiki/Iris flower data set[iris]. Это набор данных, собранных с цветов разных видов ( Iris setosa , Iris versicolor и Iris virginica__).

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

Мы собираемся использовать CSV-версию этих данных, где столбцы 0..3 содержат различные признаки вида, а столбец 4 содержит класс записи или виды, закодированные со значением 0, 1 или 2:

5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
...
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1
...

4.2. Векторизация и чтение данных

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

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

try (RecordReader recordReader = new CSVRecordReader(0, ',')) {
    recordReader.initialize(new FileSplit(
      new ClassPathResource("iris.txt").getFile()));

   //...
}

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

Но наш небольшой набор данных содержит только 150 записей, поэтому давайте сразу прочитаем все данные в память с помощью вызова iterator.next () .

  • Мы также указываем индекс столбца класса , который в нашем случае совпадает с количеством объектов (4) и общим количеством классов ** (3)

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

Мы указываем постоянное случайное начальное число (42) вместо вызова по умолчанию System.currentTimeMillis () , чтобы результаты перетасовки всегда были одинаковыми. Это позволяет нам получать стабильные результаты при каждом запуске программы:

DataSetIterator iterator = new RecordReaderDataSetIterator(
  recordReader, 150, FEATURES__COUNT, CLASSES__COUNT);
DataSet allData = iterator.next();
allData.shuffle(42);

4.3. Нормализация и расщепление

Еще одна вещь, которую мы должны сделать с данными перед тренировкой, это нормализовать их. Нормализация - это двухфазный процесс:

  • сбор статистики о данных (подходят)

  • изменение (преобразование) данных каким-либо образом, чтобы сделать их единообразными

  • Нормализация может отличаться для разных типов данных. **

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

Но для чисел нормализация обычно означает преобразование их в так называемое нормальное распределение. Класс NormalizerStandardize может помочь нам в этом:

DataNormalization normalizer = new NormalizerStandardize();
normalizer.fit(allData);
normalizer.transform(allData);

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

Первая часть будет использоваться на тренировке. Мы будем использовать вторую часть данных (которую сеть вообще не увидит) для проверки обученной сети.

Это позволило бы нам убедиться, что классификация работает правильно.

Мы возьмем 65% данных (0,65) для обучения и оставим 35% для тестирования:

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(0.65);
DataSet trainingData = testAndTrain.getTrain();
DataSet testData = testAndTrain.getTest();

5. Подготовка конфигурации сети

5.1. Fluent Configuration Builder

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

MultiLayerConfiguration configuration
  = new NeuralNetConfiguration.Builder()
    .iterations(1000)
    .activation(Activation.TANH)
    .weightInit(WeightInit.XAVIER)
    .learningRate(0.1)
    .regularization(true).l2(0.0001)
    .list()
    .layer(0, new DenseLayer.Builder().nIn(FEATURES__COUNT).nOut(3).build())
    .layer(1, new DenseLayer.Builder().nIn(3).nOut(3).build())
    .layer(2, new OutputLayer.Builder(
      LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .activation(Activation.SOFTMAX)
        .nIn(3).nOut(CLASSES__COUNT).build())
    .backprop(true).pretrain(false)
    .build();

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

5.2. Настройка параметров сети

  • Метод iterations () определяет количество итераций оптимизации. **

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

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

  • Activation () - это функция, которая запускается внутри узла для определения его вывода. **

Простейшей функцией активации была бы линейная f (x) = x. Но оказалось, что только нелинейные функции позволяют сетям решать сложные задачи с использованием нескольких узлов.

Доступно множество различных функций активации, которые мы можем найти в перечислении org.nd4j.linalg.activations.Activation . Мы также можем написать нашу функцию активации, если это необходимо. Но мы будем использовать предоставленную функцию гиперболического тангенса (tanh).

  • Метод weightInit () указывает один из многих способов установки начальных весов для сети. ** Правильные начальные весы могут оказать глубокое влияние на результаты обучения. Не вдаваясь в математику, давайте установим ее в форму гауссовского распределения ( WeightInit.XAVIER ), так как обычно это хороший выбор для начала.

Все остальные методы инициализации веса можно найти в перечислении org.deeplearning4j.nn.weights.WeightInit .

  • Скорость обучения ** является ключевым параметром, который глубоко влияет на способность сети к обучению.

Мы могли бы потратить много времени на настройку этого параметра в более сложном случае. Но для нашей простой задачи мы будем использовать довольно значимое значение 0,1 и настраивать его с помощью метода learningRate () builder.

  • Одной из проблем с обучением нейронных сетей является случай переобучения ** , когда сеть «запоминает» данные обучения.

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

  • Чтобы решить эту проблему, мы собираемся настроить регуляризацию l2 ** с помощью строки .regularization (true) .l2 (0.0001) . Регуляризация «штрафует» сеть за слишком большой вес и предотвращает переоснащение.

5.3. Построение сетевых уровней

Далее мы создаем сеть из плотных (также называемых полностью подключенными) слоев.

Первый слой должен содержать то же количество узлов, что и столбцы в обучающих данных (4).

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

Конечный выходной слой должен содержать количество узлов, соответствующих количеству классов (3). Структура сети показана на рисунке:

ссылка:/uploads/Untitled-Diagram.png%20447w[]

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

Это простой классификатор.

Наконец, чтобы завершить построение сети, мы настраиваем обратное распространение (один из наиболее эффективных методов обучения) и отключаем предварительное обучение строкой .backprop (true) .pretrain (false) .

6. Создание и обучение сети

Теперь давайте создадим нейронную сеть из конфигурации, инициализируем и запустим ее:

MultiLayerNetwork model = new MultiLayerNetwork(configuration);
model.init();
model.fit(trainingData);

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

INDArray output = model.output(testData.getFeatureMatrix());
Evaluation eval = new Evaluation(3);
eval.eval(testData.getLabels(), output);

Если мы теперь распечатаем eval.stats () , мы увидим, что наша сеть довольно хорошо классифицирует цветы ириса, хотя она трижды ошибочно приняла класс 1 за класс 2.

Examples labeled as 0 classified by model as 0: 19 times
Examples labeled as 1 classified by model as 1: 16 times
Examples labeled as 1 classified by model as 2: 3 times
Examples labeled as 2 classified by model as 2: 15 times

==========================Scores========================================
# of classes: 3
Accuracy: 0.9434
Precision: 0.9444
Recall: 0.9474
F1 Score: 0.9411
Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes)
========================================================================

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

7. Заключение

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

Как всегда, исходный код статьи доступен на over на GitHub .