Skip to content

Commit 0edab8e

Browse files
Initial files
0 parents  commit 0edab8e

10 files changed

+1082
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

expand_mnist.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""expand_mnist.py
2+
~~~~~~~~~~~~~~~~~~
3+
4+
Take the 50,000 MNIST training images, and create an expanded set of
5+
250,000 images, by displacing each training image up, down, left and
6+
right, by one pixel. Save the resulting file to
7+
../data/mnist_expanded.pkl.gz.
8+
9+
Note that this program is memory intensive, and may not run on small
10+
systems.
11+
12+
"""
13+
14+
from __future__ import print_function
15+
16+
#### Libraries
17+
18+
# Standard library
19+
import cPickle
20+
import gzip
21+
import os.path
22+
import random
23+
24+
# Third-party libraries
25+
import numpy as np
26+
27+
print("Expanding the MNIST training set")
28+
29+
if os.path.exists("../data/mnist_expanded.pkl.gz"):
30+
print("The expanded training set already exists. Exiting.")
31+
else:
32+
f = gzip.open("../data/mnist.pkl.gz", 'rb')
33+
training_data, validation_data, test_data = cPickle.load(f)
34+
f.close()
35+
expanded_training_pairs = []
36+
j = 0 # counter
37+
for x, y in zip(training_data[0], training_data[1]):
38+
expanded_training_pairs.append((x, y))
39+
image = np.reshape(x, (-1, 28))
40+
j += 1
41+
if j % 1000 == 0: print("Expanding image number", j)
42+
# iterate over data telling us the details of how to
43+
# do the displacement
44+
for d, axis, index_position, index in [
45+
(1, 0, "first", 0),
46+
(-1, 0, "first", 27),
47+
(1, 1, "last", 0),
48+
(-1, 1, "last", 27)]:
49+
new_img = np.roll(image, d, axis)
50+
if index_position == "first":
51+
new_img[index, :] = np.zeros(28)
52+
else:
53+
new_img[:, index] = np.zeros(28)
54+
expanded_training_pairs.append((np.reshape(new_img, 784), y))
55+
random.shuffle(expanded_training_pairs)
56+
expanded_training_data = [list(d) for d in zip(*expanded_training_pairs)]
57+
print("Saving expanded data. This may take a few minutes.")
58+
f = gzip.open("../data/mnist_expanded.pkl.gz", "w")
59+
cPickle.dump((expanded_training_data, validation_data, test_data), f)
60+
f.close()

mnist.pkl.gz

16.3 MB
Binary file not shown.

mnist_average_darkness.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
mnist_average_darkness
3+
~~~~~~~~~~~~~~~~~~~~~~
4+
5+
A naive classifier for recognizing handwritten digits from the MNIST
6+
data set. The program classifies digits based on how dark they are
7+
--- the idea is that digits like "1" tend to be less dark than digits
8+
like "8", simply because the latter has a more complex shape. When
9+
shown an image the classifier returns whichever digit in the training
10+
data had the closest average darkness.
11+
12+
The program works in two steps: first it trains the classifier, and
13+
then it applies the classifier to the MNIST test data to see how many
14+
digits are correctly classified.
15+
16+
Needless to say, this isn't a very good way of recognizing handwritten
17+
digits! Still, it's useful to show what sort of performance we get
18+
from naive ideas."""
19+
20+
#### Libraries
21+
# Standard library
22+
from collections import defaultdict
23+
24+
# My libraries
25+
import mnist_loader
26+
27+
def main():
28+
training_data, validation_data, test_data = mnist_loader.load_data()
29+
# training phase: compute the average darknesses for each digit,
30+
# based on the training data
31+
avgs = avg_darknesses(training_data)
32+
# testing phase: see how many of the test images are classified
33+
# correctly
34+
num_correct = sum(int(guess_digit(image, avgs) == digit)
35+
for image, digit in zip(test_data[0], test_data[1]))
36+
print "Baseline classifier using average darkness of image."
37+
print "%s of %s values correct." % (num_correct, len(test_data[1]))
38+
39+
def avg_darknesses(training_data):
40+
""" Return a defaultdict whose keys are the digits 0 through 9.
41+
For each digit we compute a value which is the average darkness of
42+
training images containing that digit. The darkness for any
43+
particular image is just the sum of the darknesses for each pixel."""
44+
digit_counts = defaultdict(int)
45+
darknesses = defaultdict(float)
46+
for image, digit in zip(training_data[0], training_data[1]):
47+
digit_counts[digit] += 1
48+
darknesses[digit] += sum(image)
49+
avgs = defaultdict(float)
50+
for digit, n in digit_counts.iteritems():
51+
avgs[digit] = darknesses[digit] / n
52+
return avgs
53+
54+
def guess_digit(image, avgs):
55+
"""Return the digit whose average darkness in the training data is
56+
closest to the darkness of ``image``. Note that ``avgs`` is
57+
assumed to be a defaultdict whose keys are 0...9, and whose values
58+
are the corresponding average darknesses across the training data."""
59+
darkness = sum(image)
60+
distances = {k: abs(v-darkness) for k, v in avgs.iteritems()}
61+
return min(distances, key=distances.get)
62+
63+
if __name__ == "__main__":
64+
main()

