text: """
  Train on images split into directories. This assumes we've split
  our videos into frames and moved them to their respective folders.
  
  Based on:
  https://keras.io/preprocessing/image/
  and
  https://keras.io/applications/
  """
  from keras.applications.inception_v3 import InceptionV3
  from keras.optimizers import SGD
  from keras.preprocessing.image import ImageDataGenerator
  from keras.models import Model
  from keras.layers import Dense, GlobalAveragePooling2D
  from keras.callbacks import ModelCheckpoint, TensorBoard, EarlyStopping
  from keras.layers.advanced_activations import PReLU
  from keras.layers.normalization import BatchNormalization
  from data import DataSet
  import os.path
  
  data = DataSet()
  
  # Helper: Save the model.
  checkpointer = ModelCheckpoint(
      filepath=os.path.join('data', 'checkpoints', 'inception.{epoch:03d}-{val_loss:.2f}.hdf5'),
      verbose=1,
      save_best_only=True)
  
  # Helper: Stop when we stop learning.
  early_stopper = EarlyStopping(patience=20)
  
  # Helper: TensorBoard
  tensorboard = TensorBoard(log_dir=os.path.join('data', 'logs'))
  
  def get_generators():
      train_datagen = ImageDataGenerator(
          rescale=1./255,
          shear_range=0.2,
          horizontal_flip=True,
          zoom_range=0.1,
          rotation_range=10.,
          width_shift_range=0.2,
          height_shift_range=0.2)
  
      test_datagen = ImageDataGenerator(rescale=1./255)
  
      train_generator = train_datagen.flow_from_directory(
          os.path.join('data', 'train'),
          target_size=(299, 299),
          batch_size=16,
          classes=data.classes,
          class_mode='categorical')
  
      validation_generator = test_datagen.flow_from_directory(
          os.path.join('data', 'test'),
          target_size=(299, 299),
          batch_size=16,
          classes=data.classes,
          class_mode='categorical')
  
      return train_generator, validation_generator
  
  def get_model(weights='imagenet'):
      # create the base pre-trained model
      base_model = InceptionV3(weights=weights, include_top=False)
  
      # add a global spatial average pooling layer
      x = base_model.output
      x = GlobalAveragePooling2D()(x)
      x = BatchNormalization()(x)
      # let's add a fully-connected layer
      x = Dense(1024)(x)
      x = PReLU()(x)
      # and a logistic layer
      predictions = Dense(len(data.classes), activation='softmax')(x)
  
      # this is the model we will train
      model = Model(inputs=base_model.input, outputs=predictions)
      return model
  
  def freeze_all_but_top(model):
      """Used to train just the top layers of the model."""
      # first: train only the top layers (which were randomly initialized)
      # i.e. freeze all convolutional InceptionV3 layers
      for layer in model.layers[:-2]:
          layer.trainable = False
  
      # compile the model (should be done *after* setting layers to non-trainable)
      model.compile(optimizer='Adam', loss='categorical_crossentropy', metrics=['accuracy'])
  
      return model
  
  def freeze_all_but_mid_and_top(model):
      """After we fine-tune the dense layers, train deeper."""
      # we chose to train the top 2 inception blocks, i.e. we will freeze
      # the first 172 layers and unfreeze the rest:
      for layer in model.layers[:172]:
          layer.trainable = False
      for layer in model.layers[172:]:
          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=SGD(lr=0.0001, momentum=0.9),
          loss='categorical_crossentropy',
          metrics=['accuracy', 'top_k_categorical_accuracy'])
  
      return model
  
  def train_model(model, nb_epoch, generators, callbacks=[]):
      train_generator, validation_generator = generators
      model.fit_generator(
          train_generator,
          steps_per_epoch=100,
          validation_data=validation_generator,
          validation_steps=20,
          epochs=nb_epoch,
          callbacks=callbacks)
      return model
  
  def main(weights_file):
      model = get_model()
      generators = get_generators()
  
      if weights_file is None:
          print("Loading network from ImageNet weights.")
          # Get and train the top layers.
          model = freeze_all_but_top(model)
          model = train_model(model, 15, generators)
      else:
          print("Loading saved model: %s." % weights_file)
          model.load_weights(weights_file)
  
      # Get and train the mid layers.
      model = freeze_all_but_mid_and_top(model)
      model = train_model(model, 1000, generators,
                          [checkpointer, early_stopper, tensorboard])
  
  if __name__ == '__main__':
      weights_file = None
      main(weights_file)
  