Cainvas
Model Files
sheep.h5
keras
Model
deepSea Compiled Models
sheep.exe
deepSea
Ubuntu

Sheep breed classification

Credit: AITS Cainvas Community

Photo by Petter Pentilä on Dribbble

Identify the breed of the sheep in the image using neural networks.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from keras import layers, optimizers, models, preprocessing, losses, callbacks, Input, Model, applications
import os
import random
from PIL import Image
In [2]:
!wget https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/SheepFaceImages.zip
 
!unzip -qo SheepFaceImages.zip

!rm SheepFaceImages.zip
--2021-01-26 10:23:26--  https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/SheepFaceImages.zip
Resolving cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)... 52.219.62.92
Connecting to cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)|52.219.62.92|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 11600085 (11M) [application/zip]
Saving to: ‘SheepFaceImages.zip’

SheepFaceImages.zip 100%[===================>]  11.06M  --.-KB/s    in 0.1s    

2021-01-26 10:23:26 (115 MB/s) - ‘SheepFaceImages.zip’ saved [11600085/11600085]

The dataset

On Kaggle by Divyansh Agrawal

This data is originally the efforts of Abu Jwade Sanabel et al. The team collected the data from a real farm in Australia. Data

The dataset has images of sheep, focussed on their faces, belonging to 4 classes -

In [3]:
# Loading the dataset

path = 'SheepFaceImages'

train_df = preprocessing.image_dataset_from_directory(path, label_mode = 'categorical', validation_split = 0.2, subset = 'training', seed = 113)

test_df = preprocessing.image_dataset_from_directory(path, label_mode = 'categorical', validation_split = 0.2, subset = 'validation', seed = 113)
Found 1680 files belonging to 4 classes.
Using 1344 files for training.
Found 1680 files belonging to 4 classes.
Using 336 files for validation.
In [4]:
# How many samples in each class

for x in os.listdir(path):
    print(x, ' - ', len(os.listdir(path + '/' + x)))
Poll Dorset  -  420
Marino  -  420
Suffolk  -  420
White Suffolk  -  420

This is a balanced dataset!

In [5]:
# Looking into the class labels

class_names = train_df.class_names

print("Train class names: ", train_df.class_names)
print("Test class names: ", test_df.class_names)
Train class names:  ['Marino', 'Poll Dorset', 'Suffolk', 'White Suffolk']
Test class names:  ['Marino', 'Poll Dorset', 'Suffolk', 'White Suffolk']

Visualization

In [6]:
num_samples = 4    # the number of samples to be displayed in each class

for x in class_names:
    plt.figure(figsize=(20, 20))

    filenames = os.listdir(path + '/' + x)

    for i in range(num_samples):
        ax = plt.subplot(1, num_samples, i + 1)
        img = Image.open(path +'/' + x + '/' + filenames[i])
        plt.imshow(img)
        plt.title(x)
        plt.axis("off")

Preprocessing

Normalization

In [7]:
# Normalizing the pixel values for faster convergence

normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

train_df = train_df.map(lambda x, y: (normalization_layer(x), y))
test_df = test_df.map(lambda x, y: (normalization_layer(x), y))

The model

In [8]:
model = models.Sequential([
    layers.Conv2D(8, 2, activation='relu', input_shape=(256,256,3)),
    layers.Conv2D(16, 2, activation='relu'),
    layers.MaxPool2D(pool_size=(2, 2)),
    
    layers.Conv2D(16, 2, activation='relu'),
    layers.Conv2D(32, 2, activation='relu'),
    layers.MaxPool2D(pool_size=(2, 2)),
    
    layers.Flatten(),
    layers.Dense(64, activation='relu'),
    layers.Dense(32, activation='relu'),
    layers.Dense(len(class_names), activation='softmax')
])

cb = [callbacks.EarlyStopping(monitor = 'val_loss', patience = 5, restore_best_weights = True)]
In [9]:
model.compile(loss=losses.CategoricalCrossentropy(), optimizer=optimizers.Adam(0.0001), metrics=['accuracy'])

