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/'
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)}')
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)}.')
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!
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')
Some Keras callbacks to monitor our progress using TensorBoard:
In [9]:
callback = [
keras.callbacks.TensorBoard(log_dir=LOG_DIR,
write_graph=True)
]
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)
Hmmm, we made it to around 86%! With only 2000 labeled images! Not bad, not bad...
But can we do even better?