<reponame>jhuarte/CarND-Behavioral-Cloning-P4
#!/usr/bin/env python
# coding: utf-8

# In[ ]:


import os
import cv2
import argparse
import numpy as np
import pandas as pd
import seaborn as sns
import tensorflow as tf
import matplotlib.pyplot as plt


keras = tf.keras

# Camera position and keys for the DataFrame (columns) and keys
CENTER = 'center'
LEFT = 'left'
RIGHT = 'right'
IMAGE = 'image'
POSITIONS = [CENTER, LEFT, RIGHT]
# Correction used to train the model with lateral cameras as if they were on the center
STEERING_CORRECTION = 0.2

IMG_HEIGHT_SIZE = 160
IMG_WIDTH_SIZE = 320
IMG_DEPTH = 3

################################################################################################################
# Transform the DataFrame from 3 columns for the images (center, left, right) to only one (for simplicity as
# and modify the 'steering' value for the lateral cameras (trigronometry)
################################################################################################################
def lateral_steering_transform(steering, camera_position):
    """
    Transform the steering of the lateral cameras (left, right)
    
    Parameters:
        steering (numpy.float): Original steering

    Returns:
        out (numpy.float): New steering
      """
    if (camera_position == LEFT):
        steering += STEERING_CORRECTION
    if (camera_position == RIGHT):
        steering -= STEERING_CORRECTION
    return steering

def row_update(row, camera_position, positions):
    """
    Transform the steering of the lateral cameras (left, right)
    
    Parameters:
        row (pandas.Sereis): One DataFrame's row
        camera (string): Camera position (center, left, right)
    Returns:
        out (dictionary): Dictionary (keys: image, steering, throttle, brake, speed)
    """
    d = {}
    if (camera_position in positions):
        # Fill the data (first the data on the dataframe)
        d.update(image = row[camera_position], steering = lateral_steering_transform(float(row['steering']), camera_position), 
        throttle = row['throttle'], brake = row['brake'], speed = row['speed'])     
    else:
        print('The current position {%s} is not supported'.format(camera_position))

    return d

def plain_dataframe(data):
    """
    Iterate over a DataFrame to create a new one with only 1 images column
    
    Parameters:
        data (pandas.DataFrame): Original DataFrame(3 columns images: center, left and right)

    Returns:
        out (pandas.DataFrame): New DataFrame (1 column images)
    """   
    row_list = []  
    for idx, row in data.iterrows():
        # Iterate over the three camera positions 
        for camera_position in POSITIONS:
            d = row_update(row, camera_position, POSITIONS)
            row_list.append(d)

    return pd.DataFrame(row_list)




################################################################################################################
# Flip images and reverse the sttering angle
################################################################################################################

def flip_image(fname):
    """
    Flip horizontally the image and save it on the same directory but adding the suffix '_flipped'
    
    Parameters:
        fname (string): Path of the image 

    Returns:
        fname (string): The new path of the flipped image
    """    
    # Read de image from disk
    img = cv2.imread(fname)
    # Horizontal flip
    flipped = cv2.flip(img, flipCode = 1)
    # Set the new name
    head, tail =  os.path.split(fname)
    root, ext = os.path.splitext(tail)
    fname = root + '_flipped' + ext
    # Write the flipped image to disk
    cv2.imwrite(os.path.join(head,fname), flipped)
    # Return the new name
    return fname

def flip_dataframe_images(data):
    """
    Iterate over a DataFrame, read the relative path of the images (center, left, rigth) and call the flip_image function 
    
    Parameters:
        fname (string): Path of the image 
    
    Returns:
        fname (string): The new path of the flipped image
    """   
    d = {}
    row_list = []  
    for idx, row in data.iterrows():
        # Initialize a dictionary
        # Fill the data (first the data on the dataframe)
        d = {}
        d.update(image = row[IMAGE], steering = row['steering'], throttle = row['throttle'], brake = row['brake'], speed = row['speed'])    
        row_list.append(d)
        # Flip images, store and create the dataframe row
        d = {}
        d.update(image = flip_image(os.path.join(IMAGES_DIR, row[IMAGE])),
                 steering = -row['steering'], throttle = row['throttle'], brake = row['brake'], speed = row['speed'])    
        row_list.append(d)

    return pd.DataFrame(row_list)

def load_data(log_file, valid_split, images_dir, batch_size):
    
    
    # Read the 'driving_log' file
    #     1. Add columns name to manage the data
    #     2. Add "dtype" for each column    
    driving_log = pd.read_csv(log_file, 
                              names = ['center', 'left', 'right', 'steering', 'throttle', 'brake', 'speed'],
                              dtype = {'center': pd.StringDtype(), 
                                       'left': pd.StringDtype(), 
                                       'right': pd.StringDtype(), 
                                       'steering': np.float32, 
                                       'throttle': np.float32, 
                                       'brake': np.float32,                                    
                                       'speed': np.float32}                           
                             )
    # As the path of the images contain the full path (os independat due the replace statemente) split it and replace only with the filename
    driving_log[CENTER] = driving_log[CENTER].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    driving_log[LEFT] = driving_log[LEFT].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    driving_log[RIGHT] = driving_log[RIGHT].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    # From three to one column of images (for better understand)
    data = plain_dataframe(driving_log)
    # Generate augmented dataframes
    data = flip_dataframe_images(data)
    ################################################################################################################
    # Data generator (tf.keras.preprocessing.ImageDataGenerator)
    ################################################################################################################
    # Create the image data generator
    datagen = keras.preprocessing.image.ImageDataGenerator(validation_split = valid_split,
                                                           shear_range = 0.2,
                                                           height_shift_range = 0.2,
                                                           fill_mode = "nearest",
                                                           brightness_range = [0.3, 0.9])

    # Training data generator by default color_mode = 'rgb' and shuffle = True
    train_generator = datagen.flow_from_dataframe(
        dataframe = driving_log, 
        directory = images_dir, 
        x_col = IMAGE,
        y_col = "steering",
        class_mode = "raw",
        target_size = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE),
        batch_size = batch_size,
        subset = "training")

    # Validation data generator by default color_mode = 'rgb' and shuffle = True
    valid_generator = datagen.flow_from_dataframe(
        dataframe = driving_log, 
        directory = images_dir, 
        x_col = IMAGE,
        y_col = "steering",
        class_mode = "raw",
        target_size = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE),
        batch_size = batch_size,
        subset = "validation")
    
    return train_generator, valid_generator
        
################################################################################################################
# End-to-End Learning for Self-Driving (NVIDIA)
################################################################################################################

def create_model_nvidia(keep_prob):
    """
    Create End-to-End Learning for Self-Driving (NVIDIA)
    
    Parameters:
        type (string): 

    Returns:
        model (keras.models): keras.models
    """  
    # Clean the model
    keras.backend.clear_session()   
    CONV1_FILTERS = 24
    CONV2_FILTERS = 36
    CONV3_FILTERS = 48
    CONV4_FILTERS = 64
    CONV5_FILTERS = 64
    FC1_UNITS = 1164
    FC2_UNITS = 100
    FC3_UNITS = 50
    
    model = keras.models.Sequential([
        keras.layers.Cropping2D(cropping=((70, 25), (0, 0)), input_shape = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE, IMG_DEPTH)),
        keras.layers.Lambda(lambda x: tf.image.per_image_standardization(x)), 
        keras.layers.Conv2D(CONV1_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV2_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV3_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV4_FILTERS, (3, 3), activation = 'relu'),
        keras.layers.Conv2D(CONV5_FILTERS, (3, 3), activation = 'relu'),
        # Fully connected layers
        keras.layers.Flatten(),
        keras.layers.Dense(FC1_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(FC2_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(FC3_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(1)
    ])
    
    model.compile(loss = keras.losses.mse, optimizer =Adam(lr=args.learning_rate), metrics = ["mse"])

    return model

def train_model(model, epochs, training_data, validation_data, ckp_path = "model.ckpt"):
    """
    Train the model
    
    Parameters:
        model (keras.models): Model to train
        epochs (integer): Number of epochs
        training_data (keras.preprocessing.image.ImageDataGenerator): Training data
        validation_data (keras.preprocessing.image.ImageDataGenerator): Validation data (to implement cross-validation)
        ckp_path (string): Path to store the checkpoints (only weights and the best)

    Returns:
        history (keras.history): History for data analytics
    """   
 
    # Create a callback that saves the model's weights every 5 epochs
    cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath = ckp_path,
                                                     verbose = 1,
                                                     save_weights_only = True,
                                                     save_best_only = True)
    # Configure early stopping callback
    early_stopping = keras.callbacks.EarlyStopping(patience = 10)
    # Train the model with the new callback
    history = model.fit(x = training_data, 
                        epochs = epochs, 
                        validation_data = validation_data,
                        callbacks = [cp_callback, early_stopping])
    
    return history

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description = 'Behavioral Cloning Training Program')
    parser.add_argument('-d', help='data directory',        dest='data_dir',          type = str,   default = 'data')
    parser.add_argument('-v', help='validation proportion', dest='val_prop',          type = float, default = 0.2)
    parser.add_argument('-k', help='drop out probability',  dest='keep_prob',         type = float, default = 0.5)
    parser.add_argument('-e', help='number of epochs',      dest='epochs',            type = int,   default = 3)
    parser.add_argument('-b', help='batch size',            dest='batch_size',        type = int,   default = 40)
    parser.add_argument('-l', help='learning rate',         dest='learning_rate',     type = float, default = 1.0e-4)
    args = parser.parse_args()

    BASE_DIR = os.path.join(args.data_dir)
    LOG_FILE =  os.path.join(BASE_DIR, 'driving_log.csv')
    IMAGES_DIR = os.path.join(BASE_DIR, 'IMG')
    
    print('-' * 30)
    print('Checking parameters....')
    print('-' * 30)
    for key, value in vars(args).items():
        print('{:<20} : {}'.format(key, value))
    print('-' * 30)

    print('Training...')			
    train_generator, valid_generator = load_data(LOG_FILE, args.val_prop, IMAGES_DIR, args.batch_size)
    model = build_model(keep_prob = args.keep_prob)
    train_model(model, args.epochs, train_generator, valid_generator, ckp_path):
    print('Done')


#!/usr/bin/env python
# coding: utf-8

# In[ ]:


import os
import cv2
import argparse
import numpy as np
import pandas as pd
import seaborn as sns
import tensorflow as tf
import matplotlib.pyplot as plt


keras = tf.keras

# Camera position and keys for the DataFrame (columns) and keys
CENTER = 'center'
LEFT = 'left'
RIGHT = 'right'
IMAGE = 'image'
POSITIONS = [CENTER, LEFT, RIGHT]
# Correction used to train the model with lateral cameras as if they were on the center
STEERING_CORRECTION = 0.2

IMG_HEIGHT_SIZE = 160
IMG_WIDTH_SIZE = 320
IMG_DEPTH = 3

################################################################################################################
# Transform the DataFrame from 3 columns for the images (center, left, right) to only one (for simplicity as
# and modify the 'steering' value for the lateral cameras (trigronometry)
################################################################################################################
def lateral_steering_transform(steering, camera_position):
    """
    Transform the steering of the lateral cameras (left, right)
    
    Parameters:
        steering (numpy.float): Original steering

    Returns:
        out (numpy.float): New steering
      """
    if (camera_position == LEFT):
        steering += STEERING_CORRECTION
    if (camera_position == RIGHT):
        steering -= STEERING_CORRECTION
    return steering

def row_update(row, camera_position, positions):
    """
    Transform the steering of the lateral cameras (left, right)
    
    Parameters:
        row (pandas.Sereis): One DataFrame's row
        camera (string): Camera position (center, left, right)
    Returns:
        out (dictionary): Dictionary (keys: image, steering, throttle, brake, speed)
    """
    d = {}
    if (camera_position in positions):
        # Fill the data (first the data on the dataframe)
        d.update(image = row[camera_position], steering = lateral_steering_transform(float(row['steering']), camera_position), 
        throttle = row['throttle'], brake = row['brake'], speed = row['speed'])     
    else:
        print('The current position {%s} is not supported'.format(camera_position))

    return d

def plain_dataframe(data):
    """
    Iterate over a DataFrame to create a new one with only 1 images column
    
    Parameters:
        data (pandas.DataFrame): Original DataFrame(3 columns images: center, left and right)

    Returns:
        out (pandas.DataFrame): New DataFrame (1 column images)
    """   
    row_list = []  
    for idx, row in data.iterrows():
        # Iterate over the three camera positions 
        for camera_position in POSITIONS:
            d = row_update(row, camera_position, POSITIONS)
            row_list.append(d)

    return pd.DataFrame(row_list)




################################################################################################################
# Flip images and reverse the sttering angle
################################################################################################################

def flip_image(fname):
    """
    Flip horizontally the image and save it on the same directory but adding the suffix '_flipped'
    
    Parameters:
        fname (string): Path of the image 

    Returns:
        fname (string): The new path of the flipped image
    """    
    # Read de image from disk
    img = cv2.imread(fname)
    # Horizontal flip
    flipped = cv2.flip(img, flipCode = 1)
    # Set the new name
    head, tail =  os.path.split(fname)
    root, ext = os.path.splitext(tail)
    fname = root + '_flipped' + ext
    # Write the flipped image to disk
    cv2.imwrite(os.path.join(head,fname), flipped)
    # Return the new name
    return fname

def flip_dataframe_images(data):
    """
    Iterate over a DataFrame, read the relative path of the images (center, left, rigth) and call the flip_image function 
    
    Parameters:
        fname (string): Path of the image 
    
    Returns:
        fname (string): The new path of the flipped image
    """   
    d = {}
    row_list = []  
    for idx, row in data.iterrows():
        # Initialize a dictionary
        # Fill the data (first the data on the dataframe)
        d = {}
        d.update(image = row[IMAGE], steering = row['steering'], throttle = row['throttle'], brake = row['brake'], speed = row['speed'])    
        row_list.append(d)
        # Flip images, store and create the dataframe row
        d = {}
        d.update(image = flip_image(os.path.join(IMAGES_DIR, row[IMAGE])),
                 steering = -row['steering'], throttle = row['throttle'], brake = row['brake'], speed = row['speed'])    
        row_list.append(d)

    return pd.DataFrame(row_list)

def load_data(log_file, valid_split, images_dir, batch_size):
    
    
    # Read the 'driving_log' file
    #     1. Add columns name to manage the data
    #     2. Add "dtype" for each column    
    driving_log = pd.read_csv(log_file, 
                              names = ['center', 'left', 'right', 'steering', 'throttle', 'brake', 'speed'],
                              dtype = {'center': pd.StringDtype(), 
                                       'left': pd.StringDtype(), 
                                       'right': pd.StringDtype(), 
                                       'steering': np.float32, 
                                       'throttle': np.float32, 
                                       'brake': np.float32,                                    
                                       'speed': np.float32}                           
                             )
    # As the path of the images contain the full path (os independat due the replace statemente) split it and replace only with the filename
    driving_log[CENTER] = driving_log[CENTER].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    driving_log[LEFT] = driving_log[LEFT].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    driving_log[RIGHT] = driving_log[RIGHT].str.replace('\\','/').str.rsplit("/", expand = True, n = 1)[1]
    # From three to one column of images (for better understand)
    data = plain_dataframe(driving_log)
    # Generate augmented dataframes
    data = flip_dataframe_images(data)
    ################################################################################################################
    # Data generator (tf.keras.preprocessing.ImageDataGenerator)
    ################################################################################################################
    # Create the image data generator
    datagen = keras.preprocessing.image.ImageDataGenerator(validation_split = valid_split,
                                                           shear_range = 0.2,
                                                           height_shift_range = 0.2,
                                                           fill_mode = "nearest",
                                                           brightness_range = [0.3, 0.9])

    # Training data generator by default color_mode = 'rgb' and shuffle = True
    train_generator = datagen.flow_from_dataframe(
        dataframe = driving_log, 
        directory = images_dir, 
        x_col = IMAGE,
        y_col = "steering",
        class_mode = "raw",
        target_size = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE),
        batch_size = batch_size,
        subset = "training")

    # Validation data generator by default color_mode = 'rgb' and shuffle = True
    valid_generator = datagen.flow_from_dataframe(
        dataframe = driving_log, 
        directory = images_dir, 
        x_col = IMAGE,
        y_col = "steering",
        class_mode = "raw",
        target_size = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE),
        batch_size = batch_size,
        subset = "validation")
    
    return train_generator, valid_generator
        