mnist_loader.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# %load mnist_loader.py
2+
"""
3+
mnist_loader
4+
~~~~~~~~~~~~
5+
A library to load the MNIST image data. For details of the data
6+
structures that are returned, see the doc strings for ``load_data``
7+
and ``load_data_wrapper``. In practice, ``load_data_wrapper`` is the
8+
function usually called by our neural network code.
9+
"""
10+
11+
#### Libraries
12+
# Standard library
13+
import pickle
14+
import gzip
15+
16+
# Third-party libraries
17+
import numpy as np
18+
19+
def load_data():
20+
"""Return the MNIST data as a tuple containing the training data,
21+
the validation data, and the test data.
22+
The ``training_data`` is returned as a tuple with two entries.
23+
The first entry contains the actual training images. This is a
24+
numpy ndarray with 50,000 entries. Each entry is, in turn, a
25+
numpy ndarray with 784 values, representing the 28 * 28 = 784
26+
pixels in a single MNIST image.
27+
The second entry in the ``training_data`` tuple is a numpy ndarray
28+
containing 50,000 entries. Those entries are just the digit
29+
values (0...9) for the corresponding images contained in the first
30+
entry of the tuple.
31+
The ``validation_data`` and ``test_data`` are similar, except
32+
each contains only 10,000 images.
33+
This is a nice data format, but for use in neural networks it's
34+
helpful to modify the format of the ``training_data`` a little.
35+
That's done in the wrapper function ``load_data_wrapper()``, see
36+
below.
37+
"""
38+
f = gzip.open('mnist.pkl.gz', 'rb')
39+
training_data, validation_data, test_data = pickle.load(f, encoding="latin1")
40+
f.close()
41+
return (training_data, validation_data, test_data)
42+
43+
def load_data_wrapper():
44+
"""Return a tuple containing ``(training_data, validation_data,
45+
test_data)``. Based on ``load_data``, but the format is more
46+
convenient for use in our implementation of neural networks.
47+
In particular, ``training_data`` is a list containing 50,000
48+
2-tuples ``(x, y)``. ``x`` is a 784-dimensional numpy.ndarray
49+
containing the input image. ``y`` is a 10-dimensional
50+
numpy.ndarray representing the unit vector corresponding to the
51+
correct digit for ``x``.
52+
``validation_data`` and ``test_data`` are lists containing 10,000
53+
2-tuples ``(x, y)``. In each case, ``x`` is a 784-dimensional
54+
numpy.ndarry containing the input image, and ``y`` is the
55+
corresponding classification, i.e., the digit values (integers)
56+
corresponding to ``x``.
57+
Obviously, this means we're using slightly different formats for
58+
the training data and the validation / test data. These formats
59+
turn out to be the most convenient for use in our neural network
60+
code."""
61+
tr_d, va_d, te_d = load_data()
62+
training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
63+
training_results = [vectorized_result(y) for y in tr_d[1]]
64+
training_data = zip(training_inputs, training_results)
65+
validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
66+
validation_data = zip(validation_inputs, va_d[1])
67+
test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
68+
test_data = zip(test_inputs, te_d[1])
69+
return (training_data, validation_data, test_data)
70+
71+
def vectorized_result(j):
72+
"""Return a 10-dimensional unit vector with a 1.0 in the jth
73+
position and zeroes elsewhere. This is used to convert a digit
74+
(0...9) into a corresponding desired output from the neural
75+
network."""
76+
e = np.zeros((10, 1))
77+
e[j] = 1.0
78+
return e

