Background¶

image.png

Recorded historical documents give us a peek into the past. We are able to glimpse the world before our time; and see its culture, norms, and values to reflect on our own. Japan has very unique historical pathway. Historically, Japan and its culture was relatively isolated from the West, until the Meiji restoration in 1868 where Japanese leaders reformed its education system to modernize its culture. This caused drastic changes in the Japanese language, writing and printing systems. Due to the modernization of Japanese language in this era, cursive Kuzushiji (くずし字) script is no longer taught in the official school curriculum. Even though Kuzushiji had been used for over 1000 years, most Japanese natives today cannot read books written or published over 150 years ago.

The result is that there are hundreds of thousands of Kuzushiji texts that have been digitised but have never been transcribed, and can only currently be read by a few experts. We've built Kuzushiji-MNIST and sister datasets by taking handwritten characters from these texts and preprocessing them in a format similar to the MNIST dataset, to create easy to use benchmark datasets that are more modern and difficult to classify than the original MNIST dataset.

By releasing these datasets, we're also hoping to bring together the fields of Japanese literature and ML 😊

The datasets¶

📚 Read the paper to learn more about Kuzushiji, the datasets and their motivations for making them!
Download the dataset at Kuzushi-MNIST.

Structure of the dataset:

Kuzushiji-MNIST
    ├── kmnist-test-imgs.npz
    ├── kmnist-test-labels.npz
    ├── kmnist-train-imgs.npz
    ├── kmnist-train-labels.npz
    ├── ...

kmnist-[train/test]-[images/labels].npz: These files contain the Kuzushiji-MNIST as compressed numpy arrays.

In [30]:
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, accuracy_score

import tensorflow as tf
In [3]:
DATA_DIR = '/kaggle/input/kuzushiji'
In [4]:
X_path = os.path.join(DATA_DIR, 'kmnist-train-imgs.npz')
y_path = os.path.join(DATA_DIR, 'kmnist-train-labels.npz')

X_test_path = os.path.join(DATA_DIR, 'kmnist-test-imgs.npz')
y_test_path = os.path.join(DATA_DIR, 'kmnist-test-labels.npz')
In [5]:
X = np.load(X_path)['arr_0']
y = np.load(y_path)['arr_0']
In [6]:
print('X shape:', X.shape)
print('y shape:', y.shape)
X shape: (60000, 28, 28)
y shape: (60000,)
In [7]:
# Plot some samples

plt.figure(figsize=(10, 10))
for i in range(25):
    plt.subplot(5, 5, i + 1)
    plt.imshow(X[i], cmap='viridis')
    plt.axis('off')
    plt.grid(False)
plt.show()
No description has been provided for this image

Prepare dataset¶

In [8]:
X = X.reshape(-1, 28, 28, 1)
X = X / 255.0
In [9]:
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)

Build the model¶

0. Configuration¶

In [10]:
NUM_CLASSES = 10
IMG_SIZE = 28
BATCH_SIZE = 128
EPOCHS = 100

CALLBACKS = [tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=20)]

1. Baseline model¶

In [11]:
baseline_model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(IMG_SIZE, IMG_SIZE)),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
])

baseline_model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
    metrics=['accuracy']
)

