The EMNIST (“extended MNIST”) dataset is similar to MNIST but EMNIST has letters in addition to digits. Each image is a single handwritten letter. Each image is 28 x 28 pixels where each pixel value is between 0 (white) and 255 (black).

The first eight EMNIST letters from the training set.
The EMNIST data is stored in compressed Matlab (ugh) or a proprietary binary format (double ugh) at nist.gov/itl/products-and-services/emnist-dataset. Clicking on the link on that page downloads a file named “gzip.zip” (yes, a terrible name). If you unzip the folder, you get lots of gzipped files with “.gz” filename extensions.
The file organization is mildly confusing but the letters for training are in files emnist-letters-train-images-idx3-ubyte.gz” and emnist-letters-train-labels-idx1-ubyte.gz (and two similarly named files for testing) . To unzip .gz files you need a third-party application. I prefer the free 7-Zip program.

There are a lot of files to keep track of.
I unzipped the .gz files and added a “.bin” extension to the names to remind myself that the files are in a binary format.

Converting first 100 letters in binary training files to into one text file.
I wrote a script to walk through the images file and the labels file at the same time, convert the binary data to text data, combine the pixel values and the label, and save the first 100 letters as emnist_train_100.txt”. The pixel values are stored in column-major order rather than the far more usual row-major order.
According to the documentation, there are 387,361 training and 33,941 test images, so I didn’t want to convert all of the images.
Sadly, uppercase and lowercase letters are mixed together. The label for ‘A’ or ‘a’ is 1, then 2 for ‘B’ or ‘b’, through 26 for ‘Z’ or ‘z’. This lack of separating uppercase and lowercase is one big reason why EMNIST is not used very often. However, there are gzipped files that have “bymerged” in the file name where all the letter look like uppercase.
An interesting coding challenge.

Five letters from the Tengwar alphabet used in the Lord of the Rings book series written by J.R.R. Tolkien (1892-1973).
Demo code:
# converter_emnist.py
# Anaconda3-2020.02 - Python 3.7.6
import numpy as np
import matplotlib.pyplot as plt
# convert EMNIST binary to text file; combine pixels and labels
# target format:
# pixel_1 (tab) pixel_2 (tab) . . pixel_784 (tab) digit
# BUT, pixels are stored in column-major form, not by rows
# 1. manually download "gzip.zip" zipped root folder from
# www.nist.gov/itl/products-and-services/emnist-dataset
# 2. unzip the folder to "gzip", then use 7-Zip to unzip files
# emnist-letters-train-images-idx3-ubyte.gz",
# emnist-letters-train-labels-idx1-ubyte.gz"
# 3. I add ".bin" extension to unzipped files for clarity
def convert(img_file, label_file, txt_file, n_images):
print("\nOpening binary pixels and labels files ")
lbl_f = open(label_file, "rb") # EMNIST has labels 1-26
img_f = open(img_file, "rb") # and pixel vals separate
print("Opening destination text file ")
txt_f = open(txt_file, "w") # output file to write to
print("Discarding binary pixel and label files headers ")
img_f.read(16) # discard header info
lbl_f.read(8) # discard header info
print("\nReading binary files, writing to text file ")
print("Format: 784 pixel vals then label val, tab delimited ")
for i in range(n_images): # number images requested
lbl = ord(lbl_f.read(1)) # get label (unicode, one byte)
for j in range(784): # get 784 vals from the image file
val = ord(img_f.read(1))
txt_f.write(str(val) + "\t")
txt_f.write(str(lbl) + "\n")
img_f.close(); txt_f.close(); lbl_f.close()
print("Done ")
# -----------------------------------------------------------
def display_from_array(arr, idx):
# assumes arr = loadtxt(. . ) has been called
x_data = arr[:,0:784]
y_data = arr[:,784]
label = int(y_data[idx]) # 1 to 26
letter1 = chr(label+64)
letter2 = chr(label+96)
print("letter = " + str(letter1) + " or " + \
str(letter2) + "\n")
# pixels = x_data[idx]
pixels = np.transpose(x_data[idx].reshape((28,28)))
for i in range(28):
for j in range(28):
# print("%.2X" % pixels[i,j], end="") # hex
print("%3d" % pixels[i,j], end="") # decimal
print(" ", end="")
print("")
plt.imshow(pixels, cmap=plt.get_cmap('gray_r'))
plt.show()
# -----------------------------------------------------------
def main():
n_images = 100
img_file = ".\\emnist_train_100.txt"
print("\nCreating %d EMNIST train images from binary \
files " % n_images)
convert(
".\\UnzippedBinary\\" + \
"emnist-letters-train-images-idx3-ubyte.bin", \
".\\UnzippedBinary\\" + \
"emnist-letters-train-labels-idx1-ubyte.bin", \
"emnist_train_100.txt", 100)
# show first 10 training images
# load all images so can display several
print("\nShowing train images: \n")
arr = np.loadtxt(img_file, delimiter="\t",
usecols=range(0,785), dtype=np.int64)
for idx in range(0,10):
display_from_array(arr, idx) # first 10 images
print(" ")
if __name__ == "__main__":
main()
.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
Visual Studio Live
Microsoft MLADS Conference
DevIntersection Conference
Machine Learning Week
Ai4 Conference
G2E Conference
iSC West Conference
Hello Dr. McCaffrey, cool challenge and once again an excellent example from you.
My data set appears to be the same as yours according to size. However, I did not find any information on how many examples were included at that time. After some trial and error, it turned out that the training dataset contains 124800 examples, and the test dataset contains 20800 examples.
A bit ugly with the uppercase and lowercase letters in one class, but on the other hand extremely interesting to see that neural networks can easily recognize 2 different inputs with the same meaning.
TK
Hi Thorsten,
I agree — the documentation and organization of the EMNIST dataset is not very good. EMNIST was published by a U.S. government agency, and public sector datasets are often quite bad. JM