text: import csv
  import cv2
  import numpy as np
  import tensorflow as tf
  from keras.models import Sequential
  from keras.layers.core import Dense, Activation, Flatten, Dropout, Lambda
  from keras.layers.convolutional import Conv2D
  from keras.layers.pooling import MaxPooling2D
  from keras.layers import Cropping2D
  import matplotlib.pyplot as plt
  from keras.utils.vis_utils import plot_model
  
  ### Load the data
  lines = []
  with open('data/driving_log.csv') as csvfile:
      reader = csv.reader(csvfile)
      next(reader)
      for line in reader:
          lines.append(line)
          
  ### Preprocess the data        
  images = []
  measurements = []
  correction = 0.2
  for line in lines:
      for i in range(3):
          source_path = line[i]
          filename = source_path.split('/')[-1]
          current_path = 'data/IMG/' + filename
          image = cv2.imread(current_path)
          image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
          images.append(image)
          if i == 0:
              measurement = float(line[3])#using center camera image
          elif i == 1:
              measurement = float(line[3]) + correction#left camera image
          else:
              measurement = float(line[3]) -correction# right camera image
          measurements.append(measurement)
      
  ##Data Augmentation
  augmented_images, augmented_measurements = [],[]
  for image, measurement in zip(images, measurements):
      augmented_images.append(image)
      augmented_measurements.append(measurement)
      augmented_images.append(cv2.flip(image, 1))
      augmented_measurements.append(measurement*-1.0)
  
  X_train = np.array(augmented_images)
  y_train = np.array(augmented_measurements)
  
  
  ## Model architecture NVIDIA network architecture
  model = Sequential()
  #50 rows pixels from the top of the image, 20 from the bottom,0 columns of pixels from the left of the image,0 from the right
  model.add(Cropping2D(cropping=((70,25), (0,0)), input_shape=(160,320,3)))
  model.add(Lambda(lambda x: (x / 255.0) - 0.5))#normalize the data
  model.add(Conv2D(24, (5, 5), subsample = (2,2)))
  model.add(Activation('relu'))
  model.add(Conv2D(36, (5, 5), subsample = (2,2)))
  model.add(Activation('relu'))
  model.add(Conv2D(48, (5, 5), subsample = (2,2)))
  model.add(Activation('relu'))
  model.add(Conv2D(64, (3, 3)))
  model.add(Activation('relu'))
  model.add(Conv2D(64, (3, 3)))
  model.add(Activation('relu'))
  model.add(Flatten())
  model.add(Dense(100))
  model.add(Dense(50))
  model.add(Dense(10))
  model.add(Dense(1))
  
  
  model.compile(loss = 'mse', optimizer = 'adam')
  history_object = model.fit(X_train, y_train, validation_split = 0.2, shuffle= True, nb_epoch = 3, verbose=1)
  
  model.save('model.h5')
  print(model.summary())
  plot_model(model, to_file='model_plot.png', show_shapes=True, show_layer_names=True)
  
  ### for visualizing loss
  ### print the keys contained in the history object
  #print(history_object.history.keys())
  
  ### plot the training and validation loss for each epoch
  #plt.plot(history_object.history['loss'])
  #plt.plot(history_object.history['val_loss'])
  #plt.title('model mean squared error loss')
  #plt.ylabel('mean squared error loss')
  #plt.xlabel('epoch')
  #plt.legend(['training set', 'validation set'], loc='upper right')
  #plt.show()
  