One of my standard neural network examples is image classification on the MNIST dataset. The full MNIST (modified National Institute of Standards and Technology) dataset has 60,000 images for training and 10,000 images for testing.
The UCI Digits dataset is similar to MNIST but smaller and easier to experiment with. Each UCI Digits image is an 8 x 8 (64 pixels) grayscale handwritten digit from ‘0’ to ‘9’. Each pixel value is an integer from 0 (white) to 16 (black).
The UCI Digits dataset can be found at archive.ics.uci.edu/ml/datasets/optical+recognition+of+handwritten+digits. The 3823-item training file is named optdigits.tra and the 1797-item test file is named optdigits.tes. The files are text files so I renamed them and added a “.txt” extensions. Each line has 65 comma-delimited values. The first 64 values are the pixels (0 to 16) and the last value on each line is the digit (0 to 9).
I created a CNN system using PyTorch. The code that defines my network is:
class CNN_Net(T.nn.Module):
def __init__(self):
super(CNN_Net, self).__init__() # pre Python 3.3 syntax
self.conv1 = T.nn.Conv2d(1, 16, 2) # chnl-in, out, krnl
self.conv2 = T.nn.Conv2d(16, 24, 2)
self.fc1 = T.nn.Linear(96, 64) # [24*2*2, x]
self.fc2 = T.nn.Linear(64, 10) # 10 output vals
self.pool1 = T.nn.MaxPool2d(2, 2) # kernel, stride
self.drop1 = T.nn.Dropout(0.10)
self.drop2 = T.nn.Dropout(0.15)
# default weight and bias initialization
# therefore order of defintion maters
def forward(self, x):
# input x is Size([bs, 64])
z = T.relu(self.conv1(x)) # Size([bs, 16, 7, 7])
z = self.pool1(z) # Size([bs, 16, 3, 3])
z = self.drop1(z) # Size([bs, 16, 3, 3])
z = T.relu(self.conv2(z)) # Size([bs, 24, 2, 2])
z = z.reshape(-1, 96) # Size([bs, 96)]
z = T.relu(self.fc1(z)) # Size([bs, 64)]
z = self.drop2(z) # Size([bs, 64])
z = T.log_softmax(self.fc2(z), dim=1) # for NLLLoss()
return z # Size([bs, 10])
The code is paradoxically simple and incredibly complex. The code is simple if you have implemented CNNs before because the parts — convolution layers, linear layers, pooling layers, dropout layers — are standard building blocks. However, there are literally an infinite number of ways to compose the building blocks, and furthermore, each building block has many optional parameters.
My demo code worked reasonably well and scored 99.35% accuracy on the training data (3798 of 3823 correct) and 96.22% accuracy on the test data (1729 of 1797 correct).
My motivation for creating the CNN system is related to a project I’m working on. I created a transformer-based autoencoder anomaly detection system. I used the UCI Digits dataset. To test the anomaly detection system I want to create some adversarial input data items using the Fast Gradient Sign Method (FGSM) technique, and then see if the anomaly detection system can find them when mixed with ordinary benign data items. To create FGSM items, I need access to a UCI Digits model.
Good brain exercise.