history = baseline_model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    epochs=EPOCHS, batch_size=BATCH_SIZE,
    callbacks=CALLBACKS
)
Epoch 1/100
375/375 [==============================] - 6s 4ms/step - loss: 0.9198 - accuracy: 0.7348 - val_loss: 0.7045 - val_accuracy: 0.7952
Epoch 2/100
375/375 [==============================] - 2s 6ms/step - loss: 0.6541 - accuracy: 0.8073 - val_loss: 0.6505 - val_accuracy: 0.8113
Epoch 3/100
375/375 [==============================] - 2s 4ms/step - loss: 0.6182 - accuracy: 0.8164 - val_loss: 0.6364 - val_accuracy: 0.8138
Epoch 4/100
375/375 [==============================] - 1s 4ms/step - loss: 0.6013 - accuracy: 0.8211 - val_loss: 0.6295 - val_accuracy: 0.8161
Epoch 5/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5916 - accuracy: 0.8239 - val_loss: 0.6226 - val_accuracy: 0.8173
Epoch 6/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5836 - accuracy: 0.8268 - val_loss: 0.6208 - val_accuracy: 0.8192
Epoch 7/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5783 - accuracy: 0.8293 - val_loss: 0.6174 - val_accuracy: 0.8181
Epoch 8/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5732 - accuracy: 0.8308 - val_loss: 0.6138 - val_accuracy: 0.8212
Epoch 9/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5696 - accuracy: 0.8328 - val_loss: 0.6132 - val_accuracy: 0.8214
Epoch 10/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5653 - accuracy: 0.8330 - val_loss: 0.6140 - val_accuracy: 0.8213
Epoch 11/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5630 - accuracy: 0.8329 - val_loss: 0.6133 - val_accuracy: 0.8212
Epoch 12/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5596 - accuracy: 0.8354 - val_loss: 0.6132 - val_accuracy: 0.8223
Epoch 13/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5573 - accuracy: 0.8354 - val_loss: 0.6132 - val_accuracy: 0.8217
Epoch 14/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5552 - accuracy: 0.8368 - val_loss: 0.6109 - val_accuracy: 0.8206
Epoch 15/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5537 - accuracy: 0.8389 - val_loss: 0.6106 - val_accuracy: 0.8213
Epoch 16/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5525 - accuracy: 0.8370 - val_loss: 0.6102 - val_accuracy: 0.8230
Epoch 17/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5503 - accuracy: 0.8376 - val_loss: 0.6135 - val_accuracy: 0.8217
Epoch 18/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5484 - accuracy: 0.8385 - val_loss: 0.6120 - val_accuracy: 0.8209
Epoch 19/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5473 - accuracy: 0.8382 - val_loss: 0.6108 - val_accuracy: 0.8205
Epoch 20/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5457 - accuracy: 0.8388 - val_loss: 0.6149 - val_accuracy: 0.8203
Epoch 21/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5446 - accuracy: 0.8393 - val_loss: 0.6122 - val_accuracy: 0.8200
Epoch 22/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5436 - accuracy: 0.8388 - val_loss: 0.6108 - val_accuracy: 0.8213
Epoch 23/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5421 - accuracy: 0.8404 - val_loss: 0.6127 - val_accuracy: 0.8219
Epoch 24/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5416 - accuracy: 0.8397 - val_loss: 0.6126 - val_accuracy: 0.8194
Epoch 25/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5412 - accuracy: 0.8395 - val_loss: 0.6157 - val_accuracy: 0.8179
Epoch 26/100
375/375 [==============================] - 2s 5ms/step - loss: 0.5392 - accuracy: 0.8412 - val_loss: 0.6122 - val_accuracy: 0.8203
Epoch 27/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5388 - accuracy: 0.8396 - val_loss: 0.6158 - val_accuracy: 0.8207
Epoch 28/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5380 - accuracy: 0.8408 - val_loss: 0.6147 - val_accuracy: 0.8198
Epoch 29/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5365 - accuracy: 0.8413 - val_loss: 0.6169 - val_accuracy: 0.8202
Epoch 30/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5364 - accuracy: 0.8416 - val_loss: 0.6161 - val_accuracy: 0.8184
Epoch 31/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5356 - accuracy: 0.8416 - val_loss: 0.6148 - val_accuracy: 0.8194
Epoch 32/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5346 - accuracy: 0.8411 - val_loss: 0.6159 - val_accuracy: 0.8190
Epoch 33/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5346 - accuracy: 0.8420 - val_loss: 0.6170 - val_accuracy: 0.8194
Epoch 34/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5333 - accuracy: 0.8417 - val_loss: 0.6157 - val_accuracy: 0.8198
Epoch 35/100
375/375 [==============================] - 1s 4ms/step - loss: 0.5330 - accuracy: 0.8417 - val_loss: 0.6179 - val_accuracy: 0.8173
Epoch 36/100
375/375 [==============================] - 1s 3ms/step - loss: 0.5321 - accuracy: 0.8426 - val_loss: 0.6185 - val_accuracy: 0.8186
In [12]:
loss_baseline, accuracy_baseline = baseline_model.evaluate(X_valid, y_valid, verbose=2)
375/375 - 1s - loss: 0.6185 - accuracy: 0.8186 - 763ms/epoch - 2ms/step
In [13]:
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label='val_accuracy')
plt.legend()
plt.show()
No description has been provided for this image

