In [1]:
%matplotlib inline

import glob
import os
import random
import shutil

import keras
from keras import layers
from keras import models
from keras import optimizers
from keras.preprocessing.image import ImageDataGenerator

BASE_DIR = '../data/Images/'
TRAIN_DIR = '../data/Images/training/'
VALIDATION_DIR = '../data/Images/validation/'

LOG_DIR = '../data/Images/logs/'


Using TensorFlow backend.

Preparing datasets

Load each classified images paths so we can redistribute the original dataset:


In [2]:
interactions = glob.glob('../data/Images/interaction/*.jpg')
not_interactions = glob.glob('../data/Images/not_interaction/*.jpg')

And shuffle them so we don't introduce temporal bias to the datasets:


In [3]:
random.shuffle(interactions)
random.shuffle(not_interactions)

We can easily see that the dataset is extremely imbalanced, which would interfere in the neural network's learning. To avoid this we can use several techniques, but for now, to rapidly have a model accuracy baseline we'll just truncate the biggest one to the number of entries of the smallest one:


In [4]:
print(f'Interactions images: {len(interactions)}')
print(f'Not interactions images: {len(interactions)}')


Interactions images: 1692
Not interactions images: 1692

So let's create a subset of the not encounters image dataset (since the list of paths was shuffled we can decide any inteval to perform the truncation):


In [5]:
not_interactions = not_interactions[:len(interactions)]
print(f'New not interactions dataset size is {len(not_interactions)}.')


New not interactions dataset size is 1692.

Now we'll pick 1000 images for the training set, and the remaining will be divided into validation and test sets:


In [6]:
inter = {}
not_inter = {}

inter['training'] = interactions[:1000]
inter['validation'] = interactions[1000:1346]
inter['test'] = interactions[1346:]

not_inter['training'] = not_interactions[:1000]
not_inter['validation'] = not_interactions[1000:1346]
not_inter['test'] = not_interactions[1346:]

We should now copy these sets to their respective folders so we can use Kera's flow from directory functions freely:


In [7]:
datasets = ['training', 'validation', 'test']
subsets = [('interaction', inter), ('not_interaction', not_inter)]

for dataset in datasets:
    output_folder = os.path.join(BASE_DIR, dataset)
    if not os.path.exists(output_folder):
        os.mkdir(output_folder)
    for subset in subsets:
        label_folder = os.path.join(output_folder, subset[0])
        if not os.path.exists(label_folder):
            os.mkdir(label_folder)
        for image in subset[1][dataset]:
            shutil.copyfile(image, os.path.join(label_folder, os.path.basename(image)))

Great! We now have a dataset in the correct format for use in Keras!

Data Preprocessing

Let's implement the Keras' data generators:


In [8]:
train_datagen = ImageDataGenerator(rescale=1./255)
val_datagen = ImageDataGenerator(rescale=1./255)

train_gen = train_datagen.flow_from_directory(TRAIN_DIR, 
                                              target_size=(120,120),
                                              batch_size=8,
                                              class_mode='binary')
val_gen = val_datagen.flow_from_directory(VALIDATION_DIR,
                                          target_size=(120,120),
                                          batch_size=8,
                                          class_mode='binary')


Found 2000 images belonging to 2 classes.
Found 692 images belonging to 2 classes.

Callbacks

Some Keras callbacks to monitor our progress using TensorBoard:


In [9]:
callback = [
    keras.callbacks.TensorBoard(log_dir=LOG_DIR,
    write_graph=True)
]

Our first model: a small convnet


In [10]:
small_conv = models.Sequential()

small_conv.add(layers.Conv2D(32, (3, 3), activation='relu',
               input_shape=(120, 120, 3)))
