<jupyter_start><jupyter_text>Hindi Character CNN Classifying Images of Handwritten Hindi characters---In this notebook, I train a CNN to classify images from a subset of the Devanagiri Dataset. This dataset contains both characters and numerals of the Devanigiri script.**Characters**  Here is a cheatsheet of how the characters look like (*in bold*) and how to pronounce them(*with phonetics in square bracket for those interested*):![Hindi Character](https://i.imgur.com/9ABeuwB.png)  **Digits**  And here are how the numeric digits(0-9) look like (*first row*), their pronounciation (*second row*) and the decimal equivalent (*last row*):![Hindi Digits](https://i.imgur.com/Ck4r3uG.png)The character shown above are printed characters for reference. The dataset I used is handwritten and looks less clean.  I use both the characters and digits for our Hindi character classification.---------**Footnote**Devanagiri is the script used for languages like Hindi, Nepali and most of Sanskrit.  0. Download Dataset and Extract Devanagiri Character Images[UCI Machine Learning Repository]((http://archive.ics.uci.edu/ml/datasets/Devanagari+Handwritten+Character+Dataset)) hosts this dataset.  So I'll be downloading it from there.<jupyter_code>%%bash
wget -q http://archive.ics.uci.edu/ml/machine-learning-databases/00389/DevanagariHandwrittenCharacterDataset.zip
unzip -q DevanagariHandwrittenCharacterDataset.zip
import fnmatch
import os
import shutil

root_dir='DevanagariHandwrittenCharacterDataset'

def count_characters(dir_path):
  
  print(dir_path, 'Directory Details:')
  counts = {dr: len(os.listdir(os.path.join(dir_path, dr))) for dr in sorted(os.listdir(dir_path))}
  return counts

train_counts = count_characters(os.path.join(root_dir, 'Train'))
print(train_counts)
test_counts = count_characters(os.path.join(root_dir, 'Test'))
print(test_counts)
classes = ['_'.join(ch.split('_')[1:]) for ch in train_counts.keys()]
n_classes = len(classes)

print("Total classes: ", n_classes)
print(" | ".join(classes))<jupyter_output>Total classes:  46
10_yna | 11_taamatar | 12_thaa | 13_daa | 14_dhaa | 15_adna | 16_tabala | 17_tha | 18_da | 19_dha | 1_ka | 20_na | 21_pa | 22_pha | 23_ba | 24_bha | 25_ma | 26_yaw | 27_ra | 28_la | 29_waw | 2_kha | 30_motosaw | 31_petchiryakha | 32_patalosaw | 33_ha | 34_chhya | 35_tra | 36_gya | 3_ga | 4_gha | 5_kna | 6_cha | 7_chha | 8_ja | 9_jha | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9<jupyter_text>Clean up dataset artifacts that we won't need anymore.<jupyter_code>!rm DevanagariHandwrittenCharacterDataset.zip<jupyter_output><empty_output><jupyter_text>1. Load Images from Directory<jupyter_code>import keras
from keras.preprocessing.image import ImageDataGenerator

# create a data generator
datagen = ImageDataGenerator(rescale=1./255,)        # rescale [0,255] --> [0,1]
                            # width_shift_range=0.2,  # randomly shift images horizontally (10% of total width)
                            # height_shift_range=0.2)  # randomly shift images vertically (10% of total height))

# load and iterate training dataset
train_it = datagen.flow_from_directory(root_dir + '/Train/', target_size=(32,32), color_mode='grayscale', shuffle=True, 
                                       class_mode='categorical', batch_size=64)
# load and iterate test dataset
test_it = datagen.flow_from_directory(root_dir + '/Test/', target_size=(32,32), color_mode='grayscale', shuffle=True, 
                                      class_mode='categorical', batch_size=64)<jupyter_output>Found 78200 images belonging to 46 classes.
Found 13800 images belonging to 46 classes.<jupyter_text>2. Visualize the First 24 Training Images<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

train_loader = iter(train_it)
images, _ = train_loader.next()  # loader keeps a tuple of (image,label)

fig = plt.figure(figsize=(8,8))
for i, img in enumerate(images):
    ax = fig.add_subplot(8, 8, i+1, xticks=[], yticks=[])
    ax.imshow(np.squeeze(img), cmap='gray')<jupyter_output><empty_output><jupyter_text>4.  Break Dataset into Training, Testing, and Validation Sets[TODO]  5. Define the Model Architecture<jupyter_code>from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout

model = Sequential()
model.add(Conv2D(filters=16, kernel_size=2, padding='same', activation='relu', 
                        input_shape=(32, 32, 1)))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=32, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Conv2D(filters=64, kernel_size=2, padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.3))
