# Basic MNIST Tutorial based on http://neuralnetworksanddeeplearning.com/¶

The code below was initially copied from the public git repository referenced in the ebook above. Authorship of the original code is by Michael Nielson http://michaelnielsen.org/. I have adapted that code to stand alone in this notebook for convenience.

The notebook uses a Random Forest algorithm instead of the neural network in the original post.

That code, and the code in this IPython Notebook, is subject to the MIT License:

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

In [1]:
"""
~~~~~~~~~~~~

A library to load the MNIST image data.  For details of the data
structures that are returned, see the doc strings for load_data
and load_data_wrapper.  In practice, load_data_wrapper is the
function usually called by our neural network code.
"""

#### Libraries
# Standard library
import cPickle
import gzip

# Third-party libraries
import numpy as np
import numpy  # Yes, it's poor practise to import into two different namespaces.

"""Return the MNIST data as a tuple containing the training data,
the validation data, and the test data.

The training_data is returned as a tuple with two entries.
The first entry contains the actual training images.  This is a
numpy ndarray with 50,000 entries.  Each entry is, in turn, a
numpy ndarray with 784 values, representing the 28 * 28 = 784
pixels in a single MNIST image.

The second entry in the training_data tuple is a numpy ndarray
containing 50,000 entries.  Those entries are just the digit
values (0...9) for the corresponding images contained in the first
entry of the tuple.

The validation_data and test_data are similar, except
each contains only 10,000 images.

This is a nice data format, but for use in neural networks it's
helpful to modify the format of the training_data a little.
That's done in the wrapper function load_data_wrapper(), see
below.
"""
f = gzip.open('mnist.pkl.gz', 'rb')
f.close()
return (training_data, validation_data, test_data)

"""Return a tuple containing (training_data, validation_data,
test_data). Based on load_data, but the format is more
convenient for use in our implementation of neural networks.

In particular, training_data is a list containing 50,000
2-tuples (x, y).  x is a 784-dimensional numpy.ndarray
containing the input image.  y is a 10-dimensional
numpy.ndarray representing the unit vector corresponding to the
correct digit for x.

validation_data and test_data are lists containing 10,000
2-tuples (x, y).  In each case, x is a 784-dimensional
numpy.ndarry containing the input image, and y is the
corresponding classification, i.e., the digit values (integers)
corresponding to x.

Obviously, this means we're using slightly different formats for
the training data and the validation / test data.  These formats
turn out to be the most convenient for use in our neural network
code."""
training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
training_results = [vectorized_result(y) for y in tr_d[1]]
training_data = zip(training_inputs, training_results)
validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
validation_data = zip(validation_inputs, va_d[1])
test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
test_data = zip(test_inputs, te_d[1])
return (training_data, validation_data, test_data)

def vectorized_result(j):
"""Return a 10-dimensional unit vector with a 1.0 in the jth
position and zeroes elsewhere.  This is used to convert a digit
(0...9) into a corresponding desired output from the neural
network."""
e = np.zeros((10, 1))
e[j] = 1.0
return e

In [2]:
%time training_data, validation_data, test_data = load_data_wrapper()

# Produces a list of 50, 000 examples. Each list entry is a tuple of
#    numpy array, shape (784, 1)
#    numpy array, shape (10,  1)

CPU times: user 1.41 s, sys: 534 ms, total: 1.95 s
Wall time: 1.98 s

In [3]:
%time x, y = zip(*training_data)

x = numpy.array(x)
x = x.reshape(50000, 784)

y = numpy.array(y)
y = y.reshape(50000, 10)

CPU times: user 16 ms, sys: 20.2 ms, total: 36.2 ms
Wall time: 36.6 ms

In [4]:
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
import numpy as np

clf = RandomForestClassifier(n_jobs=3, n_estimators=100)
%time clf.fit(x, y)


CPU times: user 2min 50s, sys: 1.81 s, total: 2min 52s
Wall time: 1min 2s

Out[4]:
RandomForestClassifier(bootstrap=True, compute_importances=None,
criterion='gini', max_depth=None, max_features='auto',
max_leaf_nodes=None, min_density=None, min_samples_leaf=1,
min_samples_split=2, n_estimators=100, n_jobs=3,
oob_score=False, random_state=None, verbose=0)
In [5]:
p_x, p_y = zip(*test_data)

In [6]:
p_x = numpy.array(p_x)
p_x = p_x.reshape(10000, 784)

p_y = numpy.array(p_y)
p_y = p_y.reshape(10000)

In [7]:
%time preds = clf.predict(p_x)

CPU times: user 2.22 s, sys: 248 ms, total: 2.47 s
Wall time: 1.17 s

In [8]:
pred_idx = [numpy.argmax(a) for a in preds]

In [9]:
pairs = zip(pred_idx, p_y)

In [10]:
number_correct = sum([int(a == b) for a, b in pairs])

In [11]:
number_incorrect = sum([int(a != b) for a, b in pairs])

In [12]:
number_incorrect

Out[12]:
1014
In [13]:
number_correct

Out[13]:
8986
In [15]:
number_correct / float((number_correct + number_incorrect))

Out[15]:
0.8986