Cainvas

pH-recognition

Credit: AITS Cainvas Community

Photo by Chris Gannon on Dribbble

Litmus paper is used to test acidity or basic nature of a given solution. Once dipped, the paper turns red, blue or any shade in between depending on the nature of the solution. Red stands for acidic nature and blue stands for basic nature!

Here we train a model to recognize the pH value based on RGB values of the color of the litmus paper.

In [1]:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from keras import models, optimizers, losses, layers, callbacks
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import random

Dataset

On Kaggle by Robert

The dataset is a CSV file with 4 columns - 3 indicating RGB values and 1 indicating pH value.

In [2]:
df = pd.read_csv('https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/ph-data.csv')
df
Out[2]:
blue green red label
0 36 27 231 0
1 36 84 250 1
2 37 164 255 2
3 22 205 255 3
4 38 223 221 4
... ... ... ... ...
648 201 123 0 10
649 203 51 46 11
650 169 62 48 12
651 173 37 79 13
652 131 2 77 14

653 rows × 4 columns

In [3]:
df['label'].value_counts()
Out[3]:
14    44
12    44
11    44
10    44
9     44
8     44
7     44
6     44
5     44
4     44
3     44
2     44
1     44
13    43
0     38
Name: label, dtype: int64

This is a balanced dataset.

Normalization

Pixel values in the range 0-255 are scaled down to the range 0-1 for faster convergence.

In [4]:
df[['red','green','blue']] /= 255

Train - val - test split

Defining the input and output columns.

In [5]:
# defining the input and output columns to separate the dataset in the later cells.

input_columns = df.columns.tolist()
input_columns.remove('label')

output_columns = ['label']

print("Number of input columns: ", len(input_columns))
#print("Input columns: ", ', '.join(input_columns))

print("Number of output columns: ", len(output_columns))
#print("Output columns: ", ', '.join(output_columns))
Number of input columns:  3
Number of output columns:  1
In [6]:
# Splitting into train, val and test set -- 80-10-10 split

# First, an 80-20 split
train_df, val_test_df = train_test_split(df, test_size = 0.2)

# Then split the 20% into half
val_df, test_df = train_test_split(val_test_df, test_size = 0.5)

print("Number of samples in...")
print("Training set: ", len(train_df))
print("Validation set: ", len(val_df))
print("Testing set: ", len(test_df))
Number of samples in...
Training set:  522
Validation set:  65
Testing set:  66
In [7]:
# Splitting into X (input) and y (output)

Xtrain, ytrain = np.array(train_df[input_columns]), np.array(train_df[output_columns])

Xval, yval = np.array(val_df[input_columns]), np.array(val_df[output_columns])

Xtest, ytest = np.array(test_df[input_columns]), np.array(test_df[output_columns])

Model

In [8]:
model = models.Sequential([
    layers.Dense(128, activation = 'relu', input_shape = Xtrain[0].shape),
    layers.Dense(64, activation = 'relu'),
    #layers.Dense(16, activation = 'relu'),
    layers.Dense(8, activation = 'relu'),
    layers.Dense(1)
])

cb = callbacks.EarlyStopping(patience = 10, restore_best_weights = True)
In [9]:
model.compile(optimizer = optimizers.Adam(0.001), loss = losses.MeanSquaredError(), metrics = ['mae'])