Brain Boy was a comic book series. There were only six issues, published in 1962-1963. These are covers of the first three issues.
Brain Boy was Matt Price. When his mother was pregnant, a car accident with an electrical tower killed his father and gave Matt mental powers and levitation. When he became an adult he was recruited as a government agent but he was still called “Brain Boy”, his childhood nickname.
Demo code. Replace “lt”, “gt”, “lte”, “gte” with Boolean operator symbols.
# uci_digits_cnn.py
# UCI Digits classification using a CNN
# note: intent is to use this as a basis for FGSM evil data
# then use FGSM data to test TA anomaly detection
# PyTorch 1.10.0-CPU Anaconda3-2020.02 Python 3.7.6
# Windows 10/11
import numpy as np
import matplotlib.pyplot as plt
import torch as T
device = T.device('cpu')
# -----------------------------------------------------------
class UCI_Digits_Dataset(T.utils.data.Dataset):
# like 8,12,0,16, . . 15,7
# 64 pixel values [0-16], label/digit [0-9]
def __init__(self, src_file):
tmp_xy = np.loadtxt(src_file, usecols=range(0,65),
delimiter=",", comments="#", dtype=np.float32)
tmp_x = tmp_xy[:,0:64]
tmp_x /= 16.0 # normalize pixels to [0.0, 1.0]
tmp_x = tmp_x.reshape(-1, 1, 8, 8) # bs, chnls, 8x8
tmp_y = tmp_xy[:,64] # float32 form, must convert to int
self.x_data = T.tensor(tmp_x, dtype=T.float32).to(device)
self.y_data = T.tensor(tmp_y, dtype=T.int64).to(device)
def __len__(self):
return len(self.x_data)
def __getitem__(self, idx):
pixels = self.x_data[idx]
label = self.y_data[idx]
return (pixels, label) # as a tuple
# -----------------------------------------------------------
class CNN_Net(T.nn.Module):
def __init__(self):
super(CNN_Net, self).__init__() # pre Python 3.3 syntax
self.conv1 = T.nn.Conv2d(1, 16, 2) # chnl-in, out, krnl
self.conv2 = T.nn.Conv2d(16, 24, 2)
self.fc1 = T.nn.Linear(96, 64) # [24*2*2, x]
self.fc2 = T.nn.Linear(64, 10) # 10 output vals
self.pool1 = T.nn.MaxPool2d(2, 2) # kernel, stride
self.drop1 = T.nn.Dropout(0.10)
self.drop2 = T.nn.Dropout(0.15)
# default weight and bias initialization
# therefore order of defintion maters
def forward(self, x):
# input x is Size([bs, 64])
z = T.relu(self.conv1(x)) # Size([bs, 16, 7, 7])
z = self.pool1(z) # Size([bs, 16, 3, 3])
z = self.drop1(z) # Size([bs, 16, 3, 3])
z = T.relu(self.conv2(z)) # Size([bs, 24, 2, 2])
z = z.reshape(-1, 96) # Size([bs, 96)]
z = T.relu(self.fc1(z))
z = self.drop2(z)
z = T.log_softmax(self.fc2(z), dim=1) # for NLLLoss()
return z
# -----------------------------------------------------------
def accuracy(model, ds):
ldr = T.utils.data.DataLoader(ds,
batch_size=len(ds), shuffle=False)
n_correct = 0
for data in ldr:
(pixels, labels) = data
with T.no_grad():
oupts = model(pixels)
(_, predicteds) = T.max(oupts, 1)
n_correct += (predicteds == labels).sum().item()
acc = (n_correct * 1.0) / len(ds)
return acc
# -----------------------------------------------------------
def display_digit(ds, idx):
# ds is a PyTorch Dataset
data = ds[idx][0] # [0] is the pixels, [1] is the label
pixels = np.array(data) # tensor to numpy
pixels = pixels.reshape((8,8))
for i in range(8):
for j in range(8):
pxl = pixels[i,j] # or [i][j] syntax
# print("%.2X" % pxl, end="") # hexidecimal
print("%3d" % pxl, end="")
print("")
plt.imshow(pixels, cmap=plt.get_cmap('gray_r'))
plt.show()
plt.close()
# -----------------------------------------------------------
def main():
# 0. get started
print("\nBegin UCI Digits CNN classification demo ")
T.manual_seed(1)
np.random.seed(1)
# 1. create Dataset object
print("\nLoading UCI digits data ")
# train_data = ".\\Data\\uci_digits_train_100.txt"
train_data = ".\\Data\\optdigits_train_3823.txt"
train_ds = UCI_Digits_Dataset(train_data)
bat_size = 4
train_ldr = T.utils.data.DataLoader(train_ds,
batch_size=bat_size, shuffle=True)
# -----------------------------------------------------------
# 2. create network
print("\nCreating CNN classifier ")
net = CNN_Net().to(device)
net.train() # set mode
# -----------------------------------------------------------
# 3. train
loss_func = T.nn.NLLLoss() # log_softmax output
lrn_rate = 0.01
opt = T.optim.SGD(net.parameters(), lr=lrn_rate)
max_epochs = 50
log_every = 10
print("\nStarting training ")
for epoch in range(max_epochs):
epoch_loss = 0.0
for bix, batch in enumerate(train_ldr):
X = batch[0] # 64 normalized input pixels
Y = batch[1] # the class label
opt.zero_grad()
oupt = net(X)
loss_val = loss_func(oupt, Y) # a tensor
epoch_loss += loss_val.item() # for progress display
loss_val.backward() # compute gradients
opt.step() # update weights
if epoch % log_every == 0:
print("epoch = %4d loss = %0.4f" % (epoch, epoch_loss))
print("Done ")
# -----------------------------------------------------------
# 4. evaluate model accuracy
print("\nComputing model accuracy")
net.eval()
acc_train = accuracy(net, train_ds) # all at once
print("Accuracy on training data = %0.4f" % acc_train)
test_file = ".\\Data\\digits_uci_test_1797.txt"
test_ds = UCI_Digits_Dataset(test_file)
net.eval()
acc_test = accuracy(net, test_ds) # all at once
print("Accuracy on test data = %0.4f" % acc_test)
# -----------------------------------------------------------
# 5. save model
# TODO
# -----------------------------------------------------------
# 6. use model
print("\nPredicting for 64 random pixel values ")
x = np.random.random(64) # in [0.0, 1.0]
x = x.reshape(8,8)
plt.tight_layout()
plt.imshow(x, cmap=plt.get_cmap('gray_r'))
plt.show()
x = x.reshape(1, 1, 8, 8) # make it a batch
x = T.tensor(x, dtype=T.float32).to(device) # to tensor
with T.no_grad():
oupt = net(x) # 10 log-softmax tensor logits
print("\nRaw output logits: ")
print(oupt)
oupt = oupt.numpy() # convert to numpy array
probs = np.exp(oupt) # pseudo-probs
np.set_printoptions(precision=4, suppress=True)
print("\nOutput pseudo-probabilities: ")
print(probs)
pred_class = np.argmax(probs)
print("\nPredicted class/digit: ")
print(pred_class)
print("\nEnd UCI Digits demo ")
# -----------------------------------------------------------
if __name__ == "__main__":
main()


.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
2026 Visual Studio Live
2025 Summer MLADS Conference
2026 DevIntersection Conference
2025 Machine Learning Week
2025 Ai4 Conference
2026 G2E Conference
2026 iSC West Conference
You must be logged in to post a comment.