2. CNN model¶

In [15]:
# Learning rate schedule func

def scheduler(epoch, lr):
    if epoch < 5:
        return lr
    else:
        return lr * tf.math.exp(-0.1)
In [16]:
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(4, 3, activation='relu', input_shape=(IMG_SIZE, IMG_SIZE, 1)),
    tf.keras.layers.Conv2D(8, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.BatchNormalization(),
    
    tf.keras.layers.Conv2D(16, 3, activation='relu'),
    tf.keras.layers.Conv2D(16, 3, activation='relu'),
    tf.keras.layers.MaxPooling2D(),
    tf.keras.layers.BatchNormalization(),
    
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(256, activation='relu'),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(256, activation='relu'),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
])

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
    metrics=['accuracy']
)

history = model.fit(
    X_train, y_train,
    validation_data=(X_valid, y_valid),
    epochs=EPOCHS, batch_size=BATCH_SIZE,
    callbacks=CALLBACKS + [tf.keras.callbacks.LearningRateScheduler(scheduler)]
)
Epoch 1/100
375/375 [==============================] - 12s 10ms/step - loss: 0.7432 - accuracy: 0.7606 - val_loss: 0.8401 - val_accuracy: 0.7383 - lr: 0.0010
Epoch 2/100
375/375 [==============================] - 3s 9ms/step - loss: 0.2788 - accuracy: 0.9144 - val_loss: 0.1521 - val_accuracy: 0.9526 - lr: 0.0010
Epoch 3/100
375/375 [==============================] - 2s 6ms/step - loss: 0.1971 - accuracy: 0.9401 - val_loss: 0.1220 - val_accuracy: 0.9639 - lr: 0.0010
Epoch 4/100
375/375 [==============================] - 3s 7ms/step - loss: 0.1514 - accuracy: 0.9525 - val_loss: 0.1311 - val_accuracy: 0.9597 - lr: 0.0010
Epoch 5/100
375/375 [==============================] - 3s 9ms/step - loss: 0.1292 - accuracy: 0.9604 - val_loss: 0.1005 - val_accuracy: 0.9703 - lr: 0.0010
Epoch 6/100
375/375 [==============================] - 2s 6ms/step - loss: 0.1078 - accuracy: 0.9671 - val_loss: 0.1031 - val_accuracy: 0.9688 - lr: 9.0484e-04
Epoch 7/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0929 - accuracy: 0.9716 - val_loss: 0.0766 - val_accuracy: 0.9778 - lr: 8.1873e-04
Epoch 8/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0826 - accuracy: 0.9748 - val_loss: 0.0759 - val_accuracy: 0.9787 - lr: 7.4082e-04
Epoch 9/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0774 - accuracy: 0.9768 - val_loss: 0.0723 - val_accuracy: 0.9803 - lr: 6.7032e-04
Epoch 10/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0630 - accuracy: 0.9805 - val_loss: 0.0709 - val_accuracy: 0.9810 - lr: 6.0653e-04
Epoch 11/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0610 - accuracy: 0.9809 - val_loss: 0.0637 - val_accuracy: 0.9820 - lr: 5.4881e-04
Epoch 12/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0540 - accuracy: 0.9833 - val_loss: 0.0621 - val_accuracy: 0.9832 - lr: 4.9659e-04
Epoch 13/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0524 - accuracy: 0.9837 - val_loss: 0.0620 - val_accuracy: 0.9837 - lr: 4.4933e-04
Epoch 14/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0448 - accuracy: 0.9858 - val_loss: 0.0606 - val_accuracy: 0.9836 - lr: 4.0657e-04
Epoch 15/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0442 - accuracy: 0.9861 - val_loss: 0.0629 - val_accuracy: 0.9819 - lr: 3.6788e-04
Epoch 16/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0394 - accuracy: 0.9876 - val_loss: 0.0612 - val_accuracy: 0.9841 - lr: 3.3287e-04
Epoch 17/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0370 - accuracy: 0.9884 - val_loss: 0.0609 - val_accuracy: 0.9847 - lr: 3.0119e-04
Epoch 18/100
375/375 [==============================] - 3s 8ms/step - loss: 0.0385 - accuracy: 0.9879 - val_loss: 0.0600 - val_accuracy: 0.9843 - lr: 2.7253e-04
Epoch 19/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0313 - accuracy: 0.9902 - val_loss: 0.0633 - val_accuracy: 0.9840 - lr: 2.4660e-04
Epoch 20/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0281 - accuracy: 0.9910 - val_loss: 0.0646 - val_accuracy: 0.9833 - lr: 2.2313e-04
Epoch 21/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0286 - accuracy: 0.9912 - val_loss: 0.0629 - val_accuracy: 0.9842 - lr: 2.0190e-04
Epoch 22/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0282 - accuracy: 0.9910 - val_loss: 0.0585 - val_accuracy: 0.9862 - lr: 1.8268e-04
Epoch 23/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0255 - accuracy: 0.9920 - val_loss: 0.0631 - val_accuracy: 0.9849 - lr: 1.6530e-04
Epoch 24/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0255 - accuracy: 0.9918 - val_loss: 0.0666 - val_accuracy: 0.9849 - lr: 1.4957e-04
Epoch 25/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0243 - accuracy: 0.9919 - val_loss: 0.0621 - val_accuracy: 0.9857 - lr: 1.3534e-04
Epoch 26/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0227 - accuracy: 0.9926 - val_loss: 0.0634 - val_accuracy: 0.9852 - lr: 1.2246e-04
Epoch 27/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0227 - accuracy: 0.9926 - val_loss: 0.0620 - val_accuracy: 0.9857 - lr: 1.1080e-04
Epoch 28/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0217 - accuracy: 0.9930 - val_loss: 0.0621 - val_accuracy: 0.9862 - lr: 1.0026e-04
Epoch 29/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0212 - accuracy: 0.9934 - val_loss: 0.0630 - val_accuracy: 0.9856 - lr: 9.0718e-05
Epoch 30/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0201 - accuracy: 0.9934 - val_loss: 0.0632 - val_accuracy: 0.9853 - lr: 8.2085e-05
Epoch 31/100
375/375 [==============================] - 3s 7ms/step - loss: 0.0190 - accuracy: 0.9941 - val_loss: 0.0653 - val_accuracy: 0.9860 - lr: 7.4273e-05
Epoch 32/100
375/375 [==============================] - 2s 7ms/step - loss: 0.0203 - accuracy: 0.9934 - val_loss: 0.0635 - val_accuracy: 0.9858 - lr: 6.7205e-05
Epoch 33/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0192 - accuracy: 0.9940 - val_loss: 0.0649 - val_accuracy: 0.9862 - lr: 6.0810e-05
Epoch 34/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0184 - accuracy: 0.9940 - val_loss: 0.0652 - val_accuracy: 0.9855 - lr: 5.5023e-05
Epoch 35/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0185 - accuracy: 0.9938 - val_loss: 0.0657 - val_accuracy: 0.9857 - lr: 4.9787e-05
Epoch 36/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0188 - accuracy: 0.9939 - val_loss: 0.0649 - val_accuracy: 0.9862 - lr: 4.5049e-05
Epoch 37/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0178 - accuracy: 0.9939 - val_loss: 0.0633 - val_accuracy: 0.9863 - lr: 4.0762e-05
Epoch 38/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0180 - accuracy: 0.9943 - val_loss: 0.0645 - val_accuracy: 0.9865 - lr: 3.6883e-05
Epoch 39/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0164 - accuracy: 0.9946 - val_loss: 0.0653 - val_accuracy: 0.9859 - lr: 3.3373e-05
Epoch 40/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0171 - accuracy: 0.9946 - val_loss: 0.0658 - val_accuracy: 0.9858 - lr: 3.0197e-05
Epoch 41/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0152 - accuracy: 0.9952 - val_loss: 0.0655 - val_accuracy: 0.9859 - lr: 2.7324e-05
Epoch 42/100
375/375 [==============================] - 2s 6ms/step - loss: 0.0158 - accuracy: 0.9950 - val_loss: 0.0660 - val_accuracy: 0.9862 - lr: 2.4723e-05
In [18]:
loss_cnn, accuracy_cnn = model.evaluate(X_valid, y_valid, verbose=2)
375/375 - 1s - loss: 0.0660 - accuracy: 0.9862 - 998ms/epoch - 3ms/step
In [19]:
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='accuracy')
plt.plot(history.history['val_accuracy'], label='val_accuracy')
plt.legend()
plt.show()
No description has been provided for this image