history = model.fit(Xtrain, ytrain, validation_data = (Xval, yval), epochs = 1024, callbacks = cb)
Epoch 1/1024
17/17 [==============================] - 0s 8ms/step - loss: 61.8014 - mae: 6.6361 - val_loss: 53.9326 - val_mae: 5.9858
Epoch 2/1024
17/17 [==============================] - 0s 2ms/step - loss: 44.7708 - mae: 5.4722 - val_loss: 33.8371 - val_mae: 4.7711
Epoch 3/1024
17/17 [==============================] - 0s 2ms/step - loss: 25.1328 - mae: 4.3373 - val_loss: 22.2825 - val_mae: 4.0545
Epoch 4/1024
17/17 [==============================] - 0s 2ms/step - loss: 16.0071 - mae: 3.3684 - val_loss: 14.1057 - val_mae: 3.1003
Epoch 5/1024
17/17 [==============================] - 0s 2ms/step - loss: 9.2682 - mae: 2.4050 - val_loss: 8.4179 - val_mae: 2.2462
Epoch 6/1024
17/17 [==============================] - 0s 2ms/step - loss: 5.6705 - mae: 1.8671 - val_loss: 6.0597 - val_mae: 1.9445
Epoch 7/1024
17/17 [==============================] - 0s 2ms/step - loss: 4.7305 - mae: 1.6796 - val_loss: 5.4828 - val_mae: 1.8127
Epoch 8/1024
17/17 [==============================] - 0s 2ms/step - loss: 4.3252 - mae: 1.6026 - val_loss: 5.0437 - val_mae: 1.7248
Epoch 9/1024
17/17 [==============================] - 0s 2ms/step - loss: 3.9706 - mae: 1.5203 - val_loss: 4.6549 - val_mae: 1.6443
Epoch 10/1024
17/17 [==============================] - 0s 2ms/step - loss: 3.6736 - mae: 1.4290 - val_loss: 4.3448 - val_mae: 1.5795
Epoch 11/1024
17/17 [==============================] - 0s 2ms/step - loss: 3.4287 - mae: 1.3753 - val_loss: 4.0528 - val_mae: 1.5167
Epoch 12/1024
17/17 [==============================] - 0s 2ms/step - loss: 3.1803 - mae: 1.3376 - val_loss: 3.8675 - val_mae: 1.4447
Epoch 13/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.9782 - mae: 1.2642 - val_loss: 3.5929 - val_mae: 1.4311
Epoch 14/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.9166 - mae: 1.2346 - val_loss: 3.3532 - val_mae: 1.4238
Epoch 15/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.6774 - mae: 1.1698 - val_loss: 3.2513 - val_mae: 1.3567
Epoch 16/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.5890 - mae: 1.1848 - val_loss: 3.2440 - val_mae: 1.2837
Epoch 17/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.4385 - mae: 1.1286 - val_loss: 3.0200 - val_mae: 1.2852
Epoch 18/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.4028 - mae: 1.1072 - val_loss: 2.8314 - val_mae: 1.3228
Epoch 19/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.3338 - mae: 1.0755 - val_loss: 2.7156 - val_mae: 1.3470
Epoch 20/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.2587 - mae: 1.1010 - val_loss: 2.7106 - val_mae: 1.2100
Epoch 21/1024
17/17 [==============================] - 0s 2ms/step - loss: 2.0739 - mae: 1.0198 - val_loss: 2.5909 - val_mae: 1.2038
Epoch 22/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.9903 - mae: 1.0365 - val_loss: 2.7308 - val_mae: 1.1546
Epoch 23/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.9698 - mae: 0.9969 - val_loss: 2.4415 - val_mae: 1.1691
Epoch 24/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.8420 - mae: 0.9625 - val_loss: 2.3278 - val_mae: 1.1685
Epoch 25/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.7795 - mae: 0.9653 - val_loss: 2.2933 - val_mae: 1.1250
Epoch 26/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.6859 - mae: 0.9455 - val_loss: 2.2007 - val_mae: 1.1121
Epoch 27/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.6086 - mae: 0.9065 - val_loss: 2.2527 - val_mae: 1.0705
Epoch 28/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.5517 - mae: 0.9005 - val_loss: 2.0386 - val_mae: 1.0936
Epoch 29/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.4755 - mae: 0.8610 - val_loss: 2.0313 - val_mae: 1.0516
Epoch 30/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.4137 - mae: 0.8492 - val_loss: 2.0824 - val_mae: 1.0264
Epoch 31/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.3722 - mae: 0.8498 - val_loss: 1.9007 - val_mae: 1.0402
Epoch 32/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.3272 - mae: 0.8046 - val_loss: 1.8635 - val_mae: 1.0166
Epoch 33/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.2885 - mae: 0.8138 - val_loss: 1.8381 - val_mae: 0.9973
Epoch 34/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.3138 - mae: 0.8187 - val_loss: 1.7231 - val_mae: 0.9987
Epoch 35/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.2079 - mae: 0.7589 - val_loss: 1.7080 - val_mae: 0.9872
Epoch 36/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.1659 - mae: 0.7601 - val_loss: 1.7611 - val_mae: 0.9532
Epoch 37/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.1480 - mae: 0.7579 - val_loss: 1.7167 - val_mae: 0.9470
Epoch 38/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.1374 - mae: 0.7420 - val_loss: 1.7505 - val_mae: 0.9299
Epoch 39/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.1150 - mae: 0.7522 - val_loss: 1.6313 - val_mae: 0.9406
Epoch 40/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.0778 - mae: 0.7073 - val_loss: 1.5339 - val_mae: 0.9496
Epoch 41/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.0548 - mae: 0.7104 - val_loss: 1.5394 - val_mae: 0.9309
Epoch 42/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.0521 - mae: 0.6970 - val_loss: 1.4906 - val_mae: 0.9398
Epoch 43/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.0375 - mae: 0.7131 - val_loss: 1.5185 - val_mae: 0.9110
Epoch 44/1024
17/17 [==============================] - 0s 2ms/step - loss: 1.0135 - mae: 0.6862 - val_loss: 1.5227 - val_mae: 0.8792
Epoch 45/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9942 - mae: 0.6762 - val_loss: 1.4314 - val_mae: 0.9132
Epoch 46/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9716 - mae: 0.6851 - val_loss: 1.5015 - val_mae: 0.8645
Epoch 47/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9573 - mae: 0.6589 - val_loss: 1.4089 - val_mae: 0.8974
Epoch 48/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9823 - mae: 0.7089 - val_loss: 1.6012 - val_mae: 0.8547
Epoch 49/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9678 - mae: 0.6488 - val_loss: 1.3663 - val_mae: 0.8837
Epoch 50/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9443 - mae: 0.6633 - val_loss: 1.4926 - val_mae: 0.8470
Epoch 51/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9330 - mae: 0.6595 - val_loss: 1.3514 - val_mae: 0.8576
Epoch 52/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9385 - mae: 0.6735 - val_loss: 1.3730 - val_mae: 0.8449
Epoch 53/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9177 - mae: 0.6289 - val_loss: 1.4850 - val_mae: 0.8339
Epoch 54/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9298 - mae: 0.6583 - val_loss: 1.3933 - val_mae: 0.8325
Epoch 55/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8986 - mae: 0.6464 - val_loss: 1.3949 - val_mae: 0.8297
Epoch 56/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8739 - mae: 0.6256 - val_loss: 1.3206 - val_mae: 0.8305
Epoch 57/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8837 - mae: 0.6482 - val_loss: 1.3198 - val_mae: 0.8307
Epoch 58/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8787 - mae: 0.6150 - val_loss: 1.2764 - val_mae: 0.8679
Epoch 59/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8912 - mae: 0.6839 - val_loss: 1.3849 - val_mae: 0.8052
Epoch 60/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.9079 - mae: 0.6178 - val_loss: 1.2232 - val_mae: 0.8376
Epoch 61/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8807 - mae: 0.6713 - val_loss: 1.4121 - val_mae: 0.8006
Epoch 62/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8693 - mae: 0.6188 - val_loss: 1.2895 - val_mae: 0.8046
Epoch 63/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8550 - mae: 0.6249 - val_loss: 1.2851 - val_mae: 0.7886
Epoch 64/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8487 - mae: 0.6162 - val_loss: 1.2689 - val_mae: 0.7982
Epoch 65/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8764 - mae: 0.6489 - val_loss: 1.4226 - val_mae: 0.8132
Epoch 66/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8539 - mae: 0.6261 - val_loss: 1.2536 - val_mae: 0.7908
Epoch 67/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8514 - mae: 0.6071 - val_loss: 1.2002 - val_mae: 0.8124
Epoch 68/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8419 - mae: 0.6292 - val_loss: 1.1933 - val_mae: 0.8535
Epoch 69/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8246 - mae: 0.5978 - val_loss: 1.2221 - val_mae: 0.8032
Epoch 70/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8472 - mae: 0.6274 - val_loss: 1.2085 - val_mae: 0.7843
Epoch 71/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8687 - mae: 0.6478 - val_loss: 1.1737 - val_mae: 0.7859
Epoch 72/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8195 - mae: 0.5909 - val_loss: 1.1170 - val_mae: 0.8199
Epoch 73/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8631 - mae: 0.6595 - val_loss: 1.1968 - val_mae: 0.7863
Epoch 74/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8135 - mae: 0.6000 - val_loss: 1.1583 - val_mae: 0.7735
Epoch 75/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8126 - mae: 0.6030 - val_loss: 1.1477 - val_mae: 0.7734
Epoch 76/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8062 - mae: 0.6175 - val_loss: 1.2136 - val_mae: 0.7652
Epoch 77/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8079 - mae: 0.5898 - val_loss: 1.0951 - val_mae: 0.7757
Epoch 78/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7979 - mae: 0.5912 - val_loss: 1.1718 - val_mae: 0.8662
Epoch 79/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8804 - mae: 0.6768 - val_loss: 1.1394 - val_mae: 0.7826
Epoch 80/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8116 - mae: 0.6108 - val_loss: 1.0895 - val_mae: 0.7736
Epoch 81/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7841 - mae: 0.5978 - val_loss: 1.1216 - val_mae: 0.7576
Epoch 82/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7871 - mae: 0.5873 - val_loss: 1.0869 - val_mae: 0.7627
Epoch 83/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7920 - mae: 0.5979 - val_loss: 1.0893 - val_mae: 0.7606
Epoch 84/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7932 - mae: 0.6076 - val_loss: 1.1320 - val_mae: 0.7668
Epoch 85/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7882 - mae: 0.5972 - val_loss: 1.0784 - val_mae: 0.7561
Epoch 86/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7879 - mae: 0.5856 - val_loss: 1.0935 - val_mae: 0.7494
Epoch 87/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7847 - mae: 0.5967 - val_loss: 1.0735 - val_mae: 0.7533
Epoch 88/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7753 - mae: 0.6096 - val_loss: 1.1742 - val_mae: 0.7459
Epoch 89/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7922 - mae: 0.5881 - val_loss: 1.1000 - val_mae: 0.7686
Epoch 90/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7856 - mae: 0.5897 - val_loss: 1.0425 - val_mae: 0.7634
Epoch 91/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8117 - mae: 0.6374 - val_loss: 1.1057 - val_mae: 0.7320
Epoch 92/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7854 - mae: 0.5949 - val_loss: 1.1945 - val_mae: 0.7706
Epoch 93/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8005 - mae: 0.6206 - val_loss: 1.2432 - val_mae: 0.7733
Epoch 94/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7916 - mae: 0.6038 - val_loss: 1.0340 - val_mae: 0.7521
Epoch 95/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7696 - mae: 0.5994 - val_loss: 1.0507 - val_mae: 0.7427
Epoch 96/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7684 - mae: 0.5819 - val_loss: 1.0483 - val_mae: 0.7488
Epoch 97/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7517 - mae: 0.5864 - val_loss: 1.0712 - val_mae: 0.7263
Epoch 98/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7830 - mae: 0.5927 - val_loss: 1.1278 - val_mae: 0.7390
Epoch 99/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7756 - mae: 0.5969 - val_loss: 1.0440 - val_mae: 0.7656
Epoch 100/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8198 - mae: 0.6297 - val_loss: 1.0456 - val_mae: 0.8235
Epoch 101/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.8023 - mae: 0.6374 - val_loss: 1.0750 - val_mae: 0.7548
Epoch 102/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7592 - mae: 0.5962 - val_loss: 1.0559 - val_mae: 0.7266
Epoch 103/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7468 - mae: 0.5835 - val_loss: 1.0676 - val_mae: 0.7149
Epoch 104/1024
17/17 [==============================] - 0s 2ms/step - loss: 0.7502 - mae: 0.5635 - val_loss: 1.0389 - val_mae: 0.7501
In [10]:
model.evaluate(Xtest, ytest)
3/3 [==============================] - 0s 983us/step - loss: 0.6799 - mae: 0.6073
Out[10]:
[0.6798582077026367, 0.6072772741317749]