history = model.fit(train_df, validation_data =  test_df, epochs=32, callbacks = cb)
Epoch 1/32
42/42 [==============================] - 3s 82ms/step - loss: 1.2796 - accuracy: 0.3906 - val_loss: 1.1861 - val_accuracy: 0.4911
Epoch 2/32
42/42 [==============================] - 3s 77ms/step - loss: 1.0200 - accuracy: 0.5677 - val_loss: 1.0258 - val_accuracy: 0.5208
Epoch 3/32
42/42 [==============================] - 3s 75ms/step - loss: 0.8503 - accuracy: 0.6391 - val_loss: 0.8615 - val_accuracy: 0.6429
Epoch 4/32
42/42 [==============================] - 3s 74ms/step - loss: 0.6961 - accuracy: 0.7314 - val_loss: 0.7595 - val_accuracy: 0.7054
Epoch 5/32
42/42 [==============================] - 3s 74ms/step - loss: 0.5725 - accuracy: 0.7857 - val_loss: 0.6548 - val_accuracy: 0.7560
Epoch 6/32
42/42 [==============================] - 3s 76ms/step - loss: 0.4792 - accuracy: 0.8162 - val_loss: 0.6244 - val_accuracy: 0.7411
Epoch 7/32
42/42 [==============================] - 3s 75ms/step - loss: 0.3895 - accuracy: 0.8668 - val_loss: 0.5697 - val_accuracy: 0.7738
Epoch 8/32
42/42 [==============================] - 3s 75ms/step - loss: 0.3601 - accuracy: 0.8653 - val_loss: 0.4625 - val_accuracy: 0.8363
Epoch 9/32
42/42 [==============================] - 3s 75ms/step - loss: 0.2447 - accuracy: 0.9271 - val_loss: 0.4105 - val_accuracy: 0.8571
Epoch 10/32
42/42 [==============================] - 3s 74ms/step - loss: 0.2156 - accuracy: 0.9338 - val_loss: 0.4594 - val_accuracy: 0.8095
Epoch 11/32
42/42 [==============================] - 3s 73ms/step - loss: 0.1901 - accuracy: 0.9464 - val_loss: 0.4314 - val_accuracy: 0.8482
Epoch 12/32
42/42 [==============================] - 3s 73ms/step - loss: 0.1414 - accuracy: 0.9576 - val_loss: 0.3894 - val_accuracy: 0.8542
Epoch 13/32
42/42 [==============================] - 3s 75ms/step - loss: 0.1485 - accuracy: 0.9487 - val_loss: 0.4290 - val_accuracy: 0.8452
Epoch 14/32
42/42 [==============================] - 3s 75ms/step - loss: 0.1048 - accuracy: 0.9754 - val_loss: 0.3446 - val_accuracy: 0.8690
Epoch 15/32
42/42 [==============================] - 3s 75ms/step - loss: 0.0760 - accuracy: 0.9829 - val_loss: 0.3214 - val_accuracy: 0.8780
Epoch 16/32
42/42 [==============================] - 3s 73ms/step - loss: 0.0717 - accuracy: 0.9814 - val_loss: 0.3421 - val_accuracy: 0.8720
Epoch 17/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0572 - accuracy: 0.9911 - val_loss: 0.3183 - val_accuracy: 0.8839
Epoch 18/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0421 - accuracy: 0.9933 - val_loss: 0.3415 - val_accuracy: 0.8780
Epoch 19/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0336 - accuracy: 0.9963 - val_loss: 0.3763 - val_accuracy: 0.8750
Epoch 20/32
42/42 [==============================] - 3s 73ms/step - loss: 0.0266 - accuracy: 0.9985 - val_loss: 0.3125 - val_accuracy: 0.8750
Epoch 21/32
42/42 [==============================] - 3s 77ms/step - loss: 0.0207 - accuracy: 0.9993 - val_loss: 0.3580 - val_accuracy: 0.8690
Epoch 22/32
42/42 [==============================] - 3s 73ms/step - loss: 0.0173 - accuracy: 0.9993 - val_loss: 0.3425 - val_accuracy: 0.8780
Epoch 23/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0142 - accuracy: 1.0000 - val_loss: 0.3817 - val_accuracy: 0.8750
Epoch 24/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0108 - accuracy: 1.0000 - val_loss: 0.3734 - val_accuracy: 0.8750
Epoch 25/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0097 - accuracy: 0.9993 - val_loss: 0.3688 - val_accuracy: 0.8720
In [10]:
model.compile(loss=losses.CategoricalCrossentropy(), optimizer=optimizers.Adam(0.00001), metrics=['accuracy'])

