This is my attempt to write a numpy-optimized version of a nearest centroid classifier to classify some images from the MNIST
data set of handwritten digits. I am somewhat new to numpy and was surprised by how succinctly this code could be written with the help of broadcasting and vectorized operations, but was wondering if I was still missing some possibly important improvements.
So first, the starting data are a set of 10000 training images and 1000 test images, each of which are represented as a vector of 784 pixels (corresponding to a 28x28 grayscale image). Normally these would be actual MNIST images of handwritten digits, but for simplicity, here are some "dummy" data:
import numpy as np
train_data = np.random.randint(256, size=(784, 10000), dtype="uint8")
test_data = np.random.randint(256, size=(784, 1000), dtype="uint8")
Note that the way the data is (would be, if it was the real data) arranged is such that the first tenth of each data set is a 0, the next tenth is a 1, and so on through 9 (this is important for how the data gets processed below).
Now, to process the data, I compute the mean for each digit in the training set, classify each image in both the training and test sets, and then compute and output both the training and test accuracies:
train_means = np.mean(train_data.reshape(784, 10, 1000), axis=2)
train_classes, test_classes = (np.argmin(np.sum(np.square(data[:,:,np.newaxis] - train_means[:,np.newaxis,:]), axis=0), axis=1) for data in (train_data, test_data))
train_acc, test_acc = (np.mean(np.equal(classes, np.repeat(np.arange(10), classes.size // 10))) for classes in (train_classes, test_classes))
print("training accuracy: {}\n testing accuracy: {}".format(train_acc, test_acc))
How can the above code be improved in terms of (1) efficiency, (2) memory, and (3) style / simplicity?
For instance, one concern I have is that the subtraction data[:,:,np.newaxis] - train_means[:,np.newaxis,:]
creates a (784, 10000, 10)
array, which is more memory than I need to use. Could I avoid allocating this much memory while not sacrificing any efficiency (and ideally any simplicity of code)?
Another concern is the comprehension I use to apply the procedures to both the training data and testing data. Would that be encouraged or considered convoluted (or maybe this is just irrelevant personal preference)?
Also: in general, is the code too condensed?