Summary¶

In [22]:
pd.DataFrame(
    {'Model': ['Baseline model', 'CNN model'],
     'Loss': [loss_baseline, loss_cnn],
     'Accuracy score': [accuracy_baseline, accuracy_cnn]}
)
Out[22]:
Model Loss Accuracy score
0 Baseline model 0.618453 0.818583
1 CNN model 0.065979 0.986167

Validate on testset¶

Prepare testset¶

In [23]:
X_test = np.load(X_test_path)['arr_0']
y_test = np.load(y_test_path)['arr_0']
In [24]:
X_test = X_test.reshape(-1, 28, 28, 1)
X_test = X_test / 255.0

Predict¶

In [25]:
y_pred = model.predict(X_test, verbose=0)
y_pred = np.argmax(y_pred, axis=1)
In [27]:
print('Accuracy score on testset:', accuracy_score(y_test, y_pred))
Accuracy score on testset: 0.9553
In [40]:
plt.figure(figsize=(12, 10))
sns.heatmap(confusion_matrix(y_test, y_pred, normalize='pred'), cmap='viridis', annot=True)
plt.show()
No description has been provided for this image

Plot some error predicts¶

To display japanese characters¶

In [ ]:
!pip install japanize-matplotlib
In [50]:
import japanize_matplotlib