################################################################################################################
# End-to-End Learning for Self-Driving (NVIDIA)
################################################################################################################

def create_model_nvidia(keep_prob):
    """
    Create End-to-End Learning for Self-Driving (NVIDIA)
    
    Parameters:
        type (string): 

    Returns:
        model (keras.models): keras.models
    """  
    # Clean the model
    keras.backend.clear_session()   
    CONV1_FILTERS = 24
    CONV2_FILTERS = 36
    CONV3_FILTERS = 48
    CONV4_FILTERS = 64
    CONV5_FILTERS = 64
    FC1_UNITS = 1164
    FC2_UNITS = 100
    FC3_UNITS = 50
    
    model = keras.models.Sequential([
        keras.layers.Cropping2D(cropping=((70, 25), (0, 0)), input_shape = (IMG_HEIGHT_SIZE, IMG_WIDTH_SIZE, IMG_DEPTH)),
        keras.layers.Lambda(lambda x: tf.image.per_image_standardization(x)), 
        keras.layers.Conv2D(CONV1_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV2_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV3_FILTERS, (5, 5), activation = 'relu'),
        keras.layers.MaxPooling2D(pool_size = (2, 2), strides = 2, padding = 'SAME'),
        keras.layers.Conv2D(CONV4_FILTERS, (3, 3), activation = 'relu'),
        keras.layers.Conv2D(CONV5_FILTERS, (3, 3), activation = 'relu'),
        # Fully connected layers
        keras.layers.Flatten(),
        keras.layers.Dense(FC1_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(FC2_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(FC3_UNITS, activation = 'relu'),
        keras.layers.Dropout(rate = keep_prob),
        keras.layers.Dense(1)
    ])
    
    model.compile(loss = keras.losses.mse, optimizer =Adam(lr=args.learning_rate), metrics = ["mse"])

    return model

def train_model(model, epochs, training_data, validation_data, ckp_path = "model.ckpt"):
    """
    Train the model
    
    Parameters:
        model (keras.models): Model to train
        epochs (integer): Number of epochs
        training_data (keras.preprocessing.image.ImageDataGenerator): Training data
        validation_data (keras.preprocessing.image.ImageDataGenerator): Validation data (to implement cross-validation)
        ckp_path (string): Path to store the checkpoints (only weights and the best)

    Returns:
        history (keras.history): History for data analytics
    """   
 
    # Create a callback that saves the model's weights every 5 epochs
    cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath = ckp_path,
                                                     verbose = 1,
                                                     save_weights_only = True,
                                                     save_best_only = True)
    # Configure early stopping callback
    early_stopping = keras.callbacks.EarlyStopping(patience = 10)
    # Train the model with the new callback
    history = model.fit(x = training_data, 
                        epochs = epochs, 
                        validation_data = validation_data,
                        callbacks = [cp_callback, early_stopping])
    
    return history

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description = 'Behavioral Cloning Training Program')
    parser.add_argument('-d', help='data directory',        dest='data_dir',          type = str,   default = 'data')
    parser.add_argument('-v', help='validation proportion', dest='val_prop',          type = float, default = 0.2)
    parser.add_argument('-k', help='drop out probability',  dest='keep_prob',         type = float, default = 0.5)
    parser.add_argument('-e', help='number of epochs',      dest='epochs',            type = int,   default = 3)
    parser.add_argument('-b', help='batch size',            dest='batch_size',        type = int,   default = 40)
    parser.add_argument('-l', help='learning rate',         dest='learning_rate',     type = float, default = 1.0e-4)
    args = parser.parse_args()

    BASE_DIR = os.path.join(args.data_dir)
    LOG_FILE =  os.path.join(BASE_DIR, 'driving_log.csv')
    IMAGES_DIR = os.path.join(BASE_DIR, 'IMG')
    
    print('-' * 30)
    print('Checking parameters....')
    print('-' * 30)
    for key, value in vars(args).items():
        print('{:<20} : {}'.format(key, value))
    print('-' * 30)

    print('Training...')			
    train_generator, valid_generator = load_data(LOG_FILE, args.val_prop, IMAGES_DIR, args.batch_size)
    model = build_model(keep_prob = args.keep_prob)
    train_model(model, args.epochs, train_generator, valid_generator, ckp_path):
    print('Done')