history1 = model.fit(train_df, validation_data =  test_df, epochs=32, callbacks = cb)
Epoch 1/32
42/42 [==============================] - 3s 77ms/step - loss: 0.0190 - accuracy: 0.9993 - val_loss: 0.3328 - val_accuracy: 0.8690
Epoch 2/32
42/42 [==============================] - 3s 75ms/step - loss: 0.0177 - accuracy: 0.9993 - val_loss: 0.3466 - val_accuracy: 0.8750
Epoch 3/32
42/42 [==============================] - 3s 77ms/step - loss: 0.0171 - accuracy: 0.9993 - val_loss: 0.3322 - val_accuracy: 0.8661
Epoch 4/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0160 - accuracy: 1.0000 - val_loss: 0.3485 - val_accuracy: 0.8750
Epoch 5/32
42/42 [==============================] - 3s 74ms/step - loss: 0.0152 - accuracy: 0.9993 - val_loss: 0.3356 - val_accuracy: 0.8720
Epoch 6/32
42/42 [==============================] - 3s 73ms/step - loss: 0.0142 - accuracy: 1.0000 - val_loss: 0.3446 - val_accuracy: 0.8720
Epoch 7/32
42/42 [==============================] - 3s 75ms/step - loss: 0.0135 - accuracy: 1.0000 - val_loss: 0.3399 - val_accuracy: 0.8720
Epoch 8/32
42/42 [==============================] - 3s 75ms/step - loss: 0.0126 - accuracy: 0.9993 - val_loss: 0.3444 - val_accuracy: 0.8720
In [11]:
model.evaluate(test_df)
11/11 [==============================] - 0s 21ms/step - loss: 0.3322 - accuracy: 0.8661
Out[11]:
[0.33221885561943054, 0.8660714030265808]
In [12]:
Xtest = []
ytest = []

for x in test_df.enumerate():
    for y in x[1][0]:        
        Xtest.append(np.array(y).tolist())
    ytest.extend(np.array(x[1][1]).tolist())
    
len(ytest), len(Xtest)
Out[12]:
(336, 336)
In [13]:
cm = confusion_matrix(np.argmax(ytest, axis = 1), np.argmax(model.predict(Xtest), axis = 1))
cm = cm.astype('int') / cm.sum(axis=1)[:, np.newaxis]

fig = plt.figure(figsize = (10, 10))
ax = fig.add_subplot(111)

for i in range(cm.shape[1]):
    for j in range(cm.shape[0]):
        if cm[i,j] > 0.8:
            clr = "white"
        else:
            clr = "black"
        ax.text(j, i, format(cm[i, j], '.2f'), horizontalalignment="center", color=clr)

_ = ax.imshow(cm, cmap=plt.cm.Blues)
ax.set_xticks(range(len(class_names)))
ax.set_yticks(range(len(class_names)))
ax.set_xticklabels(class_names, rotation = 90)
ax.set_yticklabels(class_names)
plt.xlabel('Predicted')
plt.ylabel('True')
plt.show()
In [14]:
del Xtest, ytest

Plotting the metrics

In [15]:
def plot(history1, history2, variable1, variable2):
    # combining metrics from both trainings    
    var1_history = history1[variable1]
    var1_history.extend(history2[variable1])
    
    var2_history = history1[variable2]
    var2_history.extend(history2[variable2])
    
    # plotting them
    plt.plot(range(len(var1_history)), var1_history)
    plt.plot(range(len(var2_history)), var2_history)
    plt.legend([variable1, variable2])
    plt.title(variable1)