small_conv.add(layers.MaxPooling2D((2, 2)))
small_conv.add(layers.Conv2D(64, (3, 3), activation='relu'))
small_conv.add(layers.MaxPooling2D((2, 2)))
small_conv.add(layers.Conv2D(128, (3, 3), activation='relu'))
small_conv.add(layers.MaxPooling2D((2, 2)))
small_conv.add(layers.Conv2D(128, (3, 3), activation='relu'))
small_conv.add(layers.MaxPooling2D((2, 2)))
small_conv.add(layers.Flatten())
small_conv.add(layers.Dense(512, activation='relu'))
small_conv.add(layers.Dense(1, activation='sigmoid'))

small_conv.compile(loss='binary_crossentropy', 
                   optimizer=optimizers.RMSprop(lr=1e-4),
                   metrics=['acc'])

In [11]:
history = small_conv.fit_generator(train_gen, steps_per_epoch=100,
                                   epochs=100, validation_data=val_gen,
                                   validation_steps=50,
                                   callbacks=callback)


Epoch 1/100
100/100 [==============================] - 4s 44ms/step - loss: 0.6899 - acc: 0.5238 - val_loss: 0.7025 - val_acc: 0.4775
Epoch 2/100
100/100 [==============================] - 4s 36ms/step - loss: 0.6748 - acc: 0.5825 - val_loss: 0.6425 - val_acc: 0.6425
Epoch 3/100
100/100 [==============================] - 4s 37ms/step - loss: 0.6299 - acc: 0.6575 - val_loss: 0.5856 - val_acc: 0.6750
Epoch 4/100
100/100 [==============================] - 4s 37ms/step - loss: 0.5408 - acc: 0.7250 - val_loss: 0.5982 - val_acc: 0.6700
Epoch 5/100
100/100 [==============================] - 4s 36ms/step - loss: 0.5377 - acc: 0.7262 - val_loss: 0.4749 - val_acc: 0.7800
Epoch 6/100
100/100 [==============================] - 4s 36ms/step - loss: 0.4714 - acc: 0.7850 - val_loss: 0.4642 - val_acc: 0.7825
Epoch 7/100
100/100 [==============================] - 4s 37ms/step - loss: 0.4876 - acc: 0.7638 - val_loss: 0.4261 - val_acc: 0.8000
Epoch 8/100
100/100 [==============================] - 4s 37ms/step - loss: 0.4677 - acc: 0.7700 - val_loss: 0.4217 - val_acc: 0.7925
Epoch 9/100
100/100 [==============================] - 4s 37ms/step - loss: 0.4312 - acc: 0.7987 - val_loss: 0.4117 - val_acc: 0.8075
Epoch 10/100
100/100 [==============================] - 4s 37ms/step - loss: 0.4580 - acc: 0.7812 - val_loss: 0.3930 - val_acc: 0.8150
Epoch 11/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3807 - acc: 0.8225 - val_loss: 0.3948 - val_acc: 0.8300
Epoch 12/100
100/100 [==============================] - 4s 37ms/step - loss: 0.4084 - acc: 0.8075 - val_loss: 0.3990 - val_acc: 0.8075
Epoch 13/100
100/100 [==============================] - 4s 36ms/step - loss: 0.4138 - acc: 0.8075 - val_loss: 0.3915 - val_acc: 0.8050
Epoch 14/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3484 - acc: 0.8488 - val_loss: 0.3670 - val_acc: 0.8225
Epoch 15/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3944 - acc: 0.8088 - val_loss: 0.3826 - val_acc: 0.8300
Epoch 16/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3506 - acc: 0.8475 - val_loss: 0.3547 - val_acc: 0.8475
Epoch 17/100
100/100 [==============================] - 4s 36ms/step - loss: 0.3605 - acc: 0.8363 - val_loss: 0.3424 - val_acc: 0.8525
Epoch 18/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3248 - acc: 0.8588 - val_loss: 0.3605 - val_acc: 0.8450
Epoch 19/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3031 - acc: 0.8650 - val_loss: 0.3433 - val_acc: 0.8375
Epoch 20/100
100/100 [==============================] - 4s 36ms/step - loss: 0.3168 - acc: 0.8550 - val_loss: 0.3560 - val_acc: 0.8250
Epoch 21/100
100/100 [==============================] - 4s 37ms/step - loss: 0.2864 - acc: 0.8812 - val_loss: 0.3481 - val_acc: 0.8525
Epoch 22/100
100/100 [==============================] - 4s 37ms/step - loss: 0.3206 - acc: 0.8500 - val_loss: 0.3425 - val_acc: 0.8550
Epoch 23/100
100/100 [==============================] - 4s 37ms/step - loss: 0.2641 - acc: 0.8787 - val_loss: 0.3376 - val_acc: 0.8450
Epoch 24/100
100/100 [==============================] - 4s 38ms/step - loss: 0.2678 - acc: 0.8850 - val_loss: 0.3206 - val_acc: 0.8725
Epoch 25/100
100/100 [==============================] - 4s 37ms/step - loss: 0.2853 - acc: 0.8750 - val_loss: 0.3267 - val_acc: 0.8700
Epoch 26/100
100/100 [==============================] - 4s 38ms/step - loss: 0.2464 - acc: 0.8900 - val_loss: 0.3354 - val_acc: 0.8500
Epoch 27/100
100/100 [==============================] - 4s 39ms/step - loss: 0.2507 - acc: 0.8912 - val_loss: 0.3186 - val_acc: 0.8650
Epoch 28/100
100/100 [==============================] - 4s 38ms/step - loss: 0.2422 - acc: 0.8975 - val_loss: 0.4454 - val_acc: 0.8050
Epoch 29/100
100/100 [==============================] - 4s 38ms/step - loss: 0.2182 - acc: 0.9087 - val_loss: 0.3648 - val_acc: 0.8275
Epoch 30/100
100/100 [==============================] - 4s 38ms/step - loss: 0.2201 - acc: 0.9025 - val_loss: 0.3264 - val_acc: 0.8675
Epoch 31/100
100/100 [==============================] - 4s 37ms/step - loss: 0.1770 - acc: 0.9300 - val_loss: 0.3314 - val_acc: 0.8650
Epoch 32/100
100/100 [==============================] - 4s 37ms/step - loss: 0.2258 - acc: 0.9050 - val_loss: 0.3099 - val_acc: 0.8750
Epoch 33/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1880 - acc: 0.9200 - val_loss: 0.3290 - val_acc: 0.8575
Epoch 34/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1634 - acc: 0.9375 - val_loss: 0.3966 - val_acc: 0.8525
Epoch 35/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1737 - acc: 0.9287 - val_loss: 0.3264 - val_acc: 0.8675
Epoch 36/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1531 - acc: 0.9350 - val_loss: 0.3189 - val_acc: 0.8500
Epoch 37/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1521 - acc: 0.9488 - val_loss: 0.3679 - val_acc: 0.8675
Epoch 38/100
100/100 [==============================] - 4s 37ms/step - loss: 0.1331 - acc: 0.9450 - val_loss: 0.3634 - val_acc: 0.8475
Epoch 39/100
100/100 [==============================] - 4s 37ms/step - loss: 0.1292 - acc: 0.9500 - val_loss: 0.3519 - val_acc: 0.8650
Epoch 40/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1372 - acc: 0.9412 - val_loss: 0.3613 - val_acc: 0.8625
Epoch 41/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0997 - acc: 0.9600 - val_loss: 0.5846 - val_acc: 0.8075
Epoch 42/100
100/100 [==============================] - 4s 38ms/step - loss: 0.1211 - acc: 0.9525 - val_loss: 0.3259 - val_acc: 0.8750
Epoch 43/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0776 - acc: 0.9725 - val_loss: 0.3524 - val_acc: 0.8700
Epoch 44/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0844 - acc: 0.9700 - val_loss: 0.3527 - val_acc: 0.8650
Epoch 45/100
100/100 [==============================] - 4s 39ms/step - loss: 0.0915 - acc: 0.9600 - val_loss: 0.5627 - val_acc: 0.8025
Epoch 46/100
100/100 [==============================] - 4s 39ms/step - loss: 0.0720 - acc: 0.9738 - val_loss: 0.3478 - val_acc: 0.8800
Epoch 47/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0820 - acc: 0.9675 - val_loss: 0.3740 - val_acc: 0.8850
Epoch 48/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0490 - acc: 0.9850 - val_loss: 0.4409 - val_acc: 0.8675
Epoch 49/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0674 - acc: 0.9688 - val_loss: 0.3357 - val_acc: 0.8975
Epoch 50/100
100/100 [==============================] - 4s 39ms/step - loss: 0.0526 - acc: 0.9875 - val_loss: 0.3976 - val_acc: 0.8650
Epoch 51/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0484 - acc: 0.9838 - val_loss: 0.3708 - val_acc: 0.8725
Epoch 52/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0414 - acc: 0.9875 - val_loss: 0.5901 - val_acc: 0.8200
Epoch 53/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0447 - acc: 0.9862 - val_loss: 0.4055 - val_acc: 0.8700
Epoch 54/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0393 - acc: 0.9887 - val_loss: 0.5566 - val_acc: 0.8550
Epoch 55/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0596 - acc: 0.9800 - val_loss: 0.4025 - val_acc: 0.8675
Epoch 56/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0285 - acc: 0.9912 - val_loss: 0.4222 - val_acc: 0.8700
Epoch 57/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0450 - acc: 0.9875 - val_loss: 0.6226 - val_acc: 0.8500
Epoch 58/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0291 - acc: 0.9925 - val_loss: 0.4968 - val_acc: 0.8650
Epoch 59/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0340 - acc: 0.9925 - val_loss: 0.5804 - val_acc: 0.8575
Epoch 60/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0416 - acc: 0.9850 - val_loss: 0.4331 - val_acc: 0.8800
Epoch 61/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0189 - acc: 0.9963 - val_loss: 0.5990 - val_acc: 0.8600
Epoch 62/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0140 - acc: 0.9975 - val_loss: 0.5230 - val_acc: 0.8750
Epoch 63/100
100/100 [==============================] - 3s 35ms/step - loss: 0.0291 - acc: 0.9912 - val_loss: 0.5555 - val_acc: 0.8725
Epoch 64/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0185 - acc: 0.9950 - val_loss: 0.5062 - val_acc: 0.8675
Epoch 65/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0199 - acc: 0.9925 - val_loss: 0.4479 - val_acc: 0.8775
Epoch 66/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0174 - acc: 0.9925 - val_loss: 0.6098 - val_acc: 0.8675
Epoch 67/100
100/100 [==============================] - 4s 36ms/step - loss: 0.0176 - acc: 0.9925 - val_loss: 0.4218 - val_acc: 0.8875
Epoch 68/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0175 - acc: 0.9912 - val_loss: 0.5117 - val_acc: 0.8900
Epoch 69/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0113 - acc: 0.9950 - val_loss: 0.5932 - val_acc: 0.8675
Epoch 70/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0131 - acc: 0.9963 - val_loss: 0.5748 - val_acc: 0.8850
Epoch 71/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0105 - acc: 0.9950 - val_loss: 0.6021 - val_acc: 0.8800
Epoch 72/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0146 - acc: 0.9975 - val_loss: 0.4835 - val_acc: 0.8950
Epoch 73/100
100/100 [==============================] - 4s 38ms/step - loss: 0.0228 - acc: 0.9938 - val_loss: 0.5878 - val_acc: 0.8675
Epoch 74/100
100/100 [==============================] - 4s 40ms/step - loss: 0.0082 - acc: 0.9975 - val_loss: 0.5190 - val_acc: 0.8875
Epoch 75/100
100/100 [==============================] - 4s 37ms/step - loss: 0.0203 - acc: 0.9925 - val_loss: 0.6455 - val_acc: 0.8650
Epoch 76/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0169 - acc: 0.9938 - val_loss: 0.5562 - val_acc: 0.8750
Epoch 77/100
100/100 [==============================] - 3s 33ms/step - loss: 0.0212 - acc: 0.9900 - val_loss: 0.6254 - val_acc: 0.8700
Epoch 78/100
100/100 [==============================] - 3s 33ms/step - loss: 0.0027 - acc: 1.0000 - val_loss: 0.6734 - val_acc: 0.8775
Epoch 79/100
100/100 [==============================] - 3s 33ms/step - loss: 0.0056 - acc: 1.0000 - val_loss: 0.6397 - val_acc: 0.8625
Epoch 80/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0041 - acc: 1.0000 - val_loss: 0.5875 - val_acc: 0.8875
Epoch 81/100
100/100 [==============================] - 4s 36ms/step - loss: 0.0172 - acc: 0.9950 - val_loss: 0.6329 - val_acc: 0.8650
Epoch 82/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0031 - acc: 0.9988 - val_loss: 0.7434 - val_acc: 0.8600
Epoch 83/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0082 - acc: 0.9975 - val_loss: 0.6759 - val_acc: 0.8775
Epoch 84/100
100/100 [==============================] - 3s 35ms/step - loss: 0.0107 - acc: 0.9975 - val_loss: 0.6520 - val_acc: 0.8850
Epoch 85/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0042 - acc: 0.9988 - val_loss: 0.7323 - val_acc: 0.8800
Epoch 86/100
100/100 [==============================] - 4s 36ms/step - loss: 0.0049 - acc: 0.9988 - val_loss: 0.9527 - val_acc: 0.8550
Epoch 87/100
100/100 [==============================] - 4s 36ms/step - loss: 0.0096 - acc: 0.9963 - val_loss: 0.7759 - val_acc: 0.8750
Epoch 88/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0095 - acc: 0.9963 - val_loss: 0.7507 - val_acc: 0.8700
Epoch 89/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0056 - acc: 0.9988 - val_loss: 0.8742 - val_acc: 0.8575
Epoch 90/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0044 - acc: 0.9975 - val_loss: 0.7358 - val_acc: 0.8600
Epoch 91/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0031 - acc: 1.0000 - val_loss: 0.6686 - val_acc: 0.8850
Epoch 92/100
100/100 [==============================] - 4s 35ms/step - loss: 0.0084 - acc: 0.9975 - val_loss: 0.7791 - val_acc: 0.8725
Epoch 93/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0015 - acc: 0.9988 - val_loss: 0.7790 - val_acc: 0.8750
Epoch 94/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0062 - acc: 0.9988 - val_loss: 0.8458 - val_acc: 0.8675
Epoch 95/100
100/100 [==============================] - 4s 35ms/step - loss: 6.6665e-04 - acc: 1.0000 - val_loss: 0.8023 - val_acc: 0.8775
Epoch 96/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0042 - acc: 0.9975 - val_loss: 0.7648 - val_acc: 0.8950
Epoch 97/100
100/100 [==============================] - 3s 34ms/step - loss: 0.0040 - acc: 0.9988 - val_loss: 0.7634 - val_acc: 0.8800
Epoch 98/100
100/100 [==============================] - 4s 36ms/step - loss: 0.0019 - acc: 1.0000 - val_loss: 1.2990 - val_acc: 0.8550
Epoch 99/100
100/100 [==============================] - 3s 35ms/step - loss: 0.0057 - acc: 0.9975 - val_loss: 0.8186 - val_acc: 0.8800
Epoch 100/100
100/100 [==============================] - 3s 35ms/step - loss: 0.0016 - acc: 1.0000 - val_loss: 0.8099 - val_acc: 0.8650

Hmmm, we made it to around 86%! With only 2000 labeled images! Not bad, not bad...

But can we do even better?