model.add(Flatten())
model.add(Dense(500, activation='relu'))
model.add(Dropout(0.4))
model.add(Dense(46, activation='softmax'))

model.summary()<jupyter_output>Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_4 (Conv2D)            (None, 32, 32, 16)        80        
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 16, 16, 16)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 16, 16, 32)        2080      
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 8, 8, 64)          8256      
_________________________________________________________________
max_pooling2d_6 (MaxPooling2 (None, 4, 4, 64)          0         
______________________________________________________[...]<jupyter_text>6. Compile the Model<jupyter_code># compile the model
model.compile(loss='categorical_crossentropy', optimizer='rmsprop', 
                  metrics=['accuracy'])<jupyter_output><empty_output><jupyter_text>7. Train the Model<jupyter_code>from keras.callbacks import ModelCheckpoint   

# checkpoint can only be used with validation accuracy. 
checkpointer = ModelCheckpoint(filepath='model.weights.best.hdf5', verbose=1, 
                               save_best_only=True)

# train the model
hist = model.fit_generator(train_it, epochs=10, verbose=2, shuffle=True)

model.save('hindi_character_classification_model_v1.h5')<jupyter_output><empty_output><jupyter_text>8. Load the Model with the Best Validation Accuracy[TODO]<jupyter_code># load the weights that yielded the best validation accuracy
# TODO: split train generator to train and valid

# model.load_weights('model.weights.best.hdf5')<jupyter_output><empty_output><jupyter_text>9. Calculate Classification Accuracy on Test Set<jupyter_code># evaluate and print test accuracy
score = model.evaluate_generator(test_it, verbose=0)
print('\n', 'Test accuracy:', score[1]*100)<jupyter_output>Test accuracy: 98.24637770652771<jupyter_text>10. Visualize Some PredictionsThis may give you some insight into why the network is misclassifying certain objects.<jupyter_code># get predictions on the test set
test_loader = iter(test_it)
test_images, test_labels = test_loader.next()
y_hat = model.predict(test_images)
# generate a random test set of images
random_test = np.random.choice(test_images.shape[0], size=32, replace=False)
# plot a random sample of test images, 
# Use to this plot to try to guess myself first (Human baseline).
fig = plt.figure(figsize=(20, 8))
for i, idx in enumerate(random_test):
    ax = fig.add_subplot(4, 8, i + 1, xticks=[], yticks=[])
    ax.imshow(np.squeeze(test_images[idx]), cmap='binary')
    pred_idx = np.argmax(y_hat[idx])
    true_idx = np.argmax(test_labels[idx])
    # ax.set_title("{} ({})".format(classes[pred_idx], classes[true_idx]),
                #  color=("green" if pred_idx == true_idx else "red"))
# plot a random sample of test images, their predicted labels, and ground truth
fig = plt.figure(figsize=(20, 8))
for i, idx in enumerate(random_test):
    ax = fig.add_subplot(4, 8, i + 1, xticks=[], yticks=[])
    ax.imshow(np.squeeze(test_images[idx]), cmap='binary')
    pred_idx = np.argmax(y_hat[idx])
    true_idx = np.argmax(test_labels[idx])
    ax.set_title("{} ({})".format(classes[pred_idx], classes[true_idx]),
                 color=("green" if pred_idx == true_idx else "red"))<jupyter_output><empty_output><jupyter_text>Future Work- Create a **Validation dataset** from the available training dataset.- Implement **Model Checkpointing** and Model Saving- Try **Image Augmentation** to be ready for worse handwritings in real world scenario.- Try to reduce the Model parameters without sacrificing accuracy. *Reason:* In order to deploy this trained model in an offline setting (or web browsers), the offline app would need to download the pretrained model weights. In such scenario it would help to reduce size of model<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11489455184534271, 'fraction_numerical': 0.03163444639718805, 'mean_word_length': 4.92387768379961, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 42, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '486080', 'license': 'MIT', 'n_tokens_mistral': 3043, 'n_tokens_neox': 2811, 'n_words': 759, 'path': 'Hindi_Character_CNN_Keras.ipynb', 'repo_name': 'kannankumar/jupyter-notebooks', 'size': '9104'}