Skip to content

Instantly share code, notes, and snippets.

@Thimira
Last active December 1, 2019 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Thimira/8f5d77a5df3aa96a195e6f7f8c62e751 to your computer and use it in GitHub Desktop.
Save Thimira/8f5d77a5df3aa96a195e6f7f8c62e751 to your computer and use it in GitHub Desktop.
The main model training code from the Bird Watch project: https://github.com/Thimira/bird_watch . The project is currently live at https://www.birdwatch.photo/
import numpy as np
from keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img
from keras.models import Sequential, Model
from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D, Input
from keras.applications.inception_v3 import InceptionV3
from keras.utils.np_utils import to_categorical
from keras import optimizers
from keras.callbacks import EarlyStopping, ModelCheckpoint
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import math
import cv2
import os
import sys
import configparser
import warnings
# filter out unwanted "Corrupt EXIF data" warnings from PIL
# https://github.com/python-pillow/Pillow/issues/518
warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
config = configparser.ConfigParser()
config.read('conf/application.ini')
app_config = config['training']
# dimensions of our images.
img_width = app_config.getint('img_width')
img_height = app_config.getint('img_height')
train_data_dir = 'data/train'
class_indices_path = app_config.get('class_dictionary_path')
initial_model_path = app_config.get('initial_model_path')
final_model_path = app_config.get('final_model_path')
# number of epochs to train top model
train_epochs = app_config.getint('train_epochs')
fine_tune_epochs = app_config.getint('fine_tune_epochs')
# batch size used by flow_from_directory and fit_generator
batch_size = app_config.getint('train_batch_size')
datagen = ImageDataGenerator(
rescale=1. / 255,
rotation_range=40,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest',
validation_split=0.25)
# define the ImageNet mean subtraction (in RGB order) and set the
# the mean subtraction value for the data augmentation object
imagenet_mean = np.array([123.68, 116.779, 103.939], dtype="float32")
datagen.mean = imagenet_mean
train_generator = datagen.flow_from_directory(
train_data_dir,
target_size=(img_width, img_height),
batch_size=batch_size,
class_mode='categorical',
interpolation='lanczos',
subset='training')
validation_generator = datagen.flow_from_directory(
train_data_dir,
target_size=(img_width, img_height),
batch_size=batch_size,
class_mode='categorical',
interpolation='lanczos',
subset='validation')
nb_train_samples = len(train_generator.filenames)
nb_validation_samples = len(validation_generator.filenames)
num_classes = len(train_generator.class_indices)
train_labels = train_generator.classes
validation_labels = validation_generator.classes
print("[Info] Dataset stats: Training: {} Validation: {}".format(nb_train_samples, nb_validation_samples))
print("[Info] Number of Classes: {}".format(num_classes))
print("[Info] Class Labels: {}".format(train_generator.class_indices))
# save the class indices for use in the predictions
np.save(class_indices_path, train_generator.class_indices)
def plot_history(history, save_fig=False, save_path='data/models-new/training.png'):
plt.rcParams["figure.figsize"] = (12, 9)
plt.style.use('ggplot')
plt.figure(1)
# summarize history for accuracy
plt.subplot(211)
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('Model Accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='lower right')
# summarize history for loss
plt.subplot(212)
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model Loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Training', 'Validation'], loc='upper right')
plt.tight_layout()
if save_fig:
plt.savefig(save_path, bbox_inches='tight', dpi=300)
else:
plt.show()
# clear and close the current figure
plt.clf()
plt.close()
# create the base pre-trained model
base_model = InceptionV3(weights='imagenet', include_top=False, input_tensor=Input(shape=(img_width, img_height, 3)))
# add a global spatial average pooling layer
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
predictions = Dense(num_classes, activation='softmax')(x)
# this is the model we will train
model = Model(inputs=base_model.input, outputs=predictions)
# first: train only the top layers (which were randomly initialized)
# i.e. freeze all convolutional InceptionV3 layers
for layer in base_model.layers:
layer.trainable = False
# compile the model (should be done *after* setting layers to non-trainable)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
train_steps = int(math.ceil(nb_train_samples / batch_size))
validation_steps = int(math.ceil(nb_validation_samples / batch_size))
bn_start_time = datetime.now()
print("[Info] Model Bottlenecking started at: {}".format(bn_start_time))
early_stop = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
history = model.fit_generator(
train_generator,
steps_per_epoch=train_steps,
epochs=train_epochs,
validation_data=validation_generator,
validation_steps=validation_steps,
callbacks=[early_stop])
plot_history(history, save_fig=True, save_path='data/models-new/bottleneck.png')
print("\n")
bn_end_time = datetime.now()
print("[Info] Model Bottlenecking completed at: {}".format(bn_end_time))
bn_duration = bn_end_time - bn_start_time
print("[Info] Total time for Bottlenecking: {}".format(bn_duration))
(eval_loss, eval_accuracy) = model.evaluate_generator(
validation_generator,
steps=validation_steps)
print("\n")
print("[INFO] accuracy: {:.2f}%".format(eval_accuracy * 100))
print("[INFO] Loss: {}".format(eval_loss))
print("[Info] Saving initial model to disk: {}".format(initial_model_path))
model.save(initial_model_path)
# reset our data generators
train_generator.reset()
validation_generator.reset()
# we chose to train the top 2 inception blocks, i.e. we will freeze
# the first 249 layers and unfreeze the rest:
for layer in model.layers[:249]:
layer.trainable = False
for layer in model.layers[249:]:
layer.trainable = True
# we need to recompile the model for these modifications to take effect
# we use SGD with a low learning rate
model.compile(optimizer=optimizers.SGD(lr=0.0001, momentum=0.9),
loss='categorical_crossentropy',
metrics=['accuracy'])
filepath="data/models-new/checkpoints/model-{epoch:02d}-{val_acc:.2f}-{val_loss:.2f}.h5"
checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, save_weights_only=False, mode='min')
early_stop_ft = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=5)
callbacks_list = [early_stop_ft, checkpoint]
ft_start_time = datetime.now()
print("[Info] Model Fine-tuning started at: {}".format(ft_start_time))
history = model.fit_generator(
train_generator,
steps_per_epoch=train_steps,
epochs=fine_tune_epochs,
validation_data=validation_generator,
validation_steps=validation_steps,
callbacks=callbacks_list)
print("[Info] Saving final model to disk: {}".format(final_model_path))
model.save(final_model_path)
print("\n")
plot_history(history, save_fig=True, save_path='data/models-new/finetune.png')
(eval_loss, eval_accuracy) = model.evaluate_generator(
validation_generator,
steps=validation_steps)
print("\n")
ft_end_time = datetime.now()
print("[Info] Model Fine-tuning completed at: {}".format(ft_end_time))
ft_duration = ft_end_time - ft_start_time
print("[Info] Total time for Fine-tuning: {}".format(ft_duration))
tot_duration = ft_end_time - bn_start_time
print("[Info] Total time for training: {}".format(tot_duration))
print("\n")
print("[INFO] accuracy: {:.2f}%".format(eval_accuracy * 100))
print("[INFO] Loss: {}".format(eval_loss))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment