How to Use Deeplearning4J in Android Apps

  Programming

Note: This tutorial was updated on 18th January 2017.

Generally speaking, training a neural network is a task best suited for powerful computers with multiple GPUs. But what if you want to do it on your humble Android phone or tablet? Well, it’s definitely possible. Considering an average Android device’s specifications, however, it will most likely be quite slow. If that’s not a problem for you, keep reading.

In this tutorial, I’ll show you how to use Deeplearning4J, a popular Java-based deep learning library, to create and train a neural network on an Android device.

Prerequisites

For best results, you’ll need the following:

  • An Android device or emulator that runs API level 21 or higher, and has about 200 MB of internal storage space free. I strongly suggest you use an emulator first because you can quickly tweak it in case you run out of memory or storage space.
  • Android Studio 2.2 or newer

Configuring Your Android Studio Project

To be able to use Deeplearning4J in your project, add the following compile dependencies to your app module’s build.gradle file:

compile 'org.deeplearning4j:deeplearning4j-core:0.7.2'
compile 'org.nd4j:nd4j-native:0.7.2'
compile 'org.nd4j:nd4j-native:0.7.2:android-x86'

As you can see, DL4J depends on ND4J, short for N-Dimensions for Java, which is a library that offers fast n-dimensional arrays. ND4J internally depends on a library called JavaCPP, which contains platform-specific native code. Therefore, you must load a version of ND4J that matches the architecture of your Android device. Because I own an x86 device, I’m using android-x86 as the platform.

Dependencies of DL4J and ND4J have several files with identical names. In order to avoid build errors, add the following exclude parameters to your packagingOptions.

packagingOptions {
    exclude 'META-INF/DEPENDENCIES'
    exclude 'META-INF/DEPENDENCIES.txt'
    exclude 'META-INF/LICENSE'
    exclude 'META-INF/LICENSE.txt'
    exclude 'META-INF/license.txt'
    exclude 'META-INF/NOTICE'
    exclude 'META-INF/NOTICE.txt'
    exclude 'META-INF/notice.txt'
    exclude 'META-INF/INDEX.LIST'
}

Furthermore, your compiled code will have well over 65,536 methods. To be able to handle this condition, add the following option in the defaultConfig:

multiDexEnabled true

And now, press Sync Now to update the project.

Starting an Asynchronous Task

Training a neural network is CPU-intensive, which is why you wouldn’t want to do it in your application’s UI thread. DL4J does not train its networks asynchronously by default. Therefore, you must spawn a separate thread now using the AsyncTask class.

AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        createAndUseNetwork();
    }
});

Because the method createAndUseNetwork() doesn’t exist yet, create it.

private void createAndUseNetwork() {

}

Creating a Neural Network

DL4J has a very intuitive API. Let us now use it to create a simple multi-layer perceptron with hidden layers. It will take two input values, and spit out two output values. To create the layers, we’ll use the DenseLayer and OutputLayer classes. Accordingly, add the following code to the createAndUseNetwork() method you created in the previous step:

DenseLayer inputLayer = new DenseLayer.Builder()
        .nIn(2)
        .nOut(3)
        .name("Input")
        .build();

DenseLayer hiddenLayer = new DenseLayer.Builder()
        .nIn(3)
        .nOut(2)
        .name("Hidden")
        .build();

OutputLayer outputLayer = new OutputLayer.Builder()
        .nIn(2)
        .nOut(2)
        .name("Output")
        .activation(Activation.SOFTMAX)
        .build();

SOFTMAX is one of the many activation functions offered by DL4J. For now, we’ll be using it for the output layer.

Now that our layers are ready, let’s create a NeuralNetConfiguration.Builder object to configure our neural network.

NeuralNetConfiguration.Builder nncBuilder = new NeuralNetConfiguration.Builder();
nncBuilder.iterations(60000);
nncBuilder.learningRate(0.1);

In the above code, I’ve set the values of two important parameters: learning rate and number of iterations. Feel free to change those values. In my experience, more iterations and lower learning rates tend to produce better results.

We must now create a NeuralNetConfiguration.ListBuilder object to actually connect our layers and specify their order.

NeuralNetConfiguration.ListBuilder listBuilder = nncBuilder.list();
listBuilder.layer(0, inputLayer);
listBuilder.layer(1, hiddenLayer);
listBuilder.layer(2, outputLayer);