#!/usr/bin/env python # coding: utf-8 # In[ ]: # Camera position and keys for the DataFrame (columns) and keys # Correction used to train the model with lateral cameras as if they were on the center ################################################################################################################ # Transform the DataFrame from 3 columns for the images (center, left, right) to only one (for simplicity as # and modify the 'steering' value for the lateral cameras (trigronometry) ################################################################################################################ Transform the steering of the lateral cameras (left, right)          Parameters:         steering (numpy.float): Original steering      Returns:         out (numpy.float): New steering Transform the steering of the lateral cameras (left, right)          Parameters:         row (pandas.Sereis): One DataFrame's row         camera (string): Camera position (center, left, right)     Returns:         out (dictionary): Dictionary (keys: image, steering, throttle, brake, speed) # Fill the data (first the data on the dataframe) Iterate over a DataFrame to create a new one with only 1 images column          Parameters:         data (pandas.DataFrame): Original DataFrame(3 columns images: center, left and right)      Returns:         out (pandas.DataFrame): New DataFrame (1 column images) # Iterate over the three camera positions ################################################################################################################ # Flip images and reverse the sttering angle ################################################################################################################ Flip horizontally the image and save it on the same directory but adding the suffix '_flipped'          Parameters:         fname (string): Path of the image       Returns:         fname (string): The new path of the flipped image # Read de image from disk # Horizontal flip # Set the new name # Write the flipped image to disk # Return the new name Iterate over a DataFrame, read the relative path of the images (center, left, rigth) and call the flip_image function           Parameters:         fname (string): Path of the image           Returns:         fname (string): The new path of the flipped image # Initialize a dictionary # Fill the data (first the data on the dataframe) # Flip images, store and create the dataframe row # Read the 'driving_log' file #     1. Add columns name to manage the data #     2. Add "dtype" for each column # As the path of the images contain the full path (os independat due the replace statemente) split it and replace only with the filename # From three to one column of images (for better understand) # Generate augmented dataframes ################################################################################################################ # Data generator (tf.keras.preprocessing.ImageDataGenerator) ################################################################################################################ # Create the image data generator # Training data generator by default color_mode = 'rgb' and shuffle = True # Validation data generator by default color_mode = 'rgb' and shuffle = True ################################################################################################################ # End-to-End Learning for Self-Driving (NVIDIA) ################################################################################################################ Create End-to-End Learning for Self-Driving (NVIDIA)          Parameters:         type (string):       Returns:         model (keras.models): keras.models # Clean the model # Fully connected layers Train the model          Parameters:         model (keras.models): Model to train         epochs (integer): Number of epochs         training_data (keras.preprocessing.image.ImageDataGenerator): Training data         validation_data (keras.preprocessing.image.ImageDataGenerator): Validation data (to implement cross-validation)         ckp_path (string): Path to store the checkpoints (only weights and the best)      Returns:         history (keras.history): History for data analytics # Create a callback that saves the model's weights every 5 epochs # Configure early stopping callback # Train the model with the new callback