mnist_svm.py

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
mnist_svm
3+
~~~~~~~~~
4+
5+
A classifier program for recognizing handwritten digits from the MNIST
6+
data set, using an SVM classifier."""
7+
8+
#### Libraries
9+
# My libraries
10+
import mnist_loader
11+
12+
# Third-party libraries
13+
from sklearn import svm
14+
15+
def svm_baseline():
16+
training_data, validation_data, test_data = mnist_loader.load_data()
17+
# train
18+
clf = svm.SVC()
19+
clf.fit(training_data[0], training_data[1])
20+
# test
21+
predictions = [int(a) for a in clf.predict(test_data[0])]
22+
num_correct = sum(int(a == y) for a, y in zip(predictions, test_data[1]))
23+
print "Baseline classifier using an SVM."
24+
print "%s of %s values correct." % (num_correct, len(test_data[1]))
25+
26+
if __name__ == "__main__":
27+
svm_baseline()
28+

network.py

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# %load network.py
2+
3+
"""
4+
network.py
5+
~~~~~~~~~~
6+
IT WORKS
7+
8+
A module to implement the stochastic gradient descent learning
9+
algorithm for a feedforward neural network. Gradients are calculated
10+
using backpropagation. Note that I have focused on making the code
11+
simple, easily readable, and easily modifiable. It is not optimized,
12+
and omits many desirable features.
13+
"""
14+
15+
#### Libraries
16+
# Standard library
17+
import random
18+
19+
# Third-party libraries
20+
import numpy as np
21+
22+
class Network(object):
23+
24+
def __init__(self, sizes):
25+
"""The list ``sizes`` contains the number of neurons in the
26+
respective layers of the network. For example, if the list
27+
was [2, 3, 1] then it would be a three-layer network, with the
28+
first layer containing 2 neurons, the second layer 3 neurons,
29+
and the third layer 1 neuron. The biases and weights for the
30+
network are initialized randomly, using a Gaussian
31+
distribution with mean 0, and variance 1. Note that the first
32+
layer is assumed to be an input layer, and by convention we
33+
won't set any biases for those neurons, since biases are only
34+
ever used in computing the outputs from later layers."""
35+
self.num_layers = len(sizes)
36+
self.sizes = sizes
37+
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
38+
self.weights = [np.random.randn(y, x)
39+
for x, y in zip(sizes[:-1], sizes[1:])]
40+
41+
def feedforward(self, a):
42+
"""Return the output of the network if ``a`` is input."""
43+
for b, w in zip(self.biases, self.weights):
44+
a = sigmoid(np.dot(w, a)+b)
45+
return a
46+
47+
def SGD(self, training_data, epochs, mini_batch_size, eta,
48+
test_data=None):
49+
"""Train the neural network using mini-batch stochastic
50+
gradient descent. The ``training_data`` is a list of tuples
51+
``(x, y)`` representing the training inputs and the desired
52+
outputs. The other non-optional parameters are
53+
self-explanatory. If ``test_data`` is provided then the
54+
network will be evaluated against the test data after each
55+
epoch, and partial progress printed out. This is useful for
56+
tracking progress, but slows things down substantially."""
57+
58+
training_data = list(training_data)
59+
n = len(training_data)
60+
61+
if test_data:
62+
test_data = list(test_data)
63+
n_test = len(test_data)
64+
65+
for j in range(epochs):
66+
random.shuffle(training_data)
67+
mini_batches = [
68+
training_data[k:k+mini_batch_size]
69+
for k in range(0, n, mini_batch_size)]
70+
for mini_batch in mini_batches:
71+
self.update_mini_batch(mini_batch, eta)
72+
if test_data:
73+
print("Epoch {} : {} / {}".format(j,self.evaluate(test_data),n_test));
74+
else:
75+
print("Epoch {} complete".format(j))
76+
77+
def update_mini_batch(self, mini_batch, eta):
78+
"""Update the network's weights and biases by applying
79+
gradient descent using backpropagation to a single mini batch.
80+
The ``mini_batch`` is a list of tuples ``(x, y)``, and ``eta``
81+
is the learning rate."""
82+
nabla_b = [np.zeros(b.shape) for b in self.biases]
83+
nabla_w = [np.zeros(w.shape) for w in self.weights]
84+
for x, y in mini_batch:
85+
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
86+
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
87+
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
88+
self.weights = [w-(eta/len(mini_batch))*nw
89+
for w, nw in zip(self.weights, nabla_w)]
90+
self.biases = [b-(eta/len(mini_batch))*nb
91+
for b, nb in zip(self.biases, nabla_b)]
92+
93+
def backprop(self, x, y):
94+
"""Return a tuple ``(nabla_b, nabla_w)`` representing the
95+
gradient for the cost function C_x. ``nabla_b`` and
96+
``nabla_w`` are layer-by-layer lists of numpy arrays, similar
97+
to ``self.biases`` and ``self.weights``."""
98+
nabla_b = [np.zeros(b.shape) for b in self.biases]
99+
nabla_w = [np.zeros(w.shape) for w in self.weights]
100+
# feedforward
101+
activation = x
102+
activations = [x] # list to store all the activations, layer by layer
103+
zs = [] # list to store all the z vectors, layer by layer
104+
for b, w in zip(self.biases, self.weights):
105+
z = np.dot(w, activation)+b
106+
zs.append(z)
107+
activation = sigmoid(z)
108+
activations.append(activation)
109+
# backward pass
110+
delta = self.cost_derivative(activations[-1], y) * \
111+
sigmoid_prime(zs[-1])
112+
nabla_b[-1] = delta
113+
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
114+
# Note that the variable l in the loop below is used a little
115+
# differently to the notation in Chapter 2 of the book. Here,
116+
# l = 1 means the last layer of neurons, l = 2 is the
117+
# second-last layer, and so on. It's a renumbering of the
118+
# scheme in the book, used here to take advantage of the fact
119+
# that Python can use negative indices in lists.
120+
for l in range(2, self.num_layers):
121+
z = zs[-l]
122+
sp = sigmoid_prime(z)
123+
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
124+
nabla_b[-l] = delta
125+
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
126+
return (nabla_b, nabla_w)
127+
128+
def evaluate(self, test_data):
129+
"""Return the number of test inputs for which the neural
130+
network outputs the correct result. Note that the neural
131+
network's output is assumed to be the index of whichever
132+
neuron in the final layer has the highest activation."""
133+
test_results = [(np.argmax(self.feedforward(x)), y)
134+
for (x, y) in test_data]
135+
return sum(int(x == y) for (x, y) in test_results)
136+
137+
def cost_derivative(self, output_activations, y):
138+
"""Return the vector of partial derivatives \partial C_x /
139+
\partial a for the output activations."""
140+
return (output_activations-y)
141+
142+
#### Miscellaneous functions
143+
def sigmoid(z):
144+
"""The sigmoid function."""
145+
return 1.0/(1.0+np.exp(-z))
146+
147+
def sigmoid_prime(z):
148+
"""Derivative of the sigmoid function."""
149+
return sigmoid(z)*(1-sigmoid(z))

0 commit comments

Comments
 (0)