# How can I train a neural net to decide the 3rd digit of a number?

As a "toy problem" to explore the new neural net library, I wanted to train a neural net with:

Input: 1 real number in the range [0, 1)
Output: the nth digit (i.e. one of {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}).

Example:

0.548923584 -> 8
0.678438456 -> 8
0.894258973 -> 4

The following simple NetChain does better than average at classifying the 2nd digit:

digit = 2;

target[x_] := Part[First[RealDigits[x, 10, 6, -1]], digit]

net =
NetChain[{100, Ramp, 100, Ramp, 100, Ramp, 100, Ramp, 100, Ramp,
100, Ramp, 10, SoftmaxLayer[]}, "Input" -> {1},
"Output" -> NetDecoder[{"Class", Range[0, 9]}]];

trainingData =
Table[{x} -> target[x], {x, RandomReal[1, 10^5]}];

trained = NetTrain[net, trainingData];

N[
Mean[Boole[
Table[target[x] == trained[{x}], {x, RandomReal[1, 1000]}]]]]

(* 0.55 *)


Very simple variants on the above can be trained to be 90% accurate. But I couldn't get something similar to classify the 3rd digit well.

What's a net design that can do well at classifying the 3rd digit of numbers?

More generally, what are the net design considerations that one should take into account to solve this type of problem?

• What I would call the overall "design consideration" is that if you want to classify individual digits of a given number you should make sure that each digit is weighted the same and not considered numerically (e.g in 0.0000000001 the 1 at the end is negligible numerically compared to all the digits before it, so classification on this minute detail is not going to be very effective) Oct 5, 2016 at 8:10
• Consider the following toy task analytically: computing last digit of an integer on range 0 to 99 with only tools you have at your disposal with neural networks. Can you do it without essentially listing all the different cases of tens separately? I think this is not easy unless you have discrete-valued outputs in some parts of the network... Oct 5, 2016 at 8:30
• @kirma I am not quite sure if I understood your comment fully but I think the gist of what you said is that one would theoretically need all possibly permutations of n-digit numbers to fully train the network? I don't think this is necessarily the case (and a total coverage of the training data is rare in real life anyway) because the network can generalize on the input it is shown (if not trained to overfit). Oct 5, 2016 at 8:35
• @Sascha Even more extreme case to consider would be the following: input is a two-bit integer (as single input), and output is the least significant bit. What is the simplest network that can solve this problem? How does the network size scale with amount of preceding bits? How do these networks look like? (These are just random thoughts, not an answer!:) Oct 5, 2016 at 8:45
• @kirma I don't know about such discrete problems but for any continuous problem there is the Universal approximation theorem. Oct 5, 2016 at 8:53

An approach is to supply the digits as individual inputs to the network using NetGraph. A possible network (for three input digits e.g. 0.123) could be

net = NetGraph[{
(*Layers*)
Ramp, Ramp, Ramp, CatenateLayer[], DotPlusLayer,
DotPlusLayer, DotPlusLayer, SoftmaxLayer[],
CrossEntropyLossLayer["Index","Target" -> NetEncoder[{"Class", Range[0, 9]}]]},

(*Layer ordering*)
{NetPort["Digit1"] -> 1, NetPort["Digit2"] -> 2, NetPort["Digit3"] -> 3,
{1, 2, 3} -> 4, 4 -> 5 -> 6 -> 7 -> 8 -> 9, 8 -> NetPort["Output"]},

(*Input and Output specification*)
"Digit1" -> NetEncoder[{"Class", Range[0, 9], "UnitVector"}],
"Digit2" -> NetEncoder[{"Class", Range[0, 9], "UnitVector"}],
"Digit3" -> NetEncoder[{"Class", Range[0, 9], "UnitVector"}],
"Output" -> NetDecoder[{"Class", Range[0, 9]}]] Note especially the format of the inputs as one-hot-encoded-vectors (via the "UnitVector"-option of NetEncoder) and the specification of the loss layer. Extending the network above for more input digits (e.g. 0.1234) requires an additional input port per digit that gets connected to the common CatenateLayer.

To generate training data I wrote the helper functions

randomDigits[] := RandomReal[{0, 1}, WorkingPrecision -> 3] // RealDigits // First

toAssociation[targetDigitIndex_][list_] :=
Association["Digit1" -> list[], "Digit2" -> list[], "Digit3" -> list[],
"Target" -> list[[targetDigitIndex]]]

generateTrainingData[targetDigitIndex_, n_] :=
toAssociation[targetDigitIndex] /@ Table[randomDigits[], n] //Merge[#, Identity] &


The training data format corresponds to the third variant listed in the documentation Thus with

trainingData = generateTrainingData[2, 1000];
trained = NetTrain[net, trainingData, "Loss"]


one gets a trained version of the net above for classifying the second digit (for any other digit just change the first argument to generateTrainingData accordingly). The result can be checked with inputs of the form

trained[<|"Digit1" -> 1, "Digit2" -> 2, "Digit3" -> 3, "Target" -> 5|>, "Output"]
(*yields 2*)


Note that a "Target" has to be supplied (which does however not influence the output). With NetExtract it should be possible to extract only the classification part of the network if so desired (removing the Target and Loss-nodes).

• Impressive. Could you give a reference for learning more about this? Oct 5, 2016 at 8:56
• Do you mean Neural Networks in general or just the implementation? Oct 5, 2016 at 9:01
• @Neural Networks in general which fit with Mathematica's functionality. Oct 5, 2016 at 9:25
• This is a good introduction to the theory of neural networks Oct 5, 2016 at 14:23
• It is very fun when I see this answer after I have learned some neural-networks.I think the result is net = PartLayer..We don't need to train it.If not the parameter will not be changed..
– yode
Sep 27, 2017 at 18:05