Cainvas

Weather Classifcation

Credit: AITS Cainvas Community

Photo by Sergey Galtsev on Dribbble

Image tagging helps in selecting images based on content, especially useful in search engines and other similar applications. Here, we tag images based on the weather of the scene. There are two classes - cloudy, sunny.

In [1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
from tensorflow.keras import layers, optimizers, models, preprocessing, losses, callbacks
import os
import random
from PIL import Image
import tensorflow as tf
import tensorflow.keras

Dataset

"Two-class Weather Classification" Cewu Lu, Di Lin, Jiaya Jia, Chi-Keung Tang IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2014

On Kaggle by Paula

In [2]:
!wget https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/weather.zip
 
!unzip -qo weather.zip

!rm weather.zip
--2021-09-07 06:46:34--  https://cainvas-static.s3.amazonaws.com/media/user_data/cainvas-admin/weather.zip
Resolving cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)... 52.219.64.104
Connecting to cainvas-static.s3.amazonaws.com (cainvas-static.s3.amazonaws.com)|52.219.64.104|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 116150379 (111M) [application/zip]
Saving to: ‘weather.zip’

weather.zip         100%[===================>] 110.77M   108MB/s    in 1.0s    

2021-09-07 06:46:35 (108 MB/s) - ‘weather.zip’ saved [116150379/116150379]

In [3]:
# Loading the dataset

path = 'weather/'
input_shape = (256, 256, 3)    # default input shape while loading the images

batch = 64

# The train and test datasets
print("Train dataset")
train_ds = preprocessing.image_dataset_from_directory(path+'train', batch_size=batch, label_mode='binary')

print("Test dataset")
test_ds = preprocessing.image_dataset_from_directory(path+'test', batch_size=batch, label_mode='binary')
Train dataset
Found 10000 files belonging to 2 classes.
Test dataset
Found 253 files belonging to 2 classes.
In [4]:
# How many samples in each class

for t in ['train', 'test']:
    print('\n', t.upper())
    for x in os.listdir(path + t):
        print(x, ' - ', len(os.listdir(path + t + '/' + x)))
 TRAIN
sunny  -  5000
cloudy  -  5000

 TEST
sunny  -  153
cloudy  -  100

The train set is balanced while the test set is imbalanced. A confusion matrix can help in finding the accuracies.

In [5]:
# Looking into the class labels

class_names = train_ds.class_names

print("Train class names: ", train_ds.class_names)
print("Test class names: ", test_ds.class_names)
Train class names:  ['cloudy', 'sunny']
Test class names:  ['cloudy', 'sunny']

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 + 'train/' + x)

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

Feel free to load more images and see the various images in the dataset.

It is important to note that the differences in the images are not very contrasting. In some cases, even humans may find it difficult to categorise them with high confidence.

Preprocessing

Normalization

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

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

train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
test_ds = test_ds.map(lambda x, y: (normalization_layer(x), y))

The model

In [8]:
base_model = tensorflow.keras.applications.DenseNet121(weights='imagenet', input_shape=input_shape, include_top=False)    # False, do not include the classification layer of the model
base_model.trainable = False

inputs = tf.keras.Input(shape=input_shape)

x = base_model(inputs, training=False)
x = tensorflow.keras.layers.GlobalAveragePooling2D()(x)
outputs = tensorflow.keras.layers.Dense(1, activation = 'sigmoid')(x)    # Add own classififcation layer

model = tensorflow.keras.Model(inputs, outputs)

