From dd2b47d206c81f9a76833d31c1f988537488a29d Mon Sep 17 00:00:00 2001 From: Dennis Atabay Date: Thu, 17 Aug 2017 10:57:21 +0200 Subject: [PATCH] add classification example --- doc/examples.rst | 98 ++++++++++++++++++ doc/img/example_python_classification.png | Bin 0 -> 17352 bytes .../examples/example_classification_mnist.py | 71 +++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 doc/img/example_python_classification.png create mode 100644 python/examples/example_classification_mnist.py diff --git a/doc/examples.rst b/doc/examples.rst index ed69da6..5378fdd 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -994,3 +994,101 @@ The function ``prn.BPTT()`` uses the Back Propagation Through Time algorithm and .. code-block:: matlab g_bptt = BPTT(net,data); + + +Classification (MNIST Data) +------------------------ + +In this example a neural network is used to learn to recognize handwritten digits. +Thefore the MNIST dataset hosted on `Yann LeCun's website`_ is used. +The data set consists of 60,000 data points for training and 10,000 data points for testing. To reduce the size of the data file, here only 25,000 data points for training and 5,000 for testing are used. +Each data point is defined by an 28X28 pixel image (784 numbers) and the +corresponing number represesented by a 10 element vector +(one element for each digit 0,1,2,3,4,5,6,7,8,9). For the number n, +only the n-th element is 1, all others are zero. So the vector [0 0 0 0 0 1 0 0 0 0] represents the number 5. +A more detailed explanation of the MNIST data can be found in the `Tensorflow tutorial`_. + +.. _ Yann LeCun's website: http://yann.lecun.com/exdb/mnist/page +.. _Tensorflow tutorial: https://www.tensorflow.org/get_started/mnist/beginners + +Python +^^^^^^^^^^^ + +At first the needed packages are imported. pickle for reading the data, matplotlib for plotting the results, numpy for its random function and pyrenn for the neural network. + +:: + + import matplotlib as mpl + import matplotlib.pyplot as plt + import pickle + import numpy as np + import pyrenn as prn + +Then the training input data P and output (target) data Y as well as the test input data Ptest and output data Ytest is read from the given pickle file. Each image is defined by the value of its 784 pixel, so P is a 2d array of size (784,Q), where Q is the number of data samples (25,000). Y is defined by an 10 element vector, which gives us a 2d array of size (10,Q=5000). + + +:: + + mnist = pickle.load( open( "MNIST_data.pkl", "rb" ) ) + P = mnist['P'] + Y = mnist['Y'] + Ptest = mnist['Ptest'] + Ytest = mnist['Ytest'] + +Then the neural network is created. Since we have a system with 28*28 inputs and 10 outputs, we need a neural network with the same number of inputs and outputs. For this system we choose a neural network with one hidden layer with 10 neurons. Since there is no interconnection between the images, we do not need a recurrent network and no delayed inputs, so we do not have to change the delay inputs. + + :: + + net = prn.CreateNN([28*28,10,10]) + +Because training the network with all the available training data would need a lot of memory and time, we randomly extract a batch of 1000 data samples and use it to train the network. Because we want to use as much information of our data as possible, we only perform one iteration (k_max=1) and then extract a new batch. In this example we do this 20 times, so we train the net for 20 iterations, but each iteration with new training data. +``verbose=True`` activates diplaying the error during training. + + :: + + batch_size = 1000 + number_of_batches=20 + + for i in range(number_of_batches): + r = np.random.randint(0,25000-batch_size) + Ptrain = P[:,r:r+batch_size] + Ytrain = Y[:,r:r+batch_size] + + #Train NN with training data Ptrain=input and Ytrain=target + #Set maximum number of iterations k_max + #Set termination condition for Error E_stop + #The Training will stop after k_max iterations or when the Error <=E_stop + net = prn.train_LM(Ptrain,Ytrain,net, + verbose=True,k_max=1,E_stop=1e-5) + print('Batch No. ',i,' of ',number_of_batches) + +After the training is finished, we can use the neural network. Therefore we choose 9 random samples of the test data set and use the input to calculate the NN outputs +Then we can plot the results, comparing the output of the neural network (number above the image) with the training (image). + + :: + + idx = np.random.randint(0,5000-9) + P_ = Ptest[:,idx:idx+9] + Y_ = prn.NNOut(P_,net) + + fig = plt.figure(figsize=[11,7]) + gs = mpl.gridspec.GridSpec(3,3) + + for i in range(9): + + ax = fig.add_subplot(gs[i]) + + y_ = np.argmax(Y_[:,i]) #find index with highest value in NN output + p_ = P_[:,i].reshape(28,28) #Convert input data for plotting + + ax.imshow(p_) #plot input data + ax.set_xticks([]) + ax.set_yticks([]) + ax.set_title(str(y_), fontsize=18) + + plt.show() + +.. figure:: img/example_python_classification.png + :width: 95% + :align: center + diff --git a/doc/img/example_python_classification.png b/doc/img/example_python_classification.png new file mode 100644 index 0000000000000000000000000000000000000000..43916caed429b29b267445d5fb0730ab6356eaab GIT binary patch literal 17352 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