Additionally, enable backpropagation by adding the following code:

listBuilder.backprop(true);

At this point, we can generate and initialize our neural network as an instance of the MultiLayerNetwork class.

MultiLayerNetwork myNetwork = new MultiLayerNetwork(listBuilder.build());
myNetwork.init();

Creating Training Data

To create our training data, we’ll be using the INDArray class, which is provided by ND4J. Here’s what our training data will look like:

INPUTS      EXPECTED OUTPUTS
------      ----------------
0 0         0 1
0 1         1 0
1 0         1 0
1 1         0 1

Looking at just the first column of the expected outputs section, you can probably tell that our neural network will behave like an XOR gate. If you are wondering why the expected outputs section has a second column, it’s because our neural network has two output neurons for it to be trained quickly. I’ve found that using a single output neuron takes too many iterations to train.

The training data has four samples, and you must mention it in your code.

final int NUM_SAMPLES = 4;

And now, create two INDArray objects for the inputs and expected outputs, and initialize them with zeroes.

INDArray trainingInputs = Nd4j.zeros(NUM_SAMPLES, inputLayer.getNIn());
INDArray trainingOutputs = Nd4j.zeros(NUM_SAMPLES, outputLayer.getNOut());

Note that the number of columns in the inputs array is equal to the number of neurons in the input layer. Similarly, the number of columns in the outputs array is equal to the number of neurons in the output layer.

Filling those arrays with the training data is easy. Just think of them as two matrices and use the putScalar() method to put the right values in the right indices:

// If 0,0 show 0
trainingInputs.putScalar(new int[]{0,0}, 0);
trainingInputs.putScalar(new int[]{0,1}, 0);
trainingOutputs.putScalar(new int[]{0,0}, 0);
trainingOutputs.putScalar(new int[]{0,1}, 1);

// If 0,1 show 1
trainingInputs.putScalar(new int[]{1,0}, 0);
trainingInputs.putScalar(new int[]{1,1}, 1);
trainingOutputs.putScalar(new int[]{1,0}, 1);
trainingOutputs.putScalar(new int[]{1,1}, 0);

// If 1,0 show 1
trainingInputs.putScalar(new int[]{2,0}, 1);
trainingInputs.putScalar(new int[]{2,1}, 0);
trainingOutputs.putScalar(new int[]{2,0}, 1);
trainingOutputs.putScalar(new int[]{2,1}, 0);

// If 1,1 show 0
trainingInputs.putScalar(new int[]{3,0}, 1);
trainingInputs.putScalar(new int[]{3,1}, 1);
trainingOutputs.putScalar(new int[]{3,0}, 0);
trainingOutputs.putScalar(new int[]{3,1}, 1);

We won’t be using the INDArray objects directly. Instead, we’ll convert them into a DataSet.

DataSet myData = new DataSet(trainingInputs, trainingOutputs);

At this point, we can start the training by calling the fit() method of the neural network and passing the data set to it.

myNetwork.fit(myData);

Be patient while the fit() method runs. You might have to wait several minutes for it to complete.

And that’s all there is to it. Your neural network is ready to be used.

Using the Neural Network

To use the neural network you just trained, you must use its output() method, which takes an INDArray as its only argument. As you might have guessed, the INDArray will contain your input values.

The following code shows you how to fetch the output for the inputs [1,1]:

// Create input
INDArray actualInput = Nd4j.zeros(1,2);
actualInput.putScalar(new int[]{0,0}, 1);
actualInput.putScalar(new int[]{0,1}, 1);

// Generate output
INDArray actualOutput = myNetwork.output(actualInput);
Log.d("myNetwork Output", actualOutput.toString());

If your network’s trained correctly, you should see that the contents of the output INDArray object are [0,1]. You can check the output in logcat. It should look like this:

01-18 09:33:34.052  3236  3249 D myNetwork Output: [0.00, 1.00]

Conclusion

In this tutorial, you saw how easy it is to create and train a neural network using the Deeplearning4J library in an Android Studio project. I’d like to warn you, however, that training a neural network on a low-powered, battery operated device might not always be a good idea.

If you found this article useful, please share it with your friends and colleagues!