cb = [callbacks.EarlyStopping(monitor = 'val_loss', patience = 5, restore_best_weights = True)]
model.summary()
Model: "functional_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_2 (InputLayer)         [(None, 256, 256, 3)]     0         
_________________________________________________________________
densenet121 (Functional)     (None, 8, 8, 1024)        7037504   
_________________________________________________________________
global_average_pooling2d (Gl (None, 1024)              0         
_________________________________________________________________
dense (Dense)                (None, 1)                 1025      
=================================================================
Total params: 7,038,529
Trainable params: 1,025
Non-trainable params: 7,037,504
_________________________________________________________________
In [9]:
model.compile(loss='binary_crossentropy', optimizer=optimizers.Adam(0.01), metrics=['accuracy'])

history = model.fit(train_ds, validation_data =  test_ds, epochs=32, callbacks = cb)
Epoch 1/32
157/157 [==============================] - 47s 302ms/step - loss: 0.4868 - accuracy: 0.7616 - val_loss: 0.8162 - val_accuracy: 0.6285
Epoch 2/32
157/157 [==============================] - 45s 285ms/step - loss: 0.3890 - accuracy: 0.8241 - val_loss: 0.6976 - val_accuracy: 0.6877
Epoch 3/32
157/157 [==============================] - 46s 291ms/step - loss: 0.3641 - accuracy: 0.8379 - val_loss: 0.5214 - val_accuracy: 0.7708
Epoch 4/32
157/157 [==============================] - 46s 294ms/step - loss: 0.3480 - accuracy: 0.8472 - val_loss: 0.7226 - val_accuracy: 0.7036
Epoch 5/32
157/157 [==============================] - 47s 298ms/step - loss: 0.3385 - accuracy: 0.8527 - val_loss: 0.7003 - val_accuracy: 0.7194
Epoch 6/32
157/157 [==============================] - 48s 305ms/step - loss: 0.3369 - accuracy: 0.8528 - val_loss: 0.5071 - val_accuracy: 0.7747
Epoch 7/32
157/157 [==============================] - 48s 303ms/step - loss: 0.3288 - accuracy: 0.8579 - val_loss: 0.4866 - val_accuracy: 0.7826
Epoch 8/32
157/157 [==============================] - 48s 304ms/step - loss: 0.3387 - accuracy: 0.8556 - val_loss: 0.6097 - val_accuracy: 0.7628
Epoch 9/32
157/157 [==============================] - 48s 305ms/step - loss: 0.3166 - accuracy: 0.8657 - val_loss: 0.5158 - val_accuracy: 0.7826
Epoch 10/32
157/157 [==============================] - 49s 311ms/step - loss: 0.3352 - accuracy: 0.8563 - val_loss: 0.6508 - val_accuracy: 0.7391
Epoch 11/32
157/157 [==============================] - 48s 308ms/step - loss: 0.3132 - accuracy: 0.8669 - val_loss: 0.6282 - val_accuracy: 0.7510
Epoch 12/32
157/157 [==============================] - 48s 304ms/step - loss: 0.3150 - accuracy: 0.8690 - val_loss: 0.4067 - val_accuracy: 0.8142
Epoch 13/32
157/157 [==============================] - 48s 303ms/step - loss: 0.3249 - accuracy: 0.8659 - val_loss: 0.6477 - val_accuracy: 0.7470
Epoch 14/32
157/157 [==============================] - 48s 305ms/step - loss: 0.3082 - accuracy: 0.8705 - val_loss: 0.8749 - val_accuracy: 0.6917
Epoch 15/32
157/157 [==============================] - 48s 303ms/step - loss: 0.3143 - accuracy: 0.8676 - val_loss: 0.5434 - val_accuracy: 0.7787
Epoch 16/32
157/157 [==============================] - 48s 308ms/step - loss: 0.3171 - accuracy: 0.8667 - val_loss: 0.6385 - val_accuracy: 0.7391
Epoch 17/32
157/157 [==============================] - 48s 307ms/step - loss: 0.3039 - accuracy: 0.8701 - val_loss: 0.5187 - val_accuracy: 0.7826
In [10]:
model.evaluate(test_ds)
4/4 [==============================] - 1s 183ms/step - loss: 0.4067 - accuracy: 0.8142
Out[10]:
[0.4067128896713257, 0.8142292499542236]
In [11]:
true_labels, predicted_labels = [], []

for x in test_ds.as_numpy_iterator():
    images, labels = x
    true_labels.extend(labels.flatten().astype('int'))
    output = (model.predict(images)>0.5).astype('int')
    predicted_labels.extend(output.flatten())
    

cm = confusion_matrix(true_labels, predicted_labels)
cm = cm.astype('int') / cm.sum(axis=1)[:, np.newaxis]

for i in range(cm.shape[1]):
    for j in range(cm.shape[0]):
        plt.text(j, i, format(cm[i, j], '.2f'), horizontalalignment="center", color="black")


plt.imshow(cm, cmap=plt.cm.Blues)
Out[11]:
<matplotlib.image.AxesImage at 0x7f3dec0cdfd0>

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.title(variable)
In [13]:
plot(history.history, "accuracy", 'val_accuracy')
In [14]:
plot(history.history, "loss", "val_loss")

Prediction

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

for i in test_ds.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][0]    # getting output; input shape (256, 256, 3) --> (1, 256, 256, 3)
    pred = (output > 0.5).astype('int')
    print("Predicted: ", class_names[pred], '(', output, '-->', pred, ')')    # Picking the label from class_names base don the model output
    print("True: ", class_names[label[x][0].astype('int')])
    break
Predicted:  cloudy ( 0.0010640864 --> 0 )
True:  cloudy

deepC

In [ ]:
model.save('weather.h5')

!deepCC weather.h5
[INFO]
Reading [keras model] 'weather.h5'
[SUCCESS]
Saved 'weather_deepC/weather.onnx'
[INFO]
Reading [onnx model] 'weather_deepC/weather.onnx'
[INFO]
Model info:
  ir_vesion : 5
  doc       : 
[WARNING]
[ONNX]: graph-node conv1/conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block1_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block2_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block3_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block4_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block5_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv2_block6_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block1_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block2_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block3_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block4_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block5_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block6_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block7_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block8_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block9_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block10_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block11_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv3_block12_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block1_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block2_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block3_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block4_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block5_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block6_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block7_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block8_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block9_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block10_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block11_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block12_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block13_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block14_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block15_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block16_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block17_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block18_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block19_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block20_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block21_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block22_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block23_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv4_block24_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block1_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block2_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block3_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block4_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block5_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block6_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block7_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block8_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block9_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block10_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block11_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block12_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block13_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block14_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block15_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: graph-node conv5_block16_2_conv's attribute auto_pad has no meaningful data.
[WARNING]
[ONNX]: terminal (input/output) input_2's shape is less than 1. Changing it to 1.
[WARNING]
[ONNX]: terminal (input/output) dense's shape is less than 1. Changing it to 1.
WARN (GRAPH): found operator node with the same name (dense) as io node.
[INFO]
Running DNNC graph sanity check ...
[SUCCESS]
Passed sanity check.
[INFO]
Writing C++ file 'weather_deepC/weather.cpp'
[INFO]
deepSea model files are ready in 'weather_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 "weather_deepC/weather.cpp" -D_AITS_MAIN -o "weather_deepC/weather.exe"