Ръководство за задълбочаване 4j

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 . Ще извлечем версията на библиотеката в запис на свойство (за най-новата версия на библиотеките вижте централното хранилище на Maven):

 0.9.1    org.nd4j nd4j-native-platform ${dl4j.version}   org.deeplearning4j deeplearning4j-core ${dl4j.version}  

Имайте предвид, че зависимостта nd4j-native-platform е една от няколкото налични реализации.

Той разчита на собствени библиотеки, достъпни за много различни платформи (macOS, Windows, Linux, Android и др.). Можем също така да превключим бекенда на nd4j-cuda-8.0-платформа , ако искаме да изпълним изчисления на графична карта, която поддържа CUDA модела за програмиране.

4. Подготовка на данните

4.1. Подготовка на файла DataSet

Ще напишем „Hello World” на машинното обучение - класификация на набора от данни за цветя на ириса. Това е набор от данни, събрани от цветята на различни видове ( 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);

Сега, когато данните са подготвени, трябва да разделим набора на две части.

Първата част ще бъде използвана в тренировъчна сесия. Ще използваме втората част от данните (които мрежата изобщо не би видяла), за да тестваме обучената мрежа.

This would allow us to verify that the classification works correctly. We will take 65% of the data (0.65) for the training and leave the rest 35% for the testing:

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

5. Preparing the Network Configuration

5.1. Fluent Configuration Builder

Now we can build a configuration of our network with a fancy fluent 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();

Even with this simplified fluent way of building a network model, there’s a lot to digest and a lot of parameters to tweak. Let’s break this model down.

5.2. Setting Network Parameters

The iterations() builder method specifies the number of optimization iterations.

The iterative optimization means performing multiple passes on the training set until the network converges to a good result.

Usually, when training on real and large datasets, we use multiple epochs (complete passes of data through the network) and one iteration for each epoch. But since our initial dataset is minimal, we'll use one epoch and multiple iterations.

The activation() is a function that runs inside a node to determine its output.

The simplest activation function would be linear f(x) = x. But it turns out that only non-linear functions allow networks to solve complex tasks by using a few nodes.

There are lots of different activation functions available which we can look up in the org.nd4j.linalg.activations.Activation enum. We could also write our activation function if needed. But we'll use the provided hyperbolic tangent (tanh) function.

The weightInit() method specifies one of the many ways to set up the initial weights for the network. Correct initial weights can profoundly affect the results of the training. Without going too much into the math, let’s set it to a form of Gaussian distribution (WeightInit.XAVIER), as this is usually a good choice for a start.

All other weight initialization methods can be looked up in the org.deeplearning4j.nn.weights.WeightInit enum.

Learning rate is a crucial parameter that profoundly affects the ability of the network to learn.

We could spend a lot of time tweaking this parameter in a more complex case. But for our simple task, we'll use a pretty significant value of 0.1 and set it up with the learningRate() builder method.

One of the problems with training neural networks is a case of overfitting when a network “memorizes” the training data.

This happens when the network sets excessively high weights for the training data and produces bad results on any other data.

To solve this issue, we’re going to set up l2 regularization with the line .regularization(true).l2(0.0001). Regularization “penalizes” the network for too large weights and prevents overfitting.

5.3. Building Network Layers

Next, we create a network of dense (also known as fully connect) layers.

The first layer should contain the same amount of nodes as the columns in the training data (4).

The second dense layer will contain three nodes. This is the value we can variate, but the number of outputs in the previous layer has to be the same.

The final output layer should contain the number of nodes matching the number of classes (3). The structure of the network is shown in the picture:

After successful training, we'll have a network that receives four values via its inputs and sends a signal to one of its three outputs. This is a simple classifier.

Finally, to finish building the network, we set up back propagation (one of the most effective training methods) and disable pre-training with the line .backprop(true).pretrain(false).

6. Creating and Training a Network

Сега нека създадем невронна мрежа от конфигурацията, инициализираме и стартираме:

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.

Както винаги, изходният код на статията е достъпен в GitHub.