Prediction

In [11]:
pd.DataFrame({'True': ytest.reshape(-1)[:10],
             'Predicted':model.predict(Xtest).reshape(-1)[:10]})
Out[11]:
True Predicted
0 2 2.948377
1 6 6.209330
2 14 13.413238
3 10 10.019506
4 13 12.632843
5 1 1.503918
6 8 9.041868
7 4 4.667073
8 12 13.446583
9 2 0.587658

Plotting the metrics

In [12]:
def plot(history, variable, variable2):
    plt.plot(range(len(history[variable])), history[variable])
    plt.plot(range(len(history[variable2])), history[variable2])
    plt.legend([variable, variable2])
    plt.title(variable)
In [13]:
plot(history.history, "loss", "val_loss")
In [14]:
plot(history.history, "mae", "val_mae")

deepC

In [15]:
model.save('pH.h5')

!deepCC pH.h5
[INFO]
Reading [keras model] 'pH.h5'
[SUCCESS]
Saved 'pH_deepC/pH.onnx'
[INFO]
Reading [onnx model] 'pH_deepC/pH.onnx'
[INFO]
Model info:
  ir_vesion : 4
  doc       : 
[WARNING]
[ONNX]: terminal (input/output) dense_input's shape is less than 1. Changing it to 1.
[WARNING]
[ONNX]: terminal (input/output) dense_3's shape is less than 1. Changing it to 1.
WARN (GRAPH): found operator node with the same name (dense_3) as io node.
[INFO]
Running DNNC graph sanity check ...
[SUCCESS]
Passed sanity check.
[INFO]
Writing C++ file 'pH_deepC/pH.cpp'
[INFO]
deepSea model files are ready in 'pH_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 "pH_deepC/pH.cpp" -D_AITS_MAIN -o "pH_deepC/pH.exe"
[RUNNING COMMAND]
size "pH_deepC/pH.exe"
   text	   data	    bss	    dec	    hex	filename
 153465	   2336	    760	 156561	  26391	pH_deepC/pH.exe
[SUCCESS]
Saved model as executable "pH_deepC/pH.exe"