Arthur C. Clarke famously stated that “any sufficiently advanced technology is indistinguishable from magic.” No current technology embodies this statement more than neural networks and deep learning. And like any good magic it not only dazzles and inspires but also puts fear into people’s hearts. This primer sheds some light on how neural networks work, hopefully adding to the wonder while reducing the fear.
One known property of artificial neural networks (ANNs) is that they are universal function approximators. This means that any mathematical function can be represented by a neural network. Let’s see how this works.
Universal function approximation may sound like a powerful spell recently discovered. In fact, function approximation has been around for centuries. Common techniques include the Taylor series and the Fourier series approximations. Recall that given enough terms, a Taylor series can approximate any function to a certain level of precision about a given point, while a Fourier series can approximate any periodic function. By definition, these series are linear combinations of multiple terms. The same is true of neural networks, where output values are linear combinations of their input. The difference is that neural networks can approximate a function over a set of points.
To illustrate, consider the function
Figure 1. The saddle function 2x^2 – 3y^2 + 1.
For sake of argument, let’s attempt to approximate this function with a
require "nn" model = nn.Sequential() model:add(nn.Linear(2,1))
After training, we can apply the training set to the model to see what the neural network thinks
Let’s bring in the cavalry and add a hidden layer to create a
require "nn" model = nn.Sequential() model:add(nn.Linear(2,10)) model:add(nn.Linear(10,1))
Alas, it isn’t so. The result is still a plane, albeit slightly rotated.
Before you lose faith in artificial neural networks, let’s understand what’s happening. In the single layer network with one output node, we said
Linearity is an exceptionally useful property, but in terms of functions, it’s not particularly interesting. The nonlinear world is far more mysterious and exciting, and so it is with neural networks. Nonlinearity is injected into neural networks through the use of an activation (aka transfer) function. This function
require "nn" model = nn.Sequential() model:add(nn.Linear(2,10)) model:add(nn.Tanh()) model:add(nn.Linear(10,1))
The result of this model is starting to look like the target function. So we’re on the right track.
However, the result is rather blocky. This suggests that there are not enough parameters to properly capture the nonlinearity. By increasing the hidden layer to 40 nodes, we can smooth this out further.
I used Torch to generate the training set and the neural networks. To analyze the results I used standard R.
Training the network requires a test dataset. I generated one (called
trainset below) using Lua that conformed to the Torch spec for row-major datasets. To train the model, I used the
SmoothL1Criterion coupled with stochastic gradient descent. Good results require fiddling with the
learningRate and the
learningRateDecay on the
trainer. (I don’t include these parameters as this is related to an exercise for my students.)
criterion = nn.SmoothL1Criterion() trainer = nn.StochasticGradient(model, criterion) trainer:train(trainset)
To render the plots, I evaluate the trained model against the complete training set and write out a CSV. Then I load into R and use the
scatterplot3D package to render the surface.
Function approximation is a powerful capability of neural networks. In theory any function can be approximated, but constructing such a network is not so trivial. One of the key lessons with neural networks is that you cannot blindly create networks and expect them to yield something useful. Not only does it take patience, but it takes an understanding and appreciation of the theory to lead you down the correct path.