In [16]:
plot(history.history, history1.history, "accuracy", 'val_accuracy')
In [17]:
plot(history.history, history1.history, "loss", 'val_loss')

Prediction

In [18]:
# pick random test data sample from one batch
x = random.randint(0, 32 - 1)

for i in test_df.as_numpy_iterator():
    img, label = i    
    plt.axis('off')   # remove axes
    plt.imshow(img[x])    # shape from (32, 256, 256, 3) --> (256, 256, 3)
    output = model.predict(np.expand_dims(img[x],0))[0]    # getting output; input shape (256, 256, 3) --> (1, 256, 256, 3)
    pred = np.argmax(output)
    
    print("Predicted: ", class_names[pred])    # Picking the label from class_names base don the model output
    print("Probability: ", output[pred])
    print("\nTrue: ", class_names[np.argmax(label[x])])
    break
Predicted:  Poll Dorset
Probability:  0.99830556

True:  Poll Dorset

deepC

In [19]:
model.save('sheep.h5')

!deepCC sheep.h5
[INFO]
Reading [keras model] 'sheep.h5'
[SUCCESS]
Saved 'sheep.onnx'
[INFO]
Reading [onnx model] 'sheep.onnx'
[INFO]
Model info:
  ir_vesion : 5
  doc       : 
[WARNING]
[ONNX]: terminal (input/output) conv2d_input's shape is less than 1. Changing it to 1.
[WARNING]
[ONNX]: terminal (input/output) dense_2's shape is less than 1. Changing it to 1.
WARN (GRAPH): found operator node with the same name (dense_2) as io node.
[INFO]
Running DNNC graph sanity check ...
[SUCCESS]
Passed sanity check.
[INFO]
Writing C++ file 'sheep_deepC/sheep.cpp'
[INFO]
deepSea model files are ready in 'sheep_deepC/' 
[RUNNING COMMAND]
g++ -std=c++11 -O3 -fno-rtti -fno-exceptions -I. -I/opt/tljh/user/lib/python3.7/site-packages/deepC-0.13-py3.7-linux-x86_64.egg/deepC/include -isystem /opt/tljh/user/lib/python3.7/site-packages/deepC-0.13-py3.7-linux-x86_64.egg/deepC/packages/eigen-eigen-323c052e1731 sheep_deepC/sheep.cpp -o sheep_deepC/sheep.exe
[RUNNING COMMAND]
size "sheep_deepC/sheep.exe"
   text	   data	    bss	    dec	    hex	filename
 169188	31518032	    760	31687980	1e3852c	sheep_deepC/sheep.exe
[SUCCESS]
Saved model as executable "sheep_deepC/sheep.exe"
In [20]:
# pick random test data sample from one batch
x = random.randint(0, 32 - 1) # batch size is 32

for i in test_df.as_numpy_iterator():
    img, label = i    
    plt.axis('off')   # remove axes
    plt.imshow(img[x])    # shape from (32, 256, 256, 3) --> (256, 256, 3)
    
    np.savetxt('sample.data', (img[x]).flatten())    # xth sample into text file
    print("True: ", class_names[np.argmax(label[x])])
    print()
    break

# run exe with input
!sheep_deepC/sheep.exe sample.data

# show predicted output
nn_out = np.loadtxt('deepSea_result_1.out')
pred = np.argmax(nn_out)

print("\nPredicted: ", class_names[pred])    # Picking the label from class_names base don the model output
print("Probability: ", nn_out[pred])
True:  Marino

reading file sample.data.
Warn: conv2d_1_Relu_0_pooling: auto_pad attribute is deprecated, it'll be ignored.
Warn: conv2d_3_Relu_0_pooling: auto_pad attribute is deprecated, it'll be ignored.
writing file deepSea_result_1.out.

Predicted:  Marino
Probability:  0.991652