kmnist_classmap = pd.read_csv('/kaggle/input/kuzushiji/kmnist_classmap.csv')
kmnist_classmap.head()
Out[50]:
index codepoint char
0 0 U+304A お
1 1 U+304D き
2 2 U+3059 す
3 3 U+3064 つ
4 4 U+306A な
In [42]:
idx_error = []
for i in range(len(y_test)):
    if y_pred[i] != y_test[i]:
        idx_error.append(i)
In [63]:
plt.figure(figsize=(10, 10))
for i in range(25):
    idx = idx_error[i]
    pred = kmnist_classmap.loc[y_pred[idx], 'char']
    actual = kmnist_classmap.loc[y_test[idx], 'char']
    plt.subplot(5, 5, i + 1)
    plt.imshow(X_test[idx], cmap='viridis')
    plt.title(f'pred: {pred} actual: {actual}')
    plt.axis('off')
    plt.grid(False)
plt.show()
No description has been provided for this image

You can see that the error in the predictions lies in hard cases, leading to confusion between similar characters. My CNN model is customized from the VGG16 architecture (you can call it by VGG4), which is a very popular architecture for image classification. Additionally, I have used Batch Normalization to speed up the training process and Dropout to prevent overfitting. Furthermore, I implemented a Learning Rate Scheduler to reduce the learning rate after a few epochs, which helps the model converge faster

Overall, the model is doing a good job and serves as a solid baseline for further improvements.

Thank you for reading my notebook. I hope you enjoy